diff options
Diffstat (limited to 'mailnews/mime')
152 files changed, 37537 insertions, 0 deletions
diff --git a/mailnews/mime/cthandlers/glue/mimexpcom.cpp b/mailnews/mime/cthandlers/glue/mimexpcom.cpp new file mode 100644 index 000000000..094f61e37 --- /dev/null +++ b/mailnews/mime/cthandlers/glue/mimexpcom.cpp @@ -0,0 +1,132 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsIComponentManager.h" +#include "nsIMimeObjectClassAccess.h" +#include "nsMsgMimeCID.h" +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +static NS_DEFINE_CID(kMimeObjectClassAccessCID, NS_MIME_OBJECT_CLASS_ACCESS_CID); + +/* + * These calls are necessary to expose the object class hierarchy + * to externally developed content type handlers. + */ +extern "C" void * +COM_GetmimeInlineTextClass(void) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr<nsIMimeObjectClassAccess> objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->GetmimeInlineTextClass(&ptr); + + return ptr; +} + +extern "C" void * +COM_GetmimeLeafClass(void) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr<nsIMimeObjectClassAccess> objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->GetmimeLeafClass(&ptr); + + return ptr; +} + +extern "C" void * +COM_GetmimeObjectClass(void) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr<nsIMimeObjectClassAccess> objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->GetmimeObjectClass(&ptr); + + return ptr; +} + +extern "C" void * +COM_GetmimeContainerClass(void) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr<nsIMimeObjectClassAccess> objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->GetmimeContainerClass(&ptr); + + return ptr; +} + +extern "C" void * +COM_GetmimeMultipartClass(void) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr<nsIMimeObjectClassAccess> objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->GetmimeMultipartClass(&ptr); + + return ptr; +} + +extern "C" void * +COM_GetmimeMultipartSignedClass(void) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr<nsIMimeObjectClassAccess> objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->GetmimeMultipartSignedClass(&ptr); + + return ptr; +} + +extern "C" int +COM_MimeObject_write(void *mimeObject, char *data, int32_t length, + bool user_visible_p) +{ + int32_t rc = -1; + + nsresult res; + nsCOMPtr<nsIMimeObjectClassAccess> objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + { + if (NS_SUCCEEDED(objAccess->MimeObjectWrite(mimeObject, data, length, user_visible_p))) + rc = length; + else + rc = -1; + } + + return rc; +} + +extern "C" void * +COM_MimeCreate(char * content_type, void * hdrs, void * opts) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr<nsIMimeObjectClassAccess> objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->MimeCreate(content_type, hdrs, opts, &ptr); + + return ptr; +} diff --git a/mailnews/mime/cthandlers/glue/mimexpcom.h b/mailnews/mime/cthandlers/glue/mimexpcom.h new file mode 100644 index 000000000..9468b22b4 --- /dev/null +++ b/mailnews/mime/cthandlers/glue/mimexpcom.h @@ -0,0 +1,93 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This is the definitions for the Content Type Handler plugins to + * access internals of libmime via XP-COM calls + */ +#ifndef _MIMEXPCOM_H_ +#define _MIMEXPCOM_H_ + +/* + This header exposes functions that are necessary to access the + object hierarchy for the mime chart. The class hierarchy is: + + MimeObject (abstract) + | + |--- MimeContainer (abstract) + | | + | |--- MimeMultipart (abstract) + | | | + | | |--- MimeMultipartMixed + | | | + | | |--- MimeMultipartDigest + | | | + | | |--- MimeMultipartParallel + | | | + | | |--- MimeMultipartAlternative + | | | + | | |--- MimeMultipartRelated + | | | + | | |--- MimeMultipartAppleDouble + | | | + | | |--- MimeSunAttachment + | | | + | | |--- MimeMultipartSigned (abstract) + | | | + | | |--- MimeMultipartSigned + | | + | |--- MimeXlateed (abstract) + | | | + | | |--- MimeXlateed + | | + | |--- MimeMessage + | | + | |--- MimeUntypedText + | + |--- MimeLeaf (abstract) + | | + | |--- MimeInlineText (abstract) + | | | + | | |--- MimeInlineTextPlain + | | | + | | |--- MimeInlineTextHTML + | | | + | | |--- MimeInlineTextRichtext + | | | | + | | | |--- MimeInlineTextEnriched + | | | + | | |--- MimeInlineTextVCard + | | + | |--- MimeInlineImage + | | + | |--- MimeExternalObject + | + |--- MimeExternalBody + */ + +/* + * These functions are exposed by libmime to be used by content type + * handler plugins for processing stream data. + */ +/* + * This is the write call for outputting processed stream data. + */ +extern "C" int COM_MimeObject_write(void *mimeObject, const char *data, + int32_t length, + bool user_visible_p); +/* + * The following group of calls expose the pointers for the object + * system within libmime. + */ +extern "C" void *COM_GetmimeInlineTextClass(void); +extern "C" void *COM_GetmimeLeafClass(void); +extern "C" void *COM_GetmimeObjectClass(void); +extern "C" void *COM_GetmimeContainerClass(void); +extern "C" void *COM_GetmimeMultipartClass(void); +extern "C" void *COM_GetmimeMultipartSignedClass(void); + +extern "C" void *COM_MimeCreate(char * content_type, void * hdrs, void * opts); + +#endif /* _MIMEXPCOM_H_ */ diff --git a/mailnews/mime/cthandlers/glue/moz.build b/mailnews/mime/cthandlers/glue/moz.build new file mode 100644 index 000000000..f51518ca9 --- /dev/null +++ b/mailnews/mime/cthandlers/glue/moz.build @@ -0,0 +1,18 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsMimeContentTypeHandler.h', +] + +SOURCES += [ + 'mimexpcom.cpp', + 'nsMimeContentTypeHandler.cpp', +] + +FINAL_LIBRARY = 'mail' + +Library('mimecthglue_s') + diff --git a/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.cpp b/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.cpp new file mode 100644 index 000000000..369f8c4bf --- /dev/null +++ b/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <stdio.h> +#include "nscore.h" +#include "plstr.h" +//#include "mimecth.h" +#include "nsMimeContentTypeHandler.h" + +/* + * The following macros actually implement addref, release and + * query interface for our component. + */ +NS_IMPL_ISUPPORTS(nsMimeContentTypeHandler, nsIMimeContentTypeHandler) + +/* + * nsIMimeEmitter definitions.... + */ +nsMimeContentTypeHandler::nsMimeContentTypeHandler(const char *aMimeType, + MCTHCreateCTHClass callback) +{ + NS_ASSERTION(aMimeType, "nsMimeContentTypeHandler should be initialized with non-null mime type"); + NS_ASSERTION(callback, "nsMimeContentTypeHandler should be initialized with non-null callback"); + mimeType = PL_strdup(aMimeType); + realCreateContentTypeHandlerClass = callback; +} + +nsMimeContentTypeHandler::~nsMimeContentTypeHandler(void) +{ + if (mimeType) { + NS_Free(mimeType); + mimeType = 0; + } + realCreateContentTypeHandlerClass = 0; +} + +// Get the content type if necessary +nsresult +nsMimeContentTypeHandler::GetContentType(char **contentType) +{ + *contentType = PL_strdup(mimeType); + return NS_OK; +} + +// Set the output stream for processed data. +nsresult +nsMimeContentTypeHandler::CreateContentTypeHandlerClass(const char *content_type, + contentTypeHandlerInitStruct *initStruct, + MimeObjectClass **objClass) +{ + *objClass = realCreateContentTypeHandlerClass(content_type, initStruct); + if (!*objClass) + return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */ + else + return NS_OK; +} + + + diff --git a/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.h b/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.h new file mode 100644 index 000000000..8cc142268 --- /dev/null +++ b/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.h @@ -0,0 +1,47 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This interface is implemented by content type handlers that will be + * called upon by libmime to process various attachments types. The primary + * purpose of these handlers will be to represent the attached data in a + * viewable HTML format that is useful for the user + * + * Note: These will all register by their content type prefixed by the + * following: mimecth:text/vcard + * + * libmime will then use the XPCOM Component Manager to + * locate the appropriate Content Type handler + */ +#ifndef nsMimeContentTypeHandler_h_ +#define nsMimeContentTypeHandler_h_ + +#include "mozilla/Attributes.h" +#include "nsIMimeContentTypeHandler.h" + +typedef MimeObjectClass * +(* MCTHCreateCTHClass)(const char *content_type, + contentTypeHandlerInitStruct *initStruct); + +class nsMimeContentTypeHandler : public nsIMimeContentTypeHandler { +public: + nsMimeContentTypeHandler (const char *aMimeType, + MCTHCreateCTHClass callback); + + /* this macro defines QueryInterface, AddRef and Release for this class */ + NS_DECL_ISUPPORTS + + NS_IMETHOD GetContentType(char **contentType) override; + + NS_IMETHOD CreateContentTypeHandlerClass(const char *content_type, + contentTypeHandlerInitStruct *initStruct, + MimeObjectClass **objClass) override; + private: + virtual ~nsMimeContentTypeHandler(); + char *mimeType; + MCTHCreateCTHClass realCreateContentTypeHandlerClass; +}; + +#endif /* nsMimeContentTypeHandler_h_ */ diff --git a/mailnews/mime/cthandlers/moz.build b/mailnews/mime/cthandlers/moz.build new file mode 100644 index 000000000..31807499d --- /dev/null +++ b/mailnews/mime/cthandlers/moz.build @@ -0,0 +1,12 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# pgpmime depends on glue. +DIRS += [ + 'glue', + 'vcard', + 'pgpmime', +] + diff --git a/mailnews/mime/cthandlers/pgpmime/moz.build b/mailnews/mime/cthandlers/pgpmime/moz.build new file mode 100644 index 000000000..878bd5bfa --- /dev/null +++ b/mailnews/mime/cthandlers/pgpmime/moz.build @@ -0,0 +1,20 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsPgpMimeProxy.h', +] + +SOURCES += [ + 'nsPgpMimeProxy.cpp', +] + +FINAL_LIBRARY = 'mail' + +Library('pgpmime_s') + +LOCAL_INCLUDES += [ + '../glue', +] diff --git a/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.cpp b/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.cpp new file mode 100644 index 000000000..de4ec3174 --- /dev/null +++ b/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.cpp @@ -0,0 +1,634 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPgpMimeProxy.h" +#include "nspr.h" +#include "plstr.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "mozilla/Services.h" +#include "nsIRequest.h" +#include "nsIStringBundle.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIURI.h" +#include "mimexpcom.h" +#include "nsMsgUtils.h" + +#include "nsMsgMimeCID.h" + +#include "mimecth.h" +#include "mimemoz2.h" +#include "nspr.h" +#include "plstr.h" +#include "nsIPgpMimeProxy.h" +#include "nsComponentManagerUtils.h" + +#define MIME_SUPERCLASS mimeEncryptedClass +MimeDefClass(MimeEncryptedPgp, MimeEncryptedPgpClass, + mimeEncryptedPgpClass, &MIME_SUPERCLASS); + +#define kCharMax 1024 + +extern "C" MimeObjectClass * +MIME_PgpMimeCreateContentTypeHandlerClass( + const char *content_type, + contentTypeHandlerInitStruct *initStruct) +{ + MimeObjectClass *objClass = (MimeObjectClass *) &mimeEncryptedPgpClass; + + initStruct->force_inline_display = false; + + return objClass; +} + +static void *MimePgpe_init(MimeObject *, + int (*output_fn) (const char *, int32_t, void *), + void *); +static int MimePgpe_write (const char *, int32_t, void *); +static int MimePgpe_eof (void *, bool); +static char* MimePgpe_generate (void *); +static void MimePgpe_free (void *); + +/* Returns a string describing the location of the part (like "2.5.3"). + This is not a full URL, just a part-number. + */ +static nsCString determineMimePart(MimeObject* obj); + + +#define PGPMIME_PROPERTIES_URL "chrome://messenger/locale/pgpmime.properties" +#define PGPMIME_STR_NOT_SUPPORTED_ID u"pgpMimeNeedsAddon" +#define PGPMIME_URL_PREF "mail.pgpmime.addon_url" + +static void PgpMimeGetNeedsAddonString(nsCString &aResult) +{ + aResult.AssignLiteral("???"); + + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + + nsCOMPtr<nsIStringBundle> stringBundle; + nsresult rv = stringBundleService->CreateBundle(PGPMIME_PROPERTIES_URL, + getter_AddRefs(stringBundle)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + nsCString url; + if (NS_FAILED(prefs->GetCharPref("mail.pgpmime.addon_url", + getter_Copies(url)))) + return; + + NS_ConvertUTF8toUTF16 url16(url); + const char16_t *formatStrings[] = { url16.get() }; + + nsString result; + rv = stringBundle->FormatStringFromName(PGPMIME_STR_NOT_SUPPORTED_ID, + formatStrings, 1, getter_Copies(result)); + if (NS_FAILED(rv)) + return; + aResult = NS_ConvertUTF16toUTF8(result); +} + +static int +MimeEncryptedPgpClassInitialize(MimeEncryptedPgpClass *clazz) +{ + mozilla::DebugOnly<MimeObjectClass *> oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "oclass is not initialized"); + + MimeEncryptedClass *eclass = (MimeEncryptedClass *) clazz; + + eclass->crypto_init = MimePgpe_init; + eclass->crypto_write = MimePgpe_write; + eclass->crypto_eof = MimePgpe_eof; + eclass->crypto_generate_html = MimePgpe_generate; + eclass->crypto_free = MimePgpe_free; + + return 0; +} + +class MimePgpeData : public nsISupports +{ +public: + NS_DECL_ISUPPORTS + + int (*output_fn) (const char *buf, int32_t buf_size, void *output_closure); + void *output_closure; + MimeObject *self; + + nsCOMPtr<nsIPgpMimeProxy> mimeDecrypt; + + MimePgpeData() + : output_fn(nullptr), + output_closure(nullptr) + { + } + +private: + virtual ~MimePgpeData() + { + } +}; + +NS_IMPL_ISUPPORTS0(MimePgpeData) + +static void* +MimePgpe_init(MimeObject *obj, + int (*output_fn) (const char *buf, int32_t buf_size, + void *output_closure), + void *output_closure) +{ + if (!(obj && obj->options && output_fn)) + return nullptr; + + MimePgpeData* data = new MimePgpeData(); + NS_ENSURE_TRUE(data, nullptr); + + data->self = obj; + data->output_fn = output_fn; + data->output_closure = output_closure; + data->mimeDecrypt = nullptr; + + nsresult rv; + data->mimeDecrypt = do_CreateInstance(NS_PGPMIMEPROXY_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return data; + + char *ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, false, false); + + rv = (ct ? data->mimeDecrypt->SetContentType(nsDependentCString(ct)) + : data->mimeDecrypt->SetContentType(EmptyCString())); + + PR_Free(ct); + + if (NS_FAILED(rv)) + return nullptr; + + nsCString mimePart = determineMimePart(obj); + + rv = data->mimeDecrypt->SetMimePart(mimePart); + if (NS_FAILED(rv)) + return nullptr; + + mime_stream_data *msd = (mime_stream_data *) (data->self->options->stream_closure); + nsIChannel *channel = msd->channel; + + nsCOMPtr<nsIURI> uri; + if (channel) + channel->GetURI(getter_AddRefs(uri)); + + if (NS_FAILED(data->mimeDecrypt->SetMimeCallback(output_fn, output_closure, uri))) + return nullptr; + + return data; +} + +static int +MimePgpe_write(const char *buf, int32_t buf_size, void *output_closure) +{ + MimePgpeData* data = (MimePgpeData *) output_closure; + + if (!data || !data->output_fn) + return -1; + + if (!data->mimeDecrypt) + return 0; + + return (NS_SUCCEEDED(data->mimeDecrypt->Write(buf, buf_size)) ? 0 : -1); +} + +static int +MimePgpe_eof(void* output_closure, bool abort_p) +{ + MimePgpeData* data = (MimePgpeData *) output_closure; + + if (!data || !data->output_fn) + return -1; + + if (NS_FAILED(data->mimeDecrypt->Finish())) + return -1; + + data->mimeDecrypt = nullptr; + return 0; +} + +static char* +MimePgpe_generate(void *output_closure) +{ + const char htmlMsg[] = "<html><body><b>GEN MSG<b></body></html>"; + char* msg = (char *) PR_MALLOC(strlen(htmlMsg) + 1); + if (msg) + PL_strcpy(msg, htmlMsg); + + return msg; +} + +static void +MimePgpe_free(void *output_closure) +{ +} + +/* Returns a string describing the location of the part (like "2.5.3"). + This is not a full URL, just a part-number. + */ +static nsCString +determineMimePart(MimeObject* obj) +{ + char mimePartNum[20]; + MimeObject *kid; + MimeContainer *cont; + int32_t i; + + nsCString mimePart; + + while (obj->parent) { + cont = (MimeContainer *) obj->parent; + for (i = 0; i < cont->nchildren; i++) { + kid = cont->children[i]; + if (kid == obj) { + sprintf(mimePartNum, ".%d", i + 1); + mimePart.Insert(mimePartNum, 0); + } + } + obj = obj->parent; + } + + // remove leading "." + if (mimePart.Length() > 0) + mimePart.Cut(0, 1); + + return mimePart; +} + + +//////////////////////////////////////////////////////////////////////////// +NS_IMPL_ISUPPORTS(nsPgpMimeProxy, + nsIPgpMimeProxy, + nsIRequestObserver, + nsIStreamListener, + nsIRequest, + nsIInputStream) + +// nsPgpMimeProxy implementation +nsPgpMimeProxy::nsPgpMimeProxy() + : mInitialized(false), + mDecryptor(nullptr), + mLoadGroup(nullptr), + mLoadFlags(LOAD_NORMAL), + mCancelStatus(NS_OK) +{ +} + +nsPgpMimeProxy::~nsPgpMimeProxy() +{ + Finalize(); +} + +nsresult +nsPgpMimeProxy::Finalize() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::SetMimeCallback(MimeDecodeCallbackFun outputFun, + void* outputClosure, + nsIURI* myUri) +{ + if (!outputFun || !outputClosure) + return NS_ERROR_NULL_POINTER; + + mOutputFun = outputFun; + mOutputClosure = outputClosure; + mInitialized = true; + + mStreamOffset = 0; + mByteBuf.Truncate(); + + if (mDecryptor) + return mDecryptor->OnStartRequest((nsIRequest*) this, myUri); + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Init() +{ + mByteBuf.Truncate(); + + nsresult rv; + nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + mDecryptor = do_CreateInstance(PGPMIME_JS_DECRYPTOR_CONTRACTID, &rv); + if (NS_FAILED(rv)) + mDecryptor = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Write(const char *buf, uint32_t buf_size) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + mByteBuf.Assign(buf, buf_size); + mStreamOffset = 0; + + if (mDecryptor) + return mDecryptor->OnDataAvailable((nsIRequest*) this, nullptr, (nsIInputStream*) this, + 0, buf_size); + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Finish() { + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + if (mDecryptor) { + return mDecryptor->OnStopRequest((nsIRequest*) this, nullptr, NS_OK); + } + else { + nsCString temp; + temp.Append("Content-Type: text/html\r\nCharset: UTF-8\r\n\r\n<html><body>"); + temp.Append("<BR><text=\"#000000\" bgcolor=\"#FFFFFF\" link=\"#FF0000\" vlink=\"#800080\" alink=\"#0000FF\">"); + temp.Append("<center><table BORDER=1 ><tr><td><CENTER>"); + + nsCString tString; + PgpMimeGetNeedsAddonString(tString); + temp.Append(tString); + temp.Append("</CENTER></td></tr></table></center><BR></body></html>\r\n"); + + PR_SetError(0,0); + int status = mOutputFun(temp.get(), temp.Length(), mOutputClosure); + if (status < 0) { + PR_SetError(status, 0); + mOutputFun = nullptr; + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::GetDecryptor(nsIStreamListener **aDecryptor) +{ + NS_IF_ADDREF(*aDecryptor = mDecryptor); + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::SetDecryptor(nsIStreamListener *aDecryptor) +{ + mDecryptor = aDecryptor; + + return NS_OK; +} + + +NS_IMETHODIMP +nsPgpMimeProxy::GetContentType(nsACString &aContentType) +{ + aContentType = mContentType; + return NS_OK; +} + + +NS_IMETHODIMP +nsPgpMimeProxy::SetContentType(const nsACString &aContentType) +{ + mContentType = aContentType; + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::GetMimePart(nsACString &aMimePart) +{ + aMimePart = mMimePart; + return NS_OK; +} + + +NS_IMETHODIMP +nsPgpMimeProxy::SetMimePart(const nsACString &aMimePart) +{ + mMimePart = aMimePart; + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIRequest methods +/////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsPgpMimeProxy::GetName(nsACString &result) +{ + result = "pgpmimeproxy"; + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::IsPending(bool *result) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + *result = NS_SUCCEEDED(mCancelStatus); + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::GetStatus(nsresult *status) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + *status = mCancelStatus; + return NS_OK; +} + +// NOTE: We assume that OnStopRequest should not be called if +// request is canceled. This may be wrong! +NS_IMETHODIMP +nsPgpMimeProxy::Cancel(nsresult status) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + // Need a non-zero status code to cancel + if (NS_SUCCEEDED(status)) + return NS_ERROR_FAILURE; + + if (NS_SUCCEEDED(mCancelStatus)) + mCancelStatus = status; + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Suspend(void) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Resume(void) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPgpMimeProxy::GetLoadGroup(nsILoadGroup * *aLoadGroup) +{ + NS_IF_ADDREF(*aLoadGroup = mLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::SetLoadGroup(nsILoadGroup* aLoadGroup) +{ + mLoadGroup = aLoadGroup; + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::GetLoadFlags(nsLoadFlags *aLoadFlags) +{ + *aLoadFlags = mLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::SetLoadFlags(nsLoadFlags aLoadFlags) +{ + mLoadFlags = aLoadFlags; + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIInputStream methods +/////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsPgpMimeProxy::Available(uint64_t* _retval) +{ + NS_ENSURE_ARG(_retval); + + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + *_retval = (mByteBuf.Length() > mStreamOffset) ? + mByteBuf.Length() - mStreamOffset : 0; + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Read(char* buf, uint32_t count, + uint32_t *readCount) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + if (!buf || !readCount) + return NS_ERROR_NULL_POINTER; + + int32_t avail = (mByteBuf.Length() > mStreamOffset) ? + mByteBuf.Length() - mStreamOffset : 0; + + uint32_t readyCount = ((uint32_t) avail > count) ? count : avail; + + if (readyCount) { + memcpy(buf, mByteBuf.get()+mStreamOffset, readyCount); + *readCount = readyCount; + } + + mStreamOffset += *readCount; + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::ReadSegments(nsWriteSegmentFun writer, + void * aClosure, uint32_t count, + uint32_t *readCount) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPgpMimeProxy::IsNonBlocking(bool *aNonBlocking) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + *aNonBlocking = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Close() +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + mStreamOffset = 0; + mByteBuf.Truncate(); + + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIStreamListener methods +/////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsPgpMimeProxy::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, + nsresult aStatus) +{ + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIStreamListener method +/////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsPgpMimeProxy::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, + nsIInputStream *aInputStream, + uint64_t aSourceOffset, + uint32_t aLength) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + NS_ENSURE_ARG(aInputStream); + + char buf[kCharMax]; + uint32_t readCount, readMax; + + while (aLength > 0) { + readMax = (aLength < kCharMax) ? aLength : kCharMax; + + nsresult rv; + rv = aInputStream->Read((char *) buf, readMax, &readCount); + NS_ENSURE_SUCCESS(rv, rv); + + int status = mOutputFun(buf, readCount, mOutputClosure); + if (status < 0) { + PR_SetError(status, 0); + mOutputFun = nullptr; + return NS_ERROR_FAILURE; + } + + aLength -= readCount; + } + + return NS_OK; +} diff --git a/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.h b/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.h new file mode 100644 index 000000000..41e7f59cc --- /dev/null +++ b/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.h @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _nsPgpmimeDecrypt_h_ +#define _nsPgpmimeDecrypt_h_ + +#include "mimecth.h" +#include "nsIPgpMimeProxy.h" +#include "nsCOMPtr.h" +#include "nsIStreamListener.h" +#include "nsIInputStream.h" +#include "nsILoadGroup.h" + +#define PGPMIME_JS_DECRYPTOR_CONTRACTID "@mozilla.org/mime/pgp-mime-js-decrypt;1" + +typedef struct MimeEncryptedPgpClass MimeEncryptedPgpClass; +typedef struct MimeEncryptedPgp MimeEncryptedPgp; + +struct MimeEncryptedPgpClass { + MimeEncryptedClass encrypted; +}; + +struct MimeEncryptedPgp { + MimeEncrypted encrypted; +}; + +class nsPgpMimeProxy : public nsIPgpMimeProxy, + public nsIRequest, + public nsIInputStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPGPMIMEPROXY + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUEST + NS_DECL_NSIINPUTSTREAM + + nsPgpMimeProxy(); + + // Define a Create method to be used with a factory: + static NS_METHOD + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +protected: + virtual ~nsPgpMimeProxy(); + bool mInitialized; + nsCOMPtr<nsIStreamListener> mDecryptor; + + MimeDecodeCallbackFun mOutputFun; + void* mOutputClosure; + + nsCOMPtr<nsILoadGroup> mLoadGroup; + nsLoadFlags mLoadFlags; + nsresult mCancelStatus; + + uint32_t mStreamOffset; + nsCString mByteBuf; + nsCString mContentType; + nsCString mMimePart; + + nsresult Finalize(); +}; + +#define MimeEncryptedPgpClassInitializer(ITYPE,CSUPER) \ + { MimeEncryptedClassInitializer(ITYPE,CSUPER) } + +#endif diff --git a/mailnews/mime/cthandlers/vcard/mimevcrd.cpp b/mailnews/mime/cthandlers/vcard/mimevcrd.cpp new file mode 100644 index 000000000..140afb5aa --- /dev/null +++ b/mailnews/mime/cthandlers/vcard/mimevcrd.cpp @@ -0,0 +1,378 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimevcrd.h" + +#include "mimecth.h" +#include "mimexpcom.h" +#include "nsIMsgVCardService.h" +#include "nsINetUtil.h" +#include "nsMsgUtils.h" +#include "prmem.h" +#include "prprf.h" +#include "nsServiceManagerUtils.h" + +static int MimeInlineTextVCard_parse_line (const char *, int32_t, MimeObject *); +static int MimeInlineTextVCard_parse_eof (MimeObject *, bool); +static int MimeInlineTextVCard_parse_begin (MimeObject *obj); + +static int s_unique = 0; + +static int BeginVCard (MimeObject *obj); +static int EndVCard (MimeObject *obj); +static int WriteOutVCard (MimeObject *obj, VObject* v); + +static int GenerateVCardData(MimeObject * aMimeObj, VObject* aVcard); +static int OutputVcardAttribute(MimeObject *aMimeObj, VObject *aVcard, const char* id, nsACString& vCardOutput); +static int OutputBasicVcard(MimeObject *aMimeObj, VObject *aVcard, nsACString& vCardOutput); + +typedef struct + { + const char *attributeName; + int resourceId; + } AttributeName; + +#define kNumAttributes 12 + +#define MSGVCARDSERVICE_CONTRACT_ID "@mozilla.org/addressbook/msgvcardservice;1" + +/* This is the object definition. Note: we will set the superclass + to NULL and manually set this on the class creation */ +MimeDefClass(MimeInlineTextVCard, MimeInlineTextVCardClass, + mimeInlineTextVCardClass, NULL); + +extern "C" MimeObjectClass * +MIME_VCardCreateContentTypeHandlerClass(const char *content_type, + contentTypeHandlerInitStruct *initStruct) +{ + MimeObjectClass *clazz = (MimeObjectClass *)&mimeInlineTextVCardClass; + /* + * Must set the superclass by hand. + */ + if (!COM_GetmimeInlineTextClass()) + return NULL; + + clazz->superclass = (MimeObjectClass *)COM_GetmimeInlineTextClass(); + initStruct->force_inline_display = true; + return clazz; +} + +/* + * Implementation of VCard clazz + */ +static int +MimeInlineTextVCardClassInitialize(MimeInlineTextVCardClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:11"); + oclass->parse_begin = MimeInlineTextVCard_parse_begin; + oclass->parse_line = MimeInlineTextVCard_parse_line; + oclass->parse_eof = MimeInlineTextVCard_parse_eof; + return 0; +} + +static int +MimeInlineTextVCard_parse_begin (MimeObject *obj) +{ + int status = ((MimeObjectClass*)COM_GetmimeLeafClass())->parse_begin(obj); + MimeInlineTextVCardClass *clazz; + if (status < 0) return status; + + if (!obj->output_p) return 0; + if (!obj->options || !obj->options->write_html_p) return 0; + + /* This is a fine place to write out any HTML before the real meat begins. + In this sample code, we tell it to start a table. */ + + clazz = ((MimeInlineTextVCardClass *) obj->clazz); + /* initialize vcard string to empty; */ + NS_MsgSACopy(&(clazz->vCardString), ""); + + obj->options->state->separator_suppressed_p = true; + return 0; +} + +char *strcpySafe (char *dest, const char *src, size_t destLength) +{ + char *result = strncpy (dest, src, --destLength); + dest[destLength] = '\0'; + return result; +} + +static int +MimeInlineTextVCard_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + // This routine gets fed each line of data, one at a time. + char* linestring; + MimeInlineTextVCardClass *clazz = ((MimeInlineTextVCardClass *) obj->clazz); + + if (!obj->output_p) return 0; + if (!obj->options || !obj->options->output_fn) return 0; + if (!obj->options->write_html_p) + { + return COM_MimeObject_write(obj, line, length, true); + } + + linestring = (char *) PR_MALLOC (length + 1); + memset(linestring, 0, (length + 1)); + + if (linestring) + { + strcpySafe((char *)linestring, line, length + 1); + NS_MsgSACat (&clazz->vCardString, linestring); + PR_Free (linestring); + } + + return 0; +} + + +//////////////////////////////////////////////////////////////////////////////// +static int +MimeInlineTextVCard_parse_eof (MimeObject *obj, bool abort_p) +{ + nsCOMPtr<nsIMsgVCardService> vCardService = + do_GetService(MSGVCARDSERVICE_CONTRACT_ID); + if (!vCardService) + return -1; + + int status = 0; + MimeInlineTextVCardClass *clazz = ((MimeInlineTextVCardClass *) obj->clazz); + + VObject *t, *v; + + if (obj->closed_p) return 0; + + /* Run parent method first, to flush out any buffered data. */ + // status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + status = ((MimeObjectClass*)COM_GetmimeInlineTextClass())->parse_eof(obj, abort_p); + if (status < 0) return status; + + // Don't quote vCards... + if ( (obj->options) && + ((obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting) || + (obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting)) + ) + return 0; + + if (!clazz->vCardString) return 0; + + v = vCardService->Parse_MIME(clazz->vCardString, strlen(clazz->vCardString)); + NS_ASSERTION(v, "parse of vCard failed"); + + if (clazz->vCardString) { + PR_Free ((char*) clazz->vCardString); + clazz->vCardString = NULL; + } + + if (obj->output_p && obj->options && obj->options->write_html_p && + obj->options->headers != MimeHeadersCitation) { + /* This is a fine place to write any closing HTML. In fact, you may + want all the writing to be here, and all of the above would just + collect data into datastructures, though that isn't very + "streaming". */ + t = v; + while (v && status >= 0) { + /* write out html */ + status = WriteOutVCard (obj, v); + /* parse next vcard incase they're embedded */ + v = vCardService->NextVObjectInList(v); + } + + (void)vCardService->CleanVObject(t); + } + + if (status < 0) + return status; + + return 0; +} + +static int EndVCard (MimeObject *obj) +{ + int status = 0; + + /* Scribble HTML-ending stuff into the stream */ + char htmlFooters[32]; + PR_snprintf (htmlFooters, sizeof(htmlFooters), "</BODY>%s</HTML>%s", MSG_LINEBREAK, MSG_LINEBREAK); + status = COM_MimeObject_write(obj, htmlFooters, strlen(htmlFooters), false); + + if (status < 0) return status; + + return 0; +} + +static int BeginVCard (MimeObject *obj) +{ + int status = 0; + + /* Scribble HTML-starting stuff into the stream */ + char htmlHeaders[32]; + + s_unique++; + PR_snprintf (htmlHeaders, sizeof(htmlHeaders), "<HTML>%s<BODY>%s", MSG_LINEBREAK, MSG_LINEBREAK); + status = COM_MimeObject_write(obj, htmlHeaders, strlen(htmlHeaders), true); + + if (status < 0) return status; + + return 0; +} + + +static int WriteOutVCard (MimeObject * aMimeObj, VObject* aVcard) +{ + BeginVCard (aMimeObj); + + GenerateVCardData(aMimeObj, aVcard); + + return EndVCard (aMimeObj); +} + + +static int GenerateVCardData(MimeObject * aMimeObj, VObject* aVcard) +{ + // style is driven from CSS not here. Just layout the minimal vCard data + nsCString vCardOutput; + + vCardOutput = "<table class=\"moz-vcard-table\"> <tr> "; // outer table plus the first (and only row) we use for this table + + // we need to get an escaped vCard url to bind to our add to address book button + nsCOMPtr<nsIMsgVCardService> vCardService = do_GetService(MSGVCARDSERVICE_CONTRACT_ID); + if (!vCardService) + return -1; + + nsAutoCString vCard; + nsAutoCString vEscCard; + int len = 0; + + vCard.Adopt(vCardService->WriteMemoryVObjects(0, &len, aVcard, false)); + MsgEscapeString(vCard, nsINetUtil::ESCAPE_XALPHAS, vEscCard); + + // first cell in the outer table row is a clickable image which brings up the rich address book UI for the vcard + vCardOutput += "<td valign=\"top\"> <a class=\"moz-vcard-badge\" href=\"addbook:add?action=add?vcard="; + vCardOutput += vEscCard; // the href is the vCard + vCardOutput += "\"></a></td>"; + + // the 2nd cell in the outer table row is a nested table containing the actual vCard properties + vCardOutput += "<td> <table id=\"moz-vcard-properties-table\"> <tr> "; + + OutputBasicVcard(aMimeObj, aVcard, vCardOutput); + + // close the properties table + vCardOutput += "</table> </td> "; + + // 2nd cell in the outer table is our vCard image + + vCardOutput += "</tr> </table>"; + + // now write out the vCard + return COM_MimeObject_write(aMimeObj, (char *) vCardOutput.get(), vCardOutput.Length(), true); +} + + +static int OutputBasicVcard(MimeObject *aMimeObj, VObject *aVcard, nsACString& vCardOutput) +{ + VObject *prop = NULL; + nsAutoCString urlstring; + nsAutoCString namestring; + nsAutoCString emailstring; + + nsCOMPtr<nsIMsgVCardService> vCardService = do_GetService(MSGVCARDSERVICE_CONTRACT_ID); + if (!vCardService) + return -1; + + /* get the name and email */ + prop = vCardService->IsAPropertyOf(aVcard, VCFullNameProp); + if (prop) + { + if (VALUE_TYPE(prop)) + { + if (VALUE_TYPE(prop) != VCVT_RAW) + namestring.Adopt(vCardService->FakeCString(prop)); + else + namestring.Adopt(vCardService->VObjectAnyValue(prop)); + + if (!namestring.IsEmpty()) + { + vCardOutput += "<td class=\"moz-vcard-title-property\"> "; + + prop = vCardService->IsAPropertyOf(aVcard, VCURLProp); + if (prop) + { + urlstring.Adopt(vCardService->FakeCString(prop)); + if (urlstring.IsEmpty()) + vCardOutput += namestring; + else + { + char buf[512]; + PR_snprintf(buf, 512, "<a href=""%s"" private>%s</a>", urlstring.get(), namestring.get()); + vCardOutput.Append(buf); + } + } + else + vCardOutput += namestring; + + /* get the email address */ + prop = vCardService->IsAPropertyOf(aVcard, VCEmailAddressProp); + if (prop) + { + emailstring.Adopt(vCardService->FakeCString(prop)); + if (!emailstring.IsEmpty()) + { + char buf[512]; + PR_snprintf(buf, 512, " <<a href=""mailto:%s"" private>%s</a>>", emailstring.get(), emailstring.get()); + vCardOutput.Append(buf); + } + } // if email address property + + vCardOutput += "</td> </tr> "; // end the cell for the name/email address + } // if we have a name property + } + } // if full name property + + // now each basic property goes on its own line + + // title + (void) OutputVcardAttribute (aMimeObj, aVcard, VCTitleProp, vCardOutput); + + // org name and company name + prop = vCardService->IsAPropertyOf(aVcard, VCOrgProp); + if (prop) + { + OutputVcardAttribute (aMimeObj, prop, VCOrgUnitProp, vCardOutput); + OutputVcardAttribute (aMimeObj, prop, VCOrgNameProp, vCardOutput); + } + + return 0; +} + +static int OutputVcardAttribute(MimeObject *aMimeObj, VObject *aVcard, const char* id, nsACString& vCardOutput) +{ + VObject *prop = NULL; + nsAutoCString string; + + nsCOMPtr<nsIMsgVCardService> vCardService = do_GetService(MSGVCARDSERVICE_CONTRACT_ID); + if (!vCardService) + return -1; + + prop = vCardService->IsAPropertyOf(aVcard, id); + if (prop) + if (VALUE_TYPE(prop)) + { + if (VALUE_TYPE(prop) != VCVT_RAW) + string.Adopt(vCardService->FakeCString(prop)); + else + string.Adopt(vCardService->VObjectAnyValue(prop)); + + if (!string.IsEmpty()) + { + vCardOutput += "<tr> <td class=\"moz-vcard-property\">"; + vCardOutput += string; + vCardOutput += "</td> </tr> "; + } + } + + return 0; +} diff --git a/mailnews/mime/cthandlers/vcard/mimevcrd.h b/mailnews/mime/cthandlers/vcard/mimevcrd.h new file mode 100644 index 000000000..6e731f555 --- /dev/null +++ b/mailnews/mime/cthandlers/vcard/mimevcrd.h @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEVCRD_H_ +#define _MIMEVCRD_H_ + +#include "mimetext.h" +#include "nsCOMPtr.h" + +/* The MimeInlineTextHTML class implements the text/x-vcard and (maybe? + someday?) the application/directory MIME content types. + */ + +typedef struct MimeInlineTextVCardClass MimeInlineTextVCardClass; +typedef struct MimeInlineTextVCard MimeInlineTextVCard; + +struct MimeInlineTextVCardClass { + MimeInlineTextClass text; + char *vCardString; +}; + +extern MimeInlineTextVCardClass mimeInlineTextVCardClass; + +struct MimeInlineTextVCard { + MimeInlineText text; +}; + +#define MimeInlineTextVCardClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEVCRD_H_ */ diff --git a/mailnews/mime/cthandlers/vcard/moz.build b/mailnews/mime/cthandlers/vcard/moz.build new file mode 100644 index 000000000..55de22391 --- /dev/null +++ b/mailnews/mime/cthandlers/vcard/moz.build @@ -0,0 +1,14 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'mimevcrd.cpp', +] + +FINAL_LIBRARY = 'mail' + +LOCAL_INCLUDES += [ + '../glue', +] diff --git a/mailnews/mime/emitters/moz.build b/mailnews/mime/emitters/moz.build new file mode 100644 index 000000000..b1e3390bd --- /dev/null +++ b/mailnews/mime/emitters/moz.build @@ -0,0 +1,21 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsMimeEmitterCID.h', +] + +SOURCES += [ + 'nsEmitterUtils.cpp', + 'nsMimeBaseEmitter.cpp', + 'nsMimeHtmlEmitter.cpp', + 'nsMimePlainEmitter.cpp', + 'nsMimeRawEmitter.cpp', + 'nsMimeRebuffer.cpp', + 'nsMimeXmlEmitter.cpp', +] + +FINAL_LIBRARY = 'mail' + diff --git a/mailnews/mime/emitters/nsEmitterUtils.cpp b/mailnews/mime/emitters/nsEmitterUtils.cpp new file mode 100644 index 000000000..551bf5b31 --- /dev/null +++ b/mailnews/mime/emitters/nsEmitterUtils.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "prmem.h" +#include "plstr.h" +#include "nsMailHeaders.h" +#include "nsIMimeEmitter.h" +#include "nsIStringBundle.h" +#include "nsIServiceManager.h" +#include "nsIIOService.h" +#include "nsIURI.h" +#include "prprf.h" + + +extern "C" bool +EmitThisHeaderForPrefSetting(int32_t dispType, const char *header) +{ + if (nsMimeHeaderDisplayTypes::AllHeaders == dispType) + return true; + + if ((!header) || (!*header)) + return false; + + if (nsMimeHeaderDisplayTypes::MicroHeaders == dispType) + { + if ( + (!strcmp(header, HEADER_SUBJECT)) || + (!strcmp(header, HEADER_FROM)) || + (!strcmp(header, HEADER_DATE)) + ) + return true; + else + return false; + } + + if (nsMimeHeaderDisplayTypes::NormalHeaders == dispType) + { + if ( + (!strcmp(header, HEADER_DATE)) || + (!strcmp(header, HEADER_TO)) || + (!strcmp(header, HEADER_SUBJECT)) || + (!strcmp(header, HEADER_SENDER)) || + (!strcmp(header, HEADER_RESENT_TO)) || + (!strcmp(header, HEADER_RESENT_SENDER)) || + (!strcmp(header, HEADER_RESENT_FROM)) || + (!strcmp(header, HEADER_RESENT_CC)) || + (!strcmp(header, HEADER_REPLY_TO)) || + (!strcmp(header, HEADER_REFERENCES)) || + (!strcmp(header, HEADER_NEWSGROUPS)) || + (!strcmp(header, HEADER_MESSAGE_ID)) || + (!strcmp(header, HEADER_FROM)) || + (!strcmp(header, HEADER_FOLLOWUP_TO)) || + (!strcmp(header, HEADER_CC)) || + (!strcmp(header, HEADER_ORGANIZATION)) || + (!strcmp(header, HEADER_REPLY_TO)) || + (!strcmp(header, HEADER_BCC)) + ) + return true; + else + return false; + } + + return true; +} + diff --git a/mailnews/mime/emitters/nsEmitterUtils.h b/mailnews/mime/emitters/nsEmitterUtils.h new file mode 100644 index 000000000..965c1e33c --- /dev/null +++ b/mailnews/mime/emitters/nsEmitterUtils.h @@ -0,0 +1,14 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsEmitterUtils_h_ +#define _nsEmitterUtils_h_ + +#include "prmem.h" +#include "plstr.h" + +extern "C" bool EmitThisHeaderForPrefSetting(int32_t dispType, const char *header); + +#endif // _nsEmitterUtils_h_ + diff --git a/mailnews/mime/emitters/nsMimeBaseEmitter.cpp b/mailnews/mime/emitters/nsMimeBaseEmitter.cpp new file mode 100644 index 000000000..223eef433 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeBaseEmitter.cpp @@ -0,0 +1,1092 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include <stdio.h> +#include "nsMimeBaseEmitter.h" +#include "nsMailHeaders.h" +#include "nscore.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIServiceManager.h" +#include "prmem.h" +#include "nsEmitterUtils.h" +#include "nsMimeStringResources.h" +#include "msgCore.h" +#include "nsIComponentManager.h" +#include "nsEmitterUtils.h" +#include "nsIMimeStreamConverter.h" +#include "nsIMimeConverter.h" +#include "nsMsgMimeCID.h" +#include "mozilla/Logging.h" +#include "prprf.h" +#include "nsIMimeHeaders.h" +#include "nsIMsgWindow.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsDateTimeFormatCID.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" +#include "nsTextFormatter.h" +#include "mozilla/Services.h" +#include <algorithm> + +static PRLogModuleInfo * gMimeEmitterLogModule = nullptr; + +#define MIME_HEADER_URL "chrome://messenger/locale/mimeheader.properties" +#define MIME_URL "chrome://messenger/locale/mime.properties" + +NS_IMPL_ISUPPORTS(nsMimeBaseEmitter, nsIMimeEmitter, nsIInterfaceRequestor) + +nsMimeBaseEmitter::nsMimeBaseEmitter() +{ + // Initialize data output vars... + mFirstHeaders = true; + + mBufferMgr = nullptr; + mTotalWritten = 0; + mTotalRead = 0; + mInputStream = nullptr; + mOutStream = nullptr; + mOutListener = nullptr; + + // Display output control vars... + mDocHeader = false; + m_stringBundle = nullptr; + mURL = nullptr; + mHeaderDisplayType = nsMimeHeaderDisplayTypes::NormalHeaders; + + // Setup array for attachments + mAttachCount = 0; + mAttachArray = new nsTArray<attachmentInfoType*>(); + mCurrentAttachment = nullptr; + + // Header cache... + mHeaderArray = new nsTArray<headerInfoType*>(); + + // Embedded Header Cache... + mEmbeddedHeaderArray = nullptr; + + // HTML Header Data... +// mHTMLHeaders = ""; +// mCharset = ""; + + // Init the body... + mBodyStarted = false; +// mBody = ""; + + // This is needed for conversion of I18N Strings... + mUnicodeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID); + + if (!gMimeEmitterLogModule) + gMimeEmitterLogModule = PR_NewLogModule("MIME"); + + // Do prefs last since we can live without this if it fails... + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (pPrefBranch) + pPrefBranch->GetIntPref("mail.show_headers", &mHeaderDisplayType); +} + +nsMimeBaseEmitter::~nsMimeBaseEmitter(void) +{ + // Delete the buffer manager... + if (mBufferMgr) + { + delete mBufferMgr; + mBufferMgr = nullptr; + } + + // Clean up the attachment array structures... + if (mAttachArray) + { + for (size_t i = 0; i < mAttachArray->Length(); i++) + { + attachmentInfoType *attachInfo = mAttachArray->ElementAt(i); + if (!attachInfo) + continue; + + PR_FREEIF(attachInfo->contentType); + if (attachInfo->displayName) + NS_Free(attachInfo->displayName); + PR_FREEIF(attachInfo->urlSpec); + PR_FREEIF(attachInfo); + } + delete mAttachArray; + } + + // Cleanup allocated header arrays... + CleanupHeaderArray(mHeaderArray); + mHeaderArray = nullptr; + + CleanupHeaderArray(mEmbeddedHeaderArray); + mEmbeddedHeaderArray = nullptr; +} + +NS_IMETHODIMP nsMimeBaseEmitter::GetInterface(const nsIID & aIID, void * *aInstancePtr) +{ + NS_ENSURE_ARG_POINTER(aInstancePtr); + return QueryInterface(aIID, aInstancePtr); +} + +void +nsMimeBaseEmitter::CleanupHeaderArray(nsTArray<headerInfoType*> *aArray) +{ + if (!aArray) + return; + + for (size_t i = 0; i < aArray->Length(); i++) + { + headerInfoType *headerInfo = aArray->ElementAt(i); + if (!headerInfo) + continue; + + PR_FREEIF(headerInfo->name); + PR_FREEIF(headerInfo->value); + PR_FREEIF(headerInfo); + } + + delete aArray; +} + +static int32_t MapHeaderNameToID(const char *header) +{ + // emitter passes UPPERCASE for header names + if (!strcmp(header, "DATE")) + return MIME_MHTML_DATE; + else if (!strcmp(header, "FROM")) + return MIME_MHTML_FROM; + else if (!strcmp(header, "SUBJECT")) + return MIME_MHTML_SUBJECT; + else if (!strcmp(header, "TO")) + return MIME_MHTML_TO; + else if (!strcmp(header, "SENDER")) + return MIME_MHTML_SENDER; + else if (!strcmp(header, "RESENT-TO")) + return MIME_MHTML_RESENT_TO; + else if (!strcmp(header, "RESENT-SENDER")) + return MIME_MHTML_RESENT_SENDER; + else if (!strcmp(header, "RESENT-FROM")) + return MIME_MHTML_RESENT_FROM; + else if (!strcmp(header, "RESENT-CC")) + return MIME_MHTML_RESENT_CC; + else if (!strcmp(header, "REPLY-TO")) + return MIME_MHTML_REPLY_TO; + else if (!strcmp(header, "REFERENCES")) + return MIME_MHTML_REFERENCES; + else if (!strcmp(header, "NEWSGROUPS")) + return MIME_MHTML_NEWSGROUPS; + else if (!strcmp(header, "MESSAGE-ID")) + return MIME_MHTML_MESSAGE_ID; + else if (!strcmp(header, "FOLLOWUP-TO")) + return MIME_MHTML_FOLLOWUP_TO; + else if (!strcmp(header, "CC")) + return MIME_MHTML_CC; + else if (!strcmp(header, "ORGANIZATION")) + return MIME_MHTML_ORGANIZATION; + else if (!strcmp(header, "BCC")) + return MIME_MHTML_BCC; + + return 0; +} + +char * +nsMimeBaseEmitter::MimeGetStringByName(const char *aHeaderName) +{ + nsresult res = NS_OK; + + if (!m_headerStringBundle) + { + static const char propertyURL[] = MIME_HEADER_URL; + + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + if (sBundleService) + { + res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(m_headerStringBundle)); + } + } + + if (m_headerStringBundle) + { + nsString val; + + res = m_headerStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aHeaderName).get(), + getter_Copies(val)); + + if (NS_FAILED(res)) + return nullptr; + + // Here we need to return a new copy of the string + // This returns a UTF-8 string so the caller needs to perform a conversion + // if this is used as UCS-2 (e.g. cannot do nsString(utfStr); + // + return ToNewUTF8String(val); + } + else + { + return nullptr; + } +} + +char * +nsMimeBaseEmitter::MimeGetStringByID(int32_t aID) +{ + nsresult res = NS_OK; + + if (!m_stringBundle) + { + static const char propertyURL[] = MIME_URL; + + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::services::GetStringBundleService(); + if (sBundleService) + res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(m_stringBundle)); + } + + if (m_stringBundle) + { + nsString val; + + res = m_stringBundle->GetStringFromID(aID, getter_Copies(val)); + + if (NS_FAILED(res)) + return nullptr; + + return ToNewUTF8String(val); + } + else + return nullptr; +} + +// +// This will search a string bundle (eventually) to find a descriptive header +// name to match what was found in the mail message. aHeaderName is passed in +// in all caps and a dropback default name is provided. The caller needs to free +// the memory returned by this function. +// +char * +nsMimeBaseEmitter::LocalizeHeaderName(const char *aHeaderName, const char *aDefaultName) +{ + char *retVal = nullptr; + + // prefer to use translated strings if not for quoting + if (mFormat != nsMimeOutput::nsMimeMessageQuoting && + mFormat != nsMimeOutput::nsMimeMessageBodyQuoting) + { + // map name to id and get the translated string + int32_t id = MapHeaderNameToID(aHeaderName); + if (id > 0) + retVal = MimeGetStringByID(id); + } + + // get the string from the other bundle (usually not translated) + if (!retVal) + retVal = MimeGetStringByName(aHeaderName); + + if (retVal) + return retVal; + else + return strdup(aDefaultName); +} + +/////////////////////////////////////////////////////////////////////////// +// nsMimeBaseEmitter Interface +/////////////////////////////////////////////////////////////////////////// +NS_IMETHODIMP +nsMimeBaseEmitter::SetPipe(nsIInputStream * aInputStream, nsIOutputStream *outStream) +{ + mInputStream = aInputStream; + mOutStream = outStream; + return NS_OK; +} + +// Note - these is setup only...you should not write +// anything to the stream since these may be image data +// output streams, etc... +NS_IMETHODIMP +nsMimeBaseEmitter::Initialize(nsIURI *url, nsIChannel * aChannel, int32_t aFormat) +{ + // set the url + mURL = url; + mChannel = aChannel; + + // Create rebuffering object + delete mBufferMgr; + mBufferMgr = new MimeRebuffer(); + + // Counters for output stream + mTotalWritten = 0; + mTotalRead = 0; + mFormat = aFormat; + + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::SetOutputListener(nsIStreamListener *listener) +{ + mOutListener = listener; + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::GetOutputListener(nsIStreamListener **listener) +{ + NS_ENSURE_ARG_POINTER(listener); + + NS_IF_ADDREF(*listener = mOutListener); + return NS_OK; +} + + +// Attachment handling routines +nsresult +nsMimeBaseEmitter::StartAttachment(const nsACString &name, + const char *contentType, + const char *url, + bool aIsExternalAttachment) +{ + // Ok, now we will setup the attachment info + mCurrentAttachment = (attachmentInfoType *) PR_NEWZAP(attachmentInfoType); + if ( (mCurrentAttachment) && mAttachArray) + { + ++mAttachCount; + + mCurrentAttachment->displayName = ToNewCString(name); + mCurrentAttachment->urlSpec = strdup(url); + mCurrentAttachment->contentType = strdup(contentType); + mCurrentAttachment->isExternalAttachment = aIsExternalAttachment; + } + + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::EndAttachment() +{ + // Ok, add the attachment info to the attachment array... + if ( (mCurrentAttachment) && (mAttachArray) ) + { + mAttachArray->AppendElement(mCurrentAttachment); + mCurrentAttachment = nullptr; + } + + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::EndAllAttachments() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::AddAttachmentField(const char *field, const char *value) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::UtilityWrite(const char *buf) +{ + NS_ENSURE_ARG_POINTER(buf); + + uint32_t written; + Write(nsDependentCString(buf), &written); + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::UtilityWrite(const nsACString &buf) +{ + uint32_t written; + Write(buf, &written); + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::UtilityWriteCRLF(const char *buf) +{ + NS_ENSURE_ARG_POINTER(buf); + + uint32_t written; + Write(nsDependentCString(buf), &written); + Write(NS_LITERAL_CSTRING(CRLF), &written); + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::Write(const nsACString &buf, uint32_t *amountWritten) +{ + unsigned int written = 0; + nsresult rv = NS_OK; + uint32_t needToWrite; + +#ifdef DEBUG_BenB + // If you want to see libmime output... + printf("%s", buf); +#endif + + MOZ_LOG(gMimeEmitterLogModule, mozilla::LogLevel::Info, ("%s", PromiseFlatCString(buf).get())); + // + // Make sure that the buffer we are "pushing" into has enough room + // for the write operation. If not, we have to buffer, return, and get + // it on the next time through + // + *amountWritten = 0; + + needToWrite = mBufferMgr->GetSize(); + // First, handle any old buffer data... + if (needToWrite > 0) + { + rv = WriteHelper(mBufferMgr->GetBuffer(), &written); + + mTotalWritten += written; + mBufferMgr->ReduceBuffer(written); + *amountWritten = written; + + // if we couldn't write all the old data, buffer the new data + // and return + if (mBufferMgr->GetSize() > 0) + { + mBufferMgr->IncreaseBuffer(buf); + return rv; + } + } + + + // if we get here, we are dealing with new data...try to write + // and then do the right thing... + rv = WriteHelper(buf, &written); + *amountWritten = written; + mTotalWritten += written; + + if (written < buf.Length()) { + const nsACString &remainder = Substring(buf, written); + mBufferMgr->IncreaseBuffer(remainder); + } + + return rv; +} + +nsresult +nsMimeBaseEmitter::WriteHelper(const nsACString &buf, uint32_t *countWritten) +{ + NS_ENSURE_TRUE(mOutStream, NS_ERROR_NOT_INITIALIZED); + + nsresult rv = mOutStream->Write(buf.BeginReading(), buf.Length(), countWritten); + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + // pipe is full, push contents of pipe to listener... + uint64_t avail; + rv = mInputStream->Available(&avail); + if (NS_SUCCEEDED(rv) && avail) { + mOutListener->OnDataAvailable(mChannel, mURL, mInputStream, 0, + std::min(avail, uint64_t(PR_UINT32_MAX))); + + // try writing again... + rv = mOutStream->Write(buf.BeginReading(), buf.Length(), countWritten); + } + } + return rv; +} + +// +// Find a cached header! Note: Do NOT free this value! +// +const char * +nsMimeBaseEmitter::GetHeaderValue(const char *aHeaderName) +{ + char *retVal = nullptr; + nsTArray<headerInfoType*> *array = mDocHeader? mHeaderArray : mEmbeddedHeaderArray; + + if (!array) + return nullptr; + + for (size_t i = 0; i < array->Length(); i++) + { + headerInfoType *headerInfo = array->ElementAt(i); + if ( (!headerInfo) || (!headerInfo->name) || (!(*headerInfo->name)) ) + continue; + + if (!PL_strcasecmp(aHeaderName, headerInfo->name)) + { + retVal = headerInfo->value; + break; + } + } + + return retVal; +} + +// +// This is called at the start of the header block for all header information in ANY +// AND ALL MESSAGES (yes, quoted, attached, etc...) +// +// NOTE: This will be called even when headers are will not follow. This is +// to allow us to be notified of the charset of the original message. This is +// important for forward and reply operations +// +NS_IMETHODIMP +nsMimeBaseEmitter::StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID, + const char *outCharset) +{ + NS_ENSURE_ARG_POINTER(outCharset); + + mDocHeader = rootMailHeader; + + // If this is not the mail messages header, then we need to create + // the mEmbeddedHeaderArray structure for use with this internal header + // structure. + if (!mDocHeader) + { + if (mEmbeddedHeaderArray) + CleanupHeaderArray(mEmbeddedHeaderArray); + + mEmbeddedHeaderArray = new nsTArray<headerInfoType*>(); + NS_ENSURE_TRUE(mEmbeddedHeaderArray, NS_ERROR_OUT_OF_MEMORY); + } + + // If the main doc, check on updated character set + if (mDocHeader) + UpdateCharacterSet(outCharset); + CopyASCIItoUTF16(nsDependentCString(outCharset), mCharset); + return NS_OK; +} + +// Ok, if we are here, and we have a aCharset passed in that is not +// UTF-8 or US-ASCII, then we should tag the mChannel member with this +// charset. This is because replying to messages with specified charset's +// need to be tagged as that charset by default. +// +NS_IMETHODIMP +nsMimeBaseEmitter::UpdateCharacterSet(const char *aCharset) +{ + if (aCharset) + { + nsAutoCString contentType; + + if (NS_SUCCEEDED(mChannel->GetContentType(contentType)) && !contentType.IsEmpty()) + { + char *cBegin = contentType.BeginWriting(); + + const char *cPtr = PL_strcasestr(cBegin, "charset="); + + if (cPtr) + { + char *ptr = cBegin; + while (*ptr) + { + if ( (*ptr == ' ') || (*ptr == ';') ) + { + if ((ptr + 1) >= cPtr) + { + *ptr = '\0'; + break; + } + } + + ++ptr; + } + } + + // have to set content-type since it could have an embedded null byte + mChannel->SetContentType(nsDependentCString(cBegin)); + if (PL_strcasecmp(aCharset, "US-ASCII") == 0) { + mChannel->SetContentCharset(NS_LITERAL_CSTRING("ISO-8859-1")); + } else { + mChannel->SetContentCharset(nsDependentCString(aCharset)); + } + } + } + + return NS_OK; +} + +// +// This will be called for every header field regardless if it is in an +// internal body or the outer message. +// +NS_IMETHODIMP +nsMimeBaseEmitter::AddHeaderField(const char *field, const char *value) +{ + if ( (!field) || (!value) ) + return NS_OK; + + nsTArray<headerInfoType*> *tPtr; + if (mDocHeader) + tPtr = mHeaderArray; + else + tPtr = mEmbeddedHeaderArray; + + // This is a header so we need to cache and output later. + // Ok, now we will setup the header info for the header array! + headerInfoType *ptr = (headerInfoType *) PR_NEWZAP(headerInfoType); + if ( (ptr) && tPtr) + { + ptr->name = strdup(field); + ptr->value = strdup(value); + tPtr->AppendElement(ptr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::AddAllHeaders(const nsACString &allheaders) +{ + if (mDocHeader) //We want to set only the main headers of a message, not the potentially embedded one + { + nsresult rv; + nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(mURL)); + if (msgurl) + { + nsCOMPtr<nsIMimeHeaders> mimeHeaders = do_CreateInstance(NS_IMIMEHEADERS_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mimeHeaders->Initialize(allheaders); + msgurl->SetMimeHeaders(mimeHeaders); + } + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// The following code is responsible for formatting headers in a manner that is +// identical to the normal XUL output. +//////////////////////////////////////////////////////////////////////////////// + +nsresult +nsMimeBaseEmitter::GenerateDateString(const char * dateString, + nsACString &formattedDate, + bool showDateForToday) +{ + nsresult rv = NS_OK; + + if (!mDateFormatter) { + mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + /** + * See if the user wants to have the date displayed in the senders + * timezone (including the timezone offset). + * We also evaluate the pref original_date which was introduced + * as makeshift in bug 118899. + */ + bool displaySenderTimezone = false; + bool displayOriginalDate = false; + + nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> dateFormatPrefs; + rv = prefs->GetBranch("mailnews.display.", getter_AddRefs(dateFormatPrefs)); + NS_ENSURE_SUCCESS(rv, rv); + + dateFormatPrefs->GetBoolPref("date_senders_timezone", &displaySenderTimezone); + dateFormatPrefs->GetBoolPref("original_date", &displayOriginalDate); + // migrate old pref to date_senders_timezone + if (displayOriginalDate && !displaySenderTimezone) + dateFormatPrefs->SetBoolPref("date_senders_timezone", true); + + PRExplodedTime explodedMsgTime; + + // Bogus date string may leave some fields uninitialized, so take precaution. + memset(&explodedMsgTime, 0, sizeof (PRExplodedTime)); + + if (PR_ParseTimeStringToExplodedTime(dateString, false, &explodedMsgTime) != PR_SUCCESS) + return NS_ERROR_FAILURE; + + /** + * To determine the date format to use, comparison of current and message + * time has to be made. If displaying in local time, both timestamps have + * to be in local time. If displaying in senders time zone, leave the compare + * time in that time zone. + * Otherwise in TZ+0100 on 2009-03-12 a message from 2009-03-11T20:49-0700 + * would be displayed as "20:49 -0700" though it in fact is not from the + * same day. + */ + PRExplodedTime explodedCompTime; + if (displaySenderTimezone) + explodedCompTime = explodedMsgTime; + else + PR_ExplodeTime(PR_ImplodeTime(&explodedMsgTime), PR_LocalTimeParameters, &explodedCompTime); + + PRExplodedTime explodedCurrentTime; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &explodedCurrentTime); + + // If we want short dates, check if the message is from today, and if so + // only show the time (e.g. 3:15 pm). + nsDateFormatSelector dateFormat = kDateFormatShort; + if (!showDateForToday && + explodedCurrentTime.tm_year == explodedCompTime.tm_year && + explodedCurrentTime.tm_month == explodedCompTime.tm_month && + explodedCurrentTime.tm_mday == explodedCompTime.tm_mday) + { + // same day... + dateFormat = kDateFormatNone; + } + + nsAutoString formattedDateString; + + rv = mDateFormatter->FormatPRExplodedTime(nullptr /* nsILocale* locale */, + dateFormat, + kTimeFormatNoSeconds, + &explodedCompTime, + formattedDateString); + + if (NS_SUCCEEDED(rv)) + { + if (displaySenderTimezone) + { + // offset of local time from UTC in minutes + int32_t senderoffset = (explodedMsgTime.tm_params.tp_gmt_offset + + explodedMsgTime.tm_params.tp_dst_offset) / 60; + // append offset to date string + char16_t *tzstring = + nsTextFormatter::smprintf(u" %+05d", + (senderoffset / 60 * 100) + + (senderoffset % 60)); + formattedDateString.Append(tzstring); + nsTextFormatter::smprintf_free(tzstring); + } + + CopyUTF16toUTF8(formattedDateString, formattedDate); + } + + return rv; +} + +char* +nsMimeBaseEmitter::GetLocalizedDateString(const char * dateString) +{ + char *i18nValue = nullptr; + + bool displayOriginalDate = false; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + + if (prefBranch) + prefBranch->GetBoolPref("mailnews.display.original_date", + &displayOriginalDate); + + if (!displayOriginalDate) + { + nsAutoCString convertedDateString; + nsresult rv = GenerateDateString(dateString, convertedDateString, true); + if (NS_SUCCEEDED(rv)) + i18nValue = strdup(convertedDateString.get()); + else + i18nValue = strdup(dateString); + } + else + i18nValue = strdup(dateString); + + return i18nValue; +} + +nsresult +nsMimeBaseEmitter::WriteHeaderFieldHTML(const char *field, const char *value) +{ + char *newValue = nullptr; + char *i18nValue = nullptr; + + if ( (!field) || (!value) ) + return NS_OK; + + // + // This is a check to see what the pref is for header display. If + // We should only output stuff that corresponds with that setting. + // + if (!EmitThisHeaderForPrefSetting(mHeaderDisplayType, field)) + return NS_OK; + + // + // If we encounter the 'Date' header we try to convert it's value + // into localized format. + // + if ( strcmp(field, "Date") == 0 ) + i18nValue = GetLocalizedDateString(value); + else + i18nValue = strdup(value); + + if ( (mUnicodeConverter) && (mFormat != nsMimeOutput::nsMimeMessageSaveAs) ) + { + nsCString tValue; + + // we're going to need a converter to convert + nsresult rv = mUnicodeConverter->DecodeMimeHeaderToUTF8( + nsDependentCString(i18nValue), nullptr, false, true, tValue); + if (NS_SUCCEEDED(rv) && !tValue.IsEmpty()) + newValue = MsgEscapeHTML(tValue.get()); + else + newValue = MsgEscapeHTML(i18nValue); + } + else + { + newValue = MsgEscapeHTML(i18nValue); + } + + free(i18nValue); + + if (!newValue) + return NS_OK; + + mHTMLHeaders.Append("<tr>"); + mHTMLHeaders.Append("<td>"); + + if (mFormat == nsMimeOutput::nsMimeMessageSaveAs) + mHTMLHeaders.Append("<b>"); + else + mHTMLHeaders.Append("<div class=\"headerdisplayname\" style=\"display:inline;\">"); + + // Here is where we are going to try to L10N the tagName so we will always + // get a field name next to an emitted header value. Note: Default will always + // be the name of the header itself. + // + nsCString newTagName(field); + newTagName.StripWhitespace(); + ToUpperCase(newTagName); + + char *l10nTagName = LocalizeHeaderName(newTagName.get(), field); + if ( (!l10nTagName) || (!*l10nTagName) ) + mHTMLHeaders.Append(field); + else + { + mHTMLHeaders.Append(l10nTagName); + PR_FREEIF(l10nTagName); + } + + mHTMLHeaders.Append(": "); + if (mFormat == nsMimeOutput::nsMimeMessageSaveAs) + mHTMLHeaders.Append("</b>"); + else + mHTMLHeaders.Append("</div>"); + + // Now write out the actual value itself and move on! + // + mHTMLHeaders.Append(newValue); + mHTMLHeaders.Append("</td>"); + + mHTMLHeaders.Append("</tr>"); + + PR_FREEIF(newValue); + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::WriteHeaderFieldHTMLPrefix(const nsACString &name) +{ + if ( + ( (mFormat == nsMimeOutput::nsMimeMessageSaveAs) && (mFirstHeaders) ) || + ( (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) && (mFirstHeaders) ) + ) + /* DO NOTHING */ ; // rhp: Do nothing...leaving the conditional like this so its + // easier to see the logic of what is going on. + else { + mHTMLHeaders.Append("<br><fieldset class=\"mimeAttachmentHeader\">"); + if (!name.IsEmpty()) { + mHTMLHeaders.Append("<legend class=\"mimeAttachmentHeaderName\">"); + nsCString escapedName; + escapedName.Adopt(MsgEscapeHTML(nsCString(name).get())); + mHTMLHeaders.Append(escapedName); + mHTMLHeaders.Append("</legend>"); + } + mHTMLHeaders.Append("</fieldset>"); + } + + mFirstHeaders = false; + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::WriteHeaderFieldHTMLPostfix() +{ + mHTMLHeaders.Append("<br>"); + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::WriteHTMLHeaders(const nsACString &name) +{ + WriteHeaderFieldHTMLPrefix(name); + + // Start with the subject, from date info! + DumpSubjectFromDate(); + + // Continue with the to and cc headers + DumpToCC(); + + // Do the rest of the headers, but these will only be written if + // the user has the "show all headers" pref set + if (mHeaderDisplayType == nsMimeHeaderDisplayTypes::AllHeaders) + DumpRestOfHeaders(); + + WriteHeaderFieldHTMLPostfix(); + + // Now, we need to either append the headers we built up to the + // overall body or output to the stream. + UtilityWriteCRLF(mHTMLHeaders.get()); + + mHTMLHeaders = ""; + + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::DumpSubjectFromDate() +{ + mHTMLHeaders.Append("<table border=0 cellspacing=0 cellpadding=0 width=\"100%\" class=\"header-part1\">"); + + // This is the envelope information + OutputGenericHeader(HEADER_SUBJECT); + OutputGenericHeader(HEADER_FROM); + OutputGenericHeader(HEADER_DATE); + + // If we are Quoting a message, then we should dump the To: also + if ( ( mFormat == nsMimeOutput::nsMimeMessageQuoting ) || + ( mFormat == nsMimeOutput::nsMimeMessageBodyQuoting ) ) + OutputGenericHeader(HEADER_TO); + + mHTMLHeaders.Append("</table>"); + + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::DumpToCC() +{ + const char * toField = GetHeaderValue(HEADER_TO); + const char * ccField = GetHeaderValue(HEADER_CC); + const char * bccField = GetHeaderValue(HEADER_BCC); + const char * newsgroupField = GetHeaderValue(HEADER_NEWSGROUPS); + + // only dump these fields if we have at least one of them! When displaying news + // messages that didn't have a To or Cc field, we'd always get an empty box + // which looked weird. + if (toField || ccField || bccField || newsgroupField) + { + mHTMLHeaders.Append("<table border=0 cellspacing=0 cellpadding=0 width=\"100%\" class=\"header-part2\">"); + + if (toField) + WriteHeaderFieldHTML(HEADER_TO, toField); + if (ccField) + WriteHeaderFieldHTML(HEADER_CC, ccField); + if (bccField) + WriteHeaderFieldHTML(HEADER_BCC, bccField); + if (newsgroupField) + WriteHeaderFieldHTML(HEADER_NEWSGROUPS, newsgroupField); + + mHTMLHeaders.Append("</table>"); + } + + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::DumpRestOfHeaders() +{ + nsTArray<headerInfoType*> *array = mDocHeader? mHeaderArray : mEmbeddedHeaderArray; + + mHTMLHeaders.Append("<table border=0 cellspacing=0 cellpadding=0 width=\"100%\" class=\"header-part3\">"); + + for (size_t i = 0; i < array->Length(); i++) + { + headerInfoType *headerInfo = array->ElementAt(i); + if ( (!headerInfo) || (!headerInfo->name) || (!(*headerInfo->name)) || + (!headerInfo->value) || (!(*headerInfo->value))) + continue; + + if ( (!PL_strcasecmp(HEADER_SUBJECT, headerInfo->name)) || + (!PL_strcasecmp(HEADER_DATE, headerInfo->name)) || + (!PL_strcasecmp(HEADER_FROM, headerInfo->name)) || + (!PL_strcasecmp(HEADER_TO, headerInfo->name)) || + (!PL_strcasecmp(HEADER_CC, headerInfo->name)) ) + continue; + + WriteHeaderFieldHTML(headerInfo->name, headerInfo->value); + } + + mHTMLHeaders.Append("</table>"); + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::OutputGenericHeader(const char *aHeaderVal) +{ + const char *val = GetHeaderValue(aHeaderVal); + + if (val) + return WriteHeaderFieldHTML(aHeaderVal, val); + + return NS_ERROR_FAILURE; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +// These are the methods that should be implemented by the child class! +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +// +// This should be implemented by the child class if special processing +// needs to be done when the entire message is read. +// +NS_IMETHODIMP +nsMimeBaseEmitter::Complete() +{ + // If we are here and still have data to write, we should try + // to flush it...if we try and fail, we should probably return + // an error! + uint32_t written; + + nsresult rv = NS_OK; + while ( NS_SUCCEEDED(rv) && (mBufferMgr) && (mBufferMgr->GetSize() > 0)) + rv = Write(EmptyCString(), &written); + + if (mOutListener) + { + uint64_t bytesInStream = 0; + mozilla::DebugOnly<nsresult> rv2 = mInputStream->Available(&bytesInStream); + NS_ASSERTION(NS_SUCCEEDED(rv2), "Available failed"); + if (bytesInStream) + { + nsCOMPtr<nsIRequest> request = do_QueryInterface(mChannel); + mOutListener->OnDataAvailable(request, mURL, mInputStream, 0, std::min(bytesInStream, uint64_t(PR_UINT32_MAX))); + } + } + + return NS_OK; +} + +// +// This needs to do the right thing with the stored information. It only +// has to do the output functions, this base class will take care of the +// memory cleanup +// +NS_IMETHODIMP +nsMimeBaseEmitter::EndHeader(const nsACString &name) +{ + return NS_OK; +} + +// body handling routines +NS_IMETHODIMP +nsMimeBaseEmitter::StartBody(bool bodyOnly, const char *msgID, const char *outCharset) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::WriteBody(const nsACString &buf, uint32_t *amountWritten) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::EndBody() +{ + return NS_OK; +} diff --git a/mailnews/mime/emitters/nsMimeBaseEmitter.h b/mailnews/mime/emitters/nsMimeBaseEmitter.h new file mode 100644 index 000000000..c33bc2687 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeBaseEmitter.h @@ -0,0 +1,147 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsMimeBaseEmitter_h_ +#define _nsMimeBaseEmitter_h_ + +#include "prio.h" +#include "nsIMimeEmitter.h" +#include "nsMimeRebuffer.h" +#include "nsIStreamListener.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIAsyncInputStream.h" +#include "nsIURI.h" +#include "nsIChannel.h" +#include "nsIMimeMiscStatus.h" +#include "nsIPipe.h" +#include "nsIStringBundle.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsIMimeConverter.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIDateTimeFormat.h" + +// +// The base emitter will serve as the place to do all of the caching, +// sorting, etc... of mail headers and bodies for this internally developed +// emitter library. The other emitter classes in this file (nsMimeHTMLEmitter, etc.) +// will only be concerned with doing output processing ONLY. +// + +// +// Used for keeping track of the attachment information... +// +typedef struct { + char *displayName; + char *urlSpec; + char *contentType; + bool isExternalAttachment; +} attachmentInfoType; + +// +// For header info... +// +typedef struct { + char *name; + char *value; +} headerInfoType; + +class nsMimeBaseEmitter : public nsIMimeEmitter, + public nsIInterfaceRequestor +{ +public: + nsMimeBaseEmitter (); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIMIMEEMITTER + NS_DECL_NSIINTERFACEREQUESTOR + + // Utility output functions... + NS_IMETHOD UtilityWrite(const nsACString &buf); + NS_IMETHOD UtilityWriteCRLF(const char *buf); + + // For string bundle usage... + char *MimeGetStringByName(const char *aHeaderName); + char *MimeGetStringByID(int32_t aID); + char *LocalizeHeaderName(const char *aHeaderName, const char *aDefaultName); + + // For header processing... + const char *GetHeaderValue(const char *aHeaderName); + + // To write out a stored header array as HTML + virtual nsresult WriteHeaderFieldHTMLPrefix(const nsACString &name); + virtual nsresult WriteHeaderFieldHTML(const char *field, const char *value); + virtual nsresult WriteHeaderFieldHTMLPostfix(); + +protected: + virtual ~nsMimeBaseEmitter(); + // Internal methods... + void CleanupHeaderArray(nsTArray<headerInfoType*> *aArray); + + // For header output... + nsresult DumpSubjectFromDate(); + nsresult DumpToCC(); + nsresult DumpRestOfHeaders(); + nsresult OutputGenericHeader(const char *aHeaderVal); + + nsresult WriteHelper(const nsACString &buf, uint32_t *countWritten); + + // For string bundle usage... + nsCOMPtr<nsIStringBundle> m_stringBundle; // for translated strings + nsCOMPtr<nsIStringBundle> m_headerStringBundle; // for non-translated header strings + + // For buffer management on output + MimeRebuffer *mBufferMgr; + + // mscott + // don't ref count the streams....the emitter is owned by the converter + // which owns these streams... + // + nsIOutputStream *mOutStream; + nsIInputStream *mInputStream; + nsIStreamListener *mOutListener; + nsCOMPtr<nsIChannel> mChannel; + + // For gathering statistics on processing... + uint32_t mTotalWritten; + uint32_t mTotalRead; + + // Output control and info... + bool mDocHeader; // For header determination... + nsIURI *mURL; // the url for the data being processed... + int32_t mHeaderDisplayType; // The setting for header output... + nsCString mHTMLHeaders; // HTML Header Data... + + // For attachment processing... + int32_t mAttachCount; + nsTArray<attachmentInfoType*> *mAttachArray; + attachmentInfoType *mCurrentAttachment; + + // For header caching... + nsTArray<headerInfoType*> *mHeaderArray; + nsTArray<headerInfoType*> *mEmbeddedHeaderArray; + + // For body caching... + bool mBodyStarted; + nsCString mBody; + bool mFirstHeaders; + + // For the format being used... + int32_t mFormat; + + // For I18N Conversion... + nsCOMPtr<nsIMimeConverter> mUnicodeConverter; + nsString mCharset; + nsCOMPtr<nsIDateTimeFormat> mDateFormatter; + nsresult GenerateDateString(const char * dateString, nsACString& formattedDate, + bool showDateForToday); + // The caller is expected to free the result of GetLocalizedDateString + char* GetLocalizedDateString(const char * dateString); +}; + +#endif /* _nsMimeBaseEmitter_h_ */ diff --git a/mailnews/mime/emitters/nsMimeEmitterCID.h b/mailnews/mime/emitters/nsMimeEmitterCID.h new file mode 100644 index 000000000..f2e8c6039 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeEmitterCID.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsMimeEmitterCID_h__ +#define nsMimeEmitterCID_h__ + +#include "nsISupports.h" +#include "nsIFactory.h" +#include "nsIComponentManager.h" + +#define NS_MIME_EMITTER_CONTRACTID_PREFIX \ + "@mozilla.org/messenger/mimeemitter;1?type=" + +#define NS_HTML_MIME_EMITTER_CONTRACTID \ + NS_MIME_EMITTER_CONTRACTID_PREFIX "text/html" +// {F0A8AF16-DCCE-11d2-A411-00805F613C79} +#define NS_HTML_MIME_EMITTER_CID \ + { 0xf0a8af16, 0xdcce, 0x11d2, \ + { 0xa4, 0x11, 0x0, 0x80, 0x5f, 0x61, 0x3c, 0x79 } } + +#define NS_XML_MIME_EMITTER_CONTRACTID \ + NS_MIME_EMITTER_CONTRACTID_PREFIX "text/xml" +// {977E418F-E392-11d2-A2AC-00A024A7D144} +#define NS_XML_MIME_EMITTER_CID \ + { 0x977e418f, 0xe392, 0x11d2, \ + { 0xa2, 0xac, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } } + +#define NS_RAW_MIME_EMITTER_CONTRACTID \ + NS_MIME_EMITTER_CONTRACTID_PREFIX "raw" +// {F0A8AF16-DCFF-11d2-A411-00805F613C79} +#define NS_RAW_MIME_EMITTER_CID \ + { 0xf0a8af16, 0xdcff, 0x11d2, \ + { 0xa4, 0x11, 0x0, 0x80, 0x5f, 0x61, 0x3c, 0x79 } } + +#define NS_XUL_MIME_EMITTER_CONTRACTID \ + NS_MIME_EMITTER_CONTRACTID_PREFIX "application/vnd.mozilla.xul+xml" +// {FAA8AF16-DCFF-11d2-A411-00805F613C19} +#define NS_XUL_MIME_EMITTER_CID \ + { 0xfaa8af16, 0xdcff, 0x11d2, \ + { 0xa4, 0x11, 0x0, 0x80, 0x5f, 0x61, 0x3c, 0x19 } } + +#define NS_PLAIN_MIME_EMITTER_CONTRACTID \ + NS_MIME_EMITTER_CONTRACTID_PREFIX "text/plain" +// {E8892265-7653-46c5-A290-307F3404D0F3} +#define NS_PLAIN_MIME_EMITTER_CID \ + { 0xe8892265, 0x7653, 0x46c5, \ + { 0xa2, 0x90, 0x30, 0x7f, 0x34, 0x4, 0xd0, 0xf3 } } + +#endif // nsMimeEmitterCID_h__ diff --git a/mailnews/mime/emitters/nsMimeHtmlEmitter.cpp b/mailnews/mime/emitters/nsMimeHtmlEmitter.cpp new file mode 100644 index 000000000..d68d7f15c --- /dev/null +++ b/mailnews/mime/emitters/nsMimeHtmlEmitter.cpp @@ -0,0 +1,543 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include <stdio.h> +#include "nsMimeRebuffer.h" +#include "nsMimeHtmlEmitter.h" +#include "plstr.h" +#include "nsMailHeaders.h" +#include "nscore.h" +#include "nsEmitterUtils.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIMimeStreamConverter.h" +#include "nsIMsgWindow.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsMimeTypes.h" +#include "prtime.h" +#include "prprf.h" +#include "nsIStringEnumerator.h" +#include "nsServiceManagerUtils.h" +// hack: include this to fix opening news attachments. +#include "nsINntpUrl.h" +#include "nsComponentManagerUtils.h" +#include "nsIMimeConverter.h" +#include "nsMsgMimeCID.h" +#include "nsMsgUtils.h" +#include "nsAutoPtr.h" +#include "nsINetUtil.h" +#include "nsMemory.h" +#include "mozilla/Services.h" + +#define VIEW_ALL_HEADERS 2 + +/** + * A helper class to implement nsIUTF8StringEnumerator + */ + +class nsMimeStringEnumerator final : public nsIUTF8StringEnumerator { +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIUTF8STRINGENUMERATOR + + nsMimeStringEnumerator() : mCurrentIndex(0) {} + + template<class T> + nsCString* Append(T value) { return mValues.AppendElement(value); } + +protected: + ~nsMimeStringEnumerator() {} + nsTArray<nsCString> mValues; + uint32_t mCurrentIndex; // consumers expect first-in first-out enumeration +}; + +NS_IMPL_ISUPPORTS(nsMimeStringEnumerator, nsIUTF8StringEnumerator) + +NS_IMETHODIMP +nsMimeStringEnumerator::HasMore(bool *result) +{ + NS_ENSURE_ARG_POINTER(result); + *result = mCurrentIndex < mValues.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsMimeStringEnumerator::GetNext(nsACString& result) +{ + if (mCurrentIndex >= mValues.Length()) + return NS_ERROR_UNEXPECTED; + + result = mValues[mCurrentIndex++]; + return NS_OK; +} + +/* + * nsMimeHtmlEmitter definitions.... + */ +nsMimeHtmlDisplayEmitter::nsMimeHtmlDisplayEmitter() : nsMimeBaseEmitter() +{ + mFirst = true; + mSkipAttachment = false; +} + +nsMimeHtmlDisplayEmitter::~nsMimeHtmlDisplayEmitter(void) +{ +} + +nsresult nsMimeHtmlDisplayEmitter::Init() +{ + return NS_OK; +} + +bool nsMimeHtmlDisplayEmitter::BroadCastHeadersAndAttachments() +{ + // try to get a header sink if there is one.... + nsCOMPtr<nsIMsgHeaderSink> headerSink; + nsresult rv = GetHeaderSink(getter_AddRefs(headerSink)); + if (NS_SUCCEEDED(rv) && headerSink && mDocHeader) + return true; + else + return false; +} + +nsresult +nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTMLPrefix(const nsACString &name) +{ + if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)) + return nsMimeBaseEmitter::WriteHeaderFieldHTMLPrefix(name); + else + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTML(const char *field, const char *value) +{ + if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)) + return nsMimeBaseEmitter::WriteHeaderFieldHTML(field, value); + else + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTMLPostfix() +{ + if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)) + return nsMimeBaseEmitter::WriteHeaderFieldHTMLPostfix(); + else + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::GetHeaderSink(nsIMsgHeaderSink ** aHeaderSink) +{ + nsresult rv = NS_OK; + if ( (mChannel) && (!mHeaderSink) ) + { + nsCOMPtr<nsIURI> uri; + mChannel->GetURI(getter_AddRefs(uri)); + if (uri) + { + nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(uri)); + if (msgurl) + { + msgurl->GetMsgHeaderSink(getter_AddRefs(mHeaderSink)); + if (!mHeaderSink) // if the url is not overriding the header sink, then just get the one from the msg window + { + nsCOMPtr<nsIMsgWindow> msgWindow; + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + msgWindow->GetMsgHeaderSink(getter_AddRefs(mHeaderSink)); + } + } + } + } + + *aHeaderSink = mHeaderSink; + NS_IF_ADDREF(*aHeaderSink); + return rv; +} + +nsresult nsMimeHtmlDisplayEmitter::BroadcastHeaders(nsIMsgHeaderSink * aHeaderSink, int32_t aHeaderMode, bool aFromNewsgroup) +{ + // two string enumerators to pass out to the header sink + RefPtr<nsMimeStringEnumerator> headerNameEnumerator = new nsMimeStringEnumerator(); + NS_ENSURE_TRUE(headerNameEnumerator, NS_ERROR_OUT_OF_MEMORY); + RefPtr<nsMimeStringEnumerator> headerValueEnumerator = new nsMimeStringEnumerator(); + NS_ENSURE_TRUE(headerValueEnumerator, NS_ERROR_OUT_OF_MEMORY); + + nsCString extraExpandedHeaders; + nsTArray<nsCString> extraExpandedHeadersArray; + nsAutoCString convertedDateString; + + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + { + pPrefBranch->GetCharPref("mailnews.headers.extraExpandedHeaders", getter_Copies(extraExpandedHeaders)); + // todo - should make this upper case + if (!extraExpandedHeaders.IsEmpty()) + { + ToLowerCase(extraExpandedHeaders); + ParseString(extraExpandedHeaders, ' ', extraExpandedHeadersArray); + } + } + + for (size_t i = 0; i < mHeaderArray->Length(); i++) + { + headerInfoType * headerInfo = mHeaderArray->ElementAt(i); + if ( (!headerInfo) || (!headerInfo->name) || (!(*headerInfo->name)) || (!headerInfo->value) || (!(*headerInfo->value))) + continue; + + const char * headerValue = headerInfo->value; + + // optimization: if we aren't in view all header view mode, we only show a small set of the total # of headers. + // don't waste time sending those out to the UI since the UI is going to ignore them anyway. + if (aHeaderMode != VIEW_ALL_HEADERS && (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer)) + { + nsDependentCString headerStr(headerInfo->name); + if (PL_strcasecmp("to", headerInfo->name) && PL_strcasecmp("from", headerInfo->name) && + PL_strcasecmp("cc", headerInfo->name) && PL_strcasecmp("newsgroups", headerInfo->name) && + PL_strcasecmp("bcc", headerInfo->name) && PL_strcasecmp("followup-to", headerInfo->name) && + PL_strcasecmp("reply-to", headerInfo->name) && PL_strcasecmp("subject", headerInfo->name) && + PL_strcasecmp("organization", headerInfo->name) && PL_strcasecmp("user-agent", headerInfo->name) && + PL_strcasecmp("content-base", headerInfo->name) && PL_strcasecmp("sender", headerInfo->name) && + PL_strcasecmp("date", headerInfo->name) && PL_strcasecmp("x-mailer", headerInfo->name) && + PL_strcasecmp("content-type", headerInfo->name) && PL_strcasecmp("message-id", headerInfo->name) && + PL_strcasecmp("x-newsreader", headerInfo->name) && PL_strcasecmp("x-mimeole", headerInfo->name) && + PL_strcasecmp("references", headerInfo->name) && PL_strcasecmp("in-reply-to", headerInfo->name) && + PL_strcasecmp("list-post", headerInfo->name) && PL_strcasecmp("delivered-to", headerInfo->name) && + // make headerStr lower case because IndexOf is case-sensitive + (!extraExpandedHeadersArray.Length() || (ToLowerCase(headerStr), + !extraExpandedHeadersArray.Contains(headerStr)))) + continue; + } + + headerNameEnumerator->Append(headerInfo->name); + headerValueEnumerator->Append(headerValue); + + // Add a localized version of the date header if we encounter it. + if (!PL_strcasecmp("Date", headerInfo->name)) + { + headerNameEnumerator->Append("X-Mozilla-LocalizedDate"); + GenerateDateString(headerValue, convertedDateString, false); + headerValueEnumerator->Append(convertedDateString); + } + } + + aHeaderSink->ProcessHeaders(headerNameEnumerator, headerValueEnumerator, aFromNewsgroup); + return rv; +} + +NS_IMETHODIMP nsMimeHtmlDisplayEmitter::WriteHTMLHeaders(const nsACString &name) +{ + // if we aren't broadcasting headers OR printing...just do whatever + // our base class does... + if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) + { + return nsMimeBaseEmitter::WriteHTMLHeaders(name); + } + else if (!BroadCastHeadersAndAttachments() || !mDocHeader) + { + // This needs to be here to correct the output format if we are + // not going to broadcast headers to the XUL document. + if (mFormat == nsMimeOutput::nsMimeMessageBodyDisplay) + mFormat = nsMimeOutput::nsMimeMessagePrintOutput; + + return nsMimeBaseEmitter::WriteHTMLHeaders(name); + } + else + mFirstHeaders = false; + + bool bFromNewsgroups = false; + for (size_t j = 0; j < mHeaderArray->Length(); j++) + { + headerInfoType *headerInfo = mHeaderArray->ElementAt(j); + if (!(headerInfo && headerInfo->name && *headerInfo->name)) + continue; + + if (!PL_strcasecmp("Newsgroups", headerInfo->name)) + { + bFromNewsgroups = true; + break; + } + } + + // try to get a header sink if there is one.... + nsCOMPtr<nsIMsgHeaderSink> headerSink; + nsresult rv = GetHeaderSink(getter_AddRefs(headerSink)); + + if (headerSink) + { + int32_t viewMode = 0; + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + rv = pPrefBranch->GetIntPref("mail.show_headers", &viewMode); + + rv = BroadcastHeaders(headerSink, viewMode, bFromNewsgroups); + } // if header Sink + + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::EndHeader(const nsACString &name) +{ + if (mDocHeader && (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer)) + { + UtilityWriteCRLF("<html>"); + UtilityWriteCRLF("<head>"); + + const char * val = GetHeaderValue(HEADER_SUBJECT); // do not free this value + if (val) + { + char * subject = MsgEscapeHTML(val); + if (subject) + { + int32_t bufLen = strlen(subject) + 16; + char *buf = new char[bufLen]; + if (!buf) + return NS_ERROR_OUT_OF_MEMORY; + PR_snprintf(buf, bufLen, "<title>%s</title>", subject); + UtilityWriteCRLF(buf); + delete [] buf; + free(subject); + } + } + + // Stylesheet info! + UtilityWriteCRLF("<link rel=\"important stylesheet\" href=\"chrome://messagebody/skin/messageBody.css\">"); + + UtilityWriteCRLF("</head>"); + UtilityWriteCRLF("<body>"); + } + + WriteHTMLHeaders(name); + + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::StartAttachment(const nsACString &name, + const char *contentType, + const char *url, + bool aIsExternalAttachment) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgHeaderSink> headerSink; + rv = GetHeaderSink(getter_AddRefs(headerSink)); + + if (NS_SUCCEEDED(rv) && headerSink) + { + nsCString uriString; + + nsCOMPtr<nsIMsgMessageUrl> msgurl (do_QueryInterface(mURL, &rv)); + if (NS_SUCCEEDED(rv)) + { + // HACK: news urls require us to use the originalSpec. Everyone + // else uses GetURI to get the RDF resource which describes the message. + nsCOMPtr<nsINntpUrl> nntpUrl (do_QueryInterface(mURL, &rv)); + if (NS_SUCCEEDED(rv) && nntpUrl) + rv = msgurl->GetOriginalSpec(getter_Copies(uriString)); + else + rv = msgurl->GetUri(getter_Copies(uriString)); + } + + // we need to convert the attachment name from UTF-8 to unicode before + // we emit it. The attachment name has already been rfc2047 processed + // upstream of us. (Namely, mime_decode_filename has been called, deferring + // to nsIMimeHeaderParam.decodeParameter.) + nsString unicodeHeaderValue; + CopyUTF8toUTF16(name, unicodeHeaderValue); + + headerSink->HandleAttachment(contentType, url /* was escapedUrl */, + unicodeHeaderValue.get(), uriString.get(), + aIsExternalAttachment); + + mSkipAttachment = false; + } + else if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) + { + // then we need to deal with the attachments in the body by inserting + // them into a table.. + rv = StartAttachmentInBody(name, contentType, url); + } + else + { + // If we don't need or cannot broadcast attachment info, just ignore it + mSkipAttachment = true; + rv = NS_OK; + } + + return rv; +} + +// Attachment handling routines +// Ok, we are changing the way we handle these now...It used to be that we output +// HTML to make a clickable link, etc... but now, this should just be informational +// and only show up during printing +// XXX should they also show up during quoting? +nsresult +nsMimeHtmlDisplayEmitter::StartAttachmentInBody(const nsACString &name, + const char *contentType, + const char *url) +{ + mSkipAttachment = false; + bool p7mExternal = false; + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) + prefs->GetBoolPref("mailnews.p7m_external", &p7mExternal); + + if ( (contentType) && + ((!p7mExternal && !strcmp(contentType, APPLICATION_XPKCS7_MIME)) || + (!p7mExternal && !strcmp(contentType, APPLICATION_PKCS7_MIME)) || + (!strcmp(contentType, APPLICATION_XPKCS7_SIGNATURE)) || + (!strcmp(contentType, APPLICATION_PKCS7_SIGNATURE)) || + (!strcmp(contentType, TEXT_VCARD))) + ) + { + mSkipAttachment = true; + return NS_OK; + } + + if (mFirst) + { + UtilityWrite("<br><fieldset class=\"mimeAttachmentHeader\">"); + if (!name.IsEmpty()) + { + nsresult rv; + + nsCOMPtr<nsIStringBundleService> bundleSvc = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleSvc->CreateBundle("chrome://messenger/locale/messenger.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString attachmentsHeader; + bundle->GetStringFromName(u"attachmentsPrintHeader", + getter_Copies(attachmentsHeader)); + + UtilityWrite("<legend class=\"mimeAttachmentHeaderName\">"); + nsCString escapedName; + escapedName.Adopt(MsgEscapeHTML(NS_ConvertUTF16toUTF8(attachmentsHeader).get())); + UtilityWrite(escapedName.get()); + UtilityWrite("</legend>"); + } + UtilityWrite("</fieldset>"); + UtilityWrite("<div class=\"mimeAttachmentWrap\">"); + UtilityWrite("<table class=\"mimeAttachmentTable\">"); + } + + UtilityWrite("<tr>"); + + UtilityWrite("<td class=\"mimeAttachmentFile\">"); + UtilityWrite(name); + UtilityWrite("</td>"); + + mFirst = false; + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::AddAttachmentField(const char *field, const char *value) +{ + if (mSkipAttachment) + return NS_OK; + + // Don't let bad things happen + if ( !value || !*value ) + return NS_OK; + + // Don't output this ugly header... + if (!strcmp(field, HEADER_X_MOZILLA_PART_URL)) + return NS_OK; + + nsCOMPtr<nsIMsgHeaderSink> headerSink; + nsresult rv = GetHeaderSink(getter_AddRefs(headerSink)); + if (NS_SUCCEEDED(rv) && headerSink) + { + headerSink->AddAttachmentField(field, value); + } + else + { + // Currently, we only care about the part size. + if (strcmp(field, HEADER_X_MOZILLA_PART_SIZE)) + return NS_OK; + + uint64_t size = atoi(value); + nsAutoString sizeString; + rv = FormatFileSize(size, false, sizeString); + UtilityWrite("<td class=\"mimeAttachmentSize\">"); + UtilityWrite(NS_ConvertUTF16toUTF8(sizeString).get()); + UtilityWrite("</td>"); + } + + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::EndAttachment() +{ + if (mSkipAttachment) + return NS_OK; + + mSkipAttachment = false; // reset it for next attachment round + + if (BroadCastHeadersAndAttachments()) + return NS_OK; + + if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) + UtilityWrite("</tr>"); + + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::EndAllAttachments() +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgHeaderSink> headerSink; + rv = GetHeaderSink(getter_AddRefs(headerSink)); + if (headerSink) + headerSink->OnEndAllAttachments(); + + if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) + { + UtilityWrite("</table>"); + UtilityWrite("</div>"); + } + + return rv; +} + +nsresult +nsMimeHtmlDisplayEmitter::WriteBody(const nsACString &buf, + uint32_t *amountWritten) +{ + Write(buf, amountWritten); + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::EndBody() +{ + if (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer) + { + UtilityWriteCRLF("</body>"); + UtilityWriteCRLF("</html>"); + } + nsCOMPtr<nsIMsgHeaderSink> headerSink; + nsresult rv = GetHeaderSink(getter_AddRefs(headerSink)); + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl (do_QueryInterface(mURL, &rv)); + if (headerSink) + headerSink->OnEndMsgHeaders(mailnewsUrl); + + return NS_OK; +} + + diff --git a/mailnews/mime/emitters/nsMimeHtmlEmitter.h b/mailnews/mime/emitters/nsMimeHtmlEmitter.h new file mode 100644 index 000000000..886687763 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeHtmlEmitter.h @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsMimeHtmlEmitter_h_ +#define _nsMimeHtmlEmitter_h_ + +#include "mozilla/Attributes.h" +#include "prio.h" +#include "nsMimeBaseEmitter.h" +#include "nsMimeRebuffer.h" +#include "nsIStreamListener.h" +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsIURI.h" +#include "nsIChannel.h" +#include "nsIMimeMiscStatus.h" +#include "nsIMimeConverter.h" + +class nsMimeHtmlDisplayEmitter : public nsMimeBaseEmitter { +public: + nsMimeHtmlDisplayEmitter (); + nsresult Init(); + + virtual ~nsMimeHtmlDisplayEmitter (void); + + // Header handling routines. + NS_IMETHOD EndHeader(const nsACString &name) override; + + // Attachment handling routines + NS_IMETHOD StartAttachment(const nsACString &name, + const char *contentType, const char *url, + bool aIsExternalAttachment) override; + NS_IMETHOD AddAttachmentField(const char *field, const char *value) override; + NS_IMETHOD EndAttachment() override; + NS_IMETHOD EndAllAttachments() override; + + // Body handling routines + NS_IMETHOD WriteBody(const nsACString &buf, uint32_t *amountWritten) override; + NS_IMETHOD EndBody() override; + NS_IMETHOD WriteHTMLHeaders(const nsACString &name) override; + + virtual nsresult WriteHeaderFieldHTMLPrefix(const nsACString &name + ) override; + virtual nsresult WriteHeaderFieldHTML(const char *field, + const char *value) override; + virtual nsresult WriteHeaderFieldHTMLPostfix() override; + +protected: + bool mFirst; // Attachment flag... + bool mSkipAttachment; // attachments we shouldn't show... + + nsCOMPtr<nsIMsgHeaderSink> mHeaderSink; + + nsresult GetHeaderSink(nsIMsgHeaderSink ** aHeaderSink); + bool BroadCastHeadersAndAttachments(); + nsresult StartAttachmentInBody(const nsACString &name, + const char *contentType, const char *url); + + nsresult BroadcastHeaders(nsIMsgHeaderSink * aHeaderSink, int32_t aHeaderMode, bool aFromNewsgroup); +}; + + +#endif /* _nsMimeHtmlEmitter_h_ */ diff --git a/mailnews/mime/emitters/nsMimePlainEmitter.cpp b/mailnews/mime/emitters/nsMimePlainEmitter.cpp new file mode 100644 index 000000000..8e6fae742 --- /dev/null +++ b/mailnews/mime/emitters/nsMimePlainEmitter.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include "nsMimeRebuffer.h" +#include "nsMimePlainEmitter.h" +#include "plstr.h" +#include "nsMailHeaders.h" +#include "nscore.h" +#include "prmem.h" +#include "nsEmitterUtils.h" +#include "nsCOMPtr.h" +#include "nsUnicharUtils.h" + +/* + * nsMimePlainEmitter definitions.... + */ +nsMimePlainEmitter::nsMimePlainEmitter() +{ +} + + +nsMimePlainEmitter::~nsMimePlainEmitter(void) +{ +} + + +// Header handling routines. +nsresult +nsMimePlainEmitter::StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID, + const char *outCharset) +{ + mDocHeader = rootMailHeader; + return NS_OK; +} + +nsresult +nsMimePlainEmitter::AddHeaderField(const char *field, const char *value) +{ + if ( (!field) || (!value) ) + return NS_OK; + + UtilityWrite(field); + UtilityWrite(":\t"); + UtilityWriteCRLF(value); + return NS_OK; +} + +nsresult +nsMimePlainEmitter::EndHeader(const nsACString &name) +{ + UtilityWriteCRLF(""); + return NS_OK; +} + +NS_IMETHODIMP +nsMimePlainEmitter::WriteBody(const nsACString &buf, uint32_t *amountWritten) +{ + Write(buf, amountWritten); + return NS_OK; +} + diff --git a/mailnews/mime/emitters/nsMimePlainEmitter.h b/mailnews/mime/emitters/nsMimePlainEmitter.h new file mode 100644 index 000000000..94cc0cc47 --- /dev/null +++ b/mailnews/mime/emitters/nsMimePlainEmitter.h @@ -0,0 +1,31 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsMimePlainEmitter_h_ +#define _nsMimePlainEmitter_h_ + +#include "mozilla/Attributes.h" +#include "prio.h" +#include "nsMimeBaseEmitter.h" +#include "nsMimeRebuffer.h" +#include "nsIStreamListener.h" +#include "nsIOutputStream.h" +#include "nsIURI.h" +#include "nsIChannel.h" + +class nsMimePlainEmitter : public nsMimeBaseEmitter { +public: + nsMimePlainEmitter (); + virtual ~nsMimePlainEmitter (void); + + // Header handling routines. + NS_IMETHOD StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID, + const char *outCharset) override; + NS_IMETHOD AddHeaderField(const char *field, const char *value) override; + NS_IMETHOD EndHeader(const nsACString &buf) override; + + NS_IMETHOD WriteBody(const nsACString &buf, uint32_t *amountWritten) override; +}; + +#endif /* _nsMimePlainEmitter_h_ */ diff --git a/mailnews/mime/emitters/nsMimeRawEmitter.cpp b/mailnews/mime/emitters/nsMimeRawEmitter.cpp new file mode 100644 index 000000000..28e5f53ec --- /dev/null +++ b/mailnews/mime/emitters/nsMimeRawEmitter.cpp @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include <stdio.h> +#include "nsMimeRebuffer.h" +#include "nsMimeRawEmitter.h" +#include "plstr.h" +#include "nsIMimeEmitter.h" +#include "nsMailHeaders.h" +#include "nscore.h" +#include "prmem.h" +#include "nsEmitterUtils.h" + +/* + * nsMimeRawEmitter definitions.... + */ +nsMimeRawEmitter::nsMimeRawEmitter() +{ +} + + +nsMimeRawEmitter::~nsMimeRawEmitter(void) +{ +} + +NS_IMETHODIMP +nsMimeRawEmitter::WriteBody(const nsACString &buf, uint32_t *amountWritten) +{ + Write(buf, amountWritten); + return NS_OK; +} + diff --git a/mailnews/mime/emitters/nsMimeRawEmitter.h b/mailnews/mime/emitters/nsMimeRawEmitter.h new file mode 100644 index 000000000..07542efb9 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeRawEmitter.h @@ -0,0 +1,29 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsMimeRawEmitter_h_ +#define _nsMimeRawEmitter_h_ + +#include "mozilla/Attributes.h" +#include "prio.h" +#include "nsMimeBaseEmitter.h" +#include "nsMimeRebuffer.h" +#include "nsIStreamListener.h" +#include "nsIOutputStream.h" +#include "nsIURI.h" +#include "nsIChannel.h" + +class nsMimeRawEmitter : public nsMimeBaseEmitter { +public: + nsMimeRawEmitter (); + virtual ~nsMimeRawEmitter (void); + + NS_IMETHOD WriteBody(const nsACString &buf, + uint32_t *amountWritten) override; + +protected: +}; + + +#endif /* _nsMimeRawEmitter_h_ */ diff --git a/mailnews/mime/emitters/nsMimeRebuffer.cpp b/mailnews/mime/emitters/nsMimeRebuffer.cpp new file mode 100644 index 000000000..0e68a586c --- /dev/null +++ b/mailnews/mime/emitters/nsMimeRebuffer.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <string.h> +#include "nsMimeRebuffer.h" +#include "prmem.h" + +MimeRebuffer::MimeRebuffer(void) +{ +} + +MimeRebuffer::~MimeRebuffer(void) +{ +} + +uint32_t +MimeRebuffer::GetSize() +{ + return mBuf.Length(); +} + +uint32_t +MimeRebuffer::IncreaseBuffer(const nsACString &addBuf) +{ + mBuf.Append(addBuf); + return mBuf.Length(); +} + +uint32_t +MimeRebuffer::ReduceBuffer(uint32_t numBytes) +{ + if (numBytes == 0) + return mBuf.Length(); + + if (numBytes >= mBuf.Length()) + { + mBuf.Truncate(); + return 0; + } + + mBuf.Cut(0, numBytes); + return mBuf.Length(); +} + +nsACString & +MimeRebuffer::GetBuffer() +{ + return mBuf; +} diff --git a/mailnews/mime/emitters/nsMimeRebuffer.h b/mailnews/mime/emitters/nsMimeRebuffer.h new file mode 100644 index 000000000..568960206 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeRebuffer.h @@ -0,0 +1,29 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _rebuffer_h_ +#define _rebuffer_h_ + +#include <stdint.h> +#include "nsStringGlue.h" + +////////////////////////////////////////////////////////////// +// A rebuffering class necessary for stream output buffering +////////////////////////////////////////////////////////////// + +class MimeRebuffer { +public: + MimeRebuffer (void); + virtual ~MimeRebuffer (void); + + uint32_t GetSize(); + uint32_t IncreaseBuffer(const nsACString &addBuf); + uint32_t ReduceBuffer(uint32_t numBytes); + nsACString & GetBuffer(); + +protected: + nsCString mBuf; +}; + +#endif /* _rebuffer_h_ */ diff --git a/mailnews/mime/emitters/nsMimeXmlEmitter.cpp b/mailnews/mime/emitters/nsMimeXmlEmitter.cpp new file mode 100644 index 000000000..f9cd1ece2 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeXmlEmitter.cpp @@ -0,0 +1,184 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <stdio.h> +#include "nsMimeRebuffer.h" +#include "nsMimeXmlEmitter.h" +#include "plstr.h" +#include "nsMailHeaders.h" +#include "nscore.h" +#include "prmem.h" +#include "nsEmitterUtils.h" +#include "nsCOMPtr.h" +#include "nsUnicharUtils.h" +#include "nsMsgUtils.h" + +/* + * nsMimeXmlEmitter definitions.... + */ +nsMimeXmlEmitter::nsMimeXmlEmitter() +{ +} + + +nsMimeXmlEmitter::~nsMimeXmlEmitter(void) +{ +} + + +// Note - this is teardown only...you should not write +// anything to the stream since these may be image data +// output streams, etc... +nsresult +nsMimeXmlEmitter::Complete() +{ + char buf[16]; + + // Now write out the total count of attachments for this message + UtilityWrite("<mailattachcount>"); + sprintf(buf, "%d", mAttachCount); + UtilityWrite(buf); + UtilityWrite("</mailattachcount>"); + + UtilityWrite("</message>"); + + return nsMimeBaseEmitter::Complete(); + +} + +nsresult +nsMimeXmlEmitter::WriteXMLHeader(const char *msgID) +{ + if ( (!msgID) || (!*msgID) ) + msgID = "none"; + + char *newValue = MsgEscapeHTML(msgID); + if (!newValue) + return NS_ERROR_OUT_OF_MEMORY; + + UtilityWrite("<?xml version=\"1.0\"?>"); + + UtilityWriteCRLF("<?xml-stylesheet href=\"chrome://messagebody/skin/messageBody.css\" type=\"text/css\"?>"); + + UtilityWrite("<message id=\""); + UtilityWrite(newValue); + UtilityWrite("\">"); + + mXMLHeaderStarted = true; + PR_FREEIF(newValue); + return NS_OK; +} + +nsresult +nsMimeXmlEmitter::WriteXMLTag(const char *tagName, const char *value) +{ + if ( (!value) || (!*value) ) + return NS_OK; + + char *upCaseTag = NULL; + char *newValue = MsgEscapeHTML(value); + if (!newValue) + return NS_OK; + + nsCString newTagName(tagName); + newTagName.StripWhitespace(); + ToUpperCase(newTagName); + upCaseTag = ToNewCString(newTagName); + + UtilityWrite("<header field=\""); + UtilityWrite(upCaseTag); + UtilityWrite("\">"); + + // Here is where we are going to try to L10N the tagName so we will always + // get a field name next to an emitted header value. Note: Default will always + // be the name of the header itself. + // + UtilityWrite("<headerdisplayname>"); + char *l10nTagName = LocalizeHeaderName(upCaseTag, tagName); + if ( (!l10nTagName) || (!*l10nTagName) ) + UtilityWrite(tagName); + else + { + UtilityWrite(l10nTagName); + } + PR_FREEIF(l10nTagName); + + UtilityWrite(": "); + UtilityWrite("</headerdisplayname>"); + + // Now write out the actual value itself and move on! + // + UtilityWrite(newValue); + UtilityWrite("</header>"); + + NS_Free(upCaseTag); + PR_FREEIF(newValue); + + return NS_OK; +} + +// Header handling routines. +nsresult +nsMimeXmlEmitter::StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID, + const char *outCharset) +{ + mDocHeader = rootMailHeader; + WriteXMLHeader(msgID); + UtilityWrite("<mailheader>"); + + return NS_OK; +} + +nsresult +nsMimeXmlEmitter::AddHeaderField(const char *field, const char *value) +{ + if ( (!field) || (!value) ) + return NS_OK; + + WriteXMLTag(field, value); + return NS_OK; +} + +nsresult +nsMimeXmlEmitter::EndHeader(const nsACString &name) +{ + UtilityWrite("</mailheader>"); + return NS_OK; +} + + +// Attachment handling routines +nsresult +nsMimeXmlEmitter::StartAttachment(const nsACString &name, + const char *contentType, + const char *url, + bool aIsExternalAttachment) +{ + char buf[128]; + + ++mAttachCount; + + sprintf(buf, "<mailattachment id=\"%d\">", mAttachCount); + UtilityWrite(buf); + + AddAttachmentField(HEADER_PARM_FILENAME, PromiseFlatCString(name).get()); + return NS_OK; +} + +nsresult +nsMimeXmlEmitter::AddAttachmentField(const char *field, const char *value) +{ + WriteXMLTag(field, value); + return NS_OK; +} + +nsresult +nsMimeXmlEmitter::EndAttachment() +{ + UtilityWrite("</mailattachment>"); + return NS_OK; +} + + diff --git a/mailnews/mime/emitters/nsMimeXmlEmitter.h b/mailnews/mime/emitters/nsMimeXmlEmitter.h new file mode 100644 index 000000000..f9e83948d --- /dev/null +++ b/mailnews/mime/emitters/nsMimeXmlEmitter.h @@ -0,0 +1,47 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsMimeXmlEmitter_h_ +#define _nsMimeXmlEmitter_h_ + +#include "mozilla/Attributes.h" +#include "prio.h" +#include "nsMimeBaseEmitter.h" +#include "nsMimeRebuffer.h" +#include "nsIStreamListener.h" +#include "nsIOutputStream.h" +#include "nsIURI.h" +#include "nsIChannel.h" + +class nsMimeXmlEmitter : public nsMimeBaseEmitter { +public: + nsMimeXmlEmitter (); + virtual ~nsMimeXmlEmitter (void); + + NS_IMETHOD Complete() override; + + // Header handling routines. + NS_IMETHOD StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID, + const char *outCharset) override; + NS_IMETHOD AddHeaderField(const char *field, const char *value) override; + NS_IMETHOD EndHeader(const nsACString &buf) override; + + // Attachment handling routines + NS_IMETHOD StartAttachment(const nsACString &name, + const char *contentType, const char *url, + bool aIsExternalAttachment) override; + NS_IMETHOD AddAttachmentField(const char *field, const char *value) override; + NS_IMETHOD EndAttachment() override; + + NS_IMETHOD WriteXMLHeader(const char *msgID); + NS_IMETHOD WriteXMLTag(const char *tagName, const char *value); + +protected: + + // For header determination... + bool mXMLHeaderStarted; + int32_t mAttachCount; +}; + +#endif /* _nsMimeXmlEmitter_h_ */ diff --git a/mailnews/mime/jsmime/LICENSE b/mailnews/mime/jsmime/LICENSE new file mode 100644 index 000000000..9ddc547d9 --- /dev/null +++ b/mailnews/mime/jsmime/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013 Joshua Cranmer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/mailnews/mime/jsmime/README.md b/mailnews/mime/jsmime/README.md new file mode 100644 index 000000000..a418516ea --- /dev/null +++ b/mailnews/mime/jsmime/README.md @@ -0,0 +1,59 @@ +Code Layout +=========== + +JSMime is a MIME parsing and composition library that is written completely in +JavaScript using ES6 functionality and WebAPIs (where such APIs exist). There +are a few features for which a standardized WebAPI does not exist; for these, +external JavaScript libraries are used. + +The MIME parser consists of three logical phases of translation: + +1. Build the MIME (and pseudo-MIME) tree. +2. Convert the MIME tree into a list of body parts and attachments. +3. Use the result to drive a displayed version of the message. + +The first stage is located in `mimeparser.js`. The latter stages have yet to be +implemented. + +Dependencies +============ + +This code depends on the following ES6 features and Web APIs: +* ES6 generators +* ES6 Map and Set +* ES6 @@iterator support (especially for Map and Set) +* ES6 let +* ES6 let-destructuring +* ES6 const +* Typed arrays (predominantly Uint8Array) +* btoa, atob (found on global Windows or WorkerScopes) +* TextDecoder + +Versions and API stability +========================== + +As APIs require some use and experimentation to get a feel for what works best, +the APIs may change between successive version updates as uses indicate +substandard or error-prone APIs. Therefore, there will be no guarantee of API +stability until version 1.0 is released. + +This code is being initially developed as an effort to replace the MIME library +within Thunderbird. New versions will be released as needed to bring new support +into the Thunderbird codebase; version 1.0 will correspond to the version where +feature-parity with the old MIME library is reached. The set of features which +will be added before 1.0 are the following: +* S/MIME encryption and decryption +* PGP encryption and decryption +* IMAP parts-on-demand support +* Support for text/plain to HTML conversion for display +* Support for HTML downgrading and sanitization for display +* Support for all major multipart types +* Ability to convert HTML documents to text/plain and multipart/related +* Support for building outgoing messages +* Support for IDN and EAI +* yEnc and uuencode decoding support +* Support for date and Message-ID/References-like headers + +Other features than these may be added before version 1.0 is released (most +notably TNEF decoding support), but they are not considered necessary to release +a version 1.0. diff --git a/mailnews/mime/jsmime/jsmime.js b/mailnews/mime/jsmime/jsmime.js new file mode 100644 index 000000000..253b5da0f --- /dev/null +++ b/mailnews/mime/jsmime/jsmime.js @@ -0,0 +1,3300 @@ +(function (root, fn) { + if (typeof define === 'function' && define.amd) { + define(fn); + } else if (typeof module !== 'undefined' && module.exports) { + module.exports = fn(); + } else { + root.jsmime = fn(); + } +}(this, function() { + var mods = {}; + function req(id) { + return mods[id.replace(/^\.\//, '')]; + } + + function def(id, fn) { + mods[id] = fn(req); + } +def('mimeutils', function() { +"use strict"; + +/** + * Decode a quoted-printable buffer into a binary string. + * + * @param buffer {BinaryString} The string to decode. + * @param more {Boolean} This argument is ignored. + * @returns {Array(BinaryString, BinaryString)} The first element of the array + * is the decoded string. The second element is always the empty + * string. + */ +function decode_qp(buffer, more) { + // Unlike base64, quoted-printable isn't stateful across multiple lines, so + // there is no need to buffer input, so we can always ignore more. + let decoded = buffer.replace( + // Replace either =<hex><hex> or =<wsp>CRLF + /=([0-9A-F][0-9A-F]|[ \t]*(\r\n|[\r\n]|$))/gi, + function replace_chars(match, param) { + // If trailing text matches [ \t]*CRLF, drop everything, since it's a + // soft line break. + if (param.trim().length == 0) + return ''; + return String.fromCharCode(parseInt(param, 16)); + }); + return [decoded, '']; +} + +/** + * Decode a base64 buffer into a binary string. Unlike window.atob, the buffer + * may contain non-base64 characters that will be ignored. + * + * @param buffer {BinaryString} The string to decode. + * @param more {Boolean} If true, we expect that this function could be + * called again and should retain extra data. If + * false, we should flush all pending output. + * @returns {Array(BinaryString, BinaryString)} The first element of the array + * is the decoded string. The second element contains the data that + * could not be decoded and needs to be retained for the next call. + */ +function decode_base64(buffer, more) { + // Drop all non-base64 characters + let sanitize = buffer.replace(/[^A-Za-z0-9+\/=]/g,''); + // Remove harmful `=' chars in the middle. + sanitize = sanitize.replace(/=+([A-Za-z0-9+\/])/g, '$1'); + // We need to encode in groups of 4 chars. If we don't have enough, leave the + // excess for later. If there aren't any more, drop enough to make it 4. + let excess = sanitize.length % 4; + if (excess != 0 && more) + buffer = sanitize.slice(-excess); + else + buffer = ''; + sanitize = sanitize.substring(0, sanitize.length - excess); + // Use the atob function we (ought to) have in global scope. + return [atob(sanitize), buffer]; +} + +/** + * Converts a binary string into a Uint8Array buffer. + * + * @param buffer {BinaryString} The string to convert. + * @returns {Uint8Array} The converted data. + */ +function stringToTypedArray(buffer) { + var typedarray = new Uint8Array(buffer.length); + for (var i = 0; i < buffer.length; i++) + typedarray[i] = buffer.charCodeAt(i); + return typedarray; +} + +/** + * Converts a Uint8Array buffer to a binary string. + * + * @param buffer {BinaryString} The string to convert. + * @returns {Uint8Array} The converted data. + */ +function typedArrayToString(buffer) { + var string = ''; + for (var i = 0; i < buffer.length; i+= 100) + string += String.fromCharCode.apply(undefined, buffer.subarray(i, i + 100)); + return string; +} + +/** A list of month names for Date parsing. */ +var kMonthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec"]; + +return { + decode_base64: decode_base64, + decode_qp: decode_qp, + kMonthNames: kMonthNames, + stringToTypedArray: stringToTypedArray, + typedArrayToString: typedArrayToString, +}; +}); +/** + * This file implements knowledge of how to encode or decode structured headers + * for several key headers. It is not meant to be used externally to jsmime. + */ + +def('structuredHeaders', function (require) { +"use strict"; + +var structuredDecoders = new Map(); +var structuredEncoders = new Map(); +var preferredSpellings = new Map(); + +function addHeader(name, decoder, encoder) { + var lowerName = name.toLowerCase(); + structuredDecoders.set(lowerName, decoder); + structuredEncoders.set(lowerName, encoder); + preferredSpellings.set(lowerName, name); +} + + +// Addressing headers: We assume that they can be specified in 1* form (this is +// false for From, but it's close enough to the truth that it shouldn't matter). +// There is no need to specialize the results for the header, so just pun it +// back to parseAddressingHeader. +function parseAddress(value) { + let results = []; + let headerparser = this; + return value.reduce(function (results, header) { + return results.concat(headerparser.parseAddressingHeader(header, true)); + }, []); +} +function writeAddress(value) { + // Make sure the input is an array (accept a single entry) + if (!Array.isArray(value)) + value = [value]; + this.addAddresses(value); +} + +// Addressing headers from RFC 5322: +addHeader("Bcc", parseAddress, writeAddress); +addHeader("Cc", parseAddress, writeAddress); +addHeader("From", parseAddress, writeAddress); +addHeader("Reply-To", parseAddress, writeAddress); +addHeader("Resent-Bcc", parseAddress, writeAddress); +addHeader("Resent-Cc", parseAddress, writeAddress); +addHeader("Resent-From", parseAddress, writeAddress); +addHeader("Resent-Reply-To", parseAddress, writeAddress); +addHeader("Resent-Sender", parseAddress, writeAddress); +addHeader("Resent-To", parseAddress, writeAddress); +addHeader("Sender", parseAddress, writeAddress); +addHeader("To", parseAddress, writeAddress); +// From RFC 5536: +addHeader("Approved", parseAddress, writeAddress); +// From RFC 3798: +addHeader("Disposition-Notification-To", parseAddress, writeAddress); +// Non-standard headers: +addHeader("Delivered-To", parseAddress, writeAddress); +addHeader("Return-Receipt-To", parseAddress, writeAddress); + +// http://cr.yp.to/proto/replyto.html +addHeader("Mail-Reply-To", parseAddress, writeAddress); +addHeader("Mail-Followup-To", parseAddress, writeAddress); + +// Parameter-based headers. Note that all parameters are slightly different, so +// we use slightly different variants here. +function parseParameterHeader(value, do2231, do2047) { + // Only use the first header for parameters; ignore subsequent redefinitions. + return this.parseParameterHeader(value[0], do2231, do2047); +} + +// RFC 2045 +function parseContentType(value) { + let params = parseParameterHeader.call(this, value, false, false); + let origtype = params.preSemi; + let parts = origtype.split('/'); + if (parts.length != 2) { + // Malformed. Return to text/plain. Evil, ain't it? + params = new Map(); + parts = ["text", "plain"]; + } + let mediatype = parts[0].toLowerCase(); + let subtype = parts[1].toLowerCase(); + let type = mediatype + '/' + subtype; + let structure = new Map(); + structure.mediatype = mediatype; + structure.subtype = subtype; + structure.type = type; + params.forEach(function (value, name) { + structure.set(name.toLowerCase(), value); + }); + return structure; +} +structuredDecoders.set("Content-Type", parseContentType); + +// Unstructured headers (just decode RFC 2047 for the first header value) +function parseUnstructured(values) { + return this.decodeRFC2047Words(values[0]); +} +function writeUnstructured(value) { + this.addUnstructured(value); +} + +// Message-ID headers. +function parseMessageID(values) { + // TODO: Proper parsing support for these headers is currently unsupported). + return this.decodeRFC2047Words(values[0]); +} +function writeMessageID(value) { + // TODO: Proper parsing support for these headers is currently unsupported). + this.addUnstructured(value); +} + +// RFC 5322 +addHeader("Comments", parseUnstructured, writeUnstructured); +addHeader("Keywords", parseUnstructured, writeUnstructured); +addHeader("Subject", parseUnstructured, writeUnstructured); + +// RFC 2045 +addHeader("MIME-Version", parseUnstructured, writeUnstructured); +addHeader("Content-Description", parseUnstructured, writeUnstructured); + +// RFC 7231 +addHeader("User-Agent", parseUnstructured, writeUnstructured); + +// Date headers +function parseDate(values) { return this.parseDateHeader(values[0]); } +function writeDate(value) { this.addDate(value); } + +// RFC 5322 +addHeader("Date", parseDate, writeDate); +addHeader("Resent-Date", parseDate, writeDate); +// RFC 5536 +addHeader("Expires", parseDate, writeDate); +addHeader("Injection-Date", parseDate, writeDate); +addHeader("NNTP-Posting-Date", parseDate, writeDate); + +// RFC 5322 +addHeader("Message-ID", parseMessageID, writeMessageID); +addHeader("Resent-Message-ID", parseMessageID, writeMessageID); + +// Miscellaneous headers (those that don't fall under the above schemes): + +// RFC 2047 +structuredDecoders.set("Content-Transfer-Encoding", function (values) { + return values[0].toLowerCase(); +}); +structuredEncoders.set("Content-Transfer-Encoding", writeUnstructured); + +// Some clients like outlook.com send non-compliant References headers that +// separate values using commas. Also, some clients don't separate References +// with spaces, since these are optional according to RFC2822. So here we +// preprocess these headers (see bug 1154521 and bug 1197686). +function preprocessMessageIDs(values) { + let msgId = /<[^>]*>/g; + let match, ids = []; + while ((match = msgId.exec(values)) !== null) { + ids.push(match[0]); + } + return ids.join(' '); +} +structuredDecoders.set("References", preprocessMessageIDs); +structuredDecoders.set("In-Reply-To", preprocessMessageIDs); + +return Object.freeze({ + decoders: structuredDecoders, + encoders: structuredEncoders, + spellings: preferredSpellings, +}); + +}); +def('headerparser', function(require) { +/** + * This file implements the structured decoding of message header fields. It is + * part of the same system as found in mimemimeutils.js, and occasionally makes + * references to globals defined in that file or other dependencies thereof. See + * documentation in that file for more information about external dependencies. + */ + +"use strict"; +var mimeutils = require('./mimeutils'); + +/** + * This is the API that we ultimately return. + * + * We define it as a global here, because we need to pass it as a |this| + * argument to a few functions. + */ +var headerparser = {}; + +/** + * Tokenizes a message header into a stream of tokens as a generator. + * + * The low-level tokens are meant to be loosely correspond to the tokens as + * defined in RFC 5322. For reasons of saner error handling, however, the two + * definitions are not exactly equivalent. The tokens we emit are the following: + * 1. Special delimiters: Any char in the delimiters string is emitted as a + * string by itself. Parsing parameter headers, for example, would use ";=" + * for the delimiter string. + * 2. Quoted-strings (if opt.qstring is true): A string which is surrounded by + * double quotes. Escapes in the string are omitted when returning. + * 3. Domain Literals (if opt.dliteral is true): A string which matches the + * dliteral construct in RFC 5322. Escapes here are NOT omitted. + * 4. Comments (if opt.comments is true): Comments are handled specially. In + * practice, decoding the comments in To headers appears to be necessary, so + * comments are not stripped in the output value. Instead, they are emitted + * as if they are a special delimiter. However, all delimiters found within a + * comment are returned as if they were a quoted string, so that consumers + * ignore delimiters within comments. If ignoring comment text completely is + * desired, upon seeing a "(" token, consumers should ignore all tokens until + * a matching ")" is found (note that comments can be nested). + * 5. RFC 2047 encoded-words (if opts.rfc2047 is true): These are strings which + * are the decoded contents of RFC 2047's =?UTF-8?Q?blah?=-style words. + * 6. Atoms: Atoms are defined not in the RFC 5322 sense, but rather as the + * longest sequence of characters that is neither whitespace nor any of the + * special characters above. + * + * The intended interpretation of the stream of output tokens is that they are + * the portions of text which can be safely wrapped in whitespace with no ill + * effect. The output tokens are either strings (which represent individual + * delimiter tokens) or instances of a class that has a customized .toString() + * for output (for quoted strings, atoms, domain literals, and encoded-words). + * Checking for a delimiter MUST use the strictly equals operator (===). For + * example, the proper way to call this method is as follows: + * + * for (let token of getHeaderTokens(rest, ";=", opts)) { + * if (token === ';') { + * // This represents a literal ';' in the string + * } else if (token === '=') { + * // This represents a literal '=' in the string + * } else { + * // If a ";" qstring was parsed, we fall through to here! + * token = token.toString(); + * } + * } + * + * This method does not properly tokenize 5322 in all corner cases; however, + * this is equivalent in those corner cases to an older header parsing + * algorithm, so the algorithm should be correct for all real-world cases. The + * corner cases are as follows: + * 1. Quoted-strings and domain literals are parsed even if they are within a + * comment block (we effectively treat ctext as containing qstring). + * 2. WSP need not be between a qstring and an atom (a"b" produces two tokens, + * a and b). This is an error case, though. + * 3. Legacy comments as display names: We recognize address fields with + * comments, and (a) either drop them if inside addr-spec or (b) preserve + * them as part of the display-name if not. If the display-name is empty + * while the last comment is not, we assume it's the legacy form above and + * take the comment content as the display-name. + * + * @param {String} value The header value, post charset conversion but + * before RFC 2047 decoding, to be parsed. + * @param {String} delimiters A set of delimiters to include as individual + * tokens. + * @param {Object} opts A set of options selecting what to parse. + * @param {Boolean} [opts.qstring] If true, recognize quoted strings. + * @param {Boolean} [opts.dliteral] If true, recognize domain literals. + * @param {Boolean} [opts.comments] If true, recognize comments. + * @param {Boolean} [opts.rfc2047] If true, parse and decode RFC 2047 + * encoded-words. + * @returns {(Token|String)[]} An array of Token objects (which have a toString + * method returning their value) or String objects + * (representing delimiters). + */ +function getHeaderTokens(value, delimiters, opts) { + // The array of parsed tokens. This method used to be a generator, but it + // appears that generators are poorly optimized in current engines, so it was + // converted to not be one. + let tokenList = []; + + /// Represents a non-delimiter token + function Token(token) { + // Unescape all quoted pairs. Any trailing \ is deleted. + this.token = token.replace(/\\(.?)/g, "$1"); + } + Token.prototype.toString = function () { return this.token; }; + + // The start of the current token (e.g., atoms, strings) + let tokenStart = undefined; + // The set of whitespace characters, as defined by RFC 5322 + let wsp = " \t\r\n"; + // If we are a domain literal ([]) or a quoted string ("), this is set to the + // character to look for at the end. + let endQuote = undefined; + // The current depth of comments, since they can be nested. A value 0 means we + // are not in a comment. + let commentDepth = 0; + + // Iterate over every character one character at a time. + let length = value.length; + for (let i = 0; i < length; i++) { + let ch = value[i]; + // If we see a \, no matter what context we are in, ignore the next + // character. + if (ch == '\\') { + i++; + continue; + } + + // If we are in a qstring or a dliteral, process the character only if it is + // what we are looking for to end the quote. + if (endQuote !== undefined) { + if (ch == endQuote && ch == '"') { + // Quoted strings don't include their delimiters. + let text = value.slice(tokenStart + 1, i); + + // If RFC 2047 is enabled, always decode the qstring. + if (opts.rfc2047) + text = decodeRFC2047Words(text); + + tokenList.push(new Token(text)); + endQuote = undefined; + tokenStart = undefined; + } else if (ch == endQuote && ch == ']') { + // Domain literals include their delimiters. + tokenList.push(new Token(value.slice(tokenStart, i + 1))); + endQuote = undefined; + tokenStart = undefined; + } + // Avoid any further processing. + continue; + } + + // If we can match the RFC 2047 encoded-word pattern, we need to decode the + // entire word or set of words. + if (opts.rfc2047 && ch == '=' && i + 1 < value.length && value[i + 1] == '?') { + // RFC 2047 tokens separated only by whitespace are conceptually part of + // the same output token, so we need to decode them all at once. + let encodedWordsRE = /([ \t\r\n]*=\?[^?]*\?[BbQq]\?[^?]*\?=)+/; + let result = encodedWordsRE.exec(value.slice(i)); + if (result !== null) { + // If we were in the middle of a prior token (i.e., something like + // foobar=?UTF-8?Q?blah?=), yield the previous segment as a token. + if (tokenStart !== undefined) { + tokenList.push(new Token(value.slice(tokenStart, i))); + tokenStart = undefined; + } + + // Find out how much we need to decode... + let encWordsLen = result[0].length; + let string = decodeRFC2047Words(value.slice(i, i + encWordsLen), + "UTF-8"); + // Don't make a new Token variable, since we do not want to unescape the + // decoded string. + tokenList.push({ toString: function() { return string; }}); + + // Skip everything we decoded. The -1 is because we don't want to + // include the starting character. + i += encWordsLen - 1; + continue; + } + + // If we are here, then we failed to match the simple 2047 encoded-word + // regular expression, despite the fact that it matched the =? at the + // beginning. Fall through and treat the text as if we aren't trying to + // decode RFC 2047. + } + + // If we reach this point, we're not inside of quoted strings, domain + // literals, or RFC 2047 encoded-words. This means that the characters we + // parse are potential delimiters (unless we're in comments, where + // everything starts to go really wonky). Several things could happen, + // depending on the kind of character we read and whether or not we were in + // the middle of a token. The three values here tell us what we could need + // to do at this point: + // tokenIsEnding: The current character is not able to be accumulated to an + // atom, so we need to flush the atom if there is one. + // tokenIsStarting: The current character could begin an atom (or + // anything that requires us to mark the starting point), so we need to save + // the location. + // isSpecial: The current character is a delimiter that needs to be output. + let tokenIsEnding = false, tokenIsStarting = false, isSpecial = false; + if (wsp.includes(ch)) { + // Whitespace ends current tokens, doesn't emit anything. + tokenIsEnding = true; + } else if (commentDepth == 0 && delimiters.includes(ch)) { + // Delimiters end the current token, and need to be output. They do not + // apply within comments. + tokenIsEnding = true; + isSpecial = true; + } else if (opts.qstring && ch == '"') { + // Quoted strings end the last token and start a new one. + tokenIsEnding = true; + tokenIsStarting = true; + endQuote = ch; + } else if (opts.dliteral && ch == '[') { + // Domain literals end the last token and start a new one. + tokenIsEnding = true; + tokenIsStarting = true; + endQuote = ']'; + } else if (opts.comments && ch == '(') { + // Comments are nested (oh joy). We only really care for the outer + // delimiter, though, which also ends the prior token and needs to be + // output if the consumer requests it. + commentDepth++; + if (commentDepth == 1) { + tokenIsEnding = true; + isSpecial = true; + } else { + tokenIsStarting = true; + } + } else if (opts.comments && ch == ')') { + // Comments are nested (oh joy). We only really care for the outer + // delimiter, though, which also ends the prior token and needs to be + // output if the consumer requests it. + if (commentDepth > 0) + commentDepth--; + if (commentDepth == 0) { + tokenIsEnding = true; + isSpecial = true; + } else { + tokenIsStarting = true; + } + } else { + // Not a delimiter, whitespace, comment, domain literal, or quoted string. + // Must be part of an atom then! + tokenIsStarting = true; + } + + // If our analysis concluded that we closed an open token, and there is an + // open token, then yield that token. + if (tokenIsEnding && tokenStart !== undefined) { + tokenList.push(new Token(value.slice(tokenStart, i))); + tokenStart = undefined; + } + // If we need to output a delimiter, do so. + if (isSpecial) + tokenList.push(ch); + // If our analysis concluded that we could open a token, and no token is + // opened yet, then start the token. + if (tokenIsStarting && tokenStart === undefined) { + tokenStart = i; + } + } + + // That concludes the loop! If there is a currently open token, close that + // token now. + if (tokenStart !== undefined) { + // Error case: a partially-open quoted string is assumed to have a trailing + // " character. + if (endQuote == '"') + tokenList.push(new Token(value.slice(tokenStart + 1))); + else + tokenList.push(new Token(value.slice(tokenStart))); + } + + return tokenList; +} + +/** + * Convert a header value into UTF-16 strings by attempting to decode as UTF-8 + * or another legacy charset. If the header is valid UTF-8, it will be decoded + * as UTF-8; if it is not, the fallbackCharset will be attempted instead. + * + * @param {String} headerValue The header (as a binary string) to attempt + * to convert to UTF-16. + * @param {String} [fallbackCharset] The optional charset to try if UTF-8 + * doesn't work. + * @returns {String} The UTF-16 representation of the string above. + */ +function convert8BitHeader(headerValue, fallbackCharset) { + // Only attempt to convert the headerValue if it contains non-ASCII + // characters. + if (/[\x80-\xff]/.exec(headerValue)) { + // First convert the value to a typed-array for TextDecoder. + let typedarray = mimeutils.stringToTypedArray(headerValue); + + // Don't try UTF-8 as fallback (redundant), and don't try UTF-16 or UTF-32 + // either, since they radically change header interpretation. + // If we have a fallback charset, we want to know if decoding will fail; + // otherwise, we want to replace with substitution chars. + let hasFallback = fallbackCharset && + !fallbackCharset.toLowerCase().startsWith("utf"); + let utf8Decoder = new TextDecoder("utf-8", {fatal: hasFallback}); + try { + headerValue = utf8Decoder.decode(typedarray); + } catch (e) { + // Failed, try the fallback + let decoder = new TextDecoder(fallbackCharset, {fatal: false}); + headerValue = decoder.decode(typedarray); + } + } + return headerValue; +} + +/** + * Decodes all RFC 2047 encoded-words in the input string. The string does not + * necessarily have to contain any such words. This is useful, for example, for + * parsing unstructured headers. + * + * @param {String} headerValue The header which may contain RFC 2047 encoded- + * words. + * @returns {String} A full UTF-16 string with all encoded words expanded. + */ +function decodeRFC2047Words(headerValue) { + // Unfortunately, many implementations of RFC 2047 encoding are actually wrong + // in that they split over-long encoded words without regard for whether or + // not the split point is in the middle of a multibyte character. Therefore, + // we need to be able to handle these situations gracefully. This is done by + // using the decoder in streaming mode so long as the next token is another + // 2047 token with the same charset. + let lastCharset = '', currentDecoder = undefined; + + /** + * Decode a single RFC 2047 token. This function is inline so that we can + * easily close over the lastCharset/currentDecoder variables, needed for + * handling bad RFC 2047 productions properly. + */ + function decode2047Token(token, isLastToken) { + let tokenParts = token.split("?"); + + // If it's obviously not a valid token, return false immediately. + if (tokenParts.length != 5 || tokenParts[4] != '=') + return false; + + // The charset parameter is defined in RFC 2231 to be charset or + // charset*language. We only care about the charset here, so ignore any + // language parameter that gets passed in. + let charset = tokenParts[1].split('*', 1)[0]; + let encoding = tokenParts[2], text = tokenParts[3]; + + let buffer; + if (encoding == 'B' || encoding == 'b') { + // Decode base64. If there's any non-base64 data, treat the string as + // an illegal token. + if (/[^A-Za-z0-9+\/=]/.exec(text)) + return false; + + // Decode the string + buffer = mimeutils.decode_base64(text, false)[0]; + } else if (encoding == 'Q' || encoding == 'q') { + // Q encoding here looks a lot like quoted-printable text. The differences + // between quoted-printable and this are that quoted-printable allows you + // to quote newlines (this doesn't), while this replaces spaces with _. + // We can reuse the decode_qp code here, since newlines are already + // stripped from the header. There is one edge case that could trigger a + // false positive, namely when you have a single = or an = followed by + // whitespace at the end of the string. Such an input string is already + // malformed to begin with, so stripping the = and following input in that + // case should not be an important loss. + buffer = mimeutils.decode_qp(text.replace(/_/g, ' '), false)[0]; + } else { + return false; + } + + // Make the buffer be a typed array for what follows + let stringBuffer = buffer; + buffer = mimeutils.stringToTypedArray(buffer); + + // If we cannot reuse the last decoder, flush out whatever remains. + var output = ''; + if (charset != lastCharset && currentDecoder) { + output += currentDecoder.decode(); + currentDecoder = null; + } + + // Initialize the decoder for this token. + lastCharset = charset; + if (!currentDecoder) { + try { + currentDecoder = new TextDecoder(charset, {fatal: false}); + } catch (e) { + // We don't recognize the charset, so give up. + return false; + } + } + + // Convert this token with the buffer. Note the stream parameter--although + // RFC 2047 tokens aren't supposed to break in the middle of a multibyte + // character, a lot of software messes up and does so because it's hard not + // to (see headeremitter.js for exactly how hard!). + // We must not stream ISO-2022-JP if the buffer switches back to + // the ASCII state, that is, ends in "ESC(B". + // Also, we shouldn't do streaming on the last token. + let doStreaming; + if (isLastToken || + (charset.toUpperCase() == "ISO-2022-JP" && + stringBuffer.endsWith("\x1B(B"))) + doStreaming = {stream: false}; + else + doStreaming = {stream: true}; + return output + currentDecoder.decode(buffer, doStreaming); + } + + // The first step of decoding is to split the string into RFC 2047 and + // non-RFC 2047 tokens. RFC 2047 tokens look like the following: + // =?charset?c?text?=, where c is one of B, b, Q, and q. The split regex does + // some amount of semantic checking, so that malformed RFC 2047 tokens will + // get ignored earlier. + let components = headerValue.split(/(=\?[^?]*\?[BQbq]\?[^?]*\?=)/); + + // Find last RFC 2047 token. + let lastRFC2047Index = -1; + for (let i = 0; i < components.length; i++) { + if (components[i].substring(0, 2) == "=?") + lastRFC2047Index = i; + } + for (let i = 0; i < components.length; i++) { + if (components[i].substring(0, 2) == "=?") { + let decoded = decode2047Token(components[i], i == lastRFC2047Index); + if (decoded !== false) { + // If 2047 decoding succeeded for this bit, rewrite the original value + // with the proper decoding. + components[i] = decoded; + + // We're done processing, so continue to the next link. + continue; + } + } else if (/^[ \t\r\n]*$/.exec(components[i])) { + // Whitespace-only tokens get squashed into nothing, so 2047 tokens will + // be concatenated together. + components[i] = ''; + continue; + } + + // If there was stuff left over from decoding the last 2047 token, flush it + // out. + lastCharset = ''; + if (currentDecoder) { + components[i] = currentDecoder.decode() + components[i]; + currentDecoder = null; + } + } + + // After the for loop, we'll have a set of decoded strings. Concatenate them + // together to make the return value. + return components.join(''); +} + +/////////////////////////////// +// Structured field decoders // +/////////////////////////////// + +/** + * Extract a list of addresses from a header which matches the RFC 5322 + * address-list production, possibly doing RFC 2047 decoding along the way. + * + * The output of this method is an array of elements corresponding to the + * addresses and the groups in the input header. An address is represented by + * an object of the form: + * { + * name: The display name of the address + * email: The address of the object + * } + * while a group is represented by an object of the form: + * { + * name: The display name of the group + * group: An array of address object for members in the group. + * } + * + * @param {String} header The MIME header text to be parsed + * @param {Boolean} doRFC2047 If true, decode RFC 2047 parameters found in the + * header. + * @returns {(Address|Group)[]} An array of the addresses found in the header, + * where each element is of the form mentioned + * above. + */ +function parseAddressingHeader(header, doRFC2047) { + // Default to true + if (doRFC2047 === undefined) + doRFC2047 = true; + + // The final (top-level) results list to append to. + let results = []; + // Temporary results + let addrlist = []; + + // Build up all of the values + let name = '', groupName = '', localPart = '', address = '', comment = ''; + // Indicators of current state + let inAngle = false, inComment = false, needsSpace = false; + let preserveSpace = false; + let commentClosed = false; + + // RFC 5322 §3.4 notes that legacy implementations exist which use a simple + // recipient form where the addr-spec appears without the angle brackets, + // but includes the name of the recipient in parentheses as a comment + // following the addr-spec. While we do not create this format, we still + // want to recognize it, though. + // Furthermore, despite allowing comments in addresses, RFC 5322 §3.4 notes + // that legacy implementations may interpret the comment, and thus it + // recommends not to use them. (Also, they may be illegal as per RFC 5321.) + // While we do not create address fields with comments, we recognize such + // comments during parsing and (a) either drop them if inside addr-spec or + // (b) preserve them as part of the display-name if not. + // If the display-name is empty while the last comment is not, we assume it's + // the legacy form above and take the comment content as the display-name. + // + // When parsing the address field, we at first do not know whether any + // strings belong to the display-name (which may include comments) or to the + // local-part of an addr-spec (where we ignore comments) until we find an + // '@' or an '<' token. Thus, we collect both variants until the fog lifts, + // plus the last comment seen. + let lastComment = ''; + + /** + * Add the parsed mailbox object to the address list. + * If it's in the legacy form above, correct the display-name. + * Also reset any faked flags. + * @param {String} displayName display-name as per RFC 5322 + * @param {String} addrSpec addr-spec as per RFC 5322 + */ + function addToAddrList(displayName, addrSpec) { + // Keep the local-part quoted if it needs to be. + let lp = addrSpec.substring(0, addrSpec.lastIndexOf("@")); + if (/[ !()<>\[\]:;@\\,"]/.exec(lp) !== null) { + addrSpec = '"' + lp.replace(/([\\"])/g, "\\$1") + '"' + + addrSpec.substring(addrSpec.lastIndexOf("@")); + } + + if (displayName === '' && lastComment !== '') { + // Take last comment content as the display-name. + let offset = lastComment[0] === ' ' ? 2 : 1; + displayName = lastComment.substr(offset, lastComment.length - offset - 1); + } + if (displayName !== '' || addrSpec !== '') + addrlist.push({name: displayName, email: addrSpec}); + // Clear pending flags and variables. + name = localPart = address = lastComment = ''; + inAngle = inComment = needsSpace = false; + } + + // Main parsing loop + for (let token of getHeaderTokens(header, ":,;<>@", + {qstring: true, comments: true, dliteral: true, rfc2047: doRFC2047})) { + if (token === ':') { + groupName = name; + name = ''; + localPart = ''; + // If we had prior email address results, commit them to the top-level. + if (addrlist.length > 0) + results = results.concat(addrlist); + addrlist = []; + } else if (token === '<') { + if (inAngle) { + // Interpret the address we were parsing as a name. + if (address.length > 0) { + name = address; + } + localPart = address = ''; + } else { + inAngle = true; + } + } else if (token === '>') { + inAngle = false; + // Forget addr-spec comments. + lastComment = ''; + } else if (token === '(') { + inComment = true; + // The needsSpace flag may not always be set even if it should be, + // e.g. for a comment behind an angle-addr. + // Also, we need to restore the needsSpace flag if we ignore the comment. + preserveSpace = needsSpace; + if (!needsSpace) + needsSpace = name !== '' && name.substr(-1) !== ' '; + comment = needsSpace ? ' (' : '('; + commentClosed = false; + } else if (token === ')') { + inComment = false; + comment += ')'; + lastComment = comment; + // The comment may be part of the name, but not of the local-part. + // Enforce a space behind the comment only when not ignoring it. + if (inAngle) { + needsSpace = preserveSpace; + } else { + name += comment; + needsSpace = true; + } + commentClosed = true; + continue; + } else if (token === '@') { + // An @ means we see an email address. If we're not within <> brackets, + // then we just parsed an email address instead of a display name. Empty + // out the display name for the current production. + if (!inAngle) { + address = localPart; + name = ''; + localPart = ''; + // The remainder of this mailbox is part of an addr-spec. + inAngle = true; + } + address += '@'; + } else if (token === ',') { + // A comma ends the current name. If we have something that's kind of a + // name, add it to the result list. If we don't, then our input looks like + // To: , , -> don't bother adding an empty entry. + addToAddrList(name, address); + } else if (token === ';') { + // Add pending name to the list + addToAddrList(name, address); + + // If no group name was found, treat the ';' as a ','. In any case, we + // need to copy the results of addrlist into either a new group object or + // the main list. + if (groupName === '') { + results = results.concat(addrlist); + } else { + results.push({ + name: groupName, + group: addrlist + }); + } + // ... and reset every other variable. + addrlist = []; + groupName = ''; + } else { + // This is either comment content, a quoted-string, or some span of + // dots and atoms. + + // Ignore the needs space if we're a "close" delimiter token. + let spacedToken = token; + if (needsSpace && token.toString()[0] != '.') + spacedToken = ' ' + spacedToken; + + // Which field do we add this data to? + if (inComment) { + comment += spacedToken; + } else if (inAngle) { + address += spacedToken; + } else { + name += spacedToken; + // Never add a space to the local-part, if we just ignored a comment. + if (commentClosed) { + localPart += token; + commentClosed = false; + } else { + localPart += spacedToken; + } + } + + // We need space for the next token if we aren't some kind of comment or + // . delimiter. + needsSpace = token.toString()[0] != '.'; + // The fall-through case after this resets needsSpace to false, and we + // don't want that! + continue; + } + + // If we just parsed a delimiter, we don't need any space for the next + // token. + needsSpace = false; + } + + // If we're missing the final ';' of a group, assume it was present. Also, add + // in the details of any email/address that we previously saw. + addToAddrList(name, address); + if (groupName !== '') { + results.push({name: groupName, group: addrlist}); + addrlist = []; + } + + // Add the current address list build-up to the list of addresses, and return + // the whole array to the caller. + return results.concat(addrlist); +} + +/** + * Extract parameters from a header which is a series of ;-separated + * attribute=value tokens. + * + * @param {String} headerValue The MIME header value to parse. + * @param {Boolean} doRFC2047 If true, decode RFC 2047 encoded-words. + * @param {Boolean} doRFC2231 If true, decode RFC 2231 encoded parameters. + * @return {Map(String -> String)} A map of parameter names to parameter values. + * The property preSemi is set to the token that + * precedes the first semicolon. + */ +function parseParameterHeader(headerValue, doRFC2047, doRFC2231) { + // The basic syntax of headerValue is token [; token = token-or-qstring]* + // Copying more or less liberally from nsMIMEHeaderParamImpl: + // The first token is the text to the first whitespace or semicolon. + var semi = headerValue.indexOf(";"); + if (semi < 0) { + var start = headerValue; + var rest = ''; + } else { + var start = headerValue.substring(0, semi); + var rest = headerValue.substring(semi); // Include the semicolon + } + // Strip start to be <WSP><nowsp><WSP>. + start = start.trim().split(/[ \t\r\n]/)[0]; + + // Decode the the parameter tokens. + let opts = {qstring: true, rfc2047: doRFC2047}; + // Name is the name of the parameter, inName is true iff we don't have a name + // yet. + let name = '', inName = true; + // Matches is a list of [name, value] pairs, where we found something that + // looks like name=value in the input string. + let matches = []; + for (let token of getHeaderTokens(rest, ";=", opts)) { + if (token === ';') { + // If we didn't find a name yet (we have ... tokenA; tokenB), push the + // name with an empty token instead. + if (name != '' && inName == false) + matches.push([name, '']); + name = ''; + inName = true; + } else if (token === '=') { + inName = false; + } else if (inName && name == '') { + name = token.toString(); + } else if (!inName && name != '') { + token = token.toString(); + // RFC 2231 doesn't make it clear if %-encoding is supposed to happen + // within a quoted string, but this is very much required in practice. If + // it ends with a '*', then the string is an extended-value, which means + // that its value may be %-encoded. + if (doRFC2231 && name.endsWith('*')) { + token = token.replace(/%([0-9A-Fa-f]{2})/g, + function percent_deencode(match, hexchars) { + return String.fromCharCode(parseInt(hexchars, 16)); + }); + } + matches.push([name, token]); + // Clear the name, so we ignore anything afterwards. + name = ''; + } else if (inName) { + // We have ...; tokenA tokenB ... -> ignore both tokens + name = ''; // Error recovery, ignore this one + } + } + // If we have a leftover ...; tokenA, push the tokenA + if (name != '' && inName == false) + matches.push([name, '']); + + // Now matches holds the parameters, so clean up for RFC 2231. There are three + // cases: param=val, param*=us-ascii'en-US'blah, and param*n= variants. The + // order of preference is to pick the middle, then the last, then the first. + // Note that we already unpacked %-encoded values. + + // simpleValues is just a straight parameter -> value map. + // charsetValues is the parameter -> value map, although values are stored + // before charset decoding happens. + // continuationValues maps parameter -> array of values, with extra properties + // valid (if we decided we couldn't do anything anymore) and hasCharset (which + // records if we need to decode the charset parameter or not). + var simpleValues = new Map(), charsetValues = new Map(), + continuationValues = new Map(); + for (let pair of matches) { + let name = pair[0]; + let value = pair[1]; + // Get first index, not last index, so we match param*0*= like param*0=. + let star = name.indexOf('*'); + if (star == -1) { + // This is the case of param=val. Select the first value here, if there + // are multiple ones. + if (!simpleValues.has(name)) + simpleValues.set(name, value); + } else if (star == name.length - 1) { + // This is the case of param*=us-ascii'en-US'blah. + name = name.substring(0, star); + // Again, select only the first value here. + if (!charsetValues.has(name)) + charsetValues.set(name, value); + } else { + // This is the case of param*0= or param*0*=. + let param = name.substring(0, star); + let entry = continuationValues.get(param); + // Did we previously find this one to be bungled? Then ignore it. + if (continuationValues.has(param) && !entry.valid) + continue; + + // If we haven't seen it yet, set up entry already. Note that entries are + // not straight string values but rather [valid, hasCharset, param0, ... ] + if (!continuationValues.has(param)) { + entry = new Array(); + entry.valid = true; + entry.hasCharset = undefined; + continuationValues.set(param, entry); + } + + // When the string ends in *, we need to charset decoding. + // Note that the star is only meaningful for the *0*= case. + let lastStar = name[name.length - 1] == '*'; + let number = name.substring(star + 1, name.length - (lastStar ? 1 : 0)); + if (number == '0') + entry.hasCharset = lastStar; + + // Is the continuation number illegal? + else if ((number[0] == '0' && number != '0') || + !(/^[0-9]+$/.test(number))) { + entry.valid = false; + continue; + } + // Normalize to an integer + number = parseInt(number, 10); + + // Is this a repeat? If so, bail. + if (entry[number] !== undefined) { + entry.valid = false; + continue; + } + + // Set the value for this continuation index. JS's magic array setter will + // expand the array if necessary. + entry[number] = value; + } + } + + // Build the actual parameter array from the parsed values + var values = new Map(); + // Simple values have lowest priority, so just add everything into the result + // now. + for (let pair of simpleValues) { + values.set(pair[0], pair[1]); + } + + if (doRFC2231) { + // Continuation values come next + for (let pair of continuationValues) { + let name = pair[0]; + let entry = pair[1]; + // If we never saw a param*0= or param*0*= value, then we can't do any + // reasoning about what it looks like, so bail out now. + if (entry.hasCharset === undefined) continue; + + // Use as many entries in the array as are valid--if we are missing an + // entry, stop there. + let valid = true; + for (var i = 0; valid && i < entry.length; i++) + if (entry[i] === undefined) + valid = false; + + // Concatenate as many parameters as are valid. If we need to decode thec + // charset, do so now. + var value = entry.slice(0, i).join(''); + if (entry.hasCharset) { + try { + value = decode2231Value(value); + } catch (e) { + // Bad charset, don't add anything. + continue; + } + } + // Finally, add this to the output array. + values.set(name, value); + } + + // Highest priority is the charset conversion. + for (let pair of charsetValues) { + try { + values.set(pair[0], decode2231Value(pair[1])); + } catch (e) { + // Bad charset, don't add anything. + } + } + } + + // Finally, return the values computed above. + values.preSemi = start; + return values; +} + +/** + * Convert a RFC 2231-encoded string parameter into a Unicode version of the + * string. This assumes that percent-decoding has already been applied. + * + * @param {String} value The RFC 2231-encoded string to decode. + * @return The Unicode version of the string. + */ +function decode2231Value(value) { + let quote1 = value.indexOf("'"); + let quote2 = quote1 >= 0 ? value.indexOf("'", quote1 + 1) : -1; + + let charset = (quote1 >= 0 ? value.substring(0, quote1) : ""); + // It turns out that the language isn't useful anywhere in our codebase for + // the present time, so we will safely ignore it. + //var language = (quote2 >= 0 ? value.substring(quote1 + 2, quote2) : ""); + value = value.substring(Math.max(quote1, quote2) + 1); + + // Convert the value into a typed array for decoding + let typedarray = mimeutils.stringToTypedArray(value); + + // Decode the charset. If the charset isn't found, we throw an error. Try to + // fallback in that case. + return new TextDecoder(charset, {fatal: true}) + .decode(typedarray, {stream: false}); +} + +// This is a map of known timezone abbreviations, for fallback in obsolete Date +// productions. +var kKnownTZs = { + // The following timezones are explicitly listed in RFC 5322. + "UT": "+0000", "GMT": "+0000", + "EST": "-0500", "EDT": "-0400", + "CST": "-0600", "CDT": "-0500", + "MST": "-0700", "MDT": "-0600", + "PST": "-0800", "PDT": "-0700", + // The following are time zones copied from NSPR's prtime.c + "AST": "-0400", // Atlantic Standard Time + "NST": "-0330", // Newfoundland Standard Time + "BST": "+0100", // British Summer Time + "MET": "+0100", // Middle Europe Time + "EET": "+0200", // Eastern Europe Time + "JST": "+0900" // Japan Standard Time +}; + +/** + * Parse a header that contains a date-time definition according to RFC 5322. + * The result is a JS date object with the same timestamp as the header. + * + * The dates returned by this parser cannot be reliably converted back into the + * original header for two reasons. First, JS date objects cannot retain the + * timezone information they were initialized with, so reserializing a date + * header would necessarily produce a date in either the current timezone or in + * UTC. Second, JS dates measure time as seconds elapsed from the POSIX epoch + * excluding leap seconds. Any timestamp containing a leap second is instead + * converted into one that represents the next second. + * + * Dates that do not match the RFC 5322 production are instead attempted to + * parse using the Date.parse function. The strings that are accepted by + * Date.parse are not fully defined by the standard, but most implementations + * should accept strings that look rather close to RFC 5322 strings. Truly + * invalid dates produce a formulation that results in an invalid date, + * detectable by having its .getTime() method return NaN. + * + * @param {String} header The MIME header value to parse. + * @returns {Date} The date contained within the header, as described + * above. + */ +function parseDateHeader(header) { + let tokens = getHeaderTokens(header, ",:", {}).map(x => x.toString()); + // What does a Date header look like? In practice, most date headers devolve + // into Date: [dow ,] dom mon year hh:mm:ss tzoff [(abbrev)], with the day of + // week mostly present and the timezone abbreviation mostly absent. + + // First, ignore the day-of-the-week if present. This would be the first two + // tokens. + if (tokens.length > 1 && tokens[1] === ',') + tokens = tokens.slice(2); + + // If there are too few tokens, the date is obviously invalid. + if (tokens.length < 8) + return new Date(NaN); + + // Save off the numeric tokens + let day = parseInt(tokens[0]); + // month is tokens[1] + let year = parseInt(tokens[2]); + let hours = parseInt(tokens[3]); + // tokens[4] === ':' + let minutes = parseInt(tokens[5]); + // tokens[6] === ':' + let seconds = parseInt(tokens[7]); + + // Compute the month. Check only the first three digits for equality; this + // allows us to accept, e.g., "January" in lieu of "Jan." + let month = mimeutils.kMonthNames.indexOf(tokens[1].slice(0, 3)); + // If the month name is not recognized, make the result illegal. + if (month < 0) + month = NaN; + + // Compute the full year if it's only 2 digits. RFC 5322 states that the + // cutoff is 50 instead of 70. + if (year < 100) { + year += year < 50 ? 2000 : 1900; + } + + // Compute the timezone offset. If it's not in the form ±hhmm, convert it to + // that form. + let tzoffset = tokens[8]; + if (tzoffset in kKnownTZs) + tzoffset = kKnownTZs[tzoffset]; + let decompose = /^([+-])(\d\d)(\d\d)$/.exec(tzoffset); + // Unknown? Make it +0000 + if (decompose === null) + decompose = ['+0000', '+', '00', '00']; + let tzOffsetInMin = parseInt(decompose[2]) * 60 + parseInt(decompose[3]); + if (decompose[1] == '-') + tzOffsetInMin = -tzOffsetInMin; + + // How do we make the date at this point? Well, the JS date's constructor + // builds the time in terms of the local timezone. To account for the offset + // properly, we need to build in UTC. + let finalDate = new Date(Date.UTC(year, month, day, hours, minutes, seconds) + - tzOffsetInMin * 60 * 1000); + + // Suppose our header was mangled and we couldn't read it--some of the fields + // became undefined. In that case, the date would become invalid, and the + // indication that it is so is that the underlying number is a NaN. In that + // scenario, we could build attempt to use JS Date parsing as a last-ditch + // attempt. But it's not clear that such messages really exist in practice, + // and the valid formats for Date in ES6 are unspecified. + return finalDate; +} + +//////////////////////////////////////// +// Structured header decoding support // +//////////////////////////////////////// + +// Load the default structured decoders +var structuredDecoders = new Map(); +var structuredHeaders = require('./structuredHeaders'); +var preferredSpellings = structuredHeaders.spellings; +var forbiddenHeaders = new Set(); +for (let pair of structuredHeaders.decoders) { + addStructuredDecoder(pair[0], pair[1]); + forbiddenHeaders.add(pair[0].toLowerCase()); +} + +/** + * Use an already-registered structured decoder to parse the value of the header + * into a structured representation. + * + * As this method is designed to be used for the internal MIME Parser to convert + * the raw header values to well-structured values, value is intended to be an + * array consisting of all occurences of the header in order. However, for ease + * of use by other callers, it can also be treated as a string. + * + * If the decoder for the header is not found, an exception will be thrown. + * + * A large set of headers have pre-defined structured decoders; these decoders + * cannot be overrided with addStructuredDecoder, as doing so could prevent the + * MIME or message parsers from working properly. The pre-defined structured + * headers break down into five clases of results, plus some ad-hoc + * representations. They are: + * + * Addressing headers (results are the same as parseAddressingHeader): + * - Approved + * - Bcc + * - Cc + * - Delivered-To + * - Disposition-Notification-To + * - From + * - Mail-Reply-To + * - Mail-Followup-To + * - Reply-To + * - Resent-Bcc + * - Resent-Cc + * - Resent-From + * - Resent-Reply-To + * - Resent-Sender + * - Resent-To + * - Return-Receipt-To + * - Sender + * - To + * + * Date headers (results are the same as parseDateHeader): + * - Date + * - Expires + * - Injection-Date + * - NNTP-Posting-Date + * - Resent-Date + * + * References headers (results are the same as parseReferencesHeader): + * - (TODO: Parsing support for these headers is currently unsupported) + * + * Message-ID headers (results are the first entry of the result of + * parseReferencesHeader): + * - (TODO: Parsing support for these headers is currently unsupported) + * + * Unstructured headers (results are merely decoded according to RFC 2047): + * - Comments + * - Content-Description + * - Keywords + * - Subject + * + * The ad-hoc headers and their resulting formats are as follows: + * Content-Type: returns a JS Map of parameter names (in lower case) to their + * values, along with the following extra properties defined on the map: + * - mediatype: the type to the left of '/' (e.g., 'text', 'message') + * - subtype: the type to the right of '/' (e.g., 'plain', 'rfc822') + * - type: the full typename (e.g., 'text/plain') + * RFC 2047 and RFC 2231 decoding is applied where appropriate. The values of + * the type, mediatype, and subtype attributes are all normalized to lower-case, + * as are the names of all parameters. + * + * Content-Transfer-Encoding: the first value is converted to lower-case. + * + * @param {String} header The name of the header of the values. + * @param {String|Array} value The value(s) of the headers, after charset + * conversion (if any) has been applied. If it is + * an array, the headers are listed in the order + * they appear in the message. + * @returns {Object} A structured representation of the header values. + */ +function parseStructuredHeader(header, value) { + // Enforce that the parameter is an array. If it's a string, make it a + // 1-element array. + if (typeof value === "string" || value instanceof String) + value = [value]; + if (!Array.isArray(value)) + throw new TypeError("Header value is not an array: " + value); + + // Lookup the header in our decoders; if present, use that to decode the + // header. + let lowerHeader = header.toLowerCase(); + if (structuredDecoders.has(lowerHeader)) { + return structuredDecoders.get(lowerHeader).call(headerparser, value); + } + + // If not present, throw an exception. + throw new Error("Unknown structured header: " + header); +} + +/** + * Add a custom structured MIME decoder to the set of known decoders. These + * decoders are used for {@link parseStructuredHeader} and similar functions to + * encode richer, more structured values instead of relying on string + * representations everywhere. + * + * Structured decoders are functions which take in a single parameter consisting + * of an array of the string values of the header, in order that they appear in + * the message. These headers have had the charset conversion (if necessary) + * applied to them already. The this parameter of the function is set to be the + * jsmime.headerparser module. + * + * There is a large set of structured decoders built-in to the jsmime library + * already. As these headers are fundamental to the workings of jsmime, + * attempting to replace them with a custom version will instead produce an + * exception. + * + * @param {String} header The header name (in any case) + * for which the decoder will be + * used. + * @param {Function(String[] -> Object)} decoder The structured decoder + * function. + */ +function addStructuredDecoder(header, decoder) { + let lowerHeader = header.toLowerCase(); + if (forbiddenHeaders.has(lowerHeader)) + throw new Error("Cannot override header: " + header); + structuredDecoders.set(lowerHeader, decoder); + if (!preferredSpellings.has(lowerHeader)) + preferredSpellings.set(lowerHeader, header); +} + +headerparser.addStructuredDecoder = addStructuredDecoder; +headerparser.convert8BitHeader = convert8BitHeader; +headerparser.decodeRFC2047Words = decodeRFC2047Words; +headerparser.getHeaderTokens = getHeaderTokens; +headerparser.parseAddressingHeader = parseAddressingHeader; +headerparser.parseDateHeader = parseDateHeader; +headerparser.parseParameterHeader = parseParameterHeader; +headerparser.parseStructuredHeader = parseStructuredHeader; +return Object.freeze(headerparser); + +}); + +//////////////////////////////////////////////////////////////////////////////// +// JavaScript Raw MIME Parser // +//////////////////////////////////////////////////////////////////////////////// + +/** + * The parser implemented in this file produces a MIME part tree for a given + * input message via a streaming callback interface. It does not, by itself, + * understand concepts like attachments (hence the term 'Raw'); the consumer + * must translate output into such a format. + * + * Charsets: + * The MIME specifications permit a single message to contain multiple charsets + * (or perhaps none) as raw octets. As JavaScript strings are implicitly + * implemented in UTF-16, it is possible that some engines will attempt to + * convert these strings using an incorrect charset or simply fail to convert + * them at all. This parser assumes that its input is in the form of a "binary + * string", a string that uses only the first 256 characters of Unicode to + * represent the individual octets. To verify that charsets are not getting + * mangled elsewhere in the pipeline, the auxiliary test file test/data/charsets + * can be used. + * + * This parser attempts to hide the charset details from clients as much as + * possible. The resulting values of structured headers are always converted + * into proper Unicode strings before being exposed to clients; getting at the + * raw binary string data can only be done via getRawHeader. The .charset + * parameter on header objects, if changed, changes the fallback charset used + * for headers. It is initialized to the presumed charset of the corresponding + * part, taking into account the charset and force-charset options of the + * parser. Body parts are only converted into Unicode strings if the strformat + * option is set to Unicode. Even then, only the bodies of parts with a media + * type of text are converted to Unicode strings using available charset data; + * other parts are retained as Uint8Array objects. + * + * Part numbering: + * Since the output is a streaming format, individual parts are identified by a + * numbering scheme. The intent of the numbering scheme for parts is to comply + * with the part numbers as dictated by RFC 3501 as much possible; however, + * that scheme does have several edge cases which would, if strictly followed, + * make it impossible to refer to certain parts of the message. In addition, we + * wish to make it possible to refer to parts which are not discoverable in the + * original MIME tree but are still viewable as parts. The part numbering + * scheme is as follows: + * - Individual sections of a multipart/* body are numbered in increasing order + * sequentially, starting from 1. Note that the prologue and the epilogue of + * a multipart/* body are not considered entities and are therefore not + * included in the part numbering scheme (there is no way to refer to them). + * - The numbers of multipart/* parts are separated by `.' characters. + * - The outermost message is referred to by use of the empty string. + * --> The following segments are not accounted for by IMAP part numbering. <-- + * - The body of any message/rfc822 or similar part is distinguished from the + * message part as a whole by appending a `$' character. This does not apply + * to the outermost message/rfc822 envelope. + */ + +def('mimeparser', function(require) { +"use strict"; + +var mimeutils = require('./mimeutils'); +var headerparser = require('./headerparser'); +var spellings = require('./structuredHeaders').spellings; + +/** + * An object that represents the structured MIME headers for a message. + * + * This class is primarily used as the 'headers' parameter in the startPart + * callback on handlers for MimeParser. As such, it is designed to do the right + * thing in common cases as much as possible, with some advanced customization + * possible for clients that need such flexibility. + * + * In a nutshell, this class stores the raw headers as an internal Map. The + * structured headers are not computed until they are actually used, which means + * that potentially expensive structuring (e.g., doing manual DKIM validation) + * can be performed as a structured decoder without impeding performance for + * those who just want a few common headers. + * + * The outer API of this class is intended to be similar to a read-only Map + * object (complete with iterability support), with a few extra properties to + * represent things that are hard to determine properly from headers. The keys + * used are "preferred spellings" of the headers, although the get and has + * methods will accept header parameters of any case. Preferred spellings are + * derived from the name passed to addStructuredDecoder/addStructuredEncoder; if + * no structured decoder has been registered, then the name capitalizes the + * first letter of every word in the header name. + * + * Extra properties compared to a Map object are: + * - charset: This field represents the assumed charset of the associated MIME + * body. It is prefilled using a combination of the charset and force-charset + * options on the associated MimeParser instance as well as attempting to find + * a charset parameter in the Content-Type header. + * + * If the force-charset option is false, the charset is guessed first using + * the Content-Type header's charset parameter, falling back to the charset + * option if it is present. If the force-charset option is true, the charset + * is initially set to the charset option. This initial guessed value can be + * overridden at any time by simply setting the field on this object. + * + * The charset is better reflected as a parameter of the body rather than the + * headers; this is ultimately the charset parameter that will be used if a + * body part is being converted to a Unicode strformat. Headers are converted + * using headerparser.convert8BitHeader, and this field is used as the + * fallbackCharset parameter, which will always to attempt to decode as UTF-8 + * first (in accordance with RFC 6532) and will refuse to decode as UTF-16 or + * UTF-32, as ASCII is not a subset of those charsets. + * + * - rawHeaderText: This read-only field contains the original header text from + * which headers were parsed, preserving case and whitespace (including + * alternate line endings instead of CRLF) exactly. If the header text begins + * with the mbox delimiter (i.e., a line that begins with "From "), then that + * is excluded from the rawHeaderText value and is not reflected anywhere in + * this object. + * + * - contentType: This field contains the structured representation of the + * Content-Type header, if it is present. If it is not present, it is set to + * the structured representation of the default Content-Type for a part (as + * this data is not easily guessed given only MIME tree events). + * + * The constructor for these objects is not externally exported, and thus they + * can only be created via MimeParser. + * + * @param rawHeaderText {BinaryString} The contents of the MIME headers to be + * parsed. + * @param options {Object} Options for the header parser. + * @param options.stripcontinuations {Boolean} If true, elide CRLFs from the + * raw header output. + */ +function StructuredHeaders(rawHeaderText, options) { + // An individual header is terminated by a CRLF, except if the CRLF is + // followed by a SP or TAB. Use negative lookahead to capture the latter case, + // and don't capture the strings or else split results get nasty. + let values = rawHeaderText.split(/(?:\r\n|\n)(?![ \t])|\r(?![ \t\n])/); + + // Ignore the first "header" if it begins with an mbox delimiter + if (values.length > 0 && values[0].substring(0, 5) == "From ") { + values.shift(); + // Elide the mbox delimiter from this._headerData + if (values.length == 0) + rawHeaderText = ''; + else + rawHeaderText = rawHeaderText.substring(rawHeaderText.indexOf(values[0])); + } + + let headers = new Map(); + for (let i = 0; i < values.length; i++) { + // Look for a colon. If it's not present, this header line is malformed, + // perhaps by premature EOF or similar. + let colon = values[i].indexOf(":"); + if (colon >= 0) { + var header = values[i].substring(0, colon); + var val = values[i].substring(colon + 1).trim(); + if (options.stripcontinuations) + val = val.replace(/[\r\n]/g, ''); + } else { + var header = values[i]; + var val = ''; + } + + // Canonicalize the header in lower-case form. + header = header.trim().toLowerCase(); + // Omit "empty" headers + if (header == '') + continue; + + // We keep an array of values for each header, since a given header may be + // repeated multiple times. + if (headers.has(header)) { + headers.get(header).push(val); + } else { + headers.set(header, [val]); + } + } + + /** + * A map of header names to arrays of raw values found in this header block. + * @private + */ + this._rawHeaders = headers; + /** + * Cached results of structured header parsing. + * @private + */ + this._cachedHeaders = new Map(); + Object.defineProperty(this, "rawHeaderText", + {get: function () { return rawHeaderText; }}); + Object.defineProperty(this, "size", + {get: function () { return this._rawHeaders.size; }}); + Object.defineProperty(this, "charset", { + get: function () { return this._charset; }, + set: function (value) { + this._charset = value; + // Clear the cached headers, since this could change their values + this._cachedHeaders.clear(); + } + }); + + // Default to the charset, until the message parser overrides us. + if ('charset' in options) + this._charset = options.charset; + else + this._charset = null; + + // If we have a Content-Type header, set contentType to return the structured + // representation. We don't set the value off the bat, since we want to let + // someone who changes the charset affect the values of 8-bit parameters. + Object.defineProperty(this, "contentType", { + configurable: true, + get: function () { return this.get('Content-Type'); } + }); +} + +/** + * Get a raw header. + * + * Raw headers are an array of the header values, listed in order that they were + * specified in the header block, and without any attempt to convert charsets or + * apply RFC 2047 decoding. For example, in the following message (where the + * <XX> is meant to represent binary-octets): + * + * X-Header: Value A + * X-Header: V<C3><A5>lue B + * Header2: Q + * + * the result of calling getRawHeader('X-Header') or getRawHeader('x-header') + * would be ['Value A', 'V\xC3\xA5lue B'] and the result of + * getRawHeader('Header2') would be ['Q']. + * + * @param headerName {String} The header name for which to get header values. + * @returns {BinaryString[]} The raw header values (with no charset conversion + * applied). + */ +StructuredHeaders.prototype.getRawHeader = function (headerName) { + return this._rawHeaders.get(headerName.toLowerCase()); +}; + +/** + * Retrieve a structured version of the header. + * + * If there is a registered structured decoder (registration happens via + * headerparser.addStructuredDecoder), then the result of calling that decoder + * on the charset-corrected version of the header is returned. Otherwise, the + * values are charset-corrected and RFC 2047 decoding is applied as if the + * header were an unstructured header. + * + * A substantial set of headers have pre-registed structured decoders, which, in + * some cases, are unable to be overridden due to their importance in the + * functioning of the parser code itself. + * + * @param headerName {String} The header name for which to get the header value. + * @returns The structured header value of the output. + */ +StructuredHeaders.prototype.get = function (headerName) { + // Normalize the header name to lower case + headerName = headerName.toLowerCase(); + + // First, check the cache for the header value + if (this._cachedHeaders.has(headerName)) + return this._cachedHeaders.get(headerName); + + // Not cached? Grab it [propagating lack of header to caller] + let headerValue = this._rawHeaders.get(headerName); + if (headerValue === undefined) + return headerValue; + + // Convert the header to Unicode + let charset = this.charset; + headerValue = headerValue.map(function (value) { + return headerparser.convert8BitHeader(value, charset); + }); + + // If there is a structured decoder, use that; otherwise, assume that the + // header is unstructured and only do RFC 2047 conversion + let structured; + try { + structured = headerparser.parseStructuredHeader(headerName, headerValue); + } catch (e) { + structured = headerValue.map(function (value) { + return headerparser.decodeRFC2047Words(value); + }); + } + + // Cache the result and return it + this._cachedHeaders.set(headerName, structured); + return structured; +}; + +/** + * Check if the message has the given header. + * + * @param headerName {String} The header name for which to get the header value. + * @returns {Boolean} True if the header is present in this header block. + */ +StructuredHeaders.prototype.has = function (headerName) { + // Check for presence in the raw headers instead of cached headers. + return this._rawHeaders.has(headerName.toLowerCase()); +}; + +// Make a custom iterator. Presently, support for Symbol isn't yet present in +// SpiderMonkey (or V8 for that matter), so type-pun the name for now. +var JS_HAS_SYMBOLS = typeof Symbol === "function"; +var ITERATOR_SYMBOL = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator"; + +/** + * An equivalent of Map.@@iterator, applied to the structured header + * representations. This is the function that makes + * for (let [header, value] of headers) work properly. + */ +StructuredHeaders.prototype[ITERATOR_SYMBOL] = function*() { + // Iterate over all the raw headers, and use the cached headers to retrieve + // them. + for (let headerName of this.keys()) { + yield [headerName, this.get(headerName)]; + } +}; + +/** + * An equivalent of Map.forEach, applied to the structured header + * representations. + * + * @param callback {Function(value, name, headers)} The callback to call for + * each header/value combo. + * @param thisarg {Object} The parameter that will be + * the |this| of the callback. + */ +StructuredHeaders.prototype.forEach = function (callback, thisarg) { + for (let [header, value] of this) { + callback.call(thisarg, value, header, this); + } +}; + +/** + * An equivalent of Map.entries, applied to the structured header + * representations. + */ +StructuredHeaders.prototype.entries = + StructuredHeaders.prototype[Symbol.iterator]; + +/// This function maps lower case names to a pseudo-preferred spelling. +function capitalize(headerName) { + return headerName.replace(/\b[a-z]/g, function (match) { + return match.toUpperCase(); + }); +} + +/** + * An equivalent of Map.keys, applied to the structured header representations. + */ +StructuredHeaders.prototype.keys = function*() { + for (let name of this._rawHeaders.keys()) { + yield spellings.get(name) || capitalize(name); + } +}; + +/** + * An equivalent of Map.values, applied to the structured header + * representations. + */ +StructuredHeaders.prototype.values = function* () { + for (let [, value] of this) { + yield value; + } +}; + + +/** + * A MIME parser. + * + * The inputs to the constructor consist of a callback object which receives + * information about the output data and an optional object containing the + * settings for the parser. + * + * The first parameter, emitter, is an object which contains several callbacks. + * Note that any and all of these methods are optional; the parser will not + * crash if one is missing. The callbacks are as follows: + * startMessage() + * Called when the stream to be parsed has started delivering data. This + * will be called exactly once, before any other call. + * endMessage() + * Called after all data has been delivered and the message parsing has + * been completed. This will be called exactly once, after any other call. + * startPart(string partNum, object headers) + * Called after the headers for a body part (including the top-level + * message) have been parsed. The first parameter is the part number (see + * the discussion on part numbering). The second parameter is an instance + * of StructuredHeaders that represents all of the headers for the part. + * endPart(string partNum) + * Called after all of the data for a body part (including sub-parts) has + * been parsed. The first parameter is the part number. + * deliverPartData(string partNum, {string,typedarray} data) + * Called when some data for a body part has been delivered. The first + * parameter is the part number. The second parameter is the data which is + * being delivered; the exact type of this data depends on the options + * used. Note that data is only delivered for leaf body parts. + * + * The second parameter, options, is an optional object containing the options + * for the parser. The following are the options that the parser may use: + * pruneat: <string> [default=""] + * Treat the message as starting at the given part number, so that no parts + * above <string> are returned. + * bodyformat: one of {none, raw, nodecode, decode} [default=nodecode] + * How to return the bodies of parts: + * none: no part data is returned + * raw: the body of the part is passed through raw + * nodecode: the body is passed through without decoding QP/Base64 + * decode: quoted-printable and base64 are fully decoded + * strformat: one of {binarystring, unicode, typedarray} [default=binarystring] + * How to treat output strings: + * binarystring: Data is a JS string with chars in the range [\x00-\xff] + * unicode: Data for text parts is converted to UTF-16; data for other + * parts is a typed array buffer, akin to typedarray. + * typedarray: Data is a JS typed array buffer + * charset: <string> [default=""] + * What charset to assume if no charset information is explicitly provided. + * This only matters if strformat is unicode. See above note on charsets + * for more details. + * force-charset: <boolean> [default=false] + * If true, this coerces all types to use the charset option, even if the + * message specifies a different content-type. + * stripcontinuations: <boolean> [default=true] + * If true, then the newlines in headers are removed in the returned + * header objects. + * onerror: <function(thrown error)> [default = nop-function] + * An error function that is called if an emitter callback throws an error. + * By default, such errors are swallowed by the parser. If you want the + * parser itself to throw an error, rethrow it via the onerror function. + */ +function MimeParser(emitter, options) { + /// The actual emitter + this._emitter = emitter; + /// Options for the parser (those listed here are defaults) + this._options = { + pruneat: "", + bodyformat: "nodecode", + strformat: "binarystring", + stripcontinuations: true, + charset: "", + "force-charset": false, + onerror: function swallow(error) {} + }; + // Load the options as a copy here (prevents people from changing on the fly). + if (options) + for (var opt in options) { + this._options[opt] = options[opt]; + } + + // Ensure that the error function is in fact a function + if (typeof this._options.onerror != "function") + throw new Exception("onerror callback must be a function"); + + // Reset the parser + this.resetParser(); +} + +/** + * Resets the parser to read a new message. This method need not be called + * immediately after construction. + */ +MimeParser.prototype.resetParser = function () { + /// Current parser state + this._state = PARSING_HEADERS; + /// Input data that needs to be held for buffer conditioning + this._holdData = ''; + /// Complete collection of headers (also used to accumulate _headerData) + this._headerData = ''; + /// Whether or not emitter.startMessage has been called + this._triggeredCall = false; + + /// Splitting input + this._splitRegex = this._handleSplit = undefined; + /// Subparsing + this._subparser = this._subPartNum = undefined; + /// Data that has yet to be consumed by _convertData + this._savedBuffer = ''; + /// Convert data + this._convertData = undefined; + /// String decoder + this._decoder = undefined; +}; + +/** + * Deliver a buffer of data to the parser. + * + * @param buffer {BinaryString} The raw data to add to the message. + */ +MimeParser.prototype.deliverData = function (buffer) { + // In ideal circumstances, we'd like to parse the message all at once. In + // reality, though, data will be coming to us in packets. To keep the amount + // of saved state low, we want to make basic guarantees about how packets get + // delivered. Our basic model is a twist on line-buffering, as the format of + // MIME and messages make it hard to not do so: we can handle multiple lines + // at once. To ensure this, we start by conditioning the packet by + // withholding data to make sure that the internal deliveries have the + // guarantees. This implies that we need to do the following steps: + // 1. We don't know if a `\r' comes from `\r\n' or the old mac line ending + // until we see the next character. So withhold the last `\r'. + // 2. Ensure that every packet ends on a newline. So scan for the end of the + // line and withhold until the \r\n comes through. + // [Note that this means that an input message that uses \r line endings and + // is being passed to us via a line-buffered input is going to have most of + // its data being withhold until the next buffer. Since \r is so uncommon of + // a line ending in modern times, this is acceptable lossage.] + // 3. Eliminate empty packets. + + // Add in previously saved data + if (this._holdData) { + buffer = this._holdData + buffer; + this._holdData = ''; + } + + // Condition the input, so that we get the multiline-buffering mentioned in + // the above comment. + if (buffer.length > 0) { + [buffer, this._holdData] = conditionToEndOnCRLF(buffer); + } + + // Ignore 0-length buffers. + if (buffer.length == 0) + return; + + // Signal the beginning, if we haven't done so. + if (!this._triggeredCall) { + this._callEmitter("startMessage"); + this._triggeredCall = true; + } + + // Finally, send it the internal parser. + this._dispatchData("", buffer, true); +} + +/** + * Ensure that a set of data always ends in an end-of-line character. + * + * @param buffer {BinaryString} The data with no guarantees about where it ends. + * @returns {BinaryString[]} An array of 2 binary strings where the first string + * ends in a newline and the last string contains the + * text in buffer following the first string. + */ +function conditionToEndOnCRLF(buffer) { + // Find the last occurrence of '\r' or '\n' to split the string. However, we + // don't want to consider '\r' if it is the very last character, as we need + // the next packet to tell if the '\r' is the beginning of a CRLF or a line + // ending by itself. + let lastCR = buffer.lastIndexOf('\r', buffer.length - 2); + let lastLF = buffer.lastIndexOf('\n'); + let end = lastLF > lastCR ? lastLF : lastCR; + return [buffer.substring(0, end + 1), buffer.substring(end + 1)]; +}; + +/** + * Tell the parser that all of the data has been delivered. + * + * This will flush all of the internal state of the parser. + */ +MimeParser.prototype.deliverEOF = function () { + // Start of input buffered too long? Call start message now. + if (!this._triggeredCall) { + this._triggeredCall = true; + this._callEmitter("startMessage"); + } + // Force a flush of all of the data. + if (this._holdData) + this._dispatchData("", this._holdData, true); + this._dispatchEOF(""); + // Signal to the emitter that we're done. + this._callEmitter("endMessage"); +}; + +/** + * Calls a method on the emitter safely. + * + * This method ensures that errors in the emitter call won't cause the parser + * to exit with an error, unless the user wants it to. + * + * @param funcname {String} The function name to call on the emitter. + * @param args... Extra arguments to pass into the emitter callback. + */ +MimeParser.prototype._callEmitter = function (funcname) { + if (this._emitter && funcname in this._emitter) { + let args = Array.prototype.splice.call(arguments, 1); + if (args.length > 0 && this._willIgnorePart(args[0])) { + // partNum is always the first argument, so check to make sure that it + // satisfies our emitter's pruneat requirement. + return; + } + try { + this._emitter[funcname].apply(this._emitter, args); + } catch (e) { + // We ensure that the onerror attribute in options is a function, so this + // is always safe. + this._options.onerror(e); + } + } +}; + +/** + * Helper function to decide if a part's output will never be seen. + * + * @param part {String} The number of the part. + * @returns {Boolean} True if the emitter is not interested in this part. + */ +MimeParser.prototype._willIgnorePart = function (part) { + if (this._options["pruneat"]) { + let match = this._options["pruneat"]; + let start = part.substr(0, match.length); + // It needs to start with and follow with a new part indicator + // (i.e., don't let 10 match with 1, but let 1.1 or 1$ do so) + if (start != match || (match.length < part.length && + "$.".indexOf(part[match.length]) == -1)) + return true; + } + return false; +}; + +////////////////////// +// MIME parser core // +////////////////////// + +// This MIME parser is a stateful parser; handling of the MIME tree is mostly +// done by creating new parsers and feeding data to them manually. In parallel +// to the externally-visible deliverData and deliverEOF, the two methods +// _dispatchData and _dispatchEOF are the internal counterparts that do the +// main work of moving data to where it needs to go; helper functions are used +// to handle translation. +// +// The overall flow of the parser is this. First, it buffers all of the data +// until the dual-CRLF pattern is noticed. Once that is found, it parses the +// entire header chunk at once. As a result of header parsing, the parser enters +// one of three modes for handling data, and uses a special regex to change +// modes and handle state changes. Specific details about the states the parser +// can be in are as follows: +// PARSING_HEADERS: The input buffer is concatenated to the currently-received +// text, which is then searched for the CRLFCRLF pattern. If found, the data +// is split at this boundary; the first chunk is parsed using _parseHeaders, +// and the second chunk will fall through to buffer processing. After +// splitting, the headers are deliverd via the emitter, and _startBody is +// called to set up state for the parser. +// SEND_TO_BLACK_HOLE: All data in the input is ignored. +// SEND_TO_EMITTER: All data is passed into the emitter, if it is desired. +// Data can be optionally converted with this._convertData. +// SEND_TO_SUBPARSER: All data is passed into the subparser's _dispatchData +// method, using _subPartNum as the part number and _subparser as the object +// to call. Data can be optionally converted first with this._convertData. +// +// Additional state modifications can be done using a regex in _splitRegex and +// the callback method this._handleSplit(partNum, regexResult). The _handleSplit +// callback is free to do any modification to the current parser, including +// modifying the _splitRegex value. Packet conditioning guarantees that every +// buffer string passed into _dispatchData will have started immediately after a +// newline character in the fully assembled message. +// +// The this._convertData method, if present, is expected to return an array of +// two values, [{typedarray, string} decoded_buffer, string unused_buffer], and +// has as its arguments (string buffer, bool moreToCome). +// +// The header parsing by itself does very little parsing, only parsing as if all +// headers were unstructured fields. Values are munged so that embedded newlines +// are stripped and the result is also trimmed. Headers themselves are +// canonicalized into lower-case. + + +// Parser states. See the large comment above. +var PARSING_HEADERS = 1; +var SEND_TO_BLACK_HOLE = 2; +var SEND_TO_EMITTER = 3; +var SEND_TO_SUBPARSER = 4; + +/** + * Main dispatch for incoming packet data. + * + * The incoming data needs to have been sanitized so that each packet begins on + * a newline boundary. The part number for the current parser also needs to be + * passed in. The checkSplit parameter controls whether or not the data in + * buffer needs to be checked against _splitRegex; this is used internally for + * the mechanics of splitting and should otherwise always be true. + * + * @param partNum {String} The part number being currently parsed. + * @param buffer {BinaryString} The text (conditioned as mentioned above) to + * pass to the parser. + * @param checkSplit {Boolean} If true, split the text using _splitRegex. + * This is set to false internally to handle + * low-level splitting details. + */ +MimeParser.prototype._dispatchData = function (partNum, buffer, checkSplit) { + // Are we parsing headers? + if (this._state == PARSING_HEADERS) { + this._headerData += buffer; + // Find the end of the headers--either it's a CRLF at the beginning (in + // which case we have no headers), or it's a pair of CRLFs. + let result = /(?:^(?:\r\n|[\r\n]))|(\r\n|[\r\n])\1/.exec(this._headerData); + if (result != null) { + // If we found the end of headers, split the data at this point and send + // the stuff after the double-CRLF into the later body parsing. + let headers = this._headerData.substr(0, result.index); + buffer = this._headerData.substring(result.index + result[0].length); + this._headerData = headers; + this._headers = this._parseHeaders(); + this._callEmitter("startPart", partNum, this._headers); + this._startBody(partNum); + } else { + return; + } + } + + // We're in the middle of the body. Start by testing the split regex, to see + // if there are many things that need to be done. + if (checkSplit && this._splitRegex) { + let splitResult = this._splitRegex.exec(buffer); + if (splitResult) { + // Pass the text before the split through the current state. + let start = splitResult.index, len = splitResult[0].length; + if (start > 0) + this._dispatchData(partNum, buffer.substr(0, start), false); + + // Tell the handler that we've seen the split. Note that this can change + // any method on `this'. + this._handleSplit(partNum, splitResult); + + // Send the rest of the data to where it needs to go. There could be more + // splits in the data, so watch out! + buffer = buffer.substring(start + len); + if (buffer.length > 0) + this._dispatchData(partNum, buffer, true); + return; + } + } + + // Where does the data go? + if (this._state == SEND_TO_BLACK_HOLE) { + // Don't send any data when going to the black hole. + return; + } else if (this._state == SEND_TO_EMITTER) { + // Don't pass body data if the format is to be none + let passData = this._options["bodyformat"] != "none"; + if (!passData || this._willIgnorePart(partNum)) + return; + buffer = this._applyDataConversion(buffer, this._options["strformat"]); + if (buffer.length > 0) + this._callEmitter("deliverPartData", partNum, buffer); + } else if (this._state == SEND_TO_SUBPARSER) { + buffer = this._applyDataConversion(buffer, "binarystring"); + if (buffer.length > 0) + this._subparser._dispatchData(this._subPartNum, buffer, true); + } +}; + +/** + * Output data using the desired output format, saving data if data conversion + * needs extra data to be saved. + * + * @param buf {BinaryString} The data to be sent to the output. + * @param type {String} The type of the data to output. Valid values are + * the same as the strformat option. + * @returns Coerced and converted data that can be sent to the emitter or + * subparser. + */ +MimeParser.prototype._applyDataConversion = function (buf, type) { + // If we need to convert data, do so. + if (this._convertData) { + // Prepend leftover data from the last conversion. + buf = this._savedBuffer + buf; + [buf, this._savedBuffer] = this._convertData(buf, true); + } + return this._coerceData(buf, type, true); +}; + +/** + * Coerce the input buffer into the given output type. + * + * @param buffer {BinaryString|Uint8Array} The data to be converted. + * @param type {String} The type to convert the data to. + * @param more {boolean} If true, this function will never be + * called again. + * @returns {BinaryString|String|Uint8Array} The desired output format. + */ +/// Coerces the buffer (a string or typedarray) into a given type +MimeParser.prototype._coerceData = function (buffer, type, more) { + if (typeof buffer == "string") { + // string -> binarystring is a nop + if (type == "binarystring") + return buffer; + // Either we're going to array or unicode. Both people need the array + var typedarray = mimeutils.stringToTypedArray(buffer); + // If it's unicode, do the coercion from the array + // If its typedarray, just return the synthesized one + return type == "unicode" ? this._coerceData(typedarray, "unicode", more) + : typedarray; + } else if (type == "binarystring") { + // Doing array -> binarystring + return mimeutils.typedArrayToString(buffer); + } else if (type == "unicode") { + // Doing array-> unicode: Use the decoder set up earlier to convert + if (this._decoder) + return this._decoder.decode(buffer, {stream: more}); + // If there is no charset, just return the typed array instead. + return buffer; + } + throw new Error("Invalid type: " + type); +}; + +/** + * Signal that no more data will be dispatched to this parser. + * + * @param partNum {String} The part number being currently parsed. + */ +MimeParser.prototype._dispatchEOF = function (partNum) { + if (this._state == PARSING_HEADERS) { + // Unexpected EOF in headers. Parse them now and call startPart/endPart + this._headers = this._parseHeaders(); + this._callEmitter("startPart", partNum, this._headers); + } else if (this._state == SEND_TO_SUBPARSER) { + // Pass in any lingering data + if (this._convertData && this._savedBuffer) + this._subparser._dispatchData(this._subPartNum, + this._convertData(this._savedBuffer, false)[0], true); + this._subparser._dispatchEOF(this._subPartNum); + // Clean up after ourselves + this._subparser = null; + } else if (this._convertData && this._savedBuffer) { + // Convert lingering data + let [buffer, ] = this._convertData(this._savedBuffer, false); + buffer = this._coerceData(buffer, this._options["strformat"], false); + if (buffer.length > 0) + this._callEmitter("deliverPartData", partNum, buffer); + } + + // We've reached EOF for this part; tell the emitter + this._callEmitter("endPart", partNum); +}; + +/** + * Produce a dictionary of all headers as if they were unstructured fields. + * + * @returns {StructuredHeaders} The structured header objects for the header + * block. + */ +MimeParser.prototype._parseHeaders = function () { + let headers = new StructuredHeaders(this._headerData, this._options); + + // Fill the headers.contentType parameter of headers. + let contentType = headers.get('Content-Type'); + if (typeof contentType === "undefined") { + contentType = headerparser.parseStructuredHeader('Content-Type', + this._defaultContentType || 'text/plain'); + Object.defineProperty(headers, "contentType", { + get: function () { return contentType; } + }); + } else { + Object.defineProperty(headers, "contentType", { configurable: false }); + } + + // Find the charset for the current part. If the user requested a forced + // conversion, use that first. Otherwise, check the content-type for one and + // fallback to a default if it is not present. + let charset = ''; + if (this._options["force-charset"]) + charset = this._options["charset"]; + else if (contentType.has("charset")) + charset = contentType.get("charset"); + else + charset = this._options["charset"]; + headers.charset = charset; + + // Retain a copy of the charset so that users don't override our decision for + // decoding body parts. + this._charset = charset; + return headers; +}; + +/** + * Initialize the parser state for the body of this message. + * + * @param partNum {String} The part number being currently parsed. + */ +MimeParser.prototype._startBody = function Parser_startBody(partNum) { + let contentType = this._headers.contentType; + + // Should the bodyformat be raw, we just want to pass through all data without + // trying to interpret it. + if (this._options["bodyformat"] == "raw" && + partNum == this._options["pruneat"]) { + this._state = SEND_TO_EMITTER; + return; + } + + // The output depents on the content-type. Basic rule of thumb: + // 1. Discrete media types (text, video, audio, image, application) are passed + // through with no alterations beyond Content-Transfer-Encoding unpacking. + // 2. Everything with a media type of multipart is treated the same. + // 3. Any message/* type that acts like a mail message (rfc822, news, global) + // is parsed as a header/body pair again. Most of the other message/* types + // have similar structures, but they don't have cascading child subparts, + // so it's better to pass their entire contents to the emitter and let the + // consumer deal with them. + // 4. For untyped data, there needs to be no Content-Type header. This helps + // avoid false positives. + if (contentType.mediatype == 'multipart') { + // If there's no boundary type, everything will be part of the prologue of + // the multipart message, so just feed everything into a black hole. + if (!contentType.has('boundary')) { + this._state = SEND_TO_BLACK_HOLE; + return; + } + // The boundary of a multipart message needs to start with -- and be at the + // beginning of the line. If -- is after the boundary, it represents the + // terminator of the multipart. After the line, there may be only whitespace + // and then the CRLF at the end. Since the CRLFs in here are necessary for + // distinguishing the parts, they are not included in the subparts, so we + // need to capture them in the regex as well to prevent them leaking out. + this._splitRegex = new RegExp('(\r\n|[\r\n]|^)--' + + contentType.get('boundary').replace(/[\\^$*+?.()|{}[\]]/g, '\\$&') + + '(--)?[ \t]*(?:\r\n|[\r\n]|$)'); + this._handleSplit = this._whenMultipart; + this._subparser = new MimeParser(this._emitter, this._options); + // multipart/digest defaults to message/rfc822 instead of text/plain + if (contentType.subtype == "digest") + this._subparser._defaultContentType = "message/rfc822"; + + // All text before the first boundary and after the closing boundary are + // supposed to be ignored ("must be ignored", according to RFC 2046 §5.1.1); + // in accordance with these wishes, ensure they don't get passed to any + // deliverPartData. + this._state = SEND_TO_BLACK_HOLE; + + // Multipart MIME messages stipulate that the final CRLF before the boundary + // delimiter is not matched. When the packet ends on a CRLF, we don't know + // if the next text could be the boundary. Therefore, we need to withhold + // the last line of text to be sure of what's going on. The _convertData is + // how we do this, even though we're not really converting any data. + this._convertData = function mpart_no_leak_crlf(buffer, more) { + let splitPoint = buffer.length; + if (more) { + if (buffer.charAt(splitPoint - 1) == '\n') + splitPoint--; + if (splitPoint >= 0 && buffer.charAt(splitPoint - 1) == '\r') + splitPoint--; + } + let res = conditionToEndOnCRLF(buffer.substring(0, splitPoint)); + let preLF = res[0]; + let rest = res[1]; + return [preLF, rest + buffer.substring(splitPoint)]; + } + } else if (contentType.type == 'message/rfc822' || + contentType.type == 'message/global' || + contentType.type == 'message/news') { + // The subpart is just another header/body pair that goes to EOF, so just + // return the parse from that blob + this._state = SEND_TO_SUBPARSER; + this._subPartNum = partNum + "$"; + this._subparser = new MimeParser(this._emitter, this._options); + + // So, RFC 6532 happily allows message/global types to have CTE applied. + // This means that subparts would need to be decoded to determine their + // contents properly. There seems to be some evidence that message/rfc822 + // that is illegally-encoded exists in the wild, so be lenient and decode + // for any message/* type that gets here. + let cte = this._extractHeader('content-transfer-encoding', ''); + if (cte in ContentDecoders) + this._convertData = ContentDecoders[cte]; + } else { + // Okay, we just have to feed the data into the output + this._state = SEND_TO_EMITTER; + if (this._options["bodyformat"] == "decode") { + // If we wish to decode, look it up in one of our decoders. + let cte = this._extractHeader('content-transfer-encoding', ''); + if (cte in ContentDecoders) + this._convertData = ContentDecoders[cte]; + } + } + + // Set up the encoder for charset conversions; only do this for text parts. + // Other parts are almost certainly binary, so no translation should be + // applied to them. + if (this._options["strformat"] == "unicode" && + contentType.mediatype == "text") { + // If the charset is nonempty, initialize the decoder + if (this._charset !== "") { + this._decoder = new TextDecoder(this._charset); + } else { + // There's no charset we can use for decoding, so pass through as an + // identity encoder or otherwise this._coerceData will complain. + this._decoder = { + decode: function identity_decoder(buffer) { + return MimeParser.prototype._coerceData(buffer, "binarystring", true); + } + }; + } + } else { + this._decoder = null; + } +}; + +// Internal split handling for multipart messages. +/** + * When a multipary boundary is found, handle the process of managing the + * subparser state. This is meant to be used as a value for this._handleSplit. + * + * @param partNum {String} The part number being currently parsed. + * @param lastResult {Array} The result of the regular expression match. + */ +MimeParser.prototype._whenMultipart = function (partNum, lastResult) { + // Fix up the part number (don't do '' -> '.4' and don't do '1' -> '14') + if (partNum != "") partNum += "."; + if (!this._subPartNum) { + // No count? This means that this is the first time we've seen the boundary, + // so do some initialization for later here. + this._count = 1; + } else { + // If we did not match a CRLF at the beginning of the line, strip CRLF from + // the saved buffer. We do this in the else block because it is not + // necessary for the prologue, since that gets ignored anyways. + if (this._savedBuffer != '' && lastResult[1] === '') { + let useEnd = this._savedBuffer.length - 1; + if (this._savedBuffer[useEnd] == '\n') + useEnd--; + if (useEnd >= 0 && this._savedBuffer[useEnd] == '\r') + useEnd--; + this._savedBuffer = this._savedBuffer.substring(0, useEnd + 1); + } + // If we have saved data and we matched a CRLF, pass the saved data in. + if (this._savedBuffer != '') + this._subparser._dispatchData(this._subPartNum, this._savedBuffer, true); + // We've seen the boundary at least once before, so this must end a subpart. + // Tell that subpart that it has reached EOF. + this._subparser._dispatchEOF(this._subPartNum); + } + this._savedBuffer = ''; + + // The regex feeder has a capture on the (--)?, so if its result is present, + // then we have seen the terminator. Alternatively, the message may have been + // mangled to exclude the terminator, so also check if EOF has occurred. + if (lastResult[2] == undefined) { + this._subparser.resetParser(); + this._state = SEND_TO_SUBPARSER; + this._subPartNum = partNum + this._count; + this._count += 1; + } else { + // Ignore the epilogue + this._splitRegex = null; + this._state = SEND_TO_BLACK_HOLE; + } +}; + +/** + * Return the structured header from the current header block, or a default if + * it is not present. + * + * @param name {String} The header name to get. + * @param dflt {String} The default MIME value of the header. + * @returns The structured representation of the header. + */ +MimeParser.prototype._extractHeader = function (name, dflt) { + name = name.toLowerCase(); // Normalize name + return this._headers.has(name) ? this._headers.get(name) : + headerparser.parseStructuredHeader(name, [dflt]); +}; + +var ContentDecoders = {}; +ContentDecoders['quoted-printable'] = mimeutils.decode_qp; +ContentDecoders['base64'] = mimeutils.decode_base64; + +return MimeParser; +}); +def('headeremitter', function(require) { +/** + * This module implements the code for emitting structured representations of + * MIME headers into their encoded forms. The code here is a companion to, + * but completely independent of, jsmime.headerparser: the structured + * representations that are used as input to the functions in this file are the + * same forms that would be parsed. + */ + +"use strict"; + +var mimeutils = require('./mimeutils'); + +// Get the default structured encoders and add them to the map +var structuredHeaders = require('./structuredHeaders'); +var encoders = new Map(); +var preferredSpellings = structuredHeaders.spellings; +for (let [header, encoder] of structuredHeaders.encoders) { + addStructuredEncoder(header, encoder); +} + +/// Clamp a value in the range [min, max], defaulting to def if it is undefined. +function clamp(value, min, max, def) { + if (value === undefined) + return def; + if (value < min) + return min; + if (value > max) + return max; + return value; +} + +/** + * An object that can assemble structured header representations into their MIME + * representation. + * + * The character-counting portion of this class operates using individual JS + * characters as its representation of logical character, which is not the same + * as the number of octets used as UTF-8. If non-ASCII characters are to be + * included in headers without some form of encoding, then care should be taken + * to set the maximum line length to account for the mismatch between character + * counts and octet counts: the maximum line is 998 octets, which could be as + * few as 332 JS characters (non-BMP characters, although they take up 4 octets + * in UTF-8, count as 2 in JS strings). + * + * This code takes care to only insert line breaks at the higher-level breaking + * points in a header (as recommended by RFC 5322), but it may need to resort to + * including them more aggressively if this is not possible. If even aggressive + * line-breaking cannot allow a header to be emitted without violating line + * length restrictions, the methods will throw an exception to indicate this + * situation. + * + * In general, this code does not attempt to modify its input; for example, it + * does not attempt to change the case of any input characters, apply any + * Unicode normalization algorithms, or convert email addresses to ACE where + * applicable. The biggest exception to this rule is that most whitespace is + * collapsed to a single space, even in unstructured headers, while most leading + * and trailing whitespace is trimmed from inputs. + * + * @param {StreamHandler} handler The handler to which all output is sent. + * @param {Function(String)} handler.deliverData Receives encoded data. + * @param {Function()} handler.deliverEOF Sent when all text is sent. + * @param {Object} options Options for the emitter. + * @param [options.softMargin=78] {30 <= Integer <= 900} + * The ideal maximum number of logical characters to include in a line, not + * including the final CRLF pair. Lines may exceed this margin if parameters + * are excessively long. + * @param [options.hardMargin=332] {softMargin <= Integer <= 998} + * The maximum number of logical characters that can be included in a line, + * not including the final CRLF pair. If this count would be exceeded, then + * an error will be thrown and encoding will not be possible. + * @param [options.useASCII=true] {Boolean} + * If true, then RFC 2047 and RFC 2231 encoding of headers will be performed + * as needed to retain headers as ASCII. + */ +function HeaderEmitter(handler, options) { + /// The inferred value of options.useASCII + this._useASCII = options.useASCII === undefined ? true : options.useASCII; + /// The handler to use. + this._handler = handler; + /** + * The current line being built; note that we may insert a line break in the + * middle to keep under the maximum line length. + * + * @type String + * @private + */ + this._currentLine = ""; + + // Our bounds for soft and margins are not completely arbitrary. The minimum + // amount we need to encode is 20 characters, which can encode a single + // non-BMP character with RFC 2047. The value of 30 is chosen to give some + // breathing room for delimiters or other unbreakable characters. The maximum + // length is 998 octets, per RFC 5322; soft margins are slightly lower to + // allow for breathing room as well. The default of 78 for the soft margin is + // recommended by RFC 5322; the default of 332 for the hard margin ensures + // that UTF-8 encoding the output never violates the 998 octet limit. + this._softMargin = clamp(options.softMargin, 30, 900, 78); + this._hardMargin = clamp(options.hardMargin, this._softMargin, 998, 332); + + /** + * The index of the last preferred breakable position in the current line. + * + * @type Integer + * @private + */ + this._preferredBreakpoint = 0; +} + + +/////////////////////// +// Low-level methods // +/////////////////////// + +// Explanation of the emitter internals: +// RFC 5322 requires that we wrap our lines, ideally at 78 characters and at +// least by 998 octets. We can't wrap in arbitrary places, but wherever CFWS is +// valid... and ideally wherever clients are likely to expect it. In theory, we +// can break between every token (this is how RFC 822 operates), but, in RFC +// 5322, many of those breaks are relegated to obsolete productions, mostly +// because it is common to not properly handle breaks in those locations. +// +// So how do we do line breaking? The algorithm we implement is greedy, to +// simplify implementation. There are two margins: the soft margin, which we +// want to keep within, and the hard margin, which we absolutely have to keep +// within. There are also two kinds of break points: preferred and emergency. +// As long as we keep the line within the hard margin, we will only break at +// preferred breakpoints; emergency breakpoints are only used if we would +// otherwise exceed the hard margin. +// +// For illustration, here is an example header and where these break points are +// located: +// +// To: John "The Rock" Smith <jsmith@a.long.domain.invalid> +// Preferred: ^ ^ ^ +// Emergency: ^ ^ ^ ^^ ^ ^ ^ ^ ^ +// +// Preferred breakpoints are indicated by setting the mayBreakAfter parameter of +// addText to true, while emergency breakpoints are set after every token passed +// into addText. This is handled implicitly by only adding text to _currentLine +// if it ends in an emergency breakpoint. +// +// Internally, the code keeps track of margins by use of two variables. The +// _softMargin and _hardMargin variables encode the positions at which code must +// absolutely break, and are set up from the initial options parameter. Breaking +// happens when _currentLine.length approaches these values, as mentioned above. + +/** + * Send a header line consisting of the first N characters to the handler. + * + * If the count parameter is missing, then we presume that the current header + * value being emitted is done and therefore we should not send a continuation + * space. Otherwise, we presume that we're still working, so we will send the + * continuation space. + * + * @private + * @param [count] {Integer} The number of characters in the current line to + * include before wrapping. + */ +HeaderEmitter.prototype._commitLine = function (count) { + let isContinuing = typeof count !== "undefined"; + + // Split at the point, and lop off whitespace immediately before and after. + if (isContinuing) { + var firstN = this._currentLine.slice(0, count).trimRight(); + var lastN = this._currentLine.slice(count).trimLeft(); + } else { + var firstN = this._currentLine.trimRight(); + var lastN = ""; + } + + // How many characters do we need to shift preferred/emergency breakpoints? + let shift = this._currentLine.length - lastN.length; + + // Send the line plus the final CRLF. + this._handler.deliverData(firstN + '\r\n'); + + // Fill the start of the line with the new data. + this._currentLine = lastN; + + // If this is a continuation, add an extra space at the beginning of the line. + // Adjust the breakpoint shift amount as well. + if (isContinuing) { + this._currentLine = ' ' + this._currentLine; + shift++; + } + + // We will always break at a point at or after the _preferredBreakpoint, if it + // exists, so this always gets reset to 0. + this._preferredBreakpoint = 0; +}; + +/** + * Reserve at least length characters in the current line. If there aren't + * enough characters, insert a line break. + * + * @private + * @param length {Integer} The number of characters to reserve space for. + * @return {Boolean} Whether or not there is enough space for length characters. + */ +HeaderEmitter.prototype._reserveTokenSpace = function (length) { + // We are not going to do a sanity check that length is within the wrap + // margins. The rationale is that this lets code simply call this function to + // force a higher-level line break than normal preferred line breaks (see + // addAddress for an example use). The text that would be added may need to be + // itself broken up, so it might not need all the length anyways, but it + // starts the break already. + + // If we have enough space, we don't need to do anything. + if (this._currentLine.length + length <= this._softMargin) + return true; + + // If we have a preferred breakpoint, commit the line at that point, and see + // if that is sufficient line-breaking. + if (this._preferredBreakpoint > 0) { + this._commitLine(this._preferredBreakpoint); + if (this._currentLine.length + length <= this._softMargin) + return true; + } + + // At this point, we can no longer keep within the soft margin. Let us see if + // we can fit within the hard margin. + if (this._currentLine.length + length <= this._hardMargin) { + return true; + } + + // Adding the text to length would violate the hard margin as well. Break at + // the last emergency breakpoint. + if (this._currentLine.length > 0) { + this._commitLine(this._currentLine.length); + } + + // At this point, if there is still insufficient room in the hard margin, we + // can no longer do anything to encode this word. Bail. + return this._currentLine.length + length <= this._hardMargin; +}; + +/** + * Adds a block of text to the current header, inserting a break if necessary. + * If mayBreakAfter is true and text does not end in whitespace, a single space + * character may be added to the output. If the text could not be added without + * violating line length restrictions, an error is thrown instead. + * + * @protected + * @param {String} text The text to add to the output. + * @param {Boolean} mayBreakAfter If true, the end of this text is a preferred + * breakpoint. + */ +HeaderEmitter.prototype.addText = function (text, mayBreakAfter) { + // Try to reserve space for the tokens. If we can't, give up. + if (!this._reserveTokenSpace(text.length)) + throw new Error("Cannot encode " + text + " due to length."); + + this._currentLine += text; + if (mayBreakAfter) { + // Make sure that there is an extra space if text could break afterwards. + this._preferredBreakpoint = this._currentLine.length; + if (text[text.length - 1] != ' ') { + this._currentLine += ' '; + } + } +}; + +/** + * Adds a block of text that may need quoting if it contains some character in + * qchars. If it is already quoted, no quoting will be applied. If the text + * cannot be added without violating maximum line length, an error is thrown + * instead. + * + * @protected + * @param {String} text The text to add to the output. + * @param {String} qchars The set of characters that cannot appear + * outside of a quoted string. + * @param {Boolean} mayBreakAfter If true, the end of this text is a preferred + * breakpoint. + */ +HeaderEmitter.prototype.addQuotable = function (text, qchars, mayBreakAfter) { + // No text -> no need to be quoted (prevents strict warning errors). + if (text.length == 0) + return; + + // Figure out if we need to quote the string. Don't quote a string which + // already appears to be quoted. + let needsQuote = false; + + if (!(text[0] == '"' && text[text.length - 1] == '"') && qchars != '') { + for (let i = 0; i < text.length; i++) { + if (qchars.includes(text[i])) { + needsQuote = true; + break; + } + } + } + + if (needsQuote) + text = '"' + text.replace(/["\\]/g, "\\$&") + '"'; + this.addText(text, mayBreakAfter); +}; + +/** + * Adds a block of text that corresponds to the phrase production in RFC 5322. + * Such text is a sequence of atoms, quoted-strings, or RFC-2047 encoded-words. + * This method will preprocess input to normalize all space sequences to a + * single space. If the text cannot be added without violating maximum line + * length, an error is thrown instead. + * + * @protected + * @param {String} text The text to add to the output. + * @param {String} qchars The set of characters that cannot appear + * outside of a quoted string. + * @param {Boolean} mayBreakAfter If true, the end of this text is a preferred + * breakpoint. + */ +HeaderEmitter.prototype.addPhrase = function (text, qchars, mayBreakAfter) { + // Collapse all whitespace spans into a single whitespace node. + text = text.replace(/[ \t\r\n]+/g, " "); + + // If we have non-ASCII text, encode it using RFC 2047. + if (this._useASCII && nonAsciiRe.test(text)) { + this.encodeRFC2047Phrase(text, mayBreakAfter); + return; + } + + // If quoting the entire string at once could fit in the line length, then do + // so. The check here is very loose, but this will inform is if we are going + // to definitely overrun the soft margin. + if ((this._currentLine.length + text.length) < this._softMargin) { + try { + this.addQuotable(text, qchars, mayBreakAfter); + // If we don't have a breakpoint, and the text is encoded as a sequence of + // atoms (and not a quoted-string), then make the last space we added a + // breakpoint, regardless of the mayBreakAfter setting. + if (this._preferredBreakpoint == 0 && text.includes(" ")) { + if (this._currentLine[this._currentLine.length - 1] != '"') + this._preferredBreakpoint = this._currentLine.lastIndexOf(" "); + } + return; + } catch (e) { + // If we get an error at this point, we failed to add the quoted string + // because the string was too long. Fall through to the case where we know + // that the input was too long to begin with. + } + } + + // If the text is too long, split the quotable string at space boundaries and + // add each word invidually. If we still can't add all those words, there is + // nothing that we can do. + let words = text.split(' '); + for (let i = 0; i < words.length; i++) { + this.addQuotable(words[i], qchars, + i == words.length - 1 ? mayBreakAfter : true); + } +}; + +/// A regular expression for characters that need to be encoded. +var nonAsciiRe = /[^\x20-\x7e]/; + +/// The beginnings of RFC 2047 encoded-word +var b64Prelude = "=?UTF-8?B?", qpPrelude = "=?UTF-8?Q?"; + +/// A list of ASCII characters forbidden in RFC 2047 encoded-words +var qpForbidden = "=?_()\","; + +var hexString = "0123456789abcdef"; + +/** + * Add a block of text as a single RFC 2047 encoded word. This does not try to + * split words if they are too long. + * + * @private + * @param {Uint8Array} encodedText The octets to encode. + * @param {Boolean} useQP If true, use quoted-printable; if false, + * use base64. + * @param {Boolean} mayBreakAfter If true, the end of this text is a + * preferred breakpoint. + */ +HeaderEmitter.prototype._addRFC2047Word = function (encodedText, useQP, + mayBreakAfter) { + let binaryString = mimeutils.typedArrayToString(encodedText); + if (useQP) { + var token = qpPrelude; + for (let i = 0; i < encodedText.length; i++) { + if (encodedText[i] < 0x20 || encodedText[i] >= 0x7F || + qpForbidden.includes(binaryString[i])) { + let ch = encodedText[i]; + token += "=" + hexString[(ch & 0xf0) >> 4] + hexString[ch & 0x0f]; + } else if (binaryString[i] == " ") { + token += "_"; + } else { + token += binaryString[i]; + } + } + token += "?="; + } else { + var token = b64Prelude + btoa(binaryString) + "?="; + } + this.addText(token, mayBreakAfter); +}; + +/** + * Add a block of text as potentially several RFC 2047 encoded-word tokens. + * + * @protected + * @param {String} text The text to add to the output. + * @param {Boolean} mayBreakAfter If true, the end of this text is a preferred + * breakpoint. + */ +HeaderEmitter.prototype.encodeRFC2047Phrase = function (text, mayBreakAfter) { + // Start by encoding the text into UTF-8 directly. + let encodedText = new TextEncoder("UTF-8").encode(text); + + // Make sure there's enough room for a single token. + let minLineLen = b64Prelude.length + 10; // Eight base64 characters plus ?= + if (!this._reserveTokenSpace(minLineLen)) { + this._commitLine(this._currentLine.length); + } + + // Try to encode as much UTF-8 text as possible in each go. + let b64Len = 0, qpLen = 0, start = 0; + let maxChars = (this._softMargin - this._currentLine.length) - + (b64Prelude.length + 2); + for (let i = 0; i < encodedText.length; i++) { + let b64Inc = 0, qpInc = 0; + // The length we need for base64 is ceil(length / 3) * 4... + if ((i - start) % 3 == 0) + b64Inc += 4; + + // The length for quoted-printable is 3 chars only if encoded + if (encodedText[i] < 0x20 || encodedText[i] >= 0x7f || + qpForbidden.includes(String.fromCharCode(encodedText[i]))) { + qpInc = 3; + } else { + qpInc = 1; + } + + if (b64Len + b64Inc > maxChars && qpLen + qpInc > maxChars) { + // Oops, we have too many characters! We need to encode everything through + // the current character. However, we can't split in the middle of a + // multibyte character. In UTF-8, characters that start with 10xx xxxx are + // the middle of multibyte characters, so backtrack until the start + // character is legal. + while ((encodedText[i] & 0xC0) == 0x80) + --i; + + // Add this part of the word and then make a continuation. + this._addRFC2047Word(encodedText.subarray(start, i), b64Len >= qpLen, + true); + + // Reset the array for parsing. + start = i; + --i; // Reparse this character as well + b64Len = qpLen = 0; + maxChars = this._softMargin - b64Prelude.length - 3; + } else { + // Add the counts for the current variable to the count to encode. + b64Len += b64Inc; + qpLen += qpInc; + } + } + + // Add the entire array at this point. + this._addRFC2047Word(encodedText.subarray(start), b64Len >= qpLen, + mayBreakAfter); +}; + +//////////////////////// +// High-level methods // +//////////////////////// + +/** + * Add the header name, with the colon and trailing space, to the output. + * + * @public + * @param {String} name The name of the header. + */ +HeaderEmitter.prototype.addHeaderName = function (name) { + this._currentLine = this._currentLine.trimRight(); + if (this._currentLine.length > 0) { + this._commitLine(); + } + this.addText(name + ": ", false); +}; + +/** + * Add a header and its structured value to the output. + * + * The name can be any case-insensitive variant of a known structured header; + * the output will include the preferred name of the structure instead of the + * case put into the name. If no structured encoder can be found, and the input + * value is a string, then the header is assumed to be unstructured and the + * value is added as if {@link addUnstructured} were called. + * + * @public + * @param {String} name The name of the header. + * @param value The structured value of the header. + */ +HeaderEmitter.prototype.addStructuredHeader = function (name, value) { + let lowerName = name.toLowerCase(); + if (encoders.has(lowerName)) { + this.addHeaderName(preferredSpellings.get(lowerName)); + encoders.get(lowerName).call(this, value); + } else if (typeof value === "string") { + // Assume it's an unstructured header. + // All-lower-case-names are ugly, so capitalize first letters. + name = name.replace(/(^|-)[a-z]/g, function(match) { + return match.toUpperCase(); + }); + this.addHeaderName(name); + this.addUnstructured(value); + } else { + throw new Error("Unknown header " + name); + } +}; + +/** + * Add a single address to the header. The address is an object consisting of a + * possibly-empty display name and an email address. + * + * @public + * @param Address addr The address to be added. + * @param {String} addr.name The (possibly-empty) name of the address to add. + * @param {String} addr.email The email of the address to add. + * @see headerparser.parseAddressingHeader + */ +HeaderEmitter.prototype.addAddress = function (addr) { + // If we have a display name, add that first. + if (addr.name) { + // This is a simple estimate that keeps names on one line if possible. + this._reserveTokenSpace(addr.name.length + addr.email.length + 3); + this.addPhrase(addr.name, ",()<>[]:;@.\"", true); + + // If we don't have an email address, don't write out the angle brackets for + // the address. It's already an abnormal situation should this appear, and + // this has better round-tripping properties. + if (!addr.email) + return; + + this.addText("<", false); + } + + // Find the local-part and domain of the address, since the local-part may + // need to be quoted separately. Note that the @ goes to the domain, so that + // the local-part may be quoted if it needs to be. + let at = addr.email.lastIndexOf("@"); + let localpart = "", domain = "" + if (at == -1) + localpart = addr.email; + else { + localpart = addr.email.slice(0, at); + domain = addr.email.slice(at); + } + + this.addQuotable(localpart, "()<>[]:;@\\,\" !", false); + this.addText(domain + (addr.name ? ">" : ""), false); +}; + +/** + * Add an array of addresses and groups to the output. Such an array may be + * found as the output of {@link headerparser.parseAddressingHeader}. Each + * element is either an address (an object with properties name and email), or a + * group (an object with properties name and group). + * + * @public + * @param {(Address|Group)[]} addrs A collection of addresses to add. + * @param {String} addrs[i].name The (possibly-empty) name of the + * address or the group to add. + * @param {String} [addrs[i].email] The email of the address to add. + * @param {Address[]} [addrs[i].group] A list of email addresses in the group. + * @see HeaderEmitter.addAddress + * @see headerparser.parseAddressingHeader + */ +HeaderEmitter.prototype.addAddresses = function (addresses) { + let needsComma = false; + for (let addr of addresses) { + // Add a comma if this is not the first element. + if (needsComma) + this.addText(", ", true); + needsComma = true; + + if ("email" in addr) { + this.addAddress(addr); + } else { + // A group has format name: member, member; + // Note that we still add a comma after the group is completed. + this.addPhrase(addr.name, ",()<>[]:;@.\"", false); + this.addText(":", true); + + this.addAddresses(addr.group); + this.addText(";", true); + } + } +}; + +/** + * Add an unstructured header value to the output. This effectively means only + * inserting line breaks were necessary, and using RFC 2047 encoding where + * necessary. + * + * @public + * @param {String} text The text to add to the output. + */ +HeaderEmitter.prototype.addUnstructured = function (text) { + if (text.length == 0) + return; + + // Unstructured text is basically a phrase that can't be quoted. So, if we + // have nothing in qchars, nothing should be quoted. + this.addPhrase(text, "", false); +}; + +/** RFC 822 labels for days of the week. */ +var kDaysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +/** + * Formatting helper to output numbers between 0-9 as 00-09 instead. + */ +function padTo2Digits(num) { + return num < 10 ? "0" + num : num.toString(); +} + +/** + * Add a date/time field to the output, using the JS date object as the time + * representation. The value will be output using the timezone offset of the + * date object, which is usually the timezone of the user (modulo timezone and + * DST changes). + * + * Note that if the date is an invalid date (its internal date parameter is a + * NaN value), this method throws an error instead of generating an invalid + * string. + * + * @public + * @param {Date} date The date to be added to the output string. + */ +HeaderEmitter.prototype.addDate = function (date) { + // Rather than make a header plastered with NaN values, throw an error on + // specific invalid dates. + if (isNaN(date.getTime())) + throw new Error("Cannot encode an invalid date"); + + // RFC 5322 says years can't be before 1900. The after 9999 is a bit that + // derives from the specification saying that years have 4 digits. + if (date.getFullYear() < 1900 || date.getFullYear() > 9999) + throw new Error("Date year is out of encodable range"); + + // Start by computing the timezone offset for a day. We lack a good format, so + // the the 0-padding is done by hand. Note that the tzoffset we output is in + // the form ±hhmm, so we need to separate the offset (in minutes) into an hour + // and minute pair. + let tzOffset = date.getTimezoneOffset(); + let tzOffHours = Math.abs(Math.trunc(tzOffset / 60)); + let tzOffMinutes = Math.abs(tzOffset) % 60; + let tzOffsetStr = (tzOffset > 0 ? "-" : "+") + + padTo2Digits(tzOffHours) + padTo2Digits(tzOffMinutes); + + // Convert the day-time figure into a single value to avoid unwanted line + // breaks in the middle. + let dayTime = [ + kDaysOfWeek[date.getDay()] + ",", + date.getDate(), + mimeutils.kMonthNames[date.getMonth()], + date.getFullYear(), + padTo2Digits(date.getHours()) + ":" + + padTo2Digits(date.getMinutes()) + ":" + + padTo2Digits(date.getSeconds()), + tzOffsetStr + ].join(" "); + this.addText(dayTime, false); +}; + +/** + * Signal that the current header has been finished encoding. + * + * @public + * @param {Boolean} deliverEOF If true, signal to the handler that no more text + * will be arriving. + */ +HeaderEmitter.prototype.finish = function (deliverEOF) { + this._commitLine(); + if (deliverEOF) + this._handler.deliverEOF(); +}; + +/** + * Make a streaming header emitter that outputs on the given handler. + * + * @param {StreamHandler} handler The handler to consume output + * @param options Options to pass into the HeaderEmitter + * constructor. + * @returns {HeaderEmitter} A header emitter constructed with the given options. + */ +function makeStreamingEmitter(handler, options) { + return new HeaderEmitter(handler, options); +} + +function StringHandler() { + this.value = ""; + this.deliverData = function (str) { this.value += str; }; + this.deliverEOF = function () { }; +} + +/** + * Given a header name and its structured value, output a string containing its + * MIME-encoded value. The trailing CRLF for the header is included. + * + * @param {String} name The name of the structured header. + * @param value The value of the structured header. + * @param options Options for the HeaderEmitter constructor. + * @returns {String} A MIME-encoded representation of the structured header. + * @see HeaderEmitter.addStructuredHeader + */ +function emitStructuredHeader(name, value, options) { + let handler = new StringHandler(); + let emitter = new HeaderEmitter(handler, options); + emitter.addStructuredHeader(name, value); + emitter.finish(true); + return handler.value; +} + +/** + * Given a map of header names and their structured values, output a string + * containing all of their headers and their MIME-encoded values. + * + * This method is designed to be able to emit header values given the headerData + * values produced by MIME parsing. Thus, the values of the map are arrays + * corresponding to header multiplicity. + * + * @param {Map(String->Object[])} headerValues A map of header names to arrays + * of their structured values. + * @param options Options for the HeaderEmitter + * constructor. + * @returns {String} A MIME-encoded representation of the structured header. + * @see HeaderEmitter.addStructuredHeader + */ +function emitStructuredHeaders(headerValues, options) { + let handler = new StringHandler(); + let emitter = new HeaderEmitter(handler, options); + for (let instance of headerValues) { + instance[1].forEach(function (e) { + emitter.addStructuredHeader(instance[0], e) + }); + } + emitter.finish(true); + return handler.value; +} + +/** + * Add a custom structured MIME encoder to the set of known encoders. These + * encoders are used for {@link emitStructuredHeader} and similar functions to + * encode richer, more structured values instead of relying on string + * representations everywhere. + * + * Structured encoders are functions which take in a single parameter + * representing their structured value. The this parameter is set to be an + * instance of {@link HeaderEmitter}, and it is intended that the several public + * or protected methods on that class are useful for encoding values. + * + * There is a large set of structured encoders built-in to the jsmime library + * already. + * + * @param {String} header The header name (in its preferred case) for + * which the encoder will be used. + * @param {Function(Value)} encoder The structured encoder function. + */ +function addStructuredEncoder(header, encoder) { + let lowerName = header.toLowerCase(); + encoders.set(lowerName, encoder); + if (!preferredSpellings.has(lowerName)) + preferredSpellings.set(lowerName, header); +} + +return Object.freeze({ + addStructuredEncoder: addStructuredEncoder, + emitStructuredHeader: emitStructuredHeader, + emitStructuredHeaders: emitStructuredHeaders, + makeStreamingEmitter: makeStreamingEmitter +}); + +}); + +def('jsmime', function(require) { + return { + MimeParser: require('./mimeparser'), + headerparser: require('./headerparser'), + headeremitter: require('./headeremitter') + } +}); + return mods['jsmime']; +})); diff --git a/mailnews/mime/moz.build b/mailnews/mime/moz.build new file mode 100644 index 000000000..0a7865a3d --- /dev/null +++ b/mailnews/mime/moz.build @@ -0,0 +1,15 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += [ + 'public', + 'src', + 'emitters', + 'cthandlers', +] + +EXTRA_JS_MODULES.jsmime += [ + 'jsmime/jsmime.js', +] diff --git a/mailnews/mime/public/MimeEncoder.h b/mailnews/mime/public/MimeEncoder.h new file mode 100644 index 000000000..7883af70e --- /dev/null +++ b/mailnews/mime/public/MimeEncoder.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MimeEncoder_h__ +#define MimeEncoder_h__ + +#include "nscore.h" + +namespace mozilla { +namespace mailnews { + +/// A class for encoding the bodies of MIME parts. +class MimeEncoder { +public: + virtual ~MimeEncoder() {} + + /// A callback for writing the encoded output + typedef nsresult (*OutputCallback) + (const char *buf, int32_t size, void *closure); + + /// Encodes the string in the buffer and sends it to the callback + virtual nsresult Write(const char *buffer, int32_t size) = 0; + /// Flush all pending data when no more data exists + virtual nsresult Flush() { return NS_OK; } + + /// Get an encoder that outputs Base64-encoded data + static MimeEncoder *GetBase64Encoder(OutputCallback callback, void *closure); + /// Get an encoder that outputs quoted-printable data + static MimeEncoder *GetQPEncoder(OutputCallback callback, void *closure); + +protected: + MimeEncoder(OutputCallback callback, void *closure); + OutputCallback mCallback; + void *mClosure; + uint32_t mCurrentColumn; +}; + +} // namespace mailnews +} // namespace mozilla + +#endif diff --git a/mailnews/mime/public/MimeHeaderParser.h b/mailnews/mime/public/MimeHeaderParser.h new file mode 100644 index 000000000..429e759b1 --- /dev/null +++ b/mailnews/mime/public/MimeHeaderParser.h @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MimeHeaderParser_h__ +#define MimeHeaderParser_h__ + +#include "nsCOMArray.h" +#include "nsStringGlue.h" +#include "nsTArray.h" + +class msgIAddressObject; + +namespace mozilla { +namespace mailnews { + +/** + * This is used to signal that the input header value has already been decoded + * according to RFC 2047 and is in UTF-16 form. + */ +nsCOMArray<msgIAddressObject> DecodedHeader(const nsAString &aHeader); + +/** + * This is used to signal that the input header value needs to be decoded + * according to RFC 2047. The charset parameter indicates the charset to assume + * that non-ASCII data is in; if the value is null (the default), then the + * charset is assumed to be UTF-8. + */ +nsCOMArray<msgIAddressObject> EncodedHeader(const nsACString &aHeader, + const char *aCharset = nullptr); + +namespace detail { +void DoConversion(const nsTArray<nsString> &aUTF16, nsTArray<nsCString> &aUTF8); +}; +/** + * This is a class designed for use as temporaries so that methods can pass + * an nsTArray<nsCString> into methods that expect nsTArray<nsString> for out + * parameters (this does not work for in-parameters). + * + * It works by internally providing an nsTArray<nsString> which it uses for its + * external API operations. If the user requests an array of nsCString elements + * instead, it converts the UTF-16 array to a UTF-8 array on destruction. + */ +template <uint32_t N = 5> +class UTF16ArrayAdapter +{ +public: + UTF16ArrayAdapter(nsTArray<nsCString> &aUTF8Array) + : mUTF8Array(aUTF8Array) {} + ~UTF16ArrayAdapter() { detail::DoConversion(mUTF16Array, mUTF8Array); } + operator nsTArray<nsString>&() { return mUTF16Array; } +private: + nsTArray<nsCString> &mUTF8Array; + AutoTArray<nsString, N> mUTF16Array; +}; + +/** + * Given a name and an email, both encoded in UTF-8, produce a string suitable + * for writing in an email header by quoting where necessary. + * + * If name is not empty, the output string will be name <email>. If it is empty, + * the output string is just the email. Note that this DOES NOT do any RFC 2047 + * encoding. + */ +void MakeMimeAddress(const nsACString &aName, const nsACString &aEmail, + nsACString &full); + +/** + * Given a name and an email, produce a string suitable for writing in an email + * header by quoting where necessary. + * + * If name is not empty, the output string will be name <email>. If it is empty, + * the output string is just the email. Note that this DOES NOT do any RFC 2047 + * encoding. + */ +void MakeMimeAddress(const nsAString &aName, const nsAString &aEmail, + nsAString &full); + +/** + * Given a name and an email, both encoded in UTF-8, produce a string suitable + * for displaying in UI. + * + * If name is not empty, the output string will be name <email>. If it is empty, + * the output string is just the email. + */ +void MakeDisplayAddress(const nsAString &aName, const nsAString &aEmail, + nsAString &full); + +/** + * Returns a copy of the input which may have had some addresses removed. + * Addresses are removed if they are already in either of the supplied + * address lists. + * + * Addresses are considered to be the same if they contain the same email + * address parts, ignoring case. Display names or comments are not compared. + * + * @param aHeader The addresses to remove duplicates from. + * @param aOtherEmails Other addresses that the duplicate removal process also + * checks for duplicates against. Addresses in this list + * will not be added to the result. + * @return The original header with duplicate addresses removed. + */ +void RemoveDuplicateAddresses(const nsACString &aHeader, + const nsACString &aOtherEmails, + nsACString &result); + +/** + * Given a message header, extract all names and email addresses found in that + * header into the two arrays. + */ +void ExtractAllAddresses(const nsCOMArray<msgIAddressObject> &aHeader, + nsTArray<nsString> &names, nsTArray<nsString> &emails); + +/** + * Given a raw message header value, extract display names for every address + * found in the header. + */ +void ExtractDisplayAddresses(const nsCOMArray<msgIAddressObject> &aHeader, + nsTArray<nsString> &addresses); + +/** + * Given a raw message header value, extract all the email addresses into an + * array. + * + * Duplicate email addresses are not removed from the output list. + */ +void ExtractEmails(const nsCOMArray<msgIAddressObject> &aHeader, + nsTArray<nsString> &emails); + +/** + * Given a raw message header value, extract the first name/email address found + * in the header. This is essentially equivalent to grabbing the first entry of + * ExtractAllAddresses. + */ +void ExtractFirstAddress(const nsCOMArray<msgIAddressObject> &aHeader, + nsACString &name, nsACString &email); + +/** + * Given an RFC 2047-decoded message header value, extract the first name/email + * address found in the header. This is essentially equivalent to grabbing the + * first entry of ExtractAllAddresses. + */ +void ExtractFirstAddress(const nsCOMArray<msgIAddressObject> &aHeader, + nsAString &name, nsACString &email); + +/** + * Given a raw message header value, extract the first email address found in + * the header. + */ +void ExtractEmail(const nsCOMArray<msgIAddressObject> &aHeader, + nsACString &email); + +/** + * Given a raw message header value, extract and clean up the first display + * name found in the header. If there is no display name, the email address is + * used instead. + */ +void ExtractName(const nsCOMArray<msgIAddressObject> &aHeader, + nsACString &name); + +/** + * Given an RFC 2047-decoded message header value, extract the first display + * name found in the header. If there is no display name, the email address is + * returned instead. + */ +void ExtractName(const nsCOMArray<msgIAddressObject> &aDecodedHeader, + nsAString &name); + +} // namespace mailnews +} // namespace mozilla + +#endif diff --git a/mailnews/mime/public/moz.build b/mailnews/mime/public/moz.build new file mode 100644 index 000000000..58243068e --- /dev/null +++ b/mailnews/mime/public/moz.build @@ -0,0 +1,36 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'msgIStructuredHeaders.idl', + 'nsICMSDecoder.idl', + 'nsICMSEncoder.idl', + 'nsICMSMessage.idl', + 'nsICMSMessage2.idl', + 'nsICMSMessageErrors.idl', + 'nsICMSSecureMessage.idl', + 'nsIMimeConverter.idl', + 'nsIMimeEmitter.idl', + 'nsIMimeHeaders.idl', + 'nsIMimeMiscStatus.idl', + 'nsIMimeStreamConverter.idl', + 'nsIMsgHeaderParser.idl', + 'nsIPgpMimeProxy.idl', + 'nsISimpleMimeConverter.idl', +] + +XPIDL_MODULE = 'mime' + +EXPORTS += [ + 'nsIMimeContentTypeHandler.h', + 'nsIMimeObjectClassAccess.h', + 'nsMailHeaders.h', + 'nsMsgMimeCID.h', +] + +EXPORTS.mozilla.mailnews += [ + 'MimeEncoder.h', + 'MimeHeaderParser.h', +] diff --git a/mailnews/mime/public/msgIStructuredHeaders.idl b/mailnews/mime/public/msgIStructuredHeaders.idl new file mode 100644 index 000000000..9d3b3aa9a --- /dev/null +++ b/mailnews/mime/public/msgIStructuredHeaders.idl @@ -0,0 +1,209 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +#include "nsCOMArray.h" + +#define NS_ISTRUCTUREDHEADERS_CONTRACTID \ + "@mozilla.org/messenger/structuredheaders;1" +%} + +interface msgIAddressObject; +interface nsIUTF8StringEnumerator; + +/** + * A collection of MIME headers that are stored in a rich, structured format. + * + * The structured forms defined in this method use the structured decoder and + * encoder functionality found in jsmime to interconvert between the raw string + * forms found in the actual message text and the structured forms supported by + * this interface. Extensions can register their own custom structured headers + * by listing the source URL of their code under the category + * "custom-mime-encoder". + * + * The alternative modes of access for specific headers are expected to only + * work for the headers for which that mode of access is the correct one. For + * example, retrieving the "To" header from getUnstructuredHeader would fail, + * since the To header is not an unstructured header but an addressing header. + * They are provided mostly as a convenience to C++ which is much less able to + * utilize a fully generic format. + * + * With the exception of mismatched headers, the methods do not throw an + * exception if the header is missing but rather return an appropriate default + * value as indicated in their documentation. + */ +[scriptable, uuid(e109bf4f-788f-47ba-bfa8-1236ede05597)] +interface msgIStructuredHeaders : nsISupports { + /** + * Retrieve the value of the header stored in this set of headers. If the + * header is not present, then undefined is returned. + * + * @param aHeaderName The name of the header to retrieve. + */ + jsval getHeader(in string aHeaderName); + + /** + * Return true if and only if the given header is already set. + * + * @param aHeaderName The name of the header to retrieve. + */ + bool hasHeader(in string aHeaderName); + + /** + * Retrieve the value of the header as if it is an unstructured header. Such + * headers include most notably the Subject header. If the header is not + * present, then null is returned. This is reflected in C++ as an empty string + * with IsVoid() set to true (distinguishing it from a header that is present + * but contains an empty string). + * + * @param aHeaderName The name of the header to retrieve. + */ + AString getUnstructuredHeader(in string aHeaderName); + + /** + * Retrieve the value of the header if it is an addressing header, such as the + * From or To headers. If the header is not present, then an empty array is + * returned. + * + * @param aHeaderName The name of the header to retrieve. + * @param aPreserveGroups If false (the default), then the result is a flat + * list of addresses, with all group annotations + * removed. + * If true, then some address objects may represent + * groups in the header, preserving the original header + * structure. + */ + void getAddressingHeader(in string aHeaderName, + [optional] in boolean aPreserveGroups, [optional] out unsigned long count, + [array, size_is(count), retval] out msgIAddressObject addresses); + + /** + * Retrieve a raw version of the header value as would be represented in MIME. + * This form does not include the header name and colon, trailing whitespace, + * nor embedded CRLF pairs in the case of very long header names. + * + * @param aHeaderName The name of the header to retrieve. + */ + AUTF8String getRawHeader(in string aHeaderName); + + /** + * Retrieve an enumerator of the names of all headers in this set of headers. + * The header names returned may be in different cases depending on the + * precise implementation of this interface, so implementations should not + * rely on an exact kind of case being returned. + */ + readonly attribute nsIUTF8StringEnumerator headerNames; + + /** + * Retrieve the MIME representation of all of the headers. + * + * The header values are emitted in an ASCII form, unless internationalized + * email addresses are involved. The extra CRLF indicating the end of headers + * is not included in this representation. + * + * This accessor is provided mainly for the benefit of C++ consumers of this + * interface, since the JSMime headeremitter functionality allows more + * fine-grained customization of the results. + */ + AUTF8String buildMimeText(); + +%{C++ + /** + * A special variant of getAddressingHeader that is specialized better for C++ + * users of this API. + */ + nsresult GetAddressingHeader(const char *aPropertyName, + nsCOMArray<msgIAddressObject> &addrs, + bool aPreserveGroups = false) + { + msgIAddressObject **addrPtr; + uint32_t length; + nsresult rv = GetAddressingHeader(aPropertyName, aPreserveGroups, &length, + &addrPtr); + NS_ENSURE_SUCCESS(rv, rv); + addrs.Adopt(addrPtr, length); + return NS_OK; + } +%} + +}; + +/** + * An interface that enhances msgIStructuredHeaders by allowing the values of + * headers to be modified. + */ +[scriptable, uuid(5dcbbef6-2356-45d8-86d7-b3e73f9c9a0c)] +interface msgIWritableStructuredHeaders : msgIStructuredHeaders { + /** + * Store the given value for the given header, overwriting any previous value + * that was stored for said header. + * + * @param aHeaderName The name of the header to store. + * @param aValue The rich, structured value of the header to store. + */ + void setHeader(in string aHeaderName, in jsval aValue); + + /** + * Forget any previous value that was stored for the given header. + * + * @param aHeaderName The name of the header to delete. + */ + void deleteHeader(in string aHeaderName); + + /** + * Copy all of the structured values from another set of structured headers to + * the current one, overwriting any values that may have been specified + * locally. Note that the copy is a shallow copy of the value. + * + * @param aOtherHeaders A set of header values to be copied. + */ + void addAllHeaders(in msgIStructuredHeaders aOtherHeaders); + + /** + * Set the value of the header as if it were an unstructured header. Such + * headers include most notably the Subject header. + * + * @param aHeaderName The name of the header to store. + * @param aValue The value to store. + */ + void setUnstructuredHeader(in string aHeaderName, in AString aValue); + + /** + * Set the value of the header as if it were an addressing header, such as the + * From or To headers. + * + * @param aHeaderName The name of the header to store. + */ + void setAddressingHeader(in string aHeaderName, + [array, size_is(aCount)] in msgIAddressObject aAddresses, + in unsigned long aCount); + + /** + * Store the value of the header using a raw version as would be represented + * in MIME. So as to handle 8-bit headers properly, the charset needs to be + * specified, although it may be null. + * + * @param aHeaderName The name of the header to store. + * @param aValue The raw MIME header value to store. + * @param aCharset The expected charset of aValue. + */ + void setRawHeader(in string aHeaderName, in ACString aValue, + in string aCharset); + +%{C++ + /** + * A special variant of setAddressingHeader that is specialized better for C++ + * users of this API. + */ + nsresult SetAddressingHeader(const char *aPropertyName, + const nsCOMArray<msgIAddressObject> &addrs) + { + return SetAddressingHeader(aPropertyName, + const_cast<nsCOMArray<msgIAddressObject>&>(addrs).Elements(), + addrs.Length()); + } +%} +}; diff --git a/mailnews/mime/public/nsICMSDecoder.idl b/mailnews/mime/public/nsICMSDecoder.idl new file mode 100644 index 000000000..f67f1e640 --- /dev/null +++ b/mailnews/mime/public/nsICMSDecoder.idl @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{ C++ +typedef void (*NSSCMSContentCallback)(void *arg, const char *buf, unsigned long len); + +#define NS_CMSDECODER_CONTRACTID "@mozilla.org/nsCMSDecoder;1" +%} + +native NSSCMSContentCallback(NSSCMSContentCallback); + +interface nsICMSMessage; + +/** + * nsICMSDecoder + * Interface to decode an CMS message + */ +[uuid(c7c7033b-f341-4065-aadd-7eef55ce0dda)] +interface nsICMSDecoder : nsISupports +{ + void start(in NSSCMSContentCallback cb, in voidPtr arg); + void update(in string aBuf, in long aLen); + void finish(out nsICMSMessage msg); +}; + diff --git a/mailnews/mime/public/nsICMSEncoder.idl b/mailnews/mime/public/nsICMSEncoder.idl new file mode 100644 index 000000000..a82f5e0de --- /dev/null +++ b/mailnews/mime/public/nsICMSEncoder.idl @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{ C++ +typedef void (*NSSCMSContentCallback)(void *arg, const char *buf, unsigned long len); + +#define NS_CMSENCODER_CONTRACTID "@mozilla.org/nsCMSEncoder;1" +%} + +native NSSCMSContentCallback(NSSCMSContentCallback); + +interface nsICMSMessage; + +/** + * nsICMSEncoder + * Interface to Encode an CMS message + */ +[uuid(17dc4fb4-e379-4e56-a4a4-57cdcc74816f)] +interface nsICMSEncoder : nsISupports +{ + void start(in nsICMSMessage aMsg, in NSSCMSContentCallback cb, in voidPtr arg); + void update(in string aBuf, in long aLen); + void finish(); + void encode(in nsICMSMessage aMsg); +}; + diff --git a/mailnews/mime/public/nsICMSMessage.idl b/mailnews/mime/public/nsICMSMessage.idl new file mode 100644 index 000000000..1d67bf51d --- /dev/null +++ b/mailnews/mime/public/nsICMSMessage.idl @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{ C++ +#define NS_CMSMESSAGE_CONTRACTID "@mozilla.org/nsCMSMessage;1" +%} + +[ptr] native UnsignedCharPtr(unsigned char); + +interface nsIX509Cert; +interface nsIArray; + +/** + * nsICMSMessage + * Interface to a CMS Message + */ +[uuid(c6d51c22-73e9-4dad-86b9-bde584e33c63)] +interface nsICMSMessage : nsISupports +{ + void contentIsSigned(out boolean aSigned); + void contentIsEncrypted(out boolean aEncrypted); + void getSignerCommonName(out string aName); + void getSignerEmailAddress(out string aEmail); + void getSignerCert(out nsIX509Cert scert); + void getEncryptionCert(out nsIX509Cert ecert); + void verifySignature(); + void verifyDetachedSignature(in UnsignedCharPtr aDigestData, in unsigned long aDigestDataLen); + void CreateEncrypted(in nsIArray aRecipientCerts); + + /* The parameter aDigestType must be one of the values in nsICryptoHash */ + void CreateSigned(in nsIX509Cert scert, in nsIX509Cert ecert, + in UnsignedCharPtr aDigestData, + in unsigned long aDigestDataLen, in int16_t aDigestType); +}; + diff --git a/mailnews/mime/public/nsICMSMessage2.idl b/mailnews/mime/public/nsICMSMessage2.idl new file mode 100644 index 000000000..9360279c6 --- /dev/null +++ b/mailnews/mime/public/nsICMSMessage2.idl @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsISMimeVerificationListener; + +[ptr] native UnsignedCharPtr(unsigned char); + +/* + * This interface is currently not marked scriptable, + * because its verification functions are meant to look like those + * in nsICMSMessage. At the time the ptr type is eliminated in both + * interfaces, both should be made scriptable. + */ + +[uuid(b21a3636-2287-4b9f-9a22-25f245981ef0)] +interface nsICMSMessage2 : nsISupports +{ + /** + * Async version of nsICMSMessage::VerifySignature. + * Code will be executed on a background thread and + * availability of results will be notified using a + * call to nsISMimeVerificationListener. + */ + void asyncVerifySignature(in nsISMimeVerificationListener listener); + + /** + * Async version of nsICMSMessage::VerifyDetachedSignature. + * Code will be executed on a background thread and + * availability of results will be notified using a + * call to nsISMimeVerificationListener. + * + * We are using "native unsigned char" ptr, because the function + * signatures of this one and nsICMSMessage::verifyDetachedSignature + * should be the identical. Cleaning up nsICMSMessages needs to be + * postponed, because this async version is needed on MOZILLA_1_8_BRANCH. + * + * Once both interfaces get cleaned up, the function signature should + * look like: + * [array, length_is(aDigestDataLen)] + * in octet aDigestData, + * in unsigned long aDigestDataLen); + */ + void asyncVerifyDetachedSignature(in nsISMimeVerificationListener listener, + in UnsignedCharPtr aDigestData, + in unsigned long aDigestDataLen); +}; + +[uuid(5226d698-0773-4f25-b94c-7944b3fc01d3)] +interface nsISMimeVerificationListener : nsISupports { + + /** + * Notify that results are ready, that have been requested + * using nsICMSMessage2::asyncVerify[Detached]Signature() + * + * verificationResultCode matches synchronous result code from + * nsICMSMessage::verify[Detached]Signature + */ + void notify(in nsICMSMessage2 verifiedMessage, + in nsresult verificationResultCode); +}; + diff --git a/mailnews/mime/public/nsICMSMessageErrors.idl b/mailnews/mime/public/nsICMSMessageErrors.idl new file mode 100644 index 000000000..d429cc671 --- /dev/null +++ b/mailnews/mime/public/nsICMSMessageErrors.idl @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsICMSMessageErrors + * Scriptable error constants for nsICMSMessage + */ +[scriptable,uuid(267f1a5b-88f7-413b-bc49-487e745282f1)] +interface nsICMSMessageErrors : nsISupports +{ + const long SUCCESS = 0; + const long GENERAL_ERROR = 1; + const long VERIFY_NOT_SIGNED = 1024; + const long VERIFY_NO_CONTENT_INFO = 1025; + const long VERIFY_BAD_DIGEST = 1026; + const long VERIFY_NOCERT = 1028; + const long VERIFY_UNTRUSTED = 1029; + const long VERIFY_ERROR_UNVERIFIED = 1031; + const long VERIFY_ERROR_PROCESSING = 1032; + const long VERIFY_BAD_SIGNATURE = 1033; + const long VERIFY_DIGEST_MISMATCH = 1034; + const long VERIFY_UNKNOWN_ALGO = 1035; + const long VERIFY_UNSUPPORTED_ALGO = 1036; + const long VERIFY_MALFORMED_SIGNATURE = 1037; + const long VERIFY_HEADER_MISMATCH = 1038; + const long VERIFY_NOT_YET_ATTEMPTED = 1039; + const long VERIFY_CERT_WITHOUT_ADDRESS = 1040; + + const long ENCRYPT_NO_BULK_ALG = 1056; + const long ENCRYPT_INCOMPLETE = 1057; +}; diff --git a/mailnews/mime/public/nsICMSSecureMessage.idl b/mailnews/mime/public/nsICMSSecureMessage.idl new file mode 100644 index 000000000..6442e319a --- /dev/null +++ b/mailnews/mime/public/nsICMSSecureMessage.idl @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIX509Cert; + +/** + * nsICMSManager (service) + * Interface to access users certificate store + */ +[scriptable, uuid(17103436-0111-4819-a751-0fc4aa6e3d79)] +interface nsICMSSecureMessage : nsISupports +{ + /** + * getCertByPrefID - a BASE64 string representing a user's + * certificate (or NULL if there isn't one) + */ + string getCertByPrefID(in string certID); + + /** + * decodeCert - decode a BASE64 string into an X509Certificate object + */ + nsIX509Cert decodeCert(in string value); + + /** + * sendMessage - send a text message to the recipient indicated + * by the base64-encoded cert. + */ + string sendMessage(in string msg, in string cert); + + /** + * receiveMessage - receive an encrypted (enveloped) message + */ + string receiveMessage(in string msg); +}; + +%{C++ +#define NS_CMSSECUREMESSAGE_CONTRACTID "@mozilla.org/nsCMSSecureMessage;1" +%} diff --git a/mailnews/mime/public/nsIMimeContentTypeHandler.h b/mailnews/mime/public/nsIMimeContentTypeHandler.h new file mode 100644 index 000000000..f1b828673 --- /dev/null +++ b/mailnews/mime/public/nsIMimeContentTypeHandler.h @@ -0,0 +1,65 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This interface is implemented by content type handlers that will be + * called upon by libmime to process various attachments types. The primary + * purpose of these handlers will be to represent the attached data in a + * viewable HTML format that is useful for the user + * + * Note: These will all register by their content type prefixed by the + * following: mimecth:text/vcard + * + * libmime will then use the XPCOM Component Manager to + * locate the appropriate Content Type handler + */ +#ifndef nsIMimeContentTypeHandler_h_ +#define nsIMimeContentTypeHandler_h_ + +typedef struct { + bool force_inline_display; +} contentTypeHandlerInitStruct; + +#include "nsISupports.h" +#include "mimecth.h" + +// {20DABD99-F8B5-11d2-8EE0-00A024A7D144} +#define NS_IMIME_CONTENT_TYPE_HANDLER_IID \ + { 0x20dabd99, 0xf8b5, 0x11d2, \ + { 0x8e, 0xe0, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } } + +// {20DABDA1-F8B5-11d2-8EE0-00A024A7D144} +#define NS_VCARD_CONTENT_TYPE_HANDLER_CID \ + { 0x20dabda1, 0xf8b5, 0x11d2, \ + { 0x8e, 0xe0, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } } + +#define NS_SMIME_CONTENT_TYPE_HANDLER_CID \ + { 0x20dabdac, 0xf8b5, 0x11d2, \ + { 0xFF, 0xe0, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } } + +#define NS_SIGNED_CONTENT_TYPE_HANDLER_CID \ + { 0x20dabdac, 0xf8b5, 0x11d2, \ + { 0xFF, 0xe0, 0x0, 0xaf, 0x19, 0xa7, 0xd1, 0x44 } } + +#define NS_PGPMIME_CONTENT_TYPE_HANDLER_CID \ + { 0x212f415f, 0xf8b5, 0x11d2, \ + { 0xFF, 0xe0, 0x0, 0xaf, 0x19, 0xa7, 0xd1, 0x44 } } + + +class nsIMimeContentTypeHandler : public nsISupports { +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMIME_CONTENT_TYPE_HANDLER_IID) + + NS_IMETHOD GetContentType(char **contentType) = 0; + + NS_IMETHOD CreateContentTypeHandlerClass(const char *content_type, + contentTypeHandlerInitStruct *initStruct, + MimeObjectClass **objClass) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIMimeContentTypeHandler, + NS_IMIME_CONTENT_TYPE_HANDLER_IID) + +#endif /* nsIMimeContentTypeHandler_h_ */ diff --git a/mailnews/mime/public/nsIMimeConverter.idl b/mailnews/mime/public/nsIMimeConverter.idl new file mode 100644 index 000000000..05aae7662 --- /dev/null +++ b/mailnews/mime/public/nsIMimeConverter.idl @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Encode/decode mail headers (via libmime). + */ +[scriptable, uuid(0d3f5531-2dbe-40d3-9280-f6ac45a6f5e0)] +interface nsIMimeConverter : nsISupports { + /** + * Suggested byte length limit for use when calling encodeMimePartIIStr_UTF8. + */ + const long MIME_ENCODED_WORD_SIZE = 72; + const long MAX_CHARSET_NAME_LENGTH = 64; + + /** + * Encode a UTF-8 string into a form containing only ASCII characters using + * RFC 2047 encoded words where necessary. + * + * Note that, although allowed for the present time, encoding to charsets + * other than UTF-8 is considered deprecated. + * + * @param aHeader UTF-8 header to encode. + * @param aAddressingHeader Is the header a list of email addresses? + * @param aMailCharset Charset to use when encoding (see above for note). + * @param aFieldNameLen Header field name length (ex: "From: " = 6) + * @param aMaxLineLen Maximum length of an individual line. Use + * MIME_ENCODED_WORD_SIZE for best results. + * + * @return The encoded header. + */ + AUTF8String encodeMimePartIIStr_UTF8(in AUTF8String aHeader, + in boolean aAddressingHeader, + in string aMailCharset, + in long aFieldNameLen, + in long aMaxLineLen); + + /** + * Decode a MIME header to UTF-8 if conversion is required. Marked as + * noscript because the return value may contain non-ASCII characters. + * + * @param header A (possibly encoded) header to decode. + * @param default_charset The charset to apply to un-labeled non-UTF-8 data. + * @param override_charset If true, default_charset is used instead of any + * charset labeling other than UTF-8. + * @param eatContinuations If true, unfold headers. + * + * @return UTF-8 encoded value if conversion was required, nullptr if no + * conversion was required. + */ + AUTF8String decodeMimeHeaderToUTF8(in ACString header, + in string default_charset, + in boolean override_charset, + in boolean eatContinuations); + + /** + * Decode a MIME header to UTF-16. + * + * @param header A (possibly encoded) header to decode. + * @param default_charset The charset to apply to un-labeled non-UTF-8 data. + * @param override_charset If true, default_charset is used instead of any + * charset labeling other than UTF-8. + * @param eatContinuations If true, unfold headers. + * + * @return UTF-16 encoded value as an AString. + */ + AString decodeMimeHeader(in string header, + in string default_charset, + in boolean override_charset, + in boolean eatContinuations); +}; + diff --git a/mailnews/mime/public/nsIMimeEmitter.idl b/mailnews/mime/public/nsIMimeEmitter.idl new file mode 100644 index 000000000..b129b4e86 --- /dev/null +++ b/mailnews/mime/public/nsIMimeEmitter.idl @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsISupports.idl" +#include "nsrootidl.idl" + +interface nsIOutputStream; +interface nsIInputStream; +interface nsIURI; +interface nsIStreamListener; +interface nsIChannel; + +[scriptable, uuid(eb9beb09-44de-4ad2-a560-f572b1afd534)] +interface nsMimeHeaderDisplayTypes +{ + const long MicroHeaders = 0; + const long NormalHeaders = 1; + const long AllHeaders = 2; +}; + +%{C++ +#define NS_IMIME_MISC_STATUS_KEY "@mozilla.org/MimeMiscStatus;1?type=" +%} + +[scriptable, uuid(7a57166f-2891-4122-9a74-6c3fab0caac3)] +interface nsIMimeEmitter : nsISupports { + + // Output listener to allow access to it from mime. + attribute nsIStreamListener outputListener; + + // These will be called to start and stop the total operation. + void initialize(in nsIURI url, in nsIChannel aChannel, in long aFormat); + void complete(); + + // Set the output stream/listener for processed data. + void setPipe(in nsIInputStream inputStream, in nsIOutputStream outStream); + + // Header handling routines. + void startHeader(in boolean rootMailHeader, in boolean headerOnly, + [const] in string msgID, [const] in string outCharset); + void addHeaderField([const] in string field, [const] in string value); + void addAllHeaders(in ACString allheaders); + + /** + * Write the HTML Headers for the current attachment. + * Note: Book case this with an EndHeader call. + * + * @param name The name of this attachment. + */ + void writeHTMLHeaders([const] in AUTF8String name); + + /** + * Finish writing the headers for the current attachment. + * + * @param name The name of this attachment. + */ + void endHeader([const] in AUTF8String name); + + void updateCharacterSet([const] in string aCharset); + + // Attachment handling routines. + void startAttachment([const] in AUTF8String name, + [const] in string contentType, + [const] in string url, in boolean aNotDownloaded); + void addAttachmentField([const] in string field, [const] in string value); + void endAttachment(); + + void endAllAttachments(); + + // Body handling routines. + void startBody(in boolean bodyOnly, [const] in string msgID, [const] in string outCharset); + void writeBody([const] in AUTF8String buf, out uint32_t amountWritten); + void endBody(); + + // Generic write routine. This is necessary for output that + // libmime needs to pass through without any particular parsing + // involved (i.e. decoded images, HTML Body Text, etc... + void write([const] in ACString buf, out uint32_t amountWritten); + void utilityWrite([const] in string buf); +}; diff --git a/mailnews/mime/public/nsIMimeHeaders.idl b/mailnews/mime/public/nsIMimeHeaders.idl new file mode 100644 index 000000000..3a0d05c49 --- /dev/null +++ b/mailnews/mime/public/nsIMimeHeaders.idl @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgIStructuredHeaders.idl" + +%{C++ +#define NS_IMIMEHEADERS_CONTRACTID \ + "@mozilla.org/messenger/mimeheaders;1" +%} + +/** + * An interface that can extract individual headers from a body of headers. + */ +[scriptable, uuid(a9222679-b991-4786-8314-f8819c3a2ba3)] +interface nsIMimeHeaders : msgIStructuredHeaders { + /// Feed in the text of headers + void initialize(in ACString allHeaders); + + /** + * Get the text of a header. + * + * Leading and trailing whitespace from headers will be stripped from the + * return value. If getAllOfThem is set to true, then the returned string will + * have all of the values of the header, in order, joined with the ',\r\n\t'. + * + * If the header is not present, then the returned value is NULL. + */ + ACString extractHeader(in string headerName, in boolean getAllOfThem); + + /** + * The current text of all header data. + * + * Unlike the asMimeText property, this result preserves the original + * representation of the header text, including alternative line endings or + * custom, non-8-bit text. For instances of this interface, this attribute is + * usually preferable to asMimeText. + */ + readonly attribute ACString allHeaders; +}; diff --git a/mailnews/mime/public/nsIMimeMiscStatus.idl b/mailnews/mime/public/nsIMimeMiscStatus.idl new file mode 100644 index 000000000..3621cdb85 --- /dev/null +++ b/mailnews/mime/public/nsIMimeMiscStatus.idl @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsISupports.idl" +#include "nsrootidl.idl" + +interface nsIChannel; +interface nsIMsgMailNewsUrl; +interface nsIUTF8StringEnumerator; +interface nsIMsgDBHdr; +interface nsIURI; +interface nsIWritablePropertyBag2; + +[scriptable, uuid(4644FB25-5255-11d3-82B8-444553540002)] +interface nsIMimeMiscStatus : nsISupports{ + + string GetWindowXULandJS(); + string GetGlobalXULandJS(); + string GetIndividualXUL(in string aName, in string aHeader, in string aEmail); + + long GetMiscStatus(in string aName, in string aEmail); + string GetImageURL(in long aStatus); +}; + +// this is a simple interface which allows someone to listen to all the headers +// that are discovered by mime. We can use this when displaying a message to update +// the msg header in JS. +[scriptable, uuid(e0e821f0-cecf-4cb3-be5b-ee58b6868343)] +interface nsIMsgHeaderSink : nsISupports +{ + // You must finish consuming the iterators before returning from processHeaders. aHeaderNames and aHeaderValues will ALWAYS have the same + // number of elements in them + void processHeaders(in nsIUTF8StringEnumerator aHeaderNames, in nsIUTF8StringEnumerator aHeaderValues, in boolean dontCollectAddress); + + void handleAttachment(in string contentType, in string url, in wstring displayName, in string uri, + in boolean aNotDownloaded); + + /** + * Add a metadata field to the current attachment, e.g. "X-Mozilla-PartSize". + * + * @param field The field to add + * @param value The value of the field + */ + void addAttachmentField(in string field, in string value); + void onEndAllAttachments(); + + // onEndMsgHeaders is called after libmime is done processing a message. At this point it is safe for + // elements like the UI to update junk status, process return receipts, etc. + void onEndMsgHeaders(in nsIMsgMailNewsUrl url); + + // onEndMsgDownload is triggered when layout says it is actually done rendering + // the message body in the UI. + void onEndMsgDownload(in nsIMsgMailNewsUrl url); + + attribute nsISupports securityInfo; + + /** + * onMsgHasRemoteContent is called each time content policy encounters remote + * content that it will block from loading. + * @param aMsgHdr header of the message the content is located in + * @param aContentURI location that will be blocked. + * @param aCanOverride can the blocking be overridden or not + */ + void onMsgHasRemoteContent(in nsIMsgDBHdr aMsgHdr, in nsIURI aContentURI, in boolean aCanOverride); + + readonly attribute nsIMsgDBHdr dummyMsgHeader; + + // used as a hook for extension mime content handlers to store data that can later + // be accessed by other parts of the code, e.g., UI code. + // TODO - Should replace securityInfo + readonly attribute nsIWritablePropertyBag2 properties; + // When streaming a new message, properties should be reset, so that there are + // not previous values lurking around. + void resetProperties(); +}; diff --git a/mailnews/mime/public/nsIMimeObjectClassAccess.h b/mailnews/mime/public/nsIMimeObjectClassAccess.h new file mode 100644 index 000000000..55d7a86ad --- /dev/null +++ b/mailnews/mime/public/nsIMimeObjectClassAccess.h @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This interface is implemented by libmime. This interface is used by + * a Content-Type handler "Plug In" (i.e. vCard) for accessing various + * internal information about the object class system of libmime. When + * libmime progresses to a C++ object class, this would probably change. + */ +#ifndef nsIMimeObjectClassAccess_h_ +#define nsIMimeObjectClassAccess_h_ + +// {C09EDB23-B7AF-11d2-B35E-525400E2D63A} +#define NS_IMIME_OBJECT_CLASS_ACCESS_IID \ + { 0xc09edb23, 0xb7af, 0x11d2, \ + { 0xb3, 0x5e, 0x52, 0x54, 0x0, 0xe2, 0xd6, 0x3a } } + +class nsIMimeObjectClassAccess : public nsISupports { +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMIME_OBJECT_CLASS_ACCESS_IID) + + // These methods are all implemented by libmime to be used by + // content type handler plugins for processing stream data. + + // This is the write call for outputting processed stream data. + NS_IMETHOD MimeObjectWrite(void *mimeObject, + char *data, + int32_t length, + bool user_visible_p) = 0; + + // The following group of calls expose the pointers for the object + // system within libmime. + NS_IMETHOD GetmimeInlineTextClass(void **ptr) = 0; + NS_IMETHOD GetmimeLeafClass(void **ptr) = 0; + NS_IMETHOD GetmimeObjectClass(void **ptr) = 0; + NS_IMETHOD GetmimeContainerClass(void **ptr) = 0; + NS_IMETHOD GetmimeMultipartClass(void **ptr) = 0; + NS_IMETHOD GetmimeMultipartSignedClass(void **ptr) = 0; + NS_IMETHOD GetmimeEncryptedClass(void **ptr) = 0; + + NS_IMETHOD MimeCreate(char* content_type, void * hdrs, void * opts, void **ptr) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIMimeObjectClassAccess, + NS_IMIME_OBJECT_CLASS_ACCESS_IID) + +#endif /* nsIMimeObjectClassAccess_h_ */ diff --git a/mailnews/mime/public/nsIMimeStreamConverter.idl b/mailnews/mime/public/nsIMimeStreamConverter.idl new file mode 100644 index 000000000..bde6175c4 --- /dev/null +++ b/mailnews/mime/public/nsIMimeStreamConverter.idl @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsrootidl.idl" +#include "nsIMimeHeaders.idl" +#include "nsIMsgIdentity.idl" +#include "nsIMsgHdr.idl" + +interface nsIURI; + +typedef long nsMimeOutputType; + +[scriptable, uuid(fdc2956e-d558-43fb-bfdd-fb9511229aa5)] +interface nsMimeOutput +{ + const long nsMimeMessageSplitDisplay = 0; + const long nsMimeMessageHeaderDisplay = 1; + const long nsMimeMessageBodyDisplay = 2; + const long nsMimeMessageQuoting = 3; + const long nsMimeMessageBodyQuoting = 4; + const long nsMimeMessageRaw = 5; + const long nsMimeMessageDraftOrTemplate = 6; + const long nsMimeMessageEditorTemplate = 7; + const long nsMimeMessagePrintOutput = 9; + const long nsMimeMessageSaveAs = 10; + const long nsMimeMessageSource = 11; + const long nsMimeMessageFilterSniffer = 12; + const long nsMimeMessageDecrypt = 13; + const long nsMimeMessageAttach = 14; + const long nsMimeUnknown = 15; +}; + +[scriptable, uuid(FA81CAA0-6261-11d3-8311-00805F2A0107)] +interface nsIMimeStreamConverterListener : nsISupports{ + void onHeadersReady(in nsIMimeHeaders headers); +}; + +/** + * This interface contains mailnews mime specific information for stream + * converters. Most of the code is just stuff that has been moved out + * of nsIStreamConverter.idl to make it more generic. + */ +[scriptable, uuid(d894c833-29c5-495b-880c-9a9f847bfdc9)] +interface nsIMimeStreamConverter : nsISupports { + + /** + * Set the desired mime output type on the converer. + */ + void SetMimeOutputType(in nsMimeOutputType aType); + + void GetMimeOutputType(out nsMimeOutputType aOutFormat); + + /** + * This is needed by libmime for MHTML link processing...the url is the URL + * string associated with this input stream. + */ + void SetStreamURI(in nsIURI aURI); + + /** + * Used to extract headers while parsing a message. + */ + void SetMimeHeadersListener(in nsIMimeStreamConverterListener listener, in nsMimeOutputType aType); + + /** + * This is used for forward inline, both as a filter action, and from the UI. + */ + attribute boolean forwardInline; + + /** + * This is used for a forward inline filter action. When streaming is done, + * we won't open a compose window with the editor contents. + */ + attribute boolean forwardInlineFilter; + + /** + * Address for the forward inline filter to forward the message to. + */ + attribute AString forwardToAddress; + /** + * Use the opposite compose format, used for forward inline. + */ + attribute boolean overrideComposeFormat; + + /** + * This is used for OpenDraft, OpenEditorTemplate and Forward inline (which use OpenDraft) + */ + attribute nsIMsgIdentity identity; + attribute string originalMsgURI; + attribute nsIMsgDBHdr origMsgHdr; +}; diff --git a/mailnews/mime/public/nsIMsgHeaderParser.idl b/mailnews/mime/public/nsIMsgHeaderParser.idl new file mode 100644 index 000000000..2512623d8 --- /dev/null +++ b/mailnews/mime/public/nsIMsgHeaderParser.idl @@ -0,0 +1,235 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +#define NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID \ + "@mozilla.org/messenger/headerparser;1" +%} + +/** + * A structured representation of an address. + * + * This is meant to correspond to the address production from RFC 5322. As a + * result, an instance of this interface is either a single mailbox or a group + * of mailboxes. The difference between the two forms is in which attribute is + * undefined: mailboxes leave the members attribute undefined while groups leave + * the email attribute undefined. + * + * For example, an address like "John Doe <jdoe@machine.example>" will, when + * parsed, result in an instance with the name attribute set to "John Doe", the + * email attribute set to "jdoe@machine.example", and the members variable left + * undefined. + * + * A group like "undisclosed-recipients:;" will, when parsed, result in an + * instance with the name attribute set to "undisclosed-recipients", the email + * attribute left defined, and the members variable set to an empty array. + * + * In general, the attributes of this interface are always meant to be in a form + * suitable for display purposes, and not in a form usable for MIME emission. In + * particular, email addresses could be fully internationalized and non-ASCII, + * RFC 2047-encoded words may appear in names, and the name or email parameters + * are unquoted. + */ +[scriptable, uuid(b19f5636-ebc4-470e-b46c-98b5fc7e88c9)] +interface msgIAddressObject : nsISupports { + /// The name of the mailbox or group. + readonly attribute AString name; + + /// The email of the mailbox. + readonly attribute AString email; + + /** + * The member mailboxes of this group, which may be an empty list. + * + * Due to the limitations of XPIDL, the type of this attribute cannot be + * properly reflected. It is actually an array of msgIAddressObject instances, + * although it is instead undefined if this object does not represent a group. + */ + readonly attribute jsval group; + + /// Return a string form of this object that is suitable for display. + AString toString(); +}; + +/** + * A utility service for manipulating addressing headers in email messages. + * + * This interface is designed primarily for use from JavaScript code; code in + * C++ should use the methods in MimeHeaderParser.h instead, as it is better + * designed to take advantage of C++'s features, particularly with respect to + * arrays. + * + * There are two methods for parsing MIME headers, one for RFC 2047-decoded + * strings, and one for non-RFC 2047-decoded strings. + * + * In general, this API attempts to preserve the format of addresses as + * faithfully as possible. No case normalization is performed at any point. + * However, internationalized email addresses generally need extra processing to + * work properly, so while this API should handle them without issues, consumers + * of this API may fail to work properly when presented with such addresses. To + * ease use for such cases, future versions of the API may incorporate necessary + * normalization steps to make oblivious consumers more likely to work properly. + */ +[scriptable, uuid(af2f9dd1-0226-4835-b981-a4f88b5e97cc)] +interface nsIMsgHeaderParser : nsISupports { + /** + * Parse an address-based header that has not yet been 2047-decoded. + * + * The result of this method is an array of objects described in the above + * comment. Note that the header is a binary string that will be decoded as if + * passed into nsIMimeConverter. + * + * @param aEncodedHeader The RFC 2047-encoded header to parse. + * @param aHeaderCharset The charset to assume for raw octets. + * @param aPreserveGroups If false (the default), the result is a flat array + * of mailbox objects, containing no group objects. + * @return An array corresponding to the header description. + */ + void parseEncodedHeader(in ACString aEncodedHeader, + in string aHeaderCharset, + [optional] in bool aPreserveGroups, + [optional] out unsigned long length, + [retval, array, size_is(length)] + out msgIAddressObject addresses); + + /** + * Parse an address-based header that has been 2047-decoded. + * + * The result of this method is an array of objects described in the above + * comment. Note that the header is a binary string that will be decoded as if + * passed into nsIMimeConverter. + * + * @param aDecodedHeader The non-RFC 2047-encoded header to parse. + * @param aPreserveGroups If false (the default), the result is a flat array + * of mailbox objects, containing no group objects. + * @return An array corresponding to the header description. + */ + void parseDecodedHeader(in AString aDecodedHeader, + [optional] in bool aPreserveGroups, + [optional] out unsigned long length, + [retval, array, size_is(length)] + out msgIAddressObject addresses); + + /** + * Given an array of addresses, make a MIME header suitable for emission. + * + * The return value of this method is not directly suitable for use in a MIME + * message but rather needs to be passed through nsIMimeConverter first to + * have RFC-2047 encoding applied and the resulting output wrapped to adhere + * to maximum line length formats. + * + * @param aAddresses An array corresponding to the header description. + * @param aLength The length of said array of addresses. + * @return A string that is suitable for writing in a MIME message. + */ + AString makeMimeHeader([array, size_is(aLength)] + in msgIAddressObject aAddresses, + in unsigned long aLength); + + /** + * Return the first address in the list in a format suitable for display. + * + * This is largely a convience method for handling From headers (or similar), + * which are expected to only have a single element in them. It is exactly + * equivalent to saying (parseDecodedHeader(decodedHeader))[0].toString(). + * + * @param decodedHeader The non-RFC 2047-encoded header to parse. + * @return The first address, suitable for display. + */ + AString extractFirstName(in AString aDecodedHeader); + + /** + * Returns a copy of the input which may have had some addresses removed. + * Addresses are removed if they are already in either of the supplied + * address lists. + * + * Addresses are considered to be the same if they contain the same email + * part (case-insensitive). Since the email part should never be RFC + * 2047-encoded, this method should work whether or not the header is + * RFC 2047-encoded. + * + * @param aAddrs The addresses to remove duplicates from. + * @param aOtherAddrs Other addresses that the duplicate removal process also + * checks for duplicates against. Addresses in this list + * will not be added to the result. + * @return The original header with duplicate addresses removed. + */ + AUTF8String removeDuplicateAddresses(in AUTF8String aAddrs, + [optional] in AUTF8String aOtherAddrs); + + /// Return a structured mailbox object having the given name and email. + msgIAddressObject makeMailboxObject(in AString aName, in AString aEmail); + + /// Return a structured group object having the given name and members. + msgIAddressObject makeGroupObject(in AString aName, + [array, size_is(aLength)] in msgIAddressObject aMembers, + in unsigned long aLength); + + /** + * Return an array of structured mailbox objects for the given display name + * string. + * + * The string is expected to be a comma-separated sequence of strings that + * would be produced by msgIAddressObject::toString(). For example, the string + * "Bond, James <agent007@mi5.invalid>" would produce one address object, + * while the string "webmaster@nowhere.invalid, child@nowhere.invalid" would + * produce two address objects. + * + * Note that the input string is RFC 2231 and RFC 2047 decoded but no UTF-8 + * decoding takes place. + */ + void makeFromDisplayAddress(in AString aDisplayAddresses, + [optional] out unsigned long count, + [retval, array, size_is(count)] out msgIAddressObject addresses); + + [deprecated] void parseHeadersWithArray(in wstring aLine, + [array, size_is(count)] out wstring aEmailAddresses, + [array, size_is(count)] out wstring aNames, + [array, size_is(count)] out wstring aFullNames, + [retval] out unsigned long count); + + + /** + * Given a string which contains a list of Header addresses, returns a + * comma-separated list of just the `mailbox' portions. + * + * @param aLine The header line to parse. + * @return A comma-separated list of just the mailbox parts + * of the email-addresses. + */ + [deprecated] ACString extractHeaderAddressMailboxes(in ACString aLine); + + /** + * Given a string which contains a list of Header addresses, returns a + * comma-separated list of just the `user name' portions. If any of + * the addresses doesn't have a name, then the mailbox is used instead. + * + * @param aLine The header line to parse. + * @return A comma-separated list of just the name parts + * of the addresses. + */ + [deprecated] AUTF8String extractHeaderAddressNames(in AUTF8String aLine); + + /* + * Like extractHeaderAddressNames, but only returns the first name in the + * header if there is one. This function will return unquoted strings suitable + * for display. + * + * @param aLine The header line to parse. + * @return The first name found in the list. + */ + [deprecated] AUTF8String extractHeaderAddressName(in AUTF8String aLine); + + /** + * Given a name and email address, produce a string that is suitable for + * emitting in a MIME header (after applying RFC 2047 encoding). + * + * @note This is a temporary method. + */ + [deprecated] AString makeMimeAddress(in AString aName, in AString aEmail); +}; + diff --git a/mailnews/mime/public/nsIPgpMimeProxy.idl b/mailnews/mime/public/nsIPgpMimeProxy.idl new file mode 100644 index 000000000..375a7b776 --- /dev/null +++ b/mailnews/mime/public/nsIPgpMimeProxy.idl @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIStreamListener.idl" +#include "nsIURI.idl" + +%{C++ +typedef int (*MimeDecodeCallbackFun)(const char *buf, int32_t buf_size, void *output_closure); + +#define NS_PGPMIMEPROXY_CLASSNAME "PGP/Mime Decryption" +#define NS_PGPMIMEPROXY_CONTRACTID "@mozilla.org/mime/pgp-mime-decrypt;1" + +#define NS_PGPMIMEPROXY_CID \ +{ /* 6b7e094f-536b-40dc-b3a4-e3d729205ce1 */ \ + 0x6b7e094f, 0x536b, 0x40dc, \ +{0xb3, 0xa4, 0xe3, 0xd7, 0x29, 0x20, 0x5c, 0xe1 } } +%} + +native MimeDecodeCallbackFun(MimeDecodeCallbackFun); + +/** + * nsIPgpMimeProxy is a proxy for a (JS-)addon for OpenPGP/MIME decryption + */ + +[scriptable, uuid(6b7e094f-536b-40dc-b3a4-e3d729205ce1)] +interface nsIPgpMimeProxy : nsIStreamListener +{ + /** + * set the decoder callback into mimelib + */ + [noscript] void setMimeCallback(in MimeDecodeCallbackFun outputFun, + in voidPtr outputClosure, + in nsIURI myUri); + + /** + * init function + */ + void init(); + + /** + * process encoded data received from mimelib + */ + void write(in string buf, in unsigned long count); + + /** + * finish writing (EOF) from mimelib + */ + void finish(); + + /** + * the listener that receives the OpenPGP/MIME data stream and decrypts + * the message + */ + attribute nsIStreamListener decryptor; + + attribute ACString contentType; + + /** + * The particular part number of the multipart object we are working on. The + * numbering is the same as in URLs that use the form "...?part=1.1.2". + * + * The value stored in mimePart is only the number, e.g. "1" or "1.1.2" + */ + attribute ACString mimePart; +}; + + +/////////////////////////////////////////////////////////////////////////////// diff --git a/mailnews/mime/public/nsISimpleMimeConverter.idl b/mailnews/mime/public/nsISimpleMimeConverter.idl new file mode 100644 index 000000000..907a1bb0e --- /dev/null +++ b/mailnews/mime/public/nsISimpleMimeConverter.idl @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsIURI; + +[scriptable, uuid(FC6E8234-BBF3-44A1-9802-5F023A929173)] +interface nsISimpleMimeConverter : nsISupports +{ + // uri of message getting displayed + attribute nsIURI uri; + AUTF8String convertToHTML(in ACString contentType, + in AUTF8String data); +}; + +%{C++ + +#define NS_SIMPLEMIMECONVERTERS_CATEGORY "simple-mime-converters" + +%} diff --git a/mailnews/mime/public/nsMailHeaders.h b/mailnews/mime/public/nsMailHeaders.h new file mode 100644 index 000000000..d8a3131a5 --- /dev/null +++ b/mailnews/mime/public/nsMailHeaders.h @@ -0,0 +1,90 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This interface allows any module to access the encoder/decoder + * routines for RFC822 headers. This will allow any mail/news module + * to call on these routines. + */ +#ifndef nsMailHeaders_h_ +#define nsMailHeaders_h_ + +/* + * These are the defines for standard header field names. + */ +#define HEADER_BCC "BCC" +#define HEADER_CC "CC" +#define HEADER_CONTENT_BASE "Content-Base" +#define HEADER_CONTENT_LOCATION "Content-Location" +#define HEADER_CONTENT_ID "Content-ID" +#define HEADER_CONTENT_DESCRIPTION "Content-Description" +#define HEADER_CONTENT_DISPOSITION "Content-Disposition" +#define HEADER_CONTENT_ENCODING "Content-Encoding" +#define HEADER_CONTENT_LANGUAGE "Content-Language" +#define HEADER_CONTENT_LENGTH "Content-Length" +#define HEADER_CONTENT_NAME "Content-Name" +#define HEADER_CONTENT_TRANSFER_ENCODING "Content-Transfer-Encoding" +#define HEADER_CONTENT_TYPE "Content-Type" +#define HEADER_DATE "Date" +#define HEADER_DISTRIBUTION "Distribution" +#define HEADER_FCC "FCC" +#define HEADER_FOLLOWUP_TO "Followup-To" +#define HEADER_FROM "From" +#define HEADER_STATUS "Status" +#define HEADER_LINES "Lines" +#define HEADER_LIST_POST "List-Post" +#define HEADER_MAIL_FOLLOWUP_TO "Mail-Followup-To" +#define HEADER_MAIL_REPLY_TO "Mail-Reply-To" +#define HEADER_MESSAGE_ID "Message-ID" +#define HEADER_MIME_VERSION "MIME-Version" +#define HEADER_NEWSGROUPS "Newsgroups" +#define HEADER_ORGANIZATION "Organization" +#define HEADER_REFERENCES "References" +#define HEADER_REPLY_TO "Reply-To" +#define HEADER_RESENT_COMMENTS "Resent-Comments" +#define HEADER_RESENT_DATE "Resent-Date" +#define HEADER_RESENT_FROM "Resent-From" +#define HEADER_RESENT_MESSAGE_ID "Resent-Message-ID" +#define HEADER_RESENT_SENDER "Resent-Sender" +#define HEADER_RESENT_TO "Resent-To" +#define HEADER_RESENT_CC "Resent-CC" +#define HEADER_SENDER "Sender" +#define HEADER_SUBJECT "Subject" +#define HEADER_TO "To" +#define HEADER_APPROVED_BY "Approved-By" +#define HEADER_X_MAILER "X-Mailer" +#define HEADER_USER_AGENT "User-Agent" +#define HEADER_X_NEWSREADER "X-Newsreader" +#define HEADER_X_POSTING_SOFTWARE "X-Posting-Software" +#define HEADER_X_MOZILLA_STATUS "X-Mozilla-Status" +#define HEADER_X_MOZILLA_STATUS2 "X-Mozilla-Status2" +#define HEADER_X_MOZILLA_NEWSHOST "X-Mozilla-News-Host" +#define HEADER_X_MOZILLA_DRAFT_INFO "X-Mozilla-Draft-Info" +#define HEADER_X_UIDL "X-UIDL" +#define HEADER_XREF "XREF" +#define HEADER_X_SUN_CHARSET "X-Sun-Charset" +#define HEADER_X_SUN_CONTENT_LENGTH "X-Sun-Content-Length" +#define HEADER_X_SUN_CONTENT_LINES "X-Sun-Content-Lines" +#define HEADER_X_SUN_DATA_DESCRIPTION "X-Sun-Data-Description" +#define HEADER_X_SUN_DATA_NAME "X-Sun-Data-Name" +#define HEADER_X_SUN_DATA_TYPE "X-Sun-Data-Type" +#define HEADER_X_SUN_ENCODING_INFO "X-Sun-Encoding-Info" +#define HEADER_X_PRIORITY "X-Priority" + +#define HEADER_PARM_CHARSET "charset" +#define HEADER_PARM_START "start" +#define HEADER_PARM_BOUNDARY "BOUNDARY" +#define HEADER_PARM_FILENAME "FILENAME" +#define HEADER_PARM_NAME "NAME" +#define HEADER_PARM_TYPE "TYPE" + +#define HEADER_X_MOZILLA_PART_URL "X-Mozilla-PartURL" +#define HEADER_X_MOZILLA_PART_SIZE "X-Mozilla-PartSize" +#define HEADER_X_MOZILLA_PART_DOWNLOADED "X-Mozilla-PartDownloaded" +#define HEADER_X_MOZILLA_CLOUD_PART "X-Mozilla-Cloud-Part" +#define HEADER_X_MOZILLA_IDENTITY_KEY "X-Identity-Key" +#define HEADER_X_MOZILLA_ACCOUNT_KEY "X-Account-Key" +#define HEADER_X_MOZILLA_KEYWORDS "X-Mozilla-Keys" +#endif /* nsMailHeaders_h_ */ diff --git a/mailnews/mime/public/nsMsgMimeCID.h b/mailnews/mime/public/nsMsgMimeCID.h new file mode 100644 index 000000000..07dec95e5 --- /dev/null +++ b/mailnews/mime/public/nsMsgMimeCID.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsMessageMimeCID_h__ +#define nsMessageMimeCID_h__ + +#define NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID \ + NS_ISTREAMCONVERTER_KEY "?from=message/rfc822&to=application/vnd.mozilla.xul+xml" + +#define NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID1 \ + NS_ISTREAMCONVERTER_KEY "?from=message/rfc822&to=text/html" + +#define NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID2 \ + NS_ISTREAMCONVERTER_KEY "?from=message/rfc822&to=*/*" + +#define NS_MAILNEWS_MIME_STREAM_CONVERTER_CID \ +{ /* FAF4F9A6-60AD-11d3-989A-001083010E9B */ \ + 0xfaf4f9a6, 0x60ad, 0x11d3, { 0x98, 0x9a, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } } + +#define NS_MIME_CONVERTER_CONTRACTID \ + "@mozilla.org/messenger/mimeconverter;1" + +// {403B0540-B7C3-11d2-B35E-525400E2D63A} +#define NS_MIME_OBJECT_CLASS_ACCESS_CID \ + { 0x403b0540, 0xb7c3, 0x11d2, \ + { 0xb3, 0x5e, 0x52, 0x54, 0x0, 0xe2, 0xd6, 0x3a } } + +#define NS_MIME_OBJECT_CONTRACTID \ + "@mozilla.org/messenger/mimeobject;1" + +#endif // nsMessageMimeCID_h__ diff --git a/mailnews/mime/src/MimeHeaderParser.cpp b/mailnews/mime/src/MimeHeaderParser.cpp new file mode 100644 index 000000000..18d81023e --- /dev/null +++ b/mailnews/mime/src/MimeHeaderParser.cpp @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "mozilla/mailnews/Services.h" +#include "mozilla/DebugOnly.h" +#include "nsMemory.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsIMimeConverter.h" +#include "nsIMsgHeaderParser.h" + +namespace mozilla { +namespace mailnews { + +void detail::DoConversion(const nsTArray<nsString> &aUTF16Array, + nsTArray<nsCString> &aUTF8Array) +{ + uint32_t count = aUTF16Array.Length(); + aUTF8Array.SetLength(count); + for (uint32_t i = 0; i < count; ++i) + CopyUTF16toUTF8(aUTF16Array[i], aUTF8Array[i]); +} + +void MakeMimeAddress(const nsACString &aName, const nsACString &aEmail, + nsACString &full) +{ + nsAutoString utf16Address; + MakeMimeAddress(NS_ConvertUTF8toUTF16(aName), NS_ConvertUTF8toUTF16(aEmail), + utf16Address); + + CopyUTF16toUTF8(utf16Address, full); +} + +void MakeMimeAddress(const nsAString &aName, const nsAString &aEmail, + nsAString &full) +{ + nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser()); + + nsCOMPtr<msgIAddressObject> address; + headerParser->MakeMailboxObject(aName, aEmail, getter_AddRefs(address)); + msgIAddressObject *obj = address; + headerParser->MakeMimeHeader(&obj, 1, full); +} + +void MakeDisplayAddress(const nsAString &aName, const nsAString &aEmail, + nsAString &full) +{ + nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser()); + + nsCOMPtr<msgIAddressObject> object; + headerParser->MakeMailboxObject(aName, aEmail, getter_AddRefs(object)); + object->ToString(full); +} + +void RemoveDuplicateAddresses(const nsACString &aHeader, + const nsACString &aOtherEmails, + nsACString &result) +{ + nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser()); + + headerParser->RemoveDuplicateAddresses(aHeader, aOtherEmails, result); +} + +///////////////////////////////////////////// +// These are the core shim methods we need // +///////////////////////////////////////////// + +nsCOMArray<msgIAddressObject> DecodedHeader(const nsAString &aHeader) +{ + nsCOMArray<msgIAddressObject> retval; + if (aHeader.IsEmpty()) { + return retval; + } + nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser()); + msgIAddressObject **addresses = nullptr; + uint32_t length; + nsresult rv = headerParser->ParseDecodedHeader(aHeader, false, + &length, &addresses); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Javascript jsmime returned an error!"); + if (NS_SUCCEEDED(rv) && length > 0 && addresses) { + retval.Adopt(addresses, length); + } + return retval; +} + +nsCOMArray<msgIAddressObject> EncodedHeader(const nsACString &aHeader, + const char *aCharset) +{ + nsCOMArray<msgIAddressObject> retval; + if (aHeader.IsEmpty()) { + return retval; + } + nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser()); + msgIAddressObject **addresses = nullptr; + uint32_t length; + nsresult rv = headerParser->ParseEncodedHeader(aHeader, aCharset, + false, &length, &addresses); + MOZ_ASSERT(NS_SUCCEEDED(rv), "This should never fail!"); + if (NS_SUCCEEDED(rv) && length > 0 && addresses) { + retval.Adopt(addresses, length); + } + return retval; +} + +void ExtractAllAddresses(const nsCOMArray<msgIAddressObject> &aHeader, + nsTArray<nsString> &names, nsTArray<nsString> &emails) +{ + uint32_t count = aHeader.Length(); + + // Prefill arrays before we start + names.SetLength(count); + emails.SetLength(count); + + for (uint32_t i = 0; i < count; i++) + { + aHeader[i]->GetName(names[i]); + aHeader[i]->GetEmail(emails[i]); + } + + if (count == 1 && names[0].IsEmpty() && emails[0].IsEmpty()) + { + names.Clear(); + emails.Clear(); + } +} + +void ExtractDisplayAddresses(const nsCOMArray<msgIAddressObject> &aHeader, + nsTArray<nsString> &displayAddrs) +{ + uint32_t count = aHeader.Length(); + + displayAddrs.SetLength(count); + for (uint32_t i = 0; i < count; i++) + aHeader[i]->ToString(displayAddrs[i]); + + if (count == 1 && displayAddrs[0].IsEmpty()) + displayAddrs.Clear(); +} + +///////////////////////////////////////////////// +// All of these are based on the above methods // +///////////////////////////////////////////////// + +void ExtractEmails(const nsCOMArray<msgIAddressObject> &aHeader, + nsTArray<nsString> &emails) +{ + nsTArray<nsString> names; + ExtractAllAddresses(aHeader, names, emails); +} + +void ExtractEmail(const nsCOMArray<msgIAddressObject> &aHeader, + nsACString &email) +{ + AutoTArray<nsString, 1> names; + AutoTArray<nsString, 1> emails; + ExtractAllAddresses(aHeader, names, emails); + + if (emails.Length() > 0) + CopyUTF16toUTF8(emails[0], email); + else + email.Truncate(); +} + +void ExtractFirstAddress(const nsCOMArray<msgIAddressObject> &aHeader, + nsACString &name, nsACString &email) +{ + AutoTArray<nsString, 1> names, emails; + ExtractAllAddresses(aHeader, names, emails); + if (names.Length() > 0) + { + CopyUTF16toUTF8(names[0], name); + CopyUTF16toUTF8(emails[0], email); + } + else + { + name.Truncate(); + email.Truncate(); + } +} + +void ExtractFirstAddress(const nsCOMArray<msgIAddressObject> &aHeader, + nsAString &name, nsACString &email) +{ + AutoTArray<nsString, 1> names, emails; + ExtractAllAddresses(aHeader, names, emails); + if (names.Length() > 0) + { + name = names[0]; + CopyUTF16toUTF8(emails[0], email); + } + else + { + name.Truncate(); + email.Truncate(); + } +} + +void ExtractName(const nsCOMArray<msgIAddressObject> &aHeader, nsACString &name) +{ + nsCString email; + ExtractFirstAddress(aHeader, name, email); + if (name.IsEmpty()) + name = email; +} + +void ExtractName(const nsCOMArray<msgIAddressObject> &aHeader, nsAString &name) +{ + AutoTArray<nsString, 1> names; + AutoTArray<nsString, 1> emails; + ExtractAllAddresses(aHeader, names, emails); + if (names.Length() > 0) + { + if (names[0].IsEmpty()) + name = emails[0]; + else + name = names[0]; + } + else + { + name.Truncate(); + } +} + +} // namespace mailnews +} // namespace mozilla diff --git a/mailnews/mime/src/comi18n.cpp b/mailnews/mime/src/comi18n.cpp new file mode 100644 index 000000000..7425e32ff --- /dev/null +++ b/mailnews/mime/src/comi18n.cpp @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "comi18n.h" +#include "nsIStringCharsetDetector.h" +#include "nsMsgUtils.h" +#include "nsICharsetConverterManager.h" +#include "nsIMIMEHeaderParam.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgMimeCID.h" +#include "nsIMimeConverter.h" + + +//////////////////////////////////////////////////////////////////////////////// +// BEGIN PUBLIC INTERFACE +extern "C" { + + +void MIME_DecodeMimeHeader(const char *header, const char *default_charset, + bool override_charset, bool eatContinuations, + nsACString &result) +{ + nsresult rv; + nsCOMPtr <nsIMimeConverter> mimeConverter = + do_GetService(NS_MIME_CONVERTER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + result.Truncate(); + return; + } + mimeConverter->DecodeMimeHeaderToUTF8(nsDependentCString(header), + default_charset, override_charset, + eatContinuations, result); +} + +// UTF-8 utility functions. +//detect charset soly based on aBuf. return in aCharset +nsresult +MIME_detect_charset(const char *aBuf, int32_t aLength, const char** aCharset) +{ + nsresult res = NS_ERROR_UNEXPECTED; + nsString detector_name; + *aCharset = nullptr; + + NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, "intl.charset.detector", EmptyString(), detector_name); + + if (!detector_name.IsEmpty()) { + nsAutoCString detector_contractid; + detector_contractid.AssignLiteral(NS_STRCDETECTOR_CONTRACTID_BASE); + detector_contractid.Append(NS_ConvertUTF16toUTF8(detector_name)); + nsCOMPtr<nsIStringCharsetDetector> detector = do_CreateInstance(detector_contractid.get(), &res); + if (NS_SUCCEEDED(res)) { + nsDetectionConfident oConfident; + res = detector->DoIt(aBuf, aLength, aCharset, oConfident); + if (NS_SUCCEEDED(res) && (eBestAnswer == oConfident || eSureAnswer == oConfident)) { + return NS_OK; + } + } + } + return res; +} + +//Get unicode decoder(from inputcharset to unicode) for aInputCharset +nsresult +MIME_get_unicode_decoder(const char* aInputCharset, nsIUnicodeDecoder **aDecoder) +{ + nsresult res; + + // get charset converters. + nsCOMPtr<nsICharsetConverterManager> ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res); + if (NS_SUCCEEDED(res)) { + + // create a decoder (conv to unicode), ok if failed if we do auto detection + if (!*aInputCharset || !PL_strcasecmp("us-ascii", aInputCharset)) + res = ccm->GetUnicodeDecoderRaw("ISO-8859-1", aDecoder); + else + // GetUnicodeDecoderInternal in order to support UTF-7 messages + // + // XXX this means that even HTML messages in UTF-7 will be decoded + res = ccm->GetUnicodeDecoderInternal(aInputCharset, aDecoder); + } + + return res; +} + +//Get unicode encoder(from unicode to inputcharset) for aOutputCharset +nsresult +MIME_get_unicode_encoder(const char* aOutputCharset, nsIUnicodeEncoder **aEncoder) +{ + nsresult res; + + // get charset converters. + nsCOMPtr<nsICharsetConverterManager> ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res); + if (NS_SUCCEEDED(res) && *aOutputCharset) { + // create a encoder (conv from unicode) + res = ccm->GetUnicodeEncoder(aOutputCharset, aEncoder); + } + + return res; +} + +} /* end of extern "C" */ +// END PUBLIC INTERFACE + diff --git a/mailnews/mime/src/comi18n.h b/mailnews/mime/src/comi18n.h new file mode 100644 index 000000000..6be89cfc5 --- /dev/null +++ b/mailnews/mime/src/comi18n.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _COMI18N_LOADED_H_ +#define _COMI18N_LOADED_H_ + +#include "msgCore.h" + +class nsIUnicodeDecoder; +class nsIUnicodeEncoder; + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Decode MIME header to UTF-8. + * Uses MIME_ConvertCharset if the decoded string needs a conversion. + * + * + * @param header [IN] A header to decode. + * @param default_charset [IN] Default charset to apply to ulabeled non-UTF-8 8bit data + * @param override_charset [IN] If true, default_charset used instead of any charset labeling other than UTF-8 + * @param eatContinuations [IN] If true, unfold headers + * @param result [OUT] Decoded buffer + */ +void MIME_DecodeMimeHeader(const char *header, const char *default_charset, + bool override_charset, bool eatContinuations, + nsACString &result); + +nsresult MIME_detect_charset(const char *aBuf, int32_t aLength, const char** aCharset); +nsresult MIME_get_unicode_decoder(const char* aInputCharset, nsIUnicodeDecoder **aDecoder); +nsresult MIME_get_unicode_encoder(const char* aOutputCharset, nsIUnicodeEncoder **aEncoder); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif // _COMI18N_LOADED_H_ + diff --git a/mailnews/mime/src/extraMimeParsers.jsm b/mailnews/mime/src/extraMimeParsers.jsm new file mode 100644 index 000000000..101eddc20 --- /dev/null +++ b/mailnews/mime/src/extraMimeParsers.jsm @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function parseNewsgroups(headers) { + let ng = []; + for (let header of headers) { + ng = ng.concat(header.split(/\s*,\s*/)); + } + return ng; +} + +function emitNewsgroups(groups) { + // Don't encode the newsgroups names in RFC 2047... + if (groups.length == 1) + this.addText(groups[0], false); + else { + this.addText(groups[0], false); + for (let i = 1; i < groups.length; i++) { + this.addText(",", false); // only comma, no space! + this.addText(groups[i], false); + } + } +} + +jsmime.headerparser.addStructuredDecoder("Newsgroups", parseNewsgroups); +jsmime.headerparser.addStructuredDecoder("Followup-To", parseNewsgroups); +jsmime.headeremitter.addStructuredEncoder("Newsgroups", emitNewsgroups); +jsmime.headeremitter.addStructuredEncoder("Followup-To", emitNewsgroups); diff --git a/mailnews/mime/src/jsmime.jsm b/mailnews/mime/src/jsmime.jsm new file mode 100644 index 000000000..70728fe9c --- /dev/null +++ b/mailnews/mime/src/jsmime.jsm @@ -0,0 +1,90 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +// vim:set ts=2 sw=2 sts=2 et ft=javascript: + +Components.utils.import("resource://gre/modules/Services.jsm"); + +/** + * This file exports the JSMime code, polyfilling code as appropriate for use in + * Gecko. + */ + +// Load the core MIME parser. Since it doesn't define EXPORTED_SYMBOLS, we must +// use the subscript loader instead. +Services.scriptloader.loadSubScript("resource:///modules/jsmime/jsmime.js"); + +var EXPORTED_SYMBOLS = ["jsmime"]; + + +// A polyfill to support non-encoding-spec charsets. Since the only converter +// available to us from JavaScript has a very, very weak and inflexible API, we +// choose to rely on the regular text decoder unless absolutely necessary. +// support non-encoding-spec charsets. +function FakeTextDecoder(label="UTF-8", options = {}) { + this._reset(label); + // So nsIScriptableUnicodeConverter only gives us fatal=false, unless we are + // using UTF-8, where we only get fatal=true. The internals of said class tell + // us to use a C++-only class if we need better behavior. +} +FakeTextDecoder.prototype = { + _reset: function (label) { + this._encoder = Components.classes[ + "@mozilla.org/intl/scriptableunicodeconverter"] + .createInstance(Components.interfaces.nsIScriptableUnicodeConverter); + this._encoder.isInternal = true; + let manager = Components.classes[ + "@mozilla.org/charset-converter-manager;1"] + .createInstance(Components.interfaces.nsICharsetConverterManager); + this._encoder.charset = manager.getCharsetAlias(label); + }, + get encoding() { return this._encoder.charset; }, + decode: function (input, options = {}) { + let more = 'stream' in options ? options.stream : false; + let result = ""; + if (input !== undefined) { + let data = new Uint8Array(input); + result = this._encoder.convertFromByteArray(data, data.length); + } + // This isn't quite right--it won't handle errors if there are a few + // remaining bytes in the buffer, but it's the best we can do. + if (!more) + this._reset(this.encoding); + return result; + }, +}; + +var RealTextDecoder = TextDecoder; +function FallbackTextDecoder(charset, options) { + try { + return new RealTextDecoder(charset, options); + } catch (e) { + return new FakeTextDecoder(charset, options); + } +} + +TextDecoder = FallbackTextDecoder; + + +// The following code loads custom MIME encoders. +var CATEGORY_NAME = "custom-mime-encoder"; +Services.obs.addObserver(function (subject, topic, data) { + subject = subject.QueryInterface(Components.interfaces.nsISupportsCString) + .data; + if (data == CATEGORY_NAME) { + let url = catman.getCategoryEntry(CATEGORY_NAME, subject); + Services.scriptloader.loadSubScript(url, {}, "UTF-8"); + } +}, "xpcom-category-entry-added", false); + +var catman = Components.classes["@mozilla.org/categorymanager;1"] + .getService(Components.interfaces.nsICategoryManager); + +var entries = catman.enumerateCategory(CATEGORY_NAME); +while (entries.hasMoreElements()) { + let string = entries.getNext() + .QueryInterface(Components.interfaces.nsISupportsCString) + .data; + let url = catman.getCategoryEntry(CATEGORY_NAME, string); + Services.scriptloader.loadSubScript(url, {}, "UTF-8"); +} diff --git a/mailnews/mime/src/mime.def b/mailnews/mime/src/mime.def new file mode 100644 index 000000000..6b1c36bf9 --- /dev/null +++ b/mailnews/mime/src/mime.def @@ -0,0 +1,7 @@ +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. + +LIBRARY mime.dll + +EXPORTS diff --git a/mailnews/mime/src/mimeJSComponents.js b/mailnews/mime/src/mimeJSComponents.js new file mode 100644 index 000000000..5ba7ff084 --- /dev/null +++ b/mailnews/mime/src/mimeJSComponents.js @@ -0,0 +1,512 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://gre/modules/Deprecated.jsm"); +Components.utils.import("resource:///modules/jsmime.jsm"); +Components.utils.import("resource:///modules/mimeParser.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function HeaderHandler() { + this.value = ""; + this.deliverData = function (str) { this.value += str; }; + this.deliverEOF = function () {}; +} + +function StringEnumerator(iterator) { + this._iterator = iterator; + this._next = undefined; +} +StringEnumerator.prototype = { + QueryInterface: XPCOMUtils.generateQI([ + Components.interfaces.nsIUTF8StringEnumerator]), + _setNext: function () { + if (this._next !== undefined) + return; + this._next = this._iterator.next(); + }, + hasMore: function () { + this._setNext(); + return !this._next.done; + }, + getNext: function () { + this._setNext(); + let result = this._next; + this._next = undefined; + if (result.done) + throw Components.results.NS_ERROR_UNEXPECTED; + return result.value; + } +}; + +/** + * If we get XPConnect-wrapped objects for msgIAddressObjects, we will have + * properties defined for 'group' that throws off jsmime. This function converts + * the addresses into the form that jsmime expects. + */ +function fixXpconnectAddresses(addrs) { + return addrs.map((addr) => { + // This is ideally !addr.group, but that causes a JS strict warning, if + // group is not in addr, since that's enabled in all chrome code now. + if (!('group' in addr) || addr.group === undefined || addr.group === null) { + return MimeAddressParser.prototype.makeMailboxObject(addr.name, + addr.email); + } else { + return MimeAddressParser.prototype.makeGroupObject(addr.name, + fixXpconnectAddresses(addr.group)); + } + }); +} + +/** + * This is a base handler for supporting msgIStructuredHeaders, since we have + * two implementations that need the readable aspects of the interface. + */ +function MimeStructuredHeaders() { +} +MimeStructuredHeaders.prototype = { + getHeader: function (aHeaderName) { + let name = aHeaderName.toLowerCase(); + return this._headers.get(name); + }, + + hasHeader: function (aHeaderName) { + return this._headers.has(aHeaderName.toLowerCase()); + }, + + getUnstructuredHeader: function (aHeaderName) { + let result = this.getHeader(aHeaderName); + if (result === undefined || typeof result == "string") + return result; + throw Components.results.NS_ERROR_ILLEGAL_VALUE; + }, + + getAddressingHeader: function (aHeaderName, aPreserveGroups, count) { + let addrs = this.getHeader(aHeaderName); + if (addrs === undefined) { + addrs = []; + } else if (!Array.isArray(addrs)) { + throw Components.results.NS_ERROR_ILLEGAL_VALUE; + } + return fixArray(addrs, aPreserveGroups, count); + }, + + getRawHeader: function (aHeaderName) { + let result = this.getHeader(aHeaderName); + if (result === undefined) + return result; + + let value = jsmime.headeremitter.emitStructuredHeader(aHeaderName, + result, {}); + // Strip off the header name and trailing whitespace before returning... + value = value.substring(aHeaderName.length + 2).trim(); + // ... as well as embedded newlines. + value = value.replace(/\r\n/g, ''); + return value; + }, + + get headerNames() { + return new StringEnumerator(this._headers.keys()); + }, + + buildMimeText: function () { + if (this._headers.size == 0) { + return ""; + } + let handler = new HeaderHandler(); + let emitter = jsmime.headeremitter.makeStreamingEmitter(handler, { + useASCII: true + }); + for (let [value, header] of this._headers) { + emitter.addStructuredHeader(value, header); + } + emitter.finish(); + return handler.value; + }, +}; + + +function MimeHeaders() { +} +MimeHeaders.prototype = { + __proto__: MimeStructuredHeaders.prototype, + classDescription: "Mime headers implementation", + classID: Components.ID("d1258011-f391-44fd-992e-c6f4b461a42f"), + contractID: "@mozilla.org/messenger/mimeheaders;1", + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMimeHeaders, + Components.interfaces.msgIStructuredHeaders]), + + initialize: function MimeHeaders_initialize(allHeaders) { + this._headers = MimeParser.extractHeaders(allHeaders); + }, + + extractHeader: function MimeHeaders_extractHeader(header, getAll) { + if (!this._headers) + throw Components.results.NS_ERROR_NOT_INITIALIZED; + // Canonicalized to lower-case form + header = header.toLowerCase(); + if (!this._headers.has(header)) + return null; + var values = this._headers.getRawHeader(header); + if (getAll) + return values.join(",\r\n\t"); + else + return values[0]; + }, + + get allHeaders() { + return this._headers.rawHeaderText; + } +}; + +function MimeWritableStructuredHeaders() { + this._headers = new Map(); +} +MimeWritableStructuredHeaders.prototype = { + __proto__: MimeStructuredHeaders.prototype, + classID: Components.ID("c560806a-425f-4f0f-bf69-397c58c599a7"), + QueryInterface: XPCOMUtils.generateQI([ + Components.interfaces.msgIStructuredHeaders, + Components.interfaces.msgIWritableStructuredHeaders]), + + setHeader: function (aHeaderName, aValue) { + this._headers.set(aHeaderName.toLowerCase(), aValue); + }, + + deleteHeader: function (aHeaderName) { + this._headers.delete(aHeaderName.toLowerCase()); + }, + + addAllHeaders: function (aHeaders) { + let headerList = aHeaders.headerNames; + while (headerList.hasMore()) { + let header = headerList.getNext(); + this.setHeader(header, aHeaders.getHeader(header)); + } + }, + + setUnstructuredHeader: function (aHeaderName, aValue) { + this.setHeader(aHeaderName, aValue); + }, + + setAddressingHeader: function (aHeaderName, aAddresses, aCount) { + this.setHeader(aHeaderName, fixXpconnectAddresses(aAddresses)); + }, + + setRawHeader: function (aHeaderName, aValue, aCharset) { + aValue = jsmime.headerparser.convert8BitHeader(aValue, aCharset); + try { + this.setHeader(aHeaderName, + jsmime.headerparser.parseStructuredHeader(aHeaderName, aValue)); + } catch (e) { + // This means we don't have a structured encoder. Just assume it's a raw + // string value then. + this.setHeader(aHeaderName, aValue); + } + } +}; + +// These are prototypes for nsIMsgHeaderParser implementation +var Mailbox = { + toString: function () { + return this.name ? this.name + " <" + this.email + ">" : this.email; + } +}; + +var EmailGroup = { + toString: function () { + return this.name + ": " + this.group.map(x => x.toString()).join(", "); + } +}; + +// A helper method for parse*Header that takes into account the desire to +// preserve group and also tweaks the output to support the prototypes for the +// XPIDL output. +function fixArray(addresses, preserveGroups, count) { + function resetPrototype(obj, prototype) { + let prototyped = Object.create(prototype); + for (let key of Object.getOwnPropertyNames(obj)) { + if (typeof obj[key] == "string") { + prototyped[key] = obj[key].replace(/\x00/g, ''); + } else { + prototyped[key] = obj[key]; + } + } + return prototyped; + } + let outputArray = []; + for (let element of addresses) { + if ('group' in element) { + // Fix up the prototypes of the group and the list members + element = resetPrototype(element, EmailGroup); + element.group = element.group.map(e => resetPrototype(e, Mailbox)); + + // Add to the output array + if (preserveGroups) + outputArray.push(element); + else + outputArray = outputArray.concat(element.group); + } else { + element = resetPrototype(element, Mailbox); + outputArray.push(element); + } + } + + if (count) + count.value = outputArray.length; + return outputArray; +} + +function MimeAddressParser() { +} +MimeAddressParser.prototype = { + classID: Components.ID("96bd8769-2d0e-4440-963d-22b97fb3ba77"), + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMsgHeaderParser]), + + parseEncodedHeader: function (aHeader, aCharset, aPreserveGroups, count) { + aHeader = aHeader || ""; + let value = MimeParser.parseHeaderField(aHeader, + MimeParser.HEADER_ADDRESS | MimeParser.HEADER_OPTION_ALL_I18N, aCharset); + return fixArray(value, aPreserveGroups, count); + }, + parseDecodedHeader: function (aHeader, aPreserveGroups, count) { + aHeader = aHeader || ""; + let value = MimeParser.parseHeaderField(aHeader, MimeParser.HEADER_ADDRESS); + return fixArray(value, aPreserveGroups, count); + }, + + makeMimeHeader: function (addresses, length) { + addresses = fixXpconnectAddresses(addresses); + // Don't output any necessary continuations, so make line length as large as + // possible first. + let options = { + softMargin: 900, + hardMargin: 900, + useASCII: false // We don't want RFC 2047 encoding here. + }; + let handler = new HeaderHandler(); + let emitter = new jsmime.headeremitter.makeStreamingEmitter(handler, + options); + emitter.addAddresses(addresses); + emitter.finish(true); + return handler.value.replace(/\r\n( |$)/g, ''); + }, + + extractFirstName: function (aHeader) { + let address = this.parseDecodedHeader(aHeader, false)[0]; + return address.name || address.email; + }, + + removeDuplicateAddresses: function (aAddrs, aOtherAddrs) { + // This is actually a rather complicated algorithm, especially if we want to + // preserve group structure. Basically, we use a set to identify which + // headers we have seen and therefore want to remove. To work in several + // various forms of edge cases, we need to normalize the entries in that + // structure. + function normalize(email) { + // XXX: This algorithm doesn't work with IDN yet. It looks like we have to + // convert from IDN then do lower case, but I haven't confirmed yet. + return email.toLowerCase(); + } + + // The filtration function, which removes email addresses that are + // duplicates of those we have already seen. + function filterAccept(e) { + if ('email' in e) { + // If we've seen the address, don't keep this one; otherwise, add it to + // the list. + let key = normalize(e.email); + if (allAddresses.has(key)) + return false; + allAddresses.add(key); + } else { + // Groups -> filter out all the member addresses. + e.group = e.group.filter(filterAccept); + } + return true; + } + + // First, collect all of the emails to forcibly delete. + let allAddresses = new Set(); + for (let element of this.parseDecodedHeader(aOtherAddrs, false)) { + allAddresses.add(normalize(element.email)); + } + + // The actual data to filter + let filtered = this.parseDecodedHeader(aAddrs, true).filter(filterAccept); + return this.makeMimeHeader(filtered); + }, + + makeMailboxObject: function (aName, aEmail) { + let object = Object.create(Mailbox); + object.name = aName; + object.email = aEmail ? aEmail.trim() : aEmail; + return object; + }, + + makeGroupObject: function (aName, aMembers) { + let object = Object.create(EmailGroup); + object.name = aName; + object.group = aMembers; + return object; + }, + + makeFromDisplayAddress: function (aDisplay, count) { + // The basic idea is to split on every comma, so long as there is a + // preceding @. + let output = []; + while (aDisplay.length) { + let at = aDisplay.indexOf('@'); + let comma = aDisplay.indexOf(',', at + 1); + let addr; + if (comma > 0) { + addr = aDisplay.substr(0, comma); + aDisplay = aDisplay.substr(comma + 1); + } else { + addr = aDisplay; + aDisplay = ""; + } + output.push(this._makeSingleAddress(addr.trimLeft())); + } + if (count) + count.value = output.length; + return output; + }, + + /// Construct a single email address from a name <local@domain> token. + _makeSingleAddress: function (aDisplayName) { + if (aDisplayName.includes('<')) { + let lbracket = aDisplayName.lastIndexOf('<'); + let rbracket = aDisplayName.lastIndexOf('>'); + return this.makeMailboxObject( + lbracket == 0 ? '' : aDisplayName.slice(0, lbracket).trim(), + aDisplayName.slice(lbracket + 1, rbracket)); + } else { + return this.makeMailboxObject('', aDisplayName); + } + }, + + // What follows is the deprecated API that will be removed shortly. + + parseHeadersWithArray: function (aHeader, aAddrs, aNames, aFullNames) { + let addrs = [], names = [], fullNames = []; + // Parse header, but without HEADER_OPTION_ALLOW_RAW. + let value = MimeParser.parseHeaderField(aHeader || "", + MimeParser.HEADER_ADDRESS | + MimeParser.HEADER_OPTION_DECODE_2231 | + MimeParser.HEADER_OPTION_DECODE_2047, + undefined); + let allAddresses = fixArray(value, false); + + // Don't index the dummy empty address. + if (aHeader.trim() == "") + allAddresses = []; + for (let address of allAddresses) { + addrs.push(address.email); + names.push(address.name || null); + fullNames.push(address.toString()); + } + + aAddrs.value = addrs; + aNames.value = names; + aFullNames.value = fullNames; + return allAddresses.length; + }, + + extractHeaderAddressMailboxes: function (aLine) { + return this.parseDecodedHeader(aLine).map(addr => addr.email).join(", "); + }, + + extractHeaderAddressNames: function (aLine) { + return this.parseDecodedHeader(aLine).map(addr => addr.name || addr.email) + .join(", "); + }, + + extractHeaderAddressName: function (aLine) { + let addrs = this.parseDecodedHeader(aLine).map(addr => + addr.name || addr.email); + return addrs.length == 0 ? "" : addrs[0]; + }, + + makeMimeAddress: function (aName, aEmail) { + let object = this.makeMailboxObject(aName, aEmail); + return this.makeMimeHeader([object]); + }, +}; + +function MimeConverter() { +} +MimeConverter.prototype = { + classID: Components.ID("93f8c049-80ed-4dda-9000-94ad8daba44c"), + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMimeConverter]), + + encodeMimePartIIStr_UTF8: function (aHeader, aStructured, aCharset, + aFieldNameLen, aLineLength) { + // The JSMime encoder only works in UTF-8, so if someone requests to not do + // it, they need to change their code. + if (aCharset.toLowerCase() != "utf-8") { + Deprecated.warning("Encoding to non-UTF-8 values is obsolete", + "http://bugzilla.mozilla.org/show_bug.cgi?id=790855"); + } + + // Compute the encoding options. The way our API is structured in this + // method is really horrendous and does not align with the way that JSMime + // handles it. Instead, we'll need to create a fake header to take into + // account the aFieldNameLen parameter. + let fakeHeader = '-'.repeat(aFieldNameLen); + let options = { + softMargin: aLineLength, + useASCII: true, + }; + let handler = new HeaderHandler(); + let emitter = new jsmime.headeremitter.makeStreamingEmitter(handler, + options); + + // Add the text to the be encoded. + emitter.addHeaderName(fakeHeader); + if (aStructured) { + // Structured really means "this is an addressing header" + let addresses = MimeParser.parseHeaderField(aHeader, + MimeParser.HEADER_ADDRESS | MimeParser.HEADER_OPTION_DECODE_2047); + // This happens in one of our tests if there is a "bare" email but no + // @ sign. Without it, the result disappears since our emission code + // assumes that an empty email is not worth emitting. + if (addresses.length === 1 && addresses[0].email === "" && + addresses[0].name !== "") { + addresses[0].email = addresses[0].name; + addresses[0].name = ""; + } + emitter.addAddresses(addresses); + } else { + emitter.addUnstructured(aHeader); + } + + // Compute the output. We need to strip off the fake prefix added earlier + // and the extra CRLF at the end. + emitter.finish(true); + let value = handler.value; + value = value.replace(new RegExp(fakeHeader + ":\\s*"), ""); + return value.substring(0, value.length - 2); + }, + + decodeMimeHeader: function (aHeader, aDefaultCharset, aOverride, aUnfold) { + let value = MimeParser.parseHeaderField(aHeader, + MimeParser.HEADER_UNSTRUCTURED | MimeParser.HEADER_OPTION_ALL_I18N, + aDefaultCharset); + if (aUnfold) { + value = value.replace(/[\r\n]\t/g, ' ') + .replace(/[\r\n]/g, ''); + } + return value; + }, + + // This is identical to the above, except for factors that are handled by the + // xpconnect conversion process + decodeMimeHeaderToUTF8: function () { + return this.decodeMimeHeader.apply(this, arguments); + }, +}; + +var components = [MimeHeaders, MimeWritableStructuredHeaders, MimeAddressParser, + MimeConverter]; +var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/mailnews/mime/src/mimeParser.jsm b/mailnews/mime/src/mimeParser.jsm new file mode 100644 index 000000000..904675f10 --- /dev/null +++ b/mailnews/mime/src/mimeParser.jsm @@ -0,0 +1,258 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +// vim:set ts=2 sw=2 sts=2 et ft=javascript: + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource:///modules/jsmime.jsm"); + +var EXPORTED_SYMBOLS = ["MimeParser"]; + +// Emitter helpers, for internal functions later on. +var ExtractHeadersEmitter = { + startPart: function (partNum, headers) { + if (partNum == '') { + this.headers = headers; + } + } +}; + +var ExtractHeadersAndBodyEmitter = { + body: '', + startPart: ExtractHeadersEmitter.startPart, + deliverPartData: function (partNum, data) { + if (partNum == '') + this.body += data; + } +}; + +var Ci = Components.interfaces; +var Cc = Components.classes; + +/// Sets appropriate default options for chrome-privileged environments +function setDefaultParserOptions(opts) { + if (!("onerror" in opts)) { + opts.onerror = Components.utils.reportError; + } +} + +var MimeParser = { + /** + * Triggers an asynchronous parse of the given input. + * + * The input is an input stream; the stream will be read until EOF and then + * closed upon completion. Both blocking and nonblocking streams are + * supported by this implementation, but it is still guaranteed that the first + * callback will not happen before this method returns. + * + * @param input An input stream of text to parse. + * @param emitter The emitter to receive callbacks on. + * @param opts A set of options for the parser. + */ + parseAsync: function MimeParser_parseAsync(input, emitter, opts) { + // Normalize the input into an input stream. + if (!(input instanceof Ci.nsIInputStream)) { + throw new Error("input is not a recognizable type!"); + } + + // We need a pump for the listener + var pump = Cc["@mozilla.org/network/input-stream-pump;1"] + .createInstance(Ci.nsIInputStreamPump); + pump.init(input, -1, -1, 0, 0, true); + + // Make a stream listener with the given emitter and use it to read from + // the pump. + var parserListener = MimeParser.makeStreamListenerParser(emitter, opts); + pump.asyncRead(parserListener, null); + }, + + /** + * Triggers an synchronous parse of the given input. + * + * The input is a string that is immediately parsed, calling all functions on + * the emitter before this function returns. + * + * @param input A string or input stream of text to parse. + * @param emitter The emitter to receive callbacks on. + * @param opts A set of options for the parser. + */ + parseSync: function MimeParser_parseSync(input, emitter, opts) { + // We only support string parsing if we are trying to do this parse + // synchronously. + if (typeof input != "string") { + throw new Error("input is not a recognizable type!"); + } + setDefaultParserOptions(opts); + var parser = new jsmime.MimeParser(emitter, opts); + parser.deliverData(input); + parser.deliverEOF(); + return; + }, + + /** + * Returns a stream listener that feeds data into a parser. + * + * In addition to the functions on the emitter that the parser may use, the + * generated stream listener will also make calls to onStartRequest and + * onStopRequest on the emitter (if they exist). + * + * @param emitter The emitter to receive callbacks on. + * @param opts A set of options for the parser. + */ + makeStreamListenerParser: function MimeParser_makeSLParser(emitter, opts) { + var StreamListener = { + onStartRequest: function SLP_onStartRequest(aRequest, aContext) { + try { + if ("onStartRequest" in emitter) + emitter.onStartRequest(aRequest, aContext); + } finally { + this._parser.resetParser(); + } + }, + onStopRequest: function SLP_onStopRequest(aRequest, aContext, aStatus) { + this._parser.deliverEOF(); + if ("onStopRequest" in emitter) + emitter.onStopRequest(aRequest, aContext, aStatus); + }, + onDataAvailable: function SLP_onData(aRequest, aContext, aStream, + aOffset, aCount) { + var scriptIn = Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + scriptIn.init(aStream); + // Use readBytes instead of read to handle embedded NULs properly. + this._parser.deliverData(scriptIn.readBytes(aCount)); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener, + Ci.nsIRequestObserver]) + }; + setDefaultParserOptions(opts); + StreamListener._parser = new jsmime.MimeParser(emitter, opts); + return StreamListener; + }, + + /** + * Returns a new raw MIME parser. + * + * Prefer one of the other methods where possible, since the input here must + * be driven manually. + * + * @param emitter The emitter to receive callbacks on. + * @param opts A set of options for the parser. + */ + makeParser: function MimeParser_makeParser(emitter, opts) { + setDefaultParserOptions(opts); + return new jsmime.MimeParser(emitter, opts); + }, + + /** + * Returns a dictionary of headers for the given input. + * + * The input is any type of input that would be accepted by parseSync. What + * is returned is a JS object that represents the headers of the entire + * envelope as would be received by startPart when partNum is the empty + * string. + * + * @param input A string of text to parse. + */ + extractHeaders: function MimeParser_extractHeaders(input) { + var emitter = Object.create(ExtractHeadersEmitter); + MimeParser.parseSync(input, emitter, {pruneat: '', bodyformat: 'none'}); + return emitter.headers; + }, + + /** + * Returns the headers and body for the given input message. + * + * The return value is an array whose first element is the dictionary of + * headers (as would be returned by extractHeaders) and whose second element + * is a binary string of the entire body of the message. + * + * @param input A string of text to parse. + */ + extractHeadersAndBody: function MimeParser_extractHeaders(input) { + var emitter = Object.create(ExtractHeadersAndBodyEmitter); + MimeParser.parseSync(input, emitter, {pruneat: '', bodyformat: 'raw'}); + return [emitter.headers, emitter.body]; + }, + + // Parameters for parseHeaderField + + /** + * Parse the header as if it were unstructured. + * + * This results in the same string if no other options are specified. If other + * options are specified, this causes the string to be modified appropriately. + */ + HEADER_UNSTRUCTURED: 0x00, + /** + * Parse the header as if it were in the form text; attr=val; attr=val. + * + * Such headers include Content-Type, Content-Disposition, and most other + * headers used by MIME as opposed to messages. + */ + HEADER_PARAMETER: 0x02, + /** + * Parse the header as if it were a sequence of mailboxes. + */ + HEADER_ADDRESS: 0x03, + + /** + * This decodes parameter values according to RFC 2231. + * + * This flag means nothing if HEADER_PARAMETER is not specified. + */ + HEADER_OPTION_DECODE_2231: 0x10, + /** + * This decodes the inline encoded-words that are in RFC 2047. + */ + HEADER_OPTION_DECODE_2047: 0x20, + /** + * This converts the header from a raw string to proper Unicode. + */ + HEADER_OPTION_ALLOW_RAW: 0x40, + + /// Convenience for all three of the above. + HEADER_OPTION_ALL_I18N: 0x70, + + /** + * Parse a header field according to the specification given by flags. + * + * Permissible flags begin with one of the HEADER_* flags, which may be or'd + * with any of the HEADER_OPTION_* flags to modify the result appropriately. + * + * If the option HEADER_OPTION_ALLOW_RAW is passed, the charset parameter, if + * present, is the charset to fallback to if the header is not decodable as + * UTF-8 text. If HEADER_OPTION_ALLOW_RAW is passed but the charset parameter + * is not provided, then no fallback decoding will be done. If + * HEADER_OPTION_ALLOW_RAW is not passed, then no attempt will be made to + * convert charsets. + * + * @param text The value of a MIME or message header to parse. + * @param flags A set of flags that controls interpretation of the header. + * @param charset A default charset to assume if no information may be found. + */ + parseHeaderField: function MimeParser_parseHeaderField(text, flags, charset) { + // If we have a raw string, convert it to Unicode first + if (flags & MimeParser.HEADER_OPTION_ALLOW_RAW) + text = jsmime.headerparser.convert8BitHeader(text, charset); + + // The low 4 bits indicate the type of the header we are parsing. All of the + // higher-order bits are flags. + switch (flags & 0x0f) { + case MimeParser.HEADER_UNSTRUCTURED: + if (flags & MimeParser.HEADER_OPTION_DECODE_2047) + text = jsmime.headerparser.decodeRFC2047Words(text); + return text; + case MimeParser.HEADER_PARAMETER: + return jsmime.headerparser.parseParameterHeader(text, + (flags & MimeParser.HEADER_OPTION_DECODE_2047) != 0, + (flags & MimeParser.HEADER_OPTION_DECODE_2231) != 0); + case MimeParser.HEADER_ADDRESS: + return jsmime.headerparser.parseAddressingHeader(text, + (flags & MimeParser.HEADER_OPTION_DECODE_2047) != 0); + default: + throw "Illegal type of header field"; + } + }, +}; diff --git a/mailnews/mime/src/mimeTextHTMLParsed.cpp b/mailnews/mime/src/mimeTextHTMLParsed.cpp new file mode 100644 index 000000000..2d45bd2c1 --- /dev/null +++ b/mailnews/mime/src/mimeTextHTMLParsed.cpp @@ -0,0 +1,150 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Most of this code is copied from mimethsa. If you find a bug here, check that class, too. */ + +/* This runs the entire HTML document through the Mozilla HTML parser, and + then outputs it as string again. This ensures that the HTML document is + syntactically correct and complete and all tags and attributes are closed. + + That prevents "MIME in the middle" attacks like efail.de. + The base problem is that we concatenate different MIME parts in the output + and render them all together as a single HTML document in the display. + + The better solution would be to put each MIME part into its own <iframe type="content">. + during rendering. Unfortunately, we'd need <iframe seamless> for that. + That would remove the need for this workaround, and stop even more attack classes. +*/ + +#include "mimeTextHTMLParsed.h" +#include "prmem.h" +#include "prlog.h" +#include "msgCore.h" +#include "nsIDOMParser.h" +#include "nsIDOMDocument.h" +#include "nsIDocument.h" +#include "nsIDocumentEncoder.h" +#include "mozilla/ErrorResult.h" +#include "nsIPrefBranch.h" +#include "mimethtm.h" + +#define MIME_SUPERCLASS mimeInlineTextHTMLClass +MimeDefClass(MimeInlineTextHTMLParsed, MimeInlineTextHTMLParsedClass, + mimeInlineTextHTMLParsedClass, &MIME_SUPERCLASS); + +static int MimeInlineTextHTMLParsed_parse_line(const char *, int32_t, + MimeObject *); +static int MimeInlineTextHTMLParsed_parse_begin(MimeObject *obj); +static int MimeInlineTextHTMLParsed_parse_eof(MimeObject *, bool); +static void MimeInlineTextHTMLParsed_finalize(MimeObject *obj); + +static int +MimeInlineTextHTMLParsedClassInitialize(MimeInlineTextHTMLParsedClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *)clazz; + NS_ASSERTION(!oclass->class_initialized, "problem with superclass"); + oclass->parse_line = MimeInlineTextHTMLParsed_parse_line; + oclass->parse_begin = MimeInlineTextHTMLParsed_parse_begin; + oclass->parse_eof = MimeInlineTextHTMLParsed_parse_eof; + oclass->finalize = MimeInlineTextHTMLParsed_finalize; + + return 0; +} + +static int +MimeInlineTextHTMLParsed_parse_begin(MimeObject *obj) +{ + MimeInlineTextHTMLParsed *me = (MimeInlineTextHTMLParsed *)obj; + me->complete_buffer = new nsString(); + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) + return status; + + return 0; +} + +static int +MimeInlineTextHTMLParsed_parse_eof(MimeObject *obj, bool abort_p) +{ + + if (obj->closed_p) + return 0; + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) + return status; + MimeInlineTextHTMLParsed *me = (MimeInlineTextHTMLParsed *)obj; + + // We have to cache all lines and parse the whole document at once. + // There's a useful sounding function parseFromStream(), but it only allows XML + // mimetypes, not HTML. Methinks that's because the HTML soup parser + // needs the entire doc to make sense of the gibberish that people write. + if (!me || !me->complete_buffer) + return 0; + + nsString& rawHTML = *(me->complete_buffer); + if (rawHTML.IsEmpty()) + return 0; + nsString parsed; + nsresult rv; + + // Parse the HTML source. + nsCOMPtr<nsIDOMDocument> document; + nsCOMPtr<nsIDOMParser> parser = do_GetService(NS_DOMPARSER_CONTRACTID); + rv = parser->ParseFromString(rawHTML.get(), "text/html", + getter_AddRefs(document)); + NS_ENSURE_SUCCESS(rv, -1); + + // Serialize it back to HTML source again. + nsCOMPtr<nsIDocumentEncoder> encoder = do_CreateInstance( + "@mozilla.org/layout/documentEncoder;1?type=text/html"); + uint32_t aFlags = nsIDocumentEncoder::OutputRaw | + nsIDocumentEncoder::OutputDisallowLineBreaking; + rv = encoder->Init(document, NS_LITERAL_STRING("text/html"), aFlags); + NS_ENSURE_SUCCESS(rv, -1); + rv = encoder->EncodeToString(parsed); + NS_ENSURE_SUCCESS(rv, -1); + + // Write it out. + NS_ConvertUTF16toUTF8 resultCStr(parsed); + MimeInlineTextHTML_insert_lang_div(obj, resultCStr); + MimeInlineTextHTML_remove_plaintext_tag(obj, resultCStr); + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_line( + resultCStr.BeginWriting(), resultCStr.Length(), obj); + rawHTML.Truncate(); + return status; +} + +void +MimeInlineTextHTMLParsed_finalize(MimeObject *obj) +{ + MimeInlineTextHTMLParsed *me = (MimeInlineTextHTMLParsed *)obj; + + if (me && me->complete_buffer) + { + obj->clazz->parse_eof(obj, false); + delete me->complete_buffer; + me->complete_buffer = NULL; + } + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj); +} + +static int +MimeInlineTextHTMLParsed_parse_line(const char *line, int32_t length, + MimeObject *obj) +{ + MimeInlineTextHTMLParsed *me = (MimeInlineTextHTMLParsed *)obj; + + if (!me || !(me->complete_buffer)) + return -1; + + nsCString linestr(line, length); + NS_ConvertUTF8toUTF16 line_ucs2(linestr.get()); + if (length && line_ucs2.IsEmpty()) + CopyASCIItoUTF16(linestr, line_ucs2); + (me->complete_buffer)->Append(line_ucs2); + + return 0; +} diff --git a/mailnews/mime/src/mimeTextHTMLParsed.h b/mailnews/mime/src/mimeTextHTMLParsed.h new file mode 100644 index 000000000..753a5153b --- /dev/null +++ b/mailnews/mime/src/mimeTextHTMLParsed.h @@ -0,0 +1,28 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETEXTHTMLPARSED_H_ +#define _MIMETEXTHTMLPARSED_H_ + +#include "mimethtm.h" + +typedef struct MimeInlineTextHTMLParsedClass MimeInlineTextHTMLParsedClass; +typedef struct MimeInlineTextHTMLParsed MimeInlineTextHTMLParsed; + +struct MimeInlineTextHTMLParsedClass { + MimeInlineTextHTMLClass html; +}; + +extern MimeInlineTextHTMLParsedClass mimeInlineTextHTMLParsedClass; + +struct MimeInlineTextHTMLParsed { + MimeInlineTextHTML html; + nsString *complete_buffer; // Gecko parser expects wide strings +}; + +#define MimeInlineTextHTMLParsedClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextHTMLClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETEXTHTMLPARSED_H_ */ diff --git a/mailnews/mime/src/mimebuf.cpp b/mailnews/mime/src/mimebuf.cpp new file mode 100644 index 000000000..f81047205 --- /dev/null +++ b/mailnews/mime/src/mimebuf.cpp @@ -0,0 +1,249 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ +/* + * mimebuf.c - libmsg like buffer handling routines for libmime + */ +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" + +extern "C" int +mime_GrowBuffer (uint32_t desired_size, uint32_t element_size, uint32_t quantum, + char **buffer, int32_t *size) +{ + if ((uint32_t) *size <= desired_size) + { + char *new_buf; + uint32_t increment = desired_size - *size; + if (increment < quantum) /* always grow by a minimum of N bytes */ + increment = quantum; + + new_buf = (*buffer + ? (char *) PR_Realloc (*buffer, (*size + increment) + * (element_size / sizeof(char))) + : (char *) PR_MALLOC ((*size + increment) + * (element_size / sizeof(char)))); + if (! new_buf) + return MIME_OUT_OF_MEMORY; + *buffer = new_buf; + *size += increment; + } + return 0; +} + +/* The opposite of mime_LineBuffer(): takes small buffers and packs them + up into bigger buffers before passing them along. + + Pass in a desired_buffer_size 0 to tell it to flush (for example, in + in the very last call to this function.) + */ +extern "C" int +mime_ReBuffer (const char *net_buffer, int32_t net_buffer_size, + uint32_t desired_buffer_size, + char **bufferP, int32_t *buffer_sizeP, uint32_t *buffer_fpP, + int32_t (*per_buffer_fn) (char *buffer, uint32_t buffer_size, + void *closure), + void *closure) +{ + int status = 0; + + if (desired_buffer_size >= (uint32_t) (*buffer_sizeP)) + { + status = mime_GrowBuffer (desired_buffer_size, sizeof(char), 1024, + bufferP, buffer_sizeP); + if (status < 0) return status; + } + + do + { + int32_t size = *buffer_sizeP - *buffer_fpP; + if (size > net_buffer_size) + size = net_buffer_size; + if (size > 0) + { + memcpy ((*bufferP) + (*buffer_fpP), net_buffer, size); + (*buffer_fpP) += size; + net_buffer += size; + net_buffer_size -= size; + } + + if (*buffer_fpP > 0 && + *buffer_fpP >= desired_buffer_size) + { + status = (*per_buffer_fn) ((*bufferP), (*buffer_fpP), closure); + *buffer_fpP = 0; + if (status < 0) return status; + } + } + while (net_buffer_size > 0); + + return 0; +} + +static int +convert_and_send_buffer(char* buf, int length, bool convert_newlines_p, + int32_t (* per_line_fn) (char *line, + uint32_t line_length, + void *closure), + void *closure) +{ + /* Convert the line terminator to the native form. + */ + char* newline; + +#if (MSG_LINEBREAK_LEN == 2) + /*** + * This is a patch to support a mail DB corruption cause by earlier version that lead to a crash. + * What happened is that the line terminator is CR+NULL+LF. Therefore, we first process a line + * terminated by CR then a second line that contains only NULL+LF. We need to ignore this second + * line. See bug http://bugzilla.mozilla.org/show_bug.cgi?id=61412 for more information. + ***/ + if (length == 2 && buf[0] == 0x00 && buf[1] == '\n') + return 0; +#endif + + NS_ASSERTION(buf && length > 0, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!buf || length <= 0) return -1; + newline = buf + length; + NS_ASSERTION(newline[-1] == '\r' || newline[-1] == '\n', "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (newline[-1] != '\r' && newline[-1] != '\n') return -1; + + if (!convert_newlines_p) + { + } +#if (MSG_LINEBREAK_LEN == 1) + else if ((newline - buf) >= 2 && + newline[-2] == '\r' && + newline[-1] == '\n') + { + /* CRLF -> CR or LF */ + buf [length - 2] = MSG_LINEBREAK[0]; + length--; + } + else if (newline > buf + 1 && + newline[-1] != MSG_LINEBREAK[0]) + { + /* CR -> LF or LF -> CR */ + buf [length - 1] = MSG_LINEBREAK[0]; + } +#else + else if (((newline - buf) >= 2 && newline[-2] != '\r') || + ((newline - buf) >= 1 && newline[-1] != '\n')) + { + /* LF -> CRLF or CR -> CRLF */ + length++; + buf[length - 2] = MSG_LINEBREAK[0]; + buf[length - 1] = MSG_LINEBREAK[1]; + } +#endif + + return (*per_line_fn)(buf, length, closure); +} + +extern "C" int +mime_LineBuffer (const char *net_buffer, int32_t net_buffer_size, + char **bufferP, int32_t *buffer_sizeP, uint32_t *buffer_fpP, + bool convert_newlines_p, + int32_t (* per_line_fn) (char *line, uint32_t line_length, + void *closure), + void *closure) +{ + int status = 0; + if (*buffer_fpP > 0 && *bufferP && (*bufferP)[*buffer_fpP - 1] == '\r' && + net_buffer_size > 0 && net_buffer[0] != '\n') { + /* The last buffer ended with a CR. The new buffer does not start + with a LF. This old buffer should be shipped out and discarded. */ + NS_ASSERTION((uint32_t) *buffer_sizeP > *buffer_fpP, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if ((uint32_t) *buffer_sizeP <= *buffer_fpP) return -1; + status = convert_and_send_buffer(*bufferP, *buffer_fpP, + convert_newlines_p, + per_line_fn, closure); + if (status < 0) return status; + *buffer_fpP = 0; + } + while (net_buffer_size > 0) + { + const char *net_buffer_end = net_buffer + net_buffer_size; + const char *newline = 0; + const char *s; + + + for (s = net_buffer; s < net_buffer_end; s++) + { + /* Move forward in the buffer until the first newline. + Stop when we see CRLF, CR, or LF, or the end of the buffer. + *But*, if we see a lone CR at the *very end* of the buffer, + treat this as if we had reached the end of the buffer without + seeing a line terminator. This is to catch the case of the + buffers splitting a CRLF pair, as in "FOO\r\nBAR\r" "\nBAZ\r\n". + */ + if (*s == '\r' || *s == '\n') + { + newline = s; + if (newline[0] == '\r') + { + if (s == net_buffer_end - 1) + { + /* CR at end - wait for the next character. */ + newline = 0; + break; + } + else if (newline[1] == '\n') + /* CRLF seen; swallow both. */ + newline++; + } + newline++; + break; + } + } + + /* Ensure room in the net_buffer and append some or all of the current + chunk of data to it. */ + { + const char *end = (newline ? newline : net_buffer_end); + uint32_t desired_size = (end - net_buffer) + (*buffer_fpP) + 1; + + if (desired_size >= (uint32_t) (*buffer_sizeP)) + { + status = mime_GrowBuffer (desired_size, sizeof(char), 1024, + bufferP, buffer_sizeP); + if (status < 0) return status; + } + memcpy ((*bufferP) + (*buffer_fpP), net_buffer, (end - net_buffer)); + (*buffer_fpP) += (end - net_buffer); + (*bufferP)[*buffer_fpP] = '\0'; + } + + /* Now *bufferP contains either a complete line, or as complete + a line as we have read so far. + + If we have a line, process it, and then remove it from `*bufferP'. + Then go around the loop again, until we drain the incoming data. + */ + if (!newline) + return 0; + + status = convert_and_send_buffer(*bufferP, *buffer_fpP, + convert_newlines_p, + per_line_fn, closure); + if (status < 0) + return status; + + net_buffer_size -= (newline - net_buffer); + net_buffer = newline; + (*buffer_fpP) = 0; + } + return 0; +} diff --git a/mailnews/mime/src/mimebuf.h b/mailnews/mime/src/mimebuf.h new file mode 100644 index 000000000..38b9a68c7 --- /dev/null +++ b/mailnews/mime/src/mimebuf.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ + +#ifndef _MIMEBUF_H_ +#define _MIMEBUF_H_ + +extern "C" int mime_GrowBuffer (uint32_t desired_size, + uint32_t element_size, uint32_t quantum, + char **buffer, int32_t *size); + +extern "C" int mime_LineBuffer (const char *net_buffer, int32_t net_buffer_size, + char **bufferP, int32_t *buffer_sizeP, + int32_t *buffer_fpP, + bool convert_newlines_p, + int32_t (* per_line_fn) (char *line, int32_t + line_length, void *closure), + void *closure); + +extern "C" int mime_ReBuffer (const char *net_buffer, int32_t net_buffer_size, + uint32_t desired_buffer_size, + char **bufferP, uint32_t *buffer_sizeP, + uint32_t *buffer_fpP, + int32_t (*per_buffer_fn) (char *buffer, + uint32_t buffer_size, + void *closure), + void *closure); + + +#endif /* _MIMEBUF_H_ */ diff --git a/mailnews/mime/src/mimecms.cpp b/mailnews/mime/src/mimecms.cpp new file mode 100644 index 000000000..18d88acaa --- /dev/null +++ b/mailnews/mime/src/mimecms.cpp @@ -0,0 +1,716 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsICMSMessage.h" +#include "nsICMSMessage2.h" +#include "nsICMSMessageErrors.h" +#include "nsICMSDecoder.h" +#include "mimecms.h" +#include "mimemsig.h" +#include "nspr.h" +#include "mimemsg.h" +#include "mimemoz2.h" +#include "nsIURI.h" +#include "nsIMsgWindow.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMimeMiscStatus.h" +#include "nsIMsgSMIMEHeaderSink.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsIX509Cert.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsProxyRelease.h" +#include "mozilla/mailnews/MimeHeaderParser.h" + +using namespace mozilla::mailnews; + + +#define MIME_SUPERCLASS mimeEncryptedClass +MimeDefClass(MimeEncryptedCMS, MimeEncryptedCMSClass, + mimeEncryptedCMSClass, &MIME_SUPERCLASS); + +static void *MimeCMS_init(MimeObject *, int (*output_fn) (const char *, int32_t, void *), void *); +static int MimeCMS_write (const char *, int32_t, void *); +static int MimeCMS_eof (void *, bool); +static char * MimeCMS_generate (void *); +static void MimeCMS_free (void *); + +extern int SEC_ERROR_CERT_ADDR_MISMATCH; + +static int MimeEncryptedCMSClassInitialize(MimeEncryptedCMSClass *clazz) +{ +#ifdef DEBUG + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59"); +#endif + + MimeEncryptedClass *eclass = (MimeEncryptedClass *) clazz; + eclass->crypto_init = MimeCMS_init; + eclass->crypto_write = MimeCMS_write; + eclass->crypto_eof = MimeCMS_eof; + eclass->crypto_generate_html = MimeCMS_generate; + eclass->crypto_free = MimeCMS_free; + + return 0; +} + + +typedef struct MimeCMSdata +{ + int (*output_fn) (const char *buf, int32_t buf_size, void *output_closure); + void *output_closure; + nsCOMPtr<nsICMSDecoder> decoder_context; + nsCOMPtr<nsICMSMessage> content_info; + bool ci_is_encrypted; + char *sender_addr; + bool decoding_failed; + uint32_t decoded_bytes; + MimeObject *self; + bool parent_is_encrypted_p; + bool parent_holds_stamp_p; + nsCOMPtr<nsIMsgSMIMEHeaderSink> smimeHeaderSink; + + MimeCMSdata() + :output_fn(nullptr), + output_closure(nullptr), + ci_is_encrypted(false), + sender_addr(nullptr), + decoding_failed(false), + decoded_bytes(0), + self(nullptr), + parent_is_encrypted_p(false), + parent_holds_stamp_p(false) + { + } + + ~MimeCMSdata() + { + if(sender_addr) + PR_Free(sender_addr); + + // Do an orderly release of nsICMSDecoder and nsICMSMessage // + if (decoder_context) + { + nsCOMPtr<nsICMSMessage> cinfo; + decoder_context->Finish(getter_AddRefs(cinfo)); + } + } +} MimeCMSdata; + +/* SEC_PKCS7DecoderContentCallback for SEC_PKCS7DecoderStart() */ +static void MimeCMS_content_callback (void *arg, const char *buf, unsigned long length) +{ + int status; + MimeCMSdata *data = (MimeCMSdata *) arg; + if (!data) return; + + if (!data->output_fn) + return; + + PR_SetError(0,0); + status = data->output_fn (buf, length, data->output_closure); + if (status < 0) + { + PR_SetError(status, 0); + data->output_fn = 0; + return; + } + + data->decoded_bytes += length; +} + +bool MimeEncryptedCMS_encrypted_p (MimeObject *obj) +{ + bool encrypted; + + if (!obj) return false; + if (mime_typep(obj, (MimeObjectClass *) &mimeEncryptedCMSClass)) + { + MimeEncrypted *enc = (MimeEncrypted *) obj; + MimeCMSdata *data = (MimeCMSdata *) enc->crypto_closure; + if (!data || !data->content_info) return false; + data->content_info->ContentIsEncrypted(&encrypted); + return encrypted; + } + return false; +} + + +bool MimeCMSHeadersAndCertsMatch(nsICMSMessage *content_info, + nsIX509Cert *signerCert, + const char *from_addr, + const char *from_name, + const char *sender_addr, + const char *sender_name, + bool *signing_cert_without_email_address) +{ + nsCString cert_addr; + bool match = true; + bool foundFrom = false; + bool foundSender = false; + + /* Find the name and address in the cert. + */ + if (content_info) + { + // Extract any address contained in the cert. + // This will be used for testing, whether the cert contains no addresses at all. + content_info->GetSignerEmailAddress (getter_Copies(cert_addr)); + } + + if (signing_cert_without_email_address) + *signing_cert_without_email_address = cert_addr.IsEmpty(); + + /* Now compare them -- + consider it a match if the address in the cert matches the + address in the From field (or as a fallback, the Sender field) + */ + + /* If there is no addr in the cert at all, it can not match and we fail. */ + if (cert_addr.IsEmpty()) + { + match = false; + } + else + { + if (signerCert) + { + if (from_addr && *from_addr) + { + NS_ConvertASCIItoUTF16 ucs2From(from_addr); + if (NS_FAILED(signerCert->ContainsEmailAddress(ucs2From, &foundFrom))) + { + foundFrom = false; + } + } + else if (sender_addr && *sender_addr) + { + NS_ConvertASCIItoUTF16 ucs2Sender(sender_addr); + if (NS_FAILED(signerCert->ContainsEmailAddress(ucs2Sender, &foundSender))) + { + foundSender = false; + } + } + } + + if (!foundSender && !foundFrom) + { + match = false; + } + } + + return match; +} + +class nsSMimeVerificationListener : public nsISMimeVerificationListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISMIMEVERIFICATIONLISTENER + + nsSMimeVerificationListener(const char *aFromAddr, const char *aFromName, + const char *aSenderAddr, const char *aSenderName, + nsIMsgSMIMEHeaderSink *aHeaderSink, int32_t aMimeNestingLevel); + +protected: + virtual ~nsSMimeVerificationListener() {} + + /** + * It is safe to declare this implementation as thread safe, + * despite not using a lock to protect the members. + * Because of the way the object will be used, we don't expect a race. + * After construction, the object is passed to another thread, + * but will no longer be accessed on the original thread. + * The other thread is unable to access/modify self's data members. + * When the other thread is finished, it will call into the "Notify" + * callback. Self's members will be accessed on the other thread, + * but this is fine, because there is no race with the original thread. + * Race-protection for XPCOM reference counting is sufficient. + */ + bool mSinkIsNull; + nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> mHeaderSink; + int32_t mMimeNestingLevel; + + nsCString mFromAddr; + nsCString mFromName; + nsCString mSenderAddr; + nsCString mSenderName; +}; + +class SignedStatusRunnable : public mozilla::Runnable +{ +public: + SignedStatusRunnable(const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> &aSink, int32_t aNestingLevel, + int32_t aSignatureStatus, nsIX509Cert *aSignerCert); + NS_DECL_NSIRUNNABLE +protected: + nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> m_sink; + int32_t m_nestingLevel; + int32_t m_signatureStatus; + nsCOMPtr<nsIX509Cert> m_signerCert; +}; + +SignedStatusRunnable::SignedStatusRunnable(const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> &aSink, + int32_t aNestingLevel, + int32_t aSignatureStatus, + nsIX509Cert *aSignerCert) : + m_sink(aSink), m_nestingLevel(aNestingLevel), + m_signatureStatus(aSignatureStatus), m_signerCert(aSignerCert) +{ +} + +NS_IMETHODIMP SignedStatusRunnable::Run() +{ + return m_sink->SignedStatus(m_nestingLevel, m_signatureStatus, m_signerCert); +} + + +nsresult ProxySignedStatus(const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> &aSink, + int32_t aNestingLevel, + int32_t aSignatureStatus, + nsIX509Cert *aSignerCert) +{ + RefPtr<SignedStatusRunnable> signedStatus = + new SignedStatusRunnable(aSink, aNestingLevel, aSignatureStatus, aSignerCert); + return NS_DispatchToMainThread(signedStatus, NS_DISPATCH_SYNC); +} + +NS_IMPL_ISUPPORTS(nsSMimeVerificationListener, nsISMimeVerificationListener) + +nsSMimeVerificationListener::nsSMimeVerificationListener(const char *aFromAddr, const char *aFromName, + const char *aSenderAddr, const char *aSenderName, + nsIMsgSMIMEHeaderSink *aHeaderSink, int32_t aMimeNestingLevel) +{ + mHeaderSink = new nsMainThreadPtrHolder<nsIMsgSMIMEHeaderSink>(aHeaderSink); + mSinkIsNull = !aHeaderSink; + mMimeNestingLevel = aMimeNestingLevel; + + mFromAddr = aFromAddr; + mFromName = aFromName; + mSenderAddr = aSenderAddr; + mSenderName = aSenderName; +} + +NS_IMETHODIMP nsSMimeVerificationListener::Notify(nsICMSMessage2 *aVerifiedMessage, + nsresult aVerificationResultCode) +{ + // Only continue if we have a valid pointer to the UI + NS_ENSURE_FALSE(mSinkIsNull, NS_OK); + + NS_ENSURE_TRUE(aVerifiedMessage, NS_ERROR_FAILURE); + + nsCOMPtr<nsICMSMessage> msg = do_QueryInterface(aVerifiedMessage); + NS_ENSURE_TRUE(msg, NS_ERROR_FAILURE); + + nsCOMPtr<nsIX509Cert> signerCert; + msg->GetSignerCert(getter_AddRefs(signerCert)); + + int32_t signature_status = nsICMSMessageErrors::GENERAL_ERROR; + + if (NS_FAILED(aVerificationResultCode)) + { + if (NS_ERROR_MODULE_SECURITY == NS_ERROR_GET_MODULE(aVerificationResultCode)) + signature_status = NS_ERROR_GET_CODE(aVerificationResultCode); + else if (NS_ERROR_NOT_IMPLEMENTED == aVerificationResultCode) + signature_status = nsICMSMessageErrors::VERIFY_ERROR_PROCESSING; + } + else + { + bool signing_cert_without_email_address; + + bool good_p = MimeCMSHeadersAndCertsMatch(msg, signerCert, + mFromAddr.get(), mFromName.get(), + mSenderAddr.get(), mSenderName.get(), + &signing_cert_without_email_address); + if (!good_p) + { + if (signing_cert_without_email_address) + signature_status = nsICMSMessageErrors::VERIFY_CERT_WITHOUT_ADDRESS; + else + signature_status = nsICMSMessageErrors::VERIFY_HEADER_MISMATCH; + } + else + signature_status = nsICMSMessageErrors::SUCCESS; + } + + ProxySignedStatus(mHeaderSink, mMimeNestingLevel, signature_status, signerCert); + + return NS_OK; +} + +int MIMEGetRelativeCryptoNestLevel(MimeObject *obj) +{ + /* + the part id of any mimeobj is mime_part_address(obj) + our currently displayed crypto part is obj + the part shown as the toplevel object in the current window is + obj->options->part_to_load + possibly stored in the toplevel object only ??? + but hopefully all nested mimeobject point to the same displayooptions + + we need to find out the nesting level of our currently displayed crypto object + wrt the shown part in the toplevel window + */ + + // if we are showing the toplevel message, aTopMessageNestLevel == 0 + int aTopMessageNestLevel = 0; + MimeObject *aTopShownObject = nullptr; + if (obj && obj->options->part_to_load) { + bool aAlreadyFoundTop = false; + for (MimeObject *walker = obj; walker; walker = walker->parent) { + if (aAlreadyFoundTop) { + if (!mime_typep(walker, (MimeObjectClass *) &mimeEncryptedClass) + && !mime_typep(walker, (MimeObjectClass *) &mimeMultipartSignedClass)) { + ++aTopMessageNestLevel; + } + } + if (!aAlreadyFoundTop && !strcmp(mime_part_address(walker), walker->options->part_to_load)) { + aAlreadyFoundTop = true; + aTopShownObject = walker; + } + if (!aAlreadyFoundTop && !walker->parent) { + // The mime part part_to_load is not a parent of the + // the crypto mime part passed in to this function as parameter obj. + // That means the crypto part belongs to another branch of the mime tree. + return -1; + } + } + } + + bool CryptoObjectIsChildOfTopShownObject = false; + if (!aTopShownObject) { + // no sub part specified, top message is displayed, and + // our crypto object is definitively a child of it + CryptoObjectIsChildOfTopShownObject = true; + } + + // if we are the child of the topmost message, aCryptoPartNestLevel == 1 + int aCryptoPartNestLevel = 0; + if (obj) { + for (MimeObject *walker = obj; walker; walker = walker->parent) { + // Crypto mime objects are transparent wrt nesting. + if (!mime_typep(walker, (MimeObjectClass *) &mimeEncryptedClass) + && !mime_typep(walker, (MimeObjectClass *) &mimeMultipartSignedClass)) { + ++aCryptoPartNestLevel; + } + if (aTopShownObject && walker->parent == aTopShownObject) { + CryptoObjectIsChildOfTopShownObject = true; + } + } + } + + if (!CryptoObjectIsChildOfTopShownObject) { + return -1; + } + + return aCryptoPartNestLevel - aTopMessageNestLevel; +} + +static void *MimeCMS_init(MimeObject *obj, + int (*output_fn) (const char *buf, int32_t buf_size, void *output_closure), + void *output_closure) +{ + MimeCMSdata *data; + nsresult rv; + + if (!(obj && obj->options && output_fn)) return 0; + + data = new MimeCMSdata; + if (!data) return 0; + + data->self = obj; + data->output_fn = output_fn; + data->output_closure = output_closure; + PR_SetError(0, 0); + data->decoder_context = do_CreateInstance(NS_CMSDECODER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + { + delete data; + return 0; + } + + rv = data->decoder_context->Start(MimeCMS_content_callback, data); + if (NS_FAILED(rv)) + { + delete data; + return 0; + } + + // XXX Fix later XXX // + data->parent_holds_stamp_p = + (obj->parent && + (mime_crypto_stamped_p(obj->parent) || + mime_typep(obj->parent, (MimeObjectClass *) &mimeEncryptedClass))); + + data->parent_is_encrypted_p = + (obj->parent && MimeEncryptedCMS_encrypted_p (obj->parent)); + + /* If the parent of this object is a crypto-blob, then it's the grandparent + who would have written out the headers and prepared for a stamp... + (This shit sucks.) + */ + if (data->parent_is_encrypted_p && + !data->parent_holds_stamp_p && + obj->parent && obj->parent->parent) + data->parent_holds_stamp_p = + mime_crypto_stamped_p (obj->parent->parent); + + mime_stream_data *msd = (mime_stream_data *) (data->self->options->stream_closure); + if (msd) + { + nsIChannel *channel = msd->channel; // note the lack of ref counting... + if (channel) + { + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIMsgWindow> msgWindow; + nsCOMPtr<nsIMsgHeaderSink> headerSink; + nsCOMPtr<nsIMsgMailNewsUrl> msgurl; + nsCOMPtr<nsISupports> securityInfo; + channel->GetURI(getter_AddRefs(uri)); + if (uri) + { + nsAutoCString urlSpec; + rv = uri->GetSpec(urlSpec); + + // We only want to update the UI if the current mime transaction + // is intended for display. + // If the current transaction is intended for background processing, + // we can learn that by looking at the additional header=filter + // string contained in the URI. + // + // If we find something, we do not set smimeHeaderSink, + // which will prevent us from giving UI feedback. + // + // If we do not find header=filter, we assume the result of the + // processing will be shown in the UI. + + if (!strstr(urlSpec.get(), "?header=filter") && + !strstr(urlSpec.get(), "&header=filter") && + !strstr(urlSpec.get(), "?header=attach") && + !strstr(urlSpec.get(), "&header=attach")) + { + msgurl = do_QueryInterface(uri); + if (msgurl) + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + msgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink)); + if (headerSink) + headerSink->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (securityInfo) + data->smimeHeaderSink = do_QueryInterface(securityInfo); + } + } + } // if channel + } // if msd + + return data; +} + +static int +MimeCMS_write (const char *buf, int32_t buf_size, void *closure) +{ + MimeCMSdata *data = (MimeCMSdata *) closure; + nsresult rv; + + if (!data || !data->output_fn || !data->decoder_context) return -1; + + PR_SetError(0, 0); + rv = data->decoder_context->Update(buf, buf_size); + data->decoding_failed = NS_FAILED(rv); + + return 0; +} + +void MimeCMSGetFromSender(MimeObject *obj, + nsCString &from_addr, + nsCString &from_name, + nsCString &sender_addr, + nsCString &sender_name) +{ + MimeHeaders *msg_headers = 0; + + /* Find the headers of the MimeMessage which is the parent (or grandparent) + of this object (remember, crypto objects nest.) */ + MimeObject *o2 = obj; + msg_headers = o2->headers; + while (o2 && + o2->parent && + !mime_typep(o2->parent, (MimeObjectClass *) &mimeMessageClass)) + { + o2 = o2->parent; + msg_headers = o2->headers; + } + + if (!msg_headers) + return; + + /* Find the names and addresses in the From and/or Sender fields. + */ + nsCString s; + + /* Extract the name and address of the "From:" field. */ + s.Adopt(MimeHeaders_get(msg_headers, HEADER_FROM, false, false)); + if (!s.IsEmpty()) + ExtractFirstAddress(EncodedHeader(s), from_name, from_addr); + + /* Extract the name and address of the "Sender:" field. */ + s.Adopt(MimeHeaders_get(msg_headers, HEADER_SENDER, false, false)); + if (!s.IsEmpty()) + ExtractFirstAddress(EncodedHeader(s), sender_name, sender_addr); +} + +void MimeCMSRequestAsyncSignatureVerification(nsICMSMessage *aCMSMsg, + const char *aFromAddr, const char *aFromName, + const char *aSenderAddr, const char *aSenderName, + nsIMsgSMIMEHeaderSink *aHeaderSink, int32_t aMimeNestingLevel, + unsigned char* item_data, uint32_t item_len) +{ + nsCOMPtr<nsICMSMessage2> msg2 = do_QueryInterface(aCMSMsg); + if (!msg2) + return; + + RefPtr<nsSMimeVerificationListener> listener = + new nsSMimeVerificationListener(aFromAddr, aFromName, aSenderAddr, aSenderName, + aHeaderSink, aMimeNestingLevel); + if (!listener) + return; + + if (item_data) + msg2->AsyncVerifyDetachedSignature(listener, item_data, item_len); + else + msg2->AsyncVerifySignature(listener); +} + +static int +MimeCMS_eof (void *crypto_closure, bool abort_p) +{ + MimeCMSdata *data = (MimeCMSdata *) crypto_closure; + nsresult rv; + int32_t status = nsICMSMessageErrors::SUCCESS; + + if (!data || !data->output_fn || !data->decoder_context) { + return -1; + } + + int aRelativeNestLevel = MIMEGetRelativeCryptoNestLevel(data->self); + + /* Hand an EOF to the crypto library. It may call data->output_fn. + (Today, the crypto library has no flushing to do, but maybe there + will be someday.) + + We save away the value returned and will use it later to emit a + blurb about whether the signature validation was cool. + */ + + PR_SetError(0, 0); + rv = data->decoder_context->Finish(getter_AddRefs(data->content_info)); + if (NS_FAILED(rv)) + status = nsICMSMessageErrors::GENERAL_ERROR; + + data->decoder_context = nullptr; + + nsCOMPtr<nsIX509Cert> certOfInterest; + + if (!data->smimeHeaderSink) + return 0; + + if (aRelativeNestLevel < 0) + return 0; + + int32_t maxNestLevel = 0; + data->smimeHeaderSink->MaxWantedNesting(&maxNestLevel); + + if (aRelativeNestLevel > maxNestLevel) + return 0; + + if (data->decoding_failed) + status = nsICMSMessageErrors::GENERAL_ERROR; + + if (!data->content_info) + { + if (!data->decoded_bytes) + { + // We were unable to decode any data. + status = nsICMSMessageErrors::GENERAL_ERROR; + } + else + { + // Some content got decoded, but we failed to decode + // the final summary, probably we got truncated data. + status = nsICMSMessageErrors::ENCRYPT_INCOMPLETE; + } + + // Although a CMS message could be either encrypted or opaquely signed, + // what we see is most likely encrypted, because if it were + // signed only, we probably would have been able to decode it. + + data->ci_is_encrypted = true; + } + else + { + rv = data->content_info->ContentIsEncrypted(&data->ci_is_encrypted); + + if (NS_SUCCEEDED(rv) && data->ci_is_encrypted) { + data->content_info->GetEncryptionCert(getter_AddRefs(certOfInterest)); + } + else { + // Existing logic in mimei assumes, if !ci_is_encrypted, then it is signed. + // Make sure it indeed is signed. + + bool testIsSigned; + rv = data->content_info->ContentIsSigned(&testIsSigned); + + if (NS_FAILED(rv) || !testIsSigned) { + // Neither signed nor encrypted? + // We are unable to understand what we got, do not try to indicate S/Mime status. + return 0; + } + + nsCString from_addr; + nsCString from_name; + nsCString sender_addr; + nsCString sender_name; + + MimeCMSGetFromSender(data->self, + from_addr, from_name, + sender_addr, sender_name); + + MimeCMSRequestAsyncSignatureVerification(data->content_info, + from_addr.get(), from_name.get(), + sender_addr.get(), sender_name.get(), + data->smimeHeaderSink, aRelativeNestLevel, + nullptr, 0); + } + } + + if (data->ci_is_encrypted) + { + data->smimeHeaderSink->EncryptionStatus( + aRelativeNestLevel, + status, + certOfInterest + ); + } + + return 0; +} + +static void +MimeCMS_free (void *crypto_closure) +{ + MimeCMSdata *data = (MimeCMSdata *) crypto_closure; + if (!data) return; + + delete data; +} + +static char * +MimeCMS_generate (void *crypto_closure) +{ + return nullptr; +} + diff --git a/mailnews/mime/src/mimecms.h b/mailnews/mime/src/mimecms.h new file mode 100644 index 000000000..fa08b28b8 --- /dev/null +++ b/mailnews/mime/src/mimecms.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMECMS_H_ +#define _MIMECMS_H_ + +#include "mimecryp.h" + +class nsICMSMessage; + +/* The MimeEncryptedCMS class implements a type of MIME object where the + object is passed through a CMS decryption engine to decrypt or verify + signatures. That module returns a new MIME object, which is then presented + to the user. See mimecryp.h for details of the general mechanism on which + this is built. + */ + +typedef struct MimeEncryptedCMSClass MimeEncryptedCMSClass; +typedef struct MimeEncryptedCMS MimeEncryptedCMS; + +struct MimeEncryptedCMSClass { + MimeEncryptedClass encrypted; +}; + +extern MimeEncryptedCMSClass mimeEncryptedCMSClass; + +struct MimeEncryptedCMS { + MimeEncrypted encrypted; /* superclass variables */ +}; + +#define MimeEncryptedCMSClassInitializer(ITYPE,CSUPER) \ + { MimeEncryptedClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEPKCS_H_ */ diff --git a/mailnews/mime/src/mimecom.cpp b/mailnews/mime/src/mimecom.cpp new file mode 100644 index 000000000..384542742 --- /dev/null +++ b/mailnews/mime/src/mimecom.cpp @@ -0,0 +1,74 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mimei.h" +#include "mimeobj.h" /* MimeObject (abstract) */ +#include "mimecont.h" /* |--- MimeContainer (abstract) */ +#include "mimemult.h" /* | |--- MimeMultipart (abstract) */ +#include "mimemsig.h" /* | | |--- MimeMultipartSigned (abstract)*/ +#include "mimetext.h" /* | |--- MimeInlineText (abstract) */ +#include "mimecryp.h" +#include "mimecth.h" + +/* + * These calls are necessary to expose the object class hierarchy + * to externally developed content type handlers. + */ +extern "C" void * +XPCOM_GetmimeInlineTextClass(void) +{ + return (void *) &mimeInlineTextClass; +} + +extern "C" void * +XPCOM_GetmimeLeafClass(void) +{ + return (void *) &mimeLeafClass; +} + +extern "C" void * +XPCOM_GetmimeObjectClass(void) +{ + return (void *) &mimeObjectClass; +} + +extern "C" void * +XPCOM_GetmimeContainerClass(void) +{ + return (void *) &mimeContainerClass; +} + +extern "C" void * +XPCOM_GetmimeMultipartClass(void) +{ + return (void *) &mimeMultipartClass; +} + +extern "C" void * +XPCOM_GetmimeMultipartSignedClass(void) +{ + return (void *) &mimeMultipartSignedClass; +} + +extern "C" void * +XPCOM_GetmimeEncryptedClass(void) +{ + return (void *) &mimeEncryptedClass; +} + +extern "C" int +XPCOM_MimeObject_write(void *mimeObject, + char *data, + int32_t length, + bool user_visible_p) +{ + return MIME_MimeObject_write((MimeObject *)mimeObject, data, + length, user_visible_p); +} + +extern "C" void * +XPCOM_Mime_create(char *content_type, void* hdrs, void* opts) +{ + return mime_create(content_type, (MimeHeaders *)hdrs, (MimeDisplayOptions *)opts); +} diff --git a/mailnews/mime/src/mimecom.h b/mailnews/mime/src/mimecom.h new file mode 100644 index 000000000..83c654a34 --- /dev/null +++ b/mailnews/mime/src/mimecom.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * XP-COM Bridges for C function calls + */ +#ifndef _MIMECOM_H_ +#define _MIMECOM_H_ + +#include <stdint.h> + +/* + * These functions are exposed by libmime to be used by content type + * handler plugins for processing stream data. + */ +/* + * This is the write call for outputting processed stream data. + */ +extern "C" int XPCOM_MimeObject_write(void *mimeObject, const char *data, + int32_t length, + bool user_visible_p); +/* + * The following group of calls expose the pointers for the object + * system within libmime. + */ +extern "C" void *XPCOM_GetmimeInlineTextClass(void); +extern "C" void *XPCOM_GetmimeLeafClass(void); +extern "C" void *XPCOM_GetmimeObjectClass(void); +extern "C" void *XPCOM_GetmimeContainerClass(void); +extern "C" void *XPCOM_GetmimeMultipartClass(void); +extern "C" void *XPCOM_GetmimeMultipartSignedClass(void); +extern "C" void *XPCOM_GetmimeEncryptedClass(void); + +extern "C" void *XPCOM_Mime_create(char *content_type, void* hdrs, void* opts); + +#endif /* _MIMECOM_H_ */ diff --git a/mailnews/mime/src/mimecont.cpp b/mailnews/mime/src/mimecont.cpp new file mode 100644 index 000000000..c5755a87f --- /dev/null +++ b/mailnews/mime/src/mimecont.cpp @@ -0,0 +1,218 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "prio.h" +#include "mimecont.h" +#include "nsMimeStringResources.h" + +#define MIME_SUPERCLASS mimeObjectClass +MimeDefClass(MimeContainer, MimeContainerClass, + mimeContainerClass, &MIME_SUPERCLASS); + +static int MimeContainer_initialize (MimeObject *); +static void MimeContainer_finalize (MimeObject *); +static int MimeContainer_add_child (MimeObject *, MimeObject *); +static int MimeContainer_parse_eof (MimeObject *, bool); +static int MimeContainer_parse_end (MimeObject *, bool); +static bool MimeContainer_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs); + +#if defined(DEBUG) && defined(XP_UNIX) +static int MimeContainer_debug_print (MimeObject *, PRFileDesc *, int32_t depth); +#endif + +static int +MimeContainerClassInitialize(MimeContainerClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) &clazz->object; + + NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + oclass->initialize = MimeContainer_initialize; + oclass->finalize = MimeContainer_finalize; + oclass->parse_eof = MimeContainer_parse_eof; + oclass->parse_end = MimeContainer_parse_end; + oclass->displayable_inline_p = MimeContainer_displayable_inline_p; + clazz->add_child = MimeContainer_add_child; + +#if defined(DEBUG) && defined(XP_UNIX) + oclass->debug_print = MimeContainer_debug_print; +#endif + return 0; +} + + +static int +MimeContainer_initialize (MimeObject *object) +{ + /* This is an abstract class; it shouldn't be directly instantiated. */ + NS_ASSERTION(object->clazz != (MimeObjectClass *) &mimeContainerClass, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeContainer_finalize (MimeObject *object) +{ + MimeContainer *cont = (MimeContainer *) object; + + /* Do this first so that children have their parse_eof methods called + in forward order (0-N) but are destroyed in backward order (N-0) + */ + if (!object->closed_p) + object->clazz->parse_eof (object, false); + if (!object->parsed_p) + object->clazz->parse_end (object, false); + + if (cont->children) + { + int i; + for (i = cont->nchildren-1; i >= 0; i--) + { + MimeObject *kid = cont->children[i]; + if (kid) + mime_free(kid); + cont->children[i] = 0; + } + PR_FREEIF(cont->children); + cont->nchildren = 0; + } + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + +static int +MimeContainer_parse_eof (MimeObject *object, bool abort_p) +{ + MimeContainer *cont = (MimeContainer *) object; + int status; + + /* We must run all of this object's parent methods first, to get all the + data flushed down its stream, so that the children's parse_eof methods + can access it. We do not access *this* object again after doing this, + only its children. + */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(object, abort_p); + if (status < 0) return status; + + if (cont->children) + { + int i; + for (i = 0; i < cont->nchildren; i++) + { + MimeObject *kid = cont->children[i]; + if (kid && !kid->closed_p) + { + int lstatus = kid->clazz->parse_eof(kid, abort_p); + if (lstatus < 0) return lstatus; + } + } + } + return 0; +} + +static int +MimeContainer_parse_end (MimeObject *object, bool abort_p) +{ + MimeContainer *cont = (MimeContainer *) object; + int status; + + /* We must run all of this object's parent methods first, to get all the + data flushed down its stream, so that the children's parse_eof methods + can access it. We do not access *this* object again after doing this, + only its children. + */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_end(object, abort_p); + if (status < 0) return status; + + if (cont->children) + { + int i; + for (i = 0; i < cont->nchildren; i++) + { + MimeObject *kid = cont->children[i]; + if (kid && !kid->parsed_p) + { + int lstatus = kid->clazz->parse_end(kid, abort_p); + if (lstatus < 0) return lstatus; + } + } + } + return 0; +} + +static int +MimeContainer_add_child (MimeObject *parent, MimeObject *child) +{ + MimeContainer *cont = (MimeContainer *) parent; + MimeObject **old_kids, **new_kids; + + NS_ASSERTION(parent && child, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!parent || !child) return -1; + + old_kids = cont->children; + new_kids = (MimeObject **)PR_MALLOC(sizeof(MimeObject *) * (cont->nchildren + 1)); + if (!new_kids) return MIME_OUT_OF_MEMORY; + + if (cont->nchildren > 0) + memcpy(new_kids, old_kids, sizeof(MimeObject *) * cont->nchildren); + new_kids[cont->nchildren] = child; + PR_Free(old_kids); + cont->children = new_kids; + cont->nchildren++; + + child->parent = parent; + + /* Copy this object's options into the child. */ + child->options = parent->options; + + return 0; +} + +static bool +MimeContainer_displayable_inline_p (MimeObjectClass *clazz, MimeHeaders *hdrs) +{ + return true; +} + + +#if defined(DEBUG) && defined(XP_UNIX) +static int +MimeContainer_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth) +{ + MimeContainer *cont = (MimeContainer *) obj; + int i; + char *addr = mime_part_address(obj); + for (i=0; i < depth; i++) + PR_Write(stream, " ", 2); + /* + PR_Write(stream, "<%s %s (%d kid%s) 0x%08X>\n", + obj->clazz->class_name, + addr ? addr : "???", + cont->nchildren, (cont->nchildren == 1 ? "" : "s"), + (uint32_t) cont); + */ + PR_FREEIF(addr); + +/* + if (cont->nchildren > 0) + fprintf(stream, "\n"); + */ + + for (i = 0; i < cont->nchildren; i++) + { + MimeObject *kid = cont->children[i]; + int status = kid->clazz->debug_print (kid, stream, depth+1); + if (status < 0) return status; + } + +/* + if (cont->nchildren > 0) + fprintf(stream, "\n"); + */ + + return 0; +} +#endif diff --git a/mailnews/mime/src/mimecont.h b/mailnews/mime/src/mimecont.h new file mode 100644 index 000000000..8f8edc566 --- /dev/null +++ b/mailnews/mime/src/mimecont.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMECONT_H_ +#define _MIMECONT_H_ + +#include "mimeobj.h" + +/* MimeContainer is the class for the objects representing all MIME + types which can contain other MIME objects within them. In addition + to the methods inherited from MimeObject, it provides one method: + + int add_child (MimeObject *parent, MimeObject *child) + + Given a parent (a subclass of MimeContainer) this method adds the + child (any MIME object) to the parent's list of children. + + The MimeContainer `finalize' method will finalize the children as well. + */ + +typedef struct MimeContainerClass MimeContainerClass; +typedef struct MimeContainer MimeContainer; + +struct MimeContainerClass { + MimeObjectClass object; + int (*add_child) (MimeObject *parent, MimeObject *child); +}; + +extern MimeContainerClass mimeContainerClass; + +struct MimeContainer { + MimeObject object; /* superclass variables */ + + MimeObject **children; /* list of contained objects */ + int32_t nchildren; /* how many */ +}; + +#define MimeContainerClassInitializer(ITYPE,CSUPER) \ + { MimeObjectClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMECONT_H_ */ diff --git a/mailnews/mime/src/mimecryp.cpp b/mailnews/mime/src/mimecryp.cpp new file mode 100644 index 000000000..9f6e3630f --- /dev/null +++ b/mailnews/mime/src/mimecryp.cpp @@ -0,0 +1,571 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimecryp.h" +#include "mimemoz2.h" +#include "nsMimeTypes.h" +#include "nsMimeStringResources.h" +#include "mimebuf.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "mimemult.h" +#include "nsIMimeConverter.h" // for MimeConverterOutputCallback + + +#define MIME_SUPERCLASS mimeContainerClass +MimeDefClass(MimeEncrypted, MimeEncryptedClass, mimeEncryptedClass, + &MIME_SUPERCLASS); + +static int MimeEncrypted_initialize (MimeObject *); +static void MimeEncrypted_finalize (MimeObject *); +static int MimeEncrypted_parse_begin (MimeObject *); +static int MimeEncrypted_parse_buffer (const char *, int32_t, MimeObject *); +static int MimeEncrypted_parse_line (const char *, int32_t, MimeObject *); +static int MimeEncrypted_parse_decoded_buffer (const char *, int32_t, MimeObject *); +static int MimeEncrypted_parse_eof (MimeObject *, bool); +static int MimeEncrypted_parse_end (MimeObject *, bool); +static int MimeEncrypted_add_child (MimeObject *, MimeObject *); + +static int MimeHandleDecryptedOutput (const char *, int32_t, void *); +static int MimeHandleDecryptedOutputLine (char *, int32_t, MimeObject *); +static int MimeEncrypted_close_headers (MimeObject *); +static int MimeEncrypted_emit_buffered_child(MimeObject *); + +static int +MimeEncryptedClassInitialize(MimeEncryptedClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeContainerClass *cclass = (MimeContainerClass *) clazz; + + NS_ASSERTION(!oclass->class_initialized, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59"); + oclass->initialize = MimeEncrypted_initialize; + oclass->finalize = MimeEncrypted_finalize; + oclass->parse_begin = MimeEncrypted_parse_begin; + oclass->parse_buffer = MimeEncrypted_parse_buffer; + oclass->parse_line = MimeEncrypted_parse_line; + oclass->parse_eof = MimeEncrypted_parse_eof; + oclass->parse_end = MimeEncrypted_parse_end; + + cclass->add_child = MimeEncrypted_add_child; + + clazz->parse_decoded_buffer = MimeEncrypted_parse_decoded_buffer; + + return 0; +} + + +static int +MimeEncrypted_initialize (MimeObject *obj) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj); +} + + +static int +MimeEncrypted_parse_begin (MimeObject *obj) +{ + MimeEncrypted *enc = (MimeEncrypted *) obj; + MimeDecoderData *(*fn) (MimeConverterOutputCallback, void*) = 0; + + if (enc->crypto_closure) + return -1; + + enc->crypto_closure = (((MimeEncryptedClass *) obj->clazz)->crypto_init) (obj, MimeHandleDecryptedOutput, obj); + if (!enc->crypto_closure) + return -1; + + + /* (Mostly duplicated from MimeLeaf, see comments in mimecryp.h.) + Initialize a decoder if necessary. + */ + if (!obj->encoding) + ; + else if (!PL_strcasecmp(obj->encoding, ENCODING_BASE64)) + fn = &MimeB64DecoderInit; + else if (!PL_strcasecmp(obj->encoding, ENCODING_QUOTED_PRINTABLE)) + { + enc->decoder_data = + MimeQPDecoderInit (/* The (MimeConverterOutputCallback) cast is to turn the + `void' argument into `MimeObject'. */ + ((MimeConverterOutputCallback) + ((MimeEncryptedClass *)obj->clazz)->parse_decoded_buffer), + obj); + + if (!enc->decoder_data) + return MIME_OUT_OF_MEMORY; + } + else if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE2) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE3) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE4)) + fn = &MimeUUDecoderInit; + else if (!PL_strcasecmp(obj->encoding, ENCODING_YENCODE)) + fn = &MimeYDecoderInit; + if (fn) + { + enc->decoder_data = + fn (/* The (MimeConverterOutputCallback) cast is to turn the `void' + argument into `MimeObject'. */ + ((MimeConverterOutputCallback) + ((MimeEncryptedClass *)obj->clazz)->parse_decoded_buffer), + obj); + + if (!enc->decoder_data) + return MIME_OUT_OF_MEMORY; + } + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); +} + + +static int +MimeEncrypted_parse_buffer (const char *buffer, int32_t size, MimeObject *obj) +{ + /* (Duplicated from MimeLeaf, see comments in mimecryp.h.) + */ + + MimeEncrypted *enc = (MimeEncrypted *) obj; + + if (obj->closed_p) return -1; + + /* Don't consult output_p here, since at this point we're behaving as a + simple container object -- the output_p decision should be made by + the child of this object. */ + + if (enc->decoder_data) + return MimeDecoderWrite (enc->decoder_data, buffer, size, nullptr); + else + return ((MimeEncryptedClass *)obj->clazz)->parse_decoded_buffer (buffer, + size, + obj); +} + + +static int +MimeEncrypted_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + NS_ERROR("This method shouldn't ever be called."); + return -1; +} + +static int +MimeEncrypted_parse_decoded_buffer (const char *buffer, int32_t size, MimeObject *obj) +{ + MimeEncrypted *enc = (MimeEncrypted *) obj; + return + ((MimeEncryptedClass *) obj->clazz)->crypto_write (buffer, size, + enc->crypto_closure); +} + + +static int +MimeEncrypted_parse_eof (MimeObject *obj, bool abort_p) +{ + int status = 0; + MimeEncrypted *enc = (MimeEncrypted *) obj; + + if (obj->closed_p) return 0; + NS_ASSERTION(!obj->parsed_p, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59"); + + /* (Duplicated from MimeLeaf, see comments in mimecryp.h.) + Close off the decoder, to cause it to give up any buffered data that + it is still holding. + */ + if (enc->decoder_data) + { + int status = MimeDecoderDestroy(enc->decoder_data, false); + enc->decoder_data = 0; + if (status < 0) return status; + } + + + /* If there is still data in the ibuffer, that means that the last + *decrypted* line of this part didn't end in a newline; so push it out + anyway (this means that the parse_line method will be called with a + string with no trailing newline, which isn't the usual case.) */ + if (!abort_p && + obj->ibuffer_fp > 0) + { + int status = MimeHandleDecryptedOutputLine (obj->ibuffer, + obj->ibuffer_fp, obj); + obj->ibuffer_fp = 0; + if (status < 0) + { + obj->closed_p = true; + return status; + } + } + + + /* Now run the superclass's parse_eof, which (because we've already taken + care of ibuffer in a way appropriate for this class, immediately above) + will ony set closed_p to true. + */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof (obj, abort_p); + if (status < 0) return status; + + + /* Now close off the underlying crypto module. At this point, the crypto + module has all of the input. (DecoderDestroy called parse_decoded_buffer + which called crypto_write, with the last of the data.) + */ + if (enc->crypto_closure) + { + status = + ((MimeEncryptedClass *) obj->clazz)->crypto_eof (enc->crypto_closure, + abort_p); + if (status < 0 && !abort_p) + return status; + } + + /* Now we have the entire child part in the part buffer. + We are now able to verify its signature, emit a blurb, and then + emit the part. + */ + if (abort_p) + return 0; + else + return MimeEncrypted_emit_buffered_child (obj); +} + + +static int +MimeEncrypted_parse_end (MimeObject *obj, bool abort_p) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_end (obj, abort_p); +} + + +static void +MimeEncrypted_cleanup (MimeObject *obj, bool finalizing_p) +{ + MimeEncrypted *enc = (MimeEncrypted *) obj; + + if (enc->part_buffer) + { + MimePartBufferDestroy(enc->part_buffer); + enc->part_buffer = 0; + } + + if (finalizing_p && enc->crypto_closure) + { + /* Don't free these until this object is really going away -- keep them + around for the lifetime of the MIME object, so that we can get at the + security info of sub-parts of the currently-displayed message. */ + ((MimeEncryptedClass *) obj->clazz)->crypto_free (enc->crypto_closure); + enc->crypto_closure = 0; + } + + /* (Duplicated from MimeLeaf, see comments in mimecryp.h.) + Free the decoder data, if it's still around. */ + if (enc->decoder_data) + { + MimeDecoderDestroy(enc->decoder_data, true); + enc->decoder_data = 0; + } + + if (enc->hdrs) + { + MimeHeaders_free(enc->hdrs); + enc->hdrs = 0; + } +} + + +static void +MimeEncrypted_finalize (MimeObject *obj) +{ + MimeEncrypted_cleanup (obj, true); + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize (obj); +} + + +static int +MimeHandleDecryptedOutput (const char *buf, int32_t buf_size, + void *output_closure) +{ + /* This method is invoked by the underlying decryption module. + The module is assumed to return a MIME object, and its associated + headers. For example, if a text/plain document was encrypted, + the encryption module would return the following data: + + Content-Type: text/plain + + Decrypted text goes here. + + This function will then extract a header block (up to the first + blank line, as usual) and will then handle the included data as + appropriate. + */ + MimeObject *obj = (MimeObject *) output_closure; + + /* Is it truly safe to use ibuffer here? I think so... */ + return mime_LineBuffer (buf, buf_size, + &obj->ibuffer, &obj->ibuffer_size, &obj->ibuffer_fp, + true, + ((int (*) (char *, int32_t, void *)) + /* This cast is to turn void into MimeObject */ + MimeHandleDecryptedOutputLine), + obj); +} + +static int +MimeHandleDecryptedOutputLine (char *line, int32_t length, MimeObject *obj) +{ + /* Largely the same as MimeMessage_parse_line (the other MIME container + type which contains exactly one child.) + */ + MimeEncrypted *enc = (MimeEncrypted *) obj; + int status = 0; + + if (!line || !*line) return -1; + + /* If we're supposed to write this object, but aren't supposed to convert + it to HTML, simply pass it through unaltered. */ + if (obj->output_p && + obj->options && + !obj->options->write_html_p && + obj->options->output_fn) + return MimeObject_write(obj, line, length, true); + + /* If we already have a child object in the buffer, then we're done parsing + headers, and all subsequent lines get passed to the inferior object + without further processing by us. (Our parent will stop feeding us + lines when this MimeMessage part is out of data.) + */ + if (enc->part_buffer) + return MimePartBufferWrite (enc->part_buffer, line, length); + + /* Otherwise we don't yet have a child object in the buffer, which means + we're not done parsing our headers yet. + */ + if (!enc->hdrs) + { + enc->hdrs = MimeHeaders_new(); + if (!enc->hdrs) return MIME_OUT_OF_MEMORY; + } + + status = MimeHeaders_parse_line(line, length, enc->hdrs); + if (status < 0) return status; + + /* If this line is blank, we're now done parsing headers, and should + examine our content-type to create our "body" part. + */ + if (*line == '\r' || *line == '\n') + { + status = MimeEncrypted_close_headers(obj); + if (status < 0) return status; + } + + return 0; +} + +static int +MimeEncrypted_close_headers (MimeObject *obj) +{ + MimeEncrypted *enc = (MimeEncrypted *) obj; + + // Notify the JS Mime Emitter that this was an encrypted part that it should + // hopefully not analyze for indexing... + if (obj->options->notify_nested_bodies) + mimeEmitterAddHeaderField(obj->options, "x-jsemitter-encrypted", "1"); + + if (enc->part_buffer) return -1; + enc->part_buffer = MimePartBufferCreate(); + if (!enc->part_buffer) + return MIME_OUT_OF_MEMORY; + + return 0; +} + + +static int +MimeEncrypted_add_child (MimeObject *parent, MimeObject *child) +{ + MimeContainer *cont = (MimeContainer *) parent; + if (!parent || !child) return -1; + + /* Encryption containers can only have one child. */ + if (cont->nchildren != 0) return -1; + + return ((MimeContainerClass*)&MIME_SUPERCLASS)->add_child (parent, child); +} + + +static int +MimeEncrypted_emit_buffered_child(MimeObject *obj) +{ + MimeEncrypted *enc = (MimeEncrypted *) obj; + int status = 0; + char *ct = 0; + MimeObject *body; + + NS_ASSERTION(enc->crypto_closure, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59"); + + /* Emit some HTML saying whether the signature was cool. + But don't emit anything if in FO_QUOTE_MESSAGE mode. + + Also, don't emit anything if the enclosed object is itself a signed + object -- in the case of an encrypted object which contains a signed + object, we only emit the HTML once (since the normal way of encrypting + and signing is to nest the signature inside the crypto envelope.) + */ + if (enc->crypto_closure && + obj->options && + obj->options->headers != MimeHeadersCitation && + obj->options->write_html_p && + obj->options->output_fn) + // && !mime_crypto_object_p(enc->hdrs, true, obj->options)) // XXX fix later XXX // + { + char *html; +#if 0 // XXX Fix this later XXX // + char *html = (((MimeEncryptedClass *) obj->clazz)->crypto_generate_html + (enc->crypto_closure)); + if (!html) return -1; /* MK_OUT_OF_MEMORY? */ + + status = MimeObject_write(obj, html, strlen(html), false); + PR_FREEIF(html); + if (status < 0) return status; +#endif + + /* Now that we have written out the crypto stamp, the outermost header + block is well and truly closed. If this is in fact the outermost + message, then run the post_header_html_fn now. + */ + if (obj->options && + obj->options->state && + obj->options->generate_post_header_html_fn && + !obj->options->state->post_header_html_run_p) + { + MimeHeaders *outer_headers = nullptr; + MimeObject *p; + for (p = obj; p->parent; p = p->parent) + outer_headers = p->headers; + NS_ASSERTION(obj->options->state->first_data_written_p, "1.2 <mscott@netscape.com> 01 Nov 2001 17:59"); + html = obj->options->generate_post_header_html_fn(NULL, + obj->options->html_closure, + outer_headers); + obj->options->state->post_header_html_run_p = true; + if (html) + { + status = MimeObject_write(obj, html, strlen(html), false); + PR_FREEIF(html); + if (status < 0) return status; + } + } + } + else if (enc->crypto_closure && + obj->options && + obj->options->decrypt_p) + { + /* Do this just to cause `mime_set_crypto_stamp' to be called, and to + cause the various `decode_error' and `verify_error' slots to be set: + we don't actually use the returned HTML, because we're not emitting + HTML. It's maybe not such a good thing that the determination of + whether it was encrypted or not is tied up with generating HTML, + but oh well. */ + char *html = (((MimeEncryptedClass *) obj->clazz)->crypto_generate_html + (enc->crypto_closure)); + PR_FREEIF(html); + } + + if (enc->hdrs) + ct = MimeHeaders_get (enc->hdrs, HEADER_CONTENT_TYPE, true, false); + body = mime_create((ct ? ct : TEXT_PLAIN), enc->hdrs, obj->options); + +#ifdef MIME_DRAFTS + if (obj->options->decompose_file_p) { + if (mime_typep (body, (MimeObjectClass*) &mimeMultipartClass) ) + obj->options->is_multipart_msg = true; + else if (obj->options->decompose_file_init_fn) + obj->options->decompose_file_init_fn(obj->options->stream_closure, + enc->hdrs); + } +#endif /* MIME_DRAFTS */ + + PR_FREEIF(ct); + + if (!body) return MIME_OUT_OF_MEMORY; + status = ((MimeContainerClass *) obj->clazz)->add_child (obj, body); + if (status < 0) + { + mime_free(body); + return status; + } + + /* Now that we've added this new object to our list of children, + start its parser going. */ + status = body->clazz->parse_begin(body); + if (status < 0) return status; + + /* If this object (or the parent) is being output, then by definition + the child is as well. (This is only necessary because this is such + a funny sort of container...) + */ + if (!body->output_p && + (obj->output_p || + (obj->parent && obj->parent->output_p))) + body->output_p = true; + + /* If the body is being written raw (not as HTML) then make sure to + write its headers as well. */ + if (body->output_p && obj->output_p && !obj->options->write_html_p) + { + status = MimeObject_write(body, "", 0, false); /* initialize */ + if (status < 0) return status; + status = MimeHeaders_write_raw_headers(body->headers, obj->options, + false); + if (status < 0) return status; + } + + if (enc->part_buffer) /* part_buffer is 0 for 0-length encrypted data. */ + { +#ifdef MIME_DRAFTS + if (obj->options->decompose_file_p && !obj->options->is_multipart_msg) + { + status = MimePartBufferRead(enc->part_buffer, + /* The (MimeConverterOutputCallback) cast is to turn the `void' + argument into `MimeObject'. */ + ((MimeConverterOutputCallback) + obj->options->decompose_file_output_fn), + obj->options->stream_closure); + } + else + { +#endif /* MIME_DRAFTS */ + + status = MimePartBufferRead(enc->part_buffer, + /* The (MimeConverterOutputCallback) cast is to turn the `void' + argument into `MimeObject'. */ + ((MimeConverterOutputCallback) body->clazz->parse_buffer), + body); +#ifdef MIME_DRAFTS + } +#endif /* MIME_DRAFTS */ + } + if (status < 0) return status; + + /* The child has been fully processed. Close it off. + */ + status = body->clazz->parse_eof(body, false); + if (status < 0) return status; + + status = body->clazz->parse_end(body, false); + if (status < 0) return status; + +#ifdef MIME_DRAFTS + if (obj->options->decompose_file_p && !obj->options->is_multipart_msg) + obj->options->decompose_file_close_fn(obj->options->stream_closure); +#endif /* MIME_DRAFTS */ + + /* Put out a separator after every encrypted object. */ + status = MimeObject_write_separator(obj); + if (status < 0) return status; + + MimeEncrypted_cleanup (obj, false); + + return 0; +} diff --git a/mailnews/mime/src/mimecryp.h b/mailnews/mime/src/mimecryp.h new file mode 100644 index 000000000..c74770b18 --- /dev/null +++ b/mailnews/mime/src/mimecryp.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMECRYP_H_ +#define _MIMECRYP_H_ + +#include "mimecont.h" +// #include "mimeenc.h" +#include "modmimee.h" +#include "mimepbuf.h" + +/* The MimeEncrypted class implements a type of MIME object where the object + is passed to some other routine, which then returns a new MIME object. + This is the basis of a decryption module. + + Oddly, this class behaves both as a container and as a leaf: it acts as a + container in that it parses out data in order to eventually present a + contained object; however, it acts as a leaf in that this container may + itself have a Content-Transfer-Encoding applied to its body. This violates + the cardinal rule of MIME containers, which is that encodings don't nest, + and therefore containers can't have encodings. But, the fact that the + S/MIME spec doesn't follow the groundwork laid down by previous MIME specs + isn't something we can do anything about at this point... + + Therefore, this class duplicates some of the work done by the MimeLeaf + class, to meet its dual goals of container-hood and leaf-hood. (We could + alternately have made this class be a subclass of leaf, and had it duplicate + the effort of MimeContainer, but that seemed like the harder approach.) + + The MimeEncrypted class provides the following methods: + + void *crypto_init(MimeObject *obj, + int (*output_fn) (const char *data, int32 data_size, + void *output_closure), + void *output_closure) + + This is called with the MimeObject representing the encrypted data. + The obj->headers should be used to initialize the decryption engine. + NULL indicates failure; otherwise, an opaque closure object should + be returned. + + output_fn is what the decryption module should use to write a new MIME + object (the decrypted data.) output_closure should be passed along to + every call to the output routine. + + The data sent to output_fn should begin with valid MIME headers indicating + the type of the data. For example, if decryption resulted in a text + document, the data fed through to the output_fn might minimally look like + + Content-Type: text/plain + + This is the decrypted data. + It is only two lines long. + + Of course, the data may be of any MIME type, including multipart/mixed. + Any returned MIME object will be recursively processed and presented + appropriately. (This also imples that encrypted objects may nest, and + thus that the underlying decryption module must be reentrant.) + + int crypto_write (const char *data, int32 data_size, void *crypto_closure) + + This is called with the raw encrypted data. This data might not come + in line-based chunks: if there was a Content-Transfer-Encoding applied + to the data (base64 or quoted-printable) then it will have been decoded + first (handing binary data to the filter_fn.) `crypto_closure' is the + object that `crypto_init' returned. This may return negative on error. + + int crypto_eof (void *crypto_closure, bool abort_p) + + This is called when no more data remains. It may call `output_fn' again + to flush out any buffered data. If `abort_p' is true, then it may choose + to discard any data rather than processing it, as we're terminating + abnormally. + + char * crypto_generate_html (void *crypto_closure) + + This is called after `crypto_eof' but before `crypto_free'. The crypto + module should return a newly-allocated string of HTML code which + explains the status of the decryption to the user (whether the signature + checked out, etc.) + + void crypto_free (void *crypto_closure) + + This will be called when we're all done, after `crypto_eof' and + `crypto_emit_html'. It is intended to free any data represented + by the crypto_closure. output_fn may not be called. + + + int (*parse_decoded_buffer) (const char *buf, int32 size, MimeObject *obj) + + This method, of the same name as one in MimeLeaf, is a part of the + afforementioned leaf/container hybridization. This method is invoked + with the content-transfer-decoded body of this part (without line + buffering.) The default behavior of this method is to simply invoke + `crypto_write' on the data with which it is called. It's unlikely that + a subclass will need to specialize this. + */ + +typedef struct MimeEncryptedClass MimeEncryptedClass; +typedef struct MimeEncrypted MimeEncrypted; + +struct MimeEncryptedClass { + MimeContainerClass container; + + /* Duplicated from MimeLeaf, see comments above. + This is the callback that is handed to the decoder. */ + int (*parse_decoded_buffer) (const char *buf, int32_t size, MimeObject *obj); + + + /* Callbacks used by decryption module. */ + void * (*crypto_init) (MimeObject *obj, + int (*output_fn) (const char *data, int32_t data_size, + void *output_closure), + void *output_closure); + int (*crypto_write) (const char *data, int32_t data_size, + void *crypto_closure); + int (*crypto_eof) (void *crypto_closure, bool abort_p); + char * (*crypto_generate_html) (void *crypto_closure); + void (*crypto_free) (void *crypto_closure); +}; + +extern MimeEncryptedClass mimeEncryptedClass; + +struct MimeEncrypted { + MimeContainer container; /* superclass variables */ + void *crypto_closure; /* Opaque data used by decryption module. */ + MimeDecoderData *decoder_data; /* Opaque data for the Transfer-Encoding + decoder. */ + MimeHeaders *hdrs; /* Headers of the enclosed object (including + the type of the *decrypted* data.) */ + MimePartBufferData *part_buffer; /* The data of the decrypted enclosed + object (see mimepbuf.h) */ +}; + +#define MimeEncryptedClassInitializer(ITYPE,CSUPER) \ + { MimeContainerClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMECRYP_H_ */ diff --git a/mailnews/mime/src/mimecth.cpp b/mailnews/mime/src/mimecth.cpp new file mode 100644 index 000000000..1cfaba82c --- /dev/null +++ b/mailnews/mime/src/mimecth.cpp @@ -0,0 +1,51 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mimecth.h" + +/* + * These calls are necessary to expose the object class hierarchy + * to externally developed content type handlers. + */ +MimeInlineTextClass * +MIME_GetmimeInlineTextClass(void) +{ + return &mimeInlineTextClass; +} + +MimeLeafClass * +MIME_GetmimeLeafClass(void) +{ + return &mimeLeafClass; +} + +MimeObjectClass * +MIME_GetmimeObjectClass(void) +{ + return &mimeObjectClass; +} + +MimeContainerClass * +MIME_GetmimeContainerClass(void) +{ + return &mimeContainerClass; +} + +MimeMultipartClass * +MIME_GetmimeMultipartClass(void) +{ + return &mimeMultipartClass; +} + +MimeMultipartSignedClass * +MIME_GetmimeMultipartSignedClass(void) +{ + return &mimeMultipartSignedClass; +} + +MimeEncryptedClass * +MIME_GetmimeEncryptedClass(void) +{ + return &mimeEncryptedClass; +} diff --git a/mailnews/mime/src/mimecth.h b/mailnews/mime/src/mimecth.h new file mode 100644 index 000000000..1f5d89663 --- /dev/null +++ b/mailnews/mime/src/mimecth.h @@ -0,0 +1,135 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This is the definitions for the Content Type Handler plugins for + * libmime. This will allow developers the dynamically add the ability + * for libmime to render new content types in the MHTML rendering of + * HTML messages. + */ + +#ifndef _MIMECTH_H_ +#define _MIMECTH_H_ + +#include "mimei.h" +#include "mimeobj.h" /* MimeObject (abstract) */ +#include "mimecont.h" /* |--- MimeContainer (abstract) */ +#include "mimemult.h" /* | |--- MimeMultipart (abstract) */ +#include "mimemsig.h" /* | | |--- MimeMultipartSigned (abstract)*/ +#include "mimetext.h" /* | |--- MimeInlineText (abstract) */ +#include "mimecryp.h" + +/* + This header exposes functions that are necessary to access the + object hierarchy for the mime chart. The class hierarchy is: + + MimeObject (abstract) + | + |--- MimeContainer (abstract) + | | + | |--- MimeMultipart (abstract) + | | | + | | |--- MimeMultipartMixed + | | | + | | |--- MimeMultipartDigest + | | | + | | |--- MimeMultipartParallel + | | | + | | |--- MimeMultipartAlternative + | | | + | | |--- MimeMultipartRelated + | | | + | | |--- MimeMultipartAppleDouble + | | | + | | |--- MimeSunAttachment + | | | + | | |--- MimeMultipartSigned (abstract) + | | | + | | |--- MimeMultipartSigned + | | + | |--- MimeXlateed (abstract) + | | | + | | |--- MimeXlateed + | | + | |--- MimeMessage + | | + | |--- MimeUntypedText + | + |--- MimeLeaf (abstract) + | | + | |--- MimeInlineText (abstract) + | | | + | | |--- MimeInlineTextPlain + | | | + | | |--- MimeInlineTextHTML + | | | + | | |--- MimeInlineTextRichtext + | | | | + | | | |--- MimeInlineTextEnriched + | | | + | | |--- MimeInlineTextVCard + | | + | |--- MimeInlineImage + | | + | |--- MimeExternalObject + | + |--- MimeExternalBody + */ + +#include "nsIMimeContentTypeHandler.h" + +/* + * These functions are exposed by libmime to be used by content type + * handler plugins for processing stream data. + */ +/* + * This is the write call for outputting processed stream data. + */ +extern int MIME_MimeObject_write(MimeObject *, + const char *data, + int32_t length, + bool user_visible_p); +/* + * The following group of calls expose the pointers for the object + * system within libmime. + */ +extern MimeInlineTextClass *MIME_GetmimeInlineTextClass(void); +extern MimeLeafClass *MIME_GetmimeLeafClass(void); +extern MimeObjectClass *MIME_GetmimeObjectClass(void); +extern MimeContainerClass *MIME_GetmimeContainerClass(void); +extern MimeMultipartClass *MIME_GetmimeMultipartClass(void); +extern MimeMultipartSignedClass *MIME_GetmimeMultipartSignedClass(void); +extern MimeEncryptedClass *MIME_GetmimeEncryptedClass(void); + +/* + * These are the functions that need to be implemented by the + * content type handler plugin. They will be called by by libmime + * when the module is loaded at runtime. + */ + +/* + * MIME_GetContentType() is called by libmime to identify the content + * type handled by this plugin. + */ +extern "C" +char *MIME_GetContentType(void); + +/* + * This will create the MimeObjectClass object to be used by the libmime + * object system. + */ +extern "C" +MimeObjectClass *MIME_CreateContentTypeHandlerClass(const char *content_type, + contentTypeHandlerInitStruct *initStruct); + +/* + * Typedefs for libmime to use when locating and calling the above + * defined functions. + */ +typedef char * (*mime_get_ct_fn_type)(void); +typedef MimeObjectClass * (*mime_create_class_fn_type) + (const char *, contentTypeHandlerInitStruct *); + +#endif /* _MIMECTH_H_ */ diff --git a/mailnews/mime/src/mimedrft.cpp b/mailnews/mime/src/mimedrft.cpp new file mode 100644 index 000000000..3293d8b96 --- /dev/null +++ b/mailnews/mime/src/mimedrft.cpp @@ -0,0 +1,2084 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ +#include "nsCOMPtr.h" +#include "modmimee.h" +#include "mimeobj.h" +#include "modlmime.h" +#include "mimei.h" +#include "mimebuf.h" +#include "mimemoz2.h" +#include "mimemsg.h" +#include "nsMimeTypes.h" +#include <ctype.h> + +#include "prmem.h" +#include "plstr.h" +#include "prprf.h" +#include "prio.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "msgCore.h" +#include "nsIMsgSend.h" +#include "nsMimeStringResources.h" +#include "nsIIOService.h" +#include "nsNetUtil.h" +#include "comi18n.h" +#include "nsIMsgAttachment.h" +#include "nsIMsgCompFields.h" +#include "nsMsgCompCID.h" +#include "nsIMsgComposeService.h" +#include "nsMsgAttachmentData.h" +#include "nsMsgI18N.h" +#include "nsNativeCharsetUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIMsgMessageService.h" +#include "nsMsgUtils.h" +#include "nsCExternalHandlerService.h" +#include "nsIMIMEService.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgBaseCID.h" +#include "nsIMimeConverter.h" // for MimeConverterOutputCallback +#include "mozilla/mailnews/MimeHeaderParser.h" + +using namespace mozilla::mailnews; + +// +// Header strings... +// +#define HEADER_NNTP_POSTING_HOST "NNTP-Posting-Host" +#define MIME_HEADER_TABLE "<TABLE CELLPADDING=0 CELLSPACING=0 BORDER=0 class=\"moz-email-headers-table\">" +#define HEADER_START_JUNK "<TR><TH VALIGN=BASELINE ALIGN=RIGHT NOWRAP>" +#define HEADER_MIDDLE_JUNK ": </TH><TD>" +#define HEADER_END_JUNK "</TD></TR>" + +// +// Forward declarations... +// +extern "C" char *MIME_StripContinuations(char *original); +int mime_decompose_file_init_fn ( void *stream_closure, MimeHeaders *headers ); +int mime_decompose_file_output_fn ( const char *buf, int32_t size, void *stream_closure ); +int mime_decompose_file_close_fn ( void *stream_closure ); +extern int MimeHeaders_build_heads_list(MimeHeaders *hdrs); + +// CID's +static NS_DEFINE_CID(kCMsgComposeServiceCID, NS_MSGCOMPOSESERVICE_CID); + +mime_draft_data::mime_draft_data() : url_name(nullptr), format_out(0), + stream(nullptr), obj(nullptr), options(nullptr), headers(nullptr), + messageBody(nullptr), curAttachment(nullptr), + decoder_data(nullptr), mailcharset(nullptr), forwardInline(false), + forwardInlineFilter(false), overrideComposeFormat(false), + originalMsgURI(nullptr) +{ +} +//////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// +// THIS SHOULD ALL MOVE TO ANOTHER FILE AFTER LANDING! +//////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// + +// safe filename for all OSes +#define SAFE_TMP_FILENAME "nsmime.tmp" + +// +// Create a file for the a unique temp file +// on the local machine. Caller must free memory +// +nsresult +nsMsgCreateTempFile(const char *tFileName, nsIFile **tFile) +{ + if ((!tFileName) || (!*tFileName)) + tFileName = SAFE_TMP_FILENAME; + + nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + tFileName, + tFile); + + NS_ENSURE_SUCCESS(rv, rv); + + rv = (*tFile)->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + if (NS_FAILED(rv)) + NS_RELEASE(*tFile); + + return rv; +} + + +//////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// +// END OF - THIS SHOULD ALL MOVE TO ANOTHER FILE AFTER LANDING! +//////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////// + +typedef enum { + nsMsg_RETURN_RECEIPT_BOOL_HEADER_MASK = 0, + nsMsg_ENCRYPTED_BOOL_HEADER_MASK, + nsMsg_SIGNED_BOOL_HEADER_MASK, + nsMsg_UUENCODE_BINARY_BOOL_HEADER_MASK, + nsMsg_ATTACH_VCARD_BOOL_HEADER_MASK, + nsMsg_LAST_BOOL_HEADER_MASK // last boolean header mask; must be the last one + // DON'T remove. +} nsMsgBoolHeaderSet; + +#ifdef NS_DEBUG +extern "C" void +mime_dump_attachments ( nsMsgAttachmentData *attachData ) +{ + int32_t i = 0; + class nsMsgAttachmentData *tmp = attachData; + + while ( (tmp) && (tmp->m_url) ) + { + printf("Real Name : %s\n", tmp->m_realName.get()); + + if ( tmp->m_url ) + { + ; + printf("URL : %s\n", tmp->m_url->GetSpecOrDefault().get()); + } + + printf("Desired Type : %s\n", tmp->m_desiredType.get()); + printf("Real Type : %s\n", tmp->m_realType.get()); + printf("Real Encoding : %s\n", tmp->m_realEncoding.get()); + printf("Description : %s\n", tmp->m_description.get()); + printf("Mac Type : %s\n", tmp->m_xMacType.get()); + printf("Mac Creator : %s\n", tmp->m_xMacCreator.get()); + printf("Size in bytes : %d\n", tmp->m_size); + i++; + tmp++; + } +} +#endif + +nsresult CreateComposeParams(nsCOMPtr<nsIMsgComposeParams> &pMsgComposeParams, + nsIMsgCompFields * compFields, + nsMsgAttachmentData *attachmentList, + MSG_ComposeType composeType, + MSG_ComposeFormat composeFormat, + nsIMsgIdentity * identity, + const char *originalMsgURI, + nsIMsgDBHdr *origMsgHdr) +{ +#ifdef NS_DEBUG + mime_dump_attachments ( attachmentList ); +#endif + + nsresult rv; + nsMsgAttachmentData *curAttachment = attachmentList; + if (curAttachment) + { + nsAutoCString spec; + + while (curAttachment && curAttachment->m_url) + { + rv = curAttachment->m_url->GetSpec(spec); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && attachment) + { + nsAutoString nameStr; + rv = ConvertToUnicode("UTF-8", curAttachment->m_realName.get(), nameStr); + if (NS_FAILED(rv)) + CopyASCIItoUTF16(curAttachment->m_realName, nameStr); + attachment->SetName(nameStr); + attachment->SetUrl(spec); + attachment->SetTemporary(true); + attachment->SetContentType(curAttachment->m_realType.get()); + attachment->SetMacType(curAttachment->m_xMacType.get()); + attachment->SetMacCreator(curAttachment->m_xMacCreator.get()); + attachment->SetSize(curAttachment->m_size); + if (!curAttachment->m_cloudPartInfo.IsEmpty()) + { + nsCString provider; + nsCString cloudUrl; + attachment->SetSendViaCloud(true); + provider.Adopt( + MimeHeaders_get_parameter(curAttachment->m_cloudPartInfo.get(), + "provider", nullptr, nullptr)); + cloudUrl.Adopt( + MimeHeaders_get_parameter(curAttachment->m_cloudPartInfo.get(), + "url", nullptr, nullptr)); + attachment->SetCloudProviderKey(provider); + attachment->SetContentLocation(cloudUrl); + } + compFields->AddAttachment(attachment); + } + } + curAttachment++; + } + } + + MSG_ComposeFormat format = composeFormat; // Format to actually use. + if (identity && composeType == nsIMsgCompType::ForwardInline) + { + bool composeHtml = false; + identity->GetComposeHtml(&composeHtml); + if (composeHtml) + format = (composeFormat == nsIMsgCompFormat::OppositeOfDefault) ? + nsIMsgCompFormat::PlainText : nsIMsgCompFormat::HTML; + else + format = (composeFormat == nsIMsgCompFormat::OppositeOfDefault) ? + nsIMsgCompFormat::HTML : nsIMsgCompFormat::PlainText; + } + + pMsgComposeParams = do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + pMsgComposeParams->SetType(composeType); + pMsgComposeParams->SetFormat(format); + pMsgComposeParams->SetIdentity(identity); + pMsgComposeParams->SetComposeFields(compFields); + if (originalMsgURI) + pMsgComposeParams->SetOriginalMsgURI(originalMsgURI); + if (origMsgHdr) + pMsgComposeParams->SetOrigMsgHdr(origMsgHdr); + return NS_OK; +} + +nsresult +CreateTheComposeWindow(nsIMsgCompFields * compFields, + nsMsgAttachmentData *attachmentList, + MSG_ComposeType composeType, + MSG_ComposeFormat composeFormat, + nsIMsgIdentity * identity, + const char * originalMsgURI, + nsIMsgDBHdr * origMsgHdr + ) +{ + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams; + nsresult rv = CreateComposeParams(pMsgComposeParams, compFields, + attachmentList, + composeType, + composeFormat, + identity, + originalMsgURI, + origMsgHdr); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgComposeService> msgComposeService = + do_GetService(kCMsgComposeServiceCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return msgComposeService->OpenComposeWindowWithParams(nullptr /* default chrome */, pMsgComposeParams); +} + +nsresult +ForwardMsgInline(nsIMsgCompFields *compFields, + nsMsgAttachmentData *attachmentList, + MSG_ComposeFormat composeFormat, + nsIMsgIdentity *identity, + const char *originalMsgURI, + nsIMsgDBHdr *origMsgHdr) +{ + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams; + nsresult rv = CreateComposeParams(pMsgComposeParams, compFields, + attachmentList, + nsIMsgCompType::ForwardInline, + composeFormat, + identity, + originalMsgURI, + origMsgHdr); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgComposeService> msgComposeService = + do_GetService(kCMsgComposeServiceCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + // create the nsIMsgCompose object to send the object + nsCOMPtr<nsIMsgCompose> pMsgCompose (do_CreateInstance(NS_MSGCOMPOSE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + /** initialize nsIMsgCompose, Send the message, wait for send completion response **/ + rv = pMsgCompose->Initialize(pMsgComposeParams, nullptr, nullptr); + NS_ENSURE_SUCCESS(rv,rv); + + rv = pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, identity, nullptr, nullptr, nullptr); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgFolder> origFolder; + origMsgHdr->GetFolder(getter_AddRefs(origFolder)); + if (origFolder) + origFolder->AddMessageDispositionState( + origMsgHdr, nsIMsgFolder::nsMsgDispositionState_Forwarded); + } + return rv; +} + +nsresult +CreateCompositionFields(const char *from, + const char *reply_to, + const char *to, + const char *cc, + const char *bcc, + const char *fcc, + const char *newsgroups, + const char *followup_to, + const char *organization, + const char *subject, + const char *references, + const char *priority, + const char *newspost_url, + char *charset, + nsIMsgCompFields **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv; + *_retval = nullptr; + + nsCOMPtr<nsIMsgCompFields> cFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(cFields, NS_ERROR_OUT_OF_MEMORY); + + // Now set all of the passed in stuff... + cFields->SetCharacterSet(!PL_strcasecmp("us-ascii", charset) ? "ISO-8859-1" : charset); + + nsAutoCString val; + nsAutoString outString; + + if (from) { + ConvertRawBytesToUTF16(from, charset, outString); + cFields->SetFrom(outString); + } + + if (subject) { + MIME_DecodeMimeHeader(subject, charset, false, true, val); + cFields->SetSubject(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : subject)); + } + + if (reply_to) { + ConvertRawBytesToUTF16(reply_to, charset, outString); + cFields->SetReplyTo(outString); + } + + if (to) { + ConvertRawBytesToUTF16(to, charset, outString); + cFields->SetTo(outString); + } + + if (cc) { + ConvertRawBytesToUTF16(cc, charset, outString); + cFields->SetCc(outString); + } + + if (bcc) { + ConvertRawBytesToUTF16(bcc, charset, outString); + cFields->SetBcc(outString); + } + + if (fcc) { + MIME_DecodeMimeHeader(fcc, charset, false, true, val); + cFields->SetFcc(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : fcc)); + } + + if (newsgroups) { + // fixme: the newsgroups header had better be decoded using the server-side + // character encoding,but this |charset| might be different from it. + MIME_DecodeMimeHeader(newsgroups, charset, false, true, val); + cFields->SetNewsgroups(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : newsgroups)); + } + + if (followup_to) { + MIME_DecodeMimeHeader(followup_to, charset, false, true, val); + cFields->SetFollowupTo(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : followup_to)); + } + + if (organization) { + MIME_DecodeMimeHeader(organization, charset, false, true, val); + cFields->SetOrganization(NS_ConvertUTF8toUTF16(!val.IsEmpty() ? val.get() : organization)); + } + + if (references) { + MIME_DecodeMimeHeader(references, charset, false, true, val); + cFields->SetReferences(!val.IsEmpty() ? val.get() : references); + } + + if (priority) { + MIME_DecodeMimeHeader(priority, charset, false, true, val); + nsMsgPriorityValue priorityValue; + NS_MsgGetPriorityFromString(!val.IsEmpty() ? val.get() : priority, priorityValue); + nsAutoCString priorityName; + NS_MsgGetUntranslatedPriorityName(priorityValue, priorityName); + cFields->SetPriority(priorityName.get()); + } + + if (newspost_url) { + MIME_DecodeMimeHeader(newspost_url, charset, false, true, val); + cFields->SetNewspostUrl(!val.IsEmpty() ? val.get() : newspost_url); + } + + *_retval = cFields; + NS_IF_ADDREF(*_retval); + + return rv; +} + +static int +dummy_file_write( char *buf, int32_t size, void *fileHandle ) +{ + if (!fileHandle) + return -1; + + nsIOutputStream *tStream = (nsIOutputStream *) fileHandle; + uint32_t bytesWritten; + tStream->Write(buf, size, &bytesWritten); + return (int) bytesWritten; +} + +static int +mime_parse_stream_write ( nsMIMESession *stream, const char *buf, int32_t size ) +{ + mime_draft_data *mdd = (mime_draft_data *) stream->data_object; + NS_ASSERTION ( mdd, "null mime draft data!" ); + + if ( !mdd || !mdd->obj ) + return -1; + + return mdd->obj->clazz->parse_buffer ((char *) buf, size, mdd->obj); +} + +static void +mime_free_attachments(nsTArray<nsMsgAttachedFile *> &attachments) +{ + if (attachments.Length() <= 0) + return; + + for (uint32_t i = 0; i < attachments.Length(); i++) + { + if (attachments[i]->m_tmpFile) + { + attachments[i]->m_tmpFile->Remove(false); + attachments[i]->m_tmpFile = nullptr; + } + delete attachments[i]; + } +} + +static nsMsgAttachmentData * +mime_draft_process_attachments(mime_draft_data *mdd) +{ + if (!mdd) + return nullptr; + + nsMsgAttachmentData *attachData = NULL, *tmp = NULL; + nsMsgAttachedFile *tmpFile = NULL; + + //It's possible we must treat the message body as attachment! + bool bodyAsAttachment = false; + if (mdd->messageBody && + !mdd->messageBody->m_type.IsEmpty() && + mdd->messageBody->m_type.Find("text/html", CaseInsensitiveCompare) == -1 && + mdd->messageBody->m_type.Find("text/plain", CaseInsensitiveCompare) == -1 && + !mdd->messageBody->m_type.LowerCaseEqualsLiteral("text")) + bodyAsAttachment = true; + + if (!mdd->attachments.Length() && !bodyAsAttachment) + return nullptr; + + int32_t totalCount = mdd->attachments.Length(); + if (bodyAsAttachment) + totalCount++; + attachData = new nsMsgAttachmentData[totalCount + 1]; + if ( !attachData ) + return nullptr; + + tmp = attachData; + + for (int i = 0, attachmentsIndex = 0; i < totalCount; i++, tmp++) + { + if (bodyAsAttachment && i == 0) + tmpFile = mdd->messageBody; + else + tmpFile = mdd->attachments[attachmentsIndex++]; + + if (tmpFile->m_type.LowerCaseEqualsLiteral("text/x-vcard")) + tmp->m_realName = tmpFile->m_description; + + if ( tmpFile->m_origUrl ) + { + nsAutoCString tmpSpec; + if (NS_FAILED(tmpFile->m_origUrl->GetSpec(tmpSpec))) + goto FAIL; + + if (NS_FAILED(nsMimeNewURI(getter_AddRefs(tmp->m_url), tmpSpec.get(), nullptr))) + goto FAIL; + + if (tmp->m_realName.IsEmpty()) + { + if (!tmpFile->m_realName.IsEmpty()) + tmp->m_realName = tmpFile->m_realName; + else { + if (tmpFile->m_type.Find(MESSAGE_RFC822, CaseInsensitiveCompare) != -1) + // we have the odd case of processing an e-mail that had an unnamed + // eml message attached + tmp->m_realName = "ForwardedMessage.eml"; + + else + tmp->m_realName = tmpSpec.get(); + } + } + } + + tmp->m_desiredType = tmpFile->m_type; + tmp->m_realType = tmpFile->m_type; + tmp->m_realEncoding = tmpFile->m_encoding; + tmp->m_description = tmpFile->m_description; + tmp->m_cloudPartInfo = tmpFile->m_cloudPartInfo; + tmp->m_xMacType = tmpFile->m_xMacType; + tmp->m_xMacCreator = tmpFile->m_xMacCreator; + tmp->m_size = tmpFile->m_size; + } + return attachData; + +FAIL: + delete [] attachData; + return nullptr; +} + +static void +mime_intl_insert_message_header_1(char **body, + const char *hdr_value, + const char *hdr_str, + const char *html_hdr_str, + const char *mailcharset, + bool htmlEdit) +{ + if (!body || !hdr_value || !hdr_str) + return; + + if (htmlEdit) + { + NS_MsgSACat(body, HEADER_START_JUNK); + } + else + { + NS_MsgSACat(body, MSG_LINEBREAK); + } + if (!html_hdr_str) + html_hdr_str = hdr_str; + NS_MsgSACat(body, html_hdr_str); + if (htmlEdit) + { + NS_MsgSACat(body, HEADER_MIDDLE_JUNK); + } + else + NS_MsgSACat(body, ": "); + + // MIME decode header + nsAutoCString utf8Value; + MIME_DecodeMimeHeader(hdr_value, mailcharset, false, true, utf8Value); + if (!utf8Value.IsEmpty()) { + char *escaped = nullptr; + if (htmlEdit) + escaped = MsgEscapeHTML(utf8Value.get()); + NS_MsgSACat(body, escaped ? escaped : utf8Value.get()); + NS_Free(escaped); + } else { + NS_MsgSACat(body, hdr_value); // raw MIME encoded string + } + + if (htmlEdit) + NS_MsgSACat(body, HEADER_END_JUNK); +} + +char * +MimeGetNamedString(int32_t id) +{ + static char retString[256]; + + retString[0] = '\0'; + char *tString = MimeGetStringByID(id); + if (tString) + { + PL_strncpy(retString, tString, sizeof(retString)); + PR_Free(tString); + } + return retString; +} + +void +MimeGetForwardHeaderDelimiter(nsACString &retString) +{ + nsCString defaultValue; + defaultValue.Adopt(MimeGetStringByID(MIME_FORWARDED_MESSAGE_HTML_USER_WROTE)); + + nsString tmpRetString; + NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, + "mailnews.forward_header_originalmessage", + NS_ConvertUTF8toUTF16(defaultValue), + tmpRetString); + + CopyUTF16toUTF8(tmpRetString, retString); +} + +/* given an address string passed though parameter "address", this one will be converted + and returned through the same parameter. The original string will be destroyed +*/ +static void UnquoteMimeAddress(nsACString &mimeHeader, const char *charset) +{ + if (!mimeHeader.IsEmpty()) + { + nsTArray<nsCString> addresses; + ExtractDisplayAddresses(EncodedHeader(mimeHeader, charset), + UTF16ArrayAdapter<>(addresses)); + mimeHeader.Truncate(); + + uint32_t count = addresses.Length(); + for (uint32_t i = 0; i < count; i++) + { + if (i != 0) + mimeHeader.AppendASCII(", "); + mimeHeader += addresses[i]; + } + } +} + +static void +mime_insert_all_headers(char **body, + MimeHeaders *headers, + MSG_ComposeFormat composeFormat, + char *mailcharset) +{ + bool htmlEdit = (composeFormat == nsIMsgCompFormat::HTML); + char *newBody = NULL; + char *html_tag = nullptr; + if (*body && PL_strncasecmp(*body, "<HTML", 5) == 0) + html_tag = PL_strchr(*body, '>') + 1; + int i; + + if (!headers->done_p) + { + MimeHeaders_build_heads_list(headers); + headers->done_p = true; + } + + nsCString replyHeader; + MimeGetForwardHeaderDelimiter(replyHeader); + if (htmlEdit) + { + NS_MsgSACopy(&(newBody), MIME_FORWARD_HTML_PREFIX); + NS_MsgSACat(&newBody, replyHeader.get()); + NS_MsgSACat(&newBody, MIME_HEADER_TABLE); + } + else + { + NS_MsgSACopy(&(newBody), MSG_LINEBREAK MSG_LINEBREAK); + NS_MsgSACat(&newBody, replyHeader.get()); + } + + for (i = 0; i < headers->heads_size; i++) + { + char *head = headers->heads[i]; + char *end = (i == headers->heads_size-1 + ? headers->all_headers + headers->all_headers_fp + : headers->heads[i+1]); + char *colon, *ocolon; + char *contents; + char *name = 0; + + // Hack for BSD Mailbox delimiter. + if (i == 0 && head[0] == 'F' && !strncmp(head, "From ", 5)) + { + colon = head + 4; + contents = colon + 1; + } + else + { + /* Find the colon. */ + for (colon = head; colon < end; colon++) + if (*colon == ':') break; + + if (colon >= end) continue; /* junk */ + + /* Back up over whitespace before the colon. */ + ocolon = colon; + for (; colon > head && IS_SPACE(colon[-1]); colon--) + ; + + contents = ocolon + 1; + } + + /* Skip over whitespace after colon. */ + while (contents <= end && IS_SPACE(*contents)) + contents++; + + /* Take off trailing whitespace... */ + while (end > contents && IS_SPACE(end[-1])) + end--; + + name = (char *)PR_MALLOC(colon - head + 1); + if (!name) + return /* MIME_OUT_OF_MEMORY */; + memcpy(name, head, colon - head); + name[colon - head] = 0; + + nsAutoCString headerValue; + headerValue.Assign(contents, end - contents); + + /* Do not reveal bcc recipients when forwarding a message! + See http://bugzilla.mozilla.org/show_bug.cgi?id=41150 + */ + if (PL_strcasecmp(name, "bcc") != 0) + { + if (!PL_strcasecmp(name, "resent-from") || !PL_strcasecmp(name, "from") || + !PL_strcasecmp(name, "resent-to") || !PL_strcasecmp(name, "to") || + !PL_strcasecmp(name, "resent-cc") || !PL_strcasecmp(name, "cc") || + !PL_strcasecmp(name, "reply-to")) + UnquoteMimeAddress(headerValue, mailcharset); + + mime_intl_insert_message_header_1(&newBody, headerValue.get(), name, name, + mailcharset, htmlEdit); + } + PR_Free(name); + } + + if (htmlEdit) + { + NS_MsgSACat(&newBody, "</TABLE>"); + NS_MsgSACat(&newBody, MSG_LINEBREAK "<BR><BR>"); + if (html_tag) + NS_MsgSACat(&newBody, html_tag); + else if (*body) + NS_MsgSACat(&newBody, *body); + } + else + { + NS_MsgSACat(&newBody, MSG_LINEBREAK MSG_LINEBREAK); + if (*body) + NS_MsgSACat(&newBody, *body); + } + + if (newBody) + { + PR_FREEIF(*body); + *body = newBody; + } +} + +static void +mime_insert_normal_headers(char **body, + MimeHeaders *headers, + MSG_ComposeFormat composeFormat, + char *mailcharset) +{ + char *newBody = nullptr; + char *subject = MimeHeaders_get(headers, HEADER_SUBJECT, false, false); + char *resent_comments = MimeHeaders_get(headers, HEADER_RESENT_COMMENTS, false, false); + char *resent_date = MimeHeaders_get(headers, HEADER_RESENT_DATE, false, true); + nsCString resent_from(MimeHeaders_get(headers, HEADER_RESENT_FROM, false, true)); + nsCString resent_to(MimeHeaders_get(headers, HEADER_RESENT_TO, false, true)); + nsCString resent_cc(MimeHeaders_get(headers, HEADER_RESENT_CC, false, true)); + char *date = MimeHeaders_get(headers, HEADER_DATE, false, true); + nsCString from(MimeHeaders_get(headers, HEADER_FROM, false, true)); + nsCString reply_to(MimeHeaders_get(headers, HEADER_REPLY_TO, false, true)); + char *organization = MimeHeaders_get(headers, HEADER_ORGANIZATION, false, false); + nsCString to(MimeHeaders_get(headers, HEADER_TO, false, true)); + nsCString cc(MimeHeaders_get(headers, HEADER_CC, false, true)); + char *newsgroups = MimeHeaders_get(headers, HEADER_NEWSGROUPS, false, true); + char *followup_to = MimeHeaders_get(headers, HEADER_FOLLOWUP_TO, false, true); + char *references = MimeHeaders_get(headers, HEADER_REFERENCES, false, true); + const char *html_tag = nullptr; + if (*body && PL_strncasecmp(*body, "<HTML", 5) == 0) + html_tag = PL_strchr(*body, '>') + 1; + bool htmlEdit = composeFormat == nsIMsgCompFormat::HTML; + + if (from.IsEmpty()) + from.Adopt(MimeHeaders_get(headers, HEADER_SENDER, false, true)); + if (resent_from.IsEmpty()) + resent_from.Adopt(MimeHeaders_get(headers, HEADER_RESENT_SENDER, false, + true)); + + UnquoteMimeAddress(resent_from, mailcharset); + UnquoteMimeAddress(resent_to, mailcharset); + UnquoteMimeAddress(resent_cc, mailcharset); + UnquoteMimeAddress(reply_to, mailcharset); + UnquoteMimeAddress(from, mailcharset); + UnquoteMimeAddress(to, mailcharset); + UnquoteMimeAddress(cc, mailcharset); + + nsCString replyHeader; + MimeGetForwardHeaderDelimiter(replyHeader); + if (htmlEdit) + { + NS_MsgSACopy(&(newBody), MIME_FORWARD_HTML_PREFIX); + NS_MsgSACat(&newBody, replyHeader.get()); + NS_MsgSACat(&newBody, MIME_HEADER_TABLE); + } + else + { + NS_MsgSACopy(&(newBody), MSG_LINEBREAK MSG_LINEBREAK); + NS_MsgSACat(&newBody, replyHeader.get()); + } + if (subject) + mime_intl_insert_message_header_1(&newBody, subject, HEADER_SUBJECT, + MimeGetNamedString(MIME_MHTML_SUBJECT), + mailcharset, htmlEdit); + if (resent_comments) + mime_intl_insert_message_header_1(&newBody, resent_comments, + HEADER_RESENT_COMMENTS, + MimeGetNamedString(MIME_MHTML_RESENT_COMMENTS), + mailcharset, htmlEdit); + if (resent_date) + mime_intl_insert_message_header_1(&newBody, resent_date, + HEADER_RESENT_DATE, + MimeGetNamedString(MIME_MHTML_RESENT_DATE), + mailcharset, htmlEdit); + if (!resent_from.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, resent_from.get(), + HEADER_RESENT_FROM, + MimeGetNamedString(MIME_MHTML_RESENT_FROM), + mailcharset, htmlEdit); + } + if (!resent_to.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, resent_to.get(), + HEADER_RESENT_TO, + MimeGetNamedString(MIME_MHTML_RESENT_TO), + mailcharset, htmlEdit); + } + if (!resent_cc.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, resent_cc.get(), + HEADER_RESENT_CC, + MimeGetNamedString(MIME_MHTML_RESENT_CC), + mailcharset, htmlEdit); + } + if (date) + mime_intl_insert_message_header_1(&newBody, date, HEADER_DATE, + MimeGetNamedString(MIME_MHTML_DATE), + mailcharset, htmlEdit); + if (!from.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, from.get(), HEADER_FROM, + MimeGetNamedString(MIME_MHTML_FROM), + mailcharset, htmlEdit); + } + if (!reply_to.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, reply_to.get(), HEADER_REPLY_TO, + MimeGetNamedString(MIME_MHTML_REPLY_TO), + mailcharset, htmlEdit); + } + if (organization) + mime_intl_insert_message_header_1(&newBody, organization, + HEADER_ORGANIZATION, + MimeGetNamedString(MIME_MHTML_ORGANIZATION), + mailcharset, htmlEdit); + if (!to.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, to.get(), HEADER_TO, + MimeGetNamedString(MIME_MHTML_TO), + mailcharset, htmlEdit); + } + if (!cc.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, cc.get(), HEADER_CC, + MimeGetNamedString(MIME_MHTML_CC), + mailcharset, htmlEdit); + } + /* + Do not reveal bcc recipients when forwarding a message! + See http://bugzilla.mozilla.org/show_bug.cgi?id=41150 + */ + if (newsgroups) + mime_intl_insert_message_header_1(&newBody, newsgroups, HEADER_NEWSGROUPS, + MimeGetNamedString(MIME_MHTML_NEWSGROUPS), + mailcharset, htmlEdit); + if (followup_to) + { + mime_intl_insert_message_header_1(&newBody, followup_to, + HEADER_FOLLOWUP_TO, + MimeGetNamedString(MIME_MHTML_FOLLOWUP_TO), + mailcharset, htmlEdit); + } + // only show references for newsgroups + if (newsgroups && references) + { + mime_intl_insert_message_header_1(&newBody, references, + HEADER_REFERENCES, + MimeGetNamedString(MIME_MHTML_REFERENCES), + mailcharset, htmlEdit); + } + if (htmlEdit) + { + NS_MsgSACat(&newBody, "</TABLE>"); + NS_MsgSACat(&newBody, MSG_LINEBREAK "<BR><BR>"); + if (html_tag) + NS_MsgSACat(&newBody, html_tag); + else if (*body) + NS_MsgSACat(&newBody, *body); + } + else + { + NS_MsgSACat(&newBody, MSG_LINEBREAK MSG_LINEBREAK); + if (*body) + NS_MsgSACat(&newBody, *body); + } + if (newBody) + { + PR_FREEIF(*body); + *body = newBody; + } + PR_FREEIF(subject); + PR_FREEIF(resent_comments); + PR_FREEIF(resent_date); + PR_FREEIF(date); + PR_FREEIF(organization); + PR_FREEIF(newsgroups); + PR_FREEIF(followup_to); + PR_FREEIF(references); +} + +static void +mime_insert_micro_headers(char **body, + MimeHeaders *headers, + MSG_ComposeFormat composeFormat, + char *mailcharset) +{ + char *newBody = NULL; + char *subject = MimeHeaders_get(headers, HEADER_SUBJECT, false, false); + nsCString from(MimeHeaders_get(headers, HEADER_FROM, false, true)); + nsCString resent_from(MimeHeaders_get(headers, HEADER_RESENT_FROM, false, true)); + char *date = MimeHeaders_get(headers, HEADER_DATE, false, true); + nsCString to(MimeHeaders_get(headers, HEADER_TO, false, true)); + nsCString cc(MimeHeaders_get(headers, HEADER_CC, false, true)); + char *newsgroups = MimeHeaders_get(headers, HEADER_NEWSGROUPS, false, + true); + const char *html_tag = nullptr; + if (*body && PL_strncasecmp(*body, "<HTML", 5) == 0) + html_tag = PL_strchr(*body, '>') + 1; + bool htmlEdit = composeFormat == nsIMsgCompFormat::HTML; + + if (from.IsEmpty()) + from.Adopt(MimeHeaders_get(headers, HEADER_SENDER, false, true)); + if (resent_from.IsEmpty()) + resent_from.Adopt(MimeHeaders_get(headers, HEADER_RESENT_SENDER, false, true)); + if (!date) + date = MimeHeaders_get(headers, HEADER_RESENT_DATE, false, true); + + UnquoteMimeAddress(resent_from, mailcharset); + UnquoteMimeAddress(from, mailcharset); + UnquoteMimeAddress(to, mailcharset); + UnquoteMimeAddress(cc, mailcharset); + + nsCString replyHeader; + MimeGetForwardHeaderDelimiter(replyHeader); + if (htmlEdit) + { + NS_MsgSACopy(&(newBody), MIME_FORWARD_HTML_PREFIX); + NS_MsgSACat(&newBody, replyHeader.get()); + NS_MsgSACat(&newBody, MIME_HEADER_TABLE); + } + else + { + NS_MsgSACopy(&(newBody), MSG_LINEBREAK MSG_LINEBREAK); + NS_MsgSACat(&newBody, replyHeader.get()); + } + + if (!from.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, from.get(), HEADER_FROM, + MimeGetNamedString(MIME_MHTML_FROM), + mailcharset, htmlEdit); + } + if (subject) + mime_intl_insert_message_header_1(&newBody, subject, HEADER_SUBJECT, + MimeGetNamedString(MIME_MHTML_SUBJECT), + mailcharset, htmlEdit); +/* + if (date) + mime_intl_insert_message_header_1(&newBody, date, HEADER_DATE, + MimeGetNamedString(MIME_MHTML_DATE), + mailcharset, htmlEdit); +*/ + if (!resent_from.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, resent_from.get(), + HEADER_RESENT_FROM, + MimeGetNamedString(MIME_MHTML_RESENT_FROM), + mailcharset, htmlEdit); + } + if (!to.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, to.get(), HEADER_TO, + MimeGetNamedString(MIME_MHTML_TO), + mailcharset, htmlEdit); + } + if (!cc.IsEmpty()) + { + mime_intl_insert_message_header_1(&newBody, cc.get(), HEADER_CC, + MimeGetNamedString(MIME_MHTML_CC), + mailcharset, htmlEdit); + } + /* + Do not reveal bcc recipients when forwarding a message! + See http://bugzilla.mozilla.org/show_bug.cgi?id=41150 + */ + if (newsgroups) + mime_intl_insert_message_header_1(&newBody, newsgroups, HEADER_NEWSGROUPS, + MimeGetNamedString(MIME_MHTML_NEWSGROUPS), + mailcharset, htmlEdit); + if (htmlEdit) + { + NS_MsgSACat(&newBody, "</TABLE>"); + NS_MsgSACat(&newBody, MSG_LINEBREAK "<BR><BR>"); + if (html_tag) + NS_MsgSACat(&newBody, html_tag); + else if (*body) + NS_MsgSACat(&newBody, *body); + } + else + { + NS_MsgSACat(&newBody, MSG_LINEBREAK MSG_LINEBREAK); + if (*body) + NS_MsgSACat(&newBody, *body); + } + if (newBody) + { + PR_FREEIF(*body); + *body = newBody; + } + PR_FREEIF(subject); + PR_FREEIF(date); + PR_FREEIF(newsgroups); + +} + +// body has to be encoded in UTF-8 +static void +mime_insert_forwarded_message_headers(char **body, + MimeHeaders *headers, + MSG_ComposeFormat composeFormat, + char *mailcharset) +{ + if (!body || !headers) + return; + + int32_t show_headers = 0; + nsresult res; + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &res)); + if (NS_SUCCEEDED(res)) + prefBranch->GetIntPref("mail.show_headers", &show_headers); + + switch (show_headers) + { + case 0: + mime_insert_micro_headers(body, headers, composeFormat, mailcharset); + break; + default: + case 1: + mime_insert_normal_headers(body, headers, composeFormat, mailcharset); + break; + case 2: + mime_insert_all_headers(body, headers, composeFormat, mailcharset); + break; + } +} + +static void +mime_parse_stream_complete (nsMIMESession *stream) +{ + mime_draft_data *mdd = (mime_draft_data *) stream->data_object; + nsCOMPtr<nsIMsgCompFields> fields; + int htmlAction = 0; + int lineWidth = 0; + + char *host = 0; + char *news_host = 0; + char *to_and_cc = 0; + char *re_subject = 0; + char *new_refs = 0; + char *from = 0; + char *repl = 0; + char *subj = 0; + char *id = 0; + char *refs = 0; + char *to = 0; + char *cc = 0; + char *bcc = 0; + char *fcc = 0; + char *org = 0; + char *grps = 0; + char *foll = 0; + char *priority = 0; + char *draftInfo = 0; + char *contentLanguage = 0; + char *identityKey = 0; + + bool forward_inline = false; + bool bodyAsAttachment = false; + bool charsetOverride = false; + + NS_ASSERTION (mdd, "null mime draft data"); + + if (!mdd) return; + + if (mdd->obj) + { + int status; + + status = mdd->obj->clazz->parse_eof ( mdd->obj, false ); + mdd->obj->clazz->parse_end( mdd->obj, status < 0 ? true : false ); + + // RICHIE + // We need to figure out how to pass the forwarded flag along with this + // operation. + + //forward_inline = (mdd->format_out != FO_CMDLINE_ATTACHMENTS); + forward_inline = mdd->forwardInline; + + NS_ASSERTION ( mdd->options == mdd->obj->options, "mime draft options not same as obj->options" ); + mime_free (mdd->obj); + mdd->obj = 0; + if (mdd->options) + { + // save the override flag before it's unavailable + charsetOverride = mdd->options->override_charset; + if ((!mdd->mailcharset || charsetOverride) && mdd->options->default_charset) + { + PR_Free(mdd->mailcharset); + mdd->mailcharset = strdup(mdd->options->default_charset); + } + + // mscott: aren't we leaking a bunch of strings here like the charset strings and such? + delete mdd->options; + mdd->options = 0; + } + if (mdd->stream) + { + mdd->stream->complete ((nsMIMESession *)mdd->stream->data_object); + PR_Free( mdd->stream ); + mdd->stream = 0; + } + } + + // + // Now, process the attachments that we have gathered from the message + // on disk + // + nsMsgAttachmentData *newAttachData = mime_draft_process_attachments(mdd); + + // + // time to bring up the compose windows with all the info gathered + // + if ( mdd->headers ) + { + subj = MimeHeaders_get(mdd->headers, HEADER_SUBJECT, false, false); + if (forward_inline) + { + if (subj) + { + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsAutoCString fwdPrefix; + prefBranch->GetCharPref("mail.forward_subject_prefix", + getter_Copies(fwdPrefix)); + char *newSubj = PR_smprintf("%s: %s", !fwdPrefix.IsEmpty() ? + fwdPrefix.get(): "Fwd", subj); + if (newSubj) + { + PR_Free(subj); + subj = newSubj; + } + } + } + } + else + { + from = MimeHeaders_get(mdd->headers, HEADER_FROM, false, false); + repl = MimeHeaders_get(mdd->headers, HEADER_REPLY_TO, false, false); + to = MimeHeaders_get(mdd->headers, HEADER_TO, false, true); + cc = MimeHeaders_get(mdd->headers, HEADER_CC, false, true); + bcc = MimeHeaders_get(mdd->headers, HEADER_BCC, false, true); + + /* These headers should not be RFC-1522-decoded. */ + grps = MimeHeaders_get(mdd->headers, HEADER_NEWSGROUPS, false, true); + foll = MimeHeaders_get(mdd->headers, HEADER_FOLLOWUP_TO, false, true); + + host = MimeHeaders_get(mdd->headers, HEADER_X_MOZILLA_NEWSHOST, false, false); + if (!host) + host = MimeHeaders_get(mdd->headers, HEADER_NNTP_POSTING_HOST, false, false); + + id = MimeHeaders_get(mdd->headers, HEADER_MESSAGE_ID, false, false); + refs = MimeHeaders_get(mdd->headers, HEADER_REFERENCES, false, true); + priority = MimeHeaders_get(mdd->headers, HEADER_X_PRIORITY, false, false); + + + if (host) + { + char *secure = NULL; + + secure = PL_strcasestr(host, "secure"); + if (secure) + { + *secure = 0; + news_host = PR_smprintf ("snews://%s", host); + } + else + { + news_host = PR_smprintf ("news://%s", host); + } + } + } + + + CreateCompositionFields( from, repl, to, cc, bcc, fcc, grps, foll, + org, subj, refs, priority, news_host, + mdd->mailcharset, + getter_AddRefs(fields)); + + contentLanguage = MimeHeaders_get(mdd->headers, HEADER_CONTENT_LANGUAGE, false, false); + if (contentLanguage) { + fields->SetContentLanguage(contentLanguage); + } + + draftInfo = MimeHeaders_get(mdd->headers, HEADER_X_MOZILLA_DRAFT_INFO, false, false); + + // Keep the same message id when editing a draft unless we're + // editing a message "as new message" (template) or forwarding inline. + if (mdd->format_out != nsMimeOutput::nsMimeMessageEditorTemplate && + fields && !forward_inline) { + fields->SetMessageId(id); + } + + if (draftInfo && fields && !forward_inline) + { + char *parm = 0; + parm = MimeHeaders_get_parameter(draftInfo, "vcard", NULL, NULL); + fields->SetAttachVCard(parm && !strcmp(parm, "1")); + PR_FREEIF(parm); + + parm = MimeHeaders_get_parameter(draftInfo, "receipt", NULL, NULL); + if (!parm || !strcmp(parm, "0")) + fields->SetReturnReceipt(false); + else + { + int receiptType = 0; + fields->SetReturnReceipt(true); + sscanf(parm, "%d", &receiptType); + // slight change compared to 4.x; we used to use receipt= to tell + // whether the draft/template has request for either MDN or DNS or both + // return receipt; since the DNS is out of the picture we now use the + // header type - 1 to tell whether user has requested the return receipt + fields->SetReceiptHeaderType(((int32_t)receiptType) - 1); + } + PR_FREEIF(parm); + parm = MimeHeaders_get_parameter(draftInfo, "DSN", NULL, NULL); + fields->SetDSN(parm && !strcmp(parm, "1")); + PR_Free(parm); + parm = MimeHeaders_get_parameter(draftInfo, "html", NULL, NULL); + if (parm) + sscanf(parm, "%d", &htmlAction); + PR_FREEIF(parm); + parm = MimeHeaders_get_parameter(draftInfo, "linewidth", NULL, NULL); + if (parm) + sscanf(parm, "%d", &lineWidth); + PR_FREEIF(parm); + parm = MimeHeaders_get_parameter(draftInfo, "attachmentreminder", NULL, NULL); + if (parm && !strcmp(parm, "1")) + fields->SetAttachmentReminder(true); + else + fields->SetAttachmentReminder(false); + PR_FREEIF(parm); + parm = MimeHeaders_get_parameter(draftInfo, "deliveryformat", NULL, NULL); + if (parm) { + int32_t deliveryFormat = nsIMsgCompSendFormat::AskUser; + sscanf(parm, "%d", &deliveryFormat); + fields->SetDeliveryFormat(deliveryFormat); + } + PR_FREEIF(parm); + } + + // identity to prefer when opening the message in the compose window? + identityKey = MimeHeaders_get(mdd->headers, HEADER_X_MOZILLA_IDENTITY_KEY, false, false); + if ( identityKey && *identityKey ) + { + nsresult rv = NS_OK; + nsCOMPtr< nsIMsgAccountManager > accountManager = + do_GetService( NS_MSGACCOUNTMANAGER_CONTRACTID, &rv ); + if ( NS_SUCCEEDED(rv) && accountManager ) + { + nsCOMPtr< nsIMsgIdentity > overrulingIdentity; + rv = accountManager->GetIdentity( nsDependentCString(identityKey), getter_AddRefs( overrulingIdentity ) ); + + if (NS_SUCCEEDED(rv) && overrulingIdentity) { + mdd->identity = overrulingIdentity; + fields->SetCreatorIdentityKey(identityKey); + } + } + } + + if (mdd->messageBody) + { + MSG_ComposeFormat composeFormat = nsIMsgCompFormat::Default; + if (!mdd->messageBody->m_type.IsEmpty()) + { + if(mdd->messageBody->m_type.Find("text/html", CaseInsensitiveCompare) != -1) + composeFormat = nsIMsgCompFormat::HTML; + else if (mdd->messageBody->m_type.Find("text/plain", CaseInsensitiveCompare) != -1 || + mdd->messageBody->m_type.LowerCaseEqualsLiteral("text")) + composeFormat = nsIMsgCompFormat::PlainText; + else + //We cannot use this kind of data for the message body! Therefore, move it as attachment + bodyAsAttachment = true; + } + else + composeFormat = nsIMsgCompFormat::PlainText; + + char *body = nullptr; + uint32_t bodyLen = 0; + + if (!bodyAsAttachment && mdd->messageBody->m_tmpFile) + { + int64_t fileSize; + nsCOMPtr<nsIFile> tempFileCopy; + mdd->messageBody->m_tmpFile->Clone(getter_AddRefs(tempFileCopy)); + mdd->messageBody->m_tmpFile = do_QueryInterface(tempFileCopy); + tempFileCopy = nullptr; + mdd->messageBody->m_tmpFile->GetFileSize(&fileSize); + + // The stream interface can only read up to 4GB (32bit uint). + // It is highly unlikely to encounter a body lager than that limit, + // so we just skip it instead of reading it in chunks. + if (fileSize < UINT32_MAX) + { + bodyLen = fileSize; + body = (char *)PR_MALLOC(bodyLen + 1); + } + if (body) + { + memset (body, 0, bodyLen+1); + + uint32_t bytesRead; + nsCOMPtr <nsIInputStream> inputStream; + + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mdd->messageBody->m_tmpFile); + if (NS_FAILED(rv)) + return; + + inputStream->Read(body, bodyLen, &bytesRead); + + inputStream->Close(); + + // Convert the body to UTF-8 + char *mimeCharset = nullptr; + // Get a charset from the header if no override is set. + if (!charsetOverride) + mimeCharset = MimeHeaders_get_parameter (mdd->messageBody->m_type.get(), "charset", nullptr, nullptr); + // If no charset is specified in the header then use the default. + char *bodyCharset = mimeCharset ? mimeCharset : mdd->mailcharset; + if (bodyCharset) + { + nsAutoString tmpUnicodeBody; + rv = ConvertToUnicode(bodyCharset, body, tmpUnicodeBody); + if (NS_FAILED(rv)) // Tough luck, ASCII/ISO-8859-1 then... + CopyASCIItoUTF16(nsDependentCString(body), tmpUnicodeBody); + + char *newBody = ToNewUTF8String(tmpUnicodeBody); + if (newBody) + { + PR_Free(body); + body = newBody; + } + } + PR_FREEIF(mimeCharset); + } + } + + bool convertToPlainText = false; + if (forward_inline) + { + if (mdd->identity) + { + bool identityComposeHTML; + mdd->identity->GetComposeHtml(&identityComposeHTML); + if ((identityComposeHTML && !mdd->overrideComposeFormat) || + (!identityComposeHTML && mdd->overrideComposeFormat)) + { + // In the end, we're going to compose in HTML mode... + + if (body && composeFormat == nsIMsgCompFormat::PlainText) + { + // ... but the message body is currently plain text. + + //We need to convert the plain/text to HTML in order to escape any HTML markup + char *escapedBody = MsgEscapeHTML(body); + if (escapedBody) + { + PR_Free(body); + body = escapedBody; + bodyLen = strlen(body); + } + + //+13 chars for <pre> & </pre> tags and CRLF + uint32_t newbodylen = bodyLen + 14; + char* newbody = (char *)PR_MALLOC (newbodylen); + if (newbody) + { + *newbody = 0; + PL_strcatn(newbody, newbodylen, "<PRE>"); + PL_strcatn(newbody, newbodylen, body); + PL_strcatn(newbody, newbodylen, "</PRE>" CRLF); + PR_Free(body); + body = newbody; + } + } + // Body is now HTML, set the format too (so headers are inserted in + // correct format). + composeFormat = nsIMsgCompFormat::HTML; + } + else if ((identityComposeHTML && mdd->overrideComposeFormat) || !identityComposeHTML) + { + // In the end, we're going to compose in plain text mode... + + if (composeFormat == nsIMsgCompFormat::HTML) + { + // ... but the message body is currently HTML. + // We'll do the conversion later on when headers have been + // inserted, body has been set and converted to unicode. + convertToPlainText = true; + } + } + } + + mime_insert_forwarded_message_headers(&body, mdd->headers, composeFormat, + mdd->mailcharset); + + } + + // convert from UTF-8 to UTF-16 + if (body) + { + fields->SetBody(NS_ConvertUTF8toUTF16(body)); + PR_Free(body); + } + + // + // At this point, we need to create a message compose window or editor + // window via XP-COM with the information that we have retrieved from + // the message store. + // + if (mdd->format_out == nsMimeOutput::nsMimeMessageEditorTemplate) + { + MSG_ComposeType msgComposeType = PL_strstr(mdd->url_name, + "&redirect=true") ? + nsIMsgCompType::Redirect : + nsIMsgCompType::Template; + CreateTheComposeWindow(fields, newAttachData, msgComposeType, + composeFormat, mdd->identity, + mdd->originalMsgURI, mdd->origMsgHdr); + } + else + { + if (mdd->forwardInline) + { + if (convertToPlainText) + fields->ConvertBodyToPlainText(); + if (mdd->overrideComposeFormat) + composeFormat = nsIMsgCompFormat::OppositeOfDefault; + if (mdd->forwardInlineFilter) + { + fields->SetTo(mdd->forwardToAddress); + ForwardMsgInline(fields, newAttachData, composeFormat, + mdd->identity, mdd->originalMsgURI, + mdd->origMsgHdr); + } + else + CreateTheComposeWindow(fields, newAttachData, + nsIMsgCompType::ForwardInline, composeFormat, + mdd->identity, mdd->originalMsgURI, + mdd->origMsgHdr); + } + else + { + fields->SetDraftId(mdd->url_name); + CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::Draft, composeFormat, mdd->identity, mdd->originalMsgURI, mdd->origMsgHdr); + } + } + } + else + { + // + // At this point, we need to create a message compose window via + // XP-COM with the information that we have retrieved from the message store. + // + if (mdd->format_out == nsMimeOutput::nsMimeMessageEditorTemplate) + { +#ifdef NS_DEBUG + printf("RICHIE: Time to create the EDITOR with this template - NO body!!!!\n"); +#endif + CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::Template, nsIMsgCompFormat::Default, mdd->identity, nullptr, mdd->origMsgHdr); + } + else + { +#ifdef NS_DEBUG + printf("Time to create the composition window WITHOUT a body!!!!\n"); +#endif + if (mdd->forwardInline) + { + MSG_ComposeFormat composeFormat = (mdd->overrideComposeFormat) ? + nsIMsgCompFormat::OppositeOfDefault : nsIMsgCompFormat::Default; + CreateTheComposeWindow(fields, newAttachData, + nsIMsgCompType::ForwardInline, composeFormat, + mdd->identity, mdd->originalMsgURI, + mdd->origMsgHdr); + } + else + { + fields->SetDraftId(mdd->url_name); + CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::Draft, nsIMsgCompFormat::Default, mdd->identity, nullptr, mdd->origMsgHdr); + } + } + } + } + else + { + CreateCompositionFields( from, repl, to, cc, bcc, fcc, grps, foll, + org, subj, refs, priority, news_host, + mdd->mailcharset, + getter_AddRefs(fields)); + if (fields) + CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::New, nsIMsgCompFormat::Default, mdd->identity, nullptr, mdd->origMsgHdr); + } + + if ( mdd->headers ) + MimeHeaders_free ( mdd->headers ); + + // + // Free the original attachment structure... + // Make sure we only cleanup the local copy of the memory and not kill + // files we need on disk + // + if (bodyAsAttachment) + mdd->messageBody->m_tmpFile = nullptr; + else if (mdd->messageBody && mdd->messageBody->m_tmpFile) + mdd->messageBody->m_tmpFile->Remove(false); + + delete mdd->messageBody; + + for (uint32_t i = 0; i < mdd->attachments.Length(); i++) + mdd->attachments[i]->m_tmpFile = nullptr; + + PR_FREEIF(mdd->mailcharset); + + mdd->identity = nullptr; + PR_Free(mdd->url_name); + PR_Free(mdd->originalMsgURI); + mdd->origMsgHdr = nullptr; + PR_Free(mdd); + + PR_FREEIF(host); + PR_FREEIF(to_and_cc); + PR_FREEIF(re_subject); + PR_FREEIF(new_refs); + PR_FREEIF(from); + PR_FREEIF(repl); + PR_FREEIF(subj); + PR_FREEIF(id); + PR_FREEIF(refs); + PR_FREEIF(to); + PR_FREEIF(cc); + PR_FREEIF(grps); + PR_FREEIF(foll); + PR_FREEIF(priority); + PR_FREEIF(draftInfo); + PR_Free(identityKey); + + delete [] newAttachData; +} + +static void +mime_parse_stream_abort (nsMIMESession *stream, int status ) +{ + mime_draft_data *mdd = (mime_draft_data *) stream->data_object; + NS_ASSERTION (mdd, "null mime draft data"); + + if (!mdd) + return; + + if (mdd->obj) + { + int status=0; + + if ( !mdd->obj->closed_p ) + status = mdd->obj->clazz->parse_eof ( mdd->obj, true ); + if ( !mdd->obj->parsed_p ) + mdd->obj->clazz->parse_end( mdd->obj, true ); + + NS_ASSERTION ( mdd->options == mdd->obj->options, "draft display options not same as mime obj" ); + mime_free (mdd->obj); + mdd->obj = 0; + if (mdd->options) + { + delete mdd->options; + mdd->options = 0; + } + + if (mdd->stream) + { + mdd->stream->abort ((nsMIMESession *)mdd->stream->data_object, status); + PR_Free( mdd->stream ); + mdd->stream = 0; + } + } + + if ( mdd->headers ) + MimeHeaders_free (mdd->headers); + + + mime_free_attachments(mdd->attachments); + + PR_FREEIF(mdd->mailcharset); + + PR_Free (mdd); +} + +static int +make_mime_headers_copy ( void *closure, MimeHeaders *headers ) +{ + mime_draft_data *mdd = (mime_draft_data *) closure; + + NS_ASSERTION ( mdd && headers, "null mime draft data and/or headers" ); + + if ( !mdd || ! headers ) + return 0; + + NS_ASSERTION ( mdd->headers == NULL , "non null mime draft data headers"); + + mdd->headers = MimeHeaders_copy ( headers ); + mdd->options->done_parsing_outer_headers = true; + + return 0; +} + +int +mime_decompose_file_init_fn ( void *stream_closure, MimeHeaders *headers ) +{ + mime_draft_data *mdd = (mime_draft_data *) stream_closure; + nsMsgAttachedFile *newAttachment = 0; + int nAttachments = 0; + //char *hdr_value = NULL; + char *parm_value = NULL; + bool creatingMsgBody = true; + + NS_ASSERTION (mdd && headers, "null mime draft data and/or headers"); + if (!mdd || !headers) + return -1; + + if (mdd->options->decompose_init_count) + { + mdd->options->decompose_init_count++; + NS_ASSERTION(mdd->curAttachment, "missing attachment in mime_decompose_file_init_fn"); + if (mdd->curAttachment) + mdd->curAttachment->m_type.Adopt(MimeHeaders_get(headers, + HEADER_CONTENT_TYPE, + false, true)); + return 0; + } + else + mdd->options->decompose_init_count++; + + nAttachments = mdd->attachments.Length(); + + if (!nAttachments && !mdd->messageBody) + { + // if we've been told to use an override charset then do so....otherwise use the charset + // inside the message header... + if (mdd->options && mdd->options->override_charset) + mdd->mailcharset = strdup(mdd->options->default_charset); + else + { + char *contentType; + contentType = MimeHeaders_get(headers, HEADER_CONTENT_TYPE, false, false); + if (contentType) + { + mdd->mailcharset = MimeHeaders_get_parameter(contentType, "charset", NULL, NULL); + PR_FREEIF(contentType); + } + } + + mdd->messageBody = new nsMsgAttachedFile; + if (!mdd->messageBody) + return MIME_OUT_OF_MEMORY; + newAttachment = mdd->messageBody; + creatingMsgBody = true; + } + else + { + /* always allocate one more extra; don't ask me why */ + newAttachment = new nsMsgAttachedFile; + if (!newAttachment) + return MIME_OUT_OF_MEMORY; + mdd->attachments.AppendElement(newAttachment); + } + + char *workURLSpec = nullptr; + char *contLoc = nullptr; + + newAttachment->m_realName.Adopt(MimeHeaders_get_name(headers, mdd->options)); + contLoc = MimeHeaders_get( headers, HEADER_CONTENT_LOCATION, false, false ); + if (!contLoc) + contLoc = MimeHeaders_get( headers, HEADER_CONTENT_BASE, false, false ); + + if (!contLoc && !newAttachment->m_realName.IsEmpty()) + workURLSpec = ToNewCString(newAttachment->m_realName); + if ( (contLoc) && (!workURLSpec) ) + workURLSpec = strdup(contLoc); + + PR_FREEIF(contLoc); + + mdd->curAttachment = newAttachment; + newAttachment->m_type.Adopt(MimeHeaders_get ( headers, HEADER_CONTENT_TYPE, false, false )); + + // + // This is to handle the degenerated Apple Double attachment. + // + parm_value = MimeHeaders_get( headers, HEADER_CONTENT_TYPE, false, false ); + if (parm_value) + { + char *boundary = NULL; + char *tmp_value = NULL; + boundary = MimeHeaders_get_parameter(parm_value, "boundary", NULL, NULL); + if (boundary) + tmp_value = PR_smprintf("; boundary=\"%s\"", boundary); + if (tmp_value) + newAttachment->m_type = tmp_value; + newAttachment->m_xMacType.Adopt( + MimeHeaders_get_parameter(parm_value, "x-mac-type", NULL, NULL)); + newAttachment->m_xMacCreator.Adopt( + MimeHeaders_get_parameter(parm_value, "x-mac-creator", NULL, NULL)); + PR_FREEIF(parm_value); + PR_FREEIF(boundary); + PR_FREEIF(tmp_value); + } + + newAttachment->m_size = 0; + newAttachment->m_encoding.Adopt(MimeHeaders_get (headers, HEADER_CONTENT_TRANSFER_ENCODING, + false, false)); + newAttachment->m_description.Adopt(MimeHeaders_get(headers, HEADER_CONTENT_DESCRIPTION, + false, false )); + // + // If we came up empty for description or the orig URL, we should do something about it. + // + if (newAttachment->m_description.IsEmpty() && workURLSpec) + newAttachment->m_description = workURLSpec; + + PR_FREEIF(workURLSpec); // resource leak otherwise + + newAttachment->m_cloudPartInfo.Adopt(MimeHeaders_get(headers, + HEADER_X_MOZILLA_CLOUD_PART, + false, false)); + + // There's no file in the message if it's a cloud part. + if (!newAttachment->m_cloudPartInfo.IsEmpty()) + { + nsAutoCString fileURL; + fileURL.Adopt( + MimeHeaders_get_parameter(newAttachment->m_cloudPartInfo.get(), "file", + nullptr, nullptr)); + if (!fileURL.IsEmpty()) + nsMimeNewURI(getter_AddRefs(newAttachment->m_origUrl), fileURL.get(), + nullptr); + mdd->tmpFile = nullptr; + return 0; + } + + nsCOMPtr <nsIFile> tmpFile = nullptr; + { + // Let's build a temp file with an extension based on the content-type: nsmail.<extension> + + nsAutoCString newAttachName ("nsmail"); + bool extensionAdded = false; + // the content type may contain a charset. i.e. text/html; ISO-2022-JP...we want to strip off the charset + // before we ask the mime service for a mime info for this content type. + nsAutoCString contentType (newAttachment->m_type); + int32_t pos = contentType.FindChar(';'); + if (pos > 0) + contentType.SetLength(pos); + nsresult rv = NS_OK; + nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && mimeFinder) + { + nsAutoCString fileExtension; + rv = mimeFinder->GetPrimaryExtension(contentType, EmptyCString(), fileExtension); + + if (NS_SUCCEEDED(rv) && !fileExtension.IsEmpty()) + { + newAttachName.Append("."); + newAttachName.Append(fileExtension); + extensionAdded = true; + } + } + + if (!extensionAdded) + { + newAttachName.Append(".tmp"); + } + + nsMsgCreateTempFile(newAttachName.get(), getter_AddRefs(tmpFile)); + } + nsresult rv; + + // This needs to be done so the attachment structure has a handle + // on the temp file for this attachment... + if (tmpFile) + { + nsAutoCString fileURL; + rv = NS_GetURLSpecFromFile(tmpFile, fileURL); + if (NS_SUCCEEDED(rv)) + nsMimeNewURI(getter_AddRefs(newAttachment->m_origUrl), + fileURL.get(), nullptr); + } + + if (!tmpFile) + return MIME_OUT_OF_MEMORY; + + mdd->tmpFile = do_QueryInterface(tmpFile); + + newAttachment->m_tmpFile = mdd->tmpFile; + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mdd->tmpFileStream), tmpFile,PR_WRONLY | PR_CREATE_FILE, 00600); + if (NS_FAILED(rv)) + return MIME_UNABLE_TO_OPEN_TMP_FILE; + + // For now, we are always going to decode all of the attachments + // for the message. This way, we have native data + if (creatingMsgBody) + { + MimeDecoderData *(*fn) (MimeConverterOutputCallback, void*) = 0; + + // + // Initialize a decoder if necessary. + // + if (newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_BASE64)) + fn = &MimeB64DecoderInit; + else if (newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_QUOTED_PRINTABLE)) + { + mdd->decoder_data = MimeQPDecoderInit (/* The (MimeConverterOutputCallback) cast is to turn the `void' argument into `MimeObject'. */ + ((MimeConverterOutputCallback) dummy_file_write), + mdd->tmpFileStream); + if (!mdd->decoder_data) + return MIME_OUT_OF_MEMORY; + } + else if (newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE) || + newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE2) || + newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE3) || + newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE4)) + fn = &MimeUUDecoderInit; + else if (newAttachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_YENCODE)) + fn = &MimeYDecoderInit; + + if (fn) + { + mdd->decoder_data = fn (/* The (MimeConverterOutputCallback) cast is to + turn the `void' argument into `MimeObject'. */ + ((MimeConverterOutputCallback) dummy_file_write), + mdd->tmpFileStream); + if (!mdd->decoder_data) + return MIME_OUT_OF_MEMORY; + } + } + + return 0; +} + +int +mime_decompose_file_output_fn (const char *buf, + int32_t size, + void *stream_closure ) +{ + mime_draft_data *mdd = (mime_draft_data *) stream_closure; + int ret = 0; + + NS_ASSERTION (mdd && buf, "missing mime draft data and/or buf"); + if (!mdd || !buf) return -1; + if (!size) return 0; + + if ( !mdd->tmpFileStream ) + return 0; + + if (mdd->decoder_data) { + int32_t outsize; + ret = MimeDecoderWrite(mdd->decoder_data, buf, size, &outsize); + if (ret == -1) return -1; + mdd->curAttachment->m_size += outsize; + } + else + { + uint32_t bytesWritten; + mdd->tmpFileStream->Write(buf, size, &bytesWritten); + if ((int32_t)bytesWritten < size) + return MIME_ERROR_WRITING_FILE; + mdd->curAttachment->m_size += size; + } + + return 0; +} + +int +mime_decompose_file_close_fn ( void *stream_closure ) +{ + mime_draft_data *mdd = (mime_draft_data *) stream_closure; + + if (!mdd) + return -1; + + if ( --mdd->options->decompose_init_count > 0 ) + return 0; + + if (mdd->decoder_data) { + MimeDecoderDestroy(mdd->decoder_data, false); + mdd->decoder_data = 0; + } + + if (!mdd->tmpFileStream) { + // it's ok to have a null tmpFileStream if there's no tmpFile. + // This happens for cloud file attachments. + NS_ASSERTION(!mdd->tmpFile, "shouldn't have a tmp file bu no stream"); + return 0; + } + mdd->tmpFileStream->Close(); + + mdd->tmpFileStream = nullptr; + + mdd->tmpFile = nullptr; + + return 0; +} + +extern "C" void * +mime_bridge_create_draft_stream( + nsIMimeEmitter *newEmitter, + nsStreamConverter *newPluginObj2, + nsIURI *uri, + nsMimeOutputType format_out) +{ + int status = 0; + nsMIMESession *stream = nullptr; + mime_draft_data *mdd = nullptr; + MimeObject *obj = nullptr; + + if ( !uri ) + return nullptr; + + mdd = new mime_draft_data; + if (!mdd) + return nullptr; + + nsAutoCString turl; + nsCOMPtr <nsIMsgMessageService> msgService; + nsCOMPtr<nsIURI> aURL; + nsAutoCString urlString; + nsresult rv; + + // first, convert the rdf msg uri into a url that represents the message... + if (NS_FAILED(uri->GetSpec(turl))) + goto FAIL; + + rv = GetMessageServiceFromURI(turl, getter_AddRefs(msgService)); + if (NS_FAILED(rv)) + goto FAIL; + + rv = msgService->GetUrlForUri(turl.get(), getter_AddRefs(aURL), nullptr); + if (NS_FAILED(rv)) + goto FAIL; + + if (NS_SUCCEEDED(aURL->GetSpec(urlString))) + { + int32_t typeIndex = urlString.Find("&type=application/x-message-display"); + if (typeIndex != -1) + urlString.Cut(typeIndex, sizeof("&type=application/x-message-display") - 1); + + mdd->url_name = ToNewCString(urlString); + if (!(mdd->url_name)) + goto FAIL; + } + + newPluginObj2->GetForwardInline(&mdd->forwardInline); + newPluginObj2->GetForwardInlineFilter(&mdd->forwardInlineFilter); + newPluginObj2->GetForwardToAddress(mdd->forwardToAddress); + newPluginObj2->GetOverrideComposeFormat(&mdd->overrideComposeFormat); + newPluginObj2->GetIdentity(getter_AddRefs(mdd->identity)); + newPluginObj2->GetOriginalMsgURI(&mdd->originalMsgURI); + newPluginObj2->GetOrigMsgHdr(getter_AddRefs(mdd->origMsgHdr)); + mdd->format_out = format_out; + mdd->options = new MimeDisplayOptions ; + if (!mdd->options) + goto FAIL; + + mdd->options->url = strdup(mdd->url_name); + mdd->options->format_out = format_out; // output format + mdd->options->decompose_file_p = true; /* new field in MimeDisplayOptions */ + mdd->options->stream_closure = mdd; + mdd->options->html_closure = mdd; + mdd->options->decompose_headers_info_fn = make_mime_headers_copy; + mdd->options->decompose_file_init_fn = mime_decompose_file_init_fn; + mdd->options->decompose_file_output_fn = mime_decompose_file_output_fn; + mdd->options->decompose_file_close_fn = mime_decompose_file_close_fn; + + mdd->options->m_prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + goto FAIL; + +#ifdef ENABLE_SMIME + /* If we're attaching a message (for forwarding) then we must eradicate all + traces of xlateion from it, since forwarding someone else a message + that wasn't xlated for them doesn't work. We have to dexlate it + before sending it. + */ + mdd->options->decrypt_p = true; +#endif /* ENABLE_SMIME */ + + obj = mime_new ( (MimeObjectClass *) &mimeMessageClass, (MimeHeaders *) NULL, MESSAGE_RFC822 ); + if ( !obj ) + goto FAIL; + + obj->options = mdd->options; + mdd->obj = obj; + + stream = PR_NEWZAP ( nsMIMESession ); + if ( !stream ) + goto FAIL; + + stream->name = "MIME To Draft Converter Stream"; + stream->complete = mime_parse_stream_complete; + stream->abort = mime_parse_stream_abort; + stream->put_block = mime_parse_stream_write; + stream->data_object = mdd; + + status = obj->clazz->initialize ( obj ); + if ( status >= 0 ) + status = obj->clazz->parse_begin ( obj ); + if ( status < 0 ) + goto FAIL; + + return stream; + +FAIL: + if (mdd) + { + PR_Free(mdd->url_name); + PR_Free(mdd->originalMsgURI); + if (mdd->options) + delete mdd->options; + PR_Free ( mdd ); + } + PR_Free ( stream ); + PR_Free ( obj ); + + return nullptr; +} diff --git a/mailnews/mime/src/mimeebod.cpp b/mailnews/mime/src/mimeebod.cpp new file mode 100644 index 000000000..2ca056feb --- /dev/null +++ b/mailnews/mime/src/mimeebod.cpp @@ -0,0 +1,509 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "nsIURL.h" +#include "mimeebod.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "prio.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" +#include "nsINetUtil.h" +#include <ctype.h> + +#define MIME_SUPERCLASS mimeObjectClass +MimeDefClass(MimeExternalBody, MimeExternalBodyClass, + mimeExternalBodyClass, &MIME_SUPERCLASS); + +#ifdef XP_MACOSX +extern MimeObjectClass mimeMultipartAppleDoubleClass; +#endif + +static int MimeExternalBody_initialize (MimeObject *); +static void MimeExternalBody_finalize (MimeObject *); +static int MimeExternalBody_parse_line (const char *, int32_t, MimeObject *); +static int MimeExternalBody_parse_eof (MimeObject *, bool); +static bool MimeExternalBody_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs); + +#if 0 +#if defined(DEBUG) && defined(XP_UNIX) +static int MimeExternalBody_debug_print (MimeObject *, PRFileDesc *, int32_t); +#endif +#endif /* 0 */ + +static int +MimeExternalBodyClassInitialize(MimeExternalBodyClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + + NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + oclass->initialize = MimeExternalBody_initialize; + oclass->finalize = MimeExternalBody_finalize; + oclass->parse_line = MimeExternalBody_parse_line; + oclass->parse_eof = MimeExternalBody_parse_eof; + oclass->displayable_inline_p = MimeExternalBody_displayable_inline_p; + +#if 0 +#if defined(DEBUG) && defined(XP_UNIX) + oclass->debug_print = MimeExternalBody_debug_print; +#endif +#endif /* 0 */ + + return 0; +} + + +static int +MimeExternalBody_initialize (MimeObject *object) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeExternalBody_finalize (MimeObject *object) +{ + MimeExternalBody *bod = (MimeExternalBody *) object; + if (bod->hdrs) + { + MimeHeaders_free(bod->hdrs); + bod->hdrs = 0; + } + PR_FREEIF(bod->body); + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + +static int +MimeExternalBody_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + MimeExternalBody *bod = (MimeExternalBody *) obj; + int status = 0; + + NS_ASSERTION(line && *line, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!line || !*line) return -1; + + if (!obj->output_p) return 0; + + /* If we're supposed to write this object, but aren't supposed to convert + it to HTML, simply pass it through unaltered. */ + if (obj->options && + !obj->options->write_html_p && + obj->options->output_fn) + return MimeObject_write(obj, line, length, true); + + + /* If we already have a `body' then we're done parsing headers, and all + subsequent lines get tacked onto the body. */ + if (bod->body) + { + int L = strlen(bod->body); + char *new_str = (char *)PR_Realloc(bod->body, L + length + 1); + if (!new_str) return MIME_OUT_OF_MEMORY; + bod->body = new_str; + memcpy(bod->body + L, line, length); + bod->body[L + length] = 0; + return 0; + } + + /* Otherwise we don't yet have a body, which means we're not done parsing + our headers. + */ + if (!bod->hdrs) + { + bod->hdrs = MimeHeaders_new(); + if (!bod->hdrs) return MIME_OUT_OF_MEMORY; + } + + status = MimeHeaders_parse_line(line, length, bod->hdrs); + if (status < 0) return status; + + /* If this line is blank, we're now done parsing headers, and should + create a dummy body to show that. Gag. + */ + if (*line == '\r' || *line == '\n') + { + bod->body = strdup(""); + if (!bod->body) return MIME_OUT_OF_MEMORY; + } + + return 0; +} + + +char * +MimeExternalBody_make_url(const char *ct, + const char *at, const char *lexp, const char *size, + const char *perm, const char *dir, const char *mode, + const char *name, const char *url, const char *site, + const char *svr, const char *subj, const char *body) +{ + char *s; + uint32_t slen; + if (!at) + { + return 0; + } + else if (!PL_strcasecmp(at, "ftp") || !PL_strcasecmp(at, "anon-ftp")) + { + if (!site || !name) + return 0; + + slen = strlen(name) + strlen(site) + (dir ? strlen(dir) : 0) + 20; + s = (char *) PR_MALLOC(slen); + + if (!s) return 0; + PL_strncpyz(s, "ftp://", slen); + PL_strcatn(s, slen, site); + PL_strcatn(s, slen, "/"); + if (dir) PL_strcatn(s, slen, (dir[0] == '/' ? dir+1 : dir)); + if (s[strlen(s)-1] != '/') + PL_strcatn(s, slen, "/"); + PL_strcatn(s, slen, name); + return s; + } + else if (!PL_strcasecmp(at, "local-file") || !PL_strcasecmp(at, "afs")) + { + if (!name) + return 0; + +#ifdef XP_UNIX + if (!PL_strcasecmp(at, "afs")) /* only if there is a /afs/ directory */ + { + nsCOMPtr <nsIFile> fs = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + bool exists = false; + if (fs) + { + fs->InitWithNativePath(NS_LITERAL_CSTRING("/afs/.")); + fs->Exists(&exists); + } + if (!exists) + return 0; + } +#else /* !XP_UNIX */ + return 0; /* never, if not Unix. */ +#endif /* !XP_UNIX */ + + slen = (strlen(name) * 3 + 20); + s = (char *) PR_MALLOC(slen); + if (!s) return 0; + PL_strncpyz(s, "file:", slen); + + nsCString s2; + MsgEscapeString(nsDependentCString(name), nsINetUtil::ESCAPE_URL_PATH, s2); + PL_strcatn(s, slen, s2.get()); + return s; + } +else if (!PL_strcasecmp(at, "mail-server")) +{ + if (!svr) + return 0; + + slen = (strlen(svr)*4 + (subj ? strlen(subj)*4 : 0) + + (body ? strlen(body)*4 : 0) + 25); // dpv xxx: why 4x? %xx escaping should be 3x + s = (char *) PR_MALLOC(slen); + if (!s) return 0; + PL_strncpyz(s, "mailto:", slen); + + nsCString s2; + MsgEscapeString(nsDependentCString(svr), nsINetUtil::ESCAPE_XALPHAS, s2); + PL_strcatn(s, slen, s2.get()); + + if (subj) + { + MsgEscapeString(nsDependentCString(subj), nsINetUtil::ESCAPE_XALPHAS, s2); + PL_strcatn(s, slen, "?subject="); + PL_strcatn(s, slen, s2.get()); + } + if (body) + { + MsgEscapeString(nsDependentCString(body), nsINetUtil::ESCAPE_XALPHAS, s2); + PL_strcatn(s, slen, (subj ? "&body=" : "?body=")); + PL_strcatn(s, slen, s2.get()); + } + return s; +} +else if (!PL_strcasecmp(at, "url")) /* RFC 2017 */ + { + if (url) + return strdup(url); /* it's already quoted and everything */ + else + return 0; + } + else + return 0; +} + +static int +MimeExternalBody_parse_eof (MimeObject *obj, bool abort_p) +{ + int status = 0; + MimeExternalBody *bod = (MimeExternalBody *) obj; + + if (obj->closed_p) return 0; + + /* Run parent method first, to flush out any buffered data. */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + +#ifdef XP_MACOSX + if (obj->parent && mime_typep(obj->parent, + (MimeObjectClass*) &mimeMultipartAppleDoubleClass)) + goto done; +#endif /* XP_MACOSX */ + + if (!abort_p && + obj->output_p && + obj->options && + obj->options->write_html_p) + { + bool all_headers_p = obj->options->headers == MimeHeadersAll; + MimeDisplayOptions *newopt = obj->options; /* copy it */ + + char *ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, + false, false); + char *at, *lexp, *size, *perm; + char *url, *dir, *mode, *name, *site, *svr, *subj; + char *h = 0, *lname = 0, *lurl = 0, *body = 0; + MimeHeaders *hdrs = 0; + + if (!ct) return MIME_OUT_OF_MEMORY; + + at = MimeHeaders_get_parameter(ct, "access-type", NULL, NULL); + lexp = MimeHeaders_get_parameter(ct, "expiration", NULL, NULL); + size = MimeHeaders_get_parameter(ct, "size", NULL, NULL); + perm = MimeHeaders_get_parameter(ct, "permission", NULL, NULL); + dir = MimeHeaders_get_parameter(ct, "directory", NULL, NULL); + mode = MimeHeaders_get_parameter(ct, "mode", NULL, NULL); + name = MimeHeaders_get_parameter(ct, "name", NULL, NULL); + site = MimeHeaders_get_parameter(ct, "site", NULL, NULL); + svr = MimeHeaders_get_parameter(ct, "server", NULL, NULL); + subj = MimeHeaders_get_parameter(ct, "subject", NULL, NULL); + url = MimeHeaders_get_parameter(ct, "url", NULL, NULL); + PR_FREEIF(ct); + + /* the *internal* content-type */ + ct = MimeHeaders_get(bod->hdrs, HEADER_CONTENT_TYPE, + true, false); + + uint32_t hlen = ((at ? strlen(at) : 0) + + (lexp ? strlen(lexp) : 0) + + (size ? strlen(size) : 0) + + (perm ? strlen(perm) : 0) + + (dir ? strlen(dir) : 0) + + (mode ? strlen(mode) : 0) + + (name ? strlen(name) : 0) + + (site ? strlen(site) : 0) + + (svr ? strlen(svr) : 0) + + (subj ? strlen(subj) : 0) + + (ct ? strlen(ct) : 0) + + (url ? strlen(url) : 0) + 100); + + h = (char *) PR_MALLOC(hlen); + if (!h) + { + status = MIME_OUT_OF_MEMORY; + goto FAIL; + } + + /* If there's a URL parameter, remove all whitespace from it. + (The URL parameter to one of these headers is stored with + lines broken every 40 characters or less; it's assumed that + all significant whitespace was URL-hex-encoded, and all the + rest of it was inserted just to keep the lines short.) + */ + if (url) + { + char *in, *out; + for (in = url, out = url; *in; in++) + if (!IS_SPACE(*in)) + *out++ = *in; + *out = 0; + } + + hdrs = MimeHeaders_new(); + if (!hdrs) + { + status = MIME_OUT_OF_MEMORY; + goto FAIL; + } + +# define FROB(STR,VAR) \ + if (VAR) \ + { \ + PL_strncpyz(h, STR ": ", hlen); \ + PL_strcatn(h, hlen, VAR); \ + PL_strcatn(h, hlen, MSG_LINEBREAK); \ + status = MimeHeaders_parse_line(h, strlen(h), hdrs); \ + if (status < 0) goto FAIL; \ + } + FROB("Access-Type", at); + FROB("URL", url); + FROB("Site", site); + FROB("Server", svr); + FROB("Directory", dir); + FROB("Name", name); + FROB("Type", ct); + FROB("Size", size); + FROB("Mode", mode); + FROB("Permission", perm); + FROB("Expiration", lexp); + FROB("Subject", subj); +# undef FROB + PL_strncpyz(h, MSG_LINEBREAK, hlen); + status = MimeHeaders_parse_line(h, strlen(h), hdrs); + if (status < 0) goto FAIL; + + lurl = MimeExternalBody_make_url(ct, at, lexp, size, perm, dir, mode, + name, url, site, svr, subj, bod->body); + if (lurl) + { + lname = MimeGetStringByID(MIME_MSG_LINK_TO_DOCUMENT); + } + else + { + lname = MimeGetStringByID(MIME_MSG_DOCUMENT_INFO); + all_headers_p = true; + } + + all_headers_p = true; /* #### just do this all the time? */ + + if (bod->body && all_headers_p) + { + char *s = bod->body; + while (IS_SPACE(*s)) s++; + if (*s) + { + char *s2; + const char *pre = "<P><PRE>"; + const char *suf = "</PRE>"; + int32_t i; + for(i = strlen(s)-1; i >= 0 && IS_SPACE(s[i]); i--) + s[i] = 0; + s2 = MsgEscapeHTML(s); + if (!s2) goto FAIL; + body = (char *) PR_MALLOC(strlen(pre) + strlen(s2) + + strlen(suf) + 1); + if (!body) + { + NS_Free(s2); + goto FAIL; + } + PL_strcpy(body, pre); + PL_strcat(body, s2); + PL_strcat(body, suf); + } + } + + newopt->fancy_headers_p = true; + newopt->headers = (all_headers_p ? MimeHeadersAll : MimeHeadersSome); + +FAIL: + if (hdrs) + MimeHeaders_free(hdrs); + PR_FREEIF(h); + PR_FREEIF(lname); + PR_FREEIF(lurl); + PR_FREEIF(body); + PR_FREEIF(ct); + PR_FREEIF(at); + PR_FREEIF(lexp); + PR_FREEIF(size); + PR_FREEIF(perm); + PR_FREEIF(dir); + PR_FREEIF(mode); + PR_FREEIF(name); + PR_FREEIF(url); + PR_FREEIF(site); + PR_FREEIF(svr); + PR_FREEIF(subj); + } + +#ifdef XP_MACOSX +done: +#endif + + return status; +} + +#if 0 +#if defined(DEBUG) && defined(XP_UNIX) +static int +MimeExternalBody_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth) +{ + MimeExternalBody *bod = (MimeExternalBody *) obj; + int i; + char *ct, *ct2; + char *addr = mime_part_address(obj); + + if (obj->headers) + ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, false, false); + if (bod->hdrs) + ct2 = MimeHeaders_get (bod->hdrs, HEADER_CONTENT_TYPE, false, false); + + for (i=0; i < depth; i++) + PR_Write(stream, " ", 2); +/*** + fprintf(stream, + "<%s %s\n" + "\tcontent-type: %s\n" + "\tcontent-type: %s\n" + "\tBody:%s\n\t0x%08X>\n\n", + obj->clazz->class_name, + addr ? addr : "???", + ct ? ct : "<none>", + ct2 ? ct2 : "<none>", + bod->body ? bod->body : "<none>", + (uint32_t) obj); +***/ + PR_FREEIF(addr); + PR_FREEIF(ct); + PR_FREEIF(ct2); + return 0; +} +#endif +#endif /* 0 */ + +static bool +MimeExternalBody_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs) +{ + char *ct = MimeHeaders_get (hdrs, HEADER_CONTENT_TYPE, false, false); + char *at = MimeHeaders_get_parameter(ct, "access-type", NULL, NULL); + bool inline_p = false; + + if (!at) + ; + else if (!PL_strcasecmp(at, "ftp") || + !PL_strcasecmp(at, "anon-ftp") || + !PL_strcasecmp(at, "local-file") || + !PL_strcasecmp(at, "mail-server") || + !PL_strcasecmp(at, "url")) + inline_p = true; +#ifdef XP_UNIX + else if (!PL_strcasecmp(at, "afs")) /* only if there is a /afs/ directory */ + { + nsCOMPtr <nsIFile> fs = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + bool exists = false; + if (fs) + { + fs->InitWithNativePath(NS_LITERAL_CSTRING("/afs/.")); + fs->Exists(&exists); + } + if (!exists) + return 0; + + inline_p = true; + } +#endif /* XP_UNIX */ + + PR_FREEIF(ct); + PR_FREEIF(at); + return inline_p; +} diff --git a/mailnews/mime/src/mimeebod.h b/mailnews/mime/src/mimeebod.h new file mode 100644 index 000000000..560bef77c --- /dev/null +++ b/mailnews/mime/src/mimeebod.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEEBOD_H_ +#define _MIMEEBOD_H_ + +#include "mimeobj.h" + +/* The MimeExternalBody class implements the message/external-body MIME type. + (This is not to be confused with MimeExternalObject, which implements the + handler for application/octet-stream and other types with no more specific + handlers.) + */ + +typedef struct MimeExternalBodyClass MimeExternalBodyClass; +typedef struct MimeExternalBody MimeExternalBody; + +struct MimeExternalBodyClass { + MimeObjectClass object; +}; + +extern MimeExternalBodyClass mimeExternalBodyClass; + +struct MimeExternalBody { + MimeObject object; /* superclass variables */ + MimeHeaders *hdrs; /* headers within this external-body, which + describe the network data which this body + is a pointer to. */ + char *body; /* The "phantom body" of this link. */ +}; + +#define MimeExternalBodyClassInitializer(ITYPE,CSUPER) \ + { MimeObjectClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEEBOD_H_ */ diff --git a/mailnews/mime/src/mimeenc.cpp b/mailnews/mime/src/mimeenc.cpp new file mode 100644 index 000000000..d565a6067 --- /dev/null +++ b/mailnews/mime/src/mimeenc.cpp @@ -0,0 +1,1107 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <stdio.h> +#include "mimei.h" +#include "prmem.h" +#include "mimeobj.h" +#include "mozilla/RangedPtr.h" +#include "mozilla/mailnews/MimeEncoder.h" + +typedef enum mime_encoding { + mime_Base64, mime_QuotedPrintable, mime_uuencode, mime_yencode +} mime_encoding; + +typedef enum mime_decoder_state { + DS_BEGIN, DS_BODY, DS_END +} mime_decoder_state; + +struct MimeDecoderData { + mime_encoding encoding; /* Which encoding to use */ + + /* A read-buffer used for QP and B64. */ + char token[4]; + int token_size; + + /* State and read-buffer used for uudecode and yencode. */ + mime_decoder_state ds_state; + char *line_buffer; + int line_buffer_size; + + MimeObject *objectToDecode; // might be null, only used for QP currently + /* Where to write the decoded data */ + MimeConverterOutputCallback write_buffer; + void *closure; +}; + + +static int +mime_decode_qp_buffer (MimeDecoderData *data, const char *buffer, + int32_t length, int32_t *outSize) +{ + /* Warning, we are overwriting the buffer which was passed in. + This is ok, because decoding these formats will never result + in larger data than the input, only smaller. */ + const char *in = buffer; + char *out = (char *) buffer; + char token [3]; + int i; + + NS_ASSERTION(data->encoding == mime_QuotedPrintable, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (data->encoding != mime_QuotedPrintable) return -1; + + /* For the first pass, initialize the token from the unread-buffer. */ + i = 0; + while (i < 3 && data->token_size > 0) + { + token [i] = data->token[i]; + data->token_size--; + i++; + } + + /* #### BUG: when decoding quoted-printable, we are required to + strip trailing whitespace from lines -- since when encoding in + qp, one is required to quote such trailing whitespace, any + trailing whitespace which remains must have been introduced + by a stupid gateway. */ + + /* Treat null bytes as spaces when format_out is + nsMimeOutput::nsMimeMessageBodyDisplay (see bug 243199 comment 7) */ + bool treatNullAsSpace = data->objectToDecode && + data->objectToDecode->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay; + + while (length > 0 || i != 0) + { + while (i < 3 && length > 0) + { + token [i++] = *in; + in++; + length--; + } + + if (i < 3) + { + /* Didn't get enough for a complete token. + If it might be a token, unread it. + Otherwise, just dump it. + */ + memcpy (data->token, token, i); + data->token_size = i; + i = 0; + length = 0; + break; + } + i = 0; + + if (token [0] == '=') + { + unsigned char c = 0; + if (token[1] >= '0' && token[1] <= '9') + c = token[1] - '0'; + else if (token[1] >= 'A' && token[1] <= 'F') + c = token[1] - ('A' - 10); + else if (token[1] >= 'a' && token[1] <= 'f') + c = token[1] - ('a' - 10); + else if (token[1] == '\r' || token[1] == '\n') + { + /* =\n means ignore the newline. */ + if (token[1] == '\r' && token[2] == '\n') + ; /* swallow all three chars */ + else + { + in--; /* put the third char back */ + length++; + } + continue; + } + else + { + /* = followed by something other than hex or newline - + pass it through unaltered, I guess. (But, if + this bogus token happened to occur over a buffer + boundary, we can't do this, since we don't have + space for it. Oh well. Screw it.) */ + if (in > out) *out++ = token[0]; + if (in > out) *out++ = token[1]; + if (in > out) *out++ = token[2]; + continue; + } + + /* Second hex digit */ + c = (c << 4); + if (token[2] >= '0' && token[2] <= '9') + c += token[2] - '0'; + else if (token[2] >= 'A' && token[2] <= 'F') + c += token[2] - ('A' - 10); + else if (token[2] >= 'a' && token[2] <= 'f') + c += token[2] - ('a' - 10); + else + { + /* We got =xy where "x" was hex and "y" was not, so + treat that as a literal "=", x, and y. (But, if + this bogus token happened to occur over a buffer + boundary, we can't do this, since we don't have + space for it. Oh well. Screw it.) */ + if (in > out) *out++ = token[0]; + if (in > out) *out++ = token[1]; + if (in > out) *out++ = token[2]; + continue; + } + + *out++ = c ? (char) c : ((treatNullAsSpace) ? ' ' : (char) c); + } + else + { + *out++ = token[0]; + + token[0] = token[1]; + token[1] = token[2]; + i = 2; + } + } + + // Fill the size + if (outSize) + *outSize = out - buffer; + + /* Now that we've altered the data in place, write it. */ + if (out > buffer) + return data->write_buffer (buffer, (out - buffer), data->closure); + else + return 1; +} + + +static int +mime_decode_base64_token (const char *in, char *out) +{ + /* reads 4, writes 0-3. Returns bytes written. + (Writes less than 3 only at EOF.) */ + int j; + int eq_count = 0; + unsigned long num = 0; + + for (j = 0; j < 4; j++) + { + unsigned char c = 0; + if (in[j] >= 'A' && in[j] <= 'Z') c = in[j] - 'A'; + else if (in[j] >= 'a' && in[j] <= 'z') c = in[j] - ('a' - 26); + else if (in[j] >= '0' && in[j] <= '9') c = in[j] - ('0' - 52); + else if (in[j] == '+') c = 62; + else if (in[j] == '/') c = 63; + else if (in[j] == '=') c = 0, eq_count++; + else + NS_ERROR("Invalid character"); + num = (num << 6) | c; + } + + *out++ = (char) (num >> 16); + *out++ = (char) ((num >> 8) & 0xFF); + *out++ = (char) (num & 0xFF); + + if (eq_count == 0) + return 3; /* No "=" padding means 4 bytes mapped to 3. */ + else if (eq_count == 1) + return 2; /* "xxx=" means 3 bytes mapped to 2. */ + else if (eq_count == 2) + return 1; /* "xx==" means 2 bytes mapped to 1. */ + else + { + // "x===" can't happen, because "x" would then be encoding only + // 6 bits, not the min of 8. + NS_ERROR("Count is 6 bits, should be at least 8"); + return 1; + } +} + + +static int +mime_decode_base64_buffer (MimeDecoderData *data, + const char *buffer, int32_t length, int32_t *outSize) +{ + /* Warning, we are overwriting the buffer which was passed in. + This is ok, because decoding these formats will never result + in larger data than the input, only smaller. */ + const char *in = buffer; + char *out = (char *) buffer; + char token [4]; + int i; + bool leftover = (data->token_size > 0); + + NS_ASSERTION(data->encoding == mime_Base64, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + /* For the first pass, initialize the token from the unread-buffer. */ + i = 0; + while (i < 4 && data->token_size > 0) + { + token [i] = data->token[i]; + data->token_size--; + i++; + } + + while (length > 0) + { + while (i < 4 && length > 0) + { + if ((*in >= 'A' && *in <= 'Z') || + (*in >= 'a' && *in <= 'z') || + (*in >= '0' && *in <= '9') || + *in == '+' || *in == '/' || *in == '=') + token [i++] = *in; + in++; + length--; + } + + if (i < 4) + { + /* Didn't get enough for a complete token. */ + memcpy (data->token, token, i); + data->token_size = i; + length = 0; + break; + } + i = 0; + + if (leftover) + { + /* If there are characters left over from the last time around, + we might not have space in the buffer to do our dirty work + (if there were 2 or 3 left over, then there is only room for + 1 or 2 in the buffer right now, and we need 3.) This is only + a problem for the first chunk in each buffer, so in that + case, just write prematurely. */ + int n; + n = mime_decode_base64_token (token, token); + n = data->write_buffer (token, n, data->closure); + if (n < 0) /* abort */ + return n; + + /* increment buffer so that we don't write the 1 or 2 unused + characters now at the front. */ + buffer = in; + out = (char *) buffer; + + leftover = false; + } + else + { + int n = mime_decode_base64_token (token, out); + /* Advance "out" by the number of bytes just written to it. */ + out += n; + } + } + + if (outSize) + *outSize = out - buffer; + /* Now that we've altered the data in place, write it. */ + if (out > buffer) + return data->write_buffer (buffer, (out - buffer), data->closure); + else + return 1; +} + + +static int +mime_decode_uue_buffer (MimeDecoderData *data, + const char *input_buffer, int32_t input_length, int32_t *outSize) +{ + /* First, copy input_buffer into state->line_buffer until we have + a complete line. + + Then decode that line in place (in the line_buffer) and write + it out. + + Then pull the next line into line_buffer and continue. + */ + if (!data->line_buffer) + { + data->line_buffer_size = 128; + data->line_buffer = (char *)PR_MALLOC(data->line_buffer_size); + if (!data->line_buffer) + return -1; + data->line_buffer[0] = 0; + } + + int status = 0; + char *line = data->line_buffer; + char *line_end = data->line_buffer + data->line_buffer_size - 1; + + NS_ASSERTION(data->encoding == mime_uuencode, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (data->encoding != mime_uuencode) return -1; + + if (data->ds_state == DS_END) + { + status = 0; + goto DONE; + } + + while (input_length > 0) + { + /* Copy data from input_buffer to `line' until we have a complete line, + or until we've run out of input. + + (line may have data in it already if the last time we were called, + we weren't called with a buffer that ended on a line boundary.) + */ + { + char *out = line + strlen(line); + while (input_length > 0 && + out < line_end) + { + *out++ = *input_buffer++; + input_length--; + + if (out[-1] == '\r' || out[-1] == '\n') + { + /* If we just copied a CR, and an LF is waiting, grab it too. + */ + if (out[-1] == '\r' && + input_length > 0 && + *input_buffer == '\n') + input_buffer++, input_length--; + + /* We have a line. */ + break; + } + } + *out = 0; + + /* Ignore blank lines. + */ + if (*line == '\r' || *line == '\n') + { + *line = 0; + continue; + } + + /* If this line was bigger than our buffer, truncate it. + (This means the data was way corrupted, and there's basically + no chance of decoding it properly, but give it a shot anyway.) + */ + if (out == line_end) + { + out--; + out[-1] = '\r'; + out[0] = 0; + } + + /* If we didn't get a complete line, simply return; we'll be called + with the rest of this line next time. + */ + if (out[-1] != '\r' && out[-1] != '\n') + { + NS_ASSERTION (input_length == 0, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + break; + } + } + + + /* Now we have a complete line. Deal with it. + */ + + + if (data->ds_state == DS_BODY && + line[0] == 'e' && + line[1] == 'n' && + line[2] == 'd' && + (line[3] == '\r' || + line[3] == '\n')) + { + /* done! */ + data->ds_state = DS_END; + *line = 0; + break; + } + else if (data->ds_state == DS_BEGIN) + { + if (!strncmp (line, "begin ", 6)) + data->ds_state = DS_BODY; + *line = 0; + continue; + } + else + { + /* We're in DS_BODY. Decode the line. */ + char *in, *out; + int32_t i; + long lost; + + NS_ASSERTION (data->ds_state == DS_BODY, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + /* We map down `line', reading four bytes and writing three. + That means that `out' always stays safely behind `in'. + */ + in = line; + out = line; + +# undef DEC +# define DEC(c) (((c) - ' ') & 077) + i = DEC (*in); /* get length */ + + /* all the parens and casts are because gcc was doing something evil. + */ + lost = ((long) i) - (((((long) strlen (in)) - 2L) * 3L) / 4L); + + if (lost > 0) /* Short line!! */ + { + /* If we get here, then the line is shorter than the length byte + at the beginning says it should be. However, the case where + the line is short because it was at the end of the buffer and + we didn't get the whole line was handled earlier (up by the + "didn't get a complete line" comment.) So if we've gotten + here, then this is a complete line which is internally + inconsistent. We will parse from it what we can... + + This probably happened because some gateway stripped trailing + whitespace from the end of the line -- so pretend the line + was padded with spaces (which map to \000.) + */ + i -= lost; + } + + for (++in; i > 0; in += 4, i -= 3) + { + char ch; + NS_ASSERTION(out <= in, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (i >= 3) + { + /* We read four; write three. */ + ch = DEC (in[0]) << 2 | DEC (in[1]) >> 4; + *out++ = ch; + + NS_ASSERTION(out <= in+1, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + ch = DEC (in[1]) << 4 | DEC (in[2]) >> 2; + *out++ = ch; + + NS_ASSERTION(out <= in+2, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + ch = DEC (in[2]) << 6 | DEC (in[3]); + *out++ = ch; + + NS_ASSERTION(out <= in+3, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + } + else + { + /* Handle a line that isn't a multiple of 4 long. + (We read 1, 2, or 3, and will write 1 or 2.) + */ + NS_ASSERTION (i > 0 && i < 3, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + ch = DEC (in[0]) << 2 | DEC (in[1]) >> 4; + *out++ = ch; + + NS_ASSERTION(out <= in+1, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (i == 2) + { + ch = DEC (in[1]) << 4 | DEC (in[2]) >> 2; + *out++ = ch; + + NS_ASSERTION(out <= in+2, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + } + } + } + + /* If the line was truncated, pad the missing bytes with 0 (SPC). */ + while (lost > 0) + { + *out++ = 0; + lost--; + in = out+1; /* just to prevent the assert, below. */ + } +# undef DEC + + /* Now write out what we decoded for this line. + */ + NS_ASSERTION(out >= line && out < in, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (out > line) + status = data->write_buffer (line, (out - line), data->closure); + + // The assertion above tells us this is >= 0 + if (outSize) + *outSize = out - line; + + /* Reset the line so that we don't think it's partial next time. */ + *line = 0; + + if (status < 0) /* abort */ + goto DONE; + } + } + + status = 1; + + DONE: + + return status; +} + +static int +mime_decode_yenc_buffer (MimeDecoderData *data, + const char *input_buffer, int32_t input_length, int32_t *outSize) +{ + /* First, copy input_buffer into state->line_buffer until we have + a complete line. + + Then decode that line in place (in the line_buffer) and write + it out. + + Then pull the next line into line_buffer and continue. + */ + if (!data->line_buffer) + { + data->line_buffer_size = 1000; // let make sure we have plenty of space for the header line + data->line_buffer = (char *)PR_MALLOC(data->line_buffer_size); + if (!data->line_buffer) + return -1; + data->line_buffer[0] = 0; + } + + int status = 0; + char *line = data->line_buffer; + char *line_end = data->line_buffer + data->line_buffer_size - 1; + + NS_ASSERTION(data->encoding == mime_yencode, "wrong decoder!"); + if (data->encoding != mime_yencode) return -1; + + if (data->ds_state == DS_END) + return 0; + + while (input_length > 0) + { + /* Copy data from input_buffer to `line' until we have a complete line, + or until we've run out of input. + + (line may have data in it already if the last time we were called, + we weren't called with a buffer that ended on a line boundary.) + */ + { + char *out = line + strlen(line); + while (input_length > 0 && out < line_end) + { + *out++ = *input_buffer++; + input_length--; + + if (out[-1] == '\r' || out[-1] == '\n') + { + /* If we just copied a CR, and an LF is waiting, grab it too. */ + if (out[-1] == '\r' && + input_length > 0 && + *input_buffer == '\n') + input_buffer++, input_length--; + + /* We have a line. */ + break; + } + } + *out = 0; + + /* Ignore blank lines. */ + if (*line == '\r' || *line == '\n') + { + *line = 0; + continue; + } + + /* If this line was bigger than our buffer, truncate it. + (This means the data was way corrupted, and there's basically + no chance of decoding it properly, but give it a shot anyway.) + */ + if (out == line_end) + { + out--; + out[-1] = '\r'; + out[0] = 0; + } + + /* If we didn't get a complete line, simply return; we'll be called + with the rest of this line next time. + */ + if (out[-1] != '\r' && out[-1] != '\n') + { + NS_ASSERTION (input_length == 0, "empty buffer!"); + break; + } + } + + + /* Now we have a complete line. Deal with it. + */ + const char * endOfLine = line + strlen(line); + + if (data->ds_state == DS_BEGIN) + { + int new_line_size = 0; + /* this yenc decoder does not support yenc v2 or multipart yenc. + Therefore, we are looking first for "=ybegin line=" + */ + if ((endOfLine - line) >= 13 && !strncmp (line, "=ybegin line=", 13)) + { + /* ...then couple digits. */ + for (line += 13; line < endOfLine; line ++) + { + if (*line < '0' || *line > '9') + break; + new_line_size = (new_line_size * 10) + *line - '0'; + } + + /* ...next, look for <space>size= */ + if ((endOfLine - line) >= 6 && !strncmp (line, " size=", 6)) + { + /* ...then couple digits. */ + for (line += 6; line < endOfLine; line ++) + if (*line < '0' || *line > '9') + break; + + /* ...next, look for <space>name= */ + if ((endOfLine - line) >= 6 && !strncmp (line, " name=", 6)) + { + /* we have found the yenc header line. + Now check if we need to grow our buffer line + */ + data->ds_state = DS_BODY; + if (new_line_size > data->line_buffer_size && new_line_size <= 997) /* don't let bad value hurt us! */ + { + PR_Free(data->line_buffer); + data->line_buffer_size = new_line_size + 4; //extra chars for line ending and potential escape char + data->line_buffer = (char *)PR_MALLOC(data->line_buffer_size); + if (!data->line_buffer) + return -1; + } + } + } + + } + *data->line_buffer = 0; + continue; + } + + if (data->ds_state == DS_BODY && line[0] == '=') + { + /* look if this this the final line */ + if (!strncmp (line, "=yend size=", 11)) + { + /* done! */ + data->ds_state = DS_END; + *line = 0; + break; + } + } + + /* We're in DS_BODY. Decode the line in place. */ + { + char *src = line; + char *dest = src; + char c; + for (; src < line_end; src ++) + { + c = *src; + if (!c || c == '\r' || c == '\n') + break; + + if (c == '=') + { + src++; + c = *src; + if (c == 0) + return -1; /* last character cannot be escape char */ + c -= 64; + } + c -= 42; + *dest = c; + dest ++; + } + + // The assertion below is helpful, too + if (outSize) + *outSize = dest - line; + + /* Now write out what we decoded for this line. */ + NS_ASSERTION(dest >= line && dest <= src, "nothing to write!"); + if (dest > line) + { + status = data->write_buffer (line, dest - line, data->closure); + if (status < 0) /* abort */ + return status; + } + + /* Reset the line so that we don't think it's partial next time. */ + *line = 0; + } + } + + return 1; +} + +int +MimeDecoderDestroy (MimeDecoderData *data, bool abort_p) +{ + int status = 0; + /* Flush out the last few buffered characters. */ + if (!abort_p && + data->token_size > 0 && + data->token[0] != '=') + { + if (data->encoding == mime_Base64) + while ((unsigned int)data->token_size < sizeof (data->token)) + data->token [data->token_size++] = '='; + + status = data->write_buffer (data->token, data->token_size, + data->closure); + } + + if (data->line_buffer) + PR_Free(data->line_buffer); + PR_Free (data); + return status; +} + + +static MimeDecoderData * +mime_decoder_init (mime_encoding which, + MimeConverterOutputCallback output_fn, + void *closure) +{ + MimeDecoderData *data = PR_NEW(MimeDecoderData); + if (!data) return 0; + memset(data, 0, sizeof(*data)); + data->encoding = which; + data->write_buffer = output_fn; + data->closure = closure; + data->line_buffer_size = 0; + data->line_buffer = nullptr; + + return data; +} + +MimeDecoderData * +MimeB64DecoderInit (MimeConverterOutputCallback output_fn, void *closure) +{ + return mime_decoder_init (mime_Base64, output_fn, closure); +} + +MimeDecoderData * +MimeQPDecoderInit (MimeConverterOutputCallback output_fn, + void *closure, MimeObject *object) +{ + MimeDecoderData *retData = mime_decoder_init (mime_QuotedPrintable, output_fn, closure); + if (retData) + retData->objectToDecode = object; + return retData; +} + +MimeDecoderData * +MimeUUDecoderInit (MimeConverterOutputCallback output_fn, + void *closure) +{ + return mime_decoder_init (mime_uuencode, output_fn, closure); +} + +MimeDecoderData * +MimeYDecoderInit (MimeConverterOutputCallback output_fn, + void *closure) +{ + return mime_decoder_init (mime_yencode, output_fn, closure); +} + +int +MimeDecoderWrite (MimeDecoderData *data, const char *buffer, int32_t size, + int32_t *outSize) +{ + NS_ASSERTION(data, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!data) return -1; + switch(data->encoding) + { + case mime_Base64: + return mime_decode_base64_buffer (data, buffer, size, outSize); + case mime_QuotedPrintable: + return mime_decode_qp_buffer (data, buffer, size, outSize); + case mime_uuencode: + return mime_decode_uue_buffer (data, buffer, size, outSize); + case mime_yencode: + return mime_decode_yenc_buffer (data, buffer, size, outSize); + default: + NS_ERROR("Invalid decoding"); + return -1; + } +} + + +namespace mozilla { +namespace mailnews { + +MimeEncoder::MimeEncoder(OutputCallback callback, void *closure) +: mCallback(callback), + mClosure(closure), + mCurrentColumn(0) +{} + +class Base64Encoder : public MimeEncoder { + unsigned char in_buffer[3]; + int32_t in_buffer_count; + +public: + Base64Encoder(OutputCallback callback, void *closure) + : MimeEncoder(callback, closure), + in_buffer_count(0) {} + virtual ~Base64Encoder() {} + + virtual nsresult Write(const char *buffer, int32_t size) override; + virtual nsresult Flush() override; + +private: + static void Base64EncodeBits(RangedPtr<char> &out, uint32_t bits); +}; + +nsresult Base64Encoder::Write(const char *buffer, int32_t size) +{ + if (size == 0) + return NS_OK; + else if (size < 0) + { + NS_ERROR("Size is less than 0"); + return NS_ERROR_FAILURE; + } + + // If this input buffer is too small, wait until next time. + if (size < (3 - in_buffer_count)) + { + NS_ASSERTION(size == 1 || size == 2, "Unexpected size"); + in_buffer[in_buffer_count++] = buffer[0]; + if (size == 2) + in_buffer[in_buffer_count++] = buffer[1]; + NS_ASSERTION(in_buffer_count < 3, "Unexpected out buffer size"); + return NS_OK; + } + + + // If there are bytes that were put back last time, take them now. + uint32_t i = in_buffer_count, bits = 0; + if (in_buffer_count > 0) bits = in_buffer[0]; + if (in_buffer_count > 1) bits = (bits << 8) + in_buffer[1]; + in_buffer_count = 0; + + // If this buffer is not a multiple of three, put one or two bytes back. + uint32_t excess = ((size + i) % 3); + if (excess) + { + in_buffer[0] = buffer[size - excess]; + if (excess > 1) + in_buffer [1] = buffer[size - excess + 1]; + in_buffer_count = excess; + size -= excess; + NS_ASSERTION (! ((size + i) % 3), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + } + + const uint8_t *in = (const uint8_t *)buffer; + const uint8_t *end = (const uint8_t *)(buffer + size); + MOZ_ASSERT((end - in + i) % 3 == 0, "Need a multiple of 3 bytes to decode"); + + // Populate the out_buffer with base64 data, one line at a time. + char out_buffer[80]; // Max line length will be 80, so this is safe. + RangedPtr<char> out(out_buffer); + while (in < end) + { + // Accumulate the input bits. + while (i < 3) + { + bits = (bits << 8) | *in++; + i++; + } + i = 0; + + Base64EncodeBits(out, bits); + + mCurrentColumn += 4; + if (mCurrentColumn >= 72) + { + // Do a linebreak before column 76. Flush out the line buffer. + mCurrentColumn = 0; + *out++ = '\x0D'; + *out++ = '\x0A'; + nsresult rv = mCallback(out_buffer, (out.get() - out_buffer), mClosure); + NS_ENSURE_SUCCESS(rv, rv); + out = out_buffer; + } + } + + // Write out the unwritten portion of the last line buffer. + if (out.get() > out_buffer) + { + nsresult rv = mCallback(out_buffer, out.get() - out_buffer, mClosure); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult Base64Encoder::Flush() +{ + if (in_buffer_count == 0) + return NS_OK; + + // Since we need to some buffering to get a multiple of three bytes on each + // block, there may be a few bytes left in the buffer after the last block has + // been written. We need to flush those out now. + char buf[4]; + RangedPtr<char> out(buf); + uint32_t bits = ((uint32_t)in_buffer[0]) << 16; + if (in_buffer_count > 1) + bits |= (((uint32_t)in_buffer[1]) << 8); + + Base64EncodeBits(out, bits); + + // Pad with equal-signs. + if (in_buffer_count == 1) + buf[2] = '='; + buf[3] = '='; + + return mCallback(buf, 4, mClosure); +} + +void Base64Encoder::Base64EncodeBits(RangedPtr<char> &out, uint32_t bits) +{ + // Convert 3 bytes to 4 base64 bytes + for (int32_t j = 18; j >= 0; j -= 6) + { + unsigned int k = (bits >> j) & 0x3F; + if (k < 26) *out++ = k + 'A'; + else if (k < 52) *out++ = k - 26 + 'a'; + else if (k < 62) *out++ = k - 52 + '0'; + else if (k == 62) *out++ = '+'; + else if (k == 63) *out++ = '/'; + else MOZ_CRASH("6 bits should only be between 0 and 64"); + } +} + +class QPEncoder : public MimeEncoder { +public: + QPEncoder(OutputCallback callback, void *closure) + : MimeEncoder(callback, closure) {} + virtual ~QPEncoder() {} + + virtual nsresult Write(const char *buffer, int32_t size) override; +}; + +nsresult QPEncoder::Write(const char *buffer, int32_t size) +{ + nsresult rv = NS_OK; + static const char *hexdigits = "0123456789ABCDEF"; + char out_buffer[80]; + RangedPtr<char> out(out_buffer); + bool white = false; + + // Populate the out_buffer with quoted-printable data, one line at a time. + const uint8_t *in = (uint8_t *)buffer; + const uint8_t *end = in + size; + for (; in < end; in++) + { + if (*in == '\r' || *in == '\n') + { + // If it's CRLF, swallow two chars instead of one. + if (in + 1 < end && in[0] == '\r' && in[1] == '\n') + in++; + + // Whitespace cannot be allowed to occur at the end of the line, so we + // back up and replace the whitespace with its code. + if (white) + { + out--; + char whitespace_char = *out; + *out++ = '='; + *out++ = hexdigits[whitespace_char >> 4]; + *out++ = hexdigits[whitespace_char & 0xF]; + } + + // Now write out the newline. + *out++ = '\r'; + *out++ = '\n'; + white = false; + + rv = mCallback(out_buffer, out.get() - out_buffer, mClosure); + NS_ENSURE_SUCCESS(rv, rv); + out = out_buffer; + mCurrentColumn = 0; + } + else if (mCurrentColumn == 0 && *in == '.') + { + // Just to be SMTP-safe, if "." appears in column 0, encode it. + goto HEX; + } + else if (mCurrentColumn == 0 && *in == 'F' + && (in >= end-1 || in[1] == 'r') + && (in >= end-2 || in[2] == 'o') + && (in >= end-3 || in[3] == 'm') + && (in >= end-4 || in[4] == ' ')) + { + // If this line begins with "From " (or it could but we don't have enough + // data in the buffer to be certain), encode the 'F' in hex to avoid + // potential problems with BSD mailbox formats. + goto HEX; + } + else if ((*in >= 33 && *in <= 60) | + (*in >= 62 && *in <= 126)) // Printable characters except for '=' + { + white = false; + *out++ = *in; + mCurrentColumn++; + } + else if (*in == ' ' || *in == '\t') // Whitespace + { + white = true; + *out++ = *in; + mCurrentColumn++; + } + else + { + // Encode the characters here +HEX: + white = false; + *out++ = '='; + *out++ = hexdigits[*in >> 4]; + *out++ = hexdigits[*in & 0xF]; + mCurrentColumn += 3; + } + + MOZ_ASSERT(mCurrentColumn <= 76, "Why haven't we added a line break yet?"); + + if (mCurrentColumn >= 73) // Soft line break for readability + { + *out++ = '='; + *out++ = '\r'; + *out++ = '\n'; + + rv = mCallback(out_buffer, out.get() - out_buffer, mClosure); + NS_ENSURE_SUCCESS(rv, rv); + out = out_buffer; + white = false; + mCurrentColumn = 0; + } + } + + // Write out the unwritten portion of the last line buffer. + if (out.get() != out_buffer) + { + rv = mCallback(out_buffer, out.get() - out_buffer, mClosure); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +MimeEncoder *MimeEncoder::GetBase64Encoder(OutputCallback callback, + void *closure) +{ + return new Base64Encoder(callback, closure); +} + +MimeEncoder *MimeEncoder::GetQPEncoder(OutputCallback callback, void *closure) +{ + return new QPEncoder(callback, closure); +} + +} // namespace mailnews +} // namespace mozilla diff --git a/mailnews/mime/src/mimeeobj.cpp b/mailnews/mime/src/mimeeobj.cpp new file mode 100644 index 000000000..da59a9d62 --- /dev/null +++ b/mailnews/mime/src/mimeeobj.cpp @@ -0,0 +1,236 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "mimeeobj.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include "mimemapl.h" +#include "nsMimeTypes.h" + + +#define MIME_SUPERCLASS mimeLeafClass +MimeDefClass(MimeExternalObject, MimeExternalObjectClass, + mimeExternalObjectClass, &MIME_SUPERCLASS); + +static int MimeExternalObject_initialize (MimeObject *); +static void MimeExternalObject_finalize (MimeObject *); +static int MimeExternalObject_parse_begin (MimeObject *); +static int MimeExternalObject_parse_buffer (const char *, int32_t, MimeObject *); +static int MimeExternalObject_parse_line (const char *, int32_t, MimeObject *); +static int MimeExternalObject_parse_decoded_buffer (const char*, int32_t, MimeObject*); +static bool MimeExternalObject_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs); + +static int +MimeExternalObjectClassInitialize(MimeExternalObjectClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeLeafClass *lclass = (MimeLeafClass *) clazz; + + NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + oclass->initialize = MimeExternalObject_initialize; + oclass->finalize = MimeExternalObject_finalize; + oclass->parse_begin = MimeExternalObject_parse_begin; + oclass->parse_buffer = MimeExternalObject_parse_buffer; + oclass->parse_line = MimeExternalObject_parse_line; + oclass->displayable_inline_p = MimeExternalObject_displayable_inline_p; + lclass->parse_decoded_buffer = MimeExternalObject_parse_decoded_buffer; + return 0; +} + + +static int +MimeExternalObject_initialize (MimeObject *object) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeExternalObject_finalize (MimeObject *object) +{ + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + + +static int +MimeExternalObject_parse_begin (MimeObject *obj) +{ + int status; + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + // If we're writing this object, and we're doing it in raw form, then + // now is the time to inform the backend what the type of this data is. + // + if (obj->output_p && + obj->options && + !obj->options->write_html_p && + !obj->options->state->first_data_written_p) + { + status = MimeObject_output_init(obj, 0); + if (status < 0) return status; + NS_ASSERTION(obj->options->state->first_data_written_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + } + + // + // If we're writing this object as HTML, do all the work now -- just write + // out a table with a link in it. (Later calls to the `parse_buffer' method + // will simply discard the data of the object itself.) + // + if (obj->options && + obj->output_p && + obj->options->write_html_p && + obj->options->output_fn) + { + MimeDisplayOptions newopt = *obj->options; // copy it + char *id = 0; + char *id_url = 0; + char *id_name = 0; + nsCString id_imap; + bool all_headers_p = obj->options->headers == MimeHeadersAll; + + id = mime_part_address (obj); + if (obj->options->missing_parts) + id_imap.Adopt(mime_imap_part_address (obj)); + if (! id) return MIME_OUT_OF_MEMORY; + + if (obj->options && obj->options->url) + { + const char *url = obj->options->url; + if (!id_imap.IsEmpty() && id) + { + // if this is an IMAP part. + id_url = mime_set_url_imap_part(url, id_imap.get(), id); + } + else + { + // This is just a normal MIME part as usual. + id_url = mime_set_url_part(url, id, true); + } + if (!id_url) + { + PR_Free(id); + return MIME_OUT_OF_MEMORY; + } + } + if (!strcmp (id, "0")) + { + PR_Free(id); + id = MimeGetStringByID(MIME_MSG_ATTACHMENT); + } + else + { + const char *p = "Part "; + uint32_t slen = strlen(p) + strlen(id) + 1; + char *s = (char *)PR_MALLOC(slen); + if (!s) + { + PR_Free(id); + PR_Free(id_url); + return MIME_OUT_OF_MEMORY; + } + // we have a valid id + if (id) + id_name = mime_find_suggested_name_of_part(id, obj); + PL_strncpyz(s, p, slen); + PL_strcatn(s, slen, id); + PR_Free(id); + id = s; + } + + if (all_headers_p && + // Don't bother showing all headers on this part if it's the only + // part in the message: in that case, we've already shown these + // headers. + obj->options->state && + obj->options->state->root == obj->parent) + all_headers_p = false; + + newopt.fancy_headers_p = true; + newopt.headers = (all_headers_p ? MimeHeadersAll : MimeHeadersSome); + +/****** +RICHIE SHERRY +GOTTA STILL DO THIS FOR QUOTING! + status = MimeHeaders_write_attachment_box (obj->headers, &newopt, + obj->content_type, + obj->encoding, + id_name? id_name : id, id_url, 0) +*****/ + + // obj->options really owns the storage for this. + newopt.part_to_load = nullptr; + newopt.default_charset = nullptr; + PR_FREEIF(id); + PR_FREEIF(id_url); + PR_FREEIF(id_name); + if (status < 0) return status; + } + + return 0; +} + +static int +MimeExternalObject_parse_buffer (const char *buffer, int32_t size, MimeObject *obj) +{ + NS_ASSERTION(!obj->closed_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (obj->closed_p) return -1; + + // Currently, we always want to stream, in order to determine the size of the + // MIME object. + + /* The data will be base64-decoded and passed to + MimeExternalObject_parse_decoded_buffer. */ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_buffer(buffer, size, obj); +} + + +static int +MimeExternalObject_parse_decoded_buffer (const char *buf, int32_t size, + MimeObject *obj) +{ + /* This is called (by MimeLeafClass->parse_buffer) with blocks of data + that have already been base64-decoded. This will only be called in + the case where we're not emitting HTML, and want access to the raw + data itself. + + We override the `parse_decoded_buffer' method provided by MimeLeaf + because, unlike most children of MimeLeaf, we do not want to line- + buffer the decoded data -- we want to simply pass it along to the + backend, without going through our `parse_line' method. + */ + + /* Don't do a roundtrip through XPConnect when we're only interested in + * metadata and size. This includes when we are writing HTML (otherwise, the + * contents of binary attachments will just get dumped into messages when + * reading them) and the JS emitter (which doesn't care about attachment data + * at all). 0 means ok, the caller just checks for negative return value. + */ + if (obj->options && (obj->options->metadata_only || + obj->options->write_html_p)) + return 0; + else + return MimeObject_write(obj, buf, size, true); +} + + +static int +MimeExternalObject_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + NS_ERROR("This method should never be called (externals do no line buffering)."); + return -1; +} + +static bool +MimeExternalObject_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs) +{ + return false; +} diff --git a/mailnews/mime/src/mimeeobj.h b/mailnews/mime/src/mimeeobj.h new file mode 100644 index 000000000..2b8ade5d5 --- /dev/null +++ b/mailnews/mime/src/mimeeobj.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEEOBJ_H_ +#define _MIMEEOBJ_H_ + +#include "mimeleaf.h" + +/* The MimeExternalObject class represents MIME parts which contain data + which cannot be displayed inline -- application/octet-stream and any + other type that is not otherwise specially handled. (This is not to + be confused with MimeExternalBody, which is the handler for the + message/external-object MIME type only.) + */ + +typedef struct MimeExternalObjectClass MimeExternalObjectClass; +typedef struct MimeExternalObject MimeExternalObject; + +struct MimeExternalObjectClass { + MimeLeafClass leaf; +}; + +extern "C" MimeExternalObjectClass mimeExternalObjectClass; + +struct MimeExternalObject { + MimeLeaf leaf; +}; + +#define MimeExternalObjectClassInitializer(ITYPE,CSUPER) \ + { MimeLeafClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEEOBJ_H_ */ diff --git a/mailnews/mime/src/mimefilt.cpp b/mailnews/mime/src/mimefilt.cpp new file mode 100644 index 000000000..9ea4996b5 --- /dev/null +++ b/mailnews/mime/src/mimefilt.cpp @@ -0,0 +1,399 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* mimefilt.c --- test harness for libmime.a + + This program reads a message from stdin and writes the output of the MIME + parser on stdout. + + Parameters can be passed to the parser through the usual URL mechanism: + + mimefilt BASE-URL?headers=all&rot13 < in > out + + Some parameters can't be affected that way, so some additional switches + may be passed on the command line after the URL: + + -fancy whether fancy headers should be generated (default) + + -no-fancy opposite; this uses the headers used in the cases of + FO_SAVE_AS_TEXT or FO_QUOTE_MESSAGE + + -html whether we should convert to HTML (like FO_PRESENT); + this is the default if no ?part= is specified. + + -raw don't convert to HTML (FO_SAVE_AS); + this is the default if a ?part= is specified. + + -outline at the end, print a debugging overview of the MIME structure + + Before any output comes a blurb listing the content-type, charset, and + various other info that would have been put in the generated URL struct. + It's printed to the beginning of the output because otherwise this out- + of-band data would have been lost. (So the output of this program is, + in fact, a raw HTTP response.) + */ + +#include "mimemsg.h" +#include "prglobal.h" + +#include "key.h" +#include "cert.h" +#include "secrng.h" +#include "secmod.h" +#include "pk11func.h" +#include "nsMimeStringResources.h" + +#ifndef XP_UNIX +ERROR! This is a unix-only file for the "mimefilt" standalone program. + This does not go into libmime.a. +#endif + + +static char * +test_file_type (const char *filename, void *stream_closure) +{ + const char *suf = PL_strrchr(filename, '.'); + if (!suf) + return 0; + suf++; + + if (!PL_strcasecmp(suf, "txt") || + !PL_strcasecmp(suf, "text")) + return strdup("text/plain"); + else if (!PL_strcasecmp(suf, "htm") || + !PL_strcasecmp(suf, "html")) + return strdup("text/html"); + else if (!PL_strcasecmp(suf, "gif")) + return strdup("image/gif"); + else if (!PL_strcasecmp(suf, "svg")) + return strdup("image/svg+xml"); + else if (!PL_strcasecmp(suf, "jpg") || + !PL_strcasecmp(suf, "jpeg")) + return strdup("image/jpeg"); + else if (!PL_strcasecmp(suf, "pjpg") || + !PL_strcasecmp(suf, "pjpeg")) + return strdup("image/pjpeg"); + else if (!PL_strcasecmp(suf, "xbm")) + return strdup("image/x-xbitmap"); + else if (!PL_strcasecmp(suf, "xpm")) + return strdup("image/x-xpixmap"); + else if (!PL_strcasecmp(suf, "xwd")) + return strdup("image/x-xwindowdump"); + else if (!PL_strcasecmp(suf, "bmp")) + return strdup("image/x-MS-bmp"); + else if (!PL_strcasecmp(suf, "au")) + return strdup("audio/basic"); + else if (!PL_strcasecmp(suf, "aif") || + !PL_strcasecmp(suf, "aiff") || + !PL_strcasecmp(suf, "aifc")) + return strdup("audio/x-aiff"); + else if (!PL_strcasecmp(suf, "ps")) + return strdup("application/postscript"); + else + return 0; +} + +static int +test_output_fn(char *buf, int32_t size, void *closure) +{ + FILE *out = (FILE *) closure; + if (out) + return fwrite(buf, sizeof(*buf), size, out); + else + return 0; +} + +static int +test_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) +{ + FILE *out = (FILE *) stream_closure; + fprintf(out, "CONTENT-TYPE: %s", type); + if (charset) + fprintf(out, "; charset=\"%s\"", charset); + if (name) + fprintf(out, "; name=\"%s\"", name); + if (x_mac_type || x_mac_creator) + fprintf(out, "; x-mac-type=\"%s\"; x-mac-creator=\"%s\"", + x_mac_type ? x_mac_type : "", + x_mac_creator ? x_mac_type : ""); + fprintf(out, CRLF CRLF); + return 0; +} + +static void * +test_image_begin(const char *image_url, const char *content_type, + void *stream_closure) +{ + return ((void *) strdup(image_url)); +} + +static void +test_image_end(void *image_closure, int status) +{ + char *url = (char *) image_closure; + if (url) PR_Free(url); +} + +static char * +test_image_make_image_html(void *image_data) +{ + char *url = (char *) image_data; +#if 0 + const char *prefix = "<P><CENTER><IMG SRC=\""; + const char *suffix = "\"></CENTER><P>"; +#else + const char *prefix = ("<P><CENTER><TABLE BORDER=2 CELLPADDING=20" + " BGCOLOR=WHITE>" + "<TR><TD ALIGN=CENTER>" + "an inlined image would have gone here for<BR>"); + const char *suffix = "</TD></TR></TABLE></CENTER><P>"; +#endif + uint32_t buflen = strlen (prefix) + strlen (suffix) + strlen (url) + 20; + char *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 test_image_write_buffer(const char *buf, int32_t size, void *image_closure) +{ + return 0; +} + +static char * +test_passwd_prompt (PK11SlotInfo *slot, void *wincx) +{ + char buf[2048], *s; + fprintf(stdout, "#### Password required: "); + s = fgets(buf, sizeof(buf)-1, stdin); + if (!s) return s; + if (s[strlen(s)-1] == '\r' || + s[strlen(s)-1] == '\n') + s[strlen(s)-1] = '\0'; + return s; +} + + +int +test(FILE *in, FILE *out, + const char *url, + bool fancy_headers_p, + bool html_p, + bool outline_p, + bool dexlate_p, + bool variable_width_plaintext_p) +{ + int status = 0; + MimeObject *obj = 0; + MimeDisplayOptions *opt = new MimeDisplayOptions; +// memset(opt, 0, sizeof(*opt)); + + if (dexlate_p) html_p = false; + + opt->fancy_headers_p = fancy_headers_p; + opt->headers = MimeHeadersSome; + opt->rot13_p = false; + + status = mime_parse_url_options(url, opt); + if (status < 0) + { + PR_Free(opt); + return MIME_OUT_OF_MEMORY; + } + + opt->url = url; + opt->write_html_p = html_p; + opt->dexlate_p = dexlate_p; + opt->output_init_fn = test_output_init_fn; + opt->output_fn = test_output_fn; + opt->charset_conversion_fn= 0; + opt->rfc1522_conversion_p = false; + opt->file_type_fn = test_file_type; + opt->stream_closure = out; + + opt->image_begin = test_image_begin; + opt->image_end = test_image_end; + opt->make_image_html = test_image_make_image_html; + opt->image_write_buffer = test_image_write_buffer; + + opt->variable_width_plaintext_p = variable_width_plaintext_p; + + obj = mime_new ((MimeObjectClass *)&mimeMessageClass, + (MimeHeaders *) NULL, + MESSAGE_RFC822); + if (!obj) + { + PR_Free(opt); + return MIME_OUT_OF_MEMORY; + } + obj->options = opt; + + status = obj->class->initialize(obj); + if (status >= 0) + status = obj->class->parse_begin(obj); + if (status < 0) + { + PR_Free(opt); + PR_Free(obj); + return MIME_OUT_OF_MEMORY; + } + + while (1) + { + char buf[255]; + int size = fread(buf, sizeof(*buf), sizeof(buf), stdin); + if (size <= 0) break; + status = obj->class->parse_buffer(buf, size, obj); + if (status < 0) + { + mime_free(obj); + PR_Free(opt); + return status; + } + } + + status = obj->class->parse_eof(obj, false); + if (status >= 0) + status = obj->class->parse_end(obj, false); + if (status < 0) + { + mime_free(obj); + PR_Free(opt); + return status; + } + + if (outline_p) + { + fprintf(out, "\n\n" + "###############################################################\n"); + obj->class->debug_print(obj, stderr, 0); + fprintf(out, + "###############################################################\n"); + } + + mime_free (obj); + PR_Free(opt); + return 0; +} + + +static char * +test_cdb_name_cb (void *arg, int vers) +{ + static char f[1024]; + if (vers <= 4) + sprintf(f, "%s/.netscape/cert.db", getenv("HOME")); + else + sprintf(f, "%s/.netscape/cert%d.db", getenv("HOME"), vers); + return f; +} + +static char * +test_kdb_name_cb (void *arg, int vers) +{ + static char f[1024]; + if (vers <= 2) + sprintf(f, "%s/.netscape/key.db", getenv("HOME")); + else + sprintf(f, "%s/.netscape/key%d.db", getenv("HOME"), vers); + return f; +} + +extern void SEC_Init(void); + +int +main (int argc, char **argv) +{ + int32_t i = 1; + char *url = ""; + bool fancy_p = true; + bool html_p = true; + bool outline_p = false; + bool dexlate_p = false; + char filename[1000]; + CERTCertDBHandle *cdb_handle; + SECKEYKeyDBHandle *kdb_handle; + + PR_Init("mimefilt", 24, 1, 0); + + cdb_handle = (CERTCertDBHandle *) calloc(1, sizeof(*cdb_handle)); + + if (SECSuccess != CERT_OpenCertDB(cdb_handle, false, test_cdb_name_cb, NULL)) + CERT_OpenVolatileCertDB(cdb_handle); + CERT_SetDefaultCertDB(cdb_handle); + + RNG_RNGInit(); + + kdb_handle = SECKEY_OpenKeyDB(false, test_kdb_name_cb, NULL); + SECKEY_SetDefaultKeyDB(kdb_handle); + + PK11_SetPasswordFunc(test_passwd_prompt); + + sprintf(filename, "%s/.netscape/secmodule.db", getenv("HOME")); + SECMOD_init(filename); + + SEC_Init(); + + + if (i < argc) + { + if (argv[i][0] == '-') + url = strdup(""); + else + url = argv[i++]; + } + + if (url && + (PL_strstr(url, "?part=") || + PL_strstr(url, "&part="))) + html_p = false; + + while (i < argc) + { + if (!strcmp(argv[i], "-fancy")) + fancy_p = true; + else if (!strcmp(argv[i], "-no-fancy")) + fancy_p = false; + else if (!strcmp(argv[i], "-html")) + html_p = true; + else if (!strcmp(argv[i], "-raw")) + html_p = false; + else if (!strcmp(argv[i], "-outline")) + outline_p = true; + else if (!strcmp(argv[i], "-dexlate")) + dexlate_p = true; + else + { + fprintf(stderr, + "usage: %s [ URL [ -fancy | -no-fancy | -html | -raw | -outline | -dexlate ]]\n" + " < message/rfc822 > output\n", + (PL_strrchr(argv[0], '/') ? + PL_strrchr(argv[0], '/') + 1 : + argv[0])); + i = 1; + goto FAIL; + } + i++; + } + + i = test(stdin, stdout, url, fancy_p, html_p, outline_p, dexlate_p, true); + fprintf(stdout, "\n"); + fflush(stdout); + + FAIL: + + CERT_ClosePermCertDB(cdb_handle); + SECKEY_CloseKeyDB(kdb_handle); + + exit(i); +} diff --git a/mailnews/mime/src/mimehdrs.cpp b/mailnews/mime/src/mimehdrs.cpp new file mode 100644 index 000000000..6d187ed52 --- /dev/null +++ b/mailnews/mime/src/mimehdrs.cpp @@ -0,0 +1,888 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "msgCore.h" +#include "mimei.h" +#include "prmem.h" +#include "prlog.h" +#include "plstr.h" +#include "mimebuf.h" +#include "mimemoz2.h" +#include "nsIMimeEmitter.h" +#include "nsMsgMessageFlags.h" +#include "comi18n.h" +#include "nsMailHeaders.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include "nsMsgI18N.h" +#include "mimehdrs.h" +#include "nsIMIMEHeaderParam.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsMemory.h" +#include <ctype.h> +#include "nsMsgUtils.h" + +// Forward declares... +int32_t MimeHeaders_build_heads_list(MimeHeaders *hdrs); + +void +MimeHeaders_convert_header_value(MimeDisplayOptions *opt, nsCString &value, + bool convert_charset_only) +{ + if (value.IsEmpty()) + return; + + if (convert_charset_only) + { + nsAutoCString output; + ConvertRawBytesToUTF8(value, opt->default_charset, output); + value.Assign(output); + return; + } + + if (opt && opt->rfc1522_conversion_p) + { + nsAutoCString temporary; + MIME_DecodeMimeHeader(value.get(), opt->default_charset, + opt->override_charset, true, temporary); + + if (!temporary.IsEmpty()) + { + value = temporary; + } + } + else + { + // This behavior, though highly unusual, was carefully preserved + // from the previous implementation. It may be that this is dead + // code, in which case opt->rfc1522_conversion_p is no longer + // needed. + value.Truncate(); + } +} + +MimeHeaders * +MimeHeaders_new (void) +{ + MimeHeaders *hdrs = (MimeHeaders *) PR_MALLOC(sizeof(MimeHeaders)); + if (!hdrs) return 0; + + memset(hdrs, 0, sizeof(*hdrs)); + hdrs->done_p = false; + + return hdrs; +} + +void +MimeHeaders_free (MimeHeaders *hdrs) +{ + if (!hdrs) return; + PR_FREEIF(hdrs->all_headers); + PR_FREEIF(hdrs->heads); + PR_FREEIF(hdrs->obuffer); + PR_FREEIF(hdrs->munged_subject); + hdrs->obuffer_fp = 0; + hdrs->obuffer_size = 0; + +# ifdef DEBUG__ + { + int i, size = sizeof(*hdrs); + uint32_t *array = (uint32_t*) hdrs; + for (i = 0; i < (size / sizeof(*array)); i++) + array[i] = (uint32_t) 0xDEADBEEF; + } +# endif /* DEBUG */ + + PR_Free(hdrs); +} + +int +MimeHeaders_parse_line (const char *buffer, int32_t size, MimeHeaders *hdrs) +{ + int status = 0; + int desired_size; + + NS_ASSERTION(hdrs, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48"); + if (!hdrs) return -1; + + /* Don't try and feed me more data after having fed me a blank line... */ + NS_ASSERTION(!hdrs->done_p, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48"); + if (hdrs->done_p) return -1; + + if (!buffer || size == 0 || *buffer == '\r' || *buffer == '\n') + { + /* If this is a blank line, we're done. + */ + hdrs->done_p = true; + return MimeHeaders_build_heads_list(hdrs); + } + + /* Tack this data on to the end of our copy. + */ + desired_size = hdrs->all_headers_fp + size + 1; + if (desired_size >= hdrs->all_headers_size) + { + status = mime_GrowBuffer (desired_size, sizeof(char), 255, + &hdrs->all_headers, &hdrs->all_headers_size); + if (status < 0) return status; + } + memcpy(hdrs->all_headers+hdrs->all_headers_fp, buffer, size); + hdrs->all_headers_fp += size; + + return 0; +} + +MimeHeaders * +MimeHeaders_copy (MimeHeaders *hdrs) +{ + MimeHeaders *hdrs2; + if (!hdrs) return 0; + + hdrs2 = (MimeHeaders *) PR_MALLOC(sizeof(*hdrs)); + if (!hdrs2) return 0; + memset(hdrs2, 0, sizeof(*hdrs2)); + + if (hdrs->all_headers) + { + hdrs2->all_headers = (char *) PR_MALLOC(hdrs->all_headers_fp); + if (!hdrs2->all_headers) + { + PR_Free(hdrs2); + return 0; + } + memcpy(hdrs2->all_headers, hdrs->all_headers, hdrs->all_headers_fp); + + hdrs2->all_headers_fp = hdrs->all_headers_fp; + hdrs2->all_headers_size = hdrs->all_headers_fp; + } + + hdrs2->done_p = hdrs->done_p; + + if (hdrs->heads) + { + int i; + hdrs2->heads = (char **) PR_MALLOC(hdrs->heads_size + * sizeof(*hdrs->heads)); + if (!hdrs2->heads) + { + PR_FREEIF(hdrs2->all_headers); + PR_Free(hdrs2); + return 0; + } + hdrs2->heads_size = hdrs->heads_size; + for (i = 0; i < hdrs->heads_size; i++) + { + hdrs2->heads[i] = (hdrs2->all_headers + + (hdrs->heads[i] - hdrs->all_headers)); + } + } + return hdrs2; +} + +int +MimeHeaders_build_heads_list(MimeHeaders *hdrs) +{ + char *s; + char *end; + int i; + NS_ASSERTION(hdrs, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!hdrs) return -1; + + NS_ASSERTION(hdrs->done_p && !hdrs->heads, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!hdrs->done_p || hdrs->heads) + return -1; + + if (hdrs->all_headers_fp == 0) + { + /* Must not have been any headers (we got the blank line right away.) */ + PR_FREEIF (hdrs->all_headers); + hdrs->all_headers_size = 0; + return 0; + } + + /* At this point, we might as well realloc all_headers back down to the + minimum size it must be (it could be up to 1k bigger.) But don't + bother if we're only off by a tiny bit. */ + NS_ASSERTION(hdrs->all_headers_fp <= hdrs->all_headers_size, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (hdrs->all_headers_fp + 60 <= hdrs->all_headers_size) + { + char *ls = (char *)PR_Realloc(hdrs->all_headers, hdrs->all_headers_fp); + if (ls) /* can this ever fail? we're making it smaller... */ + { + hdrs->all_headers = ls; /* in case it got relocated */ + hdrs->all_headers_size = hdrs->all_headers_fp; + } + } + + /* First go through and count up the number of headers in the block. + */ + end = hdrs->all_headers + hdrs->all_headers_fp; + for (s = hdrs->all_headers; s < end; s++) + { + if (s < (end-1) && s[0] == '\r' && s[1] == '\n') /* CRLF -> LF */ + s++; + + if ((s[0] == '\r' || s[0] == '\n') && /* we're at a newline, and */ + (s >= (end-1) || /* we're at EOF, or */ + !(s[1] == ' ' || s[1] == '\t'))) /* next char is nonwhite */ + hdrs->heads_size++; + } + + /* Now allocate storage for the pointers to each of those headers. + */ + hdrs->heads = (char **) PR_MALLOC((hdrs->heads_size + 1) * sizeof(char *)); + if (!hdrs->heads) + return MIME_OUT_OF_MEMORY; + memset(hdrs->heads, 0, (hdrs->heads_size + 1) * sizeof(char *)); + + /* Now make another pass through the headers, and this time, record the + starting position of each header. + */ + + i = 0; + hdrs->heads[i++] = hdrs->all_headers; + s = hdrs->all_headers; + + while (s < end) + { + SEARCH_NEWLINE: + while (s < end && *s != '\r' && *s != '\n') + s++; + + if (s >= end) + break; + + /* If "\r\n " or "\r\n\t" is next, that doesn't terminate the header. */ + else if (s+2 < end && + (s[0] == '\r' && s[1] == '\n') && + (s[2] == ' ' || s[2] == '\t')) + { + s += 3; + goto SEARCH_NEWLINE; + } + /* If "\r " or "\r\t" or "\n " or "\n\t" is next, that doesn't terminate + the header either. */ + else if (s+1 < end && + (s[0] == '\r' || s[0] == '\n') && + (s[1] == ' ' || s[1] == '\t')) + { + s += 2; + goto SEARCH_NEWLINE; + } + + /* At this point, `s' points before a header-terminating newline. + Move past that newline, and store that new position in `heads'. + */ + if (*s == '\r') + s++; + + if (s >= end) + break; + + if (*s == '\n') + s++; + + if (s < end) + { + NS_ASSERTION(! (i > hdrs->heads_size), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (i > hdrs->heads_size) + return -1; + hdrs->heads[i++] = s; + } + } + + return 0; +} + +char * +MimeHeaders_get (MimeHeaders *hdrs, const char *header_name, + bool strip_p, bool all_p) +{ + int i; + int name_length; + char *result = 0; + + if (!hdrs) return 0; + NS_ASSERTION(header_name, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!header_name) return 0; + + /* Specifying strip_p and all_p at the same time doesn't make sense... */ + NS_ASSERTION(!(strip_p && all_p), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + /* One shouldn't be trying to read headers when one hasn't finished + parsing them yet... but this can happen if the message ended + prematurely, and has no body at all (as opposed to a null body, + which is more normal.) So, if we try to read from the headers, + let's assume that the headers are now finished. If they aren't + in fact finished, then a later attempt to write to them will assert. + */ + if (!hdrs->done_p) + { + int status; + hdrs->done_p = true; + status = MimeHeaders_build_heads_list(hdrs); + if (status < 0) return 0; + } + + if (!hdrs->heads) /* Must not have been any headers. */ + { + NS_ASSERTION(hdrs->all_headers_fp == 0, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + return 0; + } + + name_length = strlen(header_name); + + for (i = 0; i < hdrs->heads_size; i++) + { + char *head = hdrs->heads[i]; + char *end = (i == hdrs->heads_size-1 + ? hdrs->all_headers + hdrs->all_headers_fp + : hdrs->heads[i+1]); + char *colon, *ocolon; + + NS_ASSERTION(head, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!head) continue; + + /* Quick hack to skip over BSD Mailbox delimiter. */ + if (i == 0 && head[0] == 'F' && !strncmp(head, "From ", 5)) + continue; + + /* Find the colon. */ + for (colon = head; colon < end; colon++) + if (*colon == ':') break; + + if (colon >= end) continue; + + /* Back up over whitespace before the colon. */ + ocolon = colon; + for (; colon > head && IS_SPACE(colon[-1]); colon--) + ; + + /* If the strings aren't the same length, it doesn't match. */ + if (name_length != colon - head ) + continue; + + /* If the strings differ, it doesn't match. */ + if (PL_strncasecmp(header_name, head, name_length)) + continue; + + /* Otherwise, we've got a match. */ + { + char *contents = ocolon + 1; + char *s; + + /* Skip over whitespace after colon. */ + while (contents < end && IS_SPACE(contents[0])) { + /* Mac or Unix style line break, followed by space or tab. */ + if (contents < (end - 1) && + (contents[0] == '\r' || contents[0] == '\n') && + (contents[1] == ' ' || contents[1] == '\t')) + contents += 2; + /* Windows style line break, followed by space or tab. */ + else if (contents < (end - 2) && + contents[0] == '\r' && contents[1] == '\n' && + (contents[2] == ' ' || contents[2] == '\t')) + contents += 3; + /* Any space or tab. */ + else if (contents[0] == ' ' || contents[0] == '\t') + contents++; + /* If we get here, it's because this character is a line break + followed by non-whitespace, or a line break followed by + another line break + */ + else { + end = contents; + break; + } + } + + /* If we're supposed to strip at the first token, pull `end' back to + the first whitespace or ';' after the first token. + */ + if (strip_p) + { + for (s = contents; + s < end && *s != ';' && *s != ',' && !IS_SPACE(*s); + s++) + ; + end = s; + } + + /* Now allocate some storage. + If `result' already has a value, enlarge it. + Otherwise, just allocate a block. + `s' gets set to the place where the new data goes. + */ + if (!result) + { + result = (char *) PR_MALLOC(end - contents + 1); + if (!result) + return 0; + s = result; + } + else + { + int32_t L = strlen(result); + s = (char *) PR_Realloc(result, (L + (end - contents + 10))); + if (!s) + { + PR_Free(result); + return 0; + } + result = s; + s = result + L; + + /* Since we are tacking more data onto the end of the header + field, we must make it be a well-formed continuation line, + by separating the old and new data with CR-LF-TAB. + */ + *s++ = ','; /* #### only do this for addr headers? */ + *s++ = MSG_LINEBREAK[0]; +# if (MSG_LINEBREAK_LEN == 2) + *s++ = MSG_LINEBREAK[1]; +# endif + *s++ = '\t'; + } + + + /* Take off trailing whitespace... */ + while (end > contents && IS_SPACE(end[-1])) + end--; + + if (end > contents) + { + /* Now copy the header's contents in... + */ + memcpy(s, contents, end - contents); + s[end - contents] = 0; + } + else + { + s[0] = 0; + } + + /* If we only wanted the first occurence of this header, we're done. */ + if (!all_p) break; + } + } + + if (result && !*result) /* empty string */ + { + PR_Free(result); + return 0; + } + + return result; +} + +char * +MimeHeaders_get_parameter (const char *header_value, const char *parm_name, + char **charset, char **language) +{ + if (!header_value || !parm_name || !*header_value || !*parm_name) + return nullptr; + + nsresult rv; + nsCOMPtr <nsIMIMEHeaderParam> mimehdrpar = + do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv); + + if (NS_FAILED(rv)) + return nullptr; + + nsCString result; + rv = mimehdrpar->GetParameterInternal(header_value, parm_name, charset, + language, getter_Copies(result)); + return NS_SUCCEEDED(rv) ? PL_strdup(result.get()) : nullptr; +} + +#define MimeHeaders_write(HDRS,OPT,DATA,LENGTH) \ + MimeOptions_write((HDRS), (OPT), (DATA), (LENGTH), true); + + +#define MimeHeaders_grow_obuffer(hdrs, desired_size) \ + ((((long) (desired_size)) >= ((long) (hdrs)->obuffer_size)) ? \ + mime_GrowBuffer ((desired_size), sizeof(char), 255, \ + &(hdrs)->obuffer, &(hdrs)->obuffer_size) \ + : 0) + +int +MimeHeaders_write_all_headers (MimeHeaders *hdrs, MimeDisplayOptions *opt, bool attachment) +{ + int status = 0; + int i; + bool wrote_any_p = false; + + NS_ASSERTION(hdrs, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!hdrs) + return -1; + + /* One shouldn't be trying to read headers when one hasn't finished + parsing them yet... but this can happen if the message ended + prematurely, and has no body at all (as opposed to a null body, + which is more normal.) So, if we try to read from the headers, + let's assume that the headers are now finished. If they aren't + in fact finished, then a later attempt to write to them will assert. + */ + if (!hdrs->done_p) + { + hdrs->done_p = true; + status = MimeHeaders_build_heads_list(hdrs); + if (status < 0) return 0; + } + + char *charset = nullptr; + if (opt->format_out == nsMimeOutput::nsMimeMessageSaveAs) + { + if (opt->override_charset) + charset = PL_strdup(opt->default_charset); + else + { + char *contentType = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false); + if (contentType) + charset = MimeHeaders_get_parameter(contentType, HEADER_PARM_CHARSET, nullptr, nullptr); + PR_FREEIF(contentType); + } + } + + for (i = 0; i < hdrs->heads_size; i++) + { + char *head = hdrs->heads[i]; + char *end = (i == hdrs->heads_size-1 + ? hdrs->all_headers + hdrs->all_headers_fp + : hdrs->heads[i+1]); + char *colon, *ocolon; + char *contents = end; + + /* Hack for BSD Mailbox delimiter. */ + if (i == 0 && head[0] == 'F' && !strncmp(head, "From ", 5)) + { + /* For now, we don't really want this header to be output so + we are going to just continue */ + continue; + /* colon = head + 4; contents = colon + 1; */ + } + else + { + /* Find the colon. */ + for (colon = head; colon < end && *colon != ':'; colon++) + ; + + /* Back up over whitespace before the colon. */ + ocolon = colon; + for (; colon > head && IS_SPACE(colon[-1]); colon--) + ; + + contents = ocolon + 1; + } + + /* Skip over whitespace after colon. */ + while (contents < end && IS_SPACE(*contents)) + contents++; + + /* Take off trailing whitespace... */ + while (end > contents && IS_SPACE(end[-1])) + end--; + + nsAutoCString name(Substring(head, colon)); + nsAutoCString hdr_value; + + if ( (end - contents) > 0 ) + { + hdr_value = Substring(contents, end); + } + + // MW Fixme: more? + bool convert_charset_only = + MsgLowerCaseEqualsLiteral(name, "to") || MsgLowerCaseEqualsLiteral(name, "from") || + MsgLowerCaseEqualsLiteral(name, "cc") || MsgLowerCaseEqualsLiteral(name, "bcc") || + MsgLowerCaseEqualsLiteral(name, "reply-to") || MsgLowerCaseEqualsLiteral(name, "sender"); + MimeHeaders_convert_header_value(opt, hdr_value, convert_charset_only); + // if we're saving as html, we need to convert headers from utf8 to message charset, if any + if (opt->format_out == nsMimeOutput::nsMimeMessageSaveAs && charset) + { + nsAutoCString convertedStr; + if (NS_SUCCEEDED(ConvertFromUnicode(charset, NS_ConvertUTF8toUTF16(hdr_value), + convertedStr))) + { + hdr_value = convertedStr; + } + } + + if (attachment) { + if (NS_FAILED(mimeEmitterAddAttachmentField(opt, name.get(), hdr_value.get()))) + status = -1; + } + else { + if (NS_FAILED(mimeEmitterAddHeaderField(opt, name.get(), hdr_value.get()))) + status = -1; + } + + if (status < 0) return status; + if (!wrote_any_p) + wrote_any_p = (status > 0); + } + mimeEmitterAddAllHeaders(opt, hdrs->all_headers, hdrs->all_headers_fp); + PR_FREEIF(charset); + + return 1; +} + +/* Strip CR+LF runs within (original). + Since the string at (original) can only shrink, + this conversion is done in place. (original) + is returned. */ +extern "C" char * +MIME_StripContinuations(char *original) +{ + char *p1, *p2; + + /* If we were given a null string, return it as is */ + if (!original) return NULL; + + /* Start source and dest pointers at the beginning */ + p1 = p2 = original; + + while (*p2) { + /* p2 runs ahead at (CR and/or LF) */ + if ((p2[0] == '\r') || (p2[0] == '\n')) + p2++; + else if (p2 > p1) + *p1++ = *p2++; + else { + p1++; + p2++; + } + } + *p1 = '\0'; + + return original; +} + +extern int16_t INTL_DefaultMailToWinCharSetID(int16_t csid); + +/* Given text purporting to be a qtext header value, strip backslashes that + may be escaping other chars in the string. */ +char * +mime_decode_filename(const char *name, const char *charset, + MimeDisplayOptions *opt) +{ + nsresult rv; + nsCOMPtr <nsIMIMEHeaderParam> mimehdrpar = + do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv); + + if (NS_FAILED(rv)) + return nullptr; + nsAutoCString result; + rv = mimehdrpar->DecodeParameter(nsDependentCString(name), charset, + opt ? opt->default_charset : nullptr, + opt ? opt->override_charset : false, + result); + return NS_SUCCEEDED(rv) ? PL_strdup(result.get()) : nullptr; +} + +/* Pull the name out of some header or another. Order is: + Content-Disposition: XXX; filename=NAME (RFC 1521/1806) + Content-Type: XXX; name=NAME (RFC 1341) + Content-Name: NAME (no RFC, but seen to occur) + X-Sun-Data-Name: NAME (no RFC, but used by MailTool) + */ +char * +MimeHeaders_get_name(MimeHeaders *hdrs, MimeDisplayOptions *opt) +{ + char *s = 0, *name = 0, *cvt = 0; + char *charset = nullptr; // for RFC2231 support + + s = MimeHeaders_get(hdrs, HEADER_CONTENT_DISPOSITION, false, false); + if (s) + { + name = MimeHeaders_get_parameter(s, HEADER_PARM_FILENAME, &charset, NULL); + PR_Free(s); + } + + if (! name) + { + s = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false); + if (s) + { + free(charset); + + name = MimeHeaders_get_parameter(s, HEADER_PARM_NAME, &charset, NULL); + PR_Free(s); + } + } + + if (! name) + name = MimeHeaders_get (hdrs, HEADER_CONTENT_NAME, false, false); + + if (! name) + name = MimeHeaders_get (hdrs, HEADER_X_SUN_DATA_NAME, false, false); + + if (name) + { + /* First remove continuation delimiters (CR+LF+space), then + remove escape ('\\') characters, then attempt to decode + mime-2 encoded-words. The latter two are done in + mime_decode_filename. + */ + MIME_StripContinuations(name); + + /* Argh. What we should do if we want to be robust is to decode qtext + in all appropriate headers. Unfortunately, that would be too scary + at this juncture. So just decode qtext/mime2 here. */ + cvt = mime_decode_filename(name, charset, opt); + + free(charset); + + if (cvt && cvt != name) + { + PR_Free(name); + name = cvt; + } + } + + return name; +} + +#ifdef XP_UNIX +/* This piece of junk is so that I can use BBDB with Mozilla. + = Put bbdb-srv.perl on your path. + = Put bbdb-srv.el on your lisp path. + = Make sure gnudoit (comes with xemacs) is on your path. + = Put (gnuserv-start) in ~/.emacs + = setenv NS_MSG_DISPLAY_HOOK bbdb-srv.perl + */ +void +MimeHeaders_do_unix_display_hook_hack(MimeHeaders *hdrs) +{ + static const char *cmd = 0; + if (!cmd) + { + /* The first time we're invoked, look up the command in the + environment. Use "" as the `no command' tag. */ + cmd = getenv("NS_MSG_DISPLAY_HOOK"); + if (!cmd) + cmd = ""; + } + + /* Invoke "cmd" at the end of a pipe, and give it the headers on stdin. + The command is expected to be safe from hostile input!! + */ + if (cmd && *cmd) + { + FILE *fp = popen(cmd, "w"); + if (fp) + { + fwrite(hdrs->all_headers, 1, hdrs->all_headers_fp, fp); + pclose(fp); + } + } +} +#endif /* XP_UNIX */ + +static void +MimeHeaders_compact (MimeHeaders *hdrs) +{ + NS_ASSERTION(hdrs, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48"); + if (!hdrs) return; + + PR_FREEIF(hdrs->obuffer); + hdrs->obuffer_fp = 0; + hdrs->obuffer_size = 0; + + /* These really shouldn't have gotten out of whack again. */ + NS_ASSERTION(hdrs->all_headers_fp <= hdrs->all_headers_size && + hdrs->all_headers_fp + 100 > hdrs->all_headers_size, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48"); +} + +/* Writes the headers as text/plain. + This writes out a blank line after the headers, unless + dont_write_content_type is true, in which case the header-block + is not closed off, and none of the Content- headers are written. + */ +int +MimeHeaders_write_raw_headers (MimeHeaders *hdrs, MimeDisplayOptions *opt, + bool dont_write_content_type) +{ + int status; + + if (hdrs && !hdrs->done_p) + { + hdrs->done_p = true; + status = MimeHeaders_build_heads_list(hdrs); + if (status < 0) return 0; + } + + if (!dont_write_content_type) + { + char nl[] = MSG_LINEBREAK; + if (hdrs) + { + status = MimeHeaders_write(hdrs, opt, hdrs->all_headers, + hdrs->all_headers_fp); + if (status < 0) return status; + } + status = MimeHeaders_write(hdrs, opt, nl, strlen(nl)); + if (status < 0) return status; + } + else if (hdrs) + { + int32_t i; + for (i = 0; i < hdrs->heads_size; i++) + { + char *head = hdrs->heads[i]; + char *end = (i == hdrs->heads_size-1 + ? hdrs->all_headers + hdrs->all_headers_fp + : hdrs->heads[i+1]); + + NS_ASSERTION(head, "1.22 <rhp@netscape.com> 22 Aug 1999 08:48"); + if (!head) continue; + + /* Don't write out any Content- header. */ + if (!PL_strncasecmp(head, "Content-", 8)) + continue; + + /* Write out this (possibly multi-line) header. */ + status = MimeHeaders_write(hdrs, opt, head, end - head); + if (status < 0) return status; + } + } + + if (hdrs) + MimeHeaders_compact(hdrs); + + return 0; +} + +// XXX Fix this XXX // +char * +MimeHeaders_open_crypto_stamp(void) +{ + return nullptr; +} + +char * +MimeHeaders_finish_open_crypto_stamp(void) +{ + return nullptr; +} + +char * +MimeHeaders_close_crypto_stamp(void) +{ + return nullptr; +} + +char * +MimeHeaders_make_crypto_stamp(bool encrypted_p, + bool signed_p, + bool good_p, + bool unverified_p, + bool close_parent_stamp_p, + const char *stamp_url) +{ + return nullptr; +} diff --git a/mailnews/mime/src/mimehdrs.h b/mailnews/mime/src/mimehdrs.h new file mode 100644 index 000000000..e854633af --- /dev/null +++ b/mailnews/mime/src/mimehdrs.h @@ -0,0 +1,88 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEHDRS_H_ +#define _MIMEHDRS_H_ + +#include "modlmime.h" + +/* This file defines the interface to message-header parsing and formatting + code, including conversion to HTML. */ + +/* Other structs defined later in this file. + */ + +/* Creation and destruction. + */ +extern MimeHeaders *MimeHeaders_new (void); +//extern void MimeHeaders_free (MimeHeaders *); +//extern MimeHeaders *MimeHeaders_copy (MimeHeaders *); + + +/* Feed this method the raw data from which you would like a header + block to be parsed, one line at a time. Feed it a blank line when + you're done. Returns negative on allocation-related failure. + */ +extern int MimeHeaders_parse_line (const char *buffer, int32_t size, + MimeHeaders *hdrs); + + +/* Converts a MimeHeaders object into HTML, by writing to the provided + output function. + */ +extern int MimeHeaders_write_headers_html (MimeHeaders *hdrs, + MimeDisplayOptions *opt, + bool attachment); + +/* + * Writes all headers to the mime emitter. + */ +extern int +MimeHeaders_write_all_headers (MimeHeaders *, MimeDisplayOptions *, bool); + +/* Writes the headers as text/plain. + This writes out a blank line after the headers, unless + dont_write_content_type is true, in which case the header-block + is not closed off, and none of the Content- headers are written. + */ +extern int MimeHeaders_write_raw_headers (MimeHeaders *hdrs, + MimeDisplayOptions *opt, + bool dont_write_content_type); + + +/* Some crypto-related HTML-generated utility routines. + * XXX This may not be needed. XXX + */ +extern char *MimeHeaders_open_crypto_stamp(void); +extern char *MimeHeaders_finish_open_crypto_stamp(void); +extern char *MimeHeaders_close_crypto_stamp(void); +extern char *MimeHeaders_make_crypto_stamp(bool encrypted_p, + + bool signed_p, + + bool good_p, + + bool unverified_p, + + bool close_parent_stamp_p, + + const char *stamp_url); + +/* Does all the heuristic silliness to find the filename in the given headers. + */ +extern char *MimeHeaders_get_name(MimeHeaders *hdrs, MimeDisplayOptions *opt); + +extern char *mime_decode_filename(const char *name, const char* charset, + MimeDisplayOptions *opt); + +extern "C" char * MIME_StripContinuations(char *original); + +/** + * Convert this value to a unicode string, based on the charset. + */ +extern void MimeHeaders_convert_header_value(MimeDisplayOptions *opt, + nsCString &value, + bool convert_charset_only); +#endif /* _MIMEHDRS_H_ */ diff --git a/mailnews/mime/src/mimei.cpp b/mailnews/mime/src/mimei.cpp new file mode 100644 index 000000000..c0a134a62 --- /dev/null +++ b/mailnews/mime/src/mimei.cpp @@ -0,0 +1,1920 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ + +#include "nsCOMPtr.h" +#include "mimeobj.h" /* MimeObject (abstract) */ +#include "mimecont.h" /* |--- MimeContainer (abstract) */ +#include "mimemult.h" /* | |--- MimeMultipart (abstract) */ +#include "mimemmix.h" /* | | |--- MimeMultipartMixed */ +#include "mimemdig.h" /* | | |--- MimeMultipartDigest */ +#include "mimempar.h" /* | | |--- MimeMultipartParallel */ +#include "mimemalt.h" /* | | |--- MimeMultipartAlternative */ +#include "mimemrel.h" /* | | |--- MimeMultipartRelated */ +#include "mimemapl.h" /* | | |--- MimeMultipartAppleDouble */ +#include "mimesun.h" /* | | |--- MimeSunAttachment */ +#include "mimemsig.h" /* | | |--- MimeMultipartSigned (abstract)*/ +#ifdef ENABLE_SMIME +#include "mimemcms.h" /* | | |---MimeMultipartSignedCMS */ +#endif +#include "mimecryp.h" /* | |--- MimeEncrypted (abstract) */ +#ifdef ENABLE_SMIME +#include "mimecms.h" /* | | |--- MimeEncryptedPKCS7 */ +#endif +#include "mimemsg.h" /* | |--- MimeMessage */ +#include "mimeunty.h" /* | |--- MimeUntypedText */ +#include "mimeleaf.h" /* |--- MimeLeaf (abstract) */ +#include "mimetext.h" /* | |--- MimeInlineText (abstract) */ +#include "mimetpla.h" /* | | |--- MimeInlineTextPlain */ +#include "mimethpl.h" /* | | | |--- M.I.TextHTMLAsPlaintext */ +#include "mimetpfl.h" /* | | |--- MimeInlineTextPlainFlowed */ +#include "mimethtm.h" /* | | |--- MimeInlineTextHTML */ +#include "mimethsa.h" /* | | | |--- M.I.TextHTMLSanitized */ +#include "mimeTextHTMLParsed.h" /*| | |--- M.I.TextHTMLParsed */ +#include "mimetric.h" /* | | |--- MimeInlineTextRichtext */ +#include "mimetenr.h" /* | | | |--- MimeInlineTextEnriched */ +/* SUPPORTED VIA PLUGIN | | |--- MimeInlineTextVCard */ +#include "mimeiimg.h" /* | |--- MimeInlineImage */ +#include "mimeeobj.h" /* | |--- MimeExternalObject */ +#include "mimeebod.h" /* |--- MimeExternalBody */ + /* If you add classes here,also add them to mimei.h */ +#include "prlog.h" +#include "prmem.h" +#include "prenv.h" +#include "plstr.h" +#include "prlink.h" +#include "prprf.h" +#include "mimecth.h" +#include "mimebuf.h" +#include "nsIServiceManager.h" +#include "mimemoz2.h" +#include "nsIMimeContentTypeHandler.h" +#include "nsIComponentManager.h" +#include "nsCategoryManagerUtils.h" +#include "nsXPCOMCID.h" +#include "nsISimpleMimeConverter.h" +#include "nsSimpleMimeConverterStub.h" +#include "nsTArray.h" +#include "nsMimeStringResources.h" +#include "nsMimeTypes.h" +#include "nsMsgUtils.h" +#include "nsIPrefBranch.h" +#include "mozilla/Preferences.h" +#include "imgLoader.h" + +#include "nsIMsgMailNewsUrl.h" +#include "nsIMsgHdr.h" + +using namespace mozilla; + +// forward declaration +void getMsgHdrForCurrentURL(MimeDisplayOptions *opts, nsIMsgDBHdr ** aMsgHdr); + +#define IMAP_EXTERNAL_CONTENT_HEADER "X-Mozilla-IMAP-Part" +#define EXTERNAL_ATTACHMENT_URL_HEADER "X-Mozilla-External-Attachment-URL" + +/* ========================================================================== + Allocation and destruction + ========================================================================== + */ +static int mime_classinit(MimeObjectClass *clazz); + +/* + * These are the necessary defines/variables for doing + * content type handlers in external plugins. + */ +typedef struct { + char content_type[128]; + bool force_inline_display; +} cthandler_struct; + +nsTArray<cthandler_struct*> *ctHandlerList = NULL; + +/* + * This will return TRUE if the content_type is found in the + * list, FALSE if it is not found. + */ +bool +find_content_type_attribs(const char *content_type, + bool *force_inline_display) +{ + *force_inline_display = false; + if (!ctHandlerList) + return false; + + for (size_t i = 0; i < ctHandlerList->Length(); i++) + { + cthandler_struct *ptr = ctHandlerList->ElementAt(i); + if (PL_strcasecmp(content_type, ptr->content_type) == 0) + { + *force_inline_display = ptr->force_inline_display; + return true; + } + } + + return false; +} + +void +add_content_type_attribs(const char *content_type, + contentTypeHandlerInitStruct *ctHandlerInfo) +{ + cthandler_struct *ptr = NULL; + bool force_inline_display; + + if (find_content_type_attribs(content_type, &force_inline_display)) + return; + + if ( (!content_type) || (!ctHandlerInfo) ) + return; + + if (!ctHandlerList) + ctHandlerList = new nsTArray<cthandler_struct*>(); + + if (!ctHandlerList) + return; + + ptr = (cthandler_struct *) PR_MALLOC(sizeof(cthandler_struct)); + if (!ptr) + return; + + PL_strncpy(ptr->content_type, content_type, sizeof(ptr->content_type)); + ptr->force_inline_display = ctHandlerInfo->force_inline_display; + ctHandlerList->AppendElement(ptr); +} + +/* + * This routine will find all content type handler for a specifc content + * type (if it exists) + */ +bool +force_inline_display(const char *content_type) +{ + bool force_inline_disp; + + find_content_type_attribs(content_type, &force_inline_disp); + return (force_inline_disp); +} + +/* + * This routine will find all content type handler for a specifc content + * type (if it exists) and is defined to the nsRegistry + */ +MimeObjectClass * +mime_locate_external_content_handler(const char *content_type, + contentTypeHandlerInitStruct *ctHandlerInfo) +{ + if (!content_type || !*(content_type)) // null or empty content type + return nullptr; + + MimeObjectClass *newObj = NULL; + nsresult rv; + + nsAutoCString lookupID("@mozilla.org/mimecth;1?type="); + nsAutoCString contentType; + ToLowerCase(nsDependentCString(content_type), contentType); + lookupID += contentType; + + nsCOMPtr<nsIMimeContentTypeHandler> ctHandler = do_CreateInstance(lookupID.get(), &rv); + if (NS_FAILED(rv) || !ctHandler) { + nsCOMPtr<nsICategoryManager> catman = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return nullptr; + + nsCString value; + rv = catman->GetCategoryEntry(NS_SIMPLEMIMECONVERTERS_CATEGORY, + contentType.get(), getter_Copies(value)); + if (NS_FAILED(rv) || value.IsEmpty()) + return nullptr; + rv = MIME_NewSimpleMimeConverterStub(contentType.get(), + getter_AddRefs(ctHandler)); + if (NS_FAILED(rv) || !ctHandler) + return nullptr; + } + + rv = ctHandler->CreateContentTypeHandlerClass(contentType.get(), ctHandlerInfo, &newObj); + if (NS_FAILED(rv)) + return nullptr; + + add_content_type_attribs(contentType.get(), ctHandlerInfo); + return newObj; +} + +/* This is necessary to expose the MimeObject method outside of this DLL */ +int +MIME_MimeObject_write(MimeObject *obj, const char *output, int32_t length, bool user_visible_p) +{ + return MimeObject_write(obj, output, length, user_visible_p); +} + +MimeObject * +mime_new (MimeObjectClass *clazz, MimeHeaders *hdrs, + const char *override_content_type) +{ + int size = clazz->instance_size; + MimeObject *object; + int status; + + /* Some assertions to verify that this isn't random junk memory... */ + NS_ASSERTION(clazz->class_name && strlen(clazz->class_name) > 0, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + NS_ASSERTION(size > 0 && size < 1000, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (!clazz->class_initialized) + { + status = mime_classinit(clazz); + if (status < 0) return 0; + } + + NS_ASSERTION(clazz->initialize && clazz->finalize, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (hdrs) + { + hdrs = MimeHeaders_copy (hdrs); + if (!hdrs) return 0; + } + + object = (MimeObject *) PR_MALLOC(size); + if (!object) return 0; + + memset(object, 0, size); + object->clazz = clazz; + object->headers = hdrs; + object->dontShowAsAttachment = false; + + if (override_content_type && *override_content_type) + object->content_type = strdup(override_content_type); + + status = clazz->initialize(object); + if (status < 0) + { + clazz->finalize(object); + PR_Free(object); + return 0; + } + + return object; +} + +void +mime_free (MimeObject *object) +{ +# ifdef DEBUG__ + int i, size = object->clazz->instance_size; + uint32_t *array = (uint32_t*) object; +# endif /* DEBUG */ + + object->clazz->finalize(object); + +# ifdef DEBUG__ + for (i = 0; i < (size / sizeof(*array)); i++) + array[i] = (uint32_t) 0xDEADBEEF; +# endif /* DEBUG */ + + PR_Free(object); +} + + +bool mime_is_allowed_class(const MimeObjectClass *clazz, + int32_t types_of_classes_to_disallow) +{ + if (types_of_classes_to_disallow == 0) + return true; + bool avoid_html = (types_of_classes_to_disallow >= 1); + bool avoid_images = (types_of_classes_to_disallow >= 2); + bool avoid_strange_content = (types_of_classes_to_disallow >= 3); + bool allow_only_vanilla_classes = (types_of_classes_to_disallow == 100); + + if (allow_only_vanilla_classes) + /* A "safe" class is one that is unlikely to have security bugs or to + allow security exploits or one that is essential for the usefulness + of the application, even for paranoid users. + What's included here is more personal judgement than following + strict rules, though, unfortunately. + The function returns true only for known good classes, i.e. is a + "whitelist" in this case. + This idea comes from Georgi Guninski. + */ + return + ( + clazz == (MimeObjectClass *)&mimeInlineTextPlainClass || + clazz == (MimeObjectClass *)&mimeInlineTextPlainFlowedClass || + clazz == (MimeObjectClass *)&mimeInlineTextHTMLSanitizedClass || + clazz == (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass || + /* The latter 2 classes bear some risk, because they use the Gecko + HTML parser, but the user has the option to make an explicit + choice in this case, via html_as. */ + clazz == (MimeObjectClass *)&mimeMultipartMixedClass || + clazz == (MimeObjectClass *)&mimeMultipartAlternativeClass || + clazz == (MimeObjectClass *)&mimeMultipartDigestClass || + clazz == (MimeObjectClass *)&mimeMultipartAppleDoubleClass || + clazz == (MimeObjectClass *)&mimeMessageClass || + clazz == (MimeObjectClass *)&mimeExternalObjectClass || + /* mimeUntypedTextClass? -- does uuencode */ +#ifdef ENABLE_SMIME + clazz == (MimeObjectClass *)&mimeMultipartSignedCMSClass || + clazz == (MimeObjectClass *)&mimeEncryptedCMSClass || +#endif + clazz == 0 + ); + + /* Contrairy to above, the below code is a "blacklist", i.e. it + *excludes* some "bad" classes. */ + return + !( + (avoid_html + && ( + clazz == (MimeObjectClass *)&mimeInlineTextHTMLParsedClass + /* Should not happen - we protect against that in + mime_find_class(). Still for safety... */ + )) || + (avoid_images + && ( + clazz == (MimeObjectClass *)&mimeInlineImageClass + )) || + (avoid_strange_content + && ( + clazz == (MimeObjectClass *)&mimeInlineTextEnrichedClass || + clazz == (MimeObjectClass *)&mimeInlineTextRichtextClass || + clazz == (MimeObjectClass *)&mimeSunAttachmentClass || + clazz == (MimeObjectClass *)&mimeExternalBodyClass + )) + ); +} + +void getMsgHdrForCurrentURL(MimeDisplayOptions *opts, nsIMsgDBHdr ** aMsgHdr) +{ + *aMsgHdr = nullptr; + + if (!opts) + return; + + mime_stream_data *msd = (mime_stream_data *) (opts->stream_closure); + if (!msd) + return; + + nsCOMPtr<nsIChannel> channel = msd->channel; // note the lack of ref counting... + if (channel) + { + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIMsgMessageUrl> msgURI; + channel->GetURI(getter_AddRefs(uri)); + if (uri) + { + msgURI = do_QueryInterface(uri); + if (msgURI) + { + msgURI->GetMessageHeader(aMsgHdr); + if (*aMsgHdr) + return; + nsCString rdfURI; + msgURI->GetUri(getter_Copies(rdfURI)); + if (!rdfURI.IsEmpty()) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + GetMsgDBHdrFromURI(rdfURI.get(), getter_AddRefs(msgHdr)); + NS_IF_ADDREF(*aMsgHdr = msgHdr); + } + } + } + } + + return; +} + +MimeObjectClass * +mime_find_class (const char *content_type, MimeHeaders *hdrs, + MimeDisplayOptions *opts, bool exact_match_p) +{ + MimeObjectClass *clazz = 0; + MimeObjectClass *tempClass = 0; + contentTypeHandlerInitStruct ctHandlerInfo; + + // Read some prefs + nsIPrefBranch *prefBranch = GetPrefBranch(opts); + int32_t html_as = 0; // def. see below + int32_t types_of_classes_to_disallow = 0; /* Let only a few libmime classes + process incoming data. This protects from bugs (e.g. buffer overflows) + and from security loopholes (e.g. allowing unchecked HTML in some + obscure classes, although the user has html_as > 0). + This option is mainly for the UI of html_as. + 0 = allow all available classes + 1 = Use hardcoded blacklist to avoid rendering (incoming) HTML + 2 = ... and images + 3 = ... and some other uncommon content types + 4 = show all body parts + 100 = Use hardcoded whitelist to avoid even more bugs(buffer overflows). + This mode will limit the features available (e.g. uncommon + attachment types and inline images) and is for paranoid users. + */ + if (opts && opts->format_out != nsMimeOutput::nsMimeMessageFilterSniffer && + opts->format_out != nsMimeOutput::nsMimeMessageDecrypt + && opts->format_out != nsMimeOutput::nsMimeMessageAttach) + if (prefBranch) + { + prefBranch->GetIntPref("mailnews.display.html_as", &html_as); + prefBranch->GetIntPref("mailnews.display.disallow_mime_handlers", + &types_of_classes_to_disallow); + if (types_of_classes_to_disallow > 0 && html_as == 0) + // We have non-sensical prefs. Do some fixup. + html_as = 1; + } + + // First, check to see if the message has been marked as JUNK. If it has, + // then force the message to be rendered as simple, unless this has been + // called by a filtering routine. + bool sanitizeJunkMail = false; + + // it is faster to read the pref first then figure out the msg hdr for the current url only if we have to + // XXX instead of reading this pref every time, part of mime should be an observer listening to this pref change + // and updating internal state accordingly. But none of the other prefs in this file seem to be doing that...=( + if (prefBranch) + prefBranch->GetBoolPref("mail.spam.display.sanitize", &sanitizeJunkMail); + + if (sanitizeJunkMail && + !(opts && opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer)) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + getMsgHdrForCurrentURL(opts, getter_AddRefs(msgHdr)); + if (msgHdr) + { + nsCString junkScoreStr; + (void) msgHdr->GetStringProperty("junkscore", getter_Copies(junkScoreStr)); + if (html_as == 0 && junkScoreStr.get() && atoi(junkScoreStr.get()) > 50) + html_as = 3; // 3 == Simple HTML + } // if msgHdr + } // if we are supposed to sanitize junk mail + + /* + * What we do first is check for an external content handler plugin. + * This will actually extend the mime handling by calling a routine + * which will allow us to load an external content type handler + * for specific content types. If one is not found, we will drop back + * to the default handler. + */ + if ((tempClass = mime_locate_external_content_handler(content_type, &ctHandlerInfo)) != NULL) + { +#ifdef MOZ_THUNDERBIRD + // This is a case where we only want to add this property if we are a thunderbird build AND + // we have found an external mime content handler for text/calendar + // This will enable iMIP support in Lightning + if ( hdrs && (!PL_strncasecmp(content_type, "text/calendar", 13))) + { + char *full_content_type = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false); + if (full_content_type) + { + char *imip_method = MimeHeaders_get_parameter(full_content_type, "method", NULL, NULL); + nsCOMPtr<nsIMsgDBHdr> msgHdr; + getMsgHdrForCurrentURL(opts, getter_AddRefs(msgHdr)); + if (msgHdr) + msgHdr->SetStringProperty("imip_method", (imip_method) ? imip_method : "nomethod"); + // PR_Free checks for null + PR_Free(imip_method); + PR_Free(full_content_type); + } + } +#endif + + if (types_of_classes_to_disallow > 0 + && (!PL_strncasecmp(content_type, "text/x-vcard", 12)) + ) + /* Use a little hack to prevent some dangerous plugins, which ship + with Mozilla, to run. + For the truely user-installed plugins, we rely on the judgement + of the user. */ + { + if (!exact_match_p) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; // As attachment + } + else + clazz = (MimeObjectClass *)tempClass; + } + else + { + if (!content_type || !*content_type || + !PL_strcasecmp(content_type, "text")) /* with no / in the type */ + clazz = (MimeObjectClass *)&mimeUntypedTextClass; + + /* Subtypes of text... + */ + else if (!PL_strncasecmp(content_type, "text/", 5)) + { + if (!PL_strcasecmp(content_type+5, "html")) + { + if (opts && + (opts->format_out == nsMimeOutput::nsMimeMessageSaveAs || + opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer || + opts->format_out == nsMimeOutput::nsMimeMessageDecrypt || + opts->format_out == nsMimeOutput::nsMimeMessageAttach)) + // SaveAs in new modes doesn't work yet. + { + // Don't use the parsed HTML class if we're ... + // - saving the HTML of a message + // - getting message content for filtering + // - snarfing attachments (nsMimeMessageDecrypt used in SnarfMsgAttachment) + // - processing attachments (like deleting attachments). + clazz = (MimeObjectClass *)&mimeInlineTextHTMLClass; + types_of_classes_to_disallow = 0; + } + else if (html_as == 0 || html_as == 4) // Render sender's HTML + clazz = (MimeObjectClass *)&mimeInlineTextHTMLParsedClass; + else if (html_as == 1) // convert HTML to plaintext + // Do a HTML->TXT->HTML conversion, see mimethpl.h. + clazz = (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass; + else if (html_as == 2) // display HTML source + /* This is for the freaks. Treat HTML as plaintext, + which will cause the HTML source to be displayed. + Not very user-friendly, but some seem to want this. */ + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + else if (html_as == 3) // Sanitize + // Strip all but allowed HTML + clazz = (MimeObjectClass *)&mimeInlineTextHTMLSanitizedClass; + else // Goofy pref + /* User has an unknown pref value. Maybe he used a newer Mozilla + with a new alternative to avoid HTML. Defaulting to option 1, + which is less dangerous than defaulting to the raw HTML. */ + clazz = (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass; + } + else if (!PL_strcasecmp(content_type+5, "enriched")) + clazz = (MimeObjectClass *)&mimeInlineTextEnrichedClass; + else if (!PL_strcasecmp(content_type+5, "richtext")) + clazz = (MimeObjectClass *)&mimeInlineTextRichtextClass; + else if (!PL_strcasecmp(content_type+5, "rtf")) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else if (!PL_strcasecmp(content_type+5, "plain")) + { + // Preliminary use the normal plain text + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + + if (opts && opts->format_out != nsMimeOutput::nsMimeMessageFilterSniffer + && opts->format_out != nsMimeOutput::nsMimeMessageAttach + && opts->format_out != nsMimeOutput::nsMimeMessageRaw) + { + bool disable_format_flowed = false; + if (prefBranch) + prefBranch->GetBoolPref("mailnews.display.disable_format_flowed_support", + &disable_format_flowed); + + if(!disable_format_flowed) + { + // Check for format=flowed, damn, it is already stripped away from + // the contenttype! + // Look in headers instead even though it's expensive and clumsy + // First find Content-Type: + char *content_type_row = + (hdrs + ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, + false, false) + : 0); + // Then the format parameter if there is one. + // I would rather use a PARAM_FORMAT but I can't find the right + // place to put the define. The others seems to be in net.h + // but is that really really the right place? There is also + // a nsMimeTypes.h but that one isn't included. Bug? + char *content_type_format = + (content_type_row + ? MimeHeaders_get_parameter(content_type_row, "format", NULL,NULL) + : 0); + + if (content_type_format && !PL_strcasecmp(content_type_format, + "flowed")) + clazz = (MimeObjectClass *)&mimeInlineTextPlainFlowedClass; + PR_FREEIF(content_type_format); + PR_FREEIF(content_type_row); + } + } + } + else if (!exact_match_p) + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + } + + /* Subtypes of multipart... + */ + else if (!PL_strncasecmp(content_type, "multipart/", 10)) + { + // When html_as is 4, we want all MIME parts of the message to + // show up in the displayed message body, if they are MIME types + // that we know how to display, and also in the attachment pane + // if it's appropriate to put them there. Both + // multipart/alternative and multipart/related play games with + // hiding various MIME parts, and we don't want that to happen, + // so we prevent that by parsing those MIME types as + // multipart/mixed, which won't mess with anything. + // + // When our output format is nsMimeOutput::nsMimeMessageAttach, + // i.e., we are reformatting the message to remove attachments, + // we are in a similar boat. The code for deleting + // attachments properly in that mode is in mimemult.cpp + // functions which are inherited by mimeMultipartMixedClass but + // not by mimeMultipartAlternativeClass or + // mimeMultipartRelatedClass. Therefore, to ensure that + // everything is handled properly, in this context too we parse + // those MIME types as multipart/mixed. + bool basic_formatting = (html_as == 4) || + (opts && opts->format_out == nsMimeOutput::nsMimeMessageAttach); + if (!PL_strcasecmp(content_type+10, "alternative")) + clazz = basic_formatting ? (MimeObjectClass *)&mimeMultipartMixedClass : + (MimeObjectClass *)&mimeMultipartAlternativeClass; + else if (!PL_strcasecmp(content_type+10, "related")) + clazz = basic_formatting ? (MimeObjectClass *)&mimeMultipartMixedClass : + (MimeObjectClass *)&mimeMultipartRelatedClass; + else if (!PL_strcasecmp(content_type+10, "digest")) + clazz = (MimeObjectClass *)&mimeMultipartDigestClass; + else if (!PL_strcasecmp(content_type+10, "appledouble") || + !PL_strcasecmp(content_type+10, "header-set")) + clazz = (MimeObjectClass *)&mimeMultipartAppleDoubleClass; + else if (!PL_strcasecmp(content_type+10, "parallel")) + clazz = (MimeObjectClass *)&mimeMultipartParallelClass; + else if (!PL_strcasecmp(content_type+10, "mixed")) + clazz = (MimeObjectClass *)&mimeMultipartMixedClass; +#ifdef ENABLE_SMIME + else if (!PL_strcasecmp(content_type+10, "signed")) + { + /* Check that the "protocol" and "micalg" parameters are ones we + know about. */ + char *ct = (hdrs + ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, + false, false) + : 0); + char *proto = (ct + ? MimeHeaders_get_parameter(ct, PARAM_PROTOCOL, NULL, NULL) + : 0); + char *micalg = (ct + ? MimeHeaders_get_parameter(ct, PARAM_MICALG, NULL, NULL) + : 0); + + if (proto + && ( + (/* is a signature */ + !PL_strcasecmp(proto, APPLICATION_XPKCS7_SIGNATURE) + || + !PL_strcasecmp(proto, APPLICATION_PKCS7_SIGNATURE)) + && micalg + && (!PL_strcasecmp(micalg, PARAM_MICALG_MD5) || + !PL_strcasecmp(micalg, PARAM_MICALG_MD5_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_4) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_5) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_MD2)))) + clazz = (MimeObjectClass *)&mimeMultipartSignedCMSClass; + else + clazz = 0; + PR_FREEIF(proto); + PR_FREEIF(micalg); + PR_FREEIF(ct); + } +#endif + + if (!clazz && !exact_match_p) + /* Treat all unknown multipart subtypes as "multipart/mixed" */ + clazz = (MimeObjectClass *)&mimeMultipartMixedClass; + + /* If we are sniffing a message, let's treat alternative parts as mixed */ + if (opts && opts->format_out == nsMimeOutput::nsMimeMessageFilterSniffer) + if (clazz == (MimeObjectClass *)&mimeMultipartAlternativeClass) + clazz = (MimeObjectClass *)&mimeMultipartMixedClass; + } + + /* Subtypes of message... + */ + else if (!PL_strncasecmp(content_type, "message/", 8)) + { + if (!PL_strcasecmp(content_type+8, "rfc822") || + !PL_strcasecmp(content_type+8, "news")) + clazz = (MimeObjectClass *)&mimeMessageClass; + else if (!PL_strcasecmp(content_type+8, "external-body")) + clazz = (MimeObjectClass *)&mimeExternalBodyClass; + else if (!PL_strcasecmp(content_type+8, "partial")) + /* I guess these are most useful as externals, for now... */ + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else if (!exact_match_p) + /* Treat all unknown message subtypes as "text/plain" */ + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + } + + /* The magic image types which we are able to display internally... + */ + else if (!PL_strncasecmp(content_type, "image/", 6)) { + if (imgLoader::SupportImageWithMimeType(content_type, AcceptedMimeTypes::IMAGES_AND_DOCUMENTS)) + clazz = (MimeObjectClass *)&mimeInlineImageClass; + else + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + } + +#ifdef ENABLE_SMIME + else if (!PL_strcasecmp(content_type, APPLICATION_XPKCS7_MIME) + || !PL_strcasecmp(content_type, APPLICATION_PKCS7_MIME)) { + + if (Preferences::GetBool("mailnews.p7m_subparts_external", false) && + opts->is_child) { + // We do not allow encrypted parts except as top level. + // Allowing them would leak the plain text in case the part is + // cleverly hidden and the decrypted content gets included in + // replies and forwards. + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + return clazz; + } + + char *ct = (hdrs ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, + false, false) + : nullptr); + char *st = (ct ? MimeHeaders_get_parameter(ct, "smime-type", NULL, NULL) + : nullptr); + + /* by default, assume that it is an encrypted message */ + clazz = (MimeObjectClass *)&mimeEncryptedCMSClass; + + /* if the smime-type parameter says that it's a certs-only or + compressed file, then show it as an attachment, however + (MimeEncryptedCMS doesn't handle these correctly) */ + if (st && + (!PL_strcasecmp(st, "certs-only") || + !PL_strcasecmp(st, "compressed-data"))) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else { + /* look at the file extension... less reliable, but still covered + by the S/MIME specification (RFC 3851, section 3.2.1) */ + char *name = (hdrs ? MimeHeaders_get_name(hdrs, opts) : nullptr); + if (name) { + char *suf = PL_strrchr(name, '.'); + bool p7mExternal = false; + + if (prefBranch) + prefBranch->GetBoolPref("mailnews.p7m_external", &p7mExternal); + if (suf && + ((!PL_strcasecmp(suf, ".p7m") && p7mExternal) || + !PL_strcasecmp(suf, ".p7c") || + !PL_strcasecmp(suf, ".p7z"))) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + } + PR_Free(name); + } + PR_Free(st); + PR_Free(ct); + } +#endif + /* A few types which occur in the real world and which we would otherwise + treat as non-text types (which would be bad) without this special-case... + */ + else if (!PL_strcasecmp(content_type, APPLICATION_PGP) || + !PL_strcasecmp(content_type, APPLICATION_PGP2)) + clazz = (MimeObjectClass *)&mimeInlineTextPlainClass; + + else if (!PL_strcasecmp(content_type, SUN_ATTACHMENT)) + clazz = (MimeObjectClass *)&mimeSunAttachmentClass; + + /* Everything else gets represented as a clickable link. + */ + else if (!exact_match_p) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + + if (!mime_is_allowed_class(clazz, types_of_classes_to_disallow)) + { + /* Do that check here (not after the if block), because we want to allow + user-installed plugins. */ + if(!exact_match_p) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else + clazz = 0; + } + } + +#ifdef ENABLE_SMIME + // see bug #189988 + if (opts && opts->format_out == nsMimeOutput::nsMimeMessageDecrypt && + (clazz != (MimeObjectClass *)&mimeEncryptedCMSClass)) { + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + } +#endif + + if (!exact_match_p) + NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!clazz) return 0; + + NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + if (clazz && !clazz->class_initialized) + { + int status = mime_classinit(clazz); + if (status < 0) return 0; + } + + return clazz; +} + + +MimeObject * +mime_create (const char *content_type, MimeHeaders *hdrs, + MimeDisplayOptions *opts, bool forceInline /* = false */) +{ + /* If there is no Content-Disposition header, or if the Content-Disposition + is ``inline'', then we display the part inline (and let mime_find_class() + decide how.) + + If there is any other Content-Disposition (either ``attachment'' or some + disposition that we don't recognise) then we always display the part as + an external link, by using MimeExternalObject to display it. + + But Content-Disposition is ignored for all containers except `message'. + (including multipart/mixed, and multipart/digest.) It's not clear if + this is to spec, but from a usability standpoint, I think it's necessary. + */ + + MimeObjectClass *clazz = 0; + char *content_disposition = 0; + MimeObject *obj = 0; + char *override_content_type = 0; + + /* We've had issues where the incoming content_type is invalid, of a format: + content_type="=?windows-1252?q?application/pdf" (bug 659355) + We decided to fix that by simply trimming the stuff before the ? + */ + if (content_type) + { + const char *lastQuestion = strrchr(content_type, '?'); + if (lastQuestion) + content_type = lastQuestion + 1; // the substring after the last '?' + } + + /* There are some clients send out all attachments with a content-type + of application/octet-stream. So, if we have an octet-stream attachment, + try to guess what type it really is based on the file extension. I HATE + that we have to do this... + */ + if (hdrs && opts && opts->file_type_fn && + + /* ### mwelch - don't override AppleSingle */ + (content_type ? PL_strcasecmp(content_type, APPLICATION_APPLEFILE) : true) && + /* ## davidm Apple double shouldn't use this #$%& either. */ + (content_type ? PL_strcasecmp(content_type, MULTIPART_APPLEDOUBLE) : true) && + (!content_type || + !PL_strcasecmp(content_type, APPLICATION_OCTET_STREAM) || + !PL_strcasecmp(content_type, UNKNOWN_CONTENT_TYPE))) + { + char *name = MimeHeaders_get_name(hdrs, opts); + if (name) + { + override_content_type = opts->file_type_fn (name, opts->stream_closure); + // appledouble isn't a valid override content type, and makes + // attachments invisible. + if (!PL_strcasecmp(override_content_type, MULTIPART_APPLEDOUBLE)) + override_content_type = nullptr; + PR_FREEIF(name); + + // Workaroung for saving '.eml" file encoded with base64. + // Do not override with message/rfc822 whenever Transfer-Encoding is + // base64 since base64 encoding of message/rfc822 is invalid. + // Our MimeMessageClass has no capability to decode it. + if (!PL_strcasecmp(override_content_type, MESSAGE_RFC822)) { + nsCString encoding; + encoding.Adopt(MimeHeaders_get(hdrs, + HEADER_CONTENT_TRANSFER_ENCODING, + true, false)); + if (encoding.LowerCaseEqualsLiteral(ENCODING_BASE64)) + override_content_type = nullptr; + } + + // If we get here and it is not the unknown content type from the + // file name, let's do some better checking not to inline something bad + if (override_content_type && + *override_content_type && + (PL_strcasecmp(override_content_type, UNKNOWN_CONTENT_TYPE))) + content_type = override_content_type; + } + } + + clazz = mime_find_class(content_type, hdrs, opts, false); + + NS_ASSERTION(clazz, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!clazz) goto FAIL; + + if (opts && opts->part_to_load) + /* Always ignore Content-Disposition when we're loading some specific + sub-part (which may be within some container that we wouldn't otherwise + descend into, if the container itself had a Content-Disposition of + `attachment'. */ + content_disposition = 0; + + else if (mime_subclass_p(clazz,(MimeObjectClass *)&mimeContainerClass) && + !mime_subclass_p(clazz,(MimeObjectClass *)&mimeMessageClass)) + /* Ignore Content-Disposition on all containers except `message'. + That is, Content-Disposition is ignored for multipart/mixed objects, + but is obeyed for message/rfc822 objects. */ + content_disposition = 0; + + else + { + /* Check to see if the plugin should override the content disposition + to make it appear inline. One example is a vcard which has a content + disposition of an "attachment;" */ + if (force_inline_display(content_type)) + NS_MsgSACopy(&content_disposition, "inline"); + else + content_disposition = (hdrs + ? MimeHeaders_get(hdrs, HEADER_CONTENT_DISPOSITION, true, false) + : 0); + } + + if (!content_disposition || !PL_strcasecmp(content_disposition, "inline")) + ; /* Use the class we've got. */ + else + { + // override messages that have content disposition set to "attachment" + // even though we probably should show them inline. + if ( (clazz != (MimeObjectClass *)&mimeInlineTextClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextPlainClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextPlainFlowedClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextHTMLClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextHTMLParsedClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextHTMLSanitizedClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextHTMLAsPlaintextClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextRichtextClass) && + (clazz != (MimeObjectClass *)&mimeInlineTextEnrichedClass) && + (clazz != (MimeObjectClass *)&mimeMessageClass) && + (clazz != (MimeObjectClass *)&mimeInlineImageClass) ) + // not a special inline type, so show as attachment + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + } + + /* If the option `Show Attachments Inline' is off, now would be the time to change our mind... */ + /* Also, if we're doing a reply (i.e. quoting the body), then treat that according to preference. */ + if (opts && ((!opts->show_attachment_inline_p && !forceInline) || + (!opts->quote_attachment_inline_p && + (opts->format_out == nsMimeOutput::nsMimeMessageQuoting || + opts->format_out == nsMimeOutput::nsMimeMessageBodyQuoting)))) + { + if (mime_subclass_p(clazz, (MimeObjectClass *)&mimeInlineTextClass)) + { + /* It's a text type. Write it only if it's the *first* part + that we're writing, and then only if it has no "filename" + specified (the assumption here being, if it has a filename, + it wasn't simply typed into the text field -- it was actually + an attached document.) */ + if (opts->state && opts->state->first_part_written_p) + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + else + { + /* If there's a name, then write this as an attachment. */ + char *name = (hdrs ? MimeHeaders_get_name(hdrs, opts) : nullptr); + if (name) + { + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + PR_Free(name); + } + } + } + else + if (mime_subclass_p(clazz,(MimeObjectClass *)&mimeContainerClass) && + !mime_subclass_p(clazz,(MimeObjectClass *)&mimeMessageClass)) + /* Multipart subtypes are ok, except for messages; descend into + multiparts, and defer judgement. + + Encrypted blobs are just like other containers (make the crypto + layer invisible, and treat them as simple containers. So there's + no easy way to save encrypted data directly to disk; it will tend + to always be wrapped inside a message/rfc822. That's ok.) */ + ; + else if (opts && opts->part_to_load && + mime_subclass_p(clazz,(MimeObjectClass *)&mimeMessageClass)) + /* Descend into messages only if we're looking for a specific sub-part. */ + ; + else + { + /* Anything else, and display it as a link (and cause subsequent + text parts to also be displayed as links.) */ + clazz = (MimeObjectClass *)&mimeExternalObjectClass; + } + } + + PR_FREEIF(content_disposition); + obj = mime_new (clazz, hdrs, content_type); + + FAIL: + + /* If we decided to ignore the content-type in the headers of this object + (see above) then make sure that our new content-type is stored in the + object itself. (Or free it, if we're in an out-of-memory situation.) + */ + if (override_content_type) + { + if (obj) + { + PR_FREEIF(obj->content_type); + obj->content_type = override_content_type; + } + else + { + PR_Free(override_content_type); + } + } + + return obj; +} + + + +static int mime_classinit_1(MimeObjectClass *clazz, MimeObjectClass *target); + +static int +mime_classinit(MimeObjectClass *clazz) +{ + int status; + if (clazz->class_initialized) + return 0; + + NS_ASSERTION(clazz->class_initialize, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (!clazz->class_initialize) + return -1; + + /* First initialize the superclass. + */ + if (clazz->superclass && !clazz->superclass->class_initialized) + { + status = mime_classinit(clazz->superclass); + if (status < 0) return status; + } + + /* Now run each of the superclass-init procedures in turn, + parentmost-first. */ + status = mime_classinit_1(clazz, clazz); + if (status < 0) return status; + + /* Now we're done. */ + clazz->class_initialized = true; + return 0; +} + +static int +mime_classinit_1(MimeObjectClass *clazz, MimeObjectClass *target) +{ + int status; + if (clazz->superclass) + { + status = mime_classinit_1(clazz->superclass, target); + if (status < 0) return status; + } + return clazz->class_initialize(target); +} + + +bool +mime_subclass_p(MimeObjectClass *child, MimeObjectClass *parent) +{ + if (child == parent) + return true; + else if (!child->superclass) + return false; + else + return mime_subclass_p(child->superclass, parent); +} + +bool +mime_typep(MimeObject *obj, MimeObjectClass *clazz) +{ + return mime_subclass_p(obj->clazz, clazz); +} + + + +/* URL munging + */ + + +/* Returns a string describing the location of the part (like "2.5.3"). + This is not a full URL, just a part-number. + */ +char * +mime_part_address(MimeObject *obj) +{ + if (!obj->parent) + return strdup("0"); + else + { + /* Find this object in its parent. */ + int32_t i, j = -1; + char buf [20]; + char *higher = 0; + MimeContainer *cont = (MimeContainer *) obj->parent; + NS_ASSERTION(mime_typep(obj->parent, + (MimeObjectClass *)&mimeContainerClass), "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + for (i = 0; i < cont->nchildren; i++) + if (cont->children[i] == obj) + { + j = i+1; + break; + } + if (j == -1) + { + NS_ERROR("No children under MeimContainer"); + return 0; + } + + PR_snprintf(buf, sizeof(buf), "%ld", j); + if (obj->parent->parent) + { + higher = mime_part_address(obj->parent); + if (!higher) return 0; /* MIME_OUT_OF_MEMORY */ + } + + if (!higher) + return strdup(buf); + else + { + uint32_t slen = strlen(higher) + strlen(buf) + 3; + char *s = (char *)PR_MALLOC(slen); + if (!s) + { + PR_Free(higher); + return 0; /* MIME_OUT_OF_MEMORY */ + } + PL_strncpyz(s, higher, slen); + PL_strcatn(s, slen, "."); + PL_strcatn(s, slen, buf); + PR_Free(higher); + return s; + } + } +} + + +/* Returns a string describing the location of the *IMAP* part (like "2.5.3"). + This is not a full URL, just a part-number. + This part is explicitly passed in the X-Mozilla-IMAP-Part header. + Return value must be freed by the caller. + */ +char * +mime_imap_part_address(MimeObject *obj) +{ + if (!obj || !obj->headers) + return 0; + else + return MimeHeaders_get(obj->headers, IMAP_EXTERNAL_CONTENT_HEADER, false, false); +} + +/* Returns a full URL if the current mime object has a EXTERNAL_ATTACHMENT_URL_HEADER + header. + Return value must be freed by the caller. +*/ +char * +mime_external_attachment_url(MimeObject *obj) +{ + if (!obj || !obj->headers) + return 0; + else + return MimeHeaders_get(obj->headers, EXTERNAL_ATTACHMENT_URL_HEADER, false, false); +} + +#ifdef ENABLE_SMIME +/* Asks whether the given object is one of the cryptographically signed + or encrypted objects that we know about. (MimeMessageClass uses this + to decide if the headers need to be presented differently.) + */ +bool +mime_crypto_object_p(MimeHeaders *hdrs, bool clearsigned_counts, MimeDisplayOptions *opts) +{ + char *ct; + MimeObjectClass *clazz; + + if (!hdrs) return false; + + ct = MimeHeaders_get (hdrs, HEADER_CONTENT_TYPE, true, false); + if (!ct) return false; + + /* Rough cut -- look at the string before doing a more complex comparison. */ + if (PL_strcasecmp(ct, MULTIPART_SIGNED) && + PL_strncasecmp(ct, "application/", 12)) + { + PR_Free(ct); + return false; + } + + /* It's a candidate for being a crypto object. Let's find out for sure... */ + clazz = mime_find_class(ct, hdrs, opts, true); + PR_Free(ct); + + if (clazz == ((MimeObjectClass *)&mimeEncryptedCMSClass)) + return true; + else if (clearsigned_counts && + clazz == ((MimeObjectClass *)&mimeMultipartSignedCMSClass)) + return true; + else + return false; +} + +/* Whether the given object has written out the HTML version of its headers + in such a way that it will have a "crypto stamp" next to the headers. If + this is true, then the child must write out its HTML slightly differently + to take this into account... + */ +bool +mime_crypto_stamped_p(MimeObject *obj) +{ + if (!obj) return false; + if (mime_typep (obj, (MimeObjectClass *) &mimeMessageClass)) + return ((MimeMessage *) obj)->crypto_stamped_p; + else + return false; +} + +#endif // ENABLE_SMIME + +/* Puts a part-number into a URL. If append_p is true, then the part number + is appended to any existing part-number already in that URL; otherwise, + it replaces it. + */ +char * +mime_set_url_part(const char *url, const char *part, bool append_p) +{ + const char *part_begin = 0; + const char *part_end = 0; + bool got_q = false; + const char *s; + char *result; + + if (!url || !part) return 0; + + nsAutoCString urlString(url); + int32_t typeIndex = urlString.Find("?type=application/x-message-display"); + if (typeIndex != -1) + { + urlString.Cut(typeIndex, sizeof("?type=application/x-message-display") - 1); + if (urlString.CharAt(typeIndex) == '&') + urlString.Replace(typeIndex, 1, '?'); + url = urlString.get(); + } + + for (s = url; *s; s++) + { + if (*s == '?') + { + got_q = true; + if (!PL_strncasecmp(s, "?part=", 6)) + part_begin = (s += 6); + } + else if (got_q && *s == '&' && !PL_strncasecmp(s, "&part=", 6)) + part_begin = (s += 6); + + if (part_begin) + { + for (; (*s && *s != '?' && *s != '&'); s++) + ; + part_end = s; + break; + } + } + + uint32_t resultlen = strlen(url) + strlen(part) + 10; + result = (char *) PR_MALLOC(resultlen); + if (!result) return 0; + + if (part_begin) + { + if (append_p) + { + memcpy(result, url, part_end - url); + result [part_end - url] = '.'; + result [part_end - url + 1] = 0; + } + else + { + memcpy(result, url, part_begin - url); + result [part_begin - url] = 0; + } + } + else + { + PL_strncpyz(result, url, resultlen); + if (got_q) + PL_strcatn(result, resultlen, "&part="); + else + PL_strcatn(result, resultlen, "?part="); + } + + PL_strcatn(result, resultlen, part); + + if (part_end && *part_end) + PL_strcatn(result, resultlen, part_end); + + /* Semi-broken kludge to omit a trailing "?part=0". */ + { + int L = strlen(result); + if (L > 6 && + (result[L-7] == '?' || result[L-7] == '&') && + !strcmp("part=0", result + L - 6)) + result[L-7] = 0; + } + + return result; +} + + + +/* Puts an *IMAP* part-number into a URL. + Strips off any previous *IMAP* part numbers, since they are absolute, not relative. + */ +char * +mime_set_url_imap_part(const char *url, const char *imappart, const char *libmimepart) +{ + char *result = 0; + char *whereCurrent = PL_strstr(url, "/;section="); + if (whereCurrent) + { + *whereCurrent = 0; + } + + uint32_t resultLen = strlen(url) + strlen(imappart) + strlen(libmimepart) + 17; + result = (char *) PR_MALLOC(resultLen); + if (!result) return 0; + + PL_strncpyz(result, url, resultLen); + PL_strcatn(result, resultLen, "/;section="); + PL_strcatn(result, resultLen, imappart); + PL_strcatn(result, resultLen, "?part="); + PL_strcatn(result, resultLen, libmimepart); + + if (whereCurrent) + *whereCurrent = '/'; + + return result; +} + + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches, and returns the MimeObject (else NULL.) + (part is not a URL -- it's of the form "1.3.5".) + */ +MimeObject * +mime_address_to_part(const char *part, MimeObject *obj) +{ + /* Note: this is an N^2 operation, but the number of parts in a message + shouldn't ever be large enough that this really matters... */ + + bool match; + + if (!part || !*part) + { + match = !obj->parent; + } + else + { + char *part2 = mime_part_address(obj); + if (!part2) return 0; /* MIME_OUT_OF_MEMORY */ + match = !strcmp(part, part2); + PR_Free(part2); + } + + if (match) + { + /* These are the droids we're looking for. */ + return obj; + } + else if (!mime_typep(obj, (MimeObjectClass *) &mimeContainerClass)) + { + /* Not a container, pull up, pull up! */ + return 0; + } + else + { + int32_t i; + MimeContainer *cont = (MimeContainer *) obj; + for (i = 0; i < cont->nchildren; i++) + { + MimeObject *o2 = mime_address_to_part(part, cont->children[i]); + if (o2) return o2; + } + return 0; + } +} + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches; if one is found, returns the Content-Name of that part. + Else returns NULL. (part is not a URL -- it's of the form "1.3.5".) + */ +char * +mime_find_content_type_of_part(const char *part, MimeObject *obj) +{ + char *result = 0; + + obj = mime_address_to_part(part, obj); + if (!obj) return 0; + + result = (obj->headers ? MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, true, false) : 0); + + return result; +} + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches; if one is found, returns the Content-Name of that part. + Else returns NULL. (part is not a URL -- it's of the form "1.3.5".) + */ +char * +mime_find_suggested_name_of_part(const char *part, MimeObject *obj) +{ + char *result = 0; + + obj = mime_address_to_part(part, obj); + if (!obj) return 0; + + result = (obj->headers ? MimeHeaders_get_name(obj->headers, obj->options) : 0); + + /* If this part doesn't have a name, but this part is one fork of an + AppleDouble, and the AppleDouble itself has a name, then use that. */ + if (!result && + obj->parent && + obj->parent->headers && + mime_typep(obj->parent, + (MimeObjectClass *) &mimeMultipartAppleDoubleClass)) + result = MimeHeaders_get_name(obj->parent->headers, obj->options); + + /* Else, if this part is itself an AppleDouble, and one of its children + has a name, then use that (check data fork first, then resource.) */ + if (!result && + mime_typep(obj, (MimeObjectClass *) &mimeMultipartAppleDoubleClass)) + { + MimeContainer *cont = (MimeContainer *) obj; + if (cont->nchildren > 1 && + cont->children[1] && + cont->children[1]->headers) + result = MimeHeaders_get_name(cont->children[1]->headers, obj->options); + + if (!result && + cont->nchildren > 0 && + cont->children[0] && + cont->children[0]->headers) + result = MimeHeaders_get_name(cont->children[0]->headers, obj->options); + } + + /* Ok, now we have the suggested name, if any. + Now we remove any extensions that correspond to the + Content-Transfer-Encoding. For example, if we see the headers + + Content-Type: text/plain + Content-Disposition: inline; filename=foo.text.uue + Content-Transfer-Encoding: x-uuencode + + then we would look up (in mime.types) the file extensions which are + associated with the x-uuencode encoding, find that "uue" is one of + them, and remove that from the end of the file name, thus returning + "foo.text" as the name. This is because, by the time this file ends + up on disk, its content-transfer-encoding will have been removed; + therefore, we should suggest a file name that indicates that. + */ + if (result && obj->encoding && *obj->encoding) + { + int32_t L = strlen(result); + const char **exts = 0; + + /* + I'd like to ask the mime.types file, "what extensions correspond + to obj->encoding (which happens to be "x-uuencode") but doing that + in a non-sphagetti way would require brain surgery. So, since + currently uuencode is the only content-transfer-encoding which we + understand which traditionally has an extension, we just special- + case it here! Icepicks in my forehead! + + Note that it's special-cased in a similar way in libmsg/compose.c. + */ + if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE)) + { + static const char *uue_exts[] = { "uu", "uue", 0 }; + exts = uue_exts; + } + + while (exts && *exts) + { + const char *ext = *exts; + int32_t L2 = strlen(ext); + if (L > L2 + 1 && /* long enough */ + result[L - L2 - 1] == '.' && /* '.' in right place*/ + !PL_strcasecmp(ext, result + (L - L2))) /* ext matches */ + { + result[L - L2 - 1] = 0; /* truncate at '.' and stop. */ + break; + } + exts++; + } + } + + return result; +} + +/* Parse the various "?" options off the URL and into the options struct. + */ +int +mime_parse_url_options(const char *url, MimeDisplayOptions *options) +{ + const char *q; + + if (!url || !*url) return 0; + if (!options) return 0; + + MimeHeadersState default_headers = options->headers; + + q = PL_strrchr (url, '?'); + if (! q) return 0; + q++; + while (*q) + { + const char *end, *value, *name_end; + for (end = q; *end && *end != '&'; end++) + ; + for (value = q; *value != '=' && value < end; value++) + ; + name_end = value; + if (value < end) value++; + if (name_end <= q) + ; + else if (!PL_strncasecmp ("headers", q, name_end - q)) + { + if (end > value && !PL_strncasecmp ("only", value, end-value)) + options->headers = MimeHeadersOnly; + else if (end > value && !PL_strncasecmp ("none", value, end-value)) + options->headers = MimeHeadersNone; + else if (end > value && !PL_strncasecmp ("all", value, end - value)) + options->headers = MimeHeadersAll; + else if (end > value && !PL_strncasecmp ("some", value, end - value)) + options->headers = MimeHeadersSome; + else if (end > value && !PL_strncasecmp ("micro", value, end - value)) + options->headers = MimeHeadersMicro; + else if (end > value && !PL_strncasecmp ("cite", value, end - value)) + options->headers = MimeHeadersCitation; + else if (end > value && !PL_strncasecmp ("citation", value, end-value)) + options->headers = MimeHeadersCitation; + else + options->headers = default_headers; + } + else if (!PL_strncasecmp ("part", q, name_end - q) && + options->format_out != nsMimeOutput::nsMimeMessageBodyQuoting) + { + PR_FREEIF (options->part_to_load); + if (end > value) + { + options->part_to_load = (char *) PR_MALLOC(end - value + 1); + if (!options->part_to_load) + return MIME_OUT_OF_MEMORY; + memcpy(options->part_to_load, value, end-value); + options->part_to_load[end-value] = 0; + } + } + else if (!PL_strncasecmp ("rot13", q, name_end - q)) + { + options->rot13_p = end <= value || !PL_strncasecmp ("true", value, end - value); + } + else if (!PL_strncasecmp ("emitter", q, name_end - q)) + { + if ((end > value) && !PL_strncasecmp ("js", value, end - value)) + { + // the js emitter needs to hear about nested message bodies + // in order to build a proper representation. + options->notify_nested_bodies = true; + // show_attachment_inline_p has the side-effect of letting the + // emitter see all parts of a multipart/alternative, which it + // really appreciates. + options->show_attachment_inline_p = true; + // however, show_attachment_inline_p also results in a few + // subclasses writing junk into the body for display purposes. + // put a stop to these shenanigans by enabling write_pure_bodies. + // current offenders are: + // - MimeInlineImage + options->write_pure_bodies = true; + // we don't actually care about the data in the attachments, just the + // metadata (i.e. size) + options->metadata_only = true; + } + } + + q = end; + if (*q) + q++; + } + + +/* Compatibility with the "?part=" syntax used in the old (Mozilla 2.0) + MIME parser. + + Basically, the problem is that the old part-numbering code was totally + busted: here's a comparison of the old and new numberings with a pair + of hypothetical messages (one with a single part, and one with nested + containers.) + NEW: OLD: OR: + message/rfc822 + image/jpeg 1 0 0 + + message/rfc822 + multipart/mixed 1 0 0 + text/plain 1.1 1 1 + image/jpeg 1.2 2 2 + message/rfc822 1.3 - 3 + text/plain 1.3.1 3 - + message/rfc822 1.4 - 4 + multipart/mixed 1.4.1 4 - + text/plain 1.4.1.1 4.1 - + image/jpeg 1.4.1.2 4.2 - + text/plain 1.5 5 5 + + The "NEW" column is how the current code counts. The "OLD" column is + what "?part=" references would do in 3.0b4 and earlier; you'll see that + you couldn't directly refer to the child message/rfc822 objects at all! + But that's when it got really weird, because if you turned on + "Attachments As Links" (or used a URL like "?inline=false&part=...") + then you got a totally different numbering system (seen in the "OR" + column.) Gag! + + So, the problem is, ClariNet had been using these part numbers in their + HTML news feeds, as a sleazy way of transmitting both complex HTML layouts + and images using NNTP as transport, without invoking HTTP. + + The following clause is to provide some small amount of backward + compatibility. By looking at that table, one can see that in the new + model, "part=0" has no meaning, and neither does "part=2" or "part=3" + and so on. + + "part=1" is ambiguous between the old and new way, as is any part + specification that has a "." in it. + + So, the compatibility hack we do here is: if the part is "0", then map + that to "1". And if the part is >= "2", then prepend "1." to it (so that + we map "2" to "1.2", and "3" to "1.3".) + + This leaves the URLs compatible in the cases of: + + = single part messages + = references to elements of a top-level multipart except the first + + and leaves them incompatible for: + + = the first part of a top-level multipart + = all elements deeper than the outermost part + + Life s#$%s when you don't properly think out things that end up turning + into de-facto standards... + */ + + if (options->part_to_load && + !PL_strchr(options->part_to_load, '.')) /* doesn't contain a dot */ + { + if (!strcmp(options->part_to_load, "0")) /* 0 */ + { + PR_Free(options->part_to_load); + options->part_to_load = strdup("1"); + if (!options->part_to_load) + return MIME_OUT_OF_MEMORY; + } + else if (strcmp(options->part_to_load, "1")) /* not 1 */ + { + const char *prefix = "1."; + uint32_t slen = strlen(options->part_to_load) + strlen(prefix) + 1; + char *s = (char *) PR_MALLOC(slen); + if (!s) return MIME_OUT_OF_MEMORY; + PL_strncpyz(s, prefix, slen); + PL_strcatn(s, slen, options->part_to_load); + PR_Free(options->part_to_load); + options->part_to_load = s; + } + } + + return 0; +} + + +/* Some output-generation utility functions... + */ + +int +MimeOptions_write(MimeHeaders *hdrs, MimeDisplayOptions *opt, const char *data, + int32_t length, bool user_visible_p) +{ + int status = 0; + void* closure = 0; + if (!opt || !opt->output_fn || !opt->state) + return 0; + + closure = opt->output_closure; + if (!closure) closure = opt->stream_closure; + +// PR_ASSERT(opt->state->first_data_written_p); + + if (opt->state->separator_queued_p && user_visible_p) + { + opt->state->separator_queued_p = false; + if (opt->state->separator_suppressed_p) + opt->state->separator_suppressed_p = false; + else { + const char *sep = "<BR><FIELDSET CLASS=\"mimeAttachmentHeader\">"; + int lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + + nsCString name; + name.Adopt(MimeHeaders_get_name(hdrs, opt)); + MimeHeaders_convert_header_value(opt, name, false); + + if (!name.IsEmpty()) { + sep = "<LEGEND CLASS=\"mimeAttachmentHeaderName\">"; + lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + + nsCString escapedName; + escapedName.Adopt(MsgEscapeHTML(name.get())); + + lstatus = opt->output_fn(escapedName.get(), + escapedName.Length(), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + + sep = "</LEGEND>"; + lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + } + + sep = "</FIELDSET><BR/>"; + lstatus = opt->output_fn(sep, strlen(sep), closure); + opt->state->separator_suppressed_p = false; + if (lstatus < 0) return lstatus; + } + } + if (user_visible_p) + opt->state->separator_suppressed_p = false; + + if (length > 0) + { + status = opt->output_fn(data, length, closure); + if (status < 0) return status; + } + + return 0; +} + +int +MimeObject_write(MimeObject *obj, const char *output, int32_t length, + bool user_visible_p) +{ + if (!obj->output_p) return 0; + + // if we're stripping attachments, check if any parent is not being ouput + if (obj->options->format_out == nsMimeOutput::nsMimeMessageAttach) + { + // if true, mime generates a separator in html - we don't want that. + user_visible_p = false; + + for (MimeObject *parent = obj->parent; parent; parent = parent->parent) + { + if (!parent->output_p) + return 0; + } + } + if (!obj->options->state->first_data_written_p) + { + int status = MimeObject_output_init(obj, 0); + if (status < 0) return status; + NS_ASSERTION(obj->options->state->first_data_written_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + } + + return MimeOptions_write(obj->headers, obj->options, output, length, user_visible_p); +} + +int +MimeObject_write_separator(MimeObject *obj) +{ + if (obj->options && obj->options->state && + // we never want separators if we are asking for pure bodies + !obj->options->write_pure_bodies) + obj->options->state->separator_queued_p = true; + return 0; +} + +int +MimeObject_output_init(MimeObject *obj, const char *content_type) +{ + if (obj && + obj->options && + obj->options->state && + !obj->options->state->first_data_written_p) + { + int status; + const char *charset = 0; + char *name = 0, *x_mac_type = 0, *x_mac_creator = 0; + + if (!obj->options->output_init_fn) + { + obj->options->state->first_data_written_p = true; + return 0; + } + + if (obj->headers) + { + char *ct; + name = MimeHeaders_get_name(obj->headers, obj->options); + + ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, + false, false); + if (ct) + { + x_mac_type = MimeHeaders_get_parameter(ct, PARAM_X_MAC_TYPE, NULL, NULL); + x_mac_creator= MimeHeaders_get_parameter(ct, PARAM_X_MAC_CREATOR, NULL, NULL); + /* if don't have a x_mac_type and x_mac_creator, we need to try to get it from its parent */ + if (!x_mac_type && !x_mac_creator && obj->parent && obj->parent->headers) + { + char * ctp = MimeHeaders_get(obj->parent->headers, HEADER_CONTENT_TYPE, false, false); + if (ctp) + { + x_mac_type = MimeHeaders_get_parameter(ctp, PARAM_X_MAC_TYPE, NULL, NULL); + x_mac_creator= MimeHeaders_get_parameter(ctp, PARAM_X_MAC_CREATOR, NULL, NULL); + PR_Free(ctp); + } + } + + if (!(obj->options->override_charset)) { + char *charset = MimeHeaders_get_parameter(ct, "charset", nullptr, nullptr); + if (charset) + { + PR_FREEIF(obj->options->default_charset); + obj->options->default_charset = charset; + } + } + PR_Free(ct); + } + } + + if (mime_typep(obj, (MimeObjectClass *) &mimeInlineTextClass)) + charset = ((MimeInlineText *)obj)->charset; + + if (!content_type) + content_type = obj->content_type; + if (!content_type) + content_type = TEXT_PLAIN; + + // + // Set the charset on the channel we are dealing with so people know + // what the charset is set to. Do this for quoting/Printing ONLY! + // + extern void ResetChannelCharset(MimeObject *obj); + if ( (obj->options) && + ( (obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting) || + (obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting) || + (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs) || + (obj->options->format_out == nsMimeOutput::nsMimeMessagePrintOutput) ) ) + ResetChannelCharset(obj); + + status = obj->options->output_init_fn (content_type, charset, name, + x_mac_type, x_mac_creator, + obj->options->stream_closure); + PR_FREEIF(name); + PR_FREEIF(x_mac_type); + PR_FREEIF(x_mac_creator); + obj->options->state->first_data_written_p = true; + return status; + } + return 0; +} + +char * +mime_get_base_url(const char *url) +{ + if (!url) + return nullptr; + + const char *s = strrchr(url, '?'); + if (s && !strncmp(s, "?type=application/x-message-display", sizeof("?type=application/x-message-display") - 1)) + { + const char *nextTerm = strchr(s, '&'); + s = (nextTerm) ? nextTerm : s + strlen(s) - 1; + } + // we need to keep the ?number part of the url, or we won't know + // which local message the part belongs to. + if (s && *s && *(s+1) && !strncmp(s + 1, "number=", sizeof("number=") - 1)) + { + const char *nextTerm = strchr(++s, '&'); + s = (nextTerm) ? nextTerm : s + strlen(s) - 1; + } + char *result = (char *) PR_MALLOC(strlen(url) + 1); + NS_ASSERTION(result, "out of memory"); + if (!result) + return nullptr; + + memcpy(result, url, s - url); + result[s - url] = 0; + return result; +} diff --git a/mailnews/mime/src/mimei.h b/mailnews/mime/src/mimei.h new file mode 100644 index 000000000..a2d8332cd --- /dev/null +++ b/mailnews/mime/src/mimei.h @@ -0,0 +1,422 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEI_H_ +#define _MIMEI_H_ + +/* + This module, libmime, implements a general-purpose MIME parser. + One of the methods provided by this parser is the ability to emit + an HTML representation of it. + + All Mozilla-specific code is (and should remain) isolated in the + file mimemoz.c. Generally, if the code involves images, netlib + streams it should be in mimemoz.c instead of in the main body of + the MIME parser. + + The parser is object-oriented and fully buzzword-compliant. + There is a class for each MIME type, and each class is responsible + for parsing itself, and/or handing the input data off to one of its + child objects. + + The class hierarchy is: + + MimeObject (abstract) + | + +--- MimeContainer (abstract) + | | + | +--- MimeMultipart (abstract) + | | | + | | +--- MimeMultipartMixed + | | | + | | +--- MimeMultipartDigest + | | | + | | +--- MimeMultipartParallel + | | | + | | +--- MimeMultipartAlternative + | | | + | | +--- MimeMultipartRelated + | | | + | | +--- MimeMultipartAppleDouble + | | | + | | +--- MimeSunAttachment + | | | + | | \--- MimeMultipartSigned (abstract) + | | | + | | \--- MimeMultipartSignedCMS + | | + | +--- MimeEncrypted (abstract) + | | | + | | \--- MimeEncryptedPKCS7 + | | + | +--- MimeXlateed (abstract) + | | | + | | \--- MimeXlateed + | | + | +--- MimeMessage + | | + | \--- MimeUntypedText + | + +--- MimeLeaf (abstract) + | | + | +--- MimeInlineText (abstract) + | | | + | | +--- MimeInlineTextPlain + | | | | + | | | \--- MimeInlineTextHTMLAsPlaintext + | | | + | | +--- MimeInlineTextPlainFlowed + | | | + | | +--- MimeInlineTextHTML + | | | | + | | | +--- MimeInlineTextHTMLParsed + | | | | + | | | \--- MimeInlineTextHTMLSanitized + | | | + | | +--- MimeInlineTextRichtext + | | | | + | | | \--- MimeInlineTextEnriched + | | | + | | +--- MimeInlineTextVCard + | | + | +--- MimeInlineImage + | | + | \--- MimeExternalObject + | + \--- MimeExternalBody + + + ========================================================================= + The definition of these classes is somewhat idiosyncratic, since I defined + my own small object system, instead of giving the C++ virus another foothold. + (I would have liked to have written this in Java, but our runtime isn't + quite ready for prime time yet.) + + There is one header file and one source file for each class (for example, + the MimeInlineText class is defined in "mimetext.h" and "mimetext.c".) + Each header file follows the following boiler-plate form: + + TYPEDEFS: these come first to avoid circular dependencies. + + typedef struct FoobarClass FoobarClass; + typedef struct Foobar Foobar; + + CLASS DECLARATION: + Theis structure defines the callback routines and other per-class data + of the class defined in this module. + + struct FoobarClass { + ParentClass superclass; + ...any callbacks or class-variables... + }; + + CLASS DEFINITION: + This variable holds an instance of the one-and-only class record; the + various instances of this class point to this object. (One interrogates + the type of an instance by comparing the value of its class pointer with + the address of this variable.) + + extern FoobarClass foobarClass; + + INSTANCE DECLARATION: + Theis structure defines the per-instance data of an object, and a pointer + to the corresponding class record. + + struct Foobar { + Parent parent; + ...any instance variables... + }; + + Then, in the corresponding .c file, the following structure is used: + + CLASS DEFINITION: + First we pull in the appropriate include file (which includes all necessary + include files for the parent classes) and then we define the class object + using the MimeDefClass macro: + + #include "foobar.h" + #define MIME_SUPERCLASS parentlClass + MimeDefClass(Foobar, FoobarClass, foobarClass, &MIME_SUPERCLASS); + + The definition of MIME_SUPERCLASS is just to move most of the knowlege of the + exact class hierarchy up to the file's header, instead of it being scattered + through the various methods; see below. + + METHOD DECLARATIONS: + We will be putting function pointers into the class object, so we declare + them here. They can generally all be static, since nobody outside of this + file needs to reference them by name; all references to these routines should + be through the class object. + + extern int FoobarMethod(Foobar *); + ...etc... + + CLASS INITIALIZATION FUNCTION: + The MimeDefClass macro expects us to define a function which will finish up + any initialization of the class object that needs to happen before the first + time it is instantiated. Its name must be of the form "<class>Initialize", + and it should initialize the various method slots in the class as + appropriate. Any methods or class variables which this class does not wish + to override will be automatically inherited from the parent class (by virtue + of its class-initialization function having been run first.) Each class + object will only be initialized once. + + static int + FoobarClassInitialize(FoobarClass *class) + { + clazz->method = FoobarMethod. + ...etc... + } + + METHOD DEFINITIONS: + Next come the definitions of the methods we referred to in the class-init + function. The way to access earlier methods (methods defined on the + superclass) is to simply extract them from the superclass's object. + But note that you CANNOT get at methods by indirecting through + object->clazz->superclass: that will only work to one level, and will + go into a loop if some subclass tries to continue on this method. + + The easiest way to do this is to make use of the MIME_SUPERCLASS macro that + was defined at the top of the file, as shown below. The alternative to that + involves typing the literal name of the direct superclass of the class + defined in this file, which will be a maintenance headache if the class + hierarchy changes. If you use the MIME_SUPERCLASS idiom, then a textual + change is required in only one place if this class's superclass changes. + + static void + Foobar_finalize (MimeObject *object) + { + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); // RIGHT + parentClass.whatnot.object.finalize(object); // (works...) + object->clazz->superclass->finalize(object); // WRONG!! + } + + If you write a libmime content type handler, libmime might create several + instances of your class at once and call e.g. the same finalize code for + 3 different objects in a row. + */ + +#include "mimehdrs.h" +#include "nsTArray.h" + +typedef struct MimeObject MimeObject; +typedef struct MimeObjectClass MimeObjectClass; + +#ifdef ENABLE_SMIME +class nsICMSMessage; +#endif // ENABLE_SMIME + +/* (I don't pretend to understand this.) */ +#define cpp_stringify_noop_helper(x)#x +#define cpp_stringify(x) cpp_stringify_noop_helper(x) + +#define MimeObjectClassInitializer(ITYPE,CSUPER) \ + cpp_stringify(ITYPE), \ + sizeof(ITYPE), \ + (MimeObjectClass *) CSUPER, \ + (int (*) (MimeObjectClass *)) ITYPE##ClassInitialize, \ + 0 + +/* Macro used for setting up class definitions. + */ +#define MimeDefClass(ITYPE,CTYPE,CVAR,CSUPER) \ + static int ITYPE##ClassInitialize(ITYPE##Class *); \ + ITYPE##Class CVAR = { ITYPE##ClassInitializer(ITYPE,CSUPER) } + + +/* Creates a new (subclass of) MimeObject of the given class, with the + given headers (which are copied.) + */ +extern MimeObject *mime_new (MimeObjectClass *clazz, MimeHeaders *hdrs, + const char *override_content_type); + + +/* Destroys a MimeObject (or subclass) and all data associated with it. + */ +extern "C" void mime_free (MimeObject *object); + +/* Given a content-type string, finds and returns an appropriate subclass + of MimeObject. A class object is returned. If `exact_match_p' is true, + then only fully-known types will be returned; that is, if it is true, + then "text/x-unknown" will return MimeInlineTextPlainType, but if it is + false, it will return NULL. + */ +extern MimeObjectClass *mime_find_class (const char *content_type, + MimeHeaders *hdrs, + MimeDisplayOptions *opts, + bool exact_match_p); + +/** Given a content-type string, creates and returns an appropriate subclass + * of MimeObject. The headers (from which the content-type was presumably + * extracted) are copied. forceInline is set to true when the caller wants + * the function to ignore opts->show_attachment_inline_p and force inline + * display, e.g., mimemalt wants the body part to be shown inline. + */ +extern MimeObject *mime_create (const char *content_type, MimeHeaders *hdrs, + MimeDisplayOptions *opts, bool forceInline = false); + + +/* Querying the type hierarchy */ +extern bool mime_subclass_p(MimeObjectClass *child, + MimeObjectClass *parent); +extern bool mime_typep(MimeObject *obj, MimeObjectClass *clazz); + +/* Returns a string describing the location of the part (like "2.5.3"). + This is not a full URL, just a part-number. + */ +extern char *mime_part_address(MimeObject *obj); + +/* Returns a string describing the location of the *IMAP* part (like "2.5.3"). + This is not a full URL, just a part-number. + This part is explicitly passed in the X-Mozilla-IMAP-Part header. + Return value must be freed by the caller. + */ +extern char *mime_imap_part_address(MimeObject *obj); + +extern char *mime_external_attachment_url(MimeObject *obj); + +/* Puts a part-number into a URL. If append_p is true, then the part number + is appended to any existing part-number already in that URL; otherwise, + it replaces it. + */ +extern char *mime_set_url_part(const char *url, const char *part, bool append_p); + +/* + cut the part of url for display a attachment as a email. +*/ +extern char *mime_get_base_url(const char *url); + +/* Puts an *IMAP* part-number into a URL. + */ +extern char *mime_set_url_imap_part(const char *url, const char *part, const char *libmimepart); + + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches, and returns the MimeObject (else NULL.) + (part is not a URL -- it's of the form "1.3.5".) + */ +extern MimeObject *mime_address_to_part(const char *part, MimeObject *obj); + + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches; if one is found, returns the Content-Name of that part. + Else returns NULL. (part is not a URL -- it's of the form "1.3.5".) + */ +extern char *mime_find_suggested_name_of_part(const char *part, + MimeObject *obj); + +/* Given a part ID, looks through the MimeObject tree for a sub-part whose ID + number matches; if one is found, returns the Content-Name of that part. + Else returns NULL. (part is not a URL -- it's of the form "1.3.5".) + */ +extern char *mime_find_content_type_of_part(const char *part, MimeObject *obj); + +/* Parse the various "?" options off the URL and into the options struct. + */ +extern int mime_parse_url_options(const char *url, MimeDisplayOptions *); + +#ifdef ENABLE_SMIME + +/* Asks whether the given object is one of the cryptographically signed + or encrypted objects that we know about. (MimeMessageClass uses this + to decide if the headers need to be presented differently.) + */ +extern bool mime_crypto_object_p(MimeHeaders *, bool clearsigned_counts, MimeDisplayOptions *); + +/* Tells whether the given MimeObject is a message which has been encrypted + or signed. (Helper for MIME_GetMessageCryptoState()). + */ +extern void mime_get_crypto_state (MimeObject *obj, + bool *signed_p, bool *encrypted_p, + bool *signed_ok, bool *encrypted_ok); + + +/* Whether the given object has written out the HTML version of its headers + in such a way that it will have a "crypto stamp" next to the headers. If + this is true, then the child must write out its HTML slightly differently + to take this into account... + */ +extern bool mime_crypto_stamped_p(MimeObject *obj); + +/* How the crypto code tells the MimeMessage object what the crypto stamp + on it says. */ +extern void mime_set_crypto_stamp(MimeObject *obj, + bool signed_p, bool encrypted_p); +#endif // ENABLE_SMIME + +class MimeParseStateObject { +public: + + MimeParseStateObject() + {root = 0; separator_queued_p = false; separator_suppressed_p = false; + first_part_written_p = false; post_header_html_run_p = false; first_data_written_p = false; + decrypted_p = false; strippingPart = false; + } + MimeObject *root; /* The outermost parser object. */ + + bool separator_queued_p; /* Whether a separator should be written out + before the next text is written (this lets + us write separators lazily, so that one + doesn't appear at the end, and so that more + than one don't appear in a row.) */ + + bool separator_suppressed_p; /* Whether the currently-queued separator + should not be printed; this is a kludge to + prevent seps from being printed just after + a header block... */ + + bool first_part_written_p; /* State used for the `Show Attachments As + Links' kludge. */ + + bool post_header_html_run_p; /* Whether we've run the + options->generate_post_header_html_fn */ + + bool first_data_written_p; /* State used for Mozilla lazy-stream- + creation evilness. */ + + bool decrypted_p; /* If options->dexlate_p is true, then this + will be set to indicate whether any + dexlateion did in fact occur. + */ + nsTArray<nsCString> partsToStrip; /* if we're stripping parts, what parts to strip */ + nsTArray<nsCString> detachToFiles; /* if we're detaching parts, where each part was detached to */ + bool strippingPart; + nsCString detachedFilePath; /* if we've detached this part, filepath of detached part */ +}; + + +/* Some output-generation utility functions... + */ +extern int MimeObject_output_init(MimeObject *obj, const char *content_type); + +/* The `user_visible_p' argument says whether the output that has just been + written will cause characters or images to show up on the screen, that + is, it should be false if the stuff being written is merely structural + HTML or whitespace ("<P>", "</TABLE>", etc.) This information is used + when making the decision of whether a separating <HR> is needed. + */ +extern int MimeObject_write(MimeObject *, const char *data, int32_t length, + bool user_visible_p); +extern int MimeOptions_write(MimeHeaders *, + MimeDisplayOptions *, + const char *data, int32_t length, + bool user_visible_p); + +/* Writes out the right kind of HR (or rather, queues it for writing.) */ +extern int MimeObject_write_separator(MimeObject *); + +extern bool MimeObjectIsMessageBody(MimeObject *obj); + +struct MimeDisplayData { /* This struct is what we hang off of + (context)->mime_data, to remember info + about the last MIME object we've + parsed and displayed. See + MimeGuessURLContentName() below. + */ + MimeObject *last_parsed_object; + char *last_parsed_url; +}; + +#endif /* _MIMEI_H_ */ diff --git a/mailnews/mime/src/mimeiimg.cpp b/mailnews/mime/src/mimeiimg.cpp new file mode 100644 index 000000000..1d47db2ab --- /dev/null +++ b/mailnews/mime/src/mimeiimg.cpp @@ -0,0 +1,249 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "mimeiimg.h" +#include "mimemoz2.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "nsMimeTypes.h" +#include "nsMimeStringResources.h" +#include "nsINetUtil.h" +#include "nsMsgUtils.h" + +#define MIME_SUPERCLASS mimeLeafClass +MimeDefClass(MimeInlineImage, MimeInlineImageClass, + mimeInlineImageClass, &MIME_SUPERCLASS); + +static int MimeInlineImage_initialize (MimeObject *); +static void MimeInlineImage_finalize (MimeObject *); +static int MimeInlineImage_parse_begin (MimeObject *); +static int MimeInlineImage_parse_line (const char *, int32_t, MimeObject *); +static int MimeInlineImage_parse_eof (MimeObject *, bool); +static int MimeInlineImage_parse_decoded_buffer (const char *, int32_t, MimeObject *); + +static int +MimeInlineImageClassInitialize(MimeInlineImageClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeLeafClass *lclass = (MimeLeafClass *) clazz; + + NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + oclass->initialize = MimeInlineImage_initialize; + oclass->finalize = MimeInlineImage_finalize; + oclass->parse_begin = MimeInlineImage_parse_begin; + oclass->parse_line = MimeInlineImage_parse_line; + oclass->parse_eof = MimeInlineImage_parse_eof; + lclass->parse_decoded_buffer = MimeInlineImage_parse_decoded_buffer; + + return 0; +} + + +static int +MimeInlineImage_initialize (MimeObject *object) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeInlineImage_finalize (MimeObject *object) +{ + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + +static int +MimeInlineImage_parse_begin (MimeObject *obj) +{ + MimeInlineImage *img = (MimeInlineImage *) obj; + + int status; + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + if (!obj->output_p) return 0; + + if (!obj->options || !obj->options->output_fn || + // don't bother processing if the consumer doesn't want us + // gunking the body up. + obj->options->write_pure_bodies) + return 0; + + if (obj->options && + obj->options->image_begin && + obj->options->write_html_p && + obj->options->image_write_buffer) + { + char *html, *part, *image_url; + const char *ct; + + part = mime_part_address(obj); + if (!part) return MIME_OUT_OF_MEMORY; + + char *no_part_url = nullptr; + if (obj->options->part_to_load && obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) + no_part_url = mime_get_base_url(obj->options->url); + + if (no_part_url) + { + image_url = mime_set_url_part(no_part_url, part, true); + PR_Free(no_part_url); + } + else + image_url = mime_set_url_part(obj->options->url, part, true); + + if (!image_url) + { + PR_Free(part); + return MIME_OUT_OF_MEMORY; + } + PR_Free(part); + + ct = obj->content_type; + if (!ct) ct = IMAGE_GIF; /* Can't happen? Close enough. */ + + // Fill in content type and attachment name here. + nsAutoCString url_with_filename(image_url); + url_with_filename += "&type="; + url_with_filename += ct; + char * filename = MimeHeaders_get_name ( obj->headers, obj->options ); + if (filename) + { + nsCString escapedName; + MsgEscapeString(nsDependentCString(filename), nsINetUtil::ESCAPE_URL_PATH, + escapedName); + url_with_filename += "&filename="; + url_with_filename += escapedName; + PR_Free(filename); + } + + // We need to separate images with HR's... + MimeObject_write_separator(obj); + + img->image_data = + obj->options->image_begin(url_with_filename.get(), ct, obj->options->stream_closure); + PR_Free(image_url); + + if (!img->image_data) return MIME_OUT_OF_MEMORY; + + html = obj->options->make_image_html(img->image_data); + if (!html) return MIME_OUT_OF_MEMORY; + + status = MimeObject_write(obj, html, strlen(html), true); + PR_Free(html); + if (status < 0) return status; + } + + // + // Now we are going to see if we should set the content type in the + // URI for the url being run... + // + if (obj->options && obj->options->stream_closure && obj->content_type) + { + mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure); + if ( (msd) && (msd->channel) ) + { + msd->channel->SetContentType(nsDependentCString(obj->content_type)); + } + } + + return 0; +} + + +static int +MimeInlineImage_parse_eof (MimeObject *obj, bool abort_p) +{ + MimeInlineImage *img = (MimeInlineImage *) obj; + int status; + if (obj->closed_p) return 0; + + /* Force out any buffered data from the superclass (the base64 decoder.) */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) abort_p = true; + + if (img->image_data) + { + obj->options->image_end(img->image_data, + (status < 0 ? status : (abort_p ? -1 : 0))); + img->image_data = 0; + } + + return status; +} + + +static int +MimeInlineImage_parse_decoded_buffer (const char *buf, int32_t size, MimeObject *obj) +{ + /* This is called (by MimeLeafClass->parse_buffer) with blocks of data + that have already been base64-decoded. Pass this raw image data + along to the backend-specific image display code. + */ + MimeInlineImage *img = (MimeInlineImage *) obj; + int status; + + /* Don't do a roundtrip through XPConnect when we're only interested in + * metadata and size. 0 means ok, the caller just checks for negative return + * value + */ + if (obj->options && obj->options->metadata_only) + return 0; + + if (obj->output_p && + obj->options && + !obj->options->write_html_p) + { + /* in this case, we just want the raw data... + Make the stream, if it's not made, and dump the data out. + */ + + if (!obj->options->state->first_data_written_p) + { + status = MimeObject_output_init(obj, 0); + if (status < 0) return status; + NS_ASSERTION(obj->options->state->first_data_written_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + } + + return MimeObject_write(obj, buf, size, true); + } + + + if (!obj->options || + !obj->options->image_write_buffer) + return 0; + + /* If we don't have any image data, the image_end method must have already + been called, so don't call image_write_buffer again. */ + if (!img->image_data) return 0; + + /* Hand this data off to the backend-specific image display stream. + */ + status = obj->options->image_write_buffer (buf, size, img->image_data); + + /* If the image display stream fails, then close the stream - but do not + return the failure status, and do not give up on parsing this object. + Just because the image data was corrupt doesn't mean we need to give up + on the whole document; we can continue by just skipping over the rest of + this part, and letting our parent continue. + */ + if (status < 0) + { + obj->options->image_end (img->image_data, status); + img->image_data = 0; + status = 0; + } + + return status; +} + + +static int +MimeInlineImage_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + NS_ERROR("This method should never be called (inline images do no line buffering)."); + return -1; +} diff --git a/mailnews/mime/src/mimeiimg.h b/mailnews/mime/src/mimeiimg.h new file mode 100644 index 000000000..ea8a9a3d1 --- /dev/null +++ b/mailnews/mime/src/mimeiimg.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEIIMG_H_ +#define _MIMEIIMG_H_ + +#include "mimeleaf.h" + +/* The MimeInlineImage class implements those MIME image types which can be + displayed inline. + */ + +typedef struct MimeInlineImageClass MimeInlineImageClass; +typedef struct MimeInlineImage MimeInlineImage; + +struct MimeInlineImageClass { + MimeLeafClass leaf; +}; + +extern MimeInlineImageClass mimeInlineImageClass; + +struct MimeInlineImage { + MimeLeaf leaf; + + /* Opaque data object for the backend-specific inline-image-display code + (internal-external-reconnect nastiness.) */ + void *image_data; +}; + +#define MimeInlineImageClassInitializer(ITYPE,CSUPER) \ + { MimeLeafClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEIIMG_H_ */ diff --git a/mailnews/mime/src/mimeleaf.cpp b/mailnews/mime/src/mimeleaf.cpp new file mode 100644 index 000000000..5d35ead37 --- /dev/null +++ b/mailnews/mime/src/mimeleaf.cpp @@ -0,0 +1,221 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "modmimee.h" +#include "mimeleaf.h" +#include "nsMimeTypes.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "nsMimeStringResources.h" + +#define MIME_SUPERCLASS mimeObjectClass +MimeDefClass(MimeLeaf, MimeLeafClass, mimeLeafClass, &MIME_SUPERCLASS); + +static int MimeLeaf_initialize (MimeObject *); +static void MimeLeaf_finalize (MimeObject *); +static int MimeLeaf_parse_begin (MimeObject *); +static int MimeLeaf_parse_buffer (const char *, int32_t, MimeObject *); +static int MimeLeaf_parse_line (const char *, int32_t, MimeObject *); +static int MimeLeaf_close_decoder (MimeObject *); +static int MimeLeaf_parse_eof (MimeObject *, bool); +static bool MimeLeaf_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs); + +static int +MimeLeafClassInitialize(MimeLeafClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + oclass->initialize = MimeLeaf_initialize; + oclass->finalize = MimeLeaf_finalize; + oclass->parse_begin = MimeLeaf_parse_begin; + oclass->parse_buffer = MimeLeaf_parse_buffer; + oclass->parse_line = MimeLeaf_parse_line; + oclass->parse_eof = MimeLeaf_parse_eof; + oclass->displayable_inline_p = MimeLeaf_displayable_inline_p; + clazz->close_decoder = MimeLeaf_close_decoder; + + /* Default `parse_buffer' method is one which line-buffers the now-decoded + data and passes it on to `parse_line'. (We snarf the implementation of + this method from our superclass's implementation of `parse_buffer', which + inherited it from MimeObject.) + */ + clazz->parse_decoded_buffer = + ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_buffer; + + return 0; +} + + +static int +MimeLeaf_initialize (MimeObject *obj) +{ + /* This is an abstract class; it shouldn't be directly instantiated. */ + NS_ASSERTION(obj->clazz != (MimeObjectClass *) &mimeLeafClass, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + + // Initial size is -1 (meaning "unknown size") - we'll correct it in + // parse_buffer. + MimeLeaf *leaf = (MimeLeaf *) obj; + leaf->sizeSoFar = -1; + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj); +} + + +static void +MimeLeaf_finalize (MimeObject *object) +{ + MimeLeaf *leaf = (MimeLeaf *)object; + object->clazz->parse_eof (object, false); + + /* Free the decoder data, if it's still around. It was probably freed + in MimeLeaf_parse_eof(), but just in case... */ + if (leaf->decoder_data) + { + MimeDecoderDestroy(leaf->decoder_data, true); + leaf->decoder_data = 0; + } + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize (object); +} + + +static int +MimeLeaf_parse_begin (MimeObject *obj) +{ + MimeLeaf *leaf = (MimeLeaf *) obj; + MimeDecoderData *(*fn) (MimeConverterOutputCallback, void*) = 0; + + /* Initialize a decoder if necessary. + */ + if (!obj->encoding || + // If we need the object as "raw" for saving or forwarding, + // don't decode text parts of message types. Other output formats, + // like "display" (nsMimeMessageBodyDisplay), need decoding. + (obj->options->format_out == nsMimeOutput::nsMimeMessageRaw && + obj->parent && + (!PL_strcasecmp(obj->parent->content_type, MESSAGE_NEWS) || + !PL_strcasecmp(obj->parent->content_type, MESSAGE_RFC822)) && + !PL_strncasecmp(obj->content_type, "text/", 5))) + /* no-op */ ; + else if (!PL_strcasecmp(obj->encoding, ENCODING_BASE64)) + fn = &MimeB64DecoderInit; + else if (!PL_strcasecmp(obj->encoding, ENCODING_QUOTED_PRINTABLE)) + leaf->decoder_data = + MimeQPDecoderInit(((MimeConverterOutputCallback) + ((MimeLeafClass *)obj->clazz)->parse_decoded_buffer), + obj, obj); + else if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE2) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE3) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE4)) + fn = &MimeUUDecoderInit; + else if (!PL_strcasecmp(obj->encoding, ENCODING_YENCODE)) + fn = &MimeYDecoderInit; + + if (fn) + { + leaf->decoder_data = + fn (/* The MimeConverterOutputCallback cast is to turn the `void' argument + into `MimeObject'. */ + ((MimeConverterOutputCallback) + ((MimeLeafClass *)obj->clazz)->parse_decoded_buffer), + obj); + + if (!leaf->decoder_data) + return MIME_OUT_OF_MEMORY; + } + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); +} + + +static int +MimeLeaf_parse_buffer (const char *buffer, int32_t size, MimeObject *obj) +{ + MimeLeaf *leaf = (MimeLeaf *) obj; + + NS_ASSERTION(!obj->closed_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00"); + if (obj->closed_p) return -1; + + /* If we're not supposed to write this object, bug out now. + */ + if (!obj->output_p || + !obj->options || + !obj->options->output_fn) + return 0; + + int rv; + if (leaf->sizeSoFar == -1) + leaf->sizeSoFar = 0; + + if (leaf->decoder_data && + obj->options && + obj->options->format_out != nsMimeOutput::nsMimeMessageDecrypt + && obj->options->format_out != nsMimeOutput::nsMimeMessageAttach) { + int outSize = 0; + rv = MimeDecoderWrite (leaf->decoder_data, buffer, size, &outSize); + leaf->sizeSoFar += outSize; + } + else { + rv = ((MimeLeafClass *)obj->clazz)->parse_decoded_buffer (buffer, size, + obj); + leaf->sizeSoFar += size; + } + return rv; +} + +static int +MimeLeaf_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + NS_ERROR("MimeLeaf_parse_line shouldn't ever be called."); + return -1; +} + + +static int +MimeLeaf_close_decoder (MimeObject *obj) +{ + MimeLeaf *leaf = (MimeLeaf *) obj; + + if (leaf->decoder_data) + { + int status = MimeDecoderDestroy(leaf->decoder_data, false); + leaf->decoder_data = 0; + return status; + } + + return 0; +} + + +static int +MimeLeaf_parse_eof (MimeObject *obj, bool abort_p) +{ + MimeLeaf *leaf = (MimeLeaf *) obj; + if (obj->closed_p) return 0; + + /* Close off the decoder, to cause it to give up any buffered data that + it is still holding. + */ + if (leaf->decoder_data) + { + int status = MimeLeaf_close_decoder(obj); + if (status < 0) return status; + } + + /* Now run the superclass's parse_eof, which will force out the line + buffer (which we may have just repopulated, above.) + */ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof (obj, abort_p); +} + + +static bool +MimeLeaf_displayable_inline_p (MimeObjectClass *clazz, MimeHeaders *hdrs) +{ + return true; +} diff --git a/mailnews/mime/src/mimeleaf.h b/mailnews/mime/src/mimeleaf.h new file mode 100644 index 000000000..10cfdc59a --- /dev/null +++ b/mailnews/mime/src/mimeleaf.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMELEAF_H_ +#define _MIMELEAF_H_ + +#include "mimeobj.h" +#include "modmimee.h" + +/* MimeLeaf is the class for the objects representing all MIME types which + are not containers for other MIME objects. The implication of this is + that they are MIME types which can have Content-Transfer-Encodings + applied to their data. This class provides that service in its + parse_buffer() method: + + int (*parse_decoded_buffer) (const char *buf, int32_t size, MimeObject *obj) + + The `parse_buffer' method of MimeLeaf passes each block of data through + the appropriate decoder (if any) and then calls `parse_decoded_buffer' + on each block (not line) of output. + + The default `parse_decoded_buffer' method of MimeLeaf line-buffers the + now-decoded data, handing each line to the `parse_line' method in turn. + If different behavior is desired (for example, if a class wants access + to the decoded data before it is line-buffered) the `parse_decoded_buffer' + method should be overridden. (MimeExternalObject does this.) + */ + +typedef struct MimeLeafClass MimeLeafClass; +typedef struct MimeLeaf MimeLeaf; + +struct MimeLeafClass { + MimeObjectClass object; + /* This is the callback that is handed to the decoder. */ + int (*parse_decoded_buffer) (const char *buf, int32_t size, MimeObject *obj); + int (*close_decoder) (MimeObject *obj); +}; + +extern MimeLeafClass mimeLeafClass; + +struct MimeLeaf { + MimeObject object; /* superclass variables */ + + /* If we're doing Base64, Quoted-Printable, or UU decoding, this is the + state object for the decoder. */ + MimeDecoderData *decoder_data; + + /* We want to count the size of the MimeObject to offer consumers the + * opportunity to display the sizes of attachments. + */ + int sizeSoFar; +}; + +#define MimeLeafClassInitializer(ITYPE,CSUPER) \ + { MimeObjectClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMELEAF_H_ */ diff --git a/mailnews/mime/src/mimemalt.cpp b/mailnews/mime/src/mimemalt.cpp new file mode 100644 index 000000000..3354b1f9b --- /dev/null +++ b/mailnews/mime/src/mimemalt.cpp @@ -0,0 +1,580 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + BACKGROUND + ---------- + + At the simplest level, multipart/alternative means "pick one of these and + display it." However, it's actually a lot more complicated than that. + + The alternatives are in preference order, and counterintuitively, they go + from *least* to *most* preferred rather than the reverse. Therefore, when + we're parsing, we can't just take the first one we like and throw the rest + away -- we have to parse through the whole thing, discarding the n'th part if + we are capable of displaying the n+1'th. + + Adding a wrinkle to that is the fact that we give the user the option of + demanding the plain-text alternative even though we are perfectly capable of + displaying the HTML, and it is almost always the preferred format, i.e., it + almost always comes after the plain-text alternative. + + Speaking of which, you can't assume that each of the alternatives is just a + basic text/[whatever]. There may be, for example, a text/plain followed by a + multipart/related which contains text/html and associated embedded + images. Yikes! + + You also can't assume that there will be just two parts. There can be an + arbitrary number, and the ones we are capable of displaying and the ones we + aren't could be interspersed in any order by the producer of the MIME. + + We can't just throw away the parts we're not displaying when we're processing + the MIME for display. If we were to do that, then the MIME parts that + remained wouldn't get numbered properly, and that would mean, for example, + that deleting attachments wouldn't work in some messages. Indeed, that very + problem is what prompted a rewrite of this file into its current + architecture. + + ARCHITECTURE + ------------ + + Parts are read and queued until we know whether we're going to display + them. If the first pending part is one we don't know how to display, then we + can add it to the MIME structure immediatelly, with output_p disabled. If the + first pending part is one we know how to display, then we can't add it to the + in-memory MIME structure until either (a) we encounter a later, more + preferred part we know how to display, or (b) we reach the end of the + parts. A display-capable part of the queue may be followed by one or more + display-incapable parts. We can't add them to the in-memory structure until + we figure out what to do with the first, display-capable pending part, + because otherwise the order and numbering will be wrong. All of the logic in + this paragraph is implemented in the flush_children function. + + The display_cached_part function is what actually adds a MIME part to the + in-memory MIME structure. There is one complication there which forces us to + violate abstrations... Even if we set output_p on a child before adding it to + the parent, the parse_begin function resets it. The kluge I came up with to + prevent that was to give the child a separate options object and set + output_fn to nullptr in it, because that causes parse_begin to set output_p to + false. This seemed like the least onerous way to accomplish this, although I + can't say it's a solution I'm particularly fond of. + + Another complication in display_cached_part is that if we were just a normal + multipart type, we could rely on MimeMultipart_parse_line to notify emitters + about content types, character sets, part numbers, etc. as our new children + get created. However, since we defer creation of some children, the + notification doesn't happen there, so we have to handle it + ourselves. Unfortunately, this requires a small abstraction violation in + MimeMultipart_parse_line -- we have to check there if the entity is + multipart/alternative and if so not notify emitters there because + MimeMultipartAlternative_create_child handles it. + + - Jonathan Kamens, 2010-07-23 + + When the option prefer_plaintext is on, the last text/plain part + should be preferred over any other part that can be displayed. But + if no text/plain part is found, then the algorithm should go as + normal and convert any html part found to text. To achive this I + found that the simplest way was to change the function display_part_p + into returning priority as an integer instead of boolean can/can't + display. Then I also changed the function flush_children so it selects + the last part with the highest priority. (Priority 0 means it cannot + be displayed and the part is never choosen.) + + - Terje Bråten, 2013-02-16 +*/ + +#include "mimemalt.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "nsMimeTypes.h" +#include "nsMimeStringResources.h" +#include "nsIPrefBranch.h" +#include "mimemoz2.h" // for prefs + +extern "C" MimeObjectClass mimeMultipartRelatedClass; + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartAlternative, MimeMultipartAlternativeClass, + mimeMultipartAlternativeClass, &MIME_SUPERCLASS); + +static int MimeMultipartAlternative_initialize (MimeObject *); +static void MimeMultipartAlternative_finalize (MimeObject *); +static int MimeMultipartAlternative_parse_eof (MimeObject *, bool); +static int MimeMultipartAlternative_create_child(MimeObject *); +static int MimeMultipartAlternative_parse_child_line (MimeObject *, const char *, + int32_t, bool); +static int MimeMultipartAlternative_close_child(MimeObject *); + +static int MimeMultipartAlternative_flush_children(MimeObject *, bool, priority_t); +static priority_t MimeMultipartAlternative_display_part_p(MimeObject *self, + MimeHeaders *sub_hdrs); +static priority_t MimeMultipartAlternative_prioritize_part(char *content_type, + bool prefer_plaintext); + +static int MimeMultipartAlternative_display_cached_part(MimeObject *, + MimeHeaders *, + MimePartBufferData *, + bool); + +static int +MimeMultipartAlternativeClassInitialize(MimeMultipartAlternativeClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + PR_ASSERT(!oclass->class_initialized); + oclass->initialize = MimeMultipartAlternative_initialize; + oclass->finalize = MimeMultipartAlternative_finalize; + oclass->parse_eof = MimeMultipartAlternative_parse_eof; + mclass->create_child = MimeMultipartAlternative_create_child; + mclass->parse_child_line = MimeMultipartAlternative_parse_child_line; + mclass->close_child = MimeMultipartAlternative_close_child; + return 0; +} + + +static int +MimeMultipartAlternative_initialize (MimeObject *obj) +{ + MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj; + + NS_ASSERTION(!malt->part_buffers, "object initialized multiple times"); + NS_ASSERTION(!malt->buffered_hdrs, "object initialized multiple times"); + malt->pending_parts = 0; + malt->max_parts = 0; + malt->buffered_priority = PRIORITY_UNDISPLAYABLE; + malt->buffered_hdrs = nullptr; + malt->part_buffers = nullptr; + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj); +} + +static void +MimeMultipartAlternative_cleanup(MimeObject *obj) +{ + MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj; + int32_t i; + + for (i = 0; i < malt->pending_parts; i++) { + MimeHeaders_free(malt->buffered_hdrs[i]); + MimePartBufferDestroy(malt->part_buffers[i]); + } + PR_FREEIF(malt->buffered_hdrs); + PR_FREEIF(malt->part_buffers); + malt->pending_parts = 0; + malt->max_parts = 0; +} + + +static void +MimeMultipartAlternative_finalize (MimeObject *obj) +{ + MimeMultipartAlternative_cleanup(obj); + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj); +} + + +static int +MimeMultipartAlternative_flush_children(MimeObject *obj, + bool finished, + priority_t next_priority) +{ + /* + The cache should always have at the head the part with highest priority. + + Possible states: + + 1. Cache contains nothing: do nothing. + + 2. Finished, and the cache contains one displayable body followed + by zero or more bodies with lower priority: + + 3. Finished, and the cache contains one non-displayable body: + create it with output off. + + 4. Not finished, and the cache contains one displayable body + followed by zero or more bodies with lower priority, and the new + body we're about to create is higher or equal priority: + create all cached bodies with output off. + + 5. Not finished, and the cache contains one displayable body + followed by zero or more bodies with lower priority, and the new + body we're about to create has lower priority: do nothing. + + 6. Not finished, and the cache contains one non-displayable body: + create it with output off. + */ + MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj; + bool have_displayable, do_flush, do_display; + + /* Case 1 */ + if (! malt->pending_parts) + return 0; + + have_displayable = (malt->buffered_priority > next_priority); + + if (finished && have_displayable) { + /* Case 2 */ + do_flush = true; + do_display = true; + } + else if (finished && ! have_displayable) { + /* Case 3 */ + do_flush = true; + do_display = false; + } + else if (! finished && have_displayable) { + /* Case 5 */ + do_flush = false; + do_display = false; + } + else if (! finished && ! have_displayable) { + /* Case 4 */ + /* Case 6 */ + do_flush = true; + do_display = false; + } + else { + NS_ERROR("mimemalt.cpp: logic error in flush_children"); + return -1; + } + + if (do_flush) { + int32_t i; + for (i = 0; i < malt->pending_parts; i++) { + MimeMultipartAlternative_display_cached_part(obj, + malt->buffered_hdrs[i], + malt->part_buffers[i], + do_display && (i == 0)); + MimeHeaders_free(malt->buffered_hdrs[i]); + MimePartBufferDestroy(malt->part_buffers[i]); + } + malt->pending_parts = 0; + } + return 0; +} + +static int +MimeMultipartAlternative_parse_eof (MimeObject *obj, bool abort_p) +{ + int status = 0; + + if (obj->closed_p) return 0; + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + + + status = MimeMultipartAlternative_flush_children(obj, true, + PRIORITY_UNDISPLAYABLE); + if (status < 0) + return status; + + MimeMultipartAlternative_cleanup(obj); + + return status; +} + + +static int +MimeMultipartAlternative_create_child(MimeObject *obj) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj; + + priority_t priority = + MimeMultipartAlternative_display_part_p (obj, mult->hdrs); + + MimeMultipartAlternative_flush_children(obj, false, priority); + + mult->state = MimeMultipartPartFirstLine; + int32_t i = malt->pending_parts++; + + if (i==0) { + malt->buffered_priority = priority; + } + + if (malt->pending_parts > malt->max_parts) { + malt->max_parts = malt->pending_parts; + MimeHeaders **newBuf = (MimeHeaders **) + PR_REALLOC(malt->buffered_hdrs, + malt->max_parts * sizeof(*malt->buffered_hdrs)); + NS_ENSURE_TRUE(newBuf, MIME_OUT_OF_MEMORY); + malt->buffered_hdrs = newBuf; + + MimePartBufferData **newBuf2 = (MimePartBufferData **) + PR_REALLOC(malt->part_buffers, + malt->max_parts * sizeof(*malt->part_buffers)); + NS_ENSURE_TRUE(newBuf2, MIME_OUT_OF_MEMORY); + malt->part_buffers = newBuf2; + } + + malt->buffered_hdrs[i] = MimeHeaders_copy(mult->hdrs); + NS_ENSURE_TRUE(malt->buffered_hdrs[i], MIME_OUT_OF_MEMORY); + + malt->part_buffers[i] = MimePartBufferCreate(); + NS_ENSURE_TRUE(malt->part_buffers[i], MIME_OUT_OF_MEMORY); + + return 0; +} + + +static int +MimeMultipartAlternative_parse_child_line (MimeObject *obj, + const char *line, int32_t length, + bool first_line_p) +{ + MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj; + + NS_ASSERTION(malt->pending_parts, "should be pending parts, but there aren't"); + if (!malt->pending_parts) + return -1; + int32_t i = malt->pending_parts - 1; + + /* Push this line into the buffer for later retrieval. */ + return MimePartBufferWrite (malt->part_buffers[i], line, length); +} + + +static int +MimeMultipartAlternative_close_child(MimeObject *obj) +{ + MimeMultipartAlternative *malt = (MimeMultipartAlternative *) obj; + MimeMultipart *mult = (MimeMultipart *) obj; + + /* PR_ASSERT(malt->part_buffer); Some Mac brokenness trips this... + if (!malt->part_buffer) return -1; */ + + if (malt->pending_parts) + MimePartBufferClose(malt->part_buffers[malt->pending_parts-1]); + + /* PR_ASSERT(mult->hdrs); I expect the Mac trips this too */ + + if (mult->hdrs) { + MimeHeaders_free(mult->hdrs); + mult->hdrs = 0; + } + + return 0; +} + + +static priority_t +MimeMultipartAlternative_display_part_p(MimeObject *self, + MimeHeaders *sub_hdrs) +{ + priority_t priority = PRIORITY_UNDISPLAYABLE; + char *ct = MimeHeaders_get (sub_hdrs, HEADER_CONTENT_TYPE, true, false); + if (!ct) + return priority; + + /* RFC 1521 says: + Receiving user agents should pick and display the last format + they are capable of displaying. In the case where one of the + alternatives is itself of type "multipart" and contains unrecognized + sub-parts, the user agent may choose either to show that alternative, + an earlier alternative, or both. + */ + + // We must pass 'true' as last parameter so that text/calendar is + // only displayable when Lightning is installed. + MimeObjectClass *clazz = mime_find_class(ct, sub_hdrs, self->options, true); + if (clazz && clazz->displayable_inline_p(clazz, sub_hdrs)) { + // prefer_plaintext pref + bool prefer_plaintext = false; + nsIPrefBranch *prefBranch = GetPrefBranch(self->options); + if (prefBranch) { + prefBranch->GetBoolPref("mailnews.display.prefer_plaintext", + &prefer_plaintext); + } + prefer_plaintext = prefer_plaintext && + (self->options->format_out != nsMimeOutput::nsMimeMessageSaveAs) && + (self->options->format_out != nsMimeOutput::nsMimeMessageRaw); + + priority = MimeMultipartAlternative_prioritize_part(ct, prefer_plaintext); + } + + PR_FREEIF(ct); + return priority; +} + +/** +* RFC 1521 says we should display the last format we are capable of displaying. +* But for various reasons (mainly to improve the user experience) we choose +* to ignore that in some cases, and rather pick one that we prioritize. +*/ +static priority_t +MimeMultipartAlternative_prioritize_part(char *content_type, + bool prefer_plaintext) +{ + /* + * PRIORITY_NORMAL is the priority of text/html, multipart/..., etc. that + * we normally display. We should try to have as few exceptions from + * PRIORITY_NORMAL as possible + */ + + /* (with no / in the type) */ + if (!PL_strcasecmp(content_type, "text")) { + if (prefer_plaintext) { + /* When in plain text view, a plain text part is what we want. */ + return PRIORITY_HIGH; + } + /* We normally prefer other parts over the unspecified text type. */ + return PRIORITY_TEXT_UNKNOWN; + } + + if (!PL_strncasecmp(content_type, "text/", 5)) { + char *text_type = content_type + 5; + + if (!PL_strncasecmp(text_type, "plain", 5)) { + if (prefer_plaintext) { + /* When in plain text view, + the text/plain part is exactly what we want */ + return PRIORITY_HIGHEST; + } + /* + * Because the html and the text part may be switched, + * or we have an extra text/plain added by f.ex. a buggy virus checker, + * we prioritize text/plain lower than normal. + */ + return PRIORITY_TEXT_PLAIN; + } + + if (!PL_strncasecmp(text_type, "calendar", 8) && prefer_plaintext) { + /* + * text/calendar receives an equally high priority so an invitation + * shows even in plaintext mode. + */ + return PRIORITY_HIGHEST; + } + + /* Need to white-list all text/... types that are or could be implemented. */ + if (!PL_strncasecmp(text_type, "html", 4) || + !PL_strncasecmp(text_type, "enriched", 8) || + !PL_strncasecmp(text_type, "richtext", 8) || + !PL_strncasecmp(text_type, "calendar", 8) || + !PL_strncasecmp(text_type, "rtf", 3)) { + return PRIORITY_NORMAL; + } + + /* We prefer other parts over unknown text types. */ + return PRIORITY_TEXT_UNKNOWN; + } + + // Guard against rogue messages with incorrect MIME structure and + // don't show images when plain text is requested. + if (!PL_strncasecmp(content_type, "image", 5)) { + if (prefer_plaintext) + return PRIORITY_UNDISPLAYABLE; + else + return PRIORITY_LOW; + } + + return PRIORITY_NORMAL; +} + +static int +MimeMultipartAlternative_display_cached_part(MimeObject *obj, + MimeHeaders *hdrs, + MimePartBufferData *buffer, + bool do_display) +{ + int status; + bool old_options_no_output_p; + + char *ct = (hdrs + ? MimeHeaders_get (hdrs, HEADER_CONTENT_TYPE, true, false) + : 0); + const char *dct = (((MimeMultipartClass *) obj->clazz)->default_part_type); + MimeObject *body; + /** Don't pass in NULL as the content-type (this means that the + * auto-uudecode-hack won't ever be done for subparts of a + * multipart, but only for untyped children of message/rfc822. + */ + const char *uct = (ct && *ct) ? ct : (dct ? dct: TEXT_PLAIN); + + // We always want to display the cached part inline. + body = mime_create(uct, hdrs, obj->options, true); + PR_FREEIF(ct); + if (!body) return MIME_OUT_OF_MEMORY; + body->output_p = do_display; + + status = ((MimeContainerClass *) obj->clazz)->add_child(obj, body); + if (status < 0) + { + mime_free(body); + return status; + } + /* add_child assigns body->options from obj->options, but that's + just a pointer so if we muck with it in the child it'll modify + the parent as well, which we definitely don't want. Therefore we + need to make a copy of the old value and restore it later. */ + old_options_no_output_p = obj->options->no_output_p; + if (! do_display) + body->options->no_output_p = true; + +#ifdef MIME_DRAFTS + /* if this object is a child of a multipart/related object, the parent is + taking care of decomposing the whole part, don't need to do it at this level. + However, we still have to call decompose_file_init_fn and decompose_file_close_fn + in order to set the correct content-type. But don't call MimePartBufferRead + */ + bool multipartRelatedChild = mime_typep(obj->parent,(MimeObjectClass*)&mimeMultipartRelatedClass); + bool decomposeFile = do_display && obj->options && + obj->options->decompose_file_p && + obj->options->decompose_file_init_fn && + !mime_typep(body, (MimeObjectClass *) &mimeMultipartClass); + + if (decomposeFile) + { + status = obj->options->decompose_file_init_fn ( + obj->options->stream_closure, hdrs); + if (status < 0) return status; + } +#endif /* MIME_DRAFTS */ + + /* Now that we've added this new object to our list of children, + notify emitters and start its parser going. */ + MimeMultipart_notify_emitter(body); + + status = body->clazz->parse_begin(body); + if (status < 0) return status; + +#ifdef MIME_DRAFTS + if (decomposeFile && !multipartRelatedChild) + status = MimePartBufferRead (buffer, + obj->options->decompose_file_output_fn, + obj->options->stream_closure); + else +#endif /* MIME_DRAFTS */ + + status = MimePartBufferRead (buffer, + /* The MimeConverterOutputCallback cast is to turn the + `void' argument into `MimeObject'. */ + ((MimeConverterOutputCallback) body->clazz->parse_buffer), + body); + + if (status < 0) return status; + + /* Done parsing. */ + status = body->clazz->parse_eof(body, false); + if (status < 0) return status; + status = body->clazz->parse_end(body, false); + if (status < 0) return status; + +#ifdef MIME_DRAFTS + if (decomposeFile) + { + status = obj->options->decompose_file_close_fn ( obj->options->stream_closure ); + if (status < 0) return status; + } +#endif /* MIME_DRAFTS */ + + /* Restore options to what parent classes expects. */ + obj->options->no_output_p = old_options_no_output_p; + + return 0; +} diff --git a/mailnews/mime/src/mimemalt.h b/mailnews/mime/src/mimemalt.h new file mode 100644 index 000000000..6cd792f54 --- /dev/null +++ b/mailnews/mime/src/mimemalt.h @@ -0,0 +1,48 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMALT_H_ +#define _MIMEMALT_H_ + +#include "mimemult.h" +#include "mimepbuf.h" + +/* The MimeMultipartAlternative class implements the multipart/alternative + MIME container, which displays only one (the `best') of a set of enclosed + documents. + */ + +typedef struct MimeMultipartAlternativeClass MimeMultipartAlternativeClass; +typedef struct MimeMultipartAlternative MimeMultipartAlternative; + +struct MimeMultipartAlternativeClass { + MimeMultipartClass multipart; +}; + +extern "C" MimeMultipartAlternativeClass mimeMultipartAlternativeClass; + +enum priority_t {PRIORITY_UNDISPLAYABLE, + PRIORITY_LOW, + PRIORITY_TEXT_UNKNOWN, + PRIORITY_TEXT_PLAIN, + PRIORITY_NORMAL, + PRIORITY_HIGH, + PRIORITY_HIGHEST}; + +struct MimeMultipartAlternative { + MimeMultipart multipart; /* superclass variables */ + + MimeHeaders **buffered_hdrs; /* The headers of pending parts */ + MimePartBufferData **part_buffers; /* The data of pending parts + (see mimepbuf.h) */ + int32_t pending_parts; + int32_t max_parts; + priority_t buffered_priority; /* Priority of head of pending parts */ +}; + +#define MimeMultipartAlternativeClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMALT_H_ */ diff --git a/mailnews/mime/src/mimemapl.cpp b/mailnews/mime/src/mimemapl.cpp new file mode 100644 index 000000000..449d80ac0 --- /dev/null +++ b/mailnews/mime/src/mimemapl.cpp @@ -0,0 +1,189 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "mimemapl.h" +#include "prmem.h" +#include "plstr.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include "nsMimeTypes.h" + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartAppleDouble, MimeMultipartAppleDoubleClass, + mimeMultipartAppleDoubleClass, &MIME_SUPERCLASS); + +static int MimeMultipartAppleDouble_parse_begin (MimeObject *); +static bool MimeMultipartAppleDouble_output_child_p(MimeObject *, + MimeObject *); + +static int +MimeMultipartAppleDoubleClassInitialize(MimeMultipartAppleDoubleClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + + NS_ASSERTION(!oclass->class_initialized, "mime class not initialized"); + oclass->parse_begin = MimeMultipartAppleDouble_parse_begin; + mclass->output_child_p = MimeMultipartAppleDouble_output_child_p; + return 0; +} + +static int +MimeMultipartAppleDouble_parse_begin (MimeObject *obj) +{ + /* #### This method is identical to MimeExternalObject_parse_begin + which kinda s#$%s... + */ + int status; + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + /* If we're writing this object, and we're doing it in raw form, then + now is the time to inform the backend what the type of this data is. + */ + if (obj->output_p && + obj->options && + !obj->options->write_html_p && + !obj->options->state->first_data_written_p) + { + status = MimeObject_output_init(obj, 0); + if (status < 0) return status; + NS_ASSERTION(obj->options->state->first_data_written_p, "first data not written"); + } + +#ifdef XP_MACOSX + if (obj->options && obj->options->state) + { +// obj->options->state->separator_suppressed_p = true; + goto done; + } + /* + * It would be nice to not showing the resource fork links + * if we are displaying inline. But, there is no way we could + * know ahead of time that we could display the data fork and + * the data fork is always hidden on MAC platform. + */ +#endif + /* If we're writing this object as HTML, then emit a link for the + multipart/appledouble part (both links) that looks just like the + links that MimeExternalObject emits for external leaf parts. + */ + if (obj->options && + obj->output_p && + obj->options->write_html_p && + obj->options->output_fn) + { + char *id = 0; + char *id_url = 0; + char *id_imap = 0; + + id = mime_part_address (obj); + if (! id) return MIME_OUT_OF_MEMORY; + if (obj->options->missing_parts) + id_imap = mime_imap_part_address (obj); + + if (obj->options && obj->options->url) + { + const char *url = obj->options->url; + if (id_imap && id) + { + /* if this is an IMAP part. */ + id_url = mime_set_url_imap_part(url, id_imap, id); + } + else + { + /* This is just a normal MIME part as usual. */ + id_url = mime_set_url_part(url, id, true); + } + if (!id_url) + { + PR_Free(id); + return MIME_OUT_OF_MEMORY; + } + } + +/********************** + if (!strcmp (id, "0")) + { + PR_Free(id); + id = MimeGetStringByID(MIME_MSG_ATTACHMENT); + } + else + { + const char *p = "Part "; + char *s = (char *)PR_MALLOC(strlen(p) + strlen(id) + 1); + if (!s) + { + PR_Free(id); + PR_Free(id_url); + return MIME_OUT_OF_MEMORY; + } + PL_strcpy(s, p); + PL_strcat(s, id); + PR_Free(id); + id = s; + } + + if (all_headers_p && + // Don't bother showing all headers on this part if it's the only + // part in the message: in that case, we've already shown these + // headers. + obj->options->state && + obj->options->state->root == obj->parent) + all_headers_p = false; + + newopt.fancy_headers_p = true; + newopt.headers = (all_headers_p ? MimeHeadersAll : MimeHeadersSome); + +// +RICHIE SHERRY +GOTTA STILL DO THIS FOR QUOTING! + status = MimeHeaders_write_attachment_box (obj->headers, &newopt, + obj->content_type, + obj->encoding, + id_name? id_name : id, id_url, 0 +// +*********************************************************************************/ + +// FAIL: + PR_FREEIF(id); + PR_FREEIF(id_url); + PR_FREEIF(id_imap); + if (status < 0) return status; + } + +#ifdef XP_MACOSX +done: +#endif + + return 0; +} + +static bool +MimeMultipartAppleDouble_output_child_p(MimeObject *obj, MimeObject *child) +{ + MimeContainer *cont = (MimeContainer *) obj; + + /* If this is the first child, and it's an application/applefile, then + don't emit a link for it. (There *should* be only two children, and + the first one should always be an application/applefile.) + */ + + if (cont->nchildren >= 1 && cont->children[0] == child && child->content_type && + !PL_strcasecmp(child->content_type, APPLICATION_APPLEFILE)) + { +#ifdef XP_MACOSX + if (obj->output_p && obj->options && obj->options->write_html_p) //output HTML + return false; +#else + /* if we are not on a Macintosh, don't emitte the resources fork at all. */ + return false; +#endif + } + + return true; +} diff --git a/mailnews/mime/src/mimemapl.h b/mailnews/mime/src/mimemapl.h new file mode 100644 index 000000000..2ba48afdc --- /dev/null +++ b/mailnews/mime/src/mimemapl.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMAPL_H_ +#define _MIMEMAPL_H_ + +#include "mimemult.h" + +/* The MimeMultipartAppleDouble class implements the multipart/appledouble + MIME container, which provides a method of encapsulating and reconstructing + a two-forked Macintosh file. + */ + +typedef struct MimeMultipartAppleDoubleClass MimeMultipartAppleDoubleClass; +typedef struct MimeMultipartAppleDouble MimeMultipartAppleDouble; + +struct MimeMultipartAppleDoubleClass { + MimeMultipartClass multipart; +}; + +extern MimeMultipartAppleDoubleClass mimeMultipartAppleDoubleClass; + +struct MimeMultipartAppleDouble { + MimeMultipart multipart; +}; + +#define MimeMultipartAppleDoubleClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMAPL_H_ */ diff --git a/mailnews/mime/src/mimemcms.cpp b/mailnews/mime/src/mimemcms.cpp new file mode 100644 index 000000000..d67f69899 --- /dev/null +++ b/mailnews/mime/src/mimemcms.cpp @@ -0,0 +1,470 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsICMSMessage.h" +#include "nsICMSMessageErrors.h" +#include "nsICMSDecoder.h" +#include "nsICryptoHash.h" +#include "mimemcms.h" +#include "mimecryp.h" +#include "nsMimeTypes.h" +#include "nspr.h" +#include "nsMimeStringResources.h" +#include "mimemsg.h" +#include "mimemoz2.h" +#include "nsIURI.h" +#include "nsIMsgWindow.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMimeMiscStatus.h" +#include "nsIMsgSMIMEHeaderSink.h" +#include "nsCOMPtr.h" +#include "nsIX509Cert.h" +#include "plstr.h" +#include "nsComponentManagerUtils.h" + +#define MIME_SUPERCLASS mimeMultipartSignedClass +MimeDefClass(MimeMultipartSignedCMS, MimeMultipartSignedCMSClass, + mimeMultipartSignedCMSClass, &MIME_SUPERCLASS); + +static int MimeMultipartSignedCMS_initialize (MimeObject *); + +static void *MimeMultCMS_init (MimeObject *); +static int MimeMultCMS_data_hash (const char *, int32_t, void *); +static int MimeMultCMS_sig_hash (const char *, int32_t, void *); +static int MimeMultCMS_data_eof (void *, bool); +static int MimeMultCMS_sig_eof (void *, bool); +static int MimeMultCMS_sig_init (void *, MimeObject *, MimeHeaders *); +static char * MimeMultCMS_generate (void *); +static void MimeMultCMS_free (void *); + +extern int SEC_ERROR_CERT_ADDR_MISMATCH; + +static int +MimeMultipartSignedCMSClassInitialize(MimeMultipartSignedCMSClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartSignedClass *sclass = (MimeMultipartSignedClass *) clazz; + + oclass->initialize = MimeMultipartSignedCMS_initialize; + + sclass->crypto_init = MimeMultCMS_init; + sclass->crypto_data_hash = MimeMultCMS_data_hash; + sclass->crypto_data_eof = MimeMultCMS_data_eof; + sclass->crypto_signature_init = MimeMultCMS_sig_init; + sclass->crypto_signature_hash = MimeMultCMS_sig_hash; + sclass->crypto_signature_eof = MimeMultCMS_sig_eof; + sclass->crypto_generate_html = MimeMultCMS_generate; + sclass->crypto_free = MimeMultCMS_free; + + PR_ASSERT(!oclass->class_initialized); + return 0; +} + +static int +MimeMultipartSignedCMS_initialize (MimeObject *object) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + + +typedef struct MimeMultCMSdata +{ + int16_t hash_type; + nsCOMPtr<nsICryptoHash> data_hash_context; + nsCOMPtr<nsICMSDecoder> sig_decoder_context; + nsCOMPtr<nsICMSMessage> content_info; + char *sender_addr; + bool decoding_failed; + unsigned char* item_data; + uint32_t item_len; + MimeObject *self; + bool parent_is_encrypted_p; + bool parent_holds_stamp_p; + nsCOMPtr<nsIMsgSMIMEHeaderSink> smimeHeaderSink; + + MimeMultCMSdata() + :hash_type(0), + sender_addr(nullptr), + decoding_failed(false), + item_data(nullptr), + self(nullptr), + parent_is_encrypted_p(false), + parent_holds_stamp_p(false) + { + } + + ~MimeMultCMSdata() + { + PR_FREEIF(sender_addr); + + // Do a graceful shutdown of the nsICMSDecoder and release the nsICMSMessage // + if (sig_decoder_context) + { + nsCOMPtr<nsICMSMessage> cinfo; + sig_decoder_context->Finish(getter_AddRefs(cinfo)); + } + + delete [] item_data; + } +} MimeMultCMSdata; + +/* #### MimeEncryptedCMS and MimeMultipartSignedCMS have a sleazy, + incestuous, dysfunctional relationship. */ +extern bool MimeEncryptedCMS_encrypted_p (MimeObject *obj); +extern void MimeCMSGetFromSender(MimeObject *obj, + nsCString &from_addr, + nsCString &from_name, + nsCString &sender_addr, + nsCString &sender_name); +extern bool MimeCMSHeadersAndCertsMatch(MimeObject *obj, + nsICMSMessage *, + bool *signing_cert_without_email_address); +extern void MimeCMSRequestAsyncSignatureVerification(nsICMSMessage *aCMSMsg, + const char *aFromAddr, const char *aFromName, + const char *aSenderAddr, const char *aSenderName, + nsIMsgSMIMEHeaderSink *aHeaderSink, int32_t aMimeNestingLevel, + unsigned char* item_data, uint32_t item_len); +extern char *MimeCMS_MakeSAURL(MimeObject *obj); +extern char *IMAP_CreateReloadAllPartsUrl(const char *url); +extern int MIMEGetRelativeCryptoNestLevel(MimeObject *obj); + +static void * +MimeMultCMS_init (MimeObject *obj) +{ + MimeHeaders *hdrs = obj->headers; + MimeMultCMSdata *data = 0; + char *ct, *micalg; + int16_t hash_type; + nsresult rv; + + ct = MimeHeaders_get (hdrs, HEADER_CONTENT_TYPE, false, false); + if (!ct) return 0; /* #### bogus message? out of memory? */ + micalg = MimeHeaders_get_parameter (ct, PARAM_MICALG, NULL, NULL); + PR_Free(ct); + ct = 0; + if (!micalg) return 0; /* #### bogus message? out of memory? */ + + if (!PL_strcasecmp(micalg, PARAM_MICALG_MD5) || + !PL_strcasecmp(micalg, PARAM_MICALG_MD5_2)) + hash_type = nsICryptoHash::MD5; + else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA1) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_3) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_4) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA1_5)) + hash_type = nsICryptoHash::SHA1; + else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA256) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA256_3)) + hash_type = nsICryptoHash::SHA256; + else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA384) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA384_3)) + hash_type = nsICryptoHash::SHA384; + else if (!PL_strcasecmp(micalg, PARAM_MICALG_SHA512) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_2) || + !PL_strcasecmp(micalg, PARAM_MICALG_SHA512_3)) + hash_type = nsICryptoHash::SHA512; + else if (!PL_strcasecmp(micalg, PARAM_MICALG_MD2)) + hash_type = nsICryptoHash::MD2; + else + hash_type = -1; + + PR_Free(micalg); + micalg = 0; + + if (hash_type == -1) return 0; /* #### bogus message? */ + + data = new MimeMultCMSdata; + if (!data) + return 0; + + data->self = obj; + data->hash_type = hash_type; + + data->data_hash_context = do_CreateInstance("@mozilla.org/security/hash;1", &rv); + if (NS_FAILED(rv)) + { + delete data; + return 0; + } + + rv = data->data_hash_context->Init(data->hash_type); + if (NS_FAILED(rv)) + { + delete data; + return 0; + } + + PR_SetError(0,0); + + data->parent_holds_stamp_p = + (obj->parent && mime_crypto_stamped_p(obj->parent)); + + data->parent_is_encrypted_p = + (obj->parent && MimeEncryptedCMS_encrypted_p (obj->parent)); + + /* If the parent of this object is a crypto-blob, then it's the grandparent + who would have written out the headers and prepared for a stamp... + (This s##t s$%#s.) + */ + if (data->parent_is_encrypted_p && + !data->parent_holds_stamp_p && + obj->parent && obj->parent->parent) + data->parent_holds_stamp_p = + mime_crypto_stamped_p (obj->parent->parent); + + mime_stream_data *msd = (mime_stream_data *) (data->self->options->stream_closure); + if (msd) + { + nsIChannel *channel = msd->channel; // note the lack of ref counting... + if (channel) + { + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIMsgWindow> msgWindow; + nsCOMPtr<nsIMsgHeaderSink> headerSink; + nsCOMPtr<nsIMsgMailNewsUrl> msgurl; + nsCOMPtr<nsISupports> securityInfo; + channel->GetURI(getter_AddRefs(uri)); + if (uri) + { + nsAutoCString urlSpec; + rv = uri->GetSpec(urlSpec); + + // We only want to update the UI if the current mime transaction + // is intended for display. + // If the current transaction is intended for background processing, + // we can learn that by looking at the additional header=filter + // string contained in the URI. + // + // If we find something, we do not set smimeHeaderSink, + // which will prevent us from giving UI feedback. + // + // If we do not find header=filter, we assume the result of the + // processing will be shown in the UI. + + if (!strstr(urlSpec.get(), "?header=filter") && + !strstr(urlSpec.get(), "&header=filter")&& + !strstr(urlSpec.get(), "?header=attach") && + !strstr(urlSpec.get(), "&header=attach")) + { + msgurl = do_QueryInterface(uri); + if (msgurl) + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + msgWindow->GetMsgHeaderSink(getter_AddRefs(headerSink)); + if (headerSink) + headerSink->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (securityInfo) + data->smimeHeaderSink = do_QueryInterface(securityInfo); + } + } + } // if channel + } // if msd + + return data; +} + +static int +MimeMultCMS_data_hash (const char *buf, int32_t size, void *crypto_closure) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + if (!data || !data->data_hash_context) { + return -1; + } + + PR_SetError(0, 0); + nsresult rv = data->data_hash_context->Update((unsigned char *) buf, size); + data->decoding_failed = NS_FAILED(rv); + + return 0; +} + +static int +MimeMultCMS_data_eof (void *crypto_closure, bool abort_p) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + if (!data || !data->data_hash_context) { + return -1; + } + + nsAutoCString hashString; + data->data_hash_context->Finish(false, hashString); + PR_SetError(0, 0); + + data->item_len = hashString.Length(); + data->item_data = new unsigned char[data->item_len]; + if (!data->item_data) return MIME_OUT_OF_MEMORY; + + memcpy(data->item_data, hashString.get(), data->item_len); + + // Release our reference to nsICryptoHash // + data->data_hash_context = nullptr; + + /* At this point, data->item.data contains a digest for the first part. + When we process the signature, the security library will compare this + digest to what's in the signature object. */ + + return 0; +} + + +static int +MimeMultCMS_sig_init (void *crypto_closure, + MimeObject *multipart_object, + MimeHeaders *signature_hdrs) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + char *ct; + int status = 0; + nsresult rv; + + if (!signature_hdrs) { + return -1; + } + + ct = MimeHeaders_get (signature_hdrs, HEADER_CONTENT_TYPE, true, false); + + /* Verify that the signature object is of the right type. */ + if (!ct || /* is not a signature type */ + (PL_strcasecmp(ct, APPLICATION_XPKCS7_SIGNATURE) != 0 + && PL_strcasecmp(ct, APPLICATION_PKCS7_SIGNATURE) != 0)) { + status = -1; /* #### error msg about bogus message */ + } + PR_FREEIF(ct); + if (status < 0) return status; + + data->sig_decoder_context = do_CreateInstance(NS_CMSDECODER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return 0; + + rv = data->sig_decoder_context->Start(nullptr, nullptr); + if (NS_FAILED(rv)) { + status = PR_GetError(); + if (status >= 0) status = -1; + } + return status; +} + + +static int +MimeMultCMS_sig_hash (const char *buf, int32_t size, void *crypto_closure) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + nsresult rv; + + if (!data || !data->sig_decoder_context) { + return -1; + } + + rv = data->sig_decoder_context->Update(buf, size); + data->decoding_failed = NS_FAILED(rv); + + return 0; +} + +static int +MimeMultCMS_sig_eof (void *crypto_closure, bool abort_p) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + + if (!data) { + return -1; + } + + /* Hand an EOF to the crypto library. + + We save away the value returned and will use it later to emit a + blurb about whether the signature validation was cool. + */ + + if (data->sig_decoder_context) { + data->sig_decoder_context->Finish(getter_AddRefs(data->content_info)); + + // Release our reference to nsICMSDecoder // + data->sig_decoder_context = nullptr; + } + + return 0; +} + +static void +MimeMultCMS_free (void *crypto_closure) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + if (!data) return; + + delete data; +} + +static char * +MimeMultCMS_generate (void *crypto_closure) +{ + MimeMultCMSdata *data = (MimeMultCMSdata *) crypto_closure; + if (!data) return 0; + nsCOMPtr<nsIX509Cert> signerCert; + + int aRelativeNestLevel = MIMEGetRelativeCryptoNestLevel(data->self); + + if (aRelativeNestLevel < 0) + return nullptr; + + int32_t maxNestLevel = 0; + if (data->smimeHeaderSink && aRelativeNestLevel >= 0) + { + data->smimeHeaderSink->MaxWantedNesting(&maxNestLevel); + + if (aRelativeNestLevel > maxNestLevel) + return nullptr; + } + + if (data->self->options->missing_parts) + { + // We were not given all parts of the message. + // We are therefore unable to verify correctness of the signature. + + if (data->smimeHeaderSink) + data->smimeHeaderSink->SignedStatus(aRelativeNestLevel, + nsICMSMessageErrors::VERIFY_NOT_YET_ATTEMPTED, + nullptr); + return nullptr; + } + + if (!data->content_info) + { + /* No content_info at all -- since we're inside a multipart/signed, + that means that we've either gotten a message that was truncated + before the signature part, or we ran out of memory, or something + awful has happened. + */ + return nullptr; + } + + nsCString from_addr; + nsCString from_name; + nsCString sender_addr; + nsCString sender_name; + + MimeCMSGetFromSender(data->self, + from_addr, from_name, + sender_addr, sender_name); + + MimeCMSRequestAsyncSignatureVerification(data->content_info, + from_addr.get(), from_name.get(), + sender_addr.get(), sender_name.get(), + data->smimeHeaderSink, aRelativeNestLevel, + data->item_data, data->item_len); + + if (data->content_info) + { +#if 0 // XXX Fix this. What do we do here? // + if (SEC_CMSContainsCertsOrCrls(data->content_info)) + { + /* #### call libsec telling it to import the certs */ + } +#endif + } + + return nullptr; +} diff --git a/mailnews/mime/src/mimemcms.h b/mailnews/mime/src/mimemcms.h new file mode 100644 index 000000000..54f41da1b --- /dev/null +++ b/mailnews/mime/src/mimemcms.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMPKC_H_ +#define _MIMEMPKC_H_ + +#include "mimemsig.h" + +class nsICMSMessage; + +/* The MimeMultipartSignedCMS class implements a multipart/signed MIME + container with protocol=application/x-CMS-signature, which passes the + signed object through CMS code to verify the signature. See mimemsig.h + for details of the general mechanism on which this is built. + */ + +typedef struct MimeMultipartSignedCMSClass MimeMultipartSignedCMSClass; +typedef struct MimeMultipartSignedCMS MimeMultipartSignedCMS; + +struct MimeMultipartSignedCMSClass { + MimeMultipartSignedClass msigned; +}; + +extern MimeMultipartSignedCMSClass mimeMultipartSignedCMSClass; + +struct MimeMultipartSignedCMS { + MimeMultipartSigned msigned; +}; + +#define MimeMultipartSignedCMSClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartSignedClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMPKC_H_ */ diff --git a/mailnews/mime/src/mimemdig.cpp b/mailnews/mime/src/mimemdig.cpp new file mode 100644 index 000000000..6b318fb2b --- /dev/null +++ b/mailnews/mime/src/mimemdig.cpp @@ -0,0 +1,24 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimemdig.h" +#include "prlog.h" +#include "nsMimeTypes.h" + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartDigest, MimeMultipartDigestClass, + mimeMultipartDigestClass, &MIME_SUPERCLASS); + +static int +MimeMultipartDigestClassInitialize(MimeMultipartDigestClass *clazz) +{ +#ifdef DEBUG + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); +#endif + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + mclass->default_part_type = MESSAGE_RFC822; + return 0; +} diff --git a/mailnews/mime/src/mimemdig.h b/mailnews/mime/src/mimemdig.h new file mode 100644 index 000000000..6844c286f --- /dev/null +++ b/mailnews/mime/src/mimemdig.h @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMDIG_H_ +#define _MIMEMDIG_H_ + +#include "mimemult.h" + +/* The MimeMultipartDigest class implements the multipart/digest MIME + container, which is just like multipart/mixed, except that the default + type (for parts with no type explicitly specified) is message/rfc822 + instead of text/plain. + */ + +typedef struct MimeMultipartDigestClass MimeMultipartDigestClass; +typedef struct MimeMultipartDigest MimeMultipartDigest; + +struct MimeMultipartDigestClass { + MimeMultipartClass multipart; +}; + +extern MimeMultipartDigestClass mimeMultipartDigestClass; + +struct MimeMultipartDigest { + MimeMultipart multipart; +}; + +#define MimeMultipartDigestClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMDIG_H_ */ diff --git a/mailnews/mime/src/mimemmix.cpp b/mailnews/mime/src/mimemmix.cpp new file mode 100644 index 000000000..f2d7e31cd --- /dev/null +++ b/mailnews/mime/src/mimemmix.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimemmix.h" +#include "prlog.h" + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartMixed, MimeMultipartMixedClass, + mimeMultipartMixedClass, &MIME_SUPERCLASS); + +static int +MimeMultipartMixedClassInitialize(MimeMultipartMixedClass *clazz) +{ +#ifdef DEBUG + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); +#endif + return 0; +} diff --git a/mailnews/mime/src/mimemmix.h b/mailnews/mime/src/mimemmix.h new file mode 100644 index 000000000..9f401fa31 --- /dev/null +++ b/mailnews/mime/src/mimemmix.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMMIX_H_ +#define _MIMEMMIX_H_ + +#include "mimemult.h" + +/* The MimeMultipartMixed class implements the multipart/mixed MIME container, + and is also used for any and all otherwise-unrecognised subparts of + multipart/. + */ + +typedef struct MimeMultipartMixedClass MimeMultipartMixedClass; +typedef struct MimeMultipartMixed MimeMultipartMixed; + +struct MimeMultipartMixedClass { + MimeMultipartClass multipart; +}; + +extern MimeMultipartMixedClass mimeMultipartMixedClass; + +struct MimeMultipartMixed { + MimeMultipart multipart; +}; + +#define MimeMultipartMixedClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMMIX_H_ */ diff --git a/mailnews/mime/src/mimemoz2.cpp b/mailnews/mime/src/mimemoz2.cpp new file mode 100644 index 000000000..09ac52545 --- /dev/null +++ b/mailnews/mime/src/mimemoz2.cpp @@ -0,0 +1,2211 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#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 "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 "nsMimeTypes.h" +#include "nsIIOService.h" +#include "nsIURI.h" +#include "nsNetCID.h" +#include "nsIMsgWindow.h" +#include "nsIMimeMiscStatus.h" +#include "nsMsgUtils.h" +#include "nsIChannel.h" +#include "nsITransport.h" +#include "mimeebod.h" +#include "mimeeobj.h" +// <for functions="HTML2Plaintext,HTMLSantinize"> +#include "nsXPCOM.h" +#include "nsLayoutCID.h" +#include "nsIComponentManager.h" +#include "nsIParserUtils.h" +// </for> +#include "mozilla/Services.h" +#include "mozilla/Unused.h" + +void ValidateRealName(nsMsgAttachmentData *aAttach, MimeHeaders *aHdrs); + +static MimeHeadersState MIME_HeaderType; +static bool MIME_WrapLongLines; +static bool MIME_VariableWidthPlaintext; + +mime_stream_data::mime_stream_data() : url_name(nullptr), orig_url_name(nullptr), + pluginObj2(nullptr), istream(nullptr), obj(nullptr), options(nullptr), + headers(nullptr), output_emitter(nullptr), firstCheck(false) +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Attachment handling routines +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +MimeObject *mime_get_main_object(MimeObject* obj); + +nsresult MimeGetSize(MimeObject *child, int32_t *size) { + bool isLeaf = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeLeafClass); + bool isContainer = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeContainerClass); + bool isMsg = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeMessageClass); + + if (isLeaf) { + *size += ((MimeLeaf *)child)->sizeSoFar; + } else if (isMsg) { + *size += ((MimeMessage *)child)->sizeSoFar; + } else if (isContainer) { + int i; + MimeContainer *cont = (MimeContainer *)child; + for (i = 0; i < cont->nchildren; ++i) { + MimeGetSize(cont->children[i], size); + } + } + return NS_OK; +} + +nsresult +ProcessBodyAsAttachment(MimeObject *obj, nsMsgAttachmentData **data) +{ + nsMsgAttachmentData *tmp; + char *disp = nullptr; + char *charset = nullptr; + + // 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; + + *data = new nsMsgAttachmentData[2]; + if (!*data) + return NS_ERROR_OUT_OF_MEMORY; + + tmp = *data; + tmp->m_realType = child->content_type; + tmp->m_realEncoding = child->encoding; + disp = MimeHeaders_get(child->headers, HEADER_CONTENT_DISPOSITION, false, false); + tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "name", &charset, NULL)); + if (!tmp->m_realName.IsEmpty()) + { + char *fname = NULL; + fname = mime_decode_filename(tmp->m_realName.get(), charset, obj->options); + free(charset); + if (fname) + tmp->m_realName.Adopt(fname); + } + else + { + tmp->m_realName.Adopt(MimeHeaders_get_name(child->headers, obj->options)); + + if (tmp->m_realName.IsEmpty() && + tmp->m_realType.LowerCaseEqualsLiteral(MESSAGE_RFC822)) + { + // We haven't actually parsed the message "attachment", so just give it a + // generic name. + tmp->m_realName = "AttachedMessage.eml"; + } + } + + tmp->m_hasFilename = !tmp->m_realName.IsEmpty(); + + if (tmp->m_realName.IsEmpty() && + StringBeginsWith(tmp->m_realType, NS_LITERAL_CSTRING("text"), + nsCaseInsensitiveCStringComparator())) + ValidateRealName(tmp, child->headers); + + tmp->m_displayableInline = obj->clazz->displayable_inline_p(obj->clazz, + obj->headers); + + char *tmpURL = nullptr; + char *id = nullptr; + char *id_imap = nullptr; + + id = mime_part_address (obj); + if (obj->options->missing_parts) + id_imap = mime_imap_part_address (obj); + + tmp->m_isDownloaded = !id_imap; + + if (! id) + { + delete [] *data; + *data = nullptr; + 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(getter_AddRefs(tmp->m_url), tmpURL, nullptr); + } + else + { + // This is just a normal MIME part as usual. + tmpURL = mime_set_url_part(url, id, true); + rv = nsMimeNewURI(getter_AddRefs(tmp->m_url), tmpURL, nullptr); + } + + if (!tmp->m_url || NS_FAILED(rv)) + { + delete [] *data; + *data = nullptr; + PR_FREEIF(id); + PR_FREEIF(id_imap); + return NS_ERROR_OUT_OF_MEMORY; + } + } + PR_FREEIF(id); + PR_FREEIF(id_imap); + PR_FREEIF(tmpURL); + tmp->m_description.Adopt(MimeHeaders_get(child->headers, HEADER_CONTENT_DESCRIPTION, false, false)); + + tmp->m_size = 0; + MimeGetSize(child, &tmp->m_size); + + return NS_OK; +} + +int32_t +CountTotalMimeAttachments(MimeContainer *aObj) +{ + int32_t i; + int32_t rc = 0; + + if ( (!aObj) || (!aObj->children) || (aObj->nchildren <= 0) ) + return 0; + + if (!mime_typep(((MimeObject *) aObj), (MimeObjectClass*) &mimeContainerClass)) + 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->m_realName.IsEmpty()) + return; + + // Internal MIME structures need not be named! + if (aAttach->m_realType.IsEmpty() || + StringBeginsWith(aAttach->m_realType, NS_LITERAL_CSTRING("multipart"), + nsCaseInsensitiveCStringComparator())) + return; + + // + // Now validate any other name we have for the attachment! + // + if (aAttach->m_realName.IsEmpty()) + { + aAttach->m_realName = "attachment"; + nsresult rv = NS_OK; + nsAutoCString contentType (aAttach->m_realType); + int32_t pos = contentType.FindChar(';'); + if (pos > 0) + contentType.SetLength(pos); + + nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsAutoCString fileExtension; + rv = mimeFinder->GetPrimaryExtension(contentType, EmptyCString(), fileExtension); + + if (NS_SUCCEEDED(rv) && !fileExtension.IsEmpty()) + { + aAttach->m_realName.Append('.'); + aAttach->m_realName.Append(fileExtension); + } + } + } +} + +static int32_t attIndex = 0; + +nsresult +GenerateAttachmentData(MimeObject *object, const char *aMessageURL, MimeDisplayOptions *options, + bool isAnAppleDoublePart, int32_t attSize, nsMsgAttachmentData *aAttachData) +{ + nsCString imappart; + nsCString part; + bool isExternalAttachment = 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 = nullptr; + if (!imappart.IsEmpty()) + { + urlSpec = mime_set_url_imap_part(aMessageURL, imappart.get(), part.get()); + } + else + { + char *no_part_url = nullptr; + 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(), 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 ? true : false; + if (!urlSpec) + urlSpec = mime_set_url_part(aMessageURL, part.get(), 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; + + nsCString urlString(urlSpec); + + nsMsgAttachmentData *tmp = &(aAttachData[attIndex++]); + + tmp->m_realType = object->content_type; + tmp->m_realEncoding = object->encoding; + tmp->m_isExternalAttachment = isExternalAttachment; + tmp->m_isExternalLinkAttachment = + (isExternalAttachment && + StringBeginsWith(urlString, NS_LITERAL_CSTRING("http"), + nsCaseInsensitiveCStringComparator())); + tmp->m_size = attSize; + tmp->m_sizeExternalStr = "-1"; + tmp->m_disposition.Adopt(MimeHeaders_get(object->headers, HEADER_CONTENT_DISPOSITION, true, false)); + tmp->m_displayableInline = object->clazz->displayable_inline_p(object->clazz, object->headers); + + char *part_addr = mime_imap_part_address(object); + tmp->m_isDownloaded = !part_addr; + PR_FREEIF(part_addr); + + int32_t i; + char *charset = nullptr; + char *disp = MimeHeaders_get(object->headers, HEADER_CONTENT_DISPOSITION, false, false); + if (disp) + { + tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "filename", &charset, nullptr)); + if (isAnAppleDoublePart) + for (i = 0; i < 2 && tmp->m_realName.IsEmpty(); i ++) + { + PR_FREEIF(disp); + free(charset); + disp = MimeHeaders_get(((MimeContainer *)object)->children[i]->headers, HEADER_CONTENT_DISPOSITION, false, false); + tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "filename", &charset, nullptr)); + } + + if (!tmp->m_realName.IsEmpty()) + { + // 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 = nullptr; + fname = mime_decode_filename(tmp->m_realName.get(), charset, options); + free(charset); + + if (fname) + tmp->m_realName.Adopt(fname); + } + + PR_FREEIF(disp); + } + + disp = MimeHeaders_get(object->headers, HEADER_CONTENT_TYPE, false, false); + if (disp) + { + tmp->m_xMacType.Adopt(MimeHeaders_get_parameter(disp, PARAM_X_MAC_TYPE, nullptr, nullptr)); + tmp->m_xMacCreator.Adopt(MimeHeaders_get_parameter(disp, PARAM_X_MAC_CREATOR, nullptr, nullptr)); + + if (tmp->m_realName.IsEmpty()) + { + tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "name", &charset, nullptr)); + 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->m_realName.IsEmpty(); i --) + { + PR_FREEIF(disp); + free(charset); + disp = MimeHeaders_get(((MimeContainer *)object)->children[i]->headers, HEADER_CONTENT_TYPE, false, false); + tmp->m_realName.Adopt(MimeHeaders_get_parameter(disp, "name", &charset, nullptr)); + tmp->m_realType.Adopt( + MimeHeaders_get(((MimeContainer *)object)->children[i]->headers, + HEADER_CONTENT_TYPE, true, false)); + } + + if (!tmp->m_realName.IsEmpty()) + { + // 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 = nullptr; + fname = mime_decode_filename(tmp->m_realName.get(), charset, options); + free(charset); + + if (fname) + tmp->m_realName.Adopt(fname); + } + } + + if (tmp->m_isExternalLinkAttachment) + { + // If an external link attachment part's Content-Type contains a + // |size| parm, store it in m_sizeExternalStr. Let the msgHeaderSink + // addAttachmentField() figure out if it's sane, and don't bother + // strtol'ing it to an int only to emit it as a string. + char* sizeStr = MimeHeaders_get_parameter(disp, "size", nullptr, nullptr); + if (sizeStr) + tmp->m_sizeExternalStr = sizeStr; + } + + PR_FREEIF(disp); + } + + tmp->m_description.Adopt(MimeHeaders_get(object->headers, HEADER_CONTENT_DESCRIPTION, + false, false)); + + // Now, do the right thing with the name! + if (tmp->m_realName.IsEmpty() && !(tmp->m_realType.LowerCaseEqualsLiteral(MESSAGE_RFC822))) + { + // Keep in mind that the name was provided by us and this is probably not a + // real attachment. + tmp->m_hasFilename = false; + /* If this attachment doesn't have a name, just give it one... */ + tmp->m_realName.Adopt(MimeGetStringByID(MIME_MSG_DEFAULT_ATTACHMENT_NAME)); + if (!tmp->m_realName.IsEmpty()) + { + char *newName = PR_smprintf(tmp->m_realName.get(), part.get()); + if (newName) + tmp->m_realName.Adopt(newName); + } + else + tmp->m_realName.Adopt(mime_part_address(object)); + } else { + tmp->m_hasFilename = true; + } + + if (!tmp->m_realName.IsEmpty() && !tmp->m_isExternalAttachment) + { + urlString.Append("&filename="); + nsAutoCString aResult; + if (NS_SUCCEEDED(MsgEscapeString(tmp->m_realName, + nsINetUtil::ESCAPE_XALPHAS, aResult))) + urlString.Append(aResult); + else + urlString.Append(tmp->m_realName); + if (tmp->m_realType.EqualsLiteral("message/rfc822") && + !StringEndsWith(urlString, NS_LITERAL_CSTRING(".eml"), nsCaseInsensitiveCStringComparator())) + urlString.Append(".eml"); + } else if (tmp->m_isExternalAttachment) { + // Allows the JS mime emitter to figure out the part information. + urlString.Append("?part="); + urlString.Append(part); + } else if (tmp->m_realType.LowerCaseEqualsLiteral(MESSAGE_RFC822)) { + // Special case...if this is a enclosed RFC822 message, give it a nice + // name. + if (object->headers->munged_subject) + { + nsCString subject; + subject.Assign(object->headers->munged_subject); + MimeHeaders_convert_header_value(options, subject, false); + tmp->m_realName.Assign(subject); + tmp->m_realName.Append(".eml"); + } + else + tmp->m_realName = "ForwardedMessage.eml"; + } + + nsresult rv = nsMimeNewURI(getter_AddRefs(tmp->m_url), urlString.get(), nullptr); + + PR_FREEIF(urlSpec); + + if (NS_FAILED(rv) || !tmp->m_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; + int32_t i; + MimeContainer *cobj = (MimeContainer *) anObject; + bool found_output = false; + + 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]; + char *ct = child->content_type; + + // We're going to ignore the output_p attribute because we want to output + // any part with a name to work around bug 674473 + + // Skip the first child that's being output if it's in fact a message body. + // Start by assuming that it is, until proven otherwise in the code below. + bool skip = true; + if (found_output) + // not first child being output + skip = false; + else if (! ct) + // no content type so can't be message body + skip = false; + else if (PL_strcasecmp (ct, TEXT_PLAIN) && + PL_strcasecmp (ct, TEXT_HTML) && + PL_strcasecmp (ct, TEXT_MDL)) + // not a type we recognize as a message body + skip = false; + // we're displaying all body parts + if (child->options->html_as_p == 4) + skip = false; + if (skip && child->headers) + { + char * disp = MimeHeaders_get (child->headers, + HEADER_CONTENT_DISPOSITION, + true, false); + if (MimeHeaders_get_name(child->headers, nullptr) && + (!disp || PL_strcasecmp(disp, "attachment"))) + // it has a filename and isn't being displayed inline + skip = false; + } + + found_output = true; + if (skip) + continue; + + // We should generate an attachment for leaf object only but... + bool isALeafObject = mime_subclass_p(child->clazz, (MimeObjectClass *) &mimeLeafClass); + + // ...we will generate an attachment for inline message too. + bool 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 + bool isAnAppleDoublePart = mime_typep(child, (MimeObjectClass *) &mimeMultipartAppleDoubleClass) && + ((MimeContainer *)child)->nchildren == 2; + + // The function below does not necessarily set the size to something (I + // don't think it will work for external objects, for instance, since they + // are neither containers nor leafs). + int32_t attSize = 0; + MimeGetSize(child, &attSize); + + if (isALeafObject || isAnInlineMessage || isAnAppleDoublePart) + { + rv = GenerateAttachmentData(child, aMessageURL, anObject->options, isAnAppleDoublePart, attSize, 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; + int32_t n; + bool isAnInlineMessage; + + if (!data) + return NS_ERROR_INVALID_ARG; + *data = nullptr; + + obj = mime_get_main_object(tobj); + if (!obj) + return NS_OK; + + if (!mime_subclass_p(obj->clazz, (MimeObjectClass*) &mimeContainerClass)) + return ProcessBodyAsAttachment(obj, data); + + isAnInlineMessage = mime_typep(obj, (MimeObjectClass *) &mimeMessageClass); + + cobj = (MimeContainer*) obj; + n = CountTotalMimeAttachments(cobj); + if (n <= 0) + // XXX n is a regular number here, not meaningful as an nsresult + return static_cast<nsresult>(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 = new nsMsgAttachmentData[n + 1]; + if (!*data) + return NS_ERROR_OUT_OF_MEMORY; + + attIndex = 0; + + // Now, build the list! + + nsresult rv; + + if (isAnInlineMessage) + { + int32_t size = 0; + MimeGetSize(obj, &size); + rv = GenerateAttachmentData(obj, aMessageURL, obj->options, false, size, + *data); + if (NS_FAILED(rv)) + { + delete [] *data; // release data in case of error return. + return rv; + } + + } + rv = BuildAttachmentList((MimeObject *) cobj, *data, aMessageURL); + if (NS_FAILED(rv)) + { + delete [] *data; // release data in case of error return. + } + return rv; +} + +extern "C" void +MimeFreeAttachmentList(nsMsgAttachmentData *data) +{ + delete [] data; +} + +extern "C" void +NotifyEmittersOfAttachmentList(MimeDisplayOptions *opt, + nsMsgAttachmentData *data) +{ + int32_t i = 0; + nsMsgAttachmentData *tmp = data; + + if (!tmp) + return; + + while (tmp->m_url) + { + // The code below implements the following logic: + // - Always display the attachment if the Content-Disposition is + // "attachment" or if it can't be displayed inline. + // - If there's no name at all, just skip it (we don't know what to do with + // it then). + // - If the attachment has a "provided name" (i.e. not something like "Part + // 1.2"), display it. + // - If we're asking for all body parts and NOT asking for metadata only, + // display it. + // - Otherwise, skip it. + if (!tmp->m_disposition.Equals("attachment") && tmp->m_displayableInline && + (tmp->m_realName.IsEmpty() || (!tmp->m_hasFilename && + (opt->html_as_p != 4 || opt->metadata_only)))) + { + ++i; + ++tmp; + continue; + } + + nsAutoCString spec; + if (tmp->m_url) { + if (tmp->m_isExternalLinkAttachment) + mozilla::Unused << tmp->m_url->GetAsciiSpec(spec); + else + mozilla::Unused << tmp->m_url->GetSpec(spec); + } + + nsAutoCString sizeStr; + if (tmp->m_isExternalLinkAttachment) + sizeStr.Append(tmp->m_sizeExternalStr); + else + sizeStr.AppendInt(tmp->m_size); + + nsAutoCString downloadedStr; + downloadedStr.AppendInt(tmp->m_isDownloaded); + + mimeEmitterStartAttachment(opt, tmp->m_realName.get(), tmp->m_realType.get(), + spec.get(), tmp->m_isExternalAttachment); + mimeEmitterAddAttachmentField(opt, HEADER_X_MOZILLA_PART_URL, spec.get()); + mimeEmitterAddAttachmentField(opt, HEADER_X_MOZILLA_PART_SIZE, sizeStr.get()); + mimeEmitterAddAttachmentField(opt, HEADER_X_MOZILLA_PART_DOWNLOADED, downloadedStr.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->m_description.get()); + mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_TYPE, tmp->m_realType.get()); + mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_ENCODING, tmp->m_realEncoding.get()); + } + + mimeEmitterEndAttachment(opt); + ++i; + ++tmp; + } + mimeEmitterEndAllAttachments(opt); +} + +// Utility to create a nsIURI object... +extern "C" nsresult +nsMimeNewURI(nsIURI** aInstancePtrResult, const char *aSpec, nsIURI *aBase) +{ + if (nullptr == aInstancePtrResult) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr<nsIIOService> pService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(pService, NS_ERROR_FACTORY_NOT_REGISTERED); + + return pService->NewURI(nsDependentCString(aSpec), nullptr, 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") ? + static_cast<const nsCString&>(NS_LITERAL_CSTRING("ISO-8859-1")) : + static_cast<const nsCString&>(nsDependentCString(aCharacterSet))); + } + } + } + } + } + + return rv; +} + +static void ResetMsgHeaderSinkProps(nsIURI *uri) +{ + nsCOMPtr<nsIMsgMailNewsUrl> msgurl(do_QueryInterface(uri)); + if (!msgurl) + return; + + nsCOMPtr<nsIMsgWindow> msgWindow; + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (!msgWindow) + return; + + nsCOMPtr<nsIMsgHeaderSink> msgHeaderSink; + msgWindow->GetMsgHeaderSink(getter_AddRefs(msgHeaderSink)); + if (!msgHeaderSink) + return; + + msgHeaderSink->ResetProperties(); +} + +static char * +mime_file_type (const char *filename, void *stream_closure) +{ + char *retType = nullptr; + char *ext = nullptr; + nsresult rv; + + ext = PL_strrchr(filename, '.'); + if (ext) + { + ext++; + nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv)); + if (mimeFinder) { + nsAutoCString type; + mimeFinder->GetTypeFromExtension(nsDependentCString(ext), type); + retType = ToNewCString(type); + } + } + + return retType; +} + +int ConvertUsingEncoderAndDecoder(const char *stringToUse, int32_t inLength, + nsIUnicodeEncoder *encoder, nsIUnicodeDecoder *decoder, + char **pConvertedString, int32_t *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 + char16_t *unichars; + int32_t unicharLength; + int32_t srcLen = inLength; + int32_t dstLength = 0; + char *dstPtr; + nsresult rv; + + // use this local buffer if possible + char16_t localbuf[klocalbufsize+1]; + if (inLength > klocalbufsize) { + rv = decoder->GetMaxLength(stringToUse, srcLen, &unicharLength); + // allocate temporary buffer to hold unicode string + unichars = new char16_t[unicharLength]; + } + else { + unichars = localbuf; + unicharLength = klocalbufsize+1; + } + if (unichars == nullptr) { + 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. + int32_t totalChars = 0, + inBufferIndex = 0, + outBufferIndex = 0; + int32_t dataLen = srcLen, + outLen = unicharLength; + + do { + int32_t 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++] = char16_t(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 == nullptr) { + rv = NS_ERROR_OUT_OF_MEMORY; + } + else { + int32_t buffLength = dstLength; + // convert from unicode + rv = encoder->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Replace, nullptr, '?'); + if (NS_SUCCEEDED(rv)) { + rv = encoder->Convert(unichars, &totalChars, dstPtr, &dstLength); + if (NS_SUCCEEDED(rv)) { + int32_t 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, int32_t input_length, + const char *input_charset, const char *output_charset, + char **output_ret, int32_t *output_size_ret, + void *stream_closure, nsIUnicodeDecoder *decoder, nsIUnicodeEncoder *encoder) +{ + int32_t res = -1; + char *convertedString = NULL; + int32_t 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, int32_t size, void *stream_closure) +{ + uint32_t written = 0; + mime_stream_data *msd = (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(Substring(buf, buf+size), + &written); + } + } + else + { + if (msd->output_emitter) + { + msd->output_emitter->Write(Substring(buf, buf+size), &written); + } + } + return written; +} + +extern "C" int +mime_display_stream_write (nsMIMESession *stream, + const char* buf, + int32_t size) +{ + mime_stream_data *msd = (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 = true; + } + } + } + } + + msd->firstCheck = false; + } + + return obj->clazz->parse_buffer((char *) buf, size, obj); +} + +extern "C" void +mime_display_stream_complete (nsMIMESession *stream) +{ + mime_stream_data *msd = (mime_stream_data *) ((nsMIMESession *)stream)->data_object; + MimeObject *obj = (msd ? msd->obj : 0); + if (obj) + { + int status; + bool abortNow = false; + + if ((obj->options) && (obj->options->headers == MimeHeadersOnly)) + abortNow = true; + + status = obj->clazz->parse_eof(obj, abortNow); + obj->clazz->parse_end(obj, (status < 0 ? true : 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); + + delete msd; +} + +extern "C" void +mime_display_stream_abort (nsMIMESession *stream, int status) +{ + mime_stream_data *msd = (mime_stream_data *) ((nsMIMESession *)stream)->data_object; + + MimeObject *obj = (msd ? msd->obj : 0); + if (obj) + { + if (!obj->closed_p) + obj->clazz->parse_eof(obj, true); + if (!obj->parsed_p) + obj->clazz->parse_end(obj, 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); + + delete 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) +{ + mime_stream_data *msd = (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, int32_t 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(); + + mime_stream_data *msd; + char *url; + nsMIMESession *istream; +}; + +mime_image_stream_data::mime_image_stream_data() +{ + url = nullptr; + istream = nullptr; + msd = nullptr; +} + +static void * +mime_image_begin(const char *image_url, const char *content_type, + void *stream_closure) +{ + mime_stream_data *msd = (mime_stream_data *) stream_closure; + class mime_image_stream_data *mid; + + mid = new mime_image_stream_data; + if (!mid) return nullptr; + + + mid->msd = msd; + + mid->url = (char *) strdup(image_url); + if (!mid->url) + { + PR_Free(mid); + return nullptr; + } + + 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; + + 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-resources/loading-image.png\" ALT=\"[Image]\"></CENTER><P>"); + + nsCOMPtr<nsIPrefBranch> prefBranch; + nsCOMPtr<nsIPrefService> prefSvc(do_GetService(NS_PREFSERVICE_CONTRACTID)); + bool resize = 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; + + uint32_t 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, int32_t size, void *image_closure) +{ + mime_image_stream_data *mid = + (mime_image_stream_data *) image_closure; + mime_stream_data *msd = mid->msd; + + if ( ( (!msd->output_emitter) ) && + ( (!msd->pluginObj2) ) ) + return -1; + + 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 = nullptr; + } + 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 nullptr; +} + +static +bool MimeObjectIsMessageBodyNoClimb(MimeObject *parent, + MimeObject *looking_for, + bool *stop) +{ + MimeContainer *container = (MimeContainer *)parent; + int32_t i; + char *disp; + + NS_ASSERTION(stop, "NULL stop to MimeObjectIsMessageBodyNoClimb"); + + for (i = 0; i < container->nchildren; i++) { + MimeObject *child = container->children[i]; + bool is_body = true; + + // The body can't be something we're not displaying. + if (! child->output_p) + is_body = false; + else if ((disp = MimeHeaders_get (child->headers, HEADER_CONTENT_DISPOSITION, + true, false))) { + PR_Free(disp); + is_body = false; + } + else if (PL_strcasecmp (child->content_type, TEXT_PLAIN) && + PL_strcasecmp (child->content_type, TEXT_HTML) && + PL_strcasecmp (child->content_type, TEXT_MDL) && + PL_strcasecmp (child->content_type, MESSAGE_NEWS) && + PL_strcasecmp (child->content_type, MESSAGE_RFC822)) + is_body = false; + + if (is_body || child == looking_for) { + *stop = true; + return child == looking_for; + } + + // The body could be down inside a multipart child, so search recursively. + if (mime_subclass_p(child->clazz, (MimeObjectClass*) &mimeContainerClass)) { + is_body = MimeObjectIsMessageBodyNoClimb(child, looking_for, stop); + if (is_body || *stop) + return is_body; + } + } + return false; +} + +/* Should this be static in mimemult.cpp? */ +bool MimeObjectIsMessageBody(MimeObject *looking_for) +{ + bool stop = false; + MimeObject *root = looking_for; + while (root->parent) + root = root->parent; + return MimeObjectIsMessageBodyNoClimb(root, looking_for, &stop); +} + +// +// New Stream Converter Interface +// + +// Get the connnection to prefs service manager +nsIPrefBranch * +GetPrefBranch(MimeDisplayOptions *opt) +{ + if (!opt) + return nullptr; + + return opt->m_prefBranch; +} + +// Get the text converter... +mozITXTToHTMLConv * +GetTextConverter(MimeDisplayOptions *opt) +{ + if (!opt) + return nullptr; + + return opt->conv; +} + +MimeDisplayOptions::MimeDisplayOptions() +{ + conv = nullptr; // For text conversion... + format_out = 0; // The format out type + url = nullptr; + + memset(&headers,0, sizeof(headers)); + fancy_headers_p = false; + + output_vcard_buttons_p = false; + + variable_width_plaintext_p = false; + wrap_long_lines_p = false; + rot13_p = false; + part_to_load = nullptr; + + no_output_p = false; + write_html_p = false; + + decrypt_p = false; + + whattodo = 0 ; + default_charset = nullptr; + override_charset = false; + force_user_charset = false; + stream_closure = nullptr; + + /* 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 = nullptr; + output_fn = nullptr; + + output_closure = nullptr; + + charset_conversion_fn = nullptr; + rfc1522_conversion_p = false; + + file_type_fn = nullptr; + + passwd_prompt_fn = nullptr; + + html_closure = nullptr; + + generate_header_html_fn = nullptr; + generate_post_header_html_fn = nullptr; + generate_footer_html_fn = nullptr; + generate_reference_url_fn = nullptr; + generate_mailto_url_fn = nullptr; + generate_news_url_fn = nullptr; + + image_begin = nullptr; + image_end = nullptr; + image_write_buffer = nullptr; + make_image_html = nullptr; + state = nullptr; + +#ifdef MIME_DRAFTS + decompose_file_p = false; + done_parsing_outer_headers = false; + is_multipart_msg = false; + decompose_init_count = 0; + + signed_p = false; + caller_need_root_headers = false; + decompose_headers_info_fn = nullptr; + decompose_file_init_fn = nullptr; + decompose_file_output_fn = nullptr; + decompose_file_close_fn = nullptr; +#endif /* MIME_DRAFTS */ + + attachment_icon_layer_id = 0; + + missing_parts = false; + show_attachment_inline_p = false; + quote_attachment_inline_p = false; + notify_nested_bodies = false; + write_pure_bodies = false; + metadata_only = 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, + uint32_t whattodo, + nsIChannel *aChannel) +{ + int status = 0; + MimeObject *obj; + mime_stream_data *msd; + nsMIMESession *stream = 0; + + if (!uri) + return nullptr; + + msd = new mime_stream_data; + if (!msd) + return NULL; + + // Assign the new mime emitter - will handle output operations + msd->output_emitter = newEmitter; + msd->firstCheck = true; + + // Store the URL string for this decode operation + nsAutoCString 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)) + { + delete 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) + { + delete 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)) + { + delete msd; + return nullptr; + } + + // Need the text converter... + rv = CallCreateInstance(MOZ_TXTTOHTMLCONV_CONTRACTID, &(msd->options->conv)); + if (NS_FAILED(rv)) + { + msd->options->m_prefBranch = nullptr; + delete msd; + return nullptr; + } + + // + // Set the defaults, based on the context, and the output-type. + // + MIME_HeaderType = MimeHeadersAll; + msd->options->write_html_p = 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 = true; + msd->options->output_vcard_buttons_p = true; + break; + + case nsMimeOutput::nsMimeMessageSaveAs: // Save As operations + case nsMimeOutput::nsMimeMessageQuoting: // all HTML quoted/printed output + case nsMimeOutput::nsMimeMessagePrintOutput: + msd->options->fancy_headers_p = 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 = 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 = true; + msd->options->write_html_p = false; + break; + } + + //////////////////////////////////////////////////////////// + // Now, get the libmime prefs... + //////////////////////////////////////////////////////////// + + MIME_WrapLongLines = true; + MIME_VariableWidthPlaintext = true; + msd->options->force_user_charset = 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)); + msd->options->m_prefBranch->GetIntPref("mailnews.display.html_as", &(msd->options->html_as_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); + delete 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 = 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 = false; + + obj = mime_new ((MimeObjectClass *)&mimeMessageClass, (MimeHeaders *) NULL, MESSAGE_RFC822); + if (!obj) + { + delete msd->options; + delete 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; + delete msd; + PR_Free(obj); + return 0; + } + + ResetMsgHeaderSinkProps(uri); + + 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; + delete 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 nullptr; + mime_stream_data *msd = (mime_stream_data *)opt->stream_closure; + return msd; +} + +bool +NoEmitterProcessing(nsMimeOutputType format_out) +{ + if ( (format_out == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (format_out == nsMimeOutput::nsMimeMessageEditorTemplate)) + return true; + else + return 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 int32_t 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, + bool 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(nsDependentCString(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, bool 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, MimeObject *obj) +{ + // 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; + + nsCString name; + if (msd->format_out == nsMimeOutput::nsMimeMessageSplitDisplay || + msd->format_out == nsMimeOutput::nsMimeMessageHeaderDisplay || + msd->format_out == nsMimeOutput::nsMimeMessageBodyDisplay || + msd->format_out == nsMimeOutput::nsMimeMessageSaveAs || + msd->format_out == nsMimeOutput::nsMimeMessagePrintOutput) { + if (obj->headers) { + nsMsgAttachmentData attachment; + attIndex = 0; + nsresult rv = GenerateAttachmentData(obj, msd->url_name, opt, false, + 0, &attachment); + + if (NS_SUCCEEDED(rv)) + name.Assign(attachment.m_realName); + } + } + + MimeHeaders_convert_header_value(opt, name, false); + return emitter->EndHeader(name); + } + + 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, bool rootMailHeader, bool 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_OUT_OF_MEMORY; + + PR_FREEIF(msd->url_name); + msd->url_name = tmpPtr; + return NS_OK; +} + +#define MIME_URL "chrome://messenger/locale/mime.properties" + +extern "C" +char * +MimeGetStringByID(int32_t stringID) +{ + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + + nsCOMPtr<nsIStringBundle> stringBundle; + stringBundleService->CreateBundle(MIME_URL, getter_AddRefs(stringBundle)); + if (stringBundle) + { + nsString v; + if (NS_SUCCEEDED(stringBundle->GetStringFromID(stringID, getter_Copies(v)))) + return ToNewUTF8String(v); + } + + return strdup("???"); +} + +extern "C" +char * +MimeGetStringByName(const char16_t *stringName) +{ + nsCOMPtr<nsIStringBundleService> stringBundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + + nsCOMPtr<nsIStringBundle> stringBundle; + stringBundleService->CreateBundle(MIME_URL, getter_AddRefs(stringBundle)); + if (stringBundle) + { + nsString v; + if (NS_SUCCEEDED(stringBundle->GetStringFromName(stringName, getter_Copies(v)))) + return ToNewUTF8String(v); + } + + return strdup("???"); +} + +void +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, false, 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 = nullptr; + 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 = true; + } + + PR_FREEIF(cSet); + } + } + } + PR_FREEIF(ct); + } + } +} + + //////////////////////////////////////////////////////////// + // Function to get up mail/news fontlang + //////////////////////////////////////////////////////////// + + +nsresult GetMailNewsFont(MimeObject *obj, bool styleFixed, int32_t *fontPixelSize, + int32_t *fontSizePercentage, nsCString& fontLang) +{ + nsresult rv = NS_OK; + + nsIPrefBranch *prefBranch = GetPrefBranch(obj->options); + if (prefBranch) { + MimeInlineText *text = (MimeInlineText *) obj; + nsAutoCString 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; + nsAutoCString 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 + int32_t originalSize; + rv = prefDefBranch->GetIntPref(prefStr.get(), &originalSize); + if (NS_FAILED(rv)) + return rv; + + // calculate percentage + *fontSizePercentage = originalSize ? + (int32_t)((float)*fontPixelSize / (float)originalSize * 100) : 0; + + } + + return NS_OK; +} + + + +/** + * This function synchronously converts an HTML document (as string) + * to plaintext (as string) using the Gecko converter. + * + * @param flags see nsIDocumentEncoder.h + */ +nsresult +HTML2Plaintext(const nsString& inString, nsString& outString, + uint32_t flags, uint32_t wrapCol) +{ + nsCOMPtr<nsIParserUtils> utils = + do_GetService(NS_PARSERUTILS_CONTRACTID); + return utils->ConvertToPlainText(inString, flags, wrapCol, outString); +} + + +/** + * This function synchronously sanitizes an HTML document (string->string) + * using the Gecko nsTreeSanitizer. + */ +nsresult +HTMLSanitize(const nsString& inString, nsString& outString) +{ + // If you want to add alternative sanitization, you can insert a conditional + // call to another sanitizer and an early return here. + + uint32_t flags = nsIParserUtils::SanitizerCidEmbedsOnly | + nsIParserUtils::SanitizerDropForms; + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + + bool dropPresentational = true; + bool dropMedia = false; + prefs->GetBoolPref( + "mailnews.display.html_sanitizer.drop_non_css_presentation", + &dropPresentational); + prefs->GetBoolPref( + "mailnews.display.html_sanitizer.drop_media", + &dropMedia); + if (dropPresentational) + flags |= nsIParserUtils::SanitizerDropNonCSSPresentation; + if (dropMedia) + flags |= nsIParserUtils::SanitizerDropMedia; + + nsCOMPtr<nsIParserUtils> utils = do_GetService(NS_PARSERUTILS_CONTRACTID); + return utils->Sanitize(inString, flags, outString); +} diff --git a/mailnews/mime/src/mimemoz2.h b/mailnews/mime/src/mimemoz2.h new file mode 100644 index 000000000..962a42ae0 --- /dev/null +++ b/mailnews/mime/src/mimemoz2.h @@ -0,0 +1,196 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMOZ_H_ +#define _MIMEMOZ_H_ + +#include "nsStreamConverter.h" +#include "nsIMimeEmitter.h" +#include "nsIURI.h" +#include "mozITXTToHTMLConv.h" +#include "nsIMsgSend.h" +#include "modmimee.h" +#include "nsMsgAttachmentData.h" + +// SHERRY - Need to get these out of here eventually + +#ifdef XP_UNIX +#undef Bool +#endif + + + +#include "mimei.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "nsIPrefBranch.h" + +typedef struct _nsMIMESession nsMIMESession; + +/* stream functions */ +typedef unsigned int +(*MKSessionWriteReadyFunc) (nsMIMESession *stream); + +#define MAX_WRITE_READY (((unsigned) (~0) << 1) >> 1) /* must be <= than MAXINT!!!!! */ + +typedef int +(*MKSessionWriteFunc) (nsMIMESession *stream, const char *str, int32_t len); + +typedef void +(*MKSessionCompleteFunc) (nsMIMESession *stream); + +typedef void +(*MKSessionAbortFunc) (nsMIMESession *stream, int status); + +/* streamclass function */ +struct _nsMIMESession { + + const char * name; /* Just for diagnostics */ + + void * window_id; /* used for progress messages, etc. */ + + void * data_object; /* a pointer to whatever + * structure you wish to have + * passed to the routines below + * during writes, etc... + * + * this data object should hold + * the document, document + * structure or a pointer to the + * document. + */ + + MKSessionWriteReadyFunc is_write_ready; /* checks to see if the stream is ready + * for writing. Returns 0 if not ready + * or the number of bytes that it can + * accept for write + */ + MKSessionWriteFunc put_block; /* writes a block of data to the stream */ + MKSessionCompleteFunc complete; /* normal end */ + MKSessionAbortFunc abort; /* abnormal end */ + + bool is_multipart; /* is the stream part of a multipart sequence */ +}; + +/* + * This is for the reworked mime parser. + */ +class mime_stream_data { /* This object is the state we pass around + amongst the various stream functions + used by MIME_MessageConverter(). */ +public: + mime_stream_data(); + + char *url_name; + char *orig_url_name; /* original url name */ + nsCOMPtr<nsIChannel> channel; + nsMimeOutputType format_out; + void *pluginObj2; /* The new XP-COM stream converter object */ + nsMIMESession *istream; /* Holdover - new stream we're writing out image data-if any. */ + MimeObject *obj; /* The root parser object */ + MimeDisplayOptions *options; /* Data for communicating with libmime.a */ + MimeHeaders *headers; /* Copy of outer most mime header */ + + nsIMimeEmitter *output_emitter; /* Output emitter engine for libmime */ + bool firstCheck; /* Is this the first look at the stream data */ +}; + +// +// This object is the state we use for loading drafts and templates... +// +class mime_draft_data +{ +public: + mime_draft_data(); + char *url_name; // original url name */ + nsMimeOutputType format_out; // intended output format; should be FO_OPEN_DRAFT */ + nsMIMESession *stream; // not used for now + MimeObject *obj; // The root + MimeDisplayOptions *options; // data for communicating with libmime + MimeHeaders *headers; // Copy of outer most mime header + nsTArray<nsMsgAttachedFile*> attachments;// attachments + nsMsgAttachedFile *messageBody; // message body + nsMsgAttachedFile *curAttachment; // temp + + nsCOMPtr <nsIFile> tmpFile; + nsCOMPtr <nsIOutputStream> tmpFileStream; // output file handle + + MimeDecoderData *decoder_data; + char *mailcharset; // get it from CHARSET of Content-Type + bool forwardInline; + bool forwardInlineFilter; + bool overrideComposeFormat; // Override compose format (for forward inline). + nsString forwardToAddress; + nsCOMPtr<nsIMsgIdentity> identity; + char *originalMsgURI; // the original URI of the message we are currently processing + nsCOMPtr<nsIMsgDBHdr> origMsgHdr; +}; + +//////////////////////////////////////////////////////////////// +// Bridge routines for legacy mime code +//////////////////////////////////////////////////////////////// + +// Create bridge stream for libmime +extern "C" +void *mime_bridge_create_display_stream(nsIMimeEmitter *newEmitter, + nsStreamConverter *newPluginObj2, + nsIURI *uri, + nsMimeOutputType format_out, + uint32_t whattodo, + nsIChannel *aChannel); + +// To get the mime emitter... +extern "C" nsIMimeEmitter *GetMimeEmitter(MimeDisplayOptions *opt); + +// To support 2 types of emitters...we need these routines :-( +extern "C" nsresult mimeSetNewURL(nsMIMESession *stream, char *url); +extern "C" nsresult mimeEmitterAddAttachmentField(MimeDisplayOptions *opt, const char *field, const char *value); +extern "C" nsresult mimeEmitterAddHeaderField(MimeDisplayOptions *opt, const char *field, const char *value); +extern "C" nsresult mimeEmitterAddAllHeaders(MimeDisplayOptions *opt, const char *allheaders, const int32_t allheadersize); +extern "C" nsresult mimeEmitterStartAttachment(MimeDisplayOptions *opt, const char *name, const char *contentType, const char *url, + bool aIsExternalAttachment); +extern "C" nsresult mimeEmitterEndAttachment(MimeDisplayOptions *opt); +extern "C" nsresult mimeEmitterEndAllAttachments(MimeDisplayOptions *opt); +extern "C" nsresult mimeEmitterStartBody(MimeDisplayOptions *opt, bool bodyOnly, const char *msgID, const char *outCharset); +extern "C" nsresult mimeEmitterEndBody(MimeDisplayOptions *opt); +extern "C" nsresult mimeEmitterEndHeader(MimeDisplayOptions *opt, MimeObject *obj); +extern "C" nsresult mimeEmitterStartHeader(MimeDisplayOptions *opt, bool rootMailHeader, bool headerOnly, const char *msgID, + const char *outCharset); +extern "C" nsresult mimeEmitterUpdateCharacterSet(MimeDisplayOptions *opt, const char *aCharset); + +extern "C" nsresult MimeGetAttachmentList(MimeObject *tobj, const char *aMessageURL, nsMsgAttachmentData **data); + +/* To Get the connnection to prefs service manager */ +extern "C" nsIPrefBranch *GetPrefBranch(MimeDisplayOptions *opt); + +// Get the text converter... +mozITXTToHTMLConv *GetTextConverter(MimeDisplayOptions *opt); + +nsresult +HTML2Plaintext(const nsString& inString, nsString& outString, + uint32_t flags, uint32_t wrapCol); +nsresult +HTMLSanitize(const nsString& inString, nsString& outString); + +extern "C" char *MimeGetStringByID(int32_t stringID); +extern "C" char *MimeGetStringByName(const char16_t *stringName); + +// Utility to create a nsIURI object... +extern "C" nsresult nsMimeNewURI(nsIURI** aInstancePtrResult, const char *aSpec, nsIURI *aBase); + +extern "C" nsresult SetMailCharacterSetToMsgWindow(MimeObject *obj, const char *aCharacterSet); + +extern "C" nsresult GetMailNewsFont(MimeObject *obj, bool styleFixed, int32_t *fontPixelSize, int32_t *fontSizePercentage, nsCString& fontLang); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _MIMEMOZ_H_ */ + diff --git a/mailnews/mime/src/mimempar.cpp b/mailnews/mime/src/mimempar.cpp new file mode 100644 index 000000000..efcd06445 --- /dev/null +++ b/mailnews/mime/src/mimempar.cpp @@ -0,0 +1,21 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimempar.h" +#include "prlog.h" + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartParallel, MimeMultipartParallelClass, + mimeMultipartParallelClass, &MIME_SUPERCLASS); + +static int +MimeMultipartParallelClassInitialize(MimeMultipartParallelClass *clazz) +{ +#ifdef DEBUG + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); +#endif + return 0; +} diff --git a/mailnews/mime/src/mimempar.h b/mailnews/mime/src/mimempar.h new file mode 100644 index 000000000..1ac39f1fc --- /dev/null +++ b/mailnews/mime/src/mimempar.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMPAR_H_ +#define _MIMEMPAR_H_ + +#include "mimemult.h" + +/* The MimeMultipartParallel class implements the multipart/parallel MIME + container, which is currently no different from multipart/mixed, since + it's not clear that there's anything useful it could do differently. + */ + +typedef struct MimeMultipartParallelClass MimeMultipartParallelClass; +typedef struct MimeMultipartParallel MimeMultipartParallel; + +struct MimeMultipartParallelClass { + MimeMultipartClass multipart; +}; + +extern MimeMultipartParallelClass mimeMultipartParallelClass; + +struct MimeMultipartParallel { + MimeMultipart multipart; +}; + +#define MimeMultipartParallelClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMPAR_H_ */ diff --git a/mailnews/mime/src/mimemrel.cpp b/mailnews/mime/src/mimemrel.cpp new file mode 100644 index 000000000..bbcb990b5 --- /dev/null +++ b/mailnews/mime/src/mimemrel.cpp @@ -0,0 +1,1199 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ + +/* Thoughts on how to implement this: + + = if the type of this multipart/related is not text/html, then treat + it the same as multipart/mixed. + + = For each part in this multipart/related + = if this part is not the "top" part + = then save this part to a tmp file or a memory object, + kind-of like what we do for multipart/alternative sub-parts. + If this is an object we're blocked on (see below) send its data along. + = else + = emit this part (remember, it's of type text/html) + = at some point, layout may load a URL for <IMG SRC="cid:xxxx">. + we intercept that. + = if one of our cached parts has that cid, return the data for it. + = else, "block", the same way the image library blocks layout when it + doesn't yet have the size of the image. + = at some point, layout may load a URL for <IMG SRC="relative/yyy">. + we need to intercept that too. + = expand the URL, and compare it to our cached objects. + if it matches, return it. + = else block on it. + + = once we get to the end, if we have any sub-part references that we're + still blocked on, map over them: + = if they're cid: references, close them ("broken image" results.) + = if they're URLs, then load them in the normal way. + + -------------------------------------------------- + + Ok, that's fairly complicated. How about an approach where we go through + all the parts first, and don't emit until the end? + + = if the type of this multipart/related is not text/html, then treat + it the same as multipart/mixed. + + = For each part in this multipart/related + = save this part to a tmp file or a memory object, + like what we do for multipart/alternative sub-parts. + + = Emit the "top" part (the text/html one) + = intercept all calls to NET_GetURL, to allow us to rewrite the URL. + (hook into netlib, or only into imglib's calls to GetURL?) + (make sure we're behaving in a context-local way.) + + = when a URL is loaded, look through our cached parts for a match. + = if we find one, map that URL to a "cid:" URL + = else, let it load normally + + = at some point, layout may load a URL for <IMG SRC="cid:xxxx">. + it will do this either because that's what was in the HTML, or because + that's how we "rewrote" the URLs when we intercepted NET_GetURL. + + = if one of our cached parts has the requested cid, return the data + for it. + = else, generate a "broken image" + + = free all the cached data + + -------------------------------------------------- + + How hard would be an approach where we rewrite the HTML? + (Looks like it's not much easier, and might be more error-prone.) + + = if the type of this multipart/related is not text/html, then treat + it the same as multipart/mixed. + + = For each part in this multipart/related + = save this part to a tmp file or a memory object, + like what we do for multipart/alternative sub-parts. + + = Parse the "top" part, and emit slightly different HTML: + = for each <IMG SRC>, <IMG LOWSRC>, <A HREF>? Any others? + = look through our cached parts for a matching URL + = if we find one, map that URL to a "cid:" URL + = else, let it load normally + + = at some point, layout may load a URL for <IMG SRC="cid:xxxx">. + = if one of our cached parts has the requested cid, return the data + for it. + = else, generate a "broken image" + + = free all the cached data + */ +#include "nsCOMPtr.h" +#include "mimemrel.h" +#include "mimemapl.h" +#include "prmem.h" +#include "prprf.h" +#include "prlog.h" +#include "plstr.h" +#include "mimemoz2.h" +#include "nsStringGlue.h" +#include "nsIURL.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include "nsMimeTypes.h" +#include "mimebuf.h" +#include "nsMsgUtils.h" +#include <ctype.h> + +// +// External Defines... +// + +extern nsresult +nsMsgCreateTempFile(const char *tFileName, nsIFile **tFile); + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartRelated, MimeMultipartRelatedClass, + mimeMultipartRelatedClass, &MIME_SUPERCLASS); + + +class MimeHashValue +{ +public: + MimeHashValue(MimeObject *obj, char *url) { + m_obj = obj; + m_url = strdup(url); + } + virtual ~MimeHashValue() { + if (m_url) + PR_Free((void *)m_url); + } + + MimeObject *m_obj; + char *m_url; +}; + +static int +MimeMultipartRelated_initialize(MimeObject* obj) +{ + MimeMultipartRelated* relobj = (MimeMultipartRelated*) obj; + relobj->base_url = MimeHeaders_get(obj->headers, HEADER_CONTENT_BASE, + false, false); + /* rhp: need this for supporting Content-Location */ + if (!relobj->base_url) + { + relobj->base_url = MimeHeaders_get(obj->headers, HEADER_CONTENT_LOCATION, + false, false); + } + /* rhp: need this for supporting Content-Location */ + + /* I used to have code here to test if the type was text/html. Then I + added multipart/alternative as being OK, too. Then I found that the + VCard spec seems to talk about having the first part of a + multipart/related be an application/directory. At that point, I decided + to punt. We handle anything as the first part, and stomp on the HTML it + generates to adjust tags to point into the other parts. This probably + works out to something reasonable in most cases. */ + + relobj->hash = PL_NewHashTable(20, PL_HashString, PL_CompareStrings, PL_CompareValues, + (PLHashAllocOps *)NULL, NULL); + + if (!relobj->hash) return MIME_OUT_OF_MEMORY; + + relobj->input_file_stream = nullptr; + relobj->output_file_stream = nullptr; + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj); +} + +static int +mime_multipart_related_nukehash(PLHashEntry *table, + int indx, void *arg) +{ + if (table->key) + PR_Free((char*) table->key); + + if (table->value) + delete (MimeHashValue *)table->value; + + return HT_ENUMERATE_NEXT; /* XP_Maphash will continue traversing the hash */ +} + +static void +MimeMultipartRelated_finalize (MimeObject *obj) +{ + MimeMultipartRelated* relobj = (MimeMultipartRelated*) obj; + PR_FREEIF(relobj->base_url); + PR_FREEIF(relobj->curtag); + if (relobj->buffered_hdrs) { + PR_FREEIF(relobj->buffered_hdrs->all_headers); + PR_FREEIF(relobj->buffered_hdrs->heads); + PR_FREEIF(relobj->buffered_hdrs); + } + PR_FREEIF(relobj->head_buffer); + relobj->head_buffer_fp = 0; + relobj->head_buffer_size = 0; + if (relobj->hash) { + PL_HashTableEnumerateEntries(relobj->hash, mime_multipart_related_nukehash, NULL); + PL_HashTableDestroy(relobj->hash); + relobj->hash = NULL; + } + + if (relobj->input_file_stream) + { + relobj->input_file_stream->Close(); + relobj->input_file_stream = nullptr; + } + + if (relobj->output_file_stream) + { + relobj->output_file_stream->Close(); + relobj->output_file_stream = nullptr; + } + + if (relobj->file_buffer) + { + relobj->file_buffer->Remove(false); + relobj->file_buffer = nullptr; + } + + if (relobj->headobj) { + // In some error conditions when MimeMultipartRelated_parse_eof() isn't run + // (for example, no temp disk space available to extract message parts), + // the head object is also referenced as a child. + // If we free it, we remove the child reference first ... or crash later :-( + MimeContainer *cont = (MimeContainer *)relobj; + for (int i = 0; i < cont->nchildren; i++) { + if (cont->children[i] == relobj->headobj) { + // Shift remaining children down. + for (int j = i+1; j < cont->nchildren; j++) { + cont->children[j-1] = cont->children[j]; + } + cont->children[--cont->nchildren] = nullptr; + break; + } + } + + mime_free(relobj->headobj); + relobj->headobj = nullptr; + } + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj); +} + +#define ISHEX(c) ( ((c) >= '0' && (c) <= '9') || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F') ) +#define NONHEX(c) (!ISHEX(c)) + +extern "C" char * +escape_unescaped_percents(const char *incomingURL) +{ + const char *inC; + char *outC; + char *result = (char *) PR_Malloc(strlen(incomingURL)*3+1); + + if (result) + { + for(inC = incomingURL, outC = result; *inC != '\0'; inC++) + { + if (*inC == '%') + { + /* Check if either of the next two characters are non-hex. */ + if ( !*(inC+1) || NONHEX(*(inC+1)) || !*(inC+2) || NONHEX(*(inC+2)) ) + { + /* Hex characters don't follow, escape the + percent char */ + *outC++ = '%'; *outC++ = '2'; *outC++ = '5'; + } + else + { + /* Hex characters follow, so assume the percent + is escaping something else */ + *outC++ = *inC; + } + } + else + *outC++ = *inC; + } + *outC = '\0'; + } + + return result; +} + +/* This routine is only necessary because the mailbox URL fed to us + by the winfe can contain spaces and '>'s in it. It's a hack. */ +static char * +escape_for_mrel_subst(char *inURL) +{ + char *output, *inC, *outC, *temp; + + int size = strlen(inURL) + 1; + + for(inC = inURL; *inC; inC++) + if ((*inC == ' ') || (*inC == '>')) + size += 2; /* space -> '%20', '>' -> '%3E', etc. */ + + output = (char *)PR_MALLOC(size); + if (output) + { + /* Walk through the source string, copying all chars + except for spaces, which get escaped. */ + inC = inURL; + outC = output; + while(*inC) + { + if (*inC == ' ') + { + *outC++ = '%'; *outC++ = '2'; *outC++ = '0'; + } + else if (*inC == '>') + { + *outC++ = '%'; *outC++ = '3'; *outC++ = 'E'; + } + else + *outC++ = *inC; + + inC++; + } + *outC = '\0'; + + temp = escape_unescaped_percents(output); + if (temp) + { + PR_FREEIF(output); + output = temp; + } + } + return output; +} + +static bool +MimeStartParamExists(MimeObject *obj, MimeObject* child) +{ + char *ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, false, false); + char *st = (ct + ? MimeHeaders_get_parameter(ct, HEADER_PARM_START, NULL, NULL) + : 0); + + PR_FREEIF(ct); + if (!st) + return false; + + PR_FREEIF(st); + return true; +} + +static bool +MimeThisIsStartPart(MimeObject *obj, MimeObject* child) +{ + bool rval = false; + char *ct, *st, *cst; + + ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, false, false); + st = (ct + ? MimeHeaders_get_parameter(ct, HEADER_PARM_START, NULL, NULL) + : 0); + + PR_FREEIF(ct); + if (!st) + return false; + + cst = MimeHeaders_get(child->headers, HEADER_CONTENT_ID, false, false); + if (!cst) + rval = false; + else + { + char *tmp = cst; + if (*tmp == '<') + { + int length; + tmp++; + length = strlen(tmp); + if (length > 0 && tmp[length - 1] == '>') + { + tmp[length - 1] = '\0'; + } + } + + rval = (!strcmp(st, tmp)); + } + + PR_FREEIF(st); + PR_FREEIF(cst); + return rval; +} +/* rhp - gotta support the "start" parameter */ + +char * +MakeAbsoluteURL(char *base_url, char *relative_url) +{ + char *retString = nullptr; + nsIURI *base = nullptr; + + // if either is NULL, just return the relative if safe... + if (!base_url || !relative_url) + { + if (!relative_url) + return nullptr; + + NS_MsgSACopy(&retString, relative_url); + return retString; + } + + nsresult err = nsMimeNewURI(&base, base_url, nullptr); + if (NS_FAILED(err)) + return nullptr; + + nsAutoCString spec; + + nsIURI *url = nullptr; + err = nsMimeNewURI(&url, relative_url, base); + if (NS_FAILED(err)) + goto done; + + err = url->GetSpec(spec); + if (NS_FAILED(err)) + { + retString = nullptr; + goto done; + } + retString = ToNewCString(spec); + +done: + NS_IF_RELEASE(url); + NS_IF_RELEASE(base); + return retString; +} + +static bool +MimeMultipartRelated_output_child_p(MimeObject *obj, MimeObject* child) +{ + MimeMultipartRelated *relobj = (MimeMultipartRelated *) obj; + + /* rhp - Changed from "if (relobj->head_loaded)" alone to support the + start parameter + */ + if ( + (relobj->head_loaded) || + (MimeStartParamExists(obj, child) && !MimeThisIsStartPart(obj, child)) + ) + { + /* This is a child part. Just remember the mapping between the URL + it represents and the part-URL to get it back. */ + + char* location = MimeHeaders_get(child->headers, HEADER_CONTENT_LOCATION, + false, false); + if (!location) { + char* tmp = MimeHeaders_get(child->headers, HEADER_CONTENT_ID, + false, false); + if (tmp) { + char* tmp2 = tmp; + if (*tmp2 == '<') { + int length; + tmp2++; + length = strlen(tmp2); + if (length > 0 && tmp2[length - 1] == '>') { + tmp2[length - 1] = '\0'; + } + } + location = PR_smprintf("cid:%s", tmp2); + PR_Free(tmp); + } + } + + if (location) { + char *absolute; + char *base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_BASE, + false, false); + absolute = MakeAbsoluteURL(base_url ? base_url : relobj->base_url, location); + + PR_FREEIF(base_url); + PR_Free(location); + if (absolute) { + nsAutoCString partnum; + nsAutoCString imappartnum; + partnum.Adopt(mime_part_address(child)); + if (!partnum.IsEmpty()) { + if (obj->options->missing_parts) + { + char * imappart = mime_imap_part_address(child); + if (imappart) + imappartnum.Adopt(imappart); + } + + /* + AppleDouble part need special care: we need to output only the data fork part of it. + The problem at this point is that we haven't yet decoded the children of the AppleDouble + part therfore we will have to hope the datafork is the second one! + */ + if (mime_typep(child, (MimeObjectClass *) &mimeMultipartAppleDoubleClass)) + partnum.Append(".2"); + + char* part; + if (!imappartnum.IsEmpty()) + part = mime_set_url_imap_part(obj->options->url, imappartnum.get(), partnum.get()); + else + { + char *no_part_url = nullptr; + if (obj->options->part_to_load && obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) + no_part_url = mime_get_base_url(obj->options->url); + if (no_part_url) + { + part = mime_set_url_part(no_part_url, partnum.get(), false); + PR_Free(no_part_url); + } + else + part = mime_set_url_part(obj->options->url, partnum.get(), false); + } + if (part) + { + char *name = MimeHeaders_get_name(child->headers, child->options); + // let's stick the filename in the part so save as will work. + if (name) + { + char *savePart = part; + part = PR_smprintf("%s&filename=%s", savePart, name); + PR_Free(savePart); + PR_Free(name); + } + char *temp = part; + /* If there's a space in the url, escape the url. + (This happens primarily on Windows and Unix.) */ + if (PL_strchr(part, ' ') || PL_strchr(part, '>') || PL_strchr(part, '%')) + temp = escape_for_mrel_subst(part); + MimeHashValue * value = new MimeHashValue(child, temp); + PL_HashTableAdd(relobj->hash, absolute, value); + + /* rhp - If this part ALSO has a Content-ID we need to put that into + the hash table and this is what this code does + */ + { + char *tloc; + char *tmp = MimeHeaders_get(child->headers, HEADER_CONTENT_ID, false, false); + if (tmp) + { + char* tmp2 = tmp; + if (*tmp2 == '<') + { + int length; + tmp2++; + length = strlen(tmp2); + if (length > 0 && tmp2[length - 1] == '>') + { + tmp2[length - 1] = '\0'; + } + } + + tloc = PR_smprintf("cid:%s", tmp2); + PR_Free(tmp); + if (tloc) + { + MimeHashValue *value; + value = (MimeHashValue*)PL_HashTableLookup(relobj->hash, tloc); + + if (!value) + { + value = new MimeHashValue(child, temp); + PL_HashTableAdd(relobj->hash, tloc, value); + } + else + PR_smprintf_free(tloc); + } + } + } + /* rhp - End of putting more stuff into the hash table */ + + /* it's possible that temp pointer is the same than the part pointer, + therefore be carefull to not freeing twice the same pointer */ + if (temp && temp != part) + PR_Free(temp); + PR_Free(part); + } + } + } + } + } else { + /* Ah-hah! We're the head object. */ + char* base_url; + relobj->head_loaded = true; + relobj->headobj = child; + relobj->buffered_hdrs = MimeHeaders_copy(child->headers); + base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_BASE, + false, false); + /* rhp: need this for supporting Content-Location */ + if (!base_url) + { + base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_LOCATION, false, false); + } + /* rhp: need this for supporting Content-Location */ + + if (base_url) { + /* If the head object has a base_url associated with it, use + that instead of any base_url that may have been associated + with the multipart/related. */ + PR_FREEIF(relobj->base_url); + relobj->base_url = base_url; + } + } + if (obj->options && !obj->options->write_html_p +#ifdef MIME_DRAFTS + && !obj->options->decompose_file_p +#endif /* MIME_DRAFTS */ + ) + { + return true; + } + + return false; /* Don't actually parse this child; we'll handle + all that at eof time. */ +} + +static int +MimeMultipartRelated_parse_child_line (MimeObject *obj, + const char *line, int32_t length, + bool first_line_p) +{ + MimeContainer *cont = (MimeContainer *) obj; + MimeMultipartRelated *relobj = (MimeMultipartRelated *) obj; + MimeObject *kid; + + if (obj->options && !obj->options->write_html_p +#ifdef MIME_DRAFTS + && !obj->options->decompose_file_p +#endif /* MIME_DRAFTS */ + ) + { + /* Oh, just go do the normal thing... */ + return ((MimeMultipartClass*)&MIME_SUPERCLASS)-> + parse_child_line(obj, line, length, first_line_p); + } + + /* Throw it away if this isn't the head object. (Someday, maybe we'll + cache it instead.) */ + PR_ASSERT(cont->nchildren > 0); + if (cont->nchildren <= 0) + return -1; + kid = cont->children[cont->nchildren-1]; + PR_ASSERT(kid); + if (!kid) return -1; + if (kid != relobj->headobj) return 0; + + /* Buffer this up (###tw much code duplication from mimemalt.c) */ + /* If we don't yet have a buffer (either memory or file) try and make a + memory buffer. */ + if (!relobj->head_buffer && !relobj->file_buffer) { + int target_size = 1024 * 50; /* try for 50k */ + while (target_size > 0) { + relobj->head_buffer = (char *) PR_MALLOC(target_size); + if (relobj->head_buffer) break; /* got it! */ + target_size -= (1024 * 5); /* decrease it and try again */ + } + + if (relobj->head_buffer) { + relobj->head_buffer_size = target_size; + } else { + relobj->head_buffer_size = 0; + } + + relobj->head_buffer_fp = 0; + } + + nsresult rv; + /* Ok, if at this point we still don't have either kind of buffer, try and + make a file buffer. */ + if (!relobj->head_buffer && !relobj->file_buffer) + { + nsCOMPtr <nsIFile> file; + rv = nsMsgCreateTempFile("nsma", getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, -1); + relobj->file_buffer = do_QueryInterface(file); + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(relobj->output_file_stream), relobj->file_buffer, PR_WRONLY | PR_CREATE_FILE, 00600); + NS_ENSURE_SUCCESS(rv, -1); + } + + PR_ASSERT(relobj->head_buffer || relobj->output_file_stream); + + + /* If this line will fit in the memory buffer, put it there. + */ + if (relobj->head_buffer && + relobj->head_buffer_fp + length < relobj->head_buffer_size) { + memcpy(relobj->head_buffer + relobj->head_buffer_fp, line, length); + relobj->head_buffer_fp += length; + } else { + /* Otherwise it won't fit; write it to the file instead. */ + + /* If the file isn't open yet, open it, and dump the memory buffer + to it. */ + if (!relobj->output_file_stream) + { + if (!relobj->file_buffer) + { + nsCOMPtr <nsIFile> file; + rv = nsMsgCreateTempFile("nsma", getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, -1); + relobj->file_buffer = do_QueryInterface(file); + } + + nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(relobj->output_file_stream), relobj->file_buffer, PR_WRONLY | PR_CREATE_FILE, 00600); + NS_ENSURE_SUCCESS(rv, -1); + + if (relobj->head_buffer && relobj->head_buffer_fp) + { + uint32_t bytesWritten; + rv = relobj->output_file_stream->Write(relobj->head_buffer, relobj->head_buffer_fp, &bytesWritten); + if (NS_FAILED(rv) || (bytesWritten < relobj->head_buffer_fp)) + return MIME_UNABLE_TO_OPEN_TMP_FILE; + } + + PR_FREEIF(relobj->head_buffer); + relobj->head_buffer_fp = 0; + relobj->head_buffer_size = 0; + } + + /* Dump this line to the file. */ + uint32_t bytesWritten; + rv = relobj->output_file_stream->Write(line, length, &bytesWritten); + if ((int32_t) bytesWritten < length || NS_FAILED(rv)) + return MIME_UNABLE_TO_OPEN_TMP_FILE; + } + + return 0; +} + + + + +static int +real_write(MimeMultipartRelated* relobj, const char* buf, int32_t size) +{ + MimeObject* obj = (MimeObject*) relobj; + void* closure = relobj->real_output_closure; + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + obj->options->decompose_file_output_fn ) + { + + // the buf here has already been decoded, but we want to use general output + // functions here that permit decoded or encoded input, using the closure + // to tell the difference. We'll temporarily disable the closure's decoder, + // then restore it when we are done. Not sure if we shouldn't just turn it off + // permanently though. + + mime_draft_data *mdd = (mime_draft_data *) obj->options->stream_closure; + MimeDecoderData* old_decoder_data = mdd->decoder_data; + mdd->decoder_data = nullptr; + int status = obj->options->decompose_file_output_fn + (buf, size, (void *)mdd); + mdd->decoder_data = old_decoder_data; + return status; + } + else +#endif /* MIME_DRAFTS */ + { + if (!closure) { + MimeObject* lobj = (MimeObject*) relobj; + closure = lobj->options->stream_closure; + } + return relobj->real_output_fn(buf, size, closure); + } +} + + +static int +push_tag(MimeMultipartRelated* relobj, const char* buf, int32_t size) +{ + if (size + relobj->curtag_length > relobj->curtag_max) { + relobj->curtag_max += 2 * size; + if (relobj->curtag_max < 1024) + relobj->curtag_max = 1024; + + char* newBuf = (char*) PR_Realloc(relobj->curtag, relobj->curtag_max); + NS_ENSURE_TRUE(newBuf, MIME_OUT_OF_MEMORY); + relobj->curtag = newBuf; + } + memcpy(relobj->curtag + relobj->curtag_length, buf, size); + relobj->curtag_length += size; + return 0; +} + +static bool accept_related_part(MimeMultipartRelated* relobj, MimeObject* part_obj) +{ + if (!relobj || !part_obj) + return false; + + /* before accepting it as a valid related part, make sure we + are able to display it inline as an embedded object. Else just ignore + it, that will prevent any bad surprise... */ + MimeObjectClass *clazz = mime_find_class (part_obj->content_type, part_obj->headers, part_obj->options, false); + if (clazz ? clazz->displayable_inline_p(clazz, part_obj->headers) : false) + return true; + + /* ...but we always accept it if it's referenced by an anchor */ + return (relobj->curtag && relobj->curtag_length >= 3 && + (relobj->curtag[1] == 'A' || relobj->curtag[1] == 'a') && IS_SPACE(relobj->curtag[2])); +} + +static int +flush_tag(MimeMultipartRelated* relobj) +{ + int length = relobj->curtag_length; + char* buf; + int status; + + if (relobj->curtag == NULL || length == 0) return 0; + + status = push_tag(relobj, "", 1); /* Push on a trailing NULL. */ + if (status < 0) return status; + buf = relobj->curtag; + PR_ASSERT(*buf == '<' && buf[length - 1] == '>'); + while (*buf) { + char c; + char* absolute; + char* part_url; + char* ptr = buf; + char *ptr2; + char quoteDelimiter = '\0'; + while (*ptr && *ptr != '=') ptr++; + if (*ptr == '=') { + /* Ignore = and leading space. */ + /* Safe, because there's a '>' at the end! */ + do {ptr++;} while (IS_SPACE(*ptr)); + if (*ptr == '"' || *ptr == '\'') { + quoteDelimiter = *ptr; + /* Take up the quote and leading space here as well. */ + /* Safe because there's a '>' at the end */ + do {ptr++;} while (IS_SPACE(*ptr)); + } + } + status = real_write(relobj, buf, ptr - buf); + if (status < 0) return status; + buf = ptr; + if (!*buf) break; + if (quoteDelimiter) + { + ptr = PL_strnchr(buf, quoteDelimiter, length - (buf - relobj->curtag)); + } else { + for (ptr = buf; *ptr ; ptr++) { + if (*ptr == '>' || IS_SPACE(*ptr)) break; + } + PR_ASSERT(*ptr); + } + if (!ptr || !*ptr) break; + + while(buf < ptr) + { + /* ### mwelch For each word in the value string, see if + the word is a cid: URL. If so, attempt to + substitute the appropriate mailbox part URL in + its place. */ + ptr2=buf; /* walk from the left end rightward */ + while((ptr2<ptr) && (!IS_SPACE(*ptr2))) + ptr2++; + /* Compare the beginning of the word with "cid:". Yuck. */ + if (((ptr2 - buf) > 4) && + ((buf[0]=='c' || buf[0]=='C') && + (buf[1]=='i' || buf[1]=='I') && + (buf[2]=='d' || buf[2]=='D') && + buf[3]==':')) + { + // Make sure it's lowercase, otherwise it won't be found in the hash table + buf[0] = 'c'; buf[1] = 'i'; buf[2] = 'd'; + + /* Null terminate the word so we can... */ + c = *ptr2; + *ptr2 = '\0'; + + /* Construct a URL out of the word. */ + absolute = MakeAbsoluteURL(relobj->base_url, buf); + + /* See if we have a mailbox part URL + corresponding to this cid. */ + part_url = nullptr; + MimeHashValue * value = nullptr; + if (absolute) + { + value = (MimeHashValue *)PL_HashTableLookup(relobj->hash, buf); + part_url = value ? value->m_url : nullptr; + PR_FREEIF(absolute); + } + + /*If we found a mailbox part URL, write that out instead.*/ + if (part_url && accept_related_part(relobj, value->m_obj)) + { + status = real_write(relobj, part_url, strlen(part_url)); + if (status < 0) return status; + buf = ptr2; /* skip over the cid: URL we substituted */ + + /* don't show that object as attachment */ + if (value->m_obj) + value->m_obj->dontShowAsAttachment = true; + } + + /* Restore the character that we nulled. */ + *ptr2 = c; + } + /* rhp - if we get here, we should still check against the hash table! */ + else + { + char holder = *ptr2; + char *realout; + + *ptr2 = '\0'; + + /* Construct a URL out of the word. */ + absolute = MakeAbsoluteURL(relobj->base_url, buf); + + /* See if we have a mailbox part URL + corresponding to this cid. */ + MimeHashValue * value; + if (absolute) + value = (MimeHashValue *)PL_HashTableLookup(relobj->hash, absolute); + else + value = (MimeHashValue *)PL_HashTableLookup(relobj->hash, buf); + realout = value ? value->m_url : nullptr; + + *ptr2 = holder; + PR_FREEIF(absolute); + + if (realout && accept_related_part(relobj, value->m_obj)) + { + status = real_write(relobj, realout, strlen(realout)); + if (status < 0) return status; + buf = ptr2; /* skip over the cid: URL we substituted */ + + /* don't show that object as attachment */ + if (value->m_obj) + value->m_obj->dontShowAsAttachment = true; + } + } + /* rhp - if we get here, we should still check against the hash table! */ + + /* Advance to the beginning of the next word, or to + the end of the value string. */ + while((ptr2<ptr) && (IS_SPACE(*ptr2))) + ptr2++; + + /* Write whatever original text remains after + cid: URL substitution. */ + status = real_write(relobj, buf, ptr2-buf); + if (status < 0) return status; + buf = ptr2; + } + } + if (buf && *buf) { + status = real_write(relobj, buf, strlen(buf)); + if (status < 0) return status; + } + relobj->curtag_length = 0; + return 0; +} + + + +static int +mime_multipart_related_output_fn(const char* buf, int32_t size, void *stream_closure) +{ + MimeMultipartRelated *relobj = (MimeMultipartRelated *) stream_closure; + char* ptr; + int32_t delta; + int status; + while (size > 0) { + if (relobj->curtag_length > 0) { + ptr = PL_strnchr(buf, '>', size); + if (!ptr) { + return push_tag(relobj, buf, size); + } + delta = ptr - buf + 1; + status = push_tag(relobj, buf, delta); + if (status < 0) return status; + status = flush_tag(relobj); + if (status < 0) return status; + buf += delta; + size -= delta; + } + ptr = PL_strnchr(buf, '<', size); + if (ptr && ptr - buf >= size) ptr = 0; + if (!ptr) { + return real_write(relobj, buf, size); + } + delta = ptr - buf; + status = real_write(relobj, buf, delta); + if (status < 0) return status; + buf += delta; + size -= delta; + PR_ASSERT(relobj->curtag_length == 0); + status = push_tag(relobj, buf, 1); + if (status < 0) return status; + PR_ASSERT(relobj->curtag_length == 1); + buf++; + size--; + } + return 0; +} + + +static int +MimeMultipartRelated_parse_eof (MimeObject *obj, bool abort_p) +{ + /* OK, all the necessary data has been collected. We now have to spew out + the HTML. We let it go through all the normal mechanisms (which + includes content-encoding handling), and intercept the output data to do + translation of the tags. Whee. */ + MimeMultipartRelated *relobj = (MimeMultipartRelated *) obj; + MimeContainer *cont = (MimeContainer *)obj; + int status = 0; + MimeObject *body; + char* ct; + const char* dct; + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) goto FAIL; + + if (!relobj->headobj) return 0; + + ct = (relobj->buffered_hdrs + ? MimeHeaders_get (relobj->buffered_hdrs, HEADER_CONTENT_TYPE, + true, false) + : 0); + dct = (((MimeMultipartClass *) obj->clazz)->default_part_type); + + relobj->real_output_fn = obj->options->output_fn; + relobj->real_output_closure = obj->options->output_closure; + + obj->options->output_fn = mime_multipart_related_output_fn; + obj->options->output_closure = obj; + + body = mime_create(((ct && *ct) ? ct : (dct ? dct : TEXT_HTML)), + relobj->buffered_hdrs, obj->options); + + PR_FREEIF(ct); + if (!body) { + status = MIME_OUT_OF_MEMORY; + goto FAIL; + } + // replace the existing head object with the new object + for (int iChild = 0; iChild < cont->nchildren; iChild++) { + if (cont->children[iChild] == relobj->headobj) { + // cleanup of the headobj is performed explicitly in our finalizer now + // that it does not get cleaned up as a child. + cont->children[iChild] = body; + body->parent = obj; + body->options = obj->options; + } + } + + if (!body->parent) { + NS_WARNING("unexpected mime multipart related structure"); + goto FAIL; + } + + body->dontShowAsAttachment = body->clazz->displayable_inline_p(body->clazz, body->headers); + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + obj->options->decompose_file_init_fn && + (relobj->file_buffer || relobj->head_buffer)) + { + status = obj->options->decompose_file_init_fn ( obj->options->stream_closure, + relobj->buffered_hdrs ); + if (status < 0) return status; + } +#endif /* MIME_DRAFTS */ + + /* if the emitter wants to know about nested bodies, then it needs + to know that we jumped back to this body part. */ + if (obj->options->notify_nested_bodies) + { + char *part_path = mime_part_address(body); + if (part_path) + { + mimeEmitterAddHeaderField(obj->options, + "x-jsemitter-part-path", + part_path); + PR_Free(part_path); + } + } + + /* Now that we've added this new object to our list of children, + start its parser going. */ + status = body->clazz->parse_begin(body); + if (status < 0) goto FAIL; + + if (relobj->head_buffer) + { + /* Read it out of memory. */ + PR_ASSERT(!relobj->file_buffer && !relobj->input_file_stream); + + status = body->clazz->parse_buffer(relobj->head_buffer, + relobj->head_buffer_fp, + body); + } + else if (relobj->file_buffer) + { + /* Read it off disk. */ + char *buf; + + PR_ASSERT(relobj->head_buffer_size == 0 && + relobj->head_buffer_fp == 0); + PR_ASSERT(relobj->file_buffer); + if (!relobj->file_buffer) + { + status = -1; + goto FAIL; + } + + buf = (char *) PR_MALLOC(FILE_IO_BUFFER_SIZE); + if (!buf) + { + status = MIME_OUT_OF_MEMORY; + goto FAIL; + } + + // First, close the output file to open the input file! + if (relobj->output_file_stream) + relobj->output_file_stream->Close(); + + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(relobj->input_file_stream), relobj->file_buffer); + if (NS_FAILED(rv)) + { + PR_Free(buf); + status = MIME_UNABLE_TO_OPEN_TMP_FILE; + goto FAIL; + } + + while(1) + { + uint32_t bytesRead = 0; + rv = relobj->input_file_stream->Read(buf, FILE_IO_BUFFER_SIZE - 1, &bytesRead); + if (NS_FAILED(rv) || !bytesRead) + { + status = NS_FAILED(rv) ? -1 : 0; + break; + } + else + { + /* It would be really nice to be able to yield here, and let + some user events and other input sources get processed. + Oh well. */ + + status = body->clazz->parse_buffer(buf, bytesRead, body); + if (status < 0) break; + } + } + PR_Free(buf); + } + + if (status < 0) goto FAIL; + + /* Done parsing. */ + status = body->clazz->parse_eof(body, false); + if (status < 0) goto FAIL; + status = body->clazz->parse_end(body, false); + if (status < 0) goto FAIL; + +FAIL: + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + obj->options->decompose_file_close_fn && + (relobj->file_buffer || relobj->head_buffer)) { + status = obj->options->decompose_file_close_fn ( obj->options->stream_closure ); + if (status < 0) return status; + } +#endif /* MIME_DRAFTS */ + + obj->options->output_fn = relobj->real_output_fn; + obj->options->output_closure = relobj->real_output_closure; + + return status; +} + + + + +static int +MimeMultipartRelatedClassInitialize(MimeMultipartRelatedClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + PR_ASSERT(!oclass->class_initialized); + oclass->initialize = MimeMultipartRelated_initialize; + oclass->finalize = MimeMultipartRelated_finalize; + oclass->parse_eof = MimeMultipartRelated_parse_eof; + mclass->output_child_p = MimeMultipartRelated_output_child_p; + mclass->parse_child_line = MimeMultipartRelated_parse_child_line; + return 0; +} diff --git a/mailnews/mime/src/mimemrel.h b/mailnews/mime/src/mimemrel.h new file mode 100644 index 000000000..0c56873cf --- /dev/null +++ b/mailnews/mime/src/mimemrel.h @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMREL_H_ +#define _MIMEMREL_H_ + +#include "mimemult.h" +#include "plhash.h" +#include "prio.h" +#include "nsNetUtil.h" +#include "nsIMimeConverter.h" // for MimeConverterOutputCallback + +/* The MimeMultipartRelated class implements the multipart/related MIME + container, which allows `sibling' sub-parts to refer to each other. + */ + +typedef struct MimeMultipartRelatedClass MimeMultipartRelatedClass; +typedef struct MimeMultipartRelated MimeMultipartRelated; + +struct MimeMultipartRelatedClass { + MimeMultipartClass multipart; +}; + +extern "C" MimeMultipartRelatedClass mimeMultipartRelatedClass; + +struct MimeMultipartRelated { + MimeMultipart multipart; /* superclass variables */ + + char* base_url; /* Base URL (if any) for the whole + multipart/related. */ + + char* head_buffer; /* Buffer used to remember the text/html 'head' + part. */ + uint32_t head_buffer_fp; /* Active length. */ + uint32_t head_buffer_size; /* How big it is. */ + + nsCOMPtr <nsIFile> file_buffer; /* The nsIFile of a temp file used when we + run out of room in the head_buffer. */ + nsCOMPtr <nsIInputStream> input_file_stream; /* A stream to it. */ + nsCOMPtr <nsIOutputStream> output_file_stream; /* A stream to it. */ + + MimeHeaders* buffered_hdrs; /* The headers of the 'head' part. */ + + bool head_loaded; /* Whether we've already passed the 'head' + part. */ + MimeObject* headobj; /* The actual text/html head object. */ + + PLHashTable *hash; + + MimeConverterOutputCallback real_output_fn; + void* real_output_closure; + + char* curtag; + int32_t curtag_max; + int32_t curtag_length; + + + +}; + +#define MimeMultipartRelatedClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMREL_H_ */ diff --git a/mailnews/mime/src/mimemsg.cpp b/mailnews/mime/src/mimemsg.cpp new file mode 100644 index 000000000..2d6957069 --- /dev/null +++ b/mailnews/mime/src/mimemsg.cpp @@ -0,0 +1,977 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "nsIMimeEmitter.h" +#include "mimemsg.h" +#include "mimemoz2.h" +#include "prmem.h" +#include "prio.h" +#include "plstr.h" +#include "msgCore.h" +#include "prlog.h" +#include "prprf.h" +#include "nsMimeStringResources.h" +#include "nsMimeTypes.h" +#include "nsMsgMessageFlags.h" +#include "nsStringGlue.h" +#include "mimetext.h" +#include "mimecryp.h" +#include "mimetpfl.h" +#include "nsINetUtil.h" +#include "nsMsgUtils.h" +#include "nsMsgI18N.h" + +#define MIME_SUPERCLASS mimeContainerClass +MimeDefClass(MimeMessage, MimeMessageClass, mimeMessageClass, + &MIME_SUPERCLASS); + +static int MimeMessage_initialize (MimeObject *); +static void MimeMessage_finalize (MimeObject *); +static int MimeMessage_add_child (MimeObject *, MimeObject *); +static int MimeMessage_parse_begin (MimeObject *); +static int MimeMessage_parse_line (const char *, int32_t, MimeObject *); +static int MimeMessage_parse_eof (MimeObject *, bool); +static int MimeMessage_close_headers (MimeObject *obj); +static int MimeMessage_write_headers_html (MimeObject *); +static char *MimeMessage_partial_message_html(const char *data, + void *closure, + MimeHeaders *headers); + +#ifdef XP_UNIX +extern void MimeHeaders_do_unix_display_hook_hack(MimeHeaders *); +#endif /* XP_UNIX */ + +#if defined(DEBUG) && defined(XP_UNIX) +static int MimeMessage_debug_print (MimeObject *, PRFileDesc *, int32_t depth); +#endif + +extern MimeObjectClass mimeMultipartClass; + +static int +MimeMessageClassInitialize(MimeMessageClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeContainerClass *cclass = (MimeContainerClass *) clazz; + + PR_ASSERT(!oclass->class_initialized); + oclass->initialize = MimeMessage_initialize; + oclass->finalize = MimeMessage_finalize; + oclass->parse_begin = MimeMessage_parse_begin; + oclass->parse_line = MimeMessage_parse_line; + oclass->parse_eof = MimeMessage_parse_eof; + cclass->add_child = MimeMessage_add_child; + +#if defined(DEBUG) && defined(XP_UNIX) + oclass->debug_print = MimeMessage_debug_print; +#endif + return 0; +} + + +static int +MimeMessage_initialize (MimeObject *object) +{ + MimeMessage *msg = (MimeMessage *)object; + msg->grabSubject = false; + msg->bodyLength = 0; + msg->sizeSoFar = 0; + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeMessage_finalize (MimeObject *object) +{ + MimeMessage *msg = (MimeMessage *)object; + if (msg->hdrs) + MimeHeaders_free(msg->hdrs); + msg->hdrs = 0; + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + +static int +MimeMessage_parse_begin (MimeObject *obj) +{ + MimeMessage *msg = (MimeMessage *)obj; + + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + if (obj->parent) + { + msg->grabSubject = true; + } + + /* Messages have separators before the headers, except for the outermost + message. */ + return MimeObject_write_separator(obj); +} + + +static int +MimeMessage_parse_line (const char *aLine, int32_t aLength, MimeObject *obj) +{ + const char * line = aLine; + int32_t length = aLength; + + MimeMessage *msg = (MimeMessage *) obj; + int status = 0; + + NS_ASSERTION(line && *line, "empty line in mime msg parse_line"); + if (!line || !*line) return -1; + + msg->sizeSoFar += length; + + if (msg->grabSubject) + { + if ( (!PL_strncasecmp(line, "Subject: ", 9)) && (obj->parent) ) + { + if ( (obj->headers) && (!obj->headers->munged_subject) ) + { + obj->headers->munged_subject = (char *) PL_strndup(line + 9, length - 9); + char *tPtr = obj->headers->munged_subject; + while (*tPtr) + { + if ( (*tPtr == '\r') || (*tPtr == '\n') ) + { + *tPtr = '\0'; + break; + } + tPtr++; + } + } + } + } + + /* If we already have a child object, then we're done parsing headers, + and all subsequent lines get passed to the inferior object without + further processing by us. (Our parent will stop feeding us lines + when this MimeMessage part is out of data.) + */ + if (msg->container.nchildren) + { + MimeObject *kid = msg->container.children[0]; + bool nl; + PR_ASSERT(kid); + if (!kid) return -1; + + msg->bodyLength += length; + + /* Don't allow MimeMessage objects to not end in a newline, since it + would be inappropriate for any following part to appear on the same + line as the last line of the message. + + #### This assumes that the only time the `parse_line' method is + called with a line that doesn't end in a newline is when that line + is the last line. + */ + nl = (length > 0 && (line[length-1] == '\r' || line[length-1] == '\n')); + +#ifdef MIME_DRAFTS + if (!mime_typep (kid, (MimeObjectClass*) &mimeMessageClass) && + obj->options && + obj->options->decompose_file_p && + !obj->options->is_multipart_msg && + obj->options->decompose_file_output_fn && + !obj->options->decrypt_p) + { + // If we are processing a flowed plain text line, we need to parse the + // line in mimeInlineTextPlainFlowedClass. + if (mime_typep(kid, (MimeObjectClass *)&mimeInlineTextPlainFlowedClass)) + { + // Remove any stuffed space. + if (length > 0 && ' ' == *line) + { + line++; + length--; + } + return kid->clazz->parse_line (line, length, kid); + } + else + { + status = obj->options->decompose_file_output_fn (line, + length, + obj->options->stream_closure); + if (status < 0) return status; + if (!nl) { + status = obj->options->decompose_file_output_fn (MSG_LINEBREAK, + MSG_LINEBREAK_LEN, + obj->options->stream_closure); + if (status < 0) return status; + } + return status; + } + } +#endif /* MIME_DRAFTS */ + + + if (nl) + return kid->clazz->parse_buffer (line, length, kid); + else + { + /* Hack a newline onto the end. */ + char *s = (char *)PR_MALLOC(length + MSG_LINEBREAK_LEN + 1); + if (!s) return MIME_OUT_OF_MEMORY; + memcpy(s, line, length); + PL_strncpyz(s + length, MSG_LINEBREAK, MSG_LINEBREAK_LEN + 1); + status = kid->clazz->parse_buffer (s, length + MSG_LINEBREAK_LEN, kid); + PR_Free(s); + return status; + } + } + + /* Otherwise we don't yet have a child object, which means we're not + done parsing our headers yet. + */ + if (!msg->hdrs) + { + msg->hdrs = MimeHeaders_new(); + if (!msg->hdrs) return MIME_OUT_OF_MEMORY; + } + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + ! obj->options->is_multipart_msg && + obj->options->done_parsing_outer_headers && + obj->options->decompose_file_output_fn ) + { + status = obj->options->decompose_file_output_fn( line, length, + obj->options->stream_closure ); + if (status < 0) + return status; + } +#endif /* MIME_DRAFTS */ + + status = MimeHeaders_parse_line(line, length, msg->hdrs); + if (status < 0) return status; + + /* If this line is blank, we're now done parsing headers, and should + examine our content-type to create our "body" part. + */ + if (*line == '\r' || *line == '\n') + { + status = MimeMessage_close_headers(obj); + if (status < 0) return status; + } + + return 0; +} + +static int +MimeMessage_close_headers (MimeObject *obj) +{ + MimeMessage *msg = (MimeMessage *) obj; + int status = 0; + char *ct = 0; /* Content-Type header */ + MimeObject *body; + + // Do a proper decoding of the munged subject. + if (obj->headers && msg->hdrs && msg->grabSubject && obj->headers->munged_subject) { + // nsMsgI18NConvertToUnicode wants nsAStrings... + nsDependentCString orig(obj->headers->munged_subject); + nsAutoString dest; + // First, get the Content-Type, then extract the charset="whatever" part of + // it. + nsCString charset; + nsCString contentType; + contentType.Adopt(MimeHeaders_get(msg->hdrs, HEADER_CONTENT_TYPE, false, false)); + if (!contentType.IsEmpty()) + charset.Adopt(MimeHeaders_get_parameter(contentType.get(), "charset", nullptr, nullptr)); + + // If we've got a charset, use nsMsgI18NConvertToUnicode to magically decode + // the munged subject. + if (!charset.IsEmpty()) { + nsresult rv = nsMsgI18NConvertToUnicode(charset.get(), orig, dest); + // If we managed to convert the string, replace munged_subject with the + // UTF8 version of it, otherwise, just forget about it (maybe there was an + // improperly encoded string in there). + PR_Free(obj->headers->munged_subject); + if (NS_SUCCEEDED(rv)) + obj->headers->munged_subject = ToNewUTF8String(dest); + else + obj->headers->munged_subject = nullptr; + } else { + PR_Free(obj->headers->munged_subject); + obj->headers->munged_subject = nullptr; + } + } + + if (msg->hdrs) + { + bool outer_p = !obj->headers; /* is this the outermost message? */ + + +#ifdef MIME_DRAFTS + if (outer_p && + obj->options && + (obj->options->decompose_file_p || obj->options->caller_need_root_headers) && + obj->options->decompose_headers_info_fn) + { +#ifdef ENABLE_SMIME + if (obj->options->decrypt_p && !mime_crypto_object_p(msg->hdrs, false, obj->options)) + obj->options->decrypt_p = false; +#endif /* ENABLE_SMIME */ + if (!obj->options->caller_need_root_headers || (obj == obj->options->state->root)) + status = obj->options->decompose_headers_info_fn ( + obj->options->stream_closure, + msg->hdrs ); + } +#endif /* MIME_DRAFTS */ + + + /* If this is the outermost message, we need to run the + `generate_header' callback. This happens here instead of + in `parse_begin', because it's only now that we've parsed + our headers. However, since this is the outermost message, + we have yet to write any HTML, so that's fine. + */ + if (outer_p && + obj->output_p && + obj->options && + obj->options->write_html_p && + obj->options->generate_header_html_fn) + { + int lstatus = 0; + char *html = 0; + + /* The generate_header_html_fn might return HTML, so it's important + that the output stream be set up with the proper type before we + make the MimeObject_write() call below. */ + if (!obj->options->state->first_data_written_p) + { + lstatus = MimeObject_output_init (obj, TEXT_HTML); + if (lstatus < 0) return lstatus; + PR_ASSERT(obj->options->state->first_data_written_p); + } + + html = obj->options->generate_header_html_fn(NULL, + obj->options->html_closure, + msg->hdrs); + if (html) + { + lstatus = MimeObject_write(obj, html, strlen(html), false); + PR_Free(html); + if (lstatus < 0) return lstatus; + } + } + + + /* Find the content-type of the body of this message. + */ + { + bool ok = true; + char *mv = MimeHeaders_get (msg->hdrs, HEADER_MIME_VERSION, + true, false); + +#ifdef REQUIRE_MIME_VERSION_HEADER + /* If this is the outermost message, it must have a MIME-Version + header with the value 1.0 for us to believe what might be in + the Content-Type header. If the MIME-Version header is not + present, we must treat this message as untyped. + */ + ok = (mv && !strcmp(mv, "1.0")); +#else + /* #### actually, we didn't check this in Mozilla 2.0, and checking + it now could cause some compatibility nonsense, so for now, let's + just believe any Content-Type header we see. + */ + ok = true; +#endif + + if (ok) + { + ct = MimeHeaders_get (msg->hdrs, HEADER_CONTENT_TYPE, true, false); + + /* If there is no Content-Type header, but there is a MIME-Version + header, then assume that this *is* in fact a MIME message. + (I've seen messages with + + MIME-Version: 1.0 + Content-Transfer-Encoding: quoted-printable + + and no Content-Type, and we should treat those as being of type + MimeInlineTextPlain rather than MimeUntypedText.) + */ + if (mv && !ct) + ct = strdup(TEXT_PLAIN); + } + + PR_FREEIF(mv); /* done with this now. */ + } + + /* If this message has a body which is encrypted and we're going to + decrypt it (whithout converting it to HTML, since decrypt_p and + write_html_p are never true at the same time) + */ + if (obj->output_p && + obj->options && + obj->options->decrypt_p +#ifdef ENABLE_SMIME + && !mime_crypto_object_p(msg->hdrs, false, obj->options) +#endif /* ENABLE_SMIME */ + ) + { + /* The body of this message is not an encrypted object, so we need + to turn off the decrypt_p flag (to prevent us from s#$%ing the + body of the internal object up into one.) In this case, + our output will end up being identical to our input. + */ + obj->options->decrypt_p = false; + } + + /* Emit the HTML for this message's headers. Do this before + creating the object representing the body. + */ + if (obj->output_p && + obj->options && + obj->options->write_html_p) + { + /* If citation headers are on, and this is not the outermost message, + turn them off. */ + if (obj->options->headers == MimeHeadersCitation && !outer_p) + obj->options->headers = MimeHeadersSome; + + /* Emit a normal header block. */ + status = MimeMessage_write_headers_html(obj); + if (status < 0) + { + PR_FREEIF(ct); + return status; + } + } + else if (obj->output_p) + { + /* Dump the headers, raw. */ + status = MimeObject_write(obj, "", 0, false); /* initialize */ + if (status < 0) + { + PR_FREEIF(ct); + return status; + } + status = MimeHeaders_write_raw_headers(msg->hdrs, obj->options, + obj->options->decrypt_p); + if (status < 0) + { + PR_FREEIF(ct); + return status; + } + } + +#ifdef XP_UNIX + if (outer_p && obj->output_p) + /* Kludge from mimehdrs.c */ + MimeHeaders_do_unix_display_hook_hack(msg->hdrs); +#endif /* XP_UNIX */ + } + + /* Never put out a separator after a message header block. */ + if (obj->options && obj->options->state) + obj->options->state->separator_suppressed_p = true; + +#ifdef MIME_DRAFTS + if ( !obj->headers && /* outer most message header */ + obj->options && + obj->options->decompose_file_p && + ct ) + obj->options->is_multipart_msg = PL_strcasestr(ct, "multipart/") != NULL; +#endif /* MIME_DRAFTS */ + + + body = mime_create(ct, msg->hdrs, obj->options); + + PR_FREEIF(ct); + if (!body) return MIME_OUT_OF_MEMORY; + status = ((MimeContainerClass *) obj->clazz)->add_child (obj, body); + if (status < 0) + { + mime_free(body); + return status; + } + + // Only do this if this is a Text Object! + if ( mime_typep(body, (MimeObjectClass *) &mimeInlineTextClass) ) + { + ((MimeInlineText *) body)->needUpdateMsgWinCharset = true; + } + + /* Now that we've added this new object to our list of children, + start its parser going. */ + status = body->clazz->parse_begin(body); + if (status < 0) return status; + + // Now notify the emitter if this is the outer most message, unless + // it is a part that is not the head of the message. If it's a part, + // we need to figure out the content type/charset of the part + // + bool outer_p = !obj->headers; /* is this the outermost message? */ + + if ( (outer_p || obj->options->notify_nested_bodies) && + (!obj->options->part_to_load || obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)) + { + // call SetMailCharacterSetToMsgWindow() to set a menu charset + if (mime_typep(body, (MimeObjectClass *) &mimeInlineTextClass)) + { + MimeInlineText *text = (MimeInlineText *) body; + if (text && text->charset && *text->charset) + SetMailCharacterSetToMsgWindow(body, text->charset); + } + + char *msgID = MimeHeaders_get (msg->hdrs, HEADER_MESSAGE_ID, + false, false); + + const char *outCharset = NULL; + if (!obj->options->force_user_charset) /* Only convert if the user prefs is false */ + outCharset = "UTF-8"; + + mimeEmitterStartBody(obj->options, (obj->options->headers == MimeHeadersNone), msgID, outCharset); + PR_FREEIF(msgID); + + // setting up truncated message html fotter function + char *xmoz = MimeHeaders_get(msg->hdrs, HEADER_X_MOZILLA_STATUS, false, + false); + if (xmoz) + { + uint32_t flags = 0; + char dummy = 0; + if (sscanf(xmoz, " %x %c", &flags, &dummy) == 1 && + flags & nsMsgMessageFlags::Partial) + { + obj->options->html_closure = obj; + obj->options->generate_footer_html_fn = + MimeMessage_partial_message_html; + } + PR_FREEIF(xmoz); + } + } + + return 0; +} + + + +static int +MimeMessage_parse_eof (MimeObject *obj, bool abort_p) +{ + int status; + bool outer_p; + MimeMessage *msg = (MimeMessage *)obj; + if (obj->closed_p) return 0; + + /* Run parent method first, to flush out any buffered data. */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + + outer_p = !obj->headers; /* is this the outermost message? */ + + // Hack for messages with truncated headers (bug 244722) + // If there is no empty line in a message, the parser can't figure out where + // the headers end, causing parsing to hang. So we insert an extra newline + // to keep it happy. This is OK, since a message without any empty lines is + // broken anyway... + if(outer_p && msg->hdrs && ! msg->hdrs->done_p) { + MimeMessage_parse_line("\n", 1, obj); + } + + // Once we get to the end of parsing the message, we will notify + // the emitter that we are done the the body. + + // Mark the end of the mail body if we are actually emitting the + // body of the message (i.e. not Header ONLY) + if ((outer_p || obj->options->notify_nested_bodies) && obj->options && + obj->options->write_html_p) + { + if (obj->options->generate_footer_html_fn) + { + mime_stream_data *msd = + (mime_stream_data *) obj->options->stream_closure; + if (msd) + { + char *html = obj->options->generate_footer_html_fn + (msd->orig_url_name, obj->options->html_closure, msg->hdrs); + if (html) + { + int lstatus = MimeObject_write(obj, html, + strlen(html), + false); + PR_Free(html); + if (lstatus < 0) return lstatus; + } + } + } + if ((!obj->options->part_to_load || obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) && + obj->options->headers != MimeHeadersOnly) + mimeEmitterEndBody(obj->options); + } + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + obj->options->done_parsing_outer_headers && + ! obj->options->is_multipart_msg && + ! mime_typep(obj, (MimeObjectClass*) &mimeEncryptedClass) && + obj->options->decompose_file_close_fn ) { + status = obj->options->decompose_file_close_fn ( + obj->options->stream_closure ); + + if ( status < 0 ) return status; + } +#endif /* MIME_DRAFTS */ + + + /* Put out a separator after every message/rfc822 object. */ + if (!abort_p && !outer_p) + { + status = MimeObject_write_separator(obj); + if (status < 0) return status; + } + + return 0; +} + + +static int +MimeMessage_add_child (MimeObject *parent, MimeObject *child) +{ + MimeContainer *cont = (MimeContainer *) parent; + PR_ASSERT(parent && child); + if (!parent || !child) return -1; + + /* message/rfc822 containers can only have one child. */ + PR_ASSERT(cont->nchildren == 0); + if (cont->nchildren != 0) return -1; + +#ifdef MIME_DRAFTS + if ( parent->options && + parent->options->decompose_file_p && + ! parent->options->is_multipart_msg && + ! mime_typep(child, (MimeObjectClass*) &mimeEncryptedClass) && + parent->options->decompose_file_init_fn ) { + int status = 0; + status = parent->options->decompose_file_init_fn ( + parent->options->stream_closure, + ((MimeMessage*)parent)->hdrs ); + if ( status < 0 ) return status; + } +#endif /* MIME_DRAFTS */ + + return ((MimeContainerClass*)&MIME_SUPERCLASS)->add_child (parent, child); +} + +// This is necessary to determine which charset to use for a reply/forward +char * +DetermineMailCharset(MimeMessage *msg) +{ + char *retCharset = nullptr; + + if ( (msg) && (msg->hdrs) ) + { + char *ct = MimeHeaders_get (msg->hdrs, HEADER_CONTENT_TYPE, + false, false); + if (ct) + { + retCharset = MimeHeaders_get_parameter (ct, "charset", NULL, NULL); + PR_Free(ct); + } + + if (!retCharset) + { + // If we didn't find "Content-Type: ...; charset=XX" then look + // for "X-Sun-Charset: XX" instead. (Maybe this should be done + // in MimeSunAttachmentClass, but it's harder there than here.) + retCharset = MimeHeaders_get (msg->hdrs, HEADER_X_SUN_CHARSET, + false, false); + } + } + + if (!retCharset) + return strdup("ISO-8859-1"); + else + return retCharset; +} + +static int +MimeMessage_write_headers_html (MimeObject *obj) +{ + MimeMessage *msg = (MimeMessage *) obj; + int status; + + if (!obj->options || !obj->options->output_fn) + return 0; + + PR_ASSERT(obj->output_p && obj->options->write_html_p); + + // To support the no header option! Make sure we are not + // suppressing headers on included email messages... + if ( (obj->options->headers == MimeHeadersNone) && + (obj == obj->options->state->root) ) + { + // Ok, we are going to kick the Emitter for a StartHeader + // operation ONLY WHEN THE CHARSET OF THE ORIGINAL MESSAGE IS + // NOT US-ASCII ("ISO-8859-1") + // + // This is only to notify the emitter of the charset of the + // original message + char *mailCharset = DetermineMailCharset(msg); + + if ( (mailCharset) && (PL_strcasecmp(mailCharset, "US-ASCII")) && + (PL_strcasecmp(mailCharset, "ISO-8859-1")) ) + mimeEmitterUpdateCharacterSet(obj->options, mailCharset); + PR_FREEIF(mailCharset); + return 0; + } + + if (!obj->options->state->first_data_written_p) + { + status = MimeObject_output_init (obj, TEXT_HTML); + if (status < 0) + { + mimeEmitterEndHeader(obj->options, obj); + return status; + } + PR_ASSERT(obj->options->state->first_data_written_p); + } + + // Start the header parsing by the emitter + char *msgID = MimeHeaders_get (msg->hdrs, HEADER_MESSAGE_ID, + false, false); + bool outer_p = !obj->headers; /* is this the outermost message? */ + if (!outer_p && obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay && + obj->options->part_to_load) + { + //Maybe we are displaying a embedded message as outer part! + char *id = mime_part_address(obj); + if (id) + { + outer_p = !strcmp(id, obj->options->part_to_load); + PR_Free(id); + } + } + + // Ok, we should really find out the charset of this part. We always + // output UTF-8 for display, but the original charset is necessary for + // reply and forward operations. + // + char *mailCharset = DetermineMailCharset(msg); + mimeEmitterStartHeader(obj->options, + outer_p, + (obj->options->headers == MimeHeadersOnly), + msgID, + mailCharset); + + // Change the default_charset by the charset of the original message + // ONLY WHEN THE CHARSET OF THE ORIGINAL MESSAGE IS NOT US-ASCII + // ("ISO-8859-1") and default_charset and mailCharset are different, + // or when there is no default_charset (this can happen with saved messages). + if ( (!obj->options->default_charset || + ((mailCharset) && (PL_strcasecmp(mailCharset, "US-ASCII")) && + (PL_strcasecmp(mailCharset, "ISO-8859-1")) && + (PL_strcasecmp(obj->options->default_charset, mailCharset)))) && + !obj->options->override_charset ) + { + PR_FREEIF(obj->options->default_charset); + obj->options->default_charset = strdup(mailCharset); + } + + PR_FREEIF(msgID); + PR_FREEIF(mailCharset); + + status = MimeHeaders_write_all_headers (msg->hdrs, obj->options, false); + if (status < 0) + { + mimeEmitterEndHeader(obj->options, obj); + return status; + } + + if (!msg->crypto_stamped_p) + { + /* If we're not writing a xlation stamp, and this is the outermost + message, then now is the time to run the post_header_html_fn. + (Otherwise, it will be run when the xlation-stamp is finally + closed off, in MimeXlateed_emit_buffered_child() or + MimeMultipartSigned_emit_child().) + */ + if (obj->options && + obj->options->state && + obj->options->generate_post_header_html_fn && + !obj->options->state->post_header_html_run_p) + { + char *html = 0; + PR_ASSERT(obj->options->state->first_data_written_p); + html = obj->options->generate_post_header_html_fn(NULL, + obj->options->html_closure, + msg->hdrs); + obj->options->state->post_header_html_run_p = true; + if (html) + { + status = MimeObject_write(obj, html, strlen(html), false); + PR_Free(html); + if (status < 0) + { + mimeEmitterEndHeader(obj->options, obj); + return status; + } + } + } + } + + mimeEmitterEndHeader(obj->options, obj); + + // rhp: + // For now, we are going to parse the entire message, even if we are + // only interested in headers...why? Well, because this is the only + // way to build the attachment list. Now we will have the attachment + // list in the output being created by the XML emitter. If we ever + // want to go back to where we were before, just uncomment the conditional + // and it will stop at header parsing. + // + // if (obj->options->headers == MimeHeadersOnly) + // return -1; + // else + + return 0; +} + +static char * +MimeMessage_partial_message_html(const char *data, void *closure, + MimeHeaders *headers) +{ + MimeMessage *msg = (MimeMessage *)closure; + nsAutoCString orig_url(data); + char *uidl = MimeHeaders_get(headers, HEADER_X_UIDL, false, false); + char *msgId = MimeHeaders_get(headers, HEADER_MESSAGE_ID, false, + false); + char *msgIdPtr = PL_strchr(msgId, '<'); + + int32_t pos = orig_url.Find("mailbox-message"); + if (pos != -1) + orig_url.Cut(pos + 7, 8); + + pos = orig_url.FindChar('#'); + if (pos != -1) + orig_url.Replace(pos, 1, "?number=", 8); + + if (msgIdPtr) + msgIdPtr++; + else + msgIdPtr = msgId; + char *gtPtr = PL_strchr(msgIdPtr, '>'); + if (gtPtr) + *gtPtr = 0; + + bool msgBaseTruncated = (msg->bodyLength > MSG_LINEBREAK_LEN); + + nsCString partialMsgHtml; + nsCString item; + + partialMsgHtml.AppendLiteral("<div style=\"margin: 1em auto; border: 1px solid black; width: 80%\">"); + partialMsgHtml.AppendLiteral("<div style=\"margin: 5px; padding: 10px; border: 1px solid gray; font-weight: bold; text-align: center;\">"); + + partialMsgHtml.AppendLiteral("<span style=\"font-size: 120%;\">"); + if (msgBaseTruncated) + item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_TRUNCATED")); + else + item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_NOT_DOWNLOADED")); + partialMsgHtml += item; + partialMsgHtml.AppendLiteral("</span><hr>"); + + if (msgBaseTruncated) + item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_TRUNCATED_EXPLANATION")); + else + item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_NOT_DOWNLOADED_EXPLANATION")); + partialMsgHtml += item; + partialMsgHtml.AppendLiteral("<br><br>"); + + partialMsgHtml.AppendLiteral("<a href=\""); + partialMsgHtml.Append(orig_url); + + if (msgIdPtr) { + partialMsgHtml.AppendLiteral("&messageid="); + + MsgEscapeString(nsDependentCString(msgIdPtr), nsINetUtil::ESCAPE_URL_PATH, + item); + + partialMsgHtml.Append(item); + } + + if (uidl) { + partialMsgHtml.AppendLiteral("&uidl="); + + MsgEscapeString(nsDependentCString(uidl), nsINetUtil::ESCAPE_XALPHAS, + item); + + partialMsgHtml.Append(item); + } + + partialMsgHtml.AppendLiteral("\">"); + item.Adopt(MimeGetStringByName(u"MIME_MSG_PARTIAL_CLICK_FOR_REST")); + partialMsgHtml += item; + partialMsgHtml.AppendLiteral("</a>"); + + partialMsgHtml.AppendLiteral("</div></div>"); + + return ToNewCString(partialMsgHtml); +} + +#if defined(DEBUG) && defined(XP_UNIX) +static int +MimeMessage_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth) +{ + MimeMessage *msg = (MimeMessage *) obj; + char *addr = mime_part_address(obj); + int i; + for (i=0; i < depth; i++) + PR_Write(stream, " ", 2); +/* + fprintf(stream, "<%s %s%s 0x%08X>\n", + obj->clazz->class_name, + addr ? addr : "???", + (msg->container.nchildren == 0 ? " (no body)" : ""), + (uint32_t) msg); +*/ + PR_FREEIF(addr); + +#if 0 + if (msg->hdrs) + { + char *s; + + depth++; + +# define DUMP(HEADER) \ + for (i=0; i < depth; i++) \ + PR_Write(stream, " ", 2); \ + s = MimeHeaders_get (msg->hdrs, HEADER, false, true); +/** + \ + PR_Write(stream, HEADER ": %s\n", s ? s : ""); \ +**/ + + PR_FREEIF(s) + + DUMP(HEADER_SUBJECT); + DUMP(HEADER_DATE); + DUMP(HEADER_FROM); + DUMP(HEADER_TO); + /* DUMP(HEADER_CC); */ + DUMP(HEADER_NEWSGROUPS); + DUMP(HEADER_MESSAGE_ID); +# undef DUMP + + PR_Write(stream, "\n", 1); + } +#endif + + PR_ASSERT(msg->container.nchildren <= 1); + if (msg->container.nchildren == 1) + { + MimeObject *kid = msg->container.children[0]; + int status = kid->clazz->debug_print (kid, stream, depth+1); + if (status < 0) return status; + } + return 0; +} +#endif diff --git a/mailnews/mime/src/mimemsg.h b/mailnews/mime/src/mimemsg.h new file mode 100644 index 000000000..c4023ccf0 --- /dev/null +++ b/mailnews/mime/src/mimemsg.h @@ -0,0 +1,43 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMSG_H_ +#define _MIMEMSG_H_ + +#include "mimecont.h" + +/* The MimeMessage class implements the message/rfc822 and message/news + MIME containers, which is to say, mail and news messages. + */ + +typedef struct MimeMessageClass MimeMessageClass; +typedef struct MimeMessage MimeMessage; + +struct MimeMessageClass { + MimeContainerClass container; +}; + +extern MimeMessageClass mimeMessageClass; + +struct MimeMessage { + MimeContainer container; /* superclass variables */ + MimeHeaders *hdrs; /* headers of this message */ + bool newline_p; /* whether the last line ended in a newline */ + bool crypto_stamped_p; /* whether the header of this message has been + emitted expecting its child to emit HTML + which says that it is xlated. */ + + bool crypto_msg_signed_p; /* What the emitted xlation-stamp *says*. */ + bool crypto_msg_encrypted_p; + bool grabSubject; /* Should we try to grab the subject of this message */ + int32_t bodyLength; /* Used for determining if the body has been truncated */ + int32_t sizeSoFar; /* The total size of the MIME message, once parsing is + finished. */ +}; + +#define MimeMessageClassInitializer(ITYPE,CSUPER) \ + { MimeContainerClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMSG_H_ */ diff --git a/mailnews/mime/src/mimemsig.cpp b/mailnews/mime/src/mimemsig.cpp new file mode 100644 index 000000000..d74cfb09a --- /dev/null +++ b/mailnews/mime/src/mimemsig.cpp @@ -0,0 +1,775 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "modmimee.h" +#include "mimemsig.h" +#include "nspr.h" + +#include "prmem.h" +#include "plstr.h" +#include "prerror.h" +#include "nsMimeTypes.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include "nsIMimeConverter.h" // for MimeConverterOutputCallback +#include "mozilla/Attributes.h" + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeMultipartSigned, MimeMultipartSignedClass, + mimeMultipartSignedClass, &MIME_SUPERCLASS); + +static int MimeMultipartSigned_initialize (MimeObject *); +static int MimeMultipartSigned_create_child (MimeObject *); +static int MimeMultipartSigned_close_child(MimeObject *); +static int MimeMultipartSigned_parse_line (const char *, int32_t, MimeObject *); +static int MimeMultipartSigned_parse_child_line (MimeObject *, const char *, int32_t, + bool); +static int MimeMultipartSigned_parse_eof (MimeObject *, bool); +static void MimeMultipartSigned_finalize (MimeObject *); + +static int MimeMultipartSigned_emit_child (MimeObject *obj); + +static int +MimeMultipartSignedClassInitialize(MimeMultipartSignedClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + + oclass->initialize = MimeMultipartSigned_initialize; + oclass->parse_line = MimeMultipartSigned_parse_line; + oclass->parse_eof = MimeMultipartSigned_parse_eof; + oclass->finalize = MimeMultipartSigned_finalize; + mclass->create_child = MimeMultipartSigned_create_child; + mclass->parse_child_line = MimeMultipartSigned_parse_child_line; + mclass->close_child = MimeMultipartSigned_close_child; + + PR_ASSERT(!oclass->class_initialized); + return 0; +} + +static int +MimeMultipartSigned_initialize (MimeObject *object) +{ + MimeMultipartSigned *sig = (MimeMultipartSigned *) object; + + /* This is an abstract class; it shouldn't be directly instantiated. */ + PR_ASSERT(object->clazz != (MimeObjectClass *) &mimeMultipartSignedClass); + + sig->state = MimeMultipartSignedPreamble; + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeMultipartSigned_cleanup (MimeObject *obj, bool finalizing_p) +{ + MimeMultipart *mult = (MimeMultipart *) obj; /* #58075. Fix suggested by jwz */ + MimeMultipartSigned *sig = (MimeMultipartSigned *) obj; + if (sig->part_buffer) + { + MimePartBufferDestroy(sig->part_buffer); + sig->part_buffer = 0; + } + if (sig->body_hdrs) + { + MimeHeaders_free (sig->body_hdrs); + sig->body_hdrs = 0; + } + if (sig->sig_hdrs) + { + MimeHeaders_free (sig->sig_hdrs); + sig->sig_hdrs = 0; + } + + mult->state = MimeMultipartEpilogue; /* #58075. Fix suggested by jwz */ + sig->state = MimeMultipartSignedEpilogue; + + if (finalizing_p && sig->crypto_closure) { + /* Don't free these until this object is really going away -- keep them + around for the lifetime of the MIME object, so that we can get at the + security info of sub-parts of the currently-displayed message. */ + ((MimeMultipartSignedClass *) obj->clazz)->crypto_free (sig->crypto_closure); + sig->crypto_closure = 0; + } + + if (sig->sig_decoder_data) + { + MimeDecoderDestroy(sig->sig_decoder_data, true); + sig->sig_decoder_data = 0; + } +} + +static int +MimeMultipartSigned_parse_eof (MimeObject *obj, bool abort_p) +{ + MimeMultipartSigned *sig = (MimeMultipartSigned *) obj; + int status = 0; + + if (obj->closed_p) return 0; + + /* Close off the signature, if we've gotten that far. + */ + if (sig->state == MimeMultipartSignedSignatureHeaders || + sig->state == MimeMultipartSignedSignatureFirstLine || + sig->state == MimeMultipartSignedSignatureLine || + sig->state == MimeMultipartSignedEpilogue) + { + status = (((MimeMultipartSignedClass *) obj->clazz)->crypto_signature_eof) (sig->crypto_closure, abort_p); + if (status < 0) return status; + } + + if (!abort_p) + { + /* Now that we've read both the signed object and the signature (and + have presumably verified the signature) write out a blurb, and then + the signed object. + */ + status = MimeMultipartSigned_emit_child(obj); + if (status < 0) return status; + } + + MimeMultipartSigned_cleanup(obj, false); + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); +} + + +static void +MimeMultipartSigned_finalize (MimeObject *obj) +{ + MimeMultipartSigned_cleanup(obj, true); + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj); +} + + +static int +MimeMultipartSigned_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + MimeMultipartSigned *sig = (MimeMultipartSigned *) obj; + MimeMultipartParseState old_state = mult->state; + bool hash_line_p = true; + bool no_headers_p = false; + int status = 0; + + /* First do the parsing for normal multipart/ objects by handing it off to + the superclass method. This includes calling the create_child and + close_child methods. + */ + status = (((MimeObjectClass *)(&MIME_SUPERCLASS)) + ->parse_line (line, length, obj)); + if (status < 0) return status; + + /* The instance variable MimeMultipartClass->state tracks motion through + the various stages of multipart/ parsing. The instance variable + MimeMultipartSigned->state tracks the difference between the first + part (the body) and the second part (the signature.) This second, + more specific state variable is updated by noticing the transitions + of the first, more general state variable. + */ + if (old_state != mult->state) /* there has been a state change */ + { + switch (mult->state) + { + case MimeMultipartPreamble: + PR_ASSERT(0); /* can't move *in* to preamble state. */ + sig->state = MimeMultipartSignedPreamble; + break; + + case MimeMultipartHeaders: + /* If we're moving in to the Headers state, then that means + that this line is the preceeding boundary string (and we + should ignore it.) + */ + hash_line_p = false; + + if (sig->state == MimeMultipartSignedPreamble) + sig->state = MimeMultipartSignedBodyFirstHeader; + else if (sig->state == MimeMultipartSignedBodyFirstLine || + sig->state == MimeMultipartSignedBodyLine) + sig->state = MimeMultipartSignedSignatureHeaders; + else if (sig->state == MimeMultipartSignedSignatureFirstLine || + sig->state == MimeMultipartSignedSignatureLine) + sig->state = MimeMultipartSignedEpilogue; + break; + + case MimeMultipartPartFirstLine: + if (sig->state == MimeMultipartSignedBodyFirstHeader) + { + sig->state = MimeMultipartSignedBodyFirstLine; + no_headers_p = true; + } + else if (sig->state == MimeMultipartSignedBodyHeaders) + sig->state = MimeMultipartSignedBodyFirstLine; + else if (sig->state == MimeMultipartSignedSignatureHeaders) + sig->state = MimeMultipartSignedSignatureFirstLine; + else + sig->state = MimeMultipartSignedEpilogue; + break; + + case MimeMultipartPartLine: + + PR_ASSERT(sig->state == MimeMultipartSignedBodyFirstLine || + sig->state == MimeMultipartSignedBodyLine || + sig->state == MimeMultipartSignedSignatureFirstLine || + sig->state == MimeMultipartSignedSignatureLine); + + if (sig->state == MimeMultipartSignedBodyFirstLine) + sig->state = MimeMultipartSignedBodyLine; + else if (sig->state == MimeMultipartSignedSignatureFirstLine) + sig->state = MimeMultipartSignedSignatureLine; + break; + + case MimeMultipartEpilogue: + sig->state = MimeMultipartSignedEpilogue; + break; + + default: /* bad state */ + NS_ERROR("bad state in MultipartSigned parse line"); + return -1; + break; + } + } + + + /* Perform multipart/signed-related actions on this line based on the state + of the parser. + */ + switch (sig->state) + { + case MimeMultipartSignedPreamble: + /* Do nothing. */ + break; + + case MimeMultipartSignedBodyFirstLine: + /* We have just moved out of the MimeMultipartSignedBodyHeaders + state, so cache away the headers that apply only to the body part. + */ + NS_ASSERTION(mult->hdrs, "null multipart hdrs"); + NS_ASSERTION(!sig->body_hdrs, "signed part shouldn't have already have body_hdrs"); + sig->body_hdrs = mult->hdrs; + mult->hdrs = 0; + + /* fall through. */ + MOZ_FALLTHROUGH; + case MimeMultipartSignedBodyFirstHeader: + case MimeMultipartSignedBodyHeaders: + case MimeMultipartSignedBodyLine: + + if (!sig->crypto_closure) + { + /* Set error change */ + PR_SetError(0, 0); + /* Initialize the signature verification library. */ + sig->crypto_closure = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_init) (obj); + if (!sig->crypto_closure) + { + status = PR_GetError(); + NS_ASSERTION(status < 0, "got non-negative status"); + if (status >= 0) + status = -1; + return status; + } + } + + if (hash_line_p) + { + /* this is the first hashed line if this is the first header + (that is, if it's the first line in the header state after + a state change.) + */ + bool first_line_p + = (no_headers_p || + sig->state == MimeMultipartSignedBodyFirstHeader); + + if (sig->state == MimeMultipartSignedBodyFirstHeader) + sig->state = MimeMultipartSignedBodyHeaders; + + /* The newline issues here are tricky, since both the newlines + before and after the boundary string are to be considered part + of the boundary: this is so that a part can be specified such + that it does not end in a trailing newline. + + To implement this, we send a newline *before* each line instead + of after, except for the first line, which is not preceeded by a + newline. + + For purposes of cryptographic hashing, we always hash line + breaks as CRLF -- the canonical, on-the-wire linebreaks, since + we have no idea of knowing what line breaks were used on the + originating system (SMTP rightly destroys that information.) + */ + + /* Remove the trailing newline... */ + if (length > 0 && line[length-1] == '\n') length--; + if (length > 0 && line[length-1] == '\r') length--; + + PR_ASSERT(sig->crypto_closure); + + if (!first_line_p) + { + /* Push out a preceeding newline... */ + char nl[] = CRLF; + status = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_data_hash (nl, 2, sig->crypto_closure)); + if (status < 0) return status; + } + + /* Now push out the line sans trailing newline. */ + if (length > 0) + status = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_data_hash (line,length, sig->crypto_closure)); + if (status < 0) return status; + } + break; + + case MimeMultipartSignedSignatureHeaders: + + if (sig->crypto_closure && + old_state != mult->state) + { + /* We have just moved out of the MimeMultipartSignedBodyLine + state, so tell the signature verification library that we've + reached the end of the signed data. + */ + status = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_data_eof) (sig->crypto_closure, false); + if (status < 0) return status; + } + break; + + case MimeMultipartSignedSignatureFirstLine: + /* We have just moved out of the MimeMultipartSignedSignatureHeaders + state, so cache away the headers that apply only to the sig part. + */ + PR_ASSERT(mult->hdrs); + PR_ASSERT(!sig->sig_hdrs); + sig->sig_hdrs = mult->hdrs; + mult->hdrs = 0; + + + /* If the signature block has an encoding, set up a decoder for it. + (Similar logic is in MimeLeafClass->parse_begin.) + */ + { + MimeDecoderData *(*fn) (MimeConverterOutputCallback, void*) = 0; + nsCString encoding; + encoding.Adopt(MimeHeaders_get (sig->sig_hdrs, + HEADER_CONTENT_TRANSFER_ENCODING, + true, false)); + if (encoding.IsEmpty()) + ; + else if (!PL_strcasecmp(encoding.get(), ENCODING_BASE64)) + fn = &MimeB64DecoderInit; + else if (!PL_strcasecmp(encoding.get(), ENCODING_QUOTED_PRINTABLE)) + { + sig->sig_decoder_data = + MimeQPDecoderInit (((MimeConverterOutputCallback) + (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_signature_hash)), + sig->crypto_closure); + if (!sig->sig_decoder_data) + return MIME_OUT_OF_MEMORY; + } + else if (!PL_strcasecmp(encoding.get(), ENCODING_UUENCODE) || + !PL_strcasecmp(encoding.get(), ENCODING_UUENCODE2) || + !PL_strcasecmp(encoding.get(), ENCODING_UUENCODE3) || + !PL_strcasecmp(encoding.get(), ENCODING_UUENCODE4)) + fn = &MimeUUDecoderInit; + else if (!PL_strcasecmp(encoding.get(), ENCODING_YENCODE)) + fn = &MimeYDecoderInit; + if (fn) + { + sig->sig_decoder_data = + fn (((MimeConverterOutputCallback) + (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_signature_hash)), + sig->crypto_closure); + if (!sig->sig_decoder_data) + return MIME_OUT_OF_MEMORY; + } + } + + /* Show these headers to the crypto module. */ + if (hash_line_p) + { + status = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_signature_init) (sig->crypto_closure, + obj, sig->sig_hdrs); + if (status < 0) return status; + } + + /* fall through. */ + MOZ_FALLTHROUGH; + case MimeMultipartSignedSignatureLine: + if (hash_line_p) + { + /* Feed this line into the signature verification routines. */ + + if (sig->sig_decoder_data) + status = MimeDecoderWrite (sig->sig_decoder_data, line, length, nullptr); + else + status = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_signature_hash (line, length, + sig->crypto_closure)); + if (status < 0) return status; + } + break; + + case MimeMultipartSignedEpilogue: + /* Nothing special to do here. */ + break; + + default: /* bad state */ + PR_ASSERT(0); + return -1; + } + + return status; +} + + +static int +MimeMultipartSigned_create_child (MimeObject *parent) +{ + /* Don't actually create a child -- we call the superclass create_child + method later, after we've fully parsed everything. (And we only call + it once, for part #1, and never for part #2 (the signature.)) + */ + MimeMultipart *mult = (MimeMultipart *) parent; + mult->state = MimeMultipartPartFirstLine; + return 0; +} + + +static int +MimeMultipartSigned_close_child (MimeObject *obj) +{ + /* The close_child method on MimeMultipartSigned doesn't actually do + anything to the children list, since the create_child method also + doesn't do anything. + */ + MimeMultipart *mult = (MimeMultipart *) obj; + MimeContainer *cont = (MimeContainer *) obj; + MimeMultipartSigned *msig = (MimeMultipartSigned *) obj; + + if (msig->part_buffer) + /* Closes the tmp file, if there is one: doesn't free the part_buffer. */ + MimePartBufferClose(msig->part_buffer); + + if (mult->hdrs) /* duplicated from MimeMultipart_close_child, ugh. */ + { + MimeHeaders_free(mult->hdrs); + mult->hdrs = 0; + } + + /* Should be no kids yet. */ + PR_ASSERT(cont->nchildren == 0); + if (cont->nchildren != 0) return -1; + + return 0; +} + + +static int +MimeMultipartSigned_parse_child_line (MimeObject *obj, + const char *line, int32_t length, + bool first_line_p) +{ + MimeMultipartSigned *sig = (MimeMultipartSigned *) obj; + MimeContainer *cont = (MimeContainer *) obj; + int status = 0; + + /* Shouldn't have made any sub-parts yet. */ + PR_ASSERT(cont->nchildren == 0); + if (cont->nchildren != 0) return -1; + + switch (sig->state) + { + case MimeMultipartSignedPreamble: + case MimeMultipartSignedBodyFirstHeader: + case MimeMultipartSignedBodyHeaders: + // How'd we get here? Oh well, fall through. + NS_ERROR("wrong state in parse child line"); + MOZ_FALLTHROUGH; + case MimeMultipartSignedBodyFirstLine: + PR_ASSERT(first_line_p); + if (!sig->part_buffer) + { + sig->part_buffer = MimePartBufferCreate(); + if (!sig->part_buffer) + return MIME_OUT_OF_MEMORY; + } + /* fall through */ + MOZ_FALLTHROUGH; + case MimeMultipartSignedBodyLine: + { + /* This is the first part; we are buffering it, and will emit it all + at the end (so that we know whether the signature matches before + showing anything to the user.) + */ + + /* The newline issues here are tricky, since both the newlines + before and after the boundary string are to be considered part + of the boundary: this is so that a part can be specified such + that it does not end in a trailing newline. + + To implement this, we send a newline *before* each line instead + of after, except for the first line, which is not preceeded by a + newline. + */ + + /* Remove the trailing newline... */ + if (length > 0 && line[length-1] == '\n') length--; + if (length > 0 && line[length-1] == '\r') length--; + + PR_ASSERT(sig->part_buffer); + PR_ASSERT(first_line_p == + (sig->state == MimeMultipartSignedBodyFirstLine)); + + if (!first_line_p) + { + /* Push out a preceeding newline... */ + char nl[] = MSG_LINEBREAK; + status = MimePartBufferWrite (sig->part_buffer, nl, MSG_LINEBREAK_LEN); + if (status < 0) return status; + } + + /* Now push out the line sans trailing newline. */ + if (length > 0) + status = MimePartBufferWrite (sig->part_buffer, line, length); + if (status < 0) return status; + } + break; + + case MimeMultipartSignedSignatureHeaders: + // How'd we get here? Oh well, fall through. + NS_ERROR("should have already parse sig hdrs"); + MOZ_FALLTHROUGH; + case MimeMultipartSignedSignatureFirstLine: + case MimeMultipartSignedSignatureLine: + /* Nothing to do here -- hashing of the signature part is handled up + in MimeMultipartSigned_parse_line(). + */ + break; + + case MimeMultipartSignedEpilogue: + /* Too many kids? MimeMultipartSigned_create_child() should have + prevented us from getting here. */ + NS_ERROR("too many kids?"); + return -1; + break; + + default: /* bad state */ + NS_ERROR("bad state in multipart signed parse line"); + return -1; + break; + } + + return status; +} + + +static int +MimeMultipartSigned_emit_child (MimeObject *obj) +{ + MimeMultipartSigned *sig = (MimeMultipartSigned *) obj; + MimeMultipart *mult = (MimeMultipart *) obj; + MimeContainer *cont = (MimeContainer *) obj; + int status = 0; + MimeObject *body; + + NS_ASSERTION(sig->crypto_closure, "no crypto closure"); + + /* Emit some HTML saying whether the signature was cool. + But don't emit anything if in FO_QUOTE_MESSAGE mode. + */ + if (obj->options && + obj->options->headers != MimeHeadersCitation && + obj->options->write_html_p && + obj->options->output_fn && + obj->options->headers != MimeHeadersCitation && + sig->crypto_closure) + { + char *html = (((MimeMultipartSignedClass *) obj->clazz) + ->crypto_generate_html (sig->crypto_closure)); +#if 0 // XXX For the moment, no HTML output. Fix this XXX // + if (!html) return -1; /* MIME_OUT_OF_MEMORY? */ + + status = MimeObject_write(obj, html, strlen(html), false); + PR_Free(html); + if (status < 0) return status; +#endif + + /* Now that we have written out the crypto stamp, the outermost header + block is well and truly closed. If this is in fact the outermost + message, then run the post_header_html_fn now. + */ + if (obj->options && + obj->options->state && + obj->options->generate_post_header_html_fn && + !obj->options->state->post_header_html_run_p) + { + MimeHeaders *outer_headers=nullptr; + MimeObject *p; + for (p = obj; p->parent; p = p->parent) + outer_headers = p->headers; + NS_ASSERTION(obj->options->state->first_data_written_p, + "should have already written some data"); + html = obj->options->generate_post_header_html_fn(NULL, + obj->options->html_closure, + outer_headers); + obj->options->state->post_header_html_run_p = true; + if (html) + { + status = MimeObject_write(obj, html, strlen(html), false); + PR_Free(html); + if (status < 0) return status; + } + } + } + + + /* Oh, this is fairly nasty. We're skipping over our "create child" method + and using the one our superclass defines. Perhaps instead we should add + a new method on this class, and initialize that method to be the + create_child method of the superclass. Whatever. + */ + + + /* The superclass method expects to find the headers for the part that it's + to create in mult->hdrs, so ensure that they're there. */ + NS_ASSERTION(!mult->hdrs, "shouldn't already have hdrs for multipart"); + if (mult->hdrs) MimeHeaders_free(mult->hdrs); + mult->hdrs = sig->body_hdrs; + sig->body_hdrs = 0; + + /* Run the superclass create_child method. + */ + status = (((MimeMultipartClass *)(&MIME_SUPERCLASS))->create_child(obj)); + if (status < 0) return status; + + // Notify the charset of the first part. + if (obj->options && !(obj->options->override_charset)) { + MimeObject *firstChild = ((MimeContainer*) obj)->children[0]; + char *disposition = MimeHeaders_get (firstChild->headers, + HEADER_CONTENT_DISPOSITION, + true, + false); + // check if need to show as inline + if (!disposition) + { + const char *content_type = firstChild->content_type; + if (!PL_strcasecmp (content_type, TEXT_PLAIN) || + !PL_strcasecmp (content_type, TEXT_HTML) || + !PL_strcasecmp (content_type, TEXT_MDL) || + !PL_strcasecmp (content_type, MULTIPART_ALTERNATIVE) || + !PL_strcasecmp (content_type, MULTIPART_RELATED) || + !PL_strcasecmp (content_type, MESSAGE_NEWS) || + !PL_strcasecmp (content_type, MESSAGE_RFC822)) { + char *ct = MimeHeaders_get(mult->hdrs, HEADER_CONTENT_TYPE, false, false); + if (ct) { + char *cset = MimeHeaders_get_parameter (ct, "charset", NULL, NULL); + if (cset) { + mimeEmitterUpdateCharacterSet(obj->options, cset); + SetMailCharacterSetToMsgWindow(obj, cset); + PR_Free(cset); + } + PR_Free(ct); + } + } + } + } + + // The js emitter wants to know about the newly created child. Because + // MimeMultipartSigned dummies out its create_child operation, the logic + // in MimeMultipart_parse_line that would normally provide this notification + // does not get to fire. + if (obj->options && obj->options->notify_nested_bodies) { + MimeObject *kid = ((MimeContainer*) obj)->children[0]; + // The emitter is expecting the content type with parameters; not the fully + // parsed thing, so get it from raw. (We do not do it in the charset + // notification block that just happened because it already has complex + // if-checks that do not jive with us. + char *ct = MimeHeaders_get(mult->hdrs, HEADER_CONTENT_TYPE, false, + false); + mimeEmitterAddHeaderField(obj->options, HEADER_CONTENT_TYPE, + ct ? ct : "text/plain"); + PR_Free(ct); + + char *part_path = mime_part_address(kid); + if (part_path) { + mimeEmitterAddHeaderField(obj->options, + "x-jsemitter-part-path", + part_path); + PR_Free(part_path); + } + } + + /* Retrieve the child that it created. + */ + NS_ASSERTION(cont->nchildren == 1, "should only have one child"); + if (cont->nchildren != 1) + return -1; + body = cont->children[0]; + NS_ASSERTION(body, "missing body"); + if (!body) + return -1; + +#ifdef MIME_DRAFTS + if (body->options->decompose_file_p) { + body->options->signed_p = true; + if (!mime_typep(body, (MimeObjectClass*)&mimeMultipartClass) && + body->options->decompose_file_init_fn) + body->options->decompose_file_init_fn ( body->options->stream_closure, body->headers ); + } +#endif /* MIME_DRAFTS */ + + /* If there's no part_buffer, this is a zero-length signed message? */ + if (sig->part_buffer) + { +#ifdef MIME_DRAFTS + if (body->options->decompose_file_p && + !mime_typep(body, (MimeObjectClass*)&mimeMultipartClass) && + body->options->decompose_file_output_fn) + status = MimePartBufferRead (sig->part_buffer, + /* The (MimeConverterOutputCallback) cast is to turn the + `void' argument into `MimeObject'. */ + ((MimeConverterOutputCallback) + body->options->decompose_file_output_fn), + body->options->stream_closure); + else +#endif /* MIME_DRAFTS */ + + status = MimePartBufferRead (sig->part_buffer, + /* The (MimeConverterOutputCallback) cast is to turn the + `void' argument into `MimeObject'. */ + ((MimeConverterOutputCallback) body->clazz->parse_buffer), + body); + if (status < 0) return status; + } + + MimeMultipartSigned_cleanup(obj, false); + + /* Done parsing. */ + status = body->clazz->parse_eof(body, false); + if (status < 0) return status; + status = body->clazz->parse_end(body, false); + if (status < 0) return status; + +#ifdef MIME_DRAFTS + if (body->options->decompose_file_p && + !mime_typep(body, (MimeObjectClass*)&mimeMultipartClass) && + body->options->decompose_file_close_fn) + body->options->decompose_file_close_fn(body->options->stream_closure); +#endif /* MIME_DRAFTS */ + + /* Put out a separator after every multipart/signed object. */ + status = MimeObject_write_separator(obj); + if (status < 0) return status; + + return 0; +} diff --git a/mailnews/mime/src/mimemsig.h b/mailnews/mime/src/mimemsig.h new file mode 100644 index 000000000..f8581c3a3 --- /dev/null +++ b/mailnews/mime/src/mimemsig.h @@ -0,0 +1,134 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMSIG_H_ +#define _MIMEMSIG_H_ + +#include "mimemult.h" +#include "mimepbuf.h" +#include "modmimee.h" + +/* The MimeMultipartSigned class implements the multipart/signed MIME + container, which provides a general method of associating a cryptographic + signature to an arbitrary MIME object. + + The MimeMultipartSigned class provides the following methods: + + void *crypto_init (MimeObject *multipart_object) + + This is called with the object, the object->headers of which should be + used to initialize the dexlateion engine. NULL indicates failure; + otherwise, an opaque closure object should be returned. + + int crypto_data_hash (const char *data, int32_t data_size, + void *crypto_closure) + + This is called with the raw data, for which a signature has been computed. + The crypto module should examine this, and compute a signature for it. + + int crypto_data_eof (void *crypto_closure, bool abort_p) + + This is called when no more data remains. If `abort_p' is true, then the + crypto module may choose to discard any data rather than processing it, + as we're terminating abnormally. + + int crypto_signature_init (void *crypto_closure, + MimeObject *multipart_object, + MimeHeaders *signature_hdrs) + + This is called after crypto_data_eof() and just before the first call to + crypto_signature_hash(). The crypto module may wish to do some + initialization here, or may wish to examine the actual headers of the + signature object itself. + + int crypto_signature_hash (const char *data, int32_t data_size, + void *crypto_closure) + + This is called with the raw data of the detached signature block. It will + be called after crypto_data_eof() has been called to signify the end of + the data which is signed. This data is the data of the signature itself. + + int crypto_signature_eof (void *crypto_closure, bool abort_p) + + This is called when no more signature data remains. If `abort_p' is true, + then the crypto module may choose to discard any data rather than + processing it, as we're terminating abnormally. + + char * crypto_generate_html (void *crypto_closure) + + This is called after `crypto_signature_eof' but before `crypto_free'. + The crypto module should return a newly-allocated string of HTML code + which explains the status of the dexlateion to the user (whether the + signature checks out, etc.) + + void crypto_free (void *crypto_closure) + + This will be called when we're all done, after `crypto_signature_eof' and + `crypto_emit_html'. It is intended to free any data represented by the + crypto_closure. + */ + +typedef struct MimeMultipartSignedClass MimeMultipartSignedClass; +typedef struct MimeMultipartSigned MimeMultipartSigned; + +typedef enum { + MimeMultipartSignedPreamble, + MimeMultipartSignedBodyFirstHeader, + MimeMultipartSignedBodyHeaders, + MimeMultipartSignedBodyFirstLine, + MimeMultipartSignedBodyLine, + MimeMultipartSignedSignatureHeaders, + MimeMultipartSignedSignatureFirstLine, + MimeMultipartSignedSignatureLine, + MimeMultipartSignedEpilogue +} MimeMultipartSignedParseState; + +struct MimeMultipartSignedClass { + MimeMultipartClass multipart; + + /* Callbacks used by dexlateion (really, signature verification) module. */ + void * (*crypto_init) (MimeObject *multipart_object); + + int (*crypto_data_hash) (const char *data, int32_t data_size, + void *crypto_closure); + int (*crypto_signature_hash) (const char *data, int32_t data_size, + void *crypto_closure); + + int (*crypto_data_eof) (void *crypto_closure, bool abort_p); + int (*crypto_signature_eof) (void *crypto_closure, bool abort_p); + + int (*crypto_signature_init) (void *crypto_closure, + MimeObject *multipart_object, + MimeHeaders *signature_hdrs); + + char * (*crypto_generate_html) (void *crypto_closure); + + void (*crypto_free) (void *crypto_closure); +}; + +extern "C" MimeMultipartSignedClass mimeMultipartSignedClass; + +struct MimeMultipartSigned { + MimeMultipart multipart; + MimeMultipartSignedParseState state; /* State of parser */ + + void *crypto_closure; /* Opaque data used by signature + verification module. */ + + MimeHeaders *body_hdrs; /* The headers of the signed object. */ + MimeHeaders *sig_hdrs; /* The headers of the signature. */ + + MimePartBufferData *part_buffer; /* The buffered body of the signed + object (see mimepbuf.h) */ + + MimeDecoderData *sig_decoder_data; /* The signature is probably base64 + encoded; this is the decoder used + to get raw bits out of it. */ +}; + +#define MimeMultipartSignedClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMSIG_H_ */ diff --git a/mailnews/mime/src/mimemult.cpp b/mailnews/mime/src/mimemult.cpp new file mode 100644 index 000000000..64f292ec0 --- /dev/null +++ b/mailnews/mime/src/mimemult.cpp @@ -0,0 +1,748 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" +#include "mimemult.h" +#include "mimemoz2.h" +#include "mimeeobj.h" + +#include "prlog.h" +#include "prmem.h" +#include "plstr.h" +#include "prio.h" +#include "nsMimeStringResources.h" +#include "nsMimeTypes.h" +#include <ctype.h> + +#ifdef XP_MACOSX + extern MimeObjectClass mimeMultipartAppleDoubleClass; +#endif + +#define MIME_SUPERCLASS mimeContainerClass +MimeDefClass(MimeMultipart, MimeMultipartClass, + mimeMultipartClass, &MIME_SUPERCLASS); + +static int MimeMultipart_initialize (MimeObject *); +static void MimeMultipart_finalize (MimeObject *); +static int MimeMultipart_parse_line (const char *line, int32_t length, MimeObject *); +static int MimeMultipart_parse_eof (MimeObject *object, bool abort_p); + +static MimeMultipartBoundaryType MimeMultipart_check_boundary(MimeObject *, + const char *, + int32_t); +static int MimeMultipart_create_child(MimeObject *); +static bool MimeMultipart_output_child_p(MimeObject *, MimeObject *); +static int MimeMultipart_parse_child_line (MimeObject *, const char *, int32_t, + bool); +static int MimeMultipart_close_child(MimeObject *); + +extern "C" MimeObjectClass mimeMultipartAlternativeClass; +extern "C" MimeObjectClass mimeMultipartRelatedClass; +extern "C" MimeObjectClass mimeMultipartSignedClass; +extern "C" MimeObjectClass mimeInlineTextVCardClass; +extern "C" MimeExternalObjectClass mimeExternalObjectClass; + +#if defined(DEBUG) && defined(XP_UNIX) +static int MimeMultipart_debug_print (MimeObject *, PRFileDesc *, int32_t); +#endif + +static int +MimeMultipartClassInitialize(MimeMultipartClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + + PR_ASSERT(!oclass->class_initialized); + oclass->initialize = MimeMultipart_initialize; + oclass->finalize = MimeMultipart_finalize; + oclass->parse_line = MimeMultipart_parse_line; + oclass->parse_eof = MimeMultipart_parse_eof; + + mclass->check_boundary = MimeMultipart_check_boundary; + mclass->create_child = MimeMultipart_create_child; + mclass->output_child_p = MimeMultipart_output_child_p; + mclass->parse_child_line = MimeMultipart_parse_child_line; + mclass->close_child = MimeMultipart_close_child; + +#if defined(DEBUG) && defined(XP_UNIX) + oclass->debug_print = MimeMultipart_debug_print; +#endif + + return 0; +} + + +static int +MimeMultipart_initialize (MimeObject *object) +{ + MimeMultipart *mult = (MimeMultipart *) object; + char *ct; + + /* This is an abstract class; it shouldn't be directly instantiated. */ + PR_ASSERT(object->clazz != (MimeObjectClass *) &mimeMultipartClass); + + ct = MimeHeaders_get (object->headers, HEADER_CONTENT_TYPE, false, false); + mult->boundary = (ct + ? MimeHeaders_get_parameter (ct, HEADER_PARM_BOUNDARY, NULL, NULL) + : 0); + PR_FREEIF(ct); + mult->state = MimeMultipartPreamble; + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + + +static void +MimeMultipart_finalize (MimeObject *object) +{ + MimeMultipart *mult = (MimeMultipart *) object; + + object->clazz->parse_eof(object, false); + + PR_FREEIF(mult->boundary); + if (mult->hdrs) + MimeHeaders_free(mult->hdrs); + mult->hdrs = 0; + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + +int MimeWriteAString(MimeObject *obj, const nsACString &string) +{ + const nsCString &flatString = PromiseFlatCString(string); + return MimeObject_write(obj, flatString.get(), flatString.Length(), true); +} + +static int +MimeMultipart_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + MimeContainer *container = (MimeContainer*) obj; + int status = 0; + MimeMultipartBoundaryType boundary; + + NS_ASSERTION(line && *line, "empty line in multipart parse_line"); + if (!line || !*line) return -1; + + NS_ASSERTION(!obj->closed_p, "obj shouldn't already be closed"); + if (obj->closed_p) return -1; + + /* If we're supposed to write this object, but aren't supposed to convert + it to HTML, simply pass it through unaltered. */ + if (obj->output_p && + obj->options && + !obj->options->write_html_p && + obj->options->output_fn + && obj->options->format_out != nsMimeOutput::nsMimeMessageAttach) + return MimeObject_write(obj, line, length, true); + + if (mult->state == MimeMultipartEpilogue) /* already done */ + boundary = MimeMultipartBoundaryTypeNone; + else + boundary = ((MimeMultipartClass *)obj->clazz)->check_boundary(obj, line, + length); + + if (boundary == MimeMultipartBoundaryTypeTerminator || + boundary == MimeMultipartBoundaryTypeSeparator) + { + /* Match! Close the currently-open part, move on to the next + state, and discard this line. + */ + bool endOfPart = (mult->state != MimeMultipartPreamble); + if (endOfPart) + status = ((MimeMultipartClass *)obj->clazz)->close_child(obj); + if (status < 0) return status; + + if (boundary == MimeMultipartBoundaryTypeTerminator) + mult->state = MimeMultipartEpilogue; + else + { + mult->state = MimeMultipartHeaders; + + /* Reset the header parser for this upcoming part. */ + NS_ASSERTION(!mult->hdrs, "mult->hdrs should be null here"); + if (mult->hdrs) + MimeHeaders_free(mult->hdrs); + mult->hdrs = MimeHeaders_new(); + if (!mult->hdrs) + return MIME_OUT_OF_MEMORY; + if (obj->options && obj->options->state && + obj->options->state->partsToStrip.Length() > 0) + { + nsAutoCString newPart(mime_part_address(obj)); + newPart.Append('.'); + newPart.AppendInt(container->nchildren + 1); + obj->options->state->strippingPart = false; + // check if this is a sub-part of a part we're stripping. + for (uint32_t partIndex = 0; partIndex < obj->options->state->partsToStrip.Length(); partIndex++) + { + nsCString &curPartToStrip = obj->options->state->partsToStrip[partIndex]; + if (newPart.Find(curPartToStrip) == 0 && (newPart.Length() == curPartToStrip.Length() || newPart.CharAt(curPartToStrip.Length()) == '.')) + { + obj->options->state->strippingPart = true; + if (partIndex < obj->options->state->detachToFiles.Length()) + obj->options->state->detachedFilePath = obj->options->state->detachToFiles[partIndex]; + break; + } + } + } + } + + // if stripping out attachments, write the boundary line. Otherwise, return + // to ignore it. + if (obj->options && obj->options->format_out == nsMimeOutput::nsMimeMessageAttach) + { + // Because MimeMultipart_parse_child_line strips out the + // the CRLF of the last line before the end of a part, we need to add that + // back in here. + if (endOfPart) + MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK)); + + status = MimeObject_write(obj, line, length, true); + } + return 0; + } + + /* Otherwise, this isn't a boundary string. So do whatever it is we + should do with this line (parse it as a header, feed it to the + child part, ignore it, etc.) */ + + switch (mult->state) + { + case MimeMultipartPreamble: + case MimeMultipartEpilogue: + /* Ignore this line. */ + break; + + case MimeMultipartHeaders: + /* Parse this line as a header for the sub-part. */ + { + status = MimeHeaders_parse_line(line, length, mult->hdrs); + bool stripping = false; + + if (status < 0) return status; + + // If this line is blank, we're now done parsing headers, and should + // now examine the content-type to create this "body" part. + // + if (*line == '\r' || *line == '\n') + { + if (obj->options && obj->options->state && + obj->options->state->strippingPart) + { + stripping = true; + bool detachingPart = obj->options->state->detachedFilePath.Length() > 0; + + nsAutoCString fileName; + fileName.Adopt(MimeHeaders_get_name(mult->hdrs, obj->options)); + if (detachingPart) + { + char *contentType = MimeHeaders_get(mult->hdrs, "Content-Type", false, false); + if (contentType) + { + MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Type: ")); + MimeWriteAString(obj, nsDependentCString(contentType)); + PR_Free(contentType); + } + MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK)); + MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Disposition: attachment; filename=\"")); + MimeWriteAString(obj, fileName); + MimeWriteAString(obj, NS_LITERAL_CSTRING("\"" MSG_LINEBREAK)); + MimeWriteAString(obj, NS_LITERAL_CSTRING("X-Mozilla-External-Attachment-URL: ")); + MimeWriteAString(obj, obj->options->state->detachedFilePath); + MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK)); + MimeWriteAString(obj, NS_LITERAL_CSTRING("X-Mozilla-Altered: AttachmentDetached; date=\"")); + } + else + { + nsAutoCString header("Content-Type: text/x-moz-deleted; name=\"Deleted: "); + header.Append(fileName); + status = MimeWriteAString(obj, header); + if (status < 0) + return status; + status = MimeWriteAString(obj, NS_LITERAL_CSTRING("\"" MSG_LINEBREAK "Content-Transfer-Encoding: 8bit" MSG_LINEBREAK)); + MimeWriteAString(obj, NS_LITERAL_CSTRING("Content-Disposition: inline; filename=\"Deleted: ")); + MimeWriteAString(obj, fileName); + MimeWriteAString(obj, NS_LITERAL_CSTRING("\"" MSG_LINEBREAK "X-Mozilla-Altered: AttachmentDeleted; date=\"")); + } + nsCString result; + char timeBuffer[128]; + PRExplodedTime now; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now); + PR_FormatTimeUSEnglish(timeBuffer, sizeof(timeBuffer), + "%a %b %d %H:%M:%S %Y", + &now); + MimeWriteAString(obj, nsDependentCString(timeBuffer)); + MimeWriteAString(obj, NS_LITERAL_CSTRING("\"" MSG_LINEBREAK)); + MimeWriteAString(obj, NS_LITERAL_CSTRING(MSG_LINEBREAK "You deleted an attachment from this message. The original MIME headers for the attachment were:" MSG_LINEBREAK)); + MimeHeaders_write_raw_headers(mult->hdrs, obj->options, false); + } + int32_t old_nchildren = container->nchildren; + status = ((MimeMultipartClass *) obj->clazz)->create_child(obj); + if (status < 0) return status; + NS_ASSERTION(mult->state != MimeMultipartHeaders, + "mult->state shouldn't be MimeMultipartHeaders"); + + if (!stripping && container->nchildren > old_nchildren && obj->options && + !mime_typep(obj, (MimeObjectClass*)&mimeMultipartAlternativeClass)) { + // Notify emitter about content type and part path. + MimeObject *kid = container->children[container->nchildren-1]; + MimeMultipart_notify_emitter(kid); + } + } + break; + } + + case MimeMultipartPartFirstLine: + /* Hand this line off to the sub-part. */ + status = (((MimeMultipartClass *) obj->clazz)->parse_child_line(obj, + line, length, true)); + if (status < 0) return status; + mult->state = MimeMultipartPartLine; + break; + + case MimeMultipartPartLine: + /* Hand this line off to the sub-part. */ + status = (((MimeMultipartClass *) obj->clazz)->parse_child_line(obj, + line, length, false)); + if (status < 0) return status; + break; + + default: + NS_ERROR("unexpected state in parse line"); + return -1; + } + + if (obj->options && + obj->options->format_out == nsMimeOutput::nsMimeMessageAttach && + (!(obj->options->state && obj->options->state->strippingPart) && + mult->state != MimeMultipartPartLine)) + return MimeObject_write(obj, line, length, false); + return 0; +} + +void MimeMultipart_notify_emitter(MimeObject *obj) +{ + char *ct = nullptr; + + NS_ASSERTION(obj->options, "MimeMultipart_notify_emitter called with null options"); + if (! obj->options) + return; + + ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, + false, false); + if (obj->options->notify_nested_bodies) { + mimeEmitterAddHeaderField(obj->options, HEADER_CONTENT_TYPE, + ct ? ct : TEXT_PLAIN); + char *part_path = mime_part_address(obj); + if (part_path) { + mimeEmitterAddHeaderField(obj->options, "x-jsemitter-part-path", + part_path); + PR_Free(part_path); + } + } + + // Examine the headers and see if there is a special charset + // (i.e. non US-ASCII) for this message. If so, we need to + // tell the emitter that this is the case for use in any + // possible reply or forward operation. + if (ct && (obj->options->notify_nested_bodies || + MimeObjectIsMessageBody(obj))) { + char *cset = MimeHeaders_get_parameter(ct, "charset", NULL, NULL); + if (cset) { + mimeEmitterUpdateCharacterSet(obj->options, cset); + if (!obj->options->override_charset) + // Also set this charset to msgWindow + SetMailCharacterSetToMsgWindow(obj, cset); + PR_Free(cset); + } + } + + PR_FREEIF(ct); +} + +static MimeMultipartBoundaryType +MimeMultipart_check_boundary(MimeObject *obj, const char *line, int32_t length) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + int32_t blen; + bool term_p; + + if (!mult->boundary || + line[0] != '-' || + line[1] != '-') + return MimeMultipartBoundaryTypeNone; + + /* This is a candidate line to be a boundary. Check it out... */ + blen = strlen(mult->boundary); + term_p = false; + + /* strip trailing whitespace (including the newline.) */ + while(length > 2 && IS_SPACE(line[length-1])) + length--; + + /* Could this be a terminating boundary? */ + if (length == blen + 4 && + line[length-1] == '-' && + line[length-2] == '-') + { + term_p = true; + } + + //looks like we have a separator but first, we need to check it's not for one of the part's children. + MimeContainer *cont = (MimeContainer *) obj; + if (cont->nchildren > 0) + { + MimeObject *kid = cont->children[cont->nchildren-1]; + if (kid) + if (mime_typep(kid, (MimeObjectClass*) &mimeMultipartClass)) + { + //Don't ask the kid to check the boundary if it has already detected a Teminator + MimeMultipart *mult = (MimeMultipart *) kid; + if (mult->state != MimeMultipartEpilogue) + if (MimeMultipart_check_boundary(kid, line, length) != MimeMultipartBoundaryTypeNone) + return MimeMultipartBoundaryTypeNone; + } + } + + if (term_p) + length -= 2; + + if (blen == length-2 && !strncmp(line+2, mult->boundary, length-2)) + return (term_p + ? MimeMultipartBoundaryTypeTerminator + : MimeMultipartBoundaryTypeSeparator); + else + return MimeMultipartBoundaryTypeNone; +} + + +static int +MimeMultipart_create_child(MimeObject *obj) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + int status; + char *ct = (mult->hdrs + ? MimeHeaders_get (mult->hdrs, HEADER_CONTENT_TYPE, + true, false) + : 0); + const char *dct = (((MimeMultipartClass *) obj->clazz)->default_part_type); + MimeObject *body = NULL; + + mult->state = MimeMultipartPartFirstLine; + if (obj->options) + obj->options->is_child = true; + + /* Don't pass in NULL as the content-type (this means that the + auto-uudecode-hack won't ever be done for subparts of a + multipart, but only for untyped children of message/rfc822. + */ + body = mime_create(((ct && *ct) ? ct : (dct ? dct: TEXT_PLAIN)), + mult->hdrs, obj->options); + PR_FREEIF(ct); + if (!body) return MIME_OUT_OF_MEMORY; + status = ((MimeContainerClass *) obj->clazz)->add_child(obj, body); + if (status < 0) + { + mime_free(body); + return status; + } + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + obj->options->is_multipart_msg && + obj->options->decompose_file_init_fn ) + { + if ( !mime_typep(obj,(MimeObjectClass*)&mimeMultipartRelatedClass) && + !mime_typep(obj,(MimeObjectClass*)&mimeMultipartAlternativeClass) && + !mime_typep(obj,(MimeObjectClass*)&mimeMultipartSignedClass) && +#ifdef MIME_DETAIL_CHECK + !mime_typep(body, (MimeObjectClass*)&mimeMultipartRelatedClass) && + !mime_typep(body, (MimeObjectClass*)&mimeMultipartAlternativeClass) && + !mime_typep(body,(MimeObjectClass*)&mimeMultipartSignedClass) +#else + /* bug 21869 -- due to the fact that we are not generating the + correct mime class object for content-typ multipart/signed part + the above check failed. to solve the problem in general and not + to cause early temination when parsing message for opening as + draft we can simply make sure that the child is not a multipart + mime object. this way we could have a proper decomposing message + part functions set correctly */ + !mime_typep(body, (MimeObjectClass*) &mimeMultipartClass) +#endif + && ! (mime_typep(body, (MimeObjectClass*)&mimeExternalObjectClass) && !strcmp(body->content_type, "text/x-vcard")) + ) + { + status = obj->options->decompose_file_init_fn ( obj->options->stream_closure, mult->hdrs ); + if (status < 0) return status; + } + } +#endif /* MIME_DRAFTS */ + + + /* Now that we've added this new object to our list of children, + start its parser going (if we want to display it.) + */ + body->output_p = (((MimeMultipartClass *) obj->clazz)->output_child_p(obj, body)); + if (body->output_p) + { + status = body->clazz->parse_begin(body); + +#ifdef XP_MACOSX + /* if we are saving an apple double attachment, we need to set correctly the conten type of the channel */ + if (mime_typep(obj, (MimeObjectClass *) &mimeMultipartAppleDoubleClass)) + { + mime_stream_data *msd = (mime_stream_data *)body->options->stream_closure; + if (!body->options->write_html_p && body->content_type && !PL_strcasecmp(body->content_type, APPLICATION_APPLEFILE)) + { + if (msd && msd->channel) + msd->channel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_APPLEFILE)); + } + } +#endif + + if (status < 0) return status; + } + + return 0; +} + + +static bool +MimeMultipart_output_child_p(MimeObject *obj, MimeObject *child) +{ + /* We don't output a child if we're stripping it. */ + if (obj->options && obj->options->state && obj->options->state->strippingPart) + return false; + /* if we are saving an apple double attachment, ignore the appledouble wrapper part */ + return (obj->options && obj->options->write_html_p) || + PL_strcasecmp(child->content_type, MULTIPART_APPLEDOUBLE); +} + + + +static int +MimeMultipart_close_child(MimeObject *object) +{ + MimeMultipart *mult = (MimeMultipart *) object; + MimeContainer *cont = (MimeContainer *) object; + + if (!mult->hdrs) + return 0; + + MimeHeaders_free(mult->hdrs); + mult->hdrs = 0; + + NS_ASSERTION(cont->nchildren > 0, "badly formed mime message"); + if (cont->nchildren > 0) + { + MimeObject *kid = cont->children[cont->nchildren-1]; + // If we have a child and it has not already been closed, process it. + // The kid would be already be closed if we encounter a multipart section + // that did not have a fully delineated header block. No header block means + // no creation of a new child, but the termination case still happens and + // we still end up here. Obviously, we don't want to close the child a + // second time and the best thing we can do is nothing. + if (kid && !kid->closed_p) + { + int status; + status = kid->clazz->parse_eof(kid, false); + if (status < 0) return status; + status = kid->clazz->parse_end(kid, false); + if (status < 0) return status; + +#ifdef MIME_DRAFTS + if ( object->options && + object->options->decompose_file_p && + object->options->is_multipart_msg && + object->options->decompose_file_close_fn ) + { + if ( !mime_typep(object,(MimeObjectClass*)&mimeMultipartRelatedClass) && + !mime_typep(object,(MimeObjectClass*)&mimeMultipartAlternativeClass) && + !mime_typep(object,(MimeObjectClass*)&mimeMultipartSignedClass) && +#ifdef MIME_DETAIL_CHECK + !mime_typep(kid,(MimeObjectClass*)&mimeMultipartRelatedClass) && + !mime_typep(kid,(MimeObjectClass*)&mimeMultipartAlternativeClass) && + !mime_typep(kid,(MimeObjectClass*)&mimeMultipartSignedClass) +#else + /* bug 21869 -- due to the fact that we are not generating the + correct mime class object for content-typ multipart/signed part + the above check failed. to solve the problem in general and not + to cause early temination when parsing message for opening as + draft we can simply make sure that the child is not a multipart + mime object. this way we could have a proper decomposing message + part functions set correctly */ + !mime_typep(kid,(MimeObjectClass*) &mimeMultipartClass) +#endif + && !(mime_typep(kid, (MimeObjectClass*)&mimeExternalObjectClass) && !strcmp(kid->content_type, "text/x-vcard")) + ) + { + status = object->options->decompose_file_close_fn ( object->options->stream_closure ); + if (status < 0) return status; + } + } +#endif /* MIME_DRAFTS */ + + } + } + return 0; +} + + +static int +MimeMultipart_parse_child_line (MimeObject *obj, const char *line, int32_t length, + bool first_line_p) +{ + MimeContainer *cont = (MimeContainer *) obj; + int status; + MimeObject *kid; + + PR_ASSERT(cont->nchildren > 0); + if (cont->nchildren <= 0) + return -1; + + kid = cont->children[cont->nchildren-1]; + PR_ASSERT(kid); + if (!kid) return -1; + +#ifdef MIME_DRAFTS + if ( obj->options && + obj->options->decompose_file_p && + obj->options->is_multipart_msg && + obj->options->decompose_file_output_fn ) + { + if (!mime_typep(obj,(MimeObjectClass*)&mimeMultipartAlternativeClass) && + !mime_typep(obj,(MimeObjectClass*)&mimeMultipartRelatedClass) && + !mime_typep(obj,(MimeObjectClass*)&mimeMultipartSignedClass) && +#ifdef MIME_DETAIL_CHECK + !mime_typep(kid,(MimeObjectClass*)&mimeMultipartAlternativeClass) && + !mime_typep(kid,(MimeObjectClass*)&mimeMultipartRelatedClass) && + !mime_typep(kid,(MimeObjectClass*)&mimeMultipartSignedClass) +#else + /* bug 21869 -- due to the fact that we are not generating the + correct mime class object for content-typ multipart/signed part + the above check failed. to solve the problem in general and not + to cause early temination when parsing message for opening as + draft we can simply make sure that the child is not a multipart + mime object. this way we could have a proper decomposing message + part functions set correctly */ + !mime_typep(kid, (MimeObjectClass*) &mimeMultipartClass) +#endif + && !(mime_typep(kid, (MimeObjectClass*)&mimeExternalObjectClass) && !strcmp(kid->content_type, "text/x-vcard")) + ) + return obj->options->decompose_file_output_fn (line, length, obj->options->stream_closure); + } +#endif /* MIME_DRAFTS */ + + /* The newline issues here are tricky, since both the newlines before + and after the boundary string are to be considered part of the + boundary: this is so that a part can be specified such that it + does not end in a trailing newline. + + To implement this, we send a newline *before* each line instead + of after, except for the first line, which is not preceeded by a + newline. + */ + + /* Remove the trailing newline... */ + if (length > 0 && line[length-1] == '\n') length--; + if (length > 0 && line[length-1] == '\r') length--; + + if (!first_line_p) + { + /* Push out a preceeding newline... */ + char nl[] = MSG_LINEBREAK; + status = kid->clazz->parse_buffer (nl, MSG_LINEBREAK_LEN, kid); + if (status < 0) return status; + } + + /* Now push out the line sans trailing newline. */ + return kid->clazz->parse_buffer (line, length, kid); +} + + +static int +MimeMultipart_parse_eof (MimeObject *obj, bool abort_p) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + MimeContainer *cont = (MimeContainer *) obj; + + if (obj->closed_p) return 0; + + /* Push out the last trailing line if there's one in the buffer. If + this happens, this object does not end in a trailing newline (and + the parse_line method will be called with a string with no trailing + newline, which isn't the usual case.) + */ + if (!abort_p && obj->ibuffer_fp > 0) + { + /* There is leftover data without a terminating newline. */ + int status = obj->clazz->parse_line(obj->ibuffer, obj->ibuffer_fp,obj); + obj->ibuffer_fp = 0; + if (status < 0) + { + obj->closed_p = true; + return status; + } + } + + /* Now call parse_eof for our active child, if there is one. + */ + if (cont->nchildren > 0 && + (mult->state == MimeMultipartPartLine || + mult->state == MimeMultipartPartFirstLine)) + { + MimeObject *kid = cont->children[cont->nchildren-1]; + NS_ASSERTION(kid, "not expecting null kid"); + if (kid) + { + int status = kid->clazz->parse_eof(kid, abort_p); + if (status < 0) return status; + } + } + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); +} + + +#if defined(DEBUG) && defined(XP_UNIX) +static int +MimeMultipart_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth) +{ + /* MimeMultipart *mult = (MimeMultipart *) obj; */ + MimeContainer *cont = (MimeContainer *) obj; + char *addr = mime_part_address(obj); + int i; + for (i=0; i < depth; i++) + PR_Write(stream, " ", 2); +/** + fprintf(stream, "<%s %s (%d kid%s) boundary=%s 0x%08X>\n", + obj->clazz->class_name, + addr ? addr : "???", + cont->nchildren, (cont->nchildren == 1 ? "" : "s"), + (mult->boundary ? mult->boundary : "(none)"), + (uint32_t) mult); +**/ + PR_FREEIF(addr); + +/* + if (cont->nchildren > 0) + fprintf(stream, "\n"); + */ + + for (i = 0; i < cont->nchildren; i++) + { + MimeObject *kid = cont->children[i]; + int status = kid->clazz->debug_print (kid, stream, depth+1); + if (status < 0) return status; + } + +/* + if (cont->nchildren > 0) + fprintf(stream, "\n"); + */ + + return 0; +} +#endif diff --git a/mailnews/mime/src/mimemult.h b/mailnews/mime/src/mimemult.h new file mode 100644 index 000000000..877afb4d9 --- /dev/null +++ b/mailnews/mime/src/mimemult.h @@ -0,0 +1,102 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEMULT_H_ +#define _MIMEMULT_H_ + +#include "mimecont.h" + +/* The MimeMultipart class class implements the objects representing all of + the "multipart/" MIME types. In addition to the methods inherited from + MimeContainer, it provides the following methods and class variables: + + int create_child (MimeObject *obj) + + When it has been determined that a new sub-part should be created, + this method is called to do that. The default value for this method + does it in the usual multipart/mixed way. The headers of the object- + to-be-created may be found in the `hdrs' slot of the `MimeMultipart' + object. + + bool output_child_p (MimeObject *parent, MimeObject *child) + + Whether this child should be output. Default method always says `yes'. + + int parse_child_line (MimeObject *obj, const char *line, int32_t length, + bool first_line_p) + + When we have a line which should be handed off to the currently-active + child object, this method is called to do that. The `first_line_p' + variable will be true only for the very first line handed off to this + sub-part. The default method simply passes the line to the most- + recently-added child object. + + int close_child (MimeObject *self) + + When we reach the end of a sub-part (a separator line) this method is + called to shut down the currently-active child. The default method + simply calls `parse_eof' on the most-recently-added child object. + + MimeMultipartBoundaryType check_boundary (MimeObject *obj, + const char *line, int32_t length) + + This method is used to examine a line and determine whether it is a + part boundary, and if so, what kind. It should return a member of + the MimeMultipartBoundaryType describing the line. + + const char *default_part_type + + This is the type which should be assumed for sub-parts which have + no explicit type specified. The default is "text/plain", but the + "multipart/digest" subclass overrides this to "message/rfc822". + */ + +typedef struct MimeMultipartClass MimeMultipartClass; +typedef struct MimeMultipart MimeMultipart; + +typedef enum { + MimeMultipartPreamble, + MimeMultipartHeaders, + MimeMultipartPartFirstLine, + MimeMultipartPartLine, + MimeMultipartEpilogue +} MimeMultipartParseState; + +typedef enum { + MimeMultipartBoundaryTypeNone, + MimeMultipartBoundaryTypeSeparator, + MimeMultipartBoundaryTypeTerminator +} MimeMultipartBoundaryType; + + +struct MimeMultipartClass { + MimeContainerClass container; + const char *default_part_type; + + int (*create_child) (MimeObject *); + bool (*output_child_p) (MimeObject *self, MimeObject *child); + int (*close_child) (MimeObject *); + int (*parse_child_line) (MimeObject *, const char *line, int32_t length, + bool first_line_p); + MimeMultipartBoundaryType (*check_boundary) (MimeObject *, const char *line, + int32_t length); +}; + +extern MimeMultipartClass mimeMultipartClass; + +struct MimeMultipart { + MimeContainer container; /* superclass variables */ + char *boundary; /* Inter-part delimiter string */ + MimeHeaders *hdrs; /* headers of the part currently + being parsed, if any */ + MimeMultipartParseState state; /* State of parser */ +}; + +extern void MimeMultipart_notify_emitter(MimeObject *); + +#define MimeMultipartClassInitializer(ITYPE,CSUPER) \ + { MimeContainerClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEMULT_H_ */ diff --git a/mailnews/mime/src/mimeobj.cpp b/mailnews/mime/src/mimeobj.cpp new file mode 100644 index 000000000..26eb618ce --- /dev/null +++ b/mailnews/mime/src/mimeobj.cpp @@ -0,0 +1,327 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ + +#include "mimeobj.h" +#include "prmem.h" +#include "plstr.h" +#include "prio.h" +#include "mimebuf.h" +#include "prlog.h" +#include "nsMimeTypes.h" +#include "nsMimeStringResources.h" +#include "nsMsgUtils.h" +#include "mimemsg.h" +#include "mimemapl.h" + +/* Way to destroy any notions of modularity or class hierarchy, Terry! */ +# include "mimetpla.h" +# include "mimethtm.h" +# include "mimecont.h" + +MimeDefClass (MimeObject, MimeObjectClass, mimeObjectClass, NULL); + +static int MimeObject_initialize (MimeObject *); +static void MimeObject_finalize (MimeObject *); +static int MimeObject_parse_begin (MimeObject *); +static int MimeObject_parse_buffer (const char *, int32_t, MimeObject *); +static int MimeObject_parse_line (const char *, int32_t, MimeObject *); +static int MimeObject_parse_eof (MimeObject *, bool); +static int MimeObject_parse_end (MimeObject *, bool); +static bool MimeObject_displayable_inline_p (MimeObjectClass *clazz, + MimeHeaders *hdrs); + +#if defined(DEBUG) && defined(XP_UNIX) +static int MimeObject_debug_print (MimeObject *, PRFileDesc *, int32_t depth); +#endif + +static int +MimeObjectClassInitialize(MimeObjectClass *clazz) +{ + NS_ASSERTION(!clazz->class_initialized, "class shouldn't already be initialized"); + clazz->initialize = MimeObject_initialize; + clazz->finalize = MimeObject_finalize; + clazz->parse_begin = MimeObject_parse_begin; + clazz->parse_buffer = MimeObject_parse_buffer; + clazz->parse_line = MimeObject_parse_line; + clazz->parse_eof = MimeObject_parse_eof; + clazz->parse_end = MimeObject_parse_end; + clazz->displayable_inline_p = MimeObject_displayable_inline_p; + +#if defined(DEBUG) && defined(XP_UNIX) + clazz->debug_print = MimeObject_debug_print; +#endif + return 0; +} + +static int +MimeObject_initialize (MimeObject *obj) +{ + /* This is an abstract class; it shouldn't be directly instantiated. */ + NS_ASSERTION(obj->clazz != &mimeObjectClass, "should directly instantiate abstract class"); + + /* Set up the content-type and encoding. */ + if (!obj->content_type && obj->headers) + obj->content_type = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, + true, false); + if (!obj->encoding && obj->headers) + obj->encoding = MimeHeaders_get (obj->headers, + HEADER_CONTENT_TRANSFER_ENCODING, + true, false); + + /* Special case to normalize some types and encodings to a canonical form. + (These are nonstandard types/encodings which have been seen to appear in + multiple forms; we normalize them so that things like looking up icons + and extensions has consistent behavior for the receiver, regardless of + the "alias" type that the sender used.) + */ + if (!obj->content_type || !*(obj->content_type)) + ; + else if (!PL_strcasecmp(obj->content_type, APPLICATION_UUENCODE2) || + !PL_strcasecmp(obj->content_type, APPLICATION_UUENCODE3) || + !PL_strcasecmp(obj->content_type, APPLICATION_UUENCODE4)) + { + PR_Free(obj->content_type); + obj->content_type = strdup(APPLICATION_UUENCODE); + } + else if (!PL_strcasecmp(obj->content_type, IMAGE_XBM2) || + !PL_strcasecmp(obj->content_type, IMAGE_XBM3)) + { + PR_Free(obj->content_type); + obj->content_type = strdup(IMAGE_XBM); + } + else { + // MIME-types are case-insenitive, but let's make it lower case internally + // to avoid some hassle later down the road. + nsAutoCString lowerCaseContentType; + ToLowerCase(nsDependentCString(obj->content_type), lowerCaseContentType); + PR_Free(obj->content_type); + obj->content_type = ToNewCString(lowerCaseContentType); + } + + if (!obj->encoding) + ; + else if (!PL_strcasecmp(obj->encoding, ENCODING_UUENCODE2) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE3) || + !PL_strcasecmp(obj->encoding, ENCODING_UUENCODE4)) + { + PR_Free(obj->encoding); + obj->encoding = strdup(ENCODING_UUENCODE); + } + else if (!PL_strcasecmp(obj->encoding, ENCODING_COMPRESS2)) + { + PR_Free(obj->encoding); + obj->encoding = strdup(ENCODING_COMPRESS); + } + else if (!PL_strcasecmp(obj->encoding, ENCODING_GZIP2)) + { + PR_Free(obj->encoding); + obj->encoding = strdup(ENCODING_GZIP); + } + + return 0; +} + +static void +MimeObject_finalize (MimeObject *obj) +{ + obj->clazz->parse_eof (obj, false); + obj->clazz->parse_end (obj, false); + + if (obj->headers) + { + MimeHeaders_free(obj->headers); + obj->headers = 0; + } + + /* Should have been freed by parse_eof, but just in case... */ + NS_ASSERTION(!obj->ibuffer, "buffer not freed"); + NS_ASSERTION(!obj->obuffer, "buffer not freed"); + PR_FREEIF (obj->ibuffer); + PR_FREEIF (obj->obuffer); + + PR_FREEIF(obj->content_type); + PR_FREEIF(obj->encoding); + + if (obj->options && obj->options->state) + { + delete obj->options->state; + obj->options->state = nullptr; + } +} + +static int +MimeObject_parse_begin (MimeObject *obj) +{ + NS_ASSERTION (!obj->closed_p, "object shouldn't be already closed"); + + /* If we haven't set up the state object yet, then this should be + the outermost object... */ + if (obj->options && !obj->options->state) + { + NS_ASSERTION(!obj->headers, "headers should be null"); /* should be the outermost object. */ + + obj->options->state = new MimeParseStateObject; + if (!obj->options->state) return MIME_OUT_OF_MEMORY; + obj->options->state->root = obj; + obj->options->state->separator_suppressed_p = true; /* no first sep */ + const char *delParts = PL_strcasestr(obj->options->url, "&del="); + const char *detachLocations = PL_strcasestr(obj->options->url, "&detachTo="); + if (delParts) + { + const char *delEnd = PL_strcasestr(delParts + 1, "&"); + if (!delEnd) + delEnd = delParts + strlen(delParts); + ParseString(Substring(delParts + 5, delEnd), ',', obj->options->state->partsToStrip); + } + if (detachLocations) + { + detachLocations += 10; // advance past "&detachTo=" + ParseString(nsDependentCString(detachLocations), ',', obj->options->state->detachToFiles); + } + } + + /* Decide whether this object should be output or not... */ + if (!obj->options || obj->options->no_output_p || !obj->options->output_fn + /* if we are decomposing the message in files and processing a multipart object, + we must not output it without parsing it first */ + || (obj->options->decompose_file_p && obj->options->decompose_file_output_fn && + mime_typep(obj, (MimeObjectClass*) &mimeMultipartClass)) + ) + obj->output_p = false; + else if (!obj->options->part_to_load) + obj->output_p = true; + else + { + char *id = mime_part_address(obj); + if (!id) return MIME_OUT_OF_MEMORY; + + // We need to check if a part is the subpart of the part to load. + // If so and this is a raw or body display output operation, then + // we should mark the part for subsequent output. + + // First, check for an exact match + obj->output_p = !strcmp(id, obj->options->part_to_load); + if (!obj->output_p && (obj->options->format_out == nsMimeOutput::nsMimeMessageRaw || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay || + obj->options->format_out == nsMimeOutput::nsMimeMessageAttach)) + { + // Then, check for subpart + unsigned int partlen = strlen(obj->options->part_to_load); + obj->output_p = (strlen(id) >= partlen + 2) && (id[partlen] == '.') && + !strncmp(id, obj->options->part_to_load, partlen); + } + + PR_Free(id); + } + + // If we've decided not to output this part, we also shouldn't be showing it + // as an attachment. + obj->dontShowAsAttachment = !obj->output_p; + + return 0; +} + +static int +MimeObject_parse_buffer (const char *buffer, int32_t size, MimeObject *obj) +{ + NS_ASSERTION(!obj->closed_p, "object shouldn't be closed"); + if (obj->closed_p) return -1; + + return mime_LineBuffer (buffer, size, + &obj->ibuffer, &obj->ibuffer_size, &obj->ibuffer_fp, + true, + ((int (*) (char *, int32_t, void *)) + /* This cast is to turn void into MimeObject */ + obj->clazz->parse_line), + obj); +} + +static int +MimeObject_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + NS_ERROR("shouldn't call this method"); + return -1; +} + +static int +MimeObject_parse_eof (MimeObject *obj, bool abort_p) +{ + if (obj->closed_p) return 0; + NS_ASSERTION(!obj->parsed_p, "obj already parsed"); + + /* If there is still data in the ibuffer, that means that the last line of + this part didn't end in a newline; so push it out anyway (this means that + the parse_line method will be called with a string with no trailing + newline, which isn't the usual case.) + */ + if (!abort_p && + obj->ibuffer_fp > 0) + { + int status = obj->clazz->parse_line (obj->ibuffer, obj->ibuffer_fp, obj); + obj->ibuffer_fp = 0; + if (status < 0) + { + obj->closed_p = true; + return status; + } + } + + obj->closed_p = true; + return 0; +} + +static int +MimeObject_parse_end (MimeObject *obj, bool abort_p) +{ + if (obj->parsed_p) + { + NS_ASSERTION(obj->closed_p, "object should be closed"); + return 0; + } + + /* We won't be needing these buffers any more; nuke 'em. */ + PR_FREEIF(obj->ibuffer); + obj->ibuffer_fp = 0; + obj->ibuffer_size = 0; + PR_FREEIF(obj->obuffer); + obj->obuffer_fp = 0; + obj->obuffer_size = 0; + + obj->parsed_p = true; + return 0; +} + +static bool +MimeObject_displayable_inline_p (MimeObjectClass *clazz, MimeHeaders *hdrs) +{ + NS_ERROR("shouldn't call this method"); + return false; +} + +#if defined(DEBUG) && defined(XP_UNIX) +static int +MimeObject_debug_print (MimeObject *obj, PRFileDesc *stream, int32_t depth) +{ + int i; + char *addr = mime_part_address(obj); + for (i=0; i < depth; i++) + PR_Write(stream, " ", 2); +/* + fprintf(stream, "<%s %s 0x%08X>\n", obj->clazz->class_name, + addr ? addr : "???", + (uint32_t) obj); +*/ + PR_FREEIF(addr); + return 0; +} +#endif diff --git a/mailnews/mime/src/mimeobj.h b/mailnews/mime/src/mimeobj.h new file mode 100644 index 000000000..7ae3eda68 --- /dev/null +++ b/mailnews/mime/src/mimeobj.h @@ -0,0 +1,186 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEOBJ_H_ +#define _MIMEOBJ_H_ + +#include "mimei.h" +#include "prio.h" +/* MimeObject is the base-class for the objects representing all other + MIME types. It provides several methods: + + int initialize (MimeObject *obj) + + This is called from mime_new() when a new instance is allocated. + Subclasses should do whatever setup is necessary from this method, + and should call the superclass's initialize method, unless there's + a specific reason not to. + + void finalize (MimeObject *obj) + + This is called from mime_free() and should free all data associated + with the object. If the object points to other MIME objects, they + should be finalized as well (by calling mime_free(), not by calling + their finalize() methods directly.) + + int parse_buffer (const char *buf, int32_t size, MimeObject *obj) + + This is the method by which you feed arbitrary data into the parser + for this object. Most subclasses will probably inherit this method + from the MimeObject base-class, which line-buffers the data and then + hands it off to the parse_line() method. + + If this object uses a Content-Transfer-Encoding (base64, qp, uue) + then the data may be decoded by parse_buffer() before parse_line() + is called. (The MimeLeaf class provides this functionality.) + + int parse_begin (MimeObject *obj) + Called after `init' but before `parse_line' or `parse_buffer'. + Can be used to initialize various parsing machinery. + + int parse_line (const char *line, int32_t length, MimeObject *obj) + + This method is called (by parse_buffer()) for each complete line of + data handed to the parser, and is the method which most subclasses + will override to implement their parsers. + + When handing data off to a MIME object for parsing, one should always + call the parse_buffer() method, and not call the parse_line() method + directly, since the parse_buffer() method may do other transformations + on the data (like base64 decoding.) + + One should generally not call parse_line() directly, since that could + bypass decoding. One should call parse_buffer() instead. + + int parse_eof (MimeObject *obj, bool abort_p) + + This is called when there is no more data to be handed to the object: + when the parent object is done feeding data to an object being parsed. + Implementors of this method should be sure to also call the parse_eof() + methods of any sub-objects to which they have pointers. + + This is also called by the finalize() method, just before object + destruction, if it has not already been called. + + The `closed_p' instance variable is used to prevent multiple calls to + `parse_eof'. + + int parse_end (MimeObject *obj) + Called after `parse_eof' but before `finalize'. + This can be used to free up any memory no longer needed now that parsing + is done (to avoid surprises due to unexpected method combination, it's + best to free things in this method in preference to `parse_eof'.) + Implementors of this method should be sure to also call the parse_end() + methods of any sub-objects to which they have pointers. + + This is also called by the finalize() method, just before object + destruction, if it has not already been called. + + The `parsed_p' instance variable is used to prevent multiple calls to + `parse_end'. + + + bool displayable_inline_p (MimeObjectClass *class, MimeHeaders *hdrs) + + This method should return true if this class of object will be displayed + directly, as opposed to being displayed as a link. This information is + used by the "multipart/alternative" parser to decide which of its children + is the ``best'' one to display. Note that this is a class method, not + an object method -- there is not yet an instance of this class at the time + that it is called. The `hdrs' provided are the headers of the object that + might be instantiated -- from this, the method may extract additional + infomation that it might need to make its decision. + */ + + +/* this one is typdedef'ed in mimei.h, since it is the base-class. */ +struct MimeObjectClass { + + /* Note: the order of these first five slots is known by MimeDefClass(). + Technically, these are part of the object system, not the MIME code. + */ + const char *class_name; + int instance_size; + struct MimeObjectClass *superclass; + int (*class_initialize) (MimeObjectClass *clazz); + bool class_initialized; + + /* These are the methods shared by all MIME objects. See comment above. + */ + int (*initialize) (MimeObject *obj); + void (*finalize) (MimeObject *obj); + int (*parse_begin) (MimeObject *obj); + int (*parse_buffer) (const char *buf, int32_t size, MimeObject *obj); + int (*parse_line) (const char *line, int32_t length, MimeObject *obj); + int (*parse_eof) (MimeObject *obj, bool abort_p); + int (*parse_end) (MimeObject *obj, bool abort_p); + + bool (*displayable_inline_p) (MimeObjectClass *clazz, MimeHeaders *hdrs); + +#if defined(DEBUG) && defined(XP_UNIX) + int (*debug_print) (MimeObject *obj, PRFileDesc *stream, int32_t depth); +#endif +}; + +extern "C" MimeObjectClass mimeObjectClass; + +/* this one is typdedef'ed in mimei.h, since it is the base-class. */ +struct MimeObject { + MimeObjectClass *clazz; /* Pointer to class object, for `type-of' */ + + MimeHeaders *headers; /* The header data associated with this object; + this is where the content-type, disposition, + description, and other meta-data live. + + For example, the outermost message/rfc822 object + would have NULL here (since it has no parent, + thus no headers to describe it.) However, a + multipart/mixed object, which was the sole + child of that message/rfc822 object, would have + here a copy of the headers which began the + parent object (the headers which describe the + child.) + */ + + char *content_type; /* The MIME content-type and encoding. */ + char *encoding; /* In most cases, these will be the same as the + values to be found in the `headers' object, + but in some cases, the values in these slots + will be more correct than the headers. + */ + + + MimeObject *parent; /* Backpointer to a MimeContainer object. */ + + MimeDisplayOptions *options; /* Display preferences set by caller. */ + + bool closed_p; /* Whether it's done being written to. */ + bool parsed_p; /* Whether the parser has been shut down. */ + bool output_p; /* Whether it should be written. */ + bool dontShowAsAttachment; /* Force an object to not be shown as attachment, + but when is false, it doesn't mean it will be + shown as attachment; specifically, body parts + are never shown as attachments. */ + + /* Read-buffer and write-buffer (on input, `parse_buffer' uses ibuffer to + compose calls to `parse_line'; on output, `obuffer' is used in various + ways by various routines.) These buffers are created and grow as needed. + `ibuffer' should be generally be considered hands-off, and `obuffer' + should generally be considered fair game. + */ + char *ibuffer, *obuffer; + int32_t ibuffer_size, obuffer_size; + int32_t ibuffer_fp, obuffer_fp; +}; + + +#define MimeObject_grow_obuffer(obj, desired_size) \ + (((desired_size) >= (obj)->obuffer_size) ? \ + mime_GrowBuffer ((uint32_t)(desired_size), (uint32_t)sizeof(char), 1024, \ + &(obj)->obuffer, (int32_t*)&(obj)->obuffer_size) \ + : 0) + + +#endif /* _MIMEOBJ_H_ */ diff --git a/mailnews/mime/src/mimepbuf.cpp b/mailnews/mime/src/mimepbuf.cpp new file mode 100644 index 000000000..8e352ed22 --- /dev/null +++ b/mailnews/mime/src/mimepbuf.cpp @@ -0,0 +1,296 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "mimepbuf.h" +#include "mimemoz2.h" +#include "prmem.h" +#include "prio.h" +#include "plstr.h" +#include "nsMimeStringResources.h" +#include "nsNetUtil.h" +#include "nsMsgUtils.h" +// +// External Defines... +// +extern nsresult +nsMsgCreateTempFile(const char *tFileName, nsIFile **tFile); + +/* See mimepbuf.h for a description of the mission of this file. + + Implementation: + + When asked to buffer an object, we first try to malloc() a buffer to + hold the upcoming part. First we try to allocate a 50k buffer, and + then back off by 5k until we are able to complete the allocation, + or are unable to allocate anything. + + As data is handed to us, we store it in the memory buffer, until the + size of the memory buffer is exceeded (including the case where no + memory buffer was able to be allocated at all.) + + Once we've filled the memory buffer, we open a temp file on disk. + Anything that is currently in the memory buffer is then flushed out + to the disk file (and the memory buffer is discarded.) Subsequent + data that is passed in is appended to the file. + + Thus only one of the memory buffer or the disk buffer ever exist at + the same time; and small parts tend to live completely in memory + while large parts tend to live on disk. + + When we are asked to read the data back out of the buffer, we call + the provided read-function with either: the contents of the memory + buffer; or blocks read from the disk file. + */ + +#define TARGET_MEMORY_BUFFER_SIZE (1024 * 50) /* try for 50k mem buffer */ +#define TARGET_MEMORY_BUFFER_QUANTUM (1024 * 5) /* decrease in steps of 5k */ +#define DISK_BUFFER_SIZE (1024 * 10) /* read disk in 10k chunks */ + + +struct MimePartBufferData +{ + char *part_buffer; /* Buffer used for part-lookahead. */ + int32_t part_buffer_fp; /* Active length. */ + int32_t part_buffer_size; /* How big it is. */ + + nsCOMPtr <nsIFile> file_buffer; /* The nsIFile of a temp file used when we + run out of room in the head_buffer. */ + nsCOMPtr <nsIInputStream> input_file_stream; /* A stream to it. */ + nsCOMPtr <nsIOutputStream> output_file_stream; /* A stream to it. */ +}; + +MimePartBufferData * +MimePartBufferCreate (void) +{ + MimePartBufferData *data = PR_NEW(MimePartBufferData); + if (!data) return 0; + memset(data, 0, sizeof(*data)); + return data; +} + + +void +MimePartBufferClose (MimePartBufferData *data) +{ + NS_ASSERTION(data, "MimePartBufferClose: no data"); + if (!data) return; + + if (data->input_file_stream) + { + data->input_file_stream->Close(); + data->input_file_stream = nullptr; + } + + if (data->output_file_stream) + { + data->output_file_stream->Close(); + data->output_file_stream = nullptr; + } +} + + +void +MimePartBufferReset (MimePartBufferData *data) +{ + NS_ASSERTION(data, "MimePartBufferReset: no data"); + if (!data) return; + + PR_FREEIF(data->part_buffer); + data->part_buffer_fp = 0; + + if (data->input_file_stream) + { + data->input_file_stream->Close(); + data->input_file_stream = nullptr; + } + + if (data->output_file_stream) + { + data->output_file_stream->Close(); + data->output_file_stream = nullptr; + } + + if (data->file_buffer) + { + data->file_buffer->Remove(false); + data->file_buffer = nullptr; + } +} + + +void +MimePartBufferDestroy (MimePartBufferData *data) +{ + NS_ASSERTION(data, "MimePartBufferDestroy: no data"); + if (!data) return; + MimePartBufferReset (data); + PR_Free(data); +} + + +int +MimePartBufferWrite (MimePartBufferData *data, + const char *buf, int32_t size) +{ + NS_ASSERTION(data && buf && size > 0, "MimePartBufferWrite: Bad param"); + if (!data || !buf || size <= 0) + return -1; + + /* If we don't yet have a buffer (either memory or file) try and make a + memory buffer. + */ + if (!data->part_buffer && + !data->file_buffer) + { + int target_size = TARGET_MEMORY_BUFFER_SIZE; + while (target_size > 0) + { + data->part_buffer = (char *) PR_MALLOC(target_size); + if (data->part_buffer) break; /* got it! */ + target_size -= TARGET_MEMORY_BUFFER_QUANTUM; /* decrease it and try + again */ + } + + if (data->part_buffer) + data->part_buffer_size = target_size; + else + data->part_buffer_size = 0; + + data->part_buffer_fp = 0; + } + + /* Ok, if at this point we still don't have either kind of buffer, try and + make a file buffer. */ + if (!data->part_buffer && !data->file_buffer) + { + nsCOMPtr <nsIFile> tmpFile; + nsresult rv = nsMsgCreateTempFile("nsma", getter_AddRefs(tmpFile)); + NS_ENSURE_SUCCESS(rv, MIME_UNABLE_TO_OPEN_TMP_FILE); + data->file_buffer = do_QueryInterface(tmpFile); + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(data->output_file_stream), data->file_buffer, PR_WRONLY | PR_CREATE_FILE, 00600); + NS_ENSURE_SUCCESS(rv, MIME_UNABLE_TO_OPEN_TMP_FILE); + } + + NS_ASSERTION(data->part_buffer || data->output_file_stream, "no part_buffer or file_stream"); + + /* If this buf will fit in the memory buffer, put it there. + */ + if (data->part_buffer && + data->part_buffer_fp + size < data->part_buffer_size) + { + memcpy(data->part_buffer + data->part_buffer_fp, + buf, size); + data->part_buffer_fp += size; + } + + /* Otherwise it won't fit; write it to the file instead. */ + else + { + /* If the file isn't open yet, open it, and dump the memory buffer + to it. */ + if (!data->output_file_stream) + { + nsresult rv; + if (!data->file_buffer) + { + nsCOMPtr <nsIFile> tmpFile; + rv = nsMsgCreateTempFile("nsma", getter_AddRefs(tmpFile)); + NS_ENSURE_SUCCESS(rv, MIME_UNABLE_TO_OPEN_TMP_FILE); + data->file_buffer = do_QueryInterface(tmpFile); + + } + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(data->output_file_stream), data->file_buffer, PR_WRONLY | PR_CREATE_FILE, 00600); + NS_ENSURE_SUCCESS(rv, MIME_UNABLE_TO_OPEN_TMP_FILE); + + if (data->part_buffer && data->part_buffer_fp) + { + uint32_t bytesWritten; + nsresult rv = data->output_file_stream->Write(data->part_buffer, + data->part_buffer_fp, &bytesWritten); + NS_ENSURE_SUCCESS(rv, MIME_ERROR_WRITING_FILE); + } + + PR_FREEIF(data->part_buffer); + data->part_buffer_fp = 0; + data->part_buffer_size = 0; + } + + /* Dump this buf to the file. */ + uint32_t bytesWritten; + nsresult rv = data->output_file_stream->Write (buf, size, &bytesWritten); + if (NS_FAILED(rv) || (int32_t) bytesWritten < size) + return MIME_OUT_OF_MEMORY; + } + + return 0; +} + + +int +MimePartBufferRead (MimePartBufferData *data, + MimeConverterOutputCallback read_fn, + void *closure) +{ + int status = 0; + NS_ASSERTION(data, "no data"); + if (!data) return -1; + + if (data->part_buffer) + { + // Read it out of memory. + status = read_fn(data->part_buffer, data->part_buffer_fp, closure); + } + else if (data->file_buffer) + { + /* Read it off disk. + */ + char *buf; + int32_t buf_size = DISK_BUFFER_SIZE; + + NS_ASSERTION(data->part_buffer_size == 0 && data->part_buffer_fp == 0, "buffer size is not null"); + NS_ASSERTION(data->file_buffer, "no file buffer name"); + if (!data->file_buffer) + return -1; + + buf = (char *) PR_MALLOC(buf_size); + if (!buf) + return MIME_OUT_OF_MEMORY; + + // First, close the output file to open the input file! + if (data->output_file_stream) + data->output_file_stream->Close(); + + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(data->input_file_stream), data->file_buffer); + if (NS_FAILED(rv)) + { + PR_Free(buf); + return MIME_UNABLE_TO_OPEN_TMP_FILE; + } + while(1) + { + uint32_t bytesRead = 0; + rv = data->input_file_stream->Read(buf, buf_size - 1, &bytesRead); + if (NS_FAILED(rv) || !bytesRead) + { + break; + } + else + { + /* It would be really nice to be able to yield here, and let + some user events and other input sources get processed. + Oh well. */ + + status = read_fn (buf, bytesRead, closure); + if (status < 0) break; + } + } + PR_Free(buf); + } + + return 0; +} + diff --git a/mailnews/mime/src/mimepbuf.h b/mailnews/mime/src/mimepbuf.h new file mode 100644 index 000000000..200b64a89 --- /dev/null +++ b/mailnews/mime/src/mimepbuf.h @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEPBUF_H_ +#define _MIMEPBUF_H_ + +#include "mimei.h" +#include "modmimee.h" // for MimeConverterOutputCallback + +/* This file provides the ability to save up the entire contents of a MIME + object (of arbitrary size), and then emit it all at once later. The + buffering is done in an efficient way that works well for both very large + and very small objects. + + This is used in two places: + + = The implementation of multipart/alternative uses this code to do a + one-part-lookahead. As it traverses its children, it moves forward + until it finds a part which cannot be displayed; and then it displays + the *previous* part (the last which *could* be displayed.) This code + is used to hold the previous part until it is needed. +*/ + +/* An opaque object used to represent the buffered data. + */ +typedef struct MimePartBufferData MimePartBufferData; + +/* Create an empty part buffer object. + */ +extern MimePartBufferData *MimePartBufferCreate (void); + +/* Assert that the buffer is now full (EOF has been reached on the current + part.) This will free some resources, but leaves the part in the buffer. + After calling MimePartBufferReset, the buffer may be used to store a + different object. + */ +void MimePartBufferClose (MimePartBufferData *data); + +/* Reset a part buffer object to the default state, discarding any currently- + buffered data. + */ +extern void MimePartBufferReset (MimePartBufferData *data); + +/* Free the part buffer itself, and discard any buffered data. + */ +extern void MimePartBufferDestroy (MimePartBufferData *data); + +/* Push a chunk of a MIME object into the buffer. + */ +extern int MimePartBufferWrite (MimePartBufferData *data, + const char *buf, int32_t size); + +/* Read the contents of the buffer back out. This will invoke the provided + read_fn with successive chunks of data until the buffer has been drained. + The provided function may be called once, or multiple times. + */ +extern int +MimePartBufferRead (MimePartBufferData *data, + MimeConverterOutputCallback read_fn, + void *closure); + +#endif /* _MIMEPBUF_H_ */ diff --git a/mailnews/mime/src/mimesun.cpp b/mailnews/mime/src/mimesun.cpp new file mode 100644 index 000000000..84f06a885 --- /dev/null +++ b/mailnews/mime/src/mimesun.cpp @@ -0,0 +1,342 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimesun.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "nsMimeTypes.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include <ctype.h> + +#define MIME_SUPERCLASS mimeMultipartClass +MimeDefClass(MimeSunAttachment, MimeSunAttachmentClass, + mimeSunAttachmentClass, &MIME_SUPERCLASS); + +static MimeMultipartBoundaryType MimeSunAttachment_check_boundary(MimeObject *, + const char *, + int32_t); +static int MimeSunAttachment_create_child(MimeObject *); +static int MimeSunAttachment_parse_child_line (MimeObject *, const char *, int32_t, + bool); +static int MimeSunAttachment_parse_begin (MimeObject *); +static int MimeSunAttachment_parse_eof (MimeObject *, bool); + +static int +MimeSunAttachmentClassInitialize(MimeSunAttachmentClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeMultipartClass *mclass = (MimeMultipartClass *) clazz; + + PR_ASSERT(!oclass->class_initialized); + oclass->parse_begin = MimeSunAttachment_parse_begin; + oclass->parse_eof = MimeSunAttachment_parse_eof; + mclass->check_boundary = MimeSunAttachment_check_boundary; + mclass->create_child = MimeSunAttachment_create_child; + mclass->parse_child_line = MimeSunAttachment_parse_child_line; + return 0; +} + + +static int +MimeSunAttachment_parse_begin (MimeObject *obj) +{ + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + /* Sun messages always have separators at the beginning. */ + return MimeObject_write_separator(obj); +} + +static int +MimeSunAttachment_parse_eof (MimeObject *obj, bool abort_p) +{ + int status = 0; + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + + /* Sun messages always have separators at the end. */ + if (!abort_p) + { + status = MimeObject_write_separator(obj); + if (status < 0) return status; + } + + return 0; +} + + +static MimeMultipartBoundaryType +MimeSunAttachment_check_boundary(MimeObject *obj, const char *line, + int32_t length) +{ + /* ten dashes */ + + if (line && + line[0] == '-' && line[1] == '-' && line[2] == '-' && line[3] == '-' && + line[4] == '-' && line[5] == '-' && line[6] == '-' && line[7] == '-' && + line[8] == '-' && line[9] == '-' && + (line[10] == '\r' || line[10] == '\n')) + return MimeMultipartBoundaryTypeSeparator; + else + return MimeMultipartBoundaryTypeNone; +} + + +static int +MimeSunAttachment_create_child(MimeObject *obj) +{ + MimeMultipart *mult = (MimeMultipart *) obj; + int status = 0; + + char *sun_data_type = 0; + const char *mime_ct = 0, *sun_enc_info = 0, *mime_cte = 0; + char *mime_ct2 = 0; /* sometimes we need to copy; this is for freeing. */ + MimeObject *child = 0; + + mult->state = MimeMultipartPartLine; + + sun_data_type = (mult->hdrs + ? MimeHeaders_get (mult->hdrs, HEADER_X_SUN_DATA_TYPE, + true, false) + : 0); + if (sun_data_type) + { + int i; + static const struct { const char *in, *out; } sun_types[] = { + + /* Convert recognised Sun types to the corresponding MIME types, + and convert unrecognized ones based on the file extension and + the mime.types file. + + These are the magic types used by MailTool that I can determine. + The only actual written spec I've found only listed the first few. + The rest were found by inspection (both of real-world messages, + and by running `strings' on the MailTool binary, and on the file + /usr/openwin/lib/cetables/cetables (the "Class Engine", Sun's + equivalent to .mailcap and mime.types.) + */ + { "default", TEXT_PLAIN }, + { "default-doc", TEXT_PLAIN }, + { "text", TEXT_PLAIN }, + { "scribe", TEXT_PLAIN }, + { "sgml", TEXT_PLAIN }, + { "tex", TEXT_PLAIN }, + { "troff", TEXT_PLAIN }, + { "c-file", TEXT_PLAIN }, + { "h-file", TEXT_PLAIN }, + { "readme-file", TEXT_PLAIN }, + { "shell-script", TEXT_PLAIN }, + { "cshell-script", TEXT_PLAIN }, + { "makefile", TEXT_PLAIN }, + { "hidden-docs", TEXT_PLAIN }, + { "message", MESSAGE_RFC822 }, + { "mail-message", MESSAGE_RFC822 }, + { "mail-file", TEXT_PLAIN }, + { "gif-file", IMAGE_GIF }, + { "jpeg-file", IMAGE_JPG }, + { "ppm-file", IMAGE_PPM }, + { "pgm-file", "image/x-portable-graymap" }, + { "pbm-file", "image/x-portable-bitmap" }, + { "xpm-file", "image/x-xpixmap" }, + { "ilbm-file", "image/ilbm" }, + { "tiff-file", "image/tiff" }, + { "photocd-file", "image/x-photo-cd" }, + { "sun-raster", "image/x-sun-raster" }, + { "audio-file", AUDIO_BASIC }, + { "postscript", APPLICATION_POSTSCRIPT }, + { "postscript-file", APPLICATION_POSTSCRIPT }, + { "framemaker-document", "application/x-framemaker" }, + { "sundraw-document", "application/x-sun-draw" }, + { "sunpaint-document", "application/x-sun-paint" }, + { "sunwrite-document", "application/x-sun-write" }, + { "islanddraw-document", "application/x-island-draw" }, + { "islandpaint-document", "application/x-island-paint" }, + { "islandwrite-document", "application/x-island-write" }, + { "sun-executable", APPLICATION_OCTET_STREAM }, + { "default-app", APPLICATION_OCTET_STREAM }, + { 0, 0 }}; + for (i = 0; sun_types[i].in; i++) + if (!PL_strcasecmp(sun_data_type, sun_types[i].in)) + { + mime_ct = sun_types[i].out; + break; + } + } + + /* If we didn't find a type, look at the extension on the file name. + */ + if (!mime_ct && + obj->options && + obj->options->file_type_fn) + { + char *name = MimeHeaders_get_name(mult->hdrs, obj->options); + if (name) + { + mime_ct2 = obj->options->file_type_fn(name, + obj->options->stream_closure); + mime_ct = mime_ct2; + PR_Free(name); + if (!mime_ct2 || !PL_strcasecmp (mime_ct2, UNKNOWN_CONTENT_TYPE)) + { + PR_FREEIF(mime_ct2); + mime_ct = APPLICATION_OCTET_STREAM; + } + } + } + if (!mime_ct) + mime_ct = APPLICATION_OCTET_STREAM; + + PR_FREEIF(sun_data_type); + + + /* Convert recognised Sun encodings to the corresponding MIME encodings. + However, if the X-Sun-Encoding-Info field contains more than one + encoding (that is, contains a comma) then assign it the encoding of + the *rightmost* element in the list; and change its Content-Type to + application/octet-stream. Examples: + + Sun Type: Translates To: + ================== ==================== + type: TEXT type: text/plain + encoding: COMPRESS encoding: x-compress + + type: POSTSCRIPT type: application/x-compress + encoding: COMPRESS,UUENCODE encoding: x-uuencode + + type: TEXT type: application/octet-stream + encoding: UNKNOWN,UUENCODE encoding: x-uuencode + */ + + sun_data_type = (mult->hdrs + ? MimeHeaders_get (mult->hdrs, HEADER_X_SUN_ENCODING_INFO, + false,false) + : 0); + sun_enc_info = sun_data_type; + + + /* this "adpcm-compress" pseudo-encoding is some random junk that + MailTool adds to the encoding description of .AU files: we can + ignore it if it is the leftmost element of the encoding field. + (It looks like it's created via `audioconvert -f g721'. Why? + Who knows.) + */ + if (sun_enc_info && !PL_strncasecmp (sun_enc_info, "adpcm-compress", 14)) + { + sun_enc_info += 14; + while (IS_SPACE(*sun_enc_info) || *sun_enc_info == ',') + sun_enc_info++; + } + + /* Extract the last element of the encoding field, changing the content + type if necessary (as described above.) + */ + if (sun_enc_info && *sun_enc_info) + { + const char *prev; + const char *end = PL_strrchr(sun_enc_info, ','); + if (end) + { + const char *start = sun_enc_info; + sun_enc_info = end + 1; + while (IS_SPACE(*sun_enc_info)) + sun_enc_info++; + for (prev = end-1; prev > start && *prev != ','; prev--) + ; + if (*prev == ',') prev++; + + if (!PL_strncasecmp (prev, "uuencode", end-prev)) + mime_ct = APPLICATION_UUENCODE; + else if (!PL_strncasecmp (prev, "gzip", end-prev)) + mime_ct = APPLICATION_GZIP; + else if (!PL_strncasecmp (prev, "compress", end-prev)) + mime_ct = APPLICATION_COMPRESS; + else if (!PL_strncasecmp (prev, "default-compress", end-prev)) + mime_ct = APPLICATION_COMPRESS; + else + mime_ct = APPLICATION_OCTET_STREAM; + } + } + + /* Convert the remaining Sun encoding to a MIME encoding. + If it isn't known, change the content-type instead. + */ + if (!sun_enc_info || !*sun_enc_info) + ; + else if (!PL_strcasecmp(sun_enc_info,"compress")) mime_cte = ENCODING_COMPRESS; + else if (!PL_strcasecmp(sun_enc_info,"uuencode")) mime_cte = ENCODING_UUENCODE; + else if (!PL_strcasecmp(sun_enc_info,"gzip")) mime_cte = ENCODING_GZIP; + else mime_ct = APPLICATION_OCTET_STREAM; + + PR_FREEIF(sun_data_type); + + + /* Now that we know its type and encoding, create a MimeObject to represent + this part. + */ + child = mime_create(mime_ct, mult->hdrs, obj->options); + if (!child) + { + status = MIME_OUT_OF_MEMORY; + goto FAIL; + } + + /* Fake out the child's content-type and encoding (it probably doesn't have + one right now, because the X-Sun- headers aren't generally recognised by + the rest of this library.) + */ + PR_FREEIF(child->content_type); + PR_FREEIF(child->encoding); + PR_ASSERT(mime_ct); + child->content_type = (mime_ct ? strdup(mime_ct) : 0); + child->encoding = (mime_cte ? strdup(mime_cte) : 0); + + status = ((MimeContainerClass *) obj->clazz)->add_child(obj, child); + if (status < 0) + { + mime_free(child); + child = 0; + goto FAIL; + } + + /* Sun attachments always have separators between parts. */ + status = MimeObject_write_separator(obj); + if (status < 0) goto FAIL; + + /* And now that we've added this new object to our list of + children, start its parser going. */ + status = child->clazz->parse_begin(child); + if (status < 0) goto FAIL; + + FAIL: + PR_FREEIF(mime_ct2); + PR_FREEIF(sun_data_type); + return status; +} + + +static int +MimeSunAttachment_parse_child_line (MimeObject *obj, const char *line, int32_t length, + bool first_line_p) +{ + MimeContainer *cont = (MimeContainer *) obj; + MimeObject *kid; + + /* This is simpler than MimeMultipart->parse_child_line in that it doesn't + play games about body parts without trailing newlines. + */ + + PR_ASSERT(cont->nchildren > 0); + if (cont->nchildren <= 0) + return -1; + + kid = cont->children[cont->nchildren-1]; + PR_ASSERT(kid); + if (!kid) return -1; + + return kid->clazz->parse_buffer (line, length, kid); +} diff --git a/mailnews/mime/src/mimesun.h b/mailnews/mime/src/mimesun.h new file mode 100644 index 000000000..5ffd7dade --- /dev/null +++ b/mailnews/mime/src/mimesun.h @@ -0,0 +1,59 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMESUN_H_ +#define _MIMESUN_H_ + +#include "mimemult.h" + +/* MimeSunAttachment is the class for X-Sun-Attachment message contents, which + is the Content-Type assigned by that pile of garbage called MailTool. This + is not a MIME type per se, but it's very similar to multipart/mixed, so it's + easy to parse. Lots of people use MailTool, so what the hell. + + The format is this: + + = Content-Type is X-Sun-Attachment + = parts are separated by lines of exactly ten dashes + = just after the dashes comes a block of headers, including: + + X-Sun-Data-Type: (manditory) + Values are Text, Postscript, Scribe, SGML, TeX, Troff, DVI, + and Message. + + X-Sun-Encoding-Info: (optional) + Ordered, comma-separated values, including Compress and Uuencode. + + X-Sun-Data-Name: (optional) + File name, maybe. + + X-Sun-Data-Description: (optional) + Longer text. + + X-Sun-Content-Lines: (manditory, unless Length is present) + Number of lines in the body, not counting headers and the blank + line that follows them. + + X-Sun-Content-Length: (manditory, unless Lines is present) + Bytes, presumably using Unix line terminators. + */ + +typedef struct MimeSunAttachmentClass MimeSunAttachmentClass; +typedef struct MimeSunAttachment MimeSunAttachment; + +struct MimeSunAttachmentClass { + MimeMultipartClass multipart; +}; + +extern MimeSunAttachmentClass mimeSunAttachmentClass; + +struct MimeSunAttachment { + MimeMultipart multipart; +}; + +#define MimeSunAttachmentClassInitializer(ITYPE,CSUPER) \ + { MimeMultipartClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMESUN_H_ */ diff --git a/mailnews/mime/src/mimetenr.cpp b/mailnews/mime/src/mimetenr.cpp new file mode 100644 index 000000000..4fecb6bcf --- /dev/null +++ b/mailnews/mime/src/mimetenr.cpp @@ -0,0 +1,28 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimetenr.h" +#include "prlog.h" + +/* All the magic for this class is in mimetric.c; since text/enriched and + text/richtext are so similar, it was easiest to implement them in the + same method (but this is a subclass anyway just for general goodness.) + */ + +#define MIME_SUPERCLASS mimeInlineTextRichtextClass +MimeDefClass(MimeInlineTextEnriched, MimeInlineTextEnrichedClass, + mimeInlineTextEnrichedClass, &MIME_SUPERCLASS); + +static int +MimeInlineTextEnrichedClassInitialize(MimeInlineTextEnrichedClass *clazz) +{ +#ifdef DEBUG + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); +#endif + MimeInlineTextRichtextClass *rclass = (MimeInlineTextRichtextClass *) clazz; + rclass->enriched_p = true; + return 0; +} diff --git a/mailnews/mime/src/mimetenr.h b/mailnews/mime/src/mimetenr.h new file mode 100644 index 000000000..34608f547 --- /dev/null +++ b/mailnews/mime/src/mimetenr.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETENR_H_ +#define _MIMETENR_H_ + +#include "mimetric.h" + +/* The MimeInlineTextEnriched class implements the text/enriched MIME content + type, as defined in RFC 1563. It does this largely by virtue of being a + subclass of the MimeInlineTextRichtext class. + */ + +typedef struct MimeInlineTextEnrichedClass MimeInlineTextEnrichedClass; +typedef struct MimeInlineTextEnriched MimeInlineTextEnriched; + +struct MimeInlineTextEnrichedClass { + MimeInlineTextRichtextClass text; +}; + +extern MimeInlineTextEnrichedClass mimeInlineTextEnrichedClass; + +struct MimeInlineTextEnriched { + MimeInlineTextRichtext richtext; +}; + +#define MimeInlineTextEnrichedClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextRichtextClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETENR_H_ */ diff --git a/mailnews/mime/src/mimetext.cpp b/mailnews/mime/src/mimetext.cpp new file mode 100644 index 000000000..a854348c5 --- /dev/null +++ b/mailnews/mime/src/mimetext.cpp @@ -0,0 +1,544 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * This Original Code has been modified by IBM Corporation. Modifications made by IBM + * described herein are Copyright (c) International Business Machines Corporation, 2000. + * Modifications to Mozilla code or documentation identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 04/20/2000 IBM Corp. OS/2 VisualAge build. + */ +#include "mimetext.h" +#include "mimebuf.h" +#include "mimethtm.h" +#include "comi18n.h" +#include "mimemoz2.h" + +#include "prlog.h" +#include "prmem.h" +#include "plstr.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIServiceManager.h" +#include "nsIPrefLocalizedString.h" +#include "nsMsgUtils.h" +#include "nsMimeTypes.h" +#include "nsServiceManagerUtils.h" + +#define MIME_SUPERCLASS mimeLeafClass +MimeDefClass(MimeInlineText, MimeInlineTextClass, mimeInlineTextClass, + &MIME_SUPERCLASS); + +static int MimeInlineText_initialize (MimeObject *); +static void MimeInlineText_finalize (MimeObject *); +static int MimeInlineText_rot13_line (MimeObject *, char *line, int32_t length); +static int MimeInlineText_parse_eof (MimeObject *obj, bool abort_p); +static int MimeInlineText_parse_end (MimeObject *, bool); +static int MimeInlineText_parse_decoded_buffer (const char *, int32_t, MimeObject *); +static int MimeInlineText_rotate_convert_and_parse_line(char *, int32_t, + MimeObject *); +static int MimeInlineText_open_dam(char *line, int32_t length, MimeObject *obj); +static int MimeInlineText_initializeCharset(MimeObject *obj); + +static int +MimeInlineTextClassInitialize(MimeInlineTextClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + MimeLeafClass *lclass = (MimeLeafClass *) clazz; + PR_ASSERT(!oclass->class_initialized); + oclass->initialize = MimeInlineText_initialize; + oclass->finalize = MimeInlineText_finalize; + oclass->parse_eof = MimeInlineText_parse_eof; + oclass->parse_end = MimeInlineText_parse_end; + clazz->rot13_line = MimeInlineText_rot13_line; + clazz->initialize_charset = MimeInlineText_initializeCharset; + lclass->parse_decoded_buffer = MimeInlineText_parse_decoded_buffer; + return 0; +} + +static int +MimeInlineText_initialize (MimeObject *obj) +{ + /* This is an abstract class; it shouldn't be directly instantiated. */ + PR_ASSERT(obj->clazz != (MimeObjectClass *) &mimeInlineTextClass); + + ((MimeInlineText *) obj)->initializeCharset = false; + ((MimeInlineText *) obj)->needUpdateMsgWinCharset = false; + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(obj); +} + +static int MimeInlineText_initializeCharset(MimeObject *obj) +{ + MimeInlineText *text = (MimeInlineText *) obj; + + text->inputAutodetect = false; + text->charsetOverridable = false; + + /* Figure out an appropriate charset for this object. + */ + if (!text->charset && obj->headers) + { + if (obj->options && obj->options->override_charset) + { + text->charset = strdup(obj->options->default_charset); + } + else + { + char *ct = MimeHeaders_get (obj->headers, HEADER_CONTENT_TYPE, + false, false); + if (ct) + { + text->charset = MimeHeaders_get_parameter (ct, "charset", NULL, NULL); + PR_Free(ct); + } + + if (!text->charset) + { + /* If we didn't find "Content-Type: ...; charset=XX" then look + for "X-Sun-Charset: XX" instead. (Maybe this should be done + in MimeSunAttachmentClass, but it's harder there than here.) + */ + text->charset = MimeHeaders_get (obj->headers, + HEADER_X_SUN_CHARSET, + false, false); + } + + /* iMIP entities without an explicit charset parameter default to + US-ASCII (RFC 2447, section 2.4). However, Microsoft Outlook generates + UTF-8 but omits the charset parameter. + When no charset is defined by the container (e.g. iMIP), iCalendar + files default to UTF-8 (RFC 2445, section 4.1.4). + */ + if (!text->charset && + obj->content_type && + !PL_strcasecmp(obj->content_type, TEXT_CALENDAR)) + text->charset = strdup("UTF-8"); + + if (!text->charset) + { + nsresult res; + + text->charsetOverridable = true; + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &res)); + if (NS_SUCCEEDED(res)) + { + nsCOMPtr<nsIPrefLocalizedString> str; + if (NS_SUCCEEDED(prefBranch->GetComplexValue("intl.charset.detector", NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(str)))) { + //only if we can get autodetector name correctly, do we set this to true + text->inputAutodetect = true; + } + } + + if (obj->options && obj->options->default_charset) + text->charset = strdup(obj->options->default_charset); + else + { + if (NS_SUCCEEDED(res)) + { + nsString value; + NS_GetLocalizedUnicharPreferenceWithDefault(prefBranch, "mailnews.view_default_charset", EmptyString(), value); + text->charset = ToNewUTF8String(value); + } + else + text->charset = strdup(""); + } + } + } + } + + if (text->inputAutodetect) + { + //we need to prepare lineDam for charset detection + text->lineDamBuffer = (char*)PR_Malloc(DAM_MAX_BUFFER_SIZE); + text->lineDamPtrs = (char**)PR_Malloc(DAM_MAX_LINES*sizeof(char*)); + text->curDamOffset = 0; + text->lastLineInDam = 0; + if (!text->lineDamBuffer || !text->lineDamPtrs) + { + text->inputAutodetect = false; + PR_FREEIF(text->lineDamBuffer); + PR_FREEIF(text->lineDamPtrs); + } + } + + text->initializeCharset = true; + + return 0; +} + +static void +MimeInlineText_finalize (MimeObject *obj) +{ + MimeInlineText *text = (MimeInlineText *) obj; + + obj->clazz->parse_eof (obj, false); + obj->clazz->parse_end (obj, false); + + text->inputDecoder = nullptr; + text->utf8Encoder = nullptr; + PR_FREEIF(text->charset); + + /* Should have been freed by parse_eof, but just in case... */ + PR_ASSERT(!text->cbuffer); + PR_FREEIF (text->cbuffer); + + if (text->inputAutodetect) { + PR_FREEIF(text->lineDamBuffer); + PR_FREEIF(text->lineDamPtrs); + text->inputAutodetect = false; + } + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize (obj); +} + + +static int +MimeInlineText_parse_eof (MimeObject *obj, bool abort_p) +{ + int status; + + if (obj->closed_p) return 0; + NS_ASSERTION(!obj->parsed_p, "obj already parsed"); + + MimeInlineText *text = (MimeInlineText *) obj; + + /* Flush any buffered data from the MimeLeaf's decoder */ + status = ((MimeLeafClass*)&MIME_SUPERCLASS)->close_decoder(obj); + if (status < 0) return status; + + /* If there is still data in the ibuffer, that means that the last + line of this part didn't end in a newline; so push it out anyway + (this means that the parse_line method will be called with a string + with no trailing newline, which isn't the usual case). We do this + here, rather than in MimeObject_parse_eof, because MimeObject isn't + aware of the rotating-and-converting / charset detection we need to + do first. + */ + if (!abort_p && obj->ibuffer_fp > 0) + { + status = MimeInlineText_rotate_convert_and_parse_line (obj->ibuffer, + obj->ibuffer_fp, + obj); + obj->ibuffer_fp = 0; + if (status < 0) + { + //we haven't find charset yet? Do it before return + if (text->inputAutodetect) + status = MimeInlineText_open_dam(nullptr, 0, obj); + + obj->closed_p = true; + return status; + } + } + + //we haven't find charset yet? now its the time + if (text->inputAutodetect) + status = MimeInlineText_open_dam(nullptr, 0, obj); + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof (obj, abort_p); +} + +static int +MimeInlineText_parse_end (MimeObject *obj, bool abort_p) +{ + MimeInlineText *text = (MimeInlineText *) obj; + + if (obj->parsed_p) + { + PR_ASSERT(obj->closed_p); + return 0; + } + + /* We won't be needing this buffer any more; nuke it. */ + PR_FREEIF(text->cbuffer); + text->cbuffer_size = 0; + + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_end (obj, abort_p); +} + + +/* This maps A-M to N-Z and N-Z to A-M. All other characters are left alone. + (Comments in GNUS imply that for Japanese, one should rotate by 47?) + */ +static const unsigned char MimeInlineText_rot13_table[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 91, 92, 93, 94, 95, 96, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 97, 98, + 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, + 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, + 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, + 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, + 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, + 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, + 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, + 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, + 247, 248, 249, 250, 251, 252, 253, 254, 255 }; + +static int +MimeInlineText_rot13_line (MimeObject *obj, char *line, int32_t length) +{ + unsigned char *s, *end; + PR_ASSERT(line); + if (!line) return -1; + s = (unsigned char *) line; + end = s + length; + while (s < end) + { + *s = MimeInlineText_rot13_table[*s]; + s++; + } + return 0; +} + + +static int +MimeInlineText_parse_decoded_buffer (const char *buf, int32_t size, MimeObject *obj) +{ + PR_ASSERT(!obj->closed_p); + if (obj->closed_p) return -1; + + /* MimeLeaf takes care of this. */ + PR_ASSERT(obj->output_p && obj->options && obj->options->output_fn); + if (!obj->options) return -1; + + /* If we're supposed to write this object, but aren't supposed to convert + it to HTML, simply pass it through unaltered. */ + if (!obj->options->write_html_p && obj->options->format_out != nsMimeOutput::nsMimeMessageAttach) + return MimeObject_write(obj, buf, size, true); + + /* This is just like the parse_decoded_buffer method we inherit from the + MimeLeaf class, except that we line-buffer to our own wrapper on the + `parse_line' method instead of calling the `parse_line' method directly. + */ + return mime_LineBuffer (buf, size, + &obj->ibuffer, &obj->ibuffer_size, &obj->ibuffer_fp, + true, + ((int (*) (char *, int32_t, void *)) + /* This cast is to turn void into MimeObject */ + MimeInlineText_rotate_convert_and_parse_line), + obj); +} + + +#define MimeInlineText_grow_cbuffer(text, desired_size) \ + (((desired_size) >= (text)->cbuffer_size) ? \ + mime_GrowBuffer ((desired_size), sizeof(char), 100, \ + &(text)->cbuffer, &(text)->cbuffer_size) \ + : 0) + +static int +MimeInlineText_convert_and_parse_line(char *line, int32_t length, MimeObject *obj) +{ + int status; + char *converted = 0; + int32_t converted_len = 0; + + MimeInlineText *text = (MimeInlineText *) obj; + + //in case of charset autodetection, charset can be override by meta charset + if (text->charsetOverridable) { + if (mime_typep(obj, (MimeObjectClass *) &mimeInlineTextHTMLClass)) + { + MimeInlineTextHTML *textHTML = (MimeInlineTextHTML *) obj; + if (textHTML->charset && + *textHTML->charset && + strcmp(textHTML->charset, text->charset)) + { + //if meta tag specified charset is different from our detected result, use meta charset. + //but we don't want to redo previous lines + MIME_get_unicode_decoder(textHTML->charset, getter_AddRefs(text->inputDecoder)); + PR_FREEIF(text->charset); + text->charset = strdup(textHTML->charset); + + //update MsgWindow charset if we are instructed to do so + if (text->needUpdateMsgWinCharset && *text->charset) + SetMailCharacterSetToMsgWindow(obj, text->charset); + } + } + } + + //initiate decoder if not yet + if (text->inputDecoder == nullptr) + MIME_get_unicode_decoder(text->charset, getter_AddRefs(text->inputDecoder)); + // If no decoder found, use ""UTF-8"", that will map most non-US-ASCII chars as invalid + // A pure-ASCII only decoder would be better, but there is none + if (text->inputDecoder == nullptr) + MIME_get_unicode_decoder("UTF-8", getter_AddRefs(text->inputDecoder)); + if (text->utf8Encoder == nullptr) + MIME_get_unicode_encoder("UTF-8", getter_AddRefs(text->utf8Encoder)); + + bool useInputCharsetConverter = obj->options->m_inputCharsetToUnicodeDecoder && !PL_strcasecmp(text->charset, obj->options->charsetForCachedInputDecoder.get()); + + if (useInputCharsetConverter) + status = obj->options->charset_conversion_fn(line, length, + text->charset, + "UTF-8", + &converted, + &converted_len, + obj->options->stream_closure, obj->options->m_inputCharsetToUnicodeDecoder, + obj->options->m_unicodeToUTF8Encoder); + else + status = obj->options->charset_conversion_fn(line, length, + text->charset, + "UTF-8", + &converted, + &converted_len, + obj->options->stream_closure, (nsIUnicodeDecoder*)text->inputDecoder, + (nsIUnicodeEncoder*)text->utf8Encoder); + + if (status < 0) + { + PR_FREEIF(converted); + return status; + } + + if (converted) + { + line = converted; + length = converted_len; + } + + /* Now that the line has been converted, call the subclass's parse_line + method with the decoded data. */ + status = obj->clazz->parse_line(line, length, obj); + PR_FREEIF(converted); + + return status; +} + +//In this function call, all buffered lines in lineDam will be sent to charset detector +// and a charset will be used to parse all those line and following lines in this mime obj. +static int +MimeInlineText_open_dam(char *line, int32_t length, MimeObject *obj) +{ + MimeInlineText *text = (MimeInlineText *) obj; + const char* detectedCharset = nullptr; + nsresult res = NS_OK; + int status = 0; + int32_t i; + + if (text->curDamOffset <= 0) { + //there is nothing in dam, use current line for detection + if (length > 0) { + res = MIME_detect_charset(line, length, &detectedCharset); + } + } else { + //we have stuff in dam, use the one + res = MIME_detect_charset(text->lineDamBuffer, text->curDamOffset, &detectedCharset); + } + + //set the charset for this obj + if (NS_SUCCEEDED(res) && detectedCharset && *detectedCharset) { + PR_FREEIF(text->charset); + text->charset = strdup(detectedCharset); + + //update MsgWindow charset if we are instructed to do so + if (text->needUpdateMsgWinCharset && *text->charset) + SetMailCharacterSetToMsgWindow(obj, text->charset); + } + + //process dam and line using the charset + if (text->curDamOffset) { + for (i = 0; i < text->lastLineInDam-1; i++) + { + status = MimeInlineText_convert_and_parse_line( + text->lineDamPtrs[i], + text->lineDamPtrs[i+1] - text->lineDamPtrs[i], + obj ); + } + status = MimeInlineText_convert_and_parse_line( + text->lineDamPtrs[i], + text->lineDamBuffer + text->curDamOffset - text->lineDamPtrs[i], + obj ); + } + + if (length) + status = MimeInlineText_convert_and_parse_line(line, length, obj); + + PR_Free(text->lineDamPtrs); + PR_Free(text->lineDamBuffer); + text->lineDamPtrs = nullptr; + text->lineDamBuffer = nullptr; + text->inputAutodetect = false; + + return status; +} + + +static int +MimeInlineText_rotate_convert_and_parse_line(char *line, int32_t length, + MimeObject *obj) +{ + int status = 0; + MimeInlineTextClass *textc = (MimeInlineTextClass *) obj->clazz; + + PR_ASSERT(!obj->closed_p); + if (obj->closed_p) return -1; + + /* Rotate the line, if desired (this happens on the raw data, before any + charset conversion.) */ + if (obj->options && obj->options->rot13_p) + { + status = textc->rot13_line(obj, line, length); + if (status < 0) return status; + } + + // Now convert to the canonical charset, if desired. + // + bool doConvert = true; + // Don't convert vCard data + if ( ( (obj->content_type) && (!PL_strcasecmp(obj->content_type, TEXT_VCARD)) ) || + (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs) + || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach) + doConvert = false; + + // Only convert if the user prefs is false + if ( (obj->options && obj->options->charset_conversion_fn) && + (!obj->options->force_user_charset) && + (doConvert) + ) + { + MimeInlineText *text = (MimeInlineText *) obj; + + if (!text->initializeCharset) + { + MimeInlineText_initializeCharset(obj); + //update MsgWindow charset if we are instructed to do so + if (text->needUpdateMsgWinCharset && *text->charset) + SetMailCharacterSetToMsgWindow(obj, text->charset); + } + + //if autodetect is on, push line to dam + if (text->inputAutodetect) + { + //see if we reach the lineDam buffer limit, if so, there is no need to keep buffering + if (text->lastLineInDam >= DAM_MAX_LINES || + DAM_MAX_BUFFER_SIZE - text->curDamOffset <= length) { + //we let open dam process this line as well as thing that already in Dam + //In case there is nothing in dam because this line is too big, we need to + //perform autodetect on this line + status = MimeInlineText_open_dam(line, length, obj); + } + else { + //buffering current line + text->lineDamPtrs[text->lastLineInDam] = text->lineDamBuffer + text->curDamOffset; + memcpy(text->lineDamPtrs[text->lastLineInDam], line, length); + text->lastLineInDam++; + text->curDamOffset += length; + } + } + else + status = MimeInlineText_convert_and_parse_line(line, length, obj); + } + else + status = obj->clazz->parse_line(line, length, obj); + + return status; +} diff --git a/mailnews/mime/src/mimetext.h b/mailnews/mime/src/mimetext.h new file mode 100644 index 000000000..78cb6bf3a --- /dev/null +++ b/mailnews/mime/src/mimetext.h @@ -0,0 +1,82 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETEXT_H_ +#define _MIMETEXT_H_ + +#include "mimeleaf.h" + +/* The MimeInlineText class is the superclass of all handlers for the + MIME text/ content types (which convert various text formats to HTML, + in one form or another.) + + It provides two services: + + = if ROT13 decoding is desired, the text will be rotated before + the `parse_line' method it called; + + = text will be converted from the message's charset to the "target" + charset before the `parse_line' method is called. + + The contract with charset-conversion is that the converted data will + be such that one may interpret any octets (8-bit bytes) in the data + which are in the range of the ASCII characters (0-127) as ASCII + characters. It is explicitly legal, for example, to scan through + the string for "<" and replace it with "<", and to search for things + that look like URLs and to wrap them with interesting HTML tags. + + The charset to which we convert will probably be UTF-8 (an encoding of + the Unicode character set, with the feature that all octets with the + high bit off have the same interpretations as ASCII.) + + #### NOTE: if it turns out that we use JIS (ISO-2022-JP) as the target + encoding, then this is not quite true; it is safe to search for the + low ASCII values (under hex 0x40, octal 0100, which is '@') but it + is NOT safe to search for values higher than that -- they may be + being used as the subsequent bytes in a multi-byte escape sequence. + It's a nice coincidence that HTML's critical characters ("<", ">", + and "&") have values under 0x40... + */ + +typedef struct MimeInlineTextClass MimeInlineTextClass; +typedef struct MimeInlineText MimeInlineText; + +struct MimeInlineTextClass { + MimeLeafClass leaf; + int (*rot13_line) (MimeObject *obj, char *line, int32_t length); + int (*convert_line_charset) (MimeObject *obj, char *line, int32_t length); + int (*initialize_charset) (MimeObject *obj); +}; + +extern MimeInlineTextClass mimeInlineTextClass; + +#define DAM_MAX_BUFFER_SIZE 8*1024 +#define DAM_MAX_LINES 1024 + +struct MimeInlineText { + MimeLeaf leaf; /* superclass variables */ + char *charset; /* The charset from the content-type of this + object, or the caller-specified overrides + or defaults. */ + bool charsetOverridable; + bool needUpdateMsgWinCharset; + char *cbuffer; /* Buffer used for charset conversion. */ + int32_t cbuffer_size; + + nsCOMPtr<nsIUnicodeDecoder> inputDecoder; + nsCOMPtr<nsIUnicodeEncoder> utf8Encoder; + + bool inputAutodetect; + bool initializeCharset; + int32_t lastLineInDam; + int32_t curDamOffset; + char *lineDamBuffer; + char **lineDamPtrs; +}; + +#define MimeInlineTextClassInitializer(ITYPE,CSUPER) \ + { MimeLeafClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETEXT_H_ */ diff --git a/mailnews/mime/src/mimethpl.cpp b/mailnews/mime/src/mimethpl.cpp new file mode 100644 index 000000000..9d01229f9 --- /dev/null +++ b/mailnews/mime/src/mimethpl.cpp @@ -0,0 +1,165 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* TODO: + - If you Save As File .html with this mode, you get a total mess. + - Print is untested (crashes in all modes). +*/ +/* If you fix a bug here, check, if the same is also in mimethsa, because that + class is based on this class. */ + +#include "mimethpl.h" +#include "prlog.h" +#include "msgCore.h" +#include "mimemoz2.h" +#include "nsStringGlue.h" +#include "nsIDocumentEncoder.h" // for output flags + +#define MIME_SUPERCLASS mimeInlineTextPlainClass +/* I should use the Flowed class as base (because our HTML->TXT converter + can generate flowed, and we tell it to) - this would get a bit nicer + rendering. However, that class is more picky about line endings + and I currently don't feel like splitting up the generated plaintext + into separate lines again. So, I just throw the whole message at once + at the TextPlain_parse_line function - it happens to work *g*. */ +MimeDefClass(MimeInlineTextHTMLAsPlaintext, MimeInlineTextHTMLAsPlaintextClass, + mimeInlineTextHTMLAsPlaintextClass, &MIME_SUPERCLASS); + +static int MimeInlineTextHTMLAsPlaintext_parse_line (const char *, int32_t, + MimeObject *); +static int MimeInlineTextHTMLAsPlaintext_parse_begin (MimeObject *obj); +static int MimeInlineTextHTMLAsPlaintext_parse_eof (MimeObject *, bool); +static void MimeInlineTextHTMLAsPlaintext_finalize (MimeObject *obj); + +static int +MimeInlineTextHTMLAsPlaintextClassInitialize(MimeInlineTextHTMLAsPlaintextClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "problem with superclass"); + oclass->parse_line = MimeInlineTextHTMLAsPlaintext_parse_line; + oclass->parse_begin = MimeInlineTextHTMLAsPlaintext_parse_begin; + oclass->parse_eof = MimeInlineTextHTMLAsPlaintext_parse_eof; + oclass->finalize = MimeInlineTextHTMLAsPlaintext_finalize; + + return 0; +} + +static int +MimeInlineTextHTMLAsPlaintext_parse_begin (MimeObject *obj) +{ + MimeInlineTextHTMLAsPlaintext *textHTMLPlain = + (MimeInlineTextHTMLAsPlaintext *) obj; + textHTMLPlain->complete_buffer = new nsString(); + // Let's just hope that libmime won't have the idea to call begin twice... + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); +} + +static int +MimeInlineTextHTMLAsPlaintext_parse_eof (MimeObject *obj, bool abort_p) +{ + if (obj->closed_p) + return 0; + + // This is a hack. We need to call parse_eof() of the super class to flush out any buffered data. + // We can't call it yet for our direct super class, because it would "close" the output + // (write tags such as </pre> and </div>). We'll do that after parsing the buffer. + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->superclass->parse_eof(obj, abort_p); + if (status < 0) + return status; + + MimeInlineTextHTMLAsPlaintext *textHTMLPlain = + (MimeInlineTextHTMLAsPlaintext *) obj; + + if (!textHTMLPlain || !textHTMLPlain->complete_buffer) + return 0; + + nsString& cb = *(textHTMLPlain->complete_buffer); + + // could be empty, e.g., if part isn't actually being displayed + if (cb.Length()) + { + nsString asPlaintext; + uint32_t flags = nsIDocumentEncoder::OutputFormatted + | nsIDocumentEncoder::OutputWrap + | nsIDocumentEncoder::OutputFormatFlowed + | nsIDocumentEncoder::OutputLFLineBreak + | nsIDocumentEncoder::OutputNoScriptContent + | nsIDocumentEncoder::OutputNoFramesContent + | nsIDocumentEncoder::OutputBodyOnly; + HTML2Plaintext(cb, asPlaintext, flags, 80); + + NS_ConvertUTF16toUTF8 resultCStr(asPlaintext); + // TODO parse each line independently + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_line( + resultCStr.BeginWriting(), + resultCStr.Length(), + obj); + cb.Truncate(); + } + + if (status < 0) + return status; + + // Second part of the flush hack. Pretend obj wasn't closed yet, so that our super class + // gets a chance to write the closing. + bool save_closed_p = obj->closed_p; + obj->closed_p = false; + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + // Restore closed_p. + obj->closed_p = save_closed_p; + return status; +} + +void +MimeInlineTextHTMLAsPlaintext_finalize (MimeObject *obj) +{ + MimeInlineTextHTMLAsPlaintext *textHTMLPlain = + (MimeInlineTextHTMLAsPlaintext *) obj; + if (textHTMLPlain && textHTMLPlain->complete_buffer) + { + // If there's content in the buffer, make sure that we output it. + // don't care about return codes + obj->clazz->parse_eof(obj, false); + + delete textHTMLPlain->complete_buffer; + textHTMLPlain->complete_buffer = NULL; + /* It is important to zero the pointer, so we can reliably check for + the validity of it in the other functions. See above. */ + } + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize (obj); +} + +static int +MimeInlineTextHTMLAsPlaintext_parse_line (const char *line, int32_t length, + MimeObject *obj) +{ + MimeInlineTextHTMLAsPlaintext *textHTMLPlain = + (MimeInlineTextHTMLAsPlaintext *) obj; + + if (!textHTMLPlain || !(textHTMLPlain->complete_buffer)) + { +#if DEBUG +printf("Can't output: %s\n", line); +#endif + return -1; + } + + /* + To convert HTML->TXT syncronously, I need the full source at once, + not line by line (how do you convert "<li>foo\n" to plaintext?). + parse_decoded_buffer claims to give me that, but in fact also gives + me single lines. + It might be theoretically possible to drive this asyncronously, but + I don't know, which odd circumstances might arise and how libmime + will behave then. It's not worth the trouble for me to figure this all out. + */ + nsCString linestr(line, length); + NS_ConvertUTF8toUTF16 line_ucs2(linestr.get()); + if (length && line_ucs2.IsEmpty()) + CopyASCIItoUTF16 (linestr, line_ucs2); + (textHTMLPlain->complete_buffer)->Append(line_ucs2); + + return 0; +} diff --git a/mailnews/mime/src/mimethpl.h b/mailnews/mime/src/mimethpl.h new file mode 100644 index 000000000..fc302f4f1 --- /dev/null +++ b/mailnews/mime/src/mimethpl.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* The MimeInlineTextHTMLAsPlaintext class converts HTML->TXT->HTML, i.e. + HTML to Plaintext and the result to HTML again. + This might sound crazy, maybe it is, but it is for the "View as Plaintext" + option, if the sender didn't supply a plaintext alternative (bah!). + */ + +#ifndef _MIMETHPL_H_ +#define _MIMETHPL_H_ + +#include "mimetpla.h" +#include "nsStringGlue.h" + +typedef struct MimeInlineTextHTMLAsPlaintextClass MimeInlineTextHTMLAsPlaintextClass; +typedef struct MimeInlineTextHTMLAsPlaintext MimeInlineTextHTMLAsPlaintext; + +struct MimeInlineTextHTMLAsPlaintextClass { + MimeInlineTextPlainClass plaintext; +}; + +extern MimeInlineTextHTMLAsPlaintextClass mimeInlineTextHTMLAsPlaintextClass; + +struct MimeInlineTextHTMLAsPlaintext { + MimeInlineTextPlain plaintext; + nsString *complete_buffer; // Gecko parser expects wide strings +}; + +#define MimeInlineTextHTMLAsPlaintextClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextPlainClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETHPL_H_ */ diff --git a/mailnews/mime/src/mimethsa.cpp b/mailnews/mime/src/mimethsa.cpp new file mode 100644 index 000000000..58441dee0 --- /dev/null +++ b/mailnews/mime/src/mimethsa.cpp @@ -0,0 +1,143 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Most of this code is copied from mimethpl; see there for source comments. + If you find a bug here, check that class, too. +*/ + +/* The MimeInlineTextHTMLSanitized class cleans up HTML + + This removes offending HTML features that have no business in mail. + It is a low-level stop gap for many classes of attacks, + and intended for security conscious users. + Paranoia is a feature here, and has served very well in practice. + + It has already prevented countless serious exploits. + + It pushes the HTML that we get from the sender of the message + through a sanitizer (nsTreeSanitizer), which lets only allowed tags through. + With the appropriate configuration, this protects from most of the + security and visual-formatting problems that otherwise usually come with HTML + (and which partly gave HTML in email the bad reputation that it has). + + However, due to the parsing and serializing (and later parsing again) + required, there is an inherent, significant performance hit, when doing the + santinizing here at the MIME / HTML source level. But users of this class + will most likely find it worth the cost. + */ + +#include "mimethsa.h" +#include "prmem.h" +#include "prlog.h" +#include "msgCore.h" +#include "mimemoz2.h" +#include "nsIPrefBranch.h" +#include "mimethtm.h" + +#define MIME_SUPERCLASS mimeInlineTextHTMLClass +MimeDefClass(MimeInlineTextHTMLSanitized, MimeInlineTextHTMLSanitizedClass, + mimeInlineTextHTMLSanitizedClass, &MIME_SUPERCLASS); + +static int MimeInlineTextHTMLSanitized_parse_line(const char *, int32_t, + MimeObject *); +static int MimeInlineTextHTMLSanitized_parse_begin(MimeObject *obj); +static int MimeInlineTextHTMLSanitized_parse_eof(MimeObject *, bool); +static void MimeInlineTextHTMLSanitized_finalize(MimeObject *obj); + +static int +MimeInlineTextHTMLSanitizedClassInitialize(MimeInlineTextHTMLSanitizedClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *)clazz; + NS_ASSERTION(!oclass->class_initialized, "problem with superclass"); + oclass->parse_line = MimeInlineTextHTMLSanitized_parse_line; + oclass->parse_begin = MimeInlineTextHTMLSanitized_parse_begin; + oclass->parse_eof = MimeInlineTextHTMLSanitized_parse_eof; + oclass->finalize = MimeInlineTextHTMLSanitized_finalize; + + return 0; +} + +static int +MimeInlineTextHTMLSanitized_parse_begin(MimeObject *obj) +{ + MimeInlineTextHTMLSanitized *me = (MimeInlineTextHTMLSanitized *)obj; + me->complete_buffer = new nsString(); + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) + return status; + + return 0; +} + +static int +MimeInlineTextHTMLSanitized_parse_eof(MimeObject *obj, bool abort_p) +{ + if (obj->closed_p) + return 0; + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) + return status; + MimeInlineTextHTMLSanitized *me = (MimeInlineTextHTMLSanitized *)obj; + + // We have to cache all lines and parse the whole document at once. + // There's a useful sounding function parseFromStream(), but it only allows XML + // mimetypes, not HTML. Methinks that's because the HTML soup parser + // needs the entire doc to make sense of the gibberish that people write. + if (!me || !me->complete_buffer) + return 0; + + nsString& cb = *(me->complete_buffer); + if (cb.IsEmpty()) + return 0; + nsString sanitized; + + // Sanitize. + HTMLSanitize(cb, sanitized); + + // Write it out. + NS_ConvertUTF16toUTF8 resultCStr(sanitized); + MimeInlineTextHTML_insert_lang_div(obj, resultCStr); + // Call to MimeInlineTextHTML_remove_plaintext_tag() not needed since + // sanitization already removes that tag. + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_line( + resultCStr.BeginWriting(), + resultCStr.Length(), + obj); + cb.Truncate(); + return status; +} + +void +MimeInlineTextHTMLSanitized_finalize(MimeObject *obj) +{ + MimeInlineTextHTMLSanitized *me = (MimeInlineTextHTMLSanitized *)obj; + + if (me && me->complete_buffer) + { + obj->clazz->parse_eof(obj, false); + delete me->complete_buffer; + me->complete_buffer = NULL; + } + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(obj); +} + +static int +MimeInlineTextHTMLSanitized_parse_line(const char *line, int32_t length, + MimeObject *obj) +{ + MimeInlineTextHTMLSanitized *me = (MimeInlineTextHTMLSanitized *)obj; + + if (!me || !(me->complete_buffer)) + return -1; + + nsCString linestr(line, length); + NS_ConvertUTF8toUTF16 line_ucs2(linestr.get()); + if (length && line_ucs2.IsEmpty()) + CopyASCIItoUTF16(linestr, line_ucs2); + (me->complete_buffer)->Append(line_ucs2); + + return 0; +} diff --git a/mailnews/mime/src/mimethsa.h b/mailnews/mime/src/mimethsa.h new file mode 100644 index 000000000..42a7f81b4 --- /dev/null +++ b/mailnews/mime/src/mimethsa.h @@ -0,0 +1,28 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETHSA_H_ +#define _MIMETHSA_H_ + +#include "mimethtm.h" + +typedef struct MimeInlineTextHTMLSanitizedClass MimeInlineTextHTMLSanitizedClass; +typedef struct MimeInlineTextHTMLSanitized MimeInlineTextHTMLSanitized; + +struct MimeInlineTextHTMLSanitizedClass { + MimeInlineTextHTMLClass html; +}; + +extern MimeInlineTextHTMLSanitizedClass mimeInlineTextHTMLSanitizedClass; + +struct MimeInlineTextHTMLSanitized { + MimeInlineTextHTML html; + nsString *complete_buffer; // Gecko parser expects wide strings +}; + +#define MimeInlineTextHTMLSanitizedClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextHTMLClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETHPL_H_ */ diff --git a/mailnews/mime/src/mimethtm.cpp b/mailnews/mime/src/mimethtm.cpp new file mode 100644 index 000000000..edf39b35e --- /dev/null +++ b/mailnews/mime/src/mimethtm.cpp @@ -0,0 +1,254 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mimethtm.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "prprf.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include <ctype.h> + +#define MIME_SUPERCLASS mimeInlineTextClass +MimeDefClass(MimeInlineTextHTML, MimeInlineTextHTMLClass, + mimeInlineTextHTMLClass, &MIME_SUPERCLASS); + +static int MimeInlineTextHTML_parse_line (const char *, int32_t, MimeObject *); +static int MimeInlineTextHTML_parse_eof (MimeObject *, bool); +static int MimeInlineTextHTML_parse_begin (MimeObject *obj); + +static int +MimeInlineTextHTMLClassInitialize(MimeInlineTextHTMLClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); + oclass->parse_begin = MimeInlineTextHTML_parse_begin; + oclass->parse_line = MimeInlineTextHTML_parse_line; + oclass->parse_eof = MimeInlineTextHTML_parse_eof; + + return 0; +} + +static int +MimeInlineTextHTML_parse_begin (MimeObject *obj) +{ + int status = ((MimeObjectClass*)&mimeLeafClass)->parse_begin(obj); + if (status < 0) return status; + + if (!obj->output_p) return 0; + + status = MimeObject_write_separator(obj); + if (status < 0) return status; + + MimeInlineTextHTML *textHTML = (MimeInlineTextHTML *) obj; + + textHTML->charset = nullptr; + + /* If this HTML part has a Content-Base header, and if we're displaying + to the screen (that is, not writing this part "raw") then translate + that Content-Base header into a <BASE> tag in the HTML. + */ + if (obj->options && + obj->options->write_html_p && + obj->options->output_fn) + { + char *base_hdr = MimeHeaders_get (obj->headers, HEADER_CONTENT_BASE, + false, false); + + /* rhp - for MHTML Spec changes!!! */ + if (!base_hdr) + { + base_hdr = MimeHeaders_get (obj->headers, HEADER_CONTENT_LOCATION, false, false); + } + /* rhp - for MHTML Spec changes!!! */ + + if (base_hdr) + { + uint32_t buflen = strlen(base_hdr) + 20; + char *buf = (char *) PR_MALLOC(buflen); + const char *in; + char *out; + if (!buf) + return MIME_OUT_OF_MEMORY; + + /* The value of the Content-Base header is a number of "words". + Whitespace in this header is not significant -- it is assumed + that any real whitespace in the URL has already been encoded, + and whitespace has been inserted to allow the lines in the + mail header to be wrapped reasonably. Creators are supposed + to insert whitespace every 40 characters or less. + */ + PL_strncpyz(buf, "<BASE HREF=\"", buflen); + out = buf + strlen(buf); + + for (in = base_hdr; *in; in++) + /* ignore whitespace and quotes */ + if (!IS_SPACE(*in) && *in != '"') + *out++ = *in; + + /* Close the tag and argument. */ + *out++ = '"'; + *out++ = '>'; + *out++ = 0; + + PR_Free(base_hdr); + + status = MimeObject_write(obj, buf, strlen(buf), false); + PR_Free(buf); + if (status < 0) return status; + } + } + + return 0; +} + + +static int +MimeInlineTextHTML_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + MimeInlineTextHTML *textHTML = (MimeInlineTextHTML *) obj; + + if (!obj->output_p) + return 0; + + if (!obj->options || !obj->options->output_fn) + return 0; + + if (!textHTML->charset) + { + char * cp; + // First, try to detect a charset via a META tag! + if ((cp = PL_strncasestr(line, "META", length)) && + (cp = PL_strncasestr(cp, "HTTP-EQUIV=", length - (int)(cp - line))) && + (cp = PL_strncasestr(cp, "CONTENT=", length - (int)(cp - line))) && + (cp = PL_strncasestr(cp, "CHARSET=", length - (int)(cp - line))) + ) + { + char* cp1 = cp + 8; //8 for the length of "CHARSET=" + char* cp2 = PL_strnpbrk(cp1, " \"\'", length - (int)(cp1 - line)); + if (cp2) + { + char* charset = PL_strndup(cp1, (int)(cp2 - cp1)); + + // Fix bug 101434, in this case since this parsing is a char* + // operation, a real UTF-16 or UTF-32 document won't be parse + // correctly, if it got parse, it cannot be UTF-16 nor UTF-32 + // there fore, we ignore them if somehow we got that value + // 6 == strlen("UTF-16") or strlen("UTF-32"), this will cover + // UTF-16, UTF-16BE, UTF-16LE, UTF-32, UTF-32BE, UTF-32LE + if ((charset != nullptr) && + PL_strncasecmp(charset, "UTF-16", 6) && + PL_strncasecmp(charset, "UTF-32", 6)) + { + textHTML->charset = charset; + + // write out the data without the charset part... + if (textHTML->charset) + { + int err = MimeObject_write(obj, line, cp - line, true); + if (err == 0) + err = MimeObject_write(obj, cp2, length - (int)(cp2 - line), true); + + return err; + } + } + PR_FREEIF(charset); + } + } + } + + // Now, just write out the data... + return MimeObject_write(obj, line, length, true); +} + +static int +MimeInlineTextHTML_parse_eof (MimeObject *obj, bool abort_p) +{ + int status; + MimeInlineTextHTML *textHTML = (MimeInlineTextHTML *) obj; + if (obj->closed_p) return 0; + + PR_FREEIF(textHTML->charset); + + /* Run parent method first, to flush out any buffered data. */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + + return 0; +} + +/* + * The following function adds <div class="moz-text-html" lang="..."> or + * <div class="moz-text-html"> as the first tag following the <body> tag in the + * serialised HTML of a message. This becomes a no-op if no <body> tag is found. + */ +void +MimeInlineTextHTML_insert_lang_div(MimeObject *obj, nsCString &message) +{ + if (obj->options->format_out != nsMimeOutput::nsMimeMessageBodyDisplay && + obj->options->format_out != nsMimeOutput::nsMimeMessagePrintOutput) + return; + + // Make sure we have a <body> before we start. + int32_t index = message.Find("<body", /* ignoreCase = */ true); + if (index == kNotFound) + return; + index = message.FindChar('>', index) + 1; + + // Insert <div class="moz-text-html" lang="..."> for the following two purposes: + // 1) Users can configure their HTML display via CSS for .moz-text-html. + // 2) The language group in the 'lang' attribure is used by Gecko to determine + // which font to use. + int32_t fontSize; // default font size + int32_t fontSizePercentage; // size percentage + nsAutoCString fontLang; // langgroup of the font. + if (NS_SUCCEEDED(GetMailNewsFont(obj, false, &fontSize, &fontSizePercentage, fontLang))) + { + message.Insert(NS_LITERAL_CSTRING("<div class=\"moz-text-html\" lang=\"") + + fontLang + + NS_LITERAL_CSTRING("\">"), + index); + } + else + { + message.Insert(NS_LITERAL_CSTRING("<div class=\"moz-text-html\">"), + index); + } + + index = message.RFind("</body>", /* ignoreCase = */ true); + if (index != kNotFound) + message.Insert(NS_LITERAL_CSTRING("</div>"), index); +} + +/* + * The following function replaces <plaintext> tags with <x-plaintext>. + * <plaintext> is a funny beast: It leads to everything following it + * being displayed verbatim, even a </plaintext> tag is ignored. + */ +void +MimeInlineTextHTML_remove_plaintext_tag(MimeObject *obj, nsCString &message) +{ + if (obj->options->format_out != nsMimeOutput::nsMimeMessageBodyDisplay && + obj->options->format_out != nsMimeOutput::nsMimeMessagePrintOutput) + return; + + // Replace all <plaintext> and </plaintext> tags. + int32_t index = 0; + bool replaced = false; + while ((index = message.Find("<plaintext", /* ignoreCase = */ true, index)) != kNotFound) { + message.Insert("x-", index+1); + index += 12; + replaced = true; + } + if (replaced) { + index = 0; + while ((index = message.Find("</plaintext", /* ignoreCase = */ true, index)) != kNotFound) { + message.Insert("x-", index+2); + index += 13; + } + } +} + diff --git a/mailnews/mime/src/mimethtm.h b/mailnews/mime/src/mimethtm.h new file mode 100644 index 000000000..fbd730cd2 --- /dev/null +++ b/mailnews/mime/src/mimethtm.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETHTM_H_ +#define _MIMETHTM_H_ + +#include "mimetext.h" + +/* The MimeInlineTextHTML class implements the text/html MIME content type. + */ + +typedef struct MimeInlineTextHTMLClass MimeInlineTextHTMLClass; +typedef struct MimeInlineTextHTML MimeInlineTextHTML; + +struct MimeInlineTextHTMLClass { + MimeInlineTextClass text; +}; + +extern MimeInlineTextHTMLClass mimeInlineTextHTMLClass; + +struct MimeInlineTextHTML { + MimeInlineText text; + char *charset; /* If we sniffed a charset, do some converting! */ +}; + +#define MimeInlineTextHTMLClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextClassInitializer(ITYPE,CSUPER) } + +void +MimeInlineTextHTML_insert_lang_div(MimeObject *obj, nsCString &message); +void +MimeInlineTextHTML_remove_plaintext_tag(MimeObject *obj, nsCString &message); +#endif /* _MIMETHTM_H_ */ diff --git a/mailnews/mime/src/mimetpfl.cpp b/mailnews/mime/src/mimetpfl.cpp new file mode 100644 index 000000000..da7eff413 --- /dev/null +++ b/mailnews/mime/src/mimetpfl.cpp @@ -0,0 +1,630 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimetpfl.h" +#include "mimebuf.h" +#include "prmem.h" +#include "plstr.h" +#include "mozITXTToHTMLConv.h" +#include "nsStringGlue.h" +#include "nsMimeStringResources.h" +#include "nsIPrefBranch.h" +#include "nsIServiceManager.h" +#include "mimemoz2.h" +#include "prprf.h" +#include "nsMsgI18N.h" + +static const uint32_t kSpacesForATab = 4; // Must be at least 1. + +#define MIME_SUPERCLASS mimeInlineTextClass +MimeDefClass(MimeInlineTextPlainFlowed, MimeInlineTextPlainFlowedClass, + mimeInlineTextPlainFlowedClass, &MIME_SUPERCLASS); + +static int MimeInlineTextPlainFlowed_parse_begin (MimeObject *); +static int MimeInlineTextPlainFlowed_parse_line (const char *, int32_t, MimeObject *); +static int MimeInlineTextPlainFlowed_parse_eof (MimeObject *, bool); + +static MimeInlineTextPlainFlowedExData *MimeInlineTextPlainFlowedExDataList = nullptr; + +// From mimetpla.cpp +extern "C" void MimeTextBuildPrefixCSS( + int32_t quotedSizeSetting, // mail.quoted_size + int32_t quotedStyleSetting, // mail.quoted_style + char *citationColor, // mail.citation_color + nsACString &style); +// Definition below +static +nsresult Line_convert_whitespace(const nsString& a_line, + const bool a_convert_all_whitespace, + nsString& a_out_line); + +static int +MimeInlineTextPlainFlowedClassInitialize(MimeInlineTextPlainFlowedClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "class not initialized"); + oclass->parse_begin = MimeInlineTextPlainFlowed_parse_begin; + oclass->parse_line = MimeInlineTextPlainFlowed_parse_line; + oclass->parse_eof = MimeInlineTextPlainFlowed_parse_eof; + + return 0; +} + +static int +MimeInlineTextPlainFlowed_parse_begin (MimeObject *obj) +{ + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + status = MimeObject_write(obj, "", 0, true); /* force out any separators... */ + if(status<0) return status; + + bool quoting = ( obj->options + && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting + ) ); // The output will be inserted in the composer as quotation + bool plainHTML = quoting || (obj->options && + obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs); + // Just good(tm) HTML. No reliance on CSS. + + // Setup the data structure that is connected to the actual document + // Saved in a linked list in case this is called with several documents + // at the same time. + /* This memory is freed when parse_eof is called. So it better be! */ + struct MimeInlineTextPlainFlowedExData *exdata = + (MimeInlineTextPlainFlowedExData *)PR_MALLOC(sizeof(struct MimeInlineTextPlainFlowedExData)); + if(!exdata) return MIME_OUT_OF_MEMORY; + + MimeInlineTextPlainFlowed *text = (MimeInlineTextPlainFlowed *) obj; + + // Link it up. + exdata->next = MimeInlineTextPlainFlowedExDataList; + MimeInlineTextPlainFlowedExDataList = exdata; + + // Initialize data + + exdata->ownerobj = obj; + exdata->inflow = false; + exdata->quotelevel = 0; + exdata->isSig = false; + + // check for DelSp=yes (RFC 3676) + + char *content_type_row = + (obj->headers + ? MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, false, false) + : 0); + char *content_type_delsp = + (content_type_row + ? MimeHeaders_get_parameter(content_type_row, "delsp", NULL,NULL) + : 0); + ((MimeInlineTextPlainFlowed *)obj)->delSp = content_type_delsp && !PL_strcasecmp(content_type_delsp, "yes"); + PR_Free(content_type_delsp); + PR_Free(content_type_row); + + // Get Prefs for viewing + + exdata->fixedwidthfont = false; + // Quotes + text->mQuotedSizeSetting = 0; // mail.quoted_size + text->mQuotedStyleSetting = 0; // mail.quoted_style + text->mCitationColor = nullptr; // mail.citation_color + text->mStripSig = true; // mail.strip_sig_on_reply + + nsIPrefBranch *prefBranch = GetPrefBranch(obj->options); + if (prefBranch) + { + prefBranch->GetIntPref("mail.quoted_size", &(text->mQuotedSizeSetting)); + prefBranch->GetIntPref("mail.quoted_style", &(text->mQuotedStyleSetting)); + prefBranch->GetCharPref("mail.citation_color", &(text->mCitationColor)); + prefBranch->GetBoolPref("mail.strip_sig_on_reply", &(text->mStripSig)); + mozilla::DebugOnly<nsresult> rv = + prefBranch->GetBoolPref("mail.fixed_width_messages", &(exdata->fixedwidthfont)); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get pref"); + // Check at least the success of one + } + + // Get font + // only used for viewing (!plainHTML) + nsAutoCString fontstyle; + nsAutoCString fontLang; // langgroup of the font + + + // generic font-family name ( -moz-fixed for fixed font and NULL for + // variable font ) is sufficient now that bug 105199 has been fixed. + + if (exdata->fixedwidthfont) + fontstyle = "font-family: -moz-fixed"; + + if (nsMimeOutput::nsMimeMessageBodyDisplay == obj->options->format_out || + nsMimeOutput::nsMimeMessagePrintOutput == obj->options->format_out) + { + int32_t fontSize; // default font size + int32_t fontSizePercentage; // size percentage + nsresult rv = GetMailNewsFont(obj, exdata->fixedwidthfont, + &fontSize, &fontSizePercentage, fontLang); + if (NS_SUCCEEDED(rv)) + { + if ( ! fontstyle.IsEmpty() ) { + fontstyle += "; "; + } + fontstyle += "font-size: "; + fontstyle.AppendInt(fontSize); + fontstyle += "px;"; + } + } + + // Opening <div>. + if (!quoting) + /* 4.x' editor can't break <div>s (e.g. to interleave comments). + We'll add the class to the <blockquote type=cite> later. */ + { + nsAutoCString openingDiv("<div class=\"moz-text-flowed\""); + // We currently have to add formatting here. :-( + if (!plainHTML && !fontstyle.IsEmpty()) + { + openingDiv += " style=\""; + openingDiv += fontstyle; + openingDiv += '"'; + } + if (!plainHTML && !fontLang.IsEmpty()) + { + openingDiv += " lang=\""; + openingDiv += fontLang; + openingDiv += '\"'; + } + openingDiv += ">"; + status = MimeObject_write(obj, openingDiv.get(), openingDiv.Length(), false); + if (status < 0) return status; + } + + return 0; +} + +static int +MimeInlineTextPlainFlowed_parse_eof (MimeObject *obj, bool abort_p) +{ + int status = 0; + struct MimeInlineTextPlainFlowedExData *exdata = nullptr; + + bool quoting = ( obj->options + && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting + ) ); // see above + + // Has this method already been called for this object? + // In that case return. + if (obj->closed_p) return 0; + + /* Run parent method first, to flush out any buffered data. */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) goto EarlyOut; + + // Look up and unlink "our" extended data structure + // We do it in the beginning so that if an error occur, we can + // just free |exdata|. + struct MimeInlineTextPlainFlowedExData **prevexdata; + prevexdata = &MimeInlineTextPlainFlowedExDataList; + + while ((exdata = *prevexdata) != nullptr) { + if (exdata->ownerobj == obj) { + // Fill hole + *prevexdata = exdata->next; + break; + } + prevexdata = &exdata->next; + } + NS_ASSERTION (exdata, "The extra data has disappeared!"); + + if (!obj->output_p) { + status = 0; + goto EarlyOut; + } + + for(; exdata->quotelevel > 0; exdata->quotelevel--) { + status = MimeObject_write(obj, "</blockquote>", 13, false); + if(status<0) goto EarlyOut; + } + + if (exdata->isSig && !quoting) { + status = MimeObject_write(obj, "</div>", 6, false); // .moz-txt-sig + if (status<0) goto EarlyOut; + } + if (!quoting) // HACK (see above) + { + status = MimeObject_write(obj, "</div>", 6, false); // .moz-text-flowed + if (status<0) goto EarlyOut; + } + + status = 0; + +EarlyOut: + PR_Free(exdata); + + // Free mCitationColor + MimeInlineTextPlainFlowed *text = (MimeInlineTextPlainFlowed *) obj; + PR_FREEIF(text->mCitationColor); + text->mCitationColor = nullptr; + + return status; +} + + +static int +MimeInlineTextPlainFlowed_parse_line (const char *aLine, int32_t length, MimeObject *obj) +{ + int status; + bool quoting = ( obj->options + && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting + ) ); // see above + bool plainHTML = quoting || (obj->options && + obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs); + // see above + + struct MimeInlineTextPlainFlowedExData *exdata; + exdata = MimeInlineTextPlainFlowedExDataList; + while(exdata && (exdata->ownerobj != obj)) { + exdata = exdata->next; + } + + NS_ASSERTION(exdata, "The extra data has disappeared!"); + + NS_ASSERTION(length > 0, "zero length"); + if (length <= 0) return 0; + + uint32_t linequotelevel = 0; + nsAutoCString real_line(aLine, length); + char *line = real_line.BeginWriting(); + const char *linep = real_line.BeginReading(); + // Space stuffed? + if(' ' == *linep) { + linep++; + } else { + // count '>':s before the first non-'>' + while('>' == *linep) { + linep++; + linequotelevel++; + } + // Space stuffed? + if(' ' == *linep) { + linep++; + } + } + + // Look if the last character (after stripping ending end + // of lines and quoting stuff) is a SPACE. If it is, we are looking at a + // flowed line. Normally we assume that the last two chars + // are CR and LF as said in RFC822, but that doesn't seem to + // be the case always. + bool flowed = false; + bool sigSeparator = false; + int32_t index = length-1; + while(index >= 0 && ('\r' == line[index] || '\n' == line[index])) { + index--; + } + if (index > linep - line && ' ' == line[index]) + /* Ignore space stuffing, i.e. lines with just + (quote marks and) a space count as empty */ + { + flowed = true; + sigSeparator = (index - (linep - line) + 1 == 3) && !strncmp(linep, "-- ", 3); + if (((MimeInlineTextPlainFlowed *) obj)->delSp && !sigSeparator) + /* If line is flowed and DelSp=yes, logically + delete trailing space. Line consisting of + dash dash space ("-- "), commonly used as + signature separator, gets special handling + (RFC 3676) */ + { + length = index; + line[index] = '\0'; + } + } + + if (obj->options && + obj->options->decompose_file_p && + obj->options->decompose_file_output_fn) + { + return obj->options->decompose_file_output_fn(line, + length, + obj->options->stream_closure); + } + + mozITXTToHTMLConv *conv = GetTextConverter(obj->options); + + bool skipConversion = !conv || + (obj->options && obj->options->force_user_charset); + + nsAutoString lineSource; + nsString lineResult; + + char *mailCharset = NULL; + nsresult rv; + + if (!skipConversion) + { + // Convert only if the source string is not empty + if (length - (linep - line) > 0) + { + uint32_t whattodo = obj->options->whattodo; + if (plainHTML) + { + if (quoting) + whattodo = 0; + else + whattodo = whattodo & ~mozITXTToHTMLConv::kGlyphSubstitution; + /* Do recognition for the case, the result is viewed in + Mozilla, but not GlyphSubstitution, because other UAs + might not be able to display the glyphs. */ + } + + const nsDependentCSubstring& inputStr = Substring(linep, linep + (length - (linep - line))); + + // For 'SaveAs', |line| is in |mailCharset|. + // convert |line| to UTF-16 before 'html'izing (calling ScanTXT()) + if (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs) + { + // Get the mail charset of this message. + MimeInlineText *inlinetext = (MimeInlineText *) obj; + if (!inlinetext->initializeCharset) + ((MimeInlineTextClass*)&mimeInlineTextClass)->initialize_charset(obj); + mailCharset = inlinetext->charset; + if (mailCharset && *mailCharset) { + rv = nsMsgI18NConvertToUnicode(mailCharset, PromiseFlatCString(inputStr), lineSource); + NS_ENSURE_SUCCESS(rv, -1); + } + else // this probably never happens... + CopyUTF8toUTF16(inputStr, lineSource); + } + else // line is in UTF-8 + CopyUTF8toUTF16(inputStr, lineSource); + + // This is the main TXT to HTML conversion: + // escaping (very important), eventually recognizing etc. + rv = conv->ScanTXT(lineSource.get(), whattodo, getter_Copies(lineResult)); + NS_ENSURE_SUCCESS(rv, -1); + } + } + else + { + CopyUTF8toUTF16(nsDependentCString(line, length), lineResult); + status = 0; + } + + nsAutoCString preface; + + /* Correct number of blockquotes */ + int32_t quoteleveldiff=linequotelevel - exdata->quotelevel; + if((quoteleveldiff != 0) && flowed && exdata->inflow) { + // From RFC 2646 4.5 + // The receiver SHOULD handle this error by using the 'quote-depth-wins' rule, + // which is to ignore the flowed indicator and treat the line as fixed. That + // is, the change in quote depth ends the paragraph. + + // We get that behaviour by just going on. + } + + // Cast so we have access to the prefs we need. + MimeInlineTextPlainFlowed *tObj = (MimeInlineTextPlainFlowed *) obj; + while(quoteleveldiff>0) { + quoteleveldiff--; + preface += "<blockquote type=cite"; + + nsAutoCString style; + MimeTextBuildPrefixCSS(tObj->mQuotedSizeSetting, tObj->mQuotedStyleSetting, + tObj->mCitationColor, style); + if (!plainHTML && !style.IsEmpty()) + { + preface += " style=\""; + preface += style; + preface += '"'; + } + preface += '>'; + } + while(quoteleveldiff<0) { + quoteleveldiff++; + preface += "</blockquote>"; + } + exdata->quotelevel = linequotelevel; + + nsAutoString lineResult2; + + if(flowed) { + // Check RFC 2646 "4.3. Usenet Signature Convention": "-- "+CRLF is + // not a flowed line + if (sigSeparator) + { + if (linequotelevel > 0 || exdata->isSig) + { + preface += "-- <br>"; + } else { + exdata->isSig = true; + preface += "<div class=\"moz-txt-sig\"><span class=\"moz-txt-tag\">" + "-- <br></span>"; + } + } else { + Line_convert_whitespace(lineResult, false /* Allow wraps */, + lineResult2); + } + + exdata->inflow=true; + } else { + // Fixed paragraph. + Line_convert_whitespace(lineResult, + !plainHTML && !obj->options->wrap_long_lines_p + /* If wrap, convert all spaces but the last in + a row into nbsp, otherwise all. */, + lineResult2); + lineResult2.AppendLiteral("<br>"); + exdata->inflow = false; + } // End Fixed line + + if (!(exdata->isSig && quoting && tObj->mStripSig)) + { + status = MimeObject_write(obj, preface.get(), preface.Length(), true); + if (status < 0) return status; + nsAutoCString outString; + if (obj->options->format_out != nsMimeOutput::nsMimeMessageSaveAs || + !mailCharset || !*mailCharset) + CopyUTF16toUTF8(lineResult2, outString); + else + { // convert back to mailCharset before writing. + rv = nsMsgI18NConvertFromUnicode(mailCharset, lineResult2, outString); + NS_ENSURE_SUCCESS(rv, -1); + } + status = MimeObject_write(obj, outString.get(), outString.Length(), true); + return status; + } + return 0; +} + + +/** + * Maintains a small state machine with three states. "Not in tag", + * "In tag, but not in quote" and "In quote inside a tag". It also + * remembers what character started the quote (" or '). The state + * variables are kept outside this function and are included as + * parameters. + * + * @param in/out a_in_tag, if we are in a tag right now. + * @param in/out a_in_quote_in_tag, if we are in a quote inside a tag. + * @param in/out a_quote_char, the kind of quote (" or '). + * @param in a_current_char, the next char. It decides which state + * will be next. + */ +static void Update_in_tag_info(bool *a_in_tag, /* IN/OUT */ + bool *a_in_quote_in_tag, /* IN/OUT */ + char16_t *a_quote_char, /* IN/OUT (pointer to single char) */ + char16_t a_current_char) /* IN */ +{ + if(*a_in_tag) { + // Keep us informed of what's quoted so that we + // don't end the tag too soon. For instance in + // <font face="weird>font<name"> + if(*a_in_quote_in_tag) { + // We are in a quote. A quote is ended by the same + // character that started it ('...' or "...") + if(*a_quote_char == a_current_char) { + *a_in_quote_in_tag = false; + } + } else { + // We are not currently in a quote, but we may enter + // one right this minute. + switch(a_current_char) { + case '"': + case '\'': + *a_in_quote_in_tag = true; + *a_quote_char = a_current_char; + break; + case '>': + // Tag is ended + *a_in_tag = false; + break; + default: + // Do nothing + ; + } + } + return; + } + + // Not in a tag. + // Check if we are entering a tag by looking for '<'. + // All normal occurrences of '<' should have been replaced + // by < + if ('<' == a_current_char) { + *a_in_tag = true; + *a_in_quote_in_tag = false; + } +} + + +/** + * Converts whitespace to | |, if appropriate. + * + * @param in a_current_char, the char to convert. + * @param in a_next_char, the char after the char to convert. + * @param in a_convert_all_whitespace, if also the last whitespace + * in a sequence should be + * converted. + * @param out a_out_string, result will be appended. +*/ +static void Convert_whitespace(const char16_t a_current_char, + const char16_t a_next_char, + const bool a_convert_all_whitespace, + nsString& a_out_string) +{ + NS_ASSERTION('\t' == a_current_char || ' ' == a_current_char, + "Convert_whitespace got something else than a whitespace!"); + + uint32_t number_of_nbsp = 0; + uint32_t number_of_space = 1; // Assume we're going to output one space. + + /* Output the spaces for a tab. All but the last are made into . + The last is treated like a normal space. + */ + if('\t' == a_current_char) { + number_of_nbsp = kSpacesForATab - 1; + } + + if(' ' == a_next_char || '\t' == a_next_char || a_convert_all_whitespace) { + number_of_nbsp += number_of_space; + number_of_space = 0; + } + + while(number_of_nbsp--) { + a_out_string.AppendLiteral(" "); + } + + while(number_of_space--) { + // a_out_string += ' '; gives error + a_out_string.AppendLiteral(" "); + } + + return; +} + +/** + * Passes over the line and converts whitespace to | |, if appropriate + * + * @param in a_convert_all_whitespace, if also the last whitespace + * in a sequence should be + * converted. + * @param out a_out_string, result will be appended. +*/ +static +nsresult Line_convert_whitespace(const nsString& a_line, + const bool a_convert_all_whitespace, + nsString& a_out_line) +{ + bool in_tag = false; + bool in_quote_in_tag = false; + char16_t quote_char; + + for (uint32_t i = 0; a_line.Length() > i; i++) + { + const char16_t ic = a_line[i]; // Cache + + Update_in_tag_info(&in_tag, &in_quote_in_tag, "e_char, ic); + // We don't touch anything inside a tag. + if (!in_tag) { + if (ic == ' ' || ic == '\t') { + // Convert the whitespace to something appropriate + Convert_whitespace(ic, a_line.Length() > i + 1 ? a_line[i + 1] : '\0', + a_convert_all_whitespace || + !i, // First char on line + a_out_line); + } else if (ic == '\r') { + // strip CRs + } else { + a_out_line += ic; + } + } else { + // In tag. Don't change anything + a_out_line += ic; + } + } + return NS_OK; +} diff --git a/mailnews/mime/src/mimetpfl.h b/mailnews/mime/src/mimetpfl.h new file mode 100644 index 000000000..c3b20c445 --- /dev/null +++ b/mailnews/mime/src/mimetpfl.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETPFL_H_ +#define _MIMETPFL_H_ + +#include "mimetext.h" + +/* The MimeInlineTextPlainFlowed class implements the + text/plain MIME content type for the special case of a supplied + format=flowed. See + ftp://ftp.ietf.org/internet-drafts/draft-gellens-format-06.txt for + more information. + */ + +typedef struct MimeInlineTextPlainFlowedClass MimeInlineTextPlainFlowedClass; +typedef struct MimeInlineTextPlainFlowed MimeInlineTextPlainFlowed; + +struct MimeInlineTextPlainFlowedClass { + MimeInlineTextClass text; +}; + +extern MimeInlineTextPlainFlowedClass mimeInlineTextPlainFlowedClass; + +struct MimeInlineTextPlainFlowed { + MimeInlineText text; + bool delSp; // DelSp=yes (RFC 3676) + int32_t mQuotedSizeSetting; // mail.quoted_size + int32_t mQuotedStyleSetting; // mail.quoted_style + char *mCitationColor; // mail.citation_color + bool mStripSig; // mail.strip_sig_on_reply +}; + + +/* + * Made to contain information to be kept during the whole message parsing. + */ +struct MimeInlineTextPlainFlowedExData { + struct MimeObject *ownerobj; /* The owner of this struct */ + bool inflow; /* If we currently are in flow */ + bool fixedwidthfont; /* If we output text for fixed width font */ + uint32_t quotelevel; /* How deep is your love, uhr, quotelevel I meen. */ + bool isSig; // we're currently in a signature + struct MimeInlineTextPlainFlowedExData *next; +}; + +#define MimeInlineTextPlainFlowedClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETPFL_H_ */ diff --git a/mailnews/mime/src/mimetpla.cpp b/mailnews/mime/src/mimetpla.cpp new file mode 100644 index 000000000..72a974a73 --- /dev/null +++ b/mailnews/mime/src/mimetpla.cpp @@ -0,0 +1,451 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimetpla.h" +#include "mimebuf.h" +#include "prmem.h" +#include "plstr.h" +#include "mozITXTToHTMLConv.h" +#include "nsCOMPtr.h" +#include "nsIComponentManager.h" +#include "nsStringGlue.h" +#include "nsMimeStringResources.h" +#include "mimemoz2.h" +#include "nsIServiceManager.h" +#include "nsIPrefBranch.h" +#include "prprf.h" +#include "nsMsgI18N.h" + +#define MIME_SUPERCLASS mimeInlineTextClass +MimeDefClass(MimeInlineTextPlain, MimeInlineTextPlainClass, + mimeInlineTextPlainClass, &MIME_SUPERCLASS); + +static int MimeInlineTextPlain_parse_begin (MimeObject *); +static int MimeInlineTextPlain_parse_line (const char *, int32_t, MimeObject *); +static int MimeInlineTextPlain_parse_eof (MimeObject *, bool); + +static int +MimeInlineTextPlainClassInitialize(MimeInlineTextPlainClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "class not initialized"); + oclass->parse_begin = MimeInlineTextPlain_parse_begin; + oclass->parse_line = MimeInlineTextPlain_parse_line; + oclass->parse_eof = MimeInlineTextPlain_parse_eof; + return 0; +} + +extern "C" +void +MimeTextBuildPrefixCSS(int32_t quotedSizeSetting, // mail.quoted_size + int32_t quotedStyleSetting, // mail.quoted_style + char *citationColor, // mail.citation_color + nsACString &style) +{ + switch (quotedStyleSetting) + { + case 0: // regular + break; + case 1: // bold + style.Append("font-weight: bold; "); + break; + case 2: // italic + style.Append("font-style: italic; "); + break; + case 3: // bold-italic + style.Append("font-weight: bold; font-style: italic; "); + break; + } + + switch (quotedSizeSetting) + { + case 0: // regular + break; + case 1: // large + style.Append("font-size: large; "); + break; + case 2: // small + style.Append("font-size: small; "); + break; + } + + if (citationColor && *citationColor) + { + style += "color: "; + style += citationColor; + style += ';'; + } +} + +static int +MimeInlineTextPlain_parse_begin (MimeObject *obj) +{ + int status = 0; + bool quoting = ( obj->options + && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting + ) ); // The output will be inserted in the composer as quotation + bool plainHTML = quoting || (obj->options && + (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs)); + // Just good(tm) HTML. No reliance on CSS. + bool rawPlainText = obj->options && + (obj->options->format_out == nsMimeOutput::nsMimeMessageFilterSniffer + || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach); + + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + if (status < 0) return status; + + if (!obj->output_p) return 0; + + if (obj->options && + obj->options->write_html_p && + obj->options->output_fn) + { + MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj; + text->mCiteLevel = 0; + + // Get the prefs + + // Quoting + text->mBlockquoting = true; // mail.quoteasblock + + // Viewing + text->mQuotedSizeSetting = 0; // mail.quoted_size + text->mQuotedStyleSetting = 0; // mail.quoted_style + text->mCitationColor = nullptr; // mail.citation_color + text->mStripSig = true; // mail.strip_sig_on_reply + bool graphicalQuote = true; // mail.quoted_graphical + + nsIPrefBranch *prefBranch = GetPrefBranch(obj->options); + if (prefBranch) + { + prefBranch->GetIntPref("mail.quoted_size", &(text->mQuotedSizeSetting)); + prefBranch->GetIntPref("mail.quoted_style", &(text->mQuotedStyleSetting)); + prefBranch->GetCharPref("mail.citation_color", &(text->mCitationColor)); + prefBranch->GetBoolPref("mail.strip_sig_on_reply", &(text->mStripSig)); + prefBranch->GetBoolPref("mail.quoted_graphical", &graphicalQuote); + prefBranch->GetBoolPref("mail.quoteasblock", &(text->mBlockquoting)); + } + + if (!rawPlainText) + { + // Get font + // only used for viewing (!plainHTML) + nsAutoCString fontstyle; + nsAutoCString fontLang; // langgroup of the font + + // generic font-family name ( -moz-fixed for fixed font and NULL for + // variable font ) is sufficient now that bug 105199 has been fixed. + + if (!obj->options->variable_width_plaintext_p) + fontstyle = "font-family: -moz-fixed"; + + if (nsMimeOutput::nsMimeMessageBodyDisplay == obj->options->format_out || + nsMimeOutput::nsMimeMessagePrintOutput == obj->options->format_out) + { + int32_t fontSize; // default font size + int32_t fontSizePercentage; // size percentage + nsresult rv = GetMailNewsFont(obj, + !obj->options->variable_width_plaintext_p, + &fontSize, &fontSizePercentage, fontLang); + if (NS_SUCCEEDED(rv)) + { + if ( ! fontstyle.IsEmpty() ) { + fontstyle += "; "; + } + fontstyle += "font-size: "; + fontstyle.AppendInt(fontSize); + fontstyle += "px;"; + } + } + + // Opening <div>. We currently have to add formatting here. :-( + nsAutoCString openingDiv; + if (!quoting) + /* 4.x' editor can't break <div>s (e.g. to interleave comments). + We'll add the class to the <blockquote type=cite> later. */ + { + openingDiv = "<div class=\"moz-text-plain\""; + if (!plainHTML) + { + if (obj->options->wrap_long_lines_p) + openingDiv += " wrap=true"; + else + openingDiv += " wrap=false"; + + if (graphicalQuote) + openingDiv += " graphical-quote=true"; + else + openingDiv += " graphical-quote=false"; + + if (!fontstyle.IsEmpty()) + { + openingDiv += " style=\""; + openingDiv += fontstyle; + openingDiv += '\"'; + } + if (!fontLang.IsEmpty()) + { + openingDiv += " lang=\""; + openingDiv += fontLang; + openingDiv += '\"'; + } + } + openingDiv += "><pre wrap>\n"; + } + else + openingDiv = "<pre wrap>\n"; + + /* text/plain objects always have separators before and after them. + Note that this is not the case for text/enriched objects. */ + status = MimeObject_write_separator(obj); + if (status < 0) return status; + + status = MimeObject_write(obj, openingDiv.get(), openingDiv.Length(), true); + if (status < 0) return status; + } + } + + return 0; +} + +static int +MimeInlineTextPlain_parse_eof (MimeObject *obj, bool abort_p) +{ + int status; + + // Has this method already been called for this object? + // In that case return. + if (obj->closed_p) return 0; + + nsCString citationColor; + MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj; + if (text && text->mCitationColor) + citationColor.Adopt(text->mCitationColor); + + bool quoting = ( obj->options + && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting + ) ); // see above + + bool rawPlainText = obj->options && + (obj->options->format_out == nsMimeOutput::nsMimeMessageFilterSniffer + || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach); + + /* Run parent method first, to flush out any buffered data. */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + + if (!obj->output_p) return 0; + + if (obj->options && + obj->options->write_html_p && + obj->options->output_fn && + !abort_p && !rawPlainText) + { + MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj; + if (text->mIsSig && !quoting) + { + status = MimeObject_write(obj, "</div>", 6, false); // .moz-txt-sig + if (status < 0) return status; + } + status = MimeObject_write(obj, "</pre>", 6, false); + if (status < 0) return status; + if (!quoting) + { + status = MimeObject_write(obj, "</div>", 6, false); + // .moz-text-plain + if (status < 0) return status; + } + + /* text/plain objects always have separators before and after them. + Note that this is not the case for text/enriched objects. + */ + status = MimeObject_write_separator(obj); + if (status < 0) return status; + } + + return 0; +} + + +static int +MimeInlineTextPlain_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + int status; + bool quoting = ( obj->options + && ( obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting || + obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting + ) ); // see above + bool plainHTML = quoting || (obj->options && + obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs); + // see above + + bool rawPlainText = obj->options && + (obj->options->format_out == nsMimeOutput::nsMimeMessageFilterSniffer + || obj->options->format_out == nsMimeOutput::nsMimeMessageAttach); + + // this routine gets called for every line of data that comes through the + // mime converter. It's important to make sure we are efficient with + // how we allocate memory in this routine. be careful if you go to add + // more to this routine. + + NS_ASSERTION(length > 0, "zero length"); + if (length <= 0) return 0; + + mozITXTToHTMLConv *conv = GetTextConverter(obj->options); + MimeInlineTextPlain *text = (MimeInlineTextPlain *) obj; + + bool skipConversion = !conv || rawPlainText || + (obj->options && obj->options->force_user_charset); + + char *mailCharset = NULL; + nsresult rv; + + if (!skipConversion) + { + nsDependentCString inputStr(line, length); + nsAutoString lineSourceStr; + + // For 'SaveAs', |line| is in |mailCharset|. + // convert |line| to UTF-16 before 'html'izing (calling ScanTXT()) + if (obj->options->format_out == nsMimeOutput::nsMimeMessageSaveAs) + { // Get the mail charset of this message. + MimeInlineText *inlinetext = (MimeInlineText *) obj; + if (!inlinetext->initializeCharset) + ((MimeInlineTextClass*)&mimeInlineTextClass)->initialize_charset(obj); + mailCharset = inlinetext->charset; + if (mailCharset && *mailCharset) { + rv = nsMsgI18NConvertToUnicode(mailCharset, inputStr, lineSourceStr); + NS_ENSURE_SUCCESS(rv, -1); + } + else // this probably never happens ... + CopyUTF8toUTF16(inputStr, lineSourceStr); + } + else // line is in UTF-8 + CopyUTF8toUTF16(inputStr, lineSourceStr); + + nsAutoCString prefaceResultStr; // Quoting stuff before the real text + + // Recognize quotes + uint32_t oldCiteLevel = text->mCiteLevel; + uint32_t logicalLineStart = 0; + rv = conv->CiteLevelTXT(lineSourceStr.get(), + &logicalLineStart, &(text->mCiteLevel)); + NS_ENSURE_SUCCESS(rv, -1); + + // Find out, which recognitions to do + uint32_t whattodo = obj->options->whattodo; + if (plainHTML) + { + if (quoting) + whattodo = 0; // This is done on Send. Don't do it twice. + else + whattodo = whattodo & ~mozITXTToHTMLConv::kGlyphSubstitution; + /* Do recognition for the case, the result is viewed in + Mozilla, but not GlyphSubstitution, because other UAs + might not be able to display the glyphs. */ + if (!text->mBlockquoting) + text->mCiteLevel = 0; + } + + // Write blockquote + if (text->mCiteLevel > oldCiteLevel) + { + prefaceResultStr += "</pre>"; + for (uint32_t i = 0; i < text->mCiteLevel - oldCiteLevel; i++) + { + nsAutoCString style; + MimeTextBuildPrefixCSS(text->mQuotedSizeSetting, text->mQuotedStyleSetting, + text->mCitationColor, style); + if (!plainHTML && !style.IsEmpty()) + { + prefaceResultStr += "<blockquote type=cite style=\""; + prefaceResultStr += style; + prefaceResultStr += "\">"; + } + else + prefaceResultStr += "<blockquote type=cite>"; + } + prefaceResultStr += "<pre wrap>\n"; + } + else if (text->mCiteLevel < oldCiteLevel) + { + prefaceResultStr += "</pre>"; + for (uint32_t i = 0; i < oldCiteLevel - text->mCiteLevel; i++) + prefaceResultStr += "</blockquote>"; + prefaceResultStr += "<pre wrap>\n"; + } + + // Write plain text quoting tags + if (logicalLineStart != 0 && !(plainHTML && text->mBlockquoting)) + { + if (!plainHTML) + prefaceResultStr += "<span class=\"moz-txt-citetags\">"; + + nsString citeTagsSource(StringHead(lineSourceStr, logicalLineStart)); + + // Convert to HTML + nsString citeTagsResultUnichar; + rv = conv->ScanTXT(citeTagsSource.get(), 0 /* no recognition */, + getter_Copies(citeTagsResultUnichar)); + if (NS_FAILED(rv)) return -1; + + prefaceResultStr.Append(NS_ConvertUTF16toUTF8(citeTagsResultUnichar)); + if (!plainHTML) + prefaceResultStr += "</span>"; + } + + + // recognize signature + if ((lineSourceStr.Length() >= 4) + && lineSourceStr.First() == '-' + && Substring(lineSourceStr, 0, 3).EqualsLiteral("-- ") + && (lineSourceStr[3] == '\r' || lineSourceStr[3] == '\n') ) + { + text->mIsSig = true; + if (!quoting) + prefaceResultStr += "<div class=\"moz-txt-sig\">"; + } + + + /* This is the main TXT to HTML conversion: + escaping (very important), eventually recognizing etc. */ + nsString lineResultUnichar; + + rv = conv->ScanTXT(lineSourceStr.get() + logicalLineStart, + whattodo, getter_Copies(lineResultUnichar)); + NS_ENSURE_SUCCESS(rv, -1); + + if (!(text->mIsSig && quoting && text->mStripSig)) + { + status = MimeObject_write(obj, prefaceResultStr.get(), prefaceResultStr.Length(), true); + if (status < 0) return status; + nsAutoCString outString; + if (obj->options->format_out != nsMimeOutput::nsMimeMessageSaveAs || + !mailCharset || !*mailCharset) + CopyUTF16toUTF8(lineResultUnichar, outString); + else + { // convert back to mailCharset before writing. + rv = nsMsgI18NConvertFromUnicode(mailCharset, + lineResultUnichar, outString); + NS_ENSURE_SUCCESS(rv, -1); + } + + status = MimeObject_write(obj, outString.get(), outString.Length(), true); + } + else + { + status = 0; + } + } + else + { + status = MimeObject_write(obj, line, length, true); + } + + return status; +} + diff --git a/mailnews/mime/src/mimetpla.h b/mailnews/mime/src/mimetpla.h new file mode 100644 index 000000000..b846403bc --- /dev/null +++ b/mailnews/mime/src/mimetpla.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* The MimeInlineTextPlain class implements the text/plain MIME content type, + and is also used for all otherwise-unknown text/ subtypes. + */ + +#ifndef _MIMETPLA_H_ +#define _MIMETPLA_H_ + +#include "mimetext.h" + +typedef struct MimeInlineTextPlainClass MimeInlineTextPlainClass; +typedef struct MimeInlineTextPlain MimeInlineTextPlain; + +struct MimeInlineTextPlainClass { + MimeInlineTextClass text; +}; + +extern MimeInlineTextPlainClass mimeInlineTextPlainClass; + +struct MimeInlineTextPlain { + MimeInlineText text; + uint32_t mCiteLevel; + bool mBlockquoting; + //bool mInsideQuote; + int32_t mQuotedSizeSetting; // mail.quoted_size + int32_t mQuotedStyleSetting; // mail.quoted_style + char *mCitationColor; // mail.citation_color + bool mStripSig; // mail.strip_sig_on_reply + bool mIsSig; +}; + +#define MimeInlineTextPlainClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETPLA_H_ */ diff --git a/mailnews/mime/src/mimetric.cpp b/mailnews/mime/src/mimetric.cpp new file mode 100644 index 000000000..95e08ac9b --- /dev/null +++ b/mailnews/mime/src/mimetric.cpp @@ -0,0 +1,353 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mimetric.h" +#include "mimebuf.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "msgCore.h" +#include <ctype.h> + +#define MIME_SUPERCLASS mimeInlineTextClass +MimeDefClass(MimeInlineTextRichtext, MimeInlineTextRichtextClass, + mimeInlineTextRichtextClass, &MIME_SUPERCLASS); + +static int MimeInlineTextRichtext_parse_line (const char *, int32_t, MimeObject *); +static int MimeInlineTextRichtext_parse_begin (MimeObject *); +static int MimeInlineTextRichtext_parse_eof (MimeObject *, bool); + +static int +MimeInlineTextRichtextClassInitialize(MimeInlineTextRichtextClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); + oclass->parse_begin = MimeInlineTextRichtext_parse_begin; + oclass->parse_line = MimeInlineTextRichtext_parse_line; + oclass->parse_eof = MimeInlineTextRichtext_parse_eof; + return 0; +} + +/* This function has this clunky interface because it needs to be called + from outside this module (no MimeObject, etc.) + */ +int +MimeRichtextConvert (const char *line, int32_t length, + MimeObject *obj, + char **obufferP, + int32_t *obuffer_sizeP, + bool enriched_p) +{ + /* RFC 1341 (the original MIME spec) defined text/richtext. + RFC 1563 superceded text/richtext with text/enriched. + The changes from text/richtext to text/enriched are: + - CRLF semantics are different + - << maps to < + - These tags were added: + <VERBATIM>, <NOFILL>, <PARAM>, <FLUSHBOTH> + - These tags were removed: + <COMMENT>, <OUTDENT>, <OUTDENTRIGHT>, <SAMEPAGE>, <SUBSCRIPT>, + <SUPERSCRIPT>, <HEADING>, <FOOTING>, <PARAGRAPH>, <SIGNATURE>, + <LT>, <NL>, <NP> + This method implements them both. + + draft-resnick-text-enriched-03.txt is a proposed update to 1563. + - These tags were added: + <FONTFAMILY>, <COLOR>, <PARAINDENT>, <LANG>. + However, all of these rely on the magic <PARAM> tag, which we + don't implement, so we're ignoring all of these. + Interesting fact: it's by Peter W. Resnick from Qualcomm (Eudora). + And it also says "It is fully expected that other text formatting + standards like HTML and SGML will supplant text/enriched in + Internet mail." + */ + int status = 0; + char *out; + const char *data_end; + const char *last_end; + const char *this_start; + const char *this_end; + unsigned int desired_size; + + // The code below must never expand the input by more than 5x; + // if it does, the desired_size multiplier (5) below must be changed too +#define BGROWTH 5 + if ( (uint32_t)length >= ( (uint32_t) 0xfffffffe)/BGROWTH ) + return -1; + desired_size = (length * BGROWTH) + 1; +#undef BGROWTH + if (desired_size >= (uint32_t) *obuffer_sizeP) + status = mime_GrowBuffer (desired_size, sizeof(char), 1024, + obufferP, obuffer_sizeP); + if (status < 0) return status; + + if (enriched_p) + { + for (this_start = line; this_start < line + length; this_start++) + if (!IS_SPACE (*this_start)) break; + if (this_start >= line + length) /* blank line */ + { + PL_strncpyz (*obufferP, "<BR>", *obuffer_sizeP); + return MimeObject_write(obj, *obufferP, strlen(*obufferP), true); + } + } + + uint32_t outlen = (uint32_t) *obuffer_sizeP; + out = *obufferP; + *out = 0; + + data_end = line + length; + last_end = line; + this_start = last_end; + this_end = this_start; + uint32_t addedlen = 0; + while (this_end < data_end) + { + /* Skip forward to next special character. */ + while (this_start < data_end && + *this_start != '<' && *this_start != '>' && + *this_start != '&') + this_start++; + + this_end = this_start; + + /* Skip to the end of the tag. */ + if (this_start < data_end && *this_start == '<') + { + this_end++; + while (this_end < data_end && + !IS_SPACE(*this_end) && + *this_end != '<' && *this_end != '>' && + *this_end != '&') + this_end++; + } + + this_end++; + + /* Push out the text preceeding the tag. */ + if (last_end && last_end != this_start) + { + memcpy (out, last_end, this_start - last_end); + out += this_start - last_end; + *out = 0; + outlen -= (this_start - last_end); + } + + if (this_start >= data_end) + break; + else if (*this_start == '&') + { + PL_strncpyz (out, "&", outlen); + addedlen = strlen(out); + outlen -= addedlen; + out += addedlen; + } + else if (*this_start == '>') + { + PL_strncpyz (out, ">", outlen); + addedlen = strlen(out); + outlen -= addedlen; + out += addedlen; + } + else if (enriched_p && + this_start < data_end + 1 && + this_start[0] == '<' && + this_start[1] == '<') + { + PL_strncpyz (out, "<", outlen); + addedlen = strlen(out); + outlen -= addedlen; + out += addedlen; + } + else if (this_start != this_end) + { + /* Push out this ID. */ + const char *old = this_start + 1; + const char *tag_open = 0; + const char *tag_close = 0; + if (*old == '/') + { + /* This is </tag> */ + old++; + } + + switch (*old) + { + case 'b': case 'B': + if (!PL_strncasecmp ("BIGGER>", old, 7)) + tag_open = "<FONT SIZE=\"+1\">", tag_close = "</FONT>"; + else if (!PL_strncasecmp ("BLINK>", old, 5)) + /* Of course, both text/richtext and text/enriched must be + enhanced *somehow*... Or else what would people think. */ + tag_open = "<BLINK>", tag_close = "</BLINK>"; + else if (!PL_strncasecmp ("BOLD>", old, 5)) + tag_open = "<B>", tag_close = "</B>"; + break; + case 'c': case 'C': + if (!PL_strncasecmp ("CENTER>", old, 7)) + tag_open = "<CENTER>", tag_close = "</CENTER>"; + else if (!enriched_p && + !PL_strncasecmp ("COMMENT>", old, 8)) + tag_open = "<!-- ", tag_close = " -->"; + break; + case 'e': case 'E': + if (!PL_strncasecmp ("EXCERPT>", old, 8)) + tag_open = "<BLOCKQUOTE>", tag_close = "</BLOCKQUOTE>"; + break; + case 'f': case 'F': + if (!PL_strncasecmp ("FIXED>", old, 6)) + tag_open = "<TT>", tag_close = "</TT>"; + else if (enriched_p && + !PL_strncasecmp ("FLUSHBOTH>", old, 10)) + tag_open = "<P ALIGN=LEFT>", tag_close = "</P>"; + else if (!PL_strncasecmp ("FLUSHLEFT>", old, 10)) + tag_open = "<P ALIGN=LEFT>", tag_close = "</P>"; + else if (!PL_strncasecmp ("FLUSHRIGHT>", old, 11)) + tag_open = "<P ALIGN=RIGHT>", tag_close = "</P>"; + else if (!enriched_p && + !PL_strncasecmp ("FOOTING>", old, 8)) + tag_open = "<H6>", tag_close = "</H6>"; + break; + case 'h': case 'H': + if (!enriched_p && + !PL_strncasecmp ("HEADING>", old, 8)) + tag_open = "<H6>", tag_close = "</H6>"; + break; + case 'i': case 'I': + if (!PL_strncasecmp ("INDENT>", old, 7)) + tag_open = "<UL>", tag_close = "</UL>"; + else if (!PL_strncasecmp ("INDENTRIGHT>", old, 12)) + tag_open = 0, tag_close = 0; +/* else if (!enriched_p && + !PL_strncasecmp ("ISO-8859-", old, 9)) + tag_open = 0, tag_close = 0; */ + else if (!PL_strncasecmp ("ITALIC>", old, 7)) + tag_open = "<I>", tag_close = "</I>"; + break; + case 'l': case 'L': + if (!enriched_p && + !PL_strncasecmp ("LT>", old, 3)) + tag_open = "<", tag_close = 0; + break; + case 'n': case 'N': + if (!enriched_p && + !PL_strncasecmp ("NL>", old, 3)) + tag_open = "<BR>", tag_close = 0; + if (enriched_p && + !PL_strncasecmp ("NOFILL>", old, 7)) + tag_open = "<NOBR>", tag_close = "</NOBR>"; +/* else if (!enriched_p && + !PL_strncasecmp ("NO-OP>", old, 6)) + tag_open = 0, tag_close = 0; */ +/* else if (!enriched_p && + !PL_strncasecmp ("NP>", old, 3)) + tag_open = 0, tag_close = 0; */ + break; + case 'o': case 'O': + if (!enriched_p && + !PL_strncasecmp ("OUTDENT>", old, 8)) + tag_open = 0, tag_close = 0; + else if (!enriched_p && + !PL_strncasecmp ("OUTDENTRIGHT>", old, 13)) + tag_open = 0, tag_close = 0; + break; + case 'p': case 'P': + if (enriched_p && + !PL_strncasecmp ("PARAM>", old, 6)) + tag_open = "<!-- ", tag_close = " -->"; + else if (!enriched_p && + !PL_strncasecmp ("PARAGRAPH>", old, 10)) + tag_open = "<P>", tag_close = 0; + break; + case 's': case 'S': + if (!enriched_p && + !PL_strncasecmp ("SAMEPAGE>", old, 9)) + tag_open = 0, tag_close = 0; + else if (!enriched_p && + !PL_strncasecmp ("SIGNATURE>", old, 10)) + tag_open = "<I><FONT SIZE=\"-1\">", tag_close = "</FONT></I>"; + else if (!PL_strncasecmp ("SMALLER>", old, 8)) + tag_open = "<FONT SIZE=\"-1\">", tag_close = "</FONT>"; + else if (!enriched_p && + !PL_strncasecmp ("SUBSCRIPT>", old, 10)) + tag_open = "<SUB>", tag_close = "</SUB>"; + else if (!enriched_p && + !PL_strncasecmp ("SUPERSCRIPT>", old, 12)) + tag_open = "<SUP>", tag_close = "</SUP>"; + break; + case 'u': case 'U': + if (!PL_strncasecmp ("UNDERLINE>", old, 10)) + tag_open = "<U>", tag_close = "</U>"; +/* else if (!enriched_p && + !PL_strncasecmp ("US-ASCII>", old, 10)) + tag_open = 0, tag_close = 0; */ + break; + case 'v': case 'V': + if (enriched_p && + !PL_strncasecmp ("VERBATIM>", old, 9)) + tag_open = "<PRE>", tag_close = "</PRE>"; + break; + } + + if (this_start[1] == '/') + { + if (tag_close) PL_strncpyz (out, tag_close, outlen); + addedlen = strlen (out); + outlen -= addedlen; + out += addedlen; + } + else + { + if (tag_open) PL_strncpyz (out, tag_open, outlen); + addedlen = strlen (out); + outlen -= addedlen; + out += addedlen; + } + } + + /* now go around again */ + last_end = this_end; + this_start = last_end; + } + *out = 0; + + return MimeObject_write(obj, *obufferP, out - *obufferP, true); +} + + +static int +MimeInlineTextRichtext_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + bool enriched_p = (((MimeInlineTextRichtextClass *) obj->clazz) + ->enriched_p); + + return MimeRichtextConvert (line, length, + obj, + &obj->obuffer, &obj->obuffer_size, + enriched_p); +} + + +static int +MimeInlineTextRichtext_parse_begin (MimeObject *obj) +{ + int status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); + char s[] = ""; + if (status < 0) return status; + return MimeObject_write(obj, s, 0, true); /* force out any separators... */ +} + + +static int +MimeInlineTextRichtext_parse_eof (MimeObject *obj, bool abort_p) +{ + int status; + if (obj->closed_p) return 0; + + /* Run parent method first, to flush out any buffered data. */ + status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + if (status < 0) return status; + + return 0; +} diff --git a/mailnews/mime/src/mimetric.h b/mailnews/mime/src/mimetric.h new file mode 100644 index 000000000..fe6c66974 --- /dev/null +++ b/mailnews/mime/src/mimetric.h @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMETRIC_H_ +#define _MIMETRIC_H_ + +#include "mimetext.h" + +/* The MimeInlineTextRichtext class implements the (obsolete and deprecated) + text/richtext MIME content type, as defined in RFC 1341, and also the + text/enriched MIME content type, as defined in RFC 1563. + */ + +typedef struct MimeInlineTextRichtextClass MimeInlineTextRichtextClass; +typedef struct MimeInlineTextRichtext MimeInlineTextRichtext; + +struct MimeInlineTextRichtextClass { + MimeInlineTextClass text; + bool enriched_p; /* Whether we should act like text/enriched instead. */ +}; + +extern MimeInlineTextRichtextClass mimeInlineTextRichtextClass; + +struct MimeInlineTextRichtext { + MimeInlineText text; +}; + +#define MimeInlineTextRichtextClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMETRIC_H_ */ diff --git a/mailnews/mime/src/mimeunty.cpp b/mailnews/mime/src/mimeunty.cpp new file mode 100644 index 000000000..212b08ba8 --- /dev/null +++ b/mailnews/mime/src/mimeunty.cpp @@ -0,0 +1,588 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimeunty.h" +#include "prmem.h" +#include "plstr.h" +#include "prlog.h" +#include "nsMimeTypes.h" +#include "msgCore.h" +#include "nsMimeStringResources.h" +#include <ctype.h> + +#define MIME_SUPERCLASS mimeContainerClass +MimeDefClass(MimeUntypedText, MimeUntypedTextClass, + mimeUntypedTextClass, &MIME_SUPERCLASS); + +static int MimeUntypedText_initialize (MimeObject *); +static void MimeUntypedText_finalize (MimeObject *); +static int MimeUntypedText_parse_begin (MimeObject *); +static int MimeUntypedText_parse_line (const char *, int32_t, MimeObject *); + +static int MimeUntypedText_open_subpart (MimeObject *obj, + MimeUntypedTextSubpartType ttype, + const char *type, + const char *enc, + const char *name, + const char *desc); +static int MimeUntypedText_close_subpart (MimeObject *obj); + +static bool MimeUntypedText_uu_begin_line_p(const char *line, int32_t length, + MimeDisplayOptions *opt, + char **type_ret, + char **name_ret); +static bool MimeUntypedText_uu_end_line_p(const char *line, int32_t length); + +static bool MimeUntypedText_yenc_begin_line_p(const char *line, int32_t length, + MimeDisplayOptions *opt, + char **type_ret, + char **name_ret); +static bool MimeUntypedText_yenc_end_line_p(const char *line, int32_t length); + +static bool MimeUntypedText_binhex_begin_line_p(const char *line, + int32_t length, + MimeDisplayOptions *opt); +static bool MimeUntypedText_binhex_end_line_p(const char *line, + int32_t length); + +static int +MimeUntypedTextClassInitialize(MimeUntypedTextClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + PR_ASSERT(!oclass->class_initialized); + oclass->initialize = MimeUntypedText_initialize; + oclass->finalize = MimeUntypedText_finalize; + oclass->parse_begin = MimeUntypedText_parse_begin; + oclass->parse_line = MimeUntypedText_parse_line; + return 0; +} + + +static int +MimeUntypedText_initialize (MimeObject *object) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->initialize(object); +} + +static void +MimeUntypedText_finalize (MimeObject *object) +{ + MimeUntypedText *uty = (MimeUntypedText *) object; + + if (uty->open_hdrs) + { + /* Oops, those shouldn't still be here... */ + MimeHeaders_free(uty->open_hdrs); + uty->open_hdrs = 0; + } + + /* What about the open_subpart? We're gonna have to assume that it + is also on the MimeContainer->children list, and will get cleaned + up by that class. */ + + ((MimeObjectClass*)&MIME_SUPERCLASS)->finalize(object); +} + +static int +MimeUntypedText_parse_begin (MimeObject *obj) +{ + return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_begin(obj); +} + +static int +MimeUntypedText_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + MimeUntypedText *uty = (MimeUntypedText *) obj; + int status = 0; + char *name = 0, *type = 0; + bool begin_line_p = false; + + NS_ASSERTION(line && *line, "empty line in mime untyped parse_line"); + if (!line || !*line) return -1; + + /* If we're supposed to write this object, but aren't supposed to convert + it to HTML, simply pass it through unaltered. */ + if (obj->output_p && + obj->options && + !obj->options->write_html_p && + obj->options->output_fn) + return MimeObject_write(obj, line, length, true); + + + /* Open a new sub-part if this line demands it. + */ + if (line[0] == 'b' && + MimeUntypedText_uu_begin_line_p(line, length, obj->options, + &type, &name)) + { + /* Close the old part and open a new one. */ + status = MimeUntypedText_open_subpart (obj, + MimeUntypedTextSubpartTypeUUE, + type, ENCODING_UUENCODE, + name, NULL); + PR_FREEIF(name); + PR_FREEIF(type); + if (status < 0) return status; + begin_line_p = true; + } + + else if (line[0] == '=' && + MimeUntypedText_yenc_begin_line_p(line, length, obj->options, + &type, &name)) + { + /* Close the old part and open a new one. */ + status = MimeUntypedText_open_subpart (obj, + MimeUntypedTextSubpartTypeYEnc, + type, ENCODING_YENCODE, + name, NULL); + PR_FREEIF(name); + PR_FREEIF(type); + if (status < 0) return status; + begin_line_p = true; + } + + else if (line[0] == '(' && line[1] == 'T' && + MimeUntypedText_binhex_begin_line_p(line, length, obj->options)) + { + /* Close the old part and open a new one. */ + status = MimeUntypedText_open_subpart (obj, + MimeUntypedTextSubpartTypeBinhex, + APPLICATION_BINHEX, NULL, + NULL, NULL); + if (status < 0) return status; + begin_line_p = true; + } + + /* Open a text/plain sub-part if there is no sub-part open. + */ + if (!uty->open_subpart) + { + // rhp: If we get here and we are being fed a line ending, we should + // just eat it and continue and if we really get more data, we'll open + // up the subpart then. + // + if (line[0] == '\r') return 0; + if (line[0] == '\n') return 0; + + PR_ASSERT(!begin_line_p); + status = MimeUntypedText_open_subpart (obj, + MimeUntypedTextSubpartTypeText, + TEXT_PLAIN, NULL, NULL, NULL); + PR_ASSERT(uty->open_subpart); + if (!uty->open_subpart) return -1; + if (status < 0) return status; + } + + /* Hand this line to the currently-open sub-part. + */ + status = uty->open_subpart->clazz->parse_buffer(line, length, + uty->open_subpart); + if (status < 0) return status; + + /* Close this sub-part if this line demands it. + */ + if (begin_line_p) + ; + else if (line[0] == 'e' && + uty->type == MimeUntypedTextSubpartTypeUUE && + MimeUntypedText_uu_end_line_p(line, length)) + { + status = MimeUntypedText_close_subpart (obj); + if (status < 0) return status; + NS_ASSERTION(!uty->open_subpart, "no open subpart"); + } + else if (line[0] == '=' && + uty->type == MimeUntypedTextSubpartTypeYEnc && + MimeUntypedText_yenc_end_line_p(line, length)) + { + status = MimeUntypedText_close_subpart (obj); + if (status < 0) return status; + NS_ASSERTION(!uty->open_subpart, "no open subpart"); + } + else if (uty->type == MimeUntypedTextSubpartTypeBinhex && + MimeUntypedText_binhex_end_line_p(line, length)) + { + status = MimeUntypedText_close_subpart (obj); + if (status < 0) return status; + NS_ASSERTION(!uty->open_subpart, "no open subpart"); + } + + return 0; +} + + +static int +MimeUntypedText_close_subpart (MimeObject *obj) +{ + MimeUntypedText *uty = (MimeUntypedText *) obj; + int status; + + if (uty->open_subpart) + { + status = uty->open_subpart->clazz->parse_eof(uty->open_subpart, false); + uty->open_subpart = 0; + + PR_ASSERT(uty->open_hdrs); + if (uty->open_hdrs) + { + MimeHeaders_free(uty->open_hdrs); + uty->open_hdrs = 0; + } + uty->type = MimeUntypedTextSubpartTypeText; + if (status < 0) return status; + + /* Never put out a separator between sub-parts of UntypedText. + (This bypasses the rule that text/plain subparts always + have separators before and after them.) + */ + if (obj->options && obj->options->state) + obj->options->state->separator_suppressed_p = true; + } + + PR_ASSERT(!uty->open_hdrs); + return 0; +} + +static int +MimeUntypedText_open_subpart (MimeObject *obj, + MimeUntypedTextSubpartType ttype, + const char *type, + const char *enc, + const char *name, + const char *desc) +{ + MimeUntypedText *uty = (MimeUntypedText *) obj; + int status = 0; + char *h = 0; + + if (!type || !*type || !PL_strcasecmp(type, UNKNOWN_CONTENT_TYPE)) + type = APPLICATION_OCTET_STREAM; + if (enc && !*enc) + enc = 0; + if (desc && !*desc) + desc = 0; + if (name && !*name) + name = 0; + + if (uty->open_subpart) + { + status = MimeUntypedText_close_subpart (obj); + if (status < 0) return status; + } + NS_ASSERTION(!uty->open_subpart, "no open subpart"); + NS_ASSERTION(!uty->open_hdrs, "no open headers"); + + /* To make one of these implicitly-typed sub-objects, we make up a fake + header block, containing only the minimum number of MIME headers needed. + We could do most of this (Type and Encoding) by making a null header + block, and simply setting obj->content_type and obj->encoding; but making + a fake header block is better for two reasons: first, it means that + something will actually be displayed when in `Show All Headers' mode; + and second, it's the only way to communicate the filename parameter, + aside from adding a new slot to MimeObject (which is something to be + avoided when possible.) + */ + + uty->open_hdrs = MimeHeaders_new(); + if (!uty->open_hdrs) return MIME_OUT_OF_MEMORY; + + uint32_t hlen = strlen(type) + + (enc ? strlen(enc) : 0) + + (desc ? strlen(desc) : 0) + + (name ? strlen(name) : 0) + + 100; + h = (char *) PR_MALLOC(hlen); + if (!h) return MIME_OUT_OF_MEMORY; + + PL_strncpyz(h, HEADER_CONTENT_TYPE ": ", hlen); + PL_strcatn(h, hlen, type); + PL_strcatn(h, hlen, MSG_LINEBREAK); + status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs); + if (status < 0) goto FAIL; + + if (enc) + { + PL_strncpyz(h, HEADER_CONTENT_TRANSFER_ENCODING ": ", hlen); + PL_strcatn(h, hlen, enc); + PL_strcatn(h, hlen, MSG_LINEBREAK); + status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs); + if (status < 0) goto FAIL; + } + + if (desc) + { + PL_strncpyz(h, HEADER_CONTENT_DESCRIPTION ": ", hlen); + PL_strcatn(h, hlen, desc); + PL_strcatn(h, hlen, MSG_LINEBREAK); + status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs); + if (status < 0) goto FAIL; + } + if (name) + { + PL_strncpyz(h, HEADER_CONTENT_DISPOSITION ": inline; filename=\"", hlen); + PL_strcatn(h, hlen, name); + PL_strcatn(h, hlen, "\"" MSG_LINEBREAK); + status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs); + if (status < 0) goto FAIL; + } + + /* push out a blank line. */ + PL_strncpyz(h, MSG_LINEBREAK, hlen); + status = MimeHeaders_parse_line(h, strlen(h), uty->open_hdrs); + if (status < 0) goto FAIL; + + + /* Create a child... */ + { + bool horrid_kludge = (obj->options && obj->options->state && + obj->options->state->first_part_written_p); + if (horrid_kludge) + obj->options->state->first_part_written_p = false; + + uty->open_subpart = mime_create(type, uty->open_hdrs, obj->options); + + if (horrid_kludge) + obj->options->state->first_part_written_p = true; + + if (!uty->open_subpart) + { + status = MIME_OUT_OF_MEMORY; + goto FAIL; + } + } + + /* Add it to the list... */ + status = ((MimeContainerClass *) obj->clazz)->add_child(obj, + uty->open_subpart); + if (status < 0) + { + mime_free(uty->open_subpart); + uty->open_subpart = 0; + goto FAIL; + } + + /* And start its parser going. */ + status = uty->open_subpart->clazz->parse_begin(uty->open_subpart); + if (status < 0) + { + /* MimeContainer->finalize will take care of shutting it down now. */ + uty->open_subpart = 0; + goto FAIL; + } + + uty->type = ttype; + + FAIL: + PR_FREEIF(h); + + if (status < 0 && uty->open_hdrs) + { + MimeHeaders_free(uty->open_hdrs); + uty->open_hdrs = 0; + } + + return status; +} + +static bool +MimeUntypedText_uu_begin_line_p(const char *line, int32_t length, + MimeDisplayOptions *opt, + char **type_ret, char **name_ret) +{ + const char *s; + char *name = 0; + char *type = 0; + + if (type_ret) *type_ret = 0; + if (name_ret) *name_ret = 0; + + if (strncmp (line, "begin ", 6)) return false; + /* ...then three or four octal digits. */ + s = line + 6; + if (*s < '0' || *s > '7') return false; + s++; + if (*s < '0' || *s > '7') return false; + s++; + if (*s < '0' || *s > '7') return false; + s++; + if (*s == ' ') + s++; + else + { + if (*s < '0' || *s > '7') return false; + s++; + if (*s != ' ') return false; + } + + while (IS_SPACE(*s)) + s++; + + name = (char *) PR_MALLOC(((line+length)-s) + 1); + if (!name) return false; /* grr... */ + memcpy(name, s, (line+length)-s); + name[(line+length)-s] = 0; + + /* take off newline. */ + if (name[strlen(name)-1] == '\n') name[strlen(name)-1] = 0; + if (name[strlen(name)-1] == '\r') name[strlen(name)-1] = 0; + + /* Now try and figure out a type. + */ + if (opt && opt->file_type_fn) + type = opt->file_type_fn(name, opt->stream_closure); + else + type = 0; + + if (name_ret) + *name_ret = name; + else + PR_FREEIF(name); + + if (type_ret) + *type_ret = type; + else + PR_FREEIF(type); + + return true; +} + +static bool +MimeUntypedText_uu_end_line_p(const char *line, int32_t length) +{ +#if 0 + /* A strictly conforming uuencode end line. */ + return (line[0] == 'e' && + line[1] == 'n' && + line[2] == 'd' && + (line[3] == 0 || IS_SPACE(line[3]))); +#else + /* ...but, why don't we accept any line that begins with the three + letters "END" in any case: I've seen lots of partial messages + that look like + + BEGIN----- Cut Here----- + begin 644 foo.gif + Mxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + Mxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + Mxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + END------- Cut Here----- + + so let's be lenient here. (This is only for the untyped-text-plain + case -- the uudecode parser itself is strict.) + */ + return (line[0] == ' ' || + line[0] == '\t' || + ((line[0] == 'e' || line[0] == 'E') && + (line[1] == 'n' || line[1] == 'N') && + (line[2] == 'd' || line[2] == 'D'))); +#endif +} + +static bool +MimeUntypedText_yenc_begin_line_p(const char *line, int32_t length, + MimeDisplayOptions *opt, + char **type_ret, char **name_ret) +{ + const char *s; + const char *endofline = line + length; + char *name = 0; + char *type = 0; + + if (type_ret) *type_ret = 0; + if (name_ret) *name_ret = 0; + + /* we don't support yenc V2 neither multipart yencode, + therefore the second parameter should always be "line="*/ + if (length < 13 || strncmp (line, "=ybegin line=", 13)) return false; + + /* ...then couple digits. */ + for (s = line + 13; s < endofline; s ++) + if (*s < '0' || *s > '9') + break; + + /* ...next, look for <space>size= */ + if ((endofline - s) < 6 || strncmp (s, " size=", 6)) return false; + + /* ...then couple digits. */ + for (s += 6; s < endofline; s ++) + if (*s < '0' || *s > '9') + break; + + /* ...next, look for <space>name= */ + if ((endofline - s) < 6 || strncmp (s, " name=", 6)) return false; + + /* anything left is the file name */ + s += 6; + name = (char *) PR_MALLOC((endofline-s) + 1); + if (!name) return false; /* grr... */ + memcpy(name, s, endofline-s); + name[endofline-s] = 0; + + /* take off newline. */ + if (name[strlen(name)-1] == '\n') name[strlen(name)-1] = 0; + if (name[strlen(name)-1] == '\r') name[strlen(name)-1] = 0; + + /* Now try and figure out a type. + */ + if (opt && opt->file_type_fn) + type = opt->file_type_fn(name, opt->stream_closure); + else + type = 0; + + if (name_ret) + *name_ret = name; + else + PR_FREEIF(name); + + if (type_ret) + *type_ret = type; + else + PR_FREEIF(type); + + return true; +} + +static bool +MimeUntypedText_yenc_end_line_p(const char *line, int32_t length) +{ + if (length < 11 || strncmp (line, "=yend size=", 11)) return false; + + return true; +} + + +#define BINHEX_MAGIC "(This file must be converted with BinHex 4.0)" +#define BINHEX_MAGIC_LEN 45 + +static bool +MimeUntypedText_binhex_begin_line_p(const char *line, int32_t length, + MimeDisplayOptions *opt) +{ + if (length <= BINHEX_MAGIC_LEN) + return false; + + while(length > 0 && IS_SPACE(line[length-1])) + length--; + + if (length != BINHEX_MAGIC_LEN) + return false; + + if (!strncmp(line, BINHEX_MAGIC, BINHEX_MAGIC_LEN)) + return true; + else + return false; +} + +static bool +MimeUntypedText_binhex_end_line_p(const char *line, int32_t length) +{ + if (length > 0 && line[length-1] == '\n') length--; + if (length > 0 && line[length-1] == '\r') length--; + + if (length != 0 && length != 64) + return true; + else + return false; +} diff --git a/mailnews/mime/src/mimeunty.h b/mailnews/mime/src/mimeunty.h new file mode 100644 index 000000000..9661f68bc --- /dev/null +++ b/mailnews/mime/src/mimeunty.h @@ -0,0 +1,70 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEUNTY_H_ +#define _MIMEUNTY_H_ + +#include "mimecont.h" + +/* The MimeUntypedText class is used for untyped message contents, that is, + it is the class used for the body of a message/rfc822 object which had + *no* Content-Type header, as opposed to an unrecognized content-type. + Such a message, technically, does not contain MIME data (it follows only + RFC 822, not RFC 1521.) + + This is a container class, and the reason for that is that it loosely + parses the body of the message looking for ``sub-parts'' and then + creates appropriate containers for them. + + More specifically, it looks for uuencoded data. It may do more than that + some day. + + Basically, the algorithm followed is: + + if line is "begin 644 foo.gif" + if there is an open sub-part, close it + add a sub-part with type: image/gif; encoding: x-uue + hand this line to it + and hand subsequent lines to that subpart + else if there is an open uuencoded sub-part, and line is "end" + hand this line to it + close off the uuencoded sub-part + else if there is an open sub-part + hand this line to it + else + open a text/plain subpart + hand this line to it + + Adding other types than uuencode to this (for example, PGP) would be + pretty straightforward. + */ + +typedef struct MimeUntypedTextClass MimeUntypedTextClass; +typedef struct MimeUntypedText MimeUntypedText; + +struct MimeUntypedTextClass { + MimeContainerClass container; +}; + +extern MimeUntypedTextClass mimeUntypedTextClass; + +typedef enum { + MimeUntypedTextSubpartTypeText, /* text/plain */ + MimeUntypedTextSubpartTypeUUE, /* uuencoded data */ + MimeUntypedTextSubpartTypeYEnc, /* yencoded data */ + MimeUntypedTextSubpartTypeBinhex /* Mac BinHex data */ +} MimeUntypedTextSubpartType; + +struct MimeUntypedText { + MimeContainer container; /* superclass variables */ + MimeObject *open_subpart; /* The part still-being-parsed */ + MimeUntypedTextSubpartType type; /* What kind of type it is */ + MimeHeaders *open_hdrs; /* The faked-up headers describing it */ +}; + +#define MimeUntypedTextClassInitializer(ITYPE,CSUPER) \ + { MimeContainerClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEUNTY_H_ */ diff --git a/mailnews/mime/src/modlmime.h b/mailnews/mime/src/modlmime.h new file mode 100644 index 000000000..547739885 --- /dev/null +++ b/mailnews/mime/src/modlmime.h @@ -0,0 +1,398 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _LIBMIME_H_ +#define _LIBMIME_H_ + +#ifdef XP_UNIX +#undef Bool +#endif + +#include "nsStringGlue.h" +#include "nsMailHeaders.h" +#include "nsIMimeStreamConverter.h" +#include "nsIUnicodeDecoder.h" +#include "nsIUnicodeEncoder.h" +#include "nsIPrefBranch.h" +#include "mozITXTToHTMLConv.h" +#include "nsCOMPtr.h" +#include "modmimee.h" // for MimeConverterOutputCallback + +#define MIME_DRAFTS + +/* Opaque object describing a block of message headers, and a couple of + routines for extracting data from one. + */ + +typedef struct MimeHeaders +{ + char *all_headers; /* A char* of the entire header section. */ + int32_t all_headers_fp; /* The length (it is not NULL-terminated.) */ + int32_t all_headers_size; /* The size of the allocated block. */ + + bool done_p; /* Whether we've read the end-of-headers marker + (the terminating blank line.) */ + + char **heads; /* An array of length n_headers which points + to the beginning of each distinct header: + just after the newline which terminated + the previous one. This is to speed search. + + This is not initialized until all the + headers have been read. + */ + int32_t heads_size; /* The length (and consequently, how many + distinct headers are in here.) */ + + + char *obuffer; /* This buffer is used for output. */ + int32_t obuffer_size; + int32_t obuffer_fp; + + char *munged_subject; /* What a hack. This is a place to write down + the subject header, after it's been + charset-ified and stuff. Remembered so that + we can later use it to generate the + <TITLE> tag. (Also works for giving names to RFC822 attachments) */ +} MimeHeaders; + +class MimeDisplayOptions; +class MimeParseStateObject; +typedef struct MSG_AttachmentData MSG_AttachmentData; + +/* Given the name of a header, returns the contents of that header as + a newly-allocated string (which the caller must free.) If the header + is not present, or has no contents, NULL is returned. + + If `strip_p' is true, then the data returned will be the first token + of the header; else it will be the full text of the header. (This is + useful for getting just "text/plain" from "text/plain; name=foo".) + + If `all_p' is false, then the first header encountered is used, and + any subsequent headers of the same name are ignored. If true, then + all headers of the same name are appended together (this is useful + for gathering up all CC headers into one, for example.) + */ +extern char *MimeHeaders_get(MimeHeaders *hdrs, + const char *header_name, + bool strip_p, + bool all_p); + +/* Given a header of the form of the MIME "Content-" headers, extracts a + named parameter from it, if it exists. For example, + MimeHeaders_get_parameter("text/plain; charset=us-ascii", "charset") + would return "us-ascii". + + Returns NULL if there is no match, or if there is an allocation failure. + + RFC2231 - MIME Parameter Value and Encoded Word Extensions: Character Sets, + Languages, and Continuations + + RFC2231 has added the character sets, languages, and continuations mechanism. + charset, and language information may also be returned to the caller. + Note that charset and language should be free()'d while + the return value (parameter) has to be PR_FREE'd. + + For example, + MimeHeaders_get_parameter("text/plain; name*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A", "name") + MimeHeaders_get_parameter("text/plain; name*0*=us-ascii'en-us'This%20is%20; CRLFLWSPname*1*=%2A%2A%2Afun%2A%2A%2A", "name") + would return "This is ***fun***" and *charset = "us-ascii", *language = "en-us" + */ +extern char *MimeHeaders_get_parameter (const char *header_value, + const char *parm_name, + char **charset, + char **language); + +extern MimeHeaders *MimeHeaders_copy (MimeHeaders *srcHeaders); + +extern void MimeHeaders_free (MimeHeaders *hdrs); + +typedef enum { + MimeHeadersAll, /* Show all headers */ + MimeHeadersSome, /* Show all "interesting" headers */ + MimeHeadersSomeNoRef, /* Same, but suppress the `References' header + (for when we're printing this message.) */ + MimeHeadersMicro, /* Show a one-line header summary */ + MimeHeadersMicroPlus, /* Same, but show the full recipient list as + well (To, CC, etc.) */ + MimeHeadersCitation, /* A one-line summary geared toward use in a + reply citation ("So-and-so wrote:") */ + MimeHeadersOnly, /* Just parse and output headers...nothing else! */ + MimeHeadersNone /* Skip showing any headers */ +} MimeHeadersState; + + +/* The signature for various callbacks in the MimeDisplayOptions structure. + */ +typedef char *(*MimeHTMLGeneratorFunction) (const char *data, void *closure, + MimeHeaders *headers); + +class MimeDisplayOptions +{ +public: + MimeDisplayOptions(); + virtual ~MimeDisplayOptions(); + mozITXTToHTMLConv *conv; // For text conversion... + nsCOMPtr<nsIPrefBranch> m_prefBranch; /* prefBranch-service */ + nsMimeOutputType format_out; // The format out type + nsCString charsetForCachedInputDecoder; + nsCOMPtr<nsIUnicodeDecoder> m_inputCharsetToUnicodeDecoder; + nsCOMPtr<nsIUnicodeEncoder> m_unicodeToUTF8Encoder; + + const char *url; /* Base URL for the document. This string should + be freed by the caller, after the parser + completes (possibly at the same time as the + MimeDisplayOptions itself.) */ + + MimeHeadersState headers; /* How headers should be displayed. */ + bool fancy_headers_p; /* Whether to do clever formatting of headers + using tables, instead of spaces. */ + + bool output_vcard_buttons_p; /* Whether to output the buttons */ + /* on vcards. */ + + bool variable_width_plaintext_p; /* Whether text/plain messages should + be in variable width, or fixed. */ + bool wrap_long_lines_p; /* Whether to wrap long lines in text/plain + messages. */ + + bool rot13_p; /* Whether text/plain parts should be rotated + Set by "?rot13=true" */ + char *part_to_load; /* The particular part of the multipart which + we are extracting. Set by "?part=3.2.4" */ + + bool no_output_p; /* Will never write output when this is true. + (When false, output or not may depend on other things.) + This needs to be in the options, because the method + MimeObject_parse_begin is controlling the property "output_p" + (on the MimeObject) so we need a way for other functions to + override it and tell that we do not want output. */ + + bool write_html_p; /* Whether the output should be HTML, or raw. */ + + bool decrypt_p; /* Whether all traces of xlateion should be + eradicated -- this is only meaningful when + write_html_p is false; we set this when + attaching a message for forwarding, since + forwarding someone else a message that wasn't + xlated for them doesn't work. We have to + dexlate it before sending it. + */ + + /* Whether this MIME part is a child of another part (and not top level). */ + bool is_child = false; + + uint32_t whattodo ; /* from the prefs, we'll get if the user wants to do glyph or structure substitutions and set this member variable. */ + + char *default_charset; /* If this is non-NULL, then it is the charset to + assume when no other one is specified via a + `charset' parameter. + */ + bool override_charset; /* If this is true, then we will assume that + all data is in the default_charset, regardless + of what the `charset' parameter of that part + says. (This is to cope with the fact that, in + the real world, many messages are mislabelled + with the wrong charset.) + */ + bool force_user_charset; /* this is the new strategy to deal with incorrectly + labeled attachments */ + + /* ======================================================================= + Stream-related callbacks; for these functions, the `closure' argument + is what is found in `options->stream_closure'. (One possible exception + is for output_fn; see "output_closure" below.) + */ + void *stream_closure; + + /* 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. */ + int (*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); + + /* How the MIME parser feeds its output (HTML or raw) back to the caller. */ + MimeConverterOutputCallback output_fn; + + /* Closure to pass to the above output_fn. If NULL, then the + stream_closure is used. */ + void *output_closure; + + /* A hook for the caller to perform charset-conversion before HTML is + returned. Each set of characters which originated in a mail message + (body or headers) will be run through this filter before being converted + into HTML. (This should return bytes which may appear in an HTML file, + ie, we must be able to scan through the string to search for "<" and + turn it in to "<", and so on.) + + `input' is a non-NULL-terminated string of a single line from the message. + `input_length' is how long it is. + `input_charset' is a string representing the charset of this string (as + specified by MIME headers.) + `output_charset' is the charset to which conversion is desired. + `output_ret' is where a newly-malloced string is returned. It may be + NULL if no translation is needed. + `output_size_ret' is how long the returned string is (it need not be + NULL-terminated.). + */ + int (*charset_conversion_fn) (const char *input_line, + int32_t input_length, const char *input_charset, + const char *output_charset, + char **output_ret, int32_t *output_size_ret, + void *stream_closure, nsIUnicodeDecoder *decoder, nsIUnicodeEncoder *encoder); + + /* If true, perform both charset-conversion and decoding of + MIME-2 header fields (using RFC-1522 encoding.) + */ + bool rfc1522_conversion_p; + + /* A hook for the caller to turn a file name into a content-type. */ + char *(*file_type_fn) (const char *filename, void *stream_closure); + + /* A hook by which the user may be prompted for a password by the security + library. (This is really of type `SECKEYGetPasswordKey'; see sec.h.) */ + void *(*passwd_prompt_fn)(void *arg1, void *arg2); + + /* ======================================================================= + Various callbacks; for all of these functions, the `closure' argument + is what is found in `html_closure'. + */ + void *html_closure; + + /* For emitting some HTML before the start of the outermost message + (this is called before any HTML is written to layout.) */ + MimeHTMLGeneratorFunction generate_header_html_fn; + + /* For emitting some HTML after the outermost header block, but before + the body of the first message. */ + MimeHTMLGeneratorFunction generate_post_header_html_fn; + + /* For emitting some HTML at the very end (this is called after libmime + has written everything it's going to write.) */ + MimeHTMLGeneratorFunction generate_footer_html_fn; + + /* For turning a message ID into a loadable URL. */ + MimeHTMLGeneratorFunction generate_reference_url_fn; + + /* For turning a mail address into a mailto URL. */ + MimeHTMLGeneratorFunction generate_mailto_url_fn; + + /* For turning a newsgroup name into a news URL. */ + MimeHTMLGeneratorFunction generate_news_url_fn; + + /* ======================================================================= + Callbacks to handle the backend-specific inlined image display + (internal-external-reconnect junk.) For `image_begin', the `closure' + argument is what is found in `stream_closure'; but for all of the + others, the `closure' argument is the data that `image_begin' returned. + */ + + /* Begins processing an embedded image; the URL and content_type are of the + image itself. */ + void *(*image_begin) (const char *image_url, const char *content_type, + void *stream_closure); + + /* Stop processing an image. */ + void (*image_end) (void *image_closure, int status); + + /* Dump some raw image data down the stream. */ + int (*image_write_buffer) (const char *buf, int32_t size, void *image_closure); + + /* What HTML should be dumped out for this image. */ + char *(*make_image_html) (void *image_closure); + + + /* ======================================================================= + Other random opaque state. + */ + MimeParseStateObject *state; /* Some state used by libmime internals; + initialize this to 0 and leave it alone. + */ + + +#ifdef MIME_DRAFTS + /* ======================================================================= + Mail Draft hooks -- 09-19-1996 + */ + bool decompose_file_p; /* are we decomposing a mime msg + into separate files */ + bool done_parsing_outer_headers; /* are we done parsing the outer message + headers; this is really useful when + we have multiple Message/RFC822 + headers */ + bool is_multipart_msg; /* are we decomposing a multipart + message */ + + int decompose_init_count; /* used for non multipart message only + */ + + bool signed_p; /* to tell draft this is a signed + message */ + + bool caller_need_root_headers; /* set it to true to receive the message main + headers through the callback + decompose_headers_info_fn */ + + /* Callback to gather the outer most headers so we could use the + information to initialize the addressing/subject/newsgroups fields + for the composition window. */ + int (*decompose_headers_info_fn) (void *closure, + MimeHeaders *headers); + + /* Callbacks to create temporary files for drafts attachments. */ + int (*decompose_file_init_fn) (void *stream_closure, + MimeHeaders *headers ); + + MimeConverterOutputCallback decompose_file_output_fn; + + int (*decompose_file_close_fn) (void *stream_closure); +#endif /* MIME_DRAFTS */ + + int32_t attachment_icon_layer_id; /* Hackhackhack. This is zero if we have + not yet emitted the attachment layer + stuff. If we have, then this is the + id number for that layer, which is a + unique random number every time, to keep + evil people from writing javascript code + to hack it. */ + + bool missing_parts; /* Whether or not this message is going to contain + missing parts (from IMAP Mime Parts On Demand) */ + + bool show_attachment_inline_p; /* Whether or not we should display attachment inline (whatever say + the content-disposition) */ + + bool quote_attachment_inline_p; /* Whether or not we should include inlined attachments in + quotes of replies) */ + + int32_t html_as_p; /* How we should display HTML, which allows us to know if we should display all body parts */ + + /** + * Should StartBody/EndBody events be generated for nested MimeMessages. If + * false (the default value), the events are only generated for the outermost + * MimeMessage. + */ + bool notify_nested_bodies; + + /** + * When true, compels mime parts to only write the actual body + * payload and not display-gunk like links to attachments. This was + * primarily introduced for the benefit of the javascript emitter. + */ + bool write_pure_bodies; + + /** + * When true, only processes metadata (i.e. size) for streamed attachments. + * Mime emitters that expect any attachment data (including inline text and + * image attachments) should leave this as false (the default value). At + * the moment, only the JS mime emitter uses this. + */ + bool metadata_only; +}; + +#endif /* _MODLMIME_H_ */ diff --git a/mailnews/mime/src/modmimee.h b/mailnews/mime/src/modmimee.h new file mode 100644 index 000000000..123fbcdb1 --- /dev/null +++ b/mailnews/mime/src/modmimee.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + /* -*- Mode: C; tab-width: 4 -*- + mimeenc.c --- MIME encoders and decoders, version 2 (see mimei.h) + Copyright (c) 1996 Netscape Communications Corporation, all rights reserved. + Created: Jamie Zawinski <jwz@netscape.com>, 15-May-96. + */ + +#ifndef _MIMEENC_H_ +#define _MIMEENC_H_ + +#include "nsError.h" +#include "nscore.h" // for nullptr + +typedef int (*MimeConverterOutputCallback) + (const char *buf, int32_t size, void *closure); + +/* This file defines interfaces to generic implementations of Base64, + Quoted-Printable, and UU decoders; and of Base64 and Quoted-Printable + encoders. + */ + + +/* Opaque objects used by the encoder/decoder to store state. */ +typedef struct MimeDecoderData MimeDecoderData; + +struct MimeObject; + + +/* functions for creating that opaque data. + */ +MimeDecoderData *MimeB64DecoderInit(MimeConverterOutputCallback output_fn, + void *closure); + +MimeDecoderData *MimeQPDecoderInit (MimeConverterOutputCallback output_fn, + void *closure, MimeObject *object = nullptr); + +MimeDecoderData *MimeUUDecoderInit (MimeConverterOutputCallback output_fn, + void *closure); +MimeDecoderData *MimeYDecoderInit (MimeConverterOutputCallback output_fn, + void *closure); + +/* Push data through the encoder/decoder, causing the above-provided write_fn + to be called with encoded/decoded data. */ +int MimeDecoderWrite (MimeDecoderData *data, const char *buffer, int32_t size, + int32_t *outSize); + +/* When you're done encoding/decoding, call this to free the data. If + abort_p is false, then calling this may cause the write_fn to be called + one last time (as the last buffered data is flushed out.) + */ +int MimeDecoderDestroy(MimeDecoderData *data, bool abort_p); + +#endif /* _MODMIMEE_H_ */ diff --git a/mailnews/mime/src/moz.build b/mailnews/mime/src/moz.build new file mode 100644 index 000000000..1d44db88a --- /dev/null +++ b/mailnews/mime/src/moz.build @@ -0,0 +1,92 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'mimecont.h', + 'mimecryp.h', + 'mimecth.h', + 'mimehdrs.h', + 'mimei.h', + 'mimeleaf.h', + 'mimemoz2.h', + 'mimemsig.h', + 'mimemult.h', + 'mimeobj.h', + 'mimepbuf.h', + 'mimetext.h', + 'modlmime.h', + 'modmimee.h', + 'nsMimeStringResources.h', + 'nsStreamConverter.h', +] + +SOURCES += [ + 'comi18n.cpp', + 'mimebuf.cpp', + 'mimecms.cpp', + 'mimecom.cpp', + 'mimecont.cpp', + 'mimecryp.cpp', + 'mimecth.cpp', + 'mimedrft.cpp', + 'mimeebod.cpp', + 'mimeenc.cpp', + 'mimeeobj.cpp', + 'mimehdrs.cpp', + 'MimeHeaderParser.cpp', + 'mimei.cpp', + 'mimeiimg.cpp', + 'mimeleaf.cpp', + 'mimemalt.cpp', + 'mimemapl.cpp', + 'mimemcms.cpp', + 'mimemdig.cpp', + 'mimemmix.cpp', + 'mimemoz2.cpp', + 'mimempar.cpp', + 'mimemrel.cpp', + 'mimemsg.cpp', + 'mimemsig.cpp', + 'mimemult.cpp', + 'mimeobj.cpp', + 'mimepbuf.cpp', + 'mimesun.cpp', + 'mimetenr.cpp', + 'mimetext.cpp', + 'mimeTextHTMLParsed.cpp', + 'mimethpl.cpp', + 'mimethsa.cpp', + 'mimethtm.cpp', + 'mimetpfl.cpp', + 'mimetpla.cpp', + 'mimetric.cpp', + 'mimeunty.cpp', + 'nsCMS.cpp', + 'nsCMSSecureMessage.cpp', + 'nsMimeObjectClassAccess.cpp', + 'nsSimpleMimeConverterStub.cpp', + 'nsStreamConverter.cpp', +] + +LOCAL_INCLUDES += [ + '/mozilla/security/certverifier', + '/mozilla/security/manager/ssl', + '/mozilla/security/pkix/include', +] + +EXTRA_COMPONENTS += [ + 'mimeJSComponents.js', + 'msgMime.manifest', +] + +EXTRA_JS_MODULES += [ + 'extraMimeParsers.jsm', + 'jsmime.jsm', + 'mimeParser.jsm' +] + +FINAL_LIBRARY = 'mail' + +DEFINES['ENABLE_SMIME'] = True diff --git a/mailnews/mime/src/msgMime.manifest b/mailnews/mime/src/msgMime.manifest new file mode 100644 index 000000000..9ce79bf74 --- /dev/null +++ b/mailnews/mime/src/msgMime.manifest @@ -0,0 +1,9 @@ +component {d1258011-f391-44fd-992e-c6f4b461a42f} mimeJSComponents.js +component {96bd8769-2d0e-4440-963d-22b97fb3ba77} mimeJSComponents.js +component {93f8c049-80ed-4dda-9000-94ad8daba44c} mimeJSComponents.js +component {c560806a-425f-4f0f-bf69-397c58c599a7} mimeJSComponents.js +contract @mozilla.org/messenger/mimeheaders;1 {d1258011-f391-44fd-992e-c6f4b461a42f} +contract @mozilla.org/messenger/mimeconverter;1 {93f8c049-80ed-4dda-9000-94ad8daba44c} +contract @mozilla.org/messenger/headerparser;1 {96bd8769-2d0e-4440-963d-22b97fb3ba77} +contract @mozilla.org/messenger/structuredheaders;1 {c560806a-425f-4f0f-bf69-397c58c599a7} +category custom-mime-encoder A-extra resource:///modules/extraMimeParsers.jsm diff --git a/mailnews/mime/src/nsCMS.cpp b/mailnews/mime/src/nsCMS.cpp new file mode 100644 index 000000000..c7cfb31ed --- /dev/null +++ b/mailnews/mime/src/nsCMS.cpp @@ -0,0 +1,966 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCMS.h" + +#include "CertVerifier.h" +#include "CryptoTask.h" +#include "ScopedNSSTypes.h" +#include "cms.h" +#include "mozilla/Logging.h" +#include "mozilla/RefPtr.h" +#include "nsArrayUtils.h" +#include "nsIArray.h" +#include "nsICMSMessageErrors.h" +#include "nsICryptoHash.h" +#include "nsISupports.h" +#include "nsIX509CertDB.h" +#include "nsNSSCertificate.h" +#include "nsNSSComponent.h" +#include "nsNSSHelper.h" +#include "nsServiceManagerUtils.h" +#include "pkix/Result.h" +#include "pkix/pkixtypes.h" +#include "smime.h" + +using namespace mozilla; +using namespace mozilla::psm; +using namespace mozilla::pkix; + +#ifdef PR_LOGGING +extern mozilla::LazyLogModule gPIPNSSLog; +#endif + +NS_IMPL_ISUPPORTS(nsCMSMessage, nsICMSMessage, nsICMSMessage2) + +nsCMSMessage::nsCMSMessage() +{ + m_cmsMsg = nullptr; +} +nsCMSMessage::nsCMSMessage(NSSCMSMessage *aCMSMsg) +{ + m_cmsMsg = aCMSMsg; +} + +nsCMSMessage::~nsCMSMessage() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + destructorSafeDestroyNSSReference(); + shutdown(ShutdownCalledFrom::Object); +} + +nsresult nsCMSMessage::Init() +{ + nsresult rv; + nsCOMPtr<nsISupports> nssInitialized = do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +void nsCMSMessage::virtualDestroyNSSReference() +{ + destructorSafeDestroyNSSReference(); +} + +void nsCMSMessage::destructorSafeDestroyNSSReference() +{ + if (m_cmsMsg) { + NSS_CMSMessage_Destroy(m_cmsMsg); + } +} + +NS_IMETHODIMP nsCMSMessage::VerifySignature() +{ + return CommonVerifySignature(nullptr, 0); +} + +NSSCMSSignerInfo* nsCMSMessage::GetTopLevelSignerInfo() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return nullptr; + + if (!m_cmsMsg) + return nullptr; + + if (!NSS_CMSMessage_IsSigned(m_cmsMsg)) + return nullptr; + + NSSCMSContentInfo *cinfo = NSS_CMSMessage_ContentLevel(m_cmsMsg, 0); + if (!cinfo) + return nullptr; + + NSSCMSSignedData *sigd = (NSSCMSSignedData*)NSS_CMSContentInfo_GetContent(cinfo); + if (!sigd) + return nullptr; + + PR_ASSERT(NSS_CMSSignedData_SignerInfoCount(sigd) > 0); + return NSS_CMSSignedData_GetSignerInfo(sigd, 0); +} + +NS_IMETHODIMP nsCMSMessage::GetSignerEmailAddress(char * * aEmail) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerEmailAddress\n")); + NS_ENSURE_ARG(aEmail); + + NSSCMSSignerInfo *si = GetTopLevelSignerInfo(); + if (!si) + return NS_ERROR_FAILURE; + + *aEmail = NSS_CMSSignerInfo_GetSignerEmailAddress(si); + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::GetSignerCommonName(char ** aName) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerCommonName\n")); + NS_ENSURE_ARG(aName); + + NSSCMSSignerInfo *si = GetTopLevelSignerInfo(); + if (!si) + return NS_ERROR_FAILURE; + + *aName = NSS_CMSSignerInfo_GetSignerCommonName(si); + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::ContentIsEncrypted(bool *isEncrypted) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::ContentIsEncrypted\n")); + NS_ENSURE_ARG(isEncrypted); + + if (!m_cmsMsg) + return NS_ERROR_FAILURE; + + *isEncrypted = NSS_CMSMessage_IsEncrypted(m_cmsMsg); + + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::ContentIsSigned(bool *isSigned) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::ContentIsSigned\n")); + NS_ENSURE_ARG(isSigned); + + if (!m_cmsMsg) + return NS_ERROR_FAILURE; + + *isSigned = NSS_CMSMessage_IsSigned(m_cmsMsg); + + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::GetSignerCert(nsIX509Cert **scert) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + NSSCMSSignerInfo *si = GetTopLevelSignerInfo(); + if (!si) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIX509Cert> cert; + if (si->cert) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerCert got signer cert\n")); + + nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID); + certdb->ConstructX509(reinterpret_cast<const char *>(si->cert->derCert.data), + si->cert->derCert.len, + getter_AddRefs(cert)); + } + else { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerCert no signer cert, do we have a cert list? %s\n", + (si->certList ? "yes" : "no") )); + + *scert = nullptr; + } + + cert.forget(scert); + + return NS_OK; +} + +NS_IMETHODIMP nsCMSMessage::GetEncryptionCert(nsIX509Cert **ecert) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsCMSMessage::VerifyDetachedSignature(unsigned char* aDigestData, uint32_t aDigestDataLen) +{ + if (!aDigestData || !aDigestDataLen) + return NS_ERROR_FAILURE; + + return CommonVerifySignature(aDigestData, aDigestDataLen); +} + +nsresult nsCMSMessage::CommonVerifySignature(unsigned char* aDigestData, uint32_t aDigestDataLen) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature, content level count %d\n", NSS_CMSMessage_ContentLevelCount(m_cmsMsg))); + NSSCMSContentInfo *cinfo = nullptr; + NSSCMSSignedData *sigd = nullptr; + NSSCMSSignerInfo *si; + int32_t nsigners; + RefPtr<SharedCertVerifier> certVerifier; + nsresult rv = NS_ERROR_FAILURE; + + if (!NSS_CMSMessage_IsSigned(m_cmsMsg)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - not signed\n")); + return NS_ERROR_CMS_VERIFY_NOT_SIGNED; + } + + cinfo = NSS_CMSMessage_ContentLevel(m_cmsMsg, 0); + if (cinfo) { + // I don't like this hard cast. We should check in some way, that we really have this type. + sigd = (NSSCMSSignedData*)NSS_CMSContentInfo_GetContent(cinfo); + } + + if (!sigd) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - no content info\n")); + rv = NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; + goto loser; + } + + if (aDigestData && aDigestDataLen) + { + SECItem digest; + digest.data = aDigestData; + digest.len = aDigestDataLen; + + if (NSS_CMSSignedData_SetDigestValue(sigd, SEC_OID_SHA1, &digest)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - bad digest\n")); + rv = NS_ERROR_CMS_VERIFY_BAD_DIGEST; + goto loser; + } + } + + // Import certs. Note that import failure is not a signature verification failure. // + if (NSS_CMSSignedData_ImportCerts(sigd, CERT_GetDefaultCertDB(), certUsageEmailRecipient, true) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - can not import certs\n")); + } + + nsigners = NSS_CMSSignedData_SignerInfoCount(sigd); + PR_ASSERT(nsigners > 0); + NS_ENSURE_TRUE(nsigners > 0, NS_ERROR_UNEXPECTED); + si = NSS_CMSSignedData_GetSignerInfo(sigd, 0); + + // See bug 324474. We want to make sure the signing cert is + // still valid at the current time. + + certVerifier = GetDefaultCertVerifier(); + NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED); + + { + UniqueCERTCertList builtChain; + mozilla::pkix::Result result = + certVerifier->VerifyCert(si->cert, + certificateUsageEmailSigner, + Now(), + nullptr /*XXX pinarg*/, + nullptr /*hostname*/, + builtChain); + if (result != mozilla::pkix::Success) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, + ("nsCMSMessage::CommonVerifySignature - signing cert not trusted now\n")); + rv = NS_ERROR_CMS_VERIFY_UNTRUSTED; + goto loser; + } + } + + // We verify the first signer info, only // + // XXX: NSS_CMSSignedData_VerifySignerInfo calls CERT_VerifyCert, which + // requires NSS's certificate verification configuration to be done in + // order to work well (e.g. honoring OCSP preferences and proxy settings + // for OCSP requests), but Gecko stopped doing that configuration. Something + // similar to what was done for Gecko bug 1028643 needs to be done here too. + if (NSS_CMSSignedData_VerifySignerInfo(sigd, 0, CERT_GetDefaultCertDB(), certUsageEmailSigner) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - unable to verify signature\n")); + + if (NSSCMSVS_SigningCertNotFound == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - signing cert not found\n")); + rv = NS_ERROR_CMS_VERIFY_NOCERT; + } + else if(NSSCMSVS_SigningCertNotTrusted == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - signing cert not trusted at signing time\n")); + rv = NS_ERROR_CMS_VERIFY_UNTRUSTED; + } + else if(NSSCMSVS_Unverified == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - can not verify\n")); + rv = NS_ERROR_CMS_VERIFY_ERROR_UNVERIFIED; + } + else if(NSSCMSVS_ProcessingError == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - processing error\n")); + rv = NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; + } + else if(NSSCMSVS_BadSignature == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - bad signature\n")); + rv = NS_ERROR_CMS_VERIFY_BAD_SIGNATURE; + } + else if(NSSCMSVS_DigestMismatch == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - digest mismatch\n")); + rv = NS_ERROR_CMS_VERIFY_DIGEST_MISMATCH; + } + else if(NSSCMSVS_SignatureAlgorithmUnknown == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - algo unknown\n")); + rv = NS_ERROR_CMS_VERIFY_UNKNOWN_ALGO; + } + else if(NSSCMSVS_SignatureAlgorithmUnsupported == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - algo not supported\n")); + rv = NS_ERROR_CMS_VERIFY_UNSUPPORTED_ALGO; + } + else if(NSSCMSVS_MalformedSignature == si->verificationStatus) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - malformed signature\n")); + rv = NS_ERROR_CMS_VERIFY_MALFORMED_SIGNATURE; + } + + goto loser; + } + + // Save the profile. Note that save import failure is not a signature verification failure. // + if (NSS_SMIMESignerInfo_SaveSMIMEProfile(si) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - unable to save smime profile\n")); + } + + rv = NS_OK; +loser: + return rv; +} + +NS_IMETHODIMP nsCMSMessage::AsyncVerifySignature( + nsISMimeVerificationListener *aListener) +{ + return CommonAsyncVerifySignature(aListener, nullptr, 0); +} + +NS_IMETHODIMP nsCMSMessage::AsyncVerifyDetachedSignature( + nsISMimeVerificationListener *aListener, + unsigned char* aDigestData, uint32_t aDigestDataLen) +{ + if (!aDigestData || !aDigestDataLen) + return NS_ERROR_FAILURE; + + return CommonAsyncVerifySignature(aListener, aDigestData, aDigestDataLen); +} + +class SMimeVerificationTask final : public CryptoTask +{ +public: + SMimeVerificationTask(nsICMSMessage *aMessage, + nsISMimeVerificationListener *aListener, + unsigned char *aDigestData, uint32_t aDigestDataLen) + { + MOZ_ASSERT(NS_IsMainThread()); + mMessage = aMessage; + mListener = aListener; + mDigestData.Assign(reinterpret_cast<char *>(aDigestData), aDigestDataLen); + } + +private: + virtual void ReleaseNSSResources() override {} + virtual nsresult CalculateResult() override + { + MOZ_ASSERT(!NS_IsMainThread()); + + nsresult rv; + if (!mDigestData.IsEmpty()) { + rv = mMessage->VerifyDetachedSignature( + reinterpret_cast<uint8_t*>(const_cast<char *>(mDigestData.get())), + mDigestData.Length()); + } else { + rv = mMessage->VerifySignature(); + } + + return rv; + } + virtual void CallCallback(nsresult rv) override + { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsICMSMessage2> m2 = do_QueryInterface(mMessage); + mListener->Notify(m2, rv); + } + + nsCOMPtr<nsICMSMessage> mMessage; + nsCOMPtr<nsISMimeVerificationListener> mListener; + nsCString mDigestData; +}; + +nsresult nsCMSMessage::CommonAsyncVerifySignature(nsISMimeVerificationListener *aListener, + unsigned char* aDigestData, uint32_t aDigestDataLen) +{ + RefPtr<CryptoTask> task = new SMimeVerificationTask(this, aListener, aDigestData, aDigestDataLen); + return task->Dispatch("SMimeVerify"); +} + +class nsZeroTerminatedCertArray : public nsNSSShutDownObject +{ +public: + nsZeroTerminatedCertArray() + :mCerts(nullptr), mPoolp(nullptr), mSize(0) + { + } + + ~nsZeroTerminatedCertArray() + { + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + destructorSafeDestroyNSSReference(); + shutdown(ShutdownCalledFrom::Object); + } + + void virtualDestroyNSSReference() + { + destructorSafeDestroyNSSReference(); + } + + void destructorSafeDestroyNSSReference() + { + if (mCerts) + { + for (uint32_t i=0; i < mSize; i++) { + if (mCerts[i]) { + CERT_DestroyCertificate(mCerts[i]); + } + } + } + + if (mPoolp) + PORT_FreeArena(mPoolp, false); + } + + bool allocate(uint32_t count) + { + // only allow allocation once + if (mPoolp) + return false; + + mSize = count; + + if (!mSize) + return false; + + mPoolp = PORT_NewArena(1024); + if (!mPoolp) + return false; + + mCerts = (CERTCertificate**)PORT_ArenaZAlloc( + mPoolp, (count+1)*sizeof(CERTCertificate*)); + + if (!mCerts) + return false; + + // null array, including zero termination + for (uint32_t i = 0; i < count+1; i++) { + mCerts[i] = nullptr; + } + + return true; + } + + void set(uint32_t i, CERTCertificate *c) + { + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return; + + if (i >= mSize) + return; + + if (mCerts[i]) { + CERT_DestroyCertificate(mCerts[i]); + } + + mCerts[i] = CERT_DupCertificate(c); + } + + CERTCertificate *get(uint32_t i) + { + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return nullptr; + + if (i >= mSize) + return nullptr; + + return CERT_DupCertificate(mCerts[i]); + } + + CERTCertificate **getRawArray() + { + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return nullptr; + + return mCerts; + } + +private: + CERTCertificate **mCerts; + PLArenaPool *mPoolp; + uint32_t mSize; +}; + +NS_IMETHODIMP nsCMSMessage::CreateEncrypted(nsIArray * aRecipientCerts) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted\n")); + NSSCMSContentInfo *cinfo; + NSSCMSEnvelopedData *envd; + NSSCMSRecipientInfo *recipientInfo; + nsZeroTerminatedCertArray recipientCerts; + SECOidTag bulkAlgTag; + int keySize; + uint32_t i; + nsresult rv = NS_ERROR_FAILURE; + + // Check the recipient certificates // + uint32_t recipientCertCount; + aRecipientCerts->GetLength(&recipientCertCount); + PR_ASSERT(recipientCertCount > 0); + + if (!recipientCerts.allocate(recipientCertCount)) { + goto loser; + } + + for (i=0; i<recipientCertCount; i++) { + nsCOMPtr<nsIX509Cert> x509cert = do_QueryElementAt(aRecipientCerts, i); + + if (!x509cert) + return NS_ERROR_FAILURE; + + UniqueCERTCertificate c(x509cert->GetCert()); + recipientCerts.set(i, c.get()); + } + + // Find a bulk key algorithm // + if (NSS_SMIMEUtil_FindBulkAlgForRecipients(recipientCerts.getRawArray(), &bulkAlgTag, + &keySize) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't find bulk alg for recipients\n")); + rv = NS_ERROR_CMS_ENCRYPT_NO_BULK_ALG; + goto loser; + } + + m_cmsMsg = NSS_CMSMessage_Create(nullptr); + if (!m_cmsMsg) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't create new cms message\n")); + rv = NS_ERROR_OUT_OF_MEMORY; + goto loser; + } + + if ((envd = NSS_CMSEnvelopedData_Create(m_cmsMsg, bulkAlgTag, keySize)) == nullptr) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't create enveloped data\n")); + goto loser; + } + + cinfo = NSS_CMSMessage_GetContentInfo(m_cmsMsg); + if (NSS_CMSContentInfo_SetContent_EnvelopedData(m_cmsMsg, cinfo, envd) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't create content enveloped data\n")); + goto loser; + } + + cinfo = NSS_CMSEnvelopedData_GetContentInfo(envd); + if (NSS_CMSContentInfo_SetContent_Data(m_cmsMsg, cinfo, nullptr, false) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't set content data\n")); + goto loser; + } + + // Create and attach recipient information // + for (i=0; i < recipientCertCount; i++) { + UniqueCERTCertificate rc(recipientCerts.get(i)); + if ((recipientInfo = NSS_CMSRecipientInfo_Create(m_cmsMsg, rc.get())) == nullptr) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't create recipient info\n")); + goto loser; + } + if (NSS_CMSEnvelopedData_AddRecipient(envd, recipientInfo) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted - can't add recipient info\n")); + goto loser; + } + } + + return NS_OK; +loser: + if (m_cmsMsg) { + NSS_CMSMessage_Destroy(m_cmsMsg); + m_cmsMsg = nullptr; + } + + return rv; +} + +NS_IMETHODIMP +nsCMSMessage::CreateSigned(nsIX509Cert* aSigningCert, nsIX509Cert* aEncryptCert, + unsigned char* aDigestData, uint32_t aDigestDataLen, + int16_t aDigestType) +{ + NS_ENSURE_ARG(aSigningCert); + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned\n")); + NSSCMSContentInfo *cinfo; + NSSCMSSignedData *sigd; + NSSCMSSignerInfo *signerinfo; + UniqueCERTCertificate scert(aSigningCert->GetCert()); + UniqueCERTCertificate ecert; + nsresult rv = NS_ERROR_FAILURE; + + if (!scert) { + return NS_ERROR_FAILURE; + } + + if (aEncryptCert) { + ecert = UniqueCERTCertificate(aEncryptCert->GetCert()); + } + + SECOidTag digestType; + switch (aDigestType) { + case nsICryptoHash::SHA1: + digestType = SEC_OID_SHA1; + break; + case nsICryptoHash::SHA256: + digestType = SEC_OID_SHA256; + break; + case nsICryptoHash::SHA384: + digestType = SEC_OID_SHA384; + break; + case nsICryptoHash::SHA512: + digestType = SEC_OID_SHA512; + break; + default: + return NS_ERROR_INVALID_ARG; + } + + /* + * create the message object + */ + m_cmsMsg = NSS_CMSMessage_Create(nullptr); /* create a message on its own pool */ + if (!m_cmsMsg) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't create new message\n")); + rv = NS_ERROR_OUT_OF_MEMORY; + goto loser; + } + + /* + * build chain of objects: message->signedData->data + */ + if ((sigd = NSS_CMSSignedData_Create(m_cmsMsg)) == nullptr) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't create signed data\n")); + goto loser; + } + cinfo = NSS_CMSMessage_GetContentInfo(m_cmsMsg); + if (NSS_CMSContentInfo_SetContent_SignedData(m_cmsMsg, cinfo, sigd) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't set content signed data\n")); + goto loser; + } + + cinfo = NSS_CMSSignedData_GetContentInfo(sigd); + + /* we're always passing data in and detaching optionally */ + if (NSS_CMSContentInfo_SetContent_Data(m_cmsMsg, cinfo, nullptr, true) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't set content data\n")); + goto loser; + } + + /* + * create & attach signer information + */ + signerinfo = NSS_CMSSignerInfo_Create(m_cmsMsg, scert.get(), digestType); + if (!signerinfo) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't create signer info\n")); + goto loser; + } + + /* we want the cert chain included for this one */ + if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain, + certUsageEmailSigner) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't include signer cert chain\n")); + goto loser; + } + + if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now()) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add signing time\n")); + goto loser; + } + + if (NSS_CMSSignerInfo_AddSMIMECaps(signerinfo) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add smime caps\n")); + goto loser; + } + + if (ecert) { + if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, ecert.get(), + CERT_GetDefaultCertDB()) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add smime enc key prefs\n")); + goto loser; + } + + if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, ecert.get(), + CERT_GetDefaultCertDB()) + != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add MS smime enc key prefs\n")); + goto loser; + } + + // If signing and encryption cert are identical, don't add it twice. + bool addEncryptionCert = + (ecert && (!scert || !CERT_CompareCerts(ecert.get(), scert.get()))); + + if (addEncryptionCert && + NSS_CMSSignedData_AddCertificate(sigd, ecert.get()) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add own encryption certificate\n")); + goto loser; + } + } + + if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't add signer info\n")); + goto loser; + } + + // Finally, add the pre-computed digest if passed in + if (aDigestData) { + SECItem digest; + + digest.data = aDigestData; + digest.len = aDigestDataLen; + + if (NSS_CMSSignedData_SetDigestValue(sigd, digestType, &digest) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned - can't set digest value\n")); + goto loser; + } + } + + return NS_OK; +loser: + if (m_cmsMsg) { + NSS_CMSMessage_Destroy(m_cmsMsg); + m_cmsMsg = nullptr; + } + return rv; +} + +NS_IMPL_ISUPPORTS(nsCMSDecoder, nsICMSDecoder) + +nsCMSDecoder::nsCMSDecoder() +: m_dcx(nullptr) +{ +} + +nsCMSDecoder::~nsCMSDecoder() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + destructorSafeDestroyNSSReference(); + shutdown(ShutdownCalledFrom::Object); +} + +nsresult nsCMSDecoder::Init() +{ + nsresult rv; + nsCOMPtr<nsISupports> nssInitialized = do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +void nsCMSDecoder::virtualDestroyNSSReference() +{ + destructorSafeDestroyNSSReference(); +} + +void nsCMSDecoder::destructorSafeDestroyNSSReference() +{ + if (m_dcx) { + NSS_CMSDecoder_Cancel(m_dcx); + m_dcx = nullptr; + } +} + +/* void start (in NSSCMSContentCallback cb, in voidPtr arg); */ +NS_IMETHODIMP nsCMSDecoder::Start(NSSCMSContentCallback cb, void * arg) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSDecoder::Start\n")); + m_ctx = new PipUIContext(); + + m_dcx = NSS_CMSDecoder_Start(0, cb, arg, 0, m_ctx, 0, 0); + if (!m_dcx) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSDecoder::Start - can't start decoder\n")); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +/* void update (in string bug, in long len); */ +NS_IMETHODIMP nsCMSDecoder::Update(const char *buf, int32_t len) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSDecoder::Update\n")); + NSS_CMSDecoder_Update(m_dcx, (char *)buf, len); + return NS_OK; +} + +/* void finish (); */ +NS_IMETHODIMP nsCMSDecoder::Finish(nsICMSMessage ** aCMSMsg) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSDecoder::Finish\n")); + NSSCMSMessage *cmsMsg; + cmsMsg = NSS_CMSDecoder_Finish(m_dcx); + m_dcx = nullptr; + if (cmsMsg) { + nsCMSMessage *obj = new nsCMSMessage(cmsMsg); + // The NSS object cmsMsg still carries a reference to the context + // we gave it on construction. + // Make sure the context will live long enough. + obj->referenceContext(m_ctx); + *aCMSMsg = obj; + NS_ADDREF(*aCMSMsg); + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsCMSEncoder, nsICMSEncoder) + +nsCMSEncoder::nsCMSEncoder() +: m_ecx(nullptr) +{ +} + +nsCMSEncoder::~nsCMSEncoder() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + destructorSafeDestroyNSSReference(); + shutdown(ShutdownCalledFrom::Object); +} + +nsresult nsCMSEncoder::Init() +{ + nsresult rv; + nsCOMPtr<nsISupports> nssInitialized = do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +void nsCMSEncoder::virtualDestroyNSSReference() +{ + destructorSafeDestroyNSSReference(); +} + +void nsCMSEncoder::destructorSafeDestroyNSSReference() +{ + if (m_ecx) + NSS_CMSEncoder_Cancel(m_ecx); +} + +/* void start (); */ +NS_IMETHODIMP nsCMSEncoder::Start(nsICMSMessage *aMsg, NSSCMSContentCallback cb, void * arg) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Start\n")); + nsCMSMessage *cmsMsg = static_cast<nsCMSMessage*>(aMsg); + m_ctx = new PipUIContext(); + + m_ecx = NSS_CMSEncoder_Start(cmsMsg->getCMS(), cb, arg, 0, 0, 0, m_ctx, 0, 0, 0, 0); + if (!m_ecx) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Start - can't start encoder\n")); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +/* void update (in string aBuf, in long aLen); */ +NS_IMETHODIMP nsCMSEncoder::Update(const char *aBuf, int32_t aLen) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Update\n")); + if (!m_ecx || NSS_CMSEncoder_Update(m_ecx, aBuf, aLen) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Update - can't update encoder\n")); + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +/* void finish (); */ +NS_IMETHODIMP nsCMSEncoder::Finish() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + nsresult rv = NS_OK; + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Finish\n")); + if (!m_ecx || NSS_CMSEncoder_Finish(m_ecx) != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Finish - can't finish encoder\n")); + rv = NS_ERROR_FAILURE; + } + m_ecx = nullptr; + return rv; +} + +/* void encode (in nsICMSMessage aMsg); */ +NS_IMETHODIMP nsCMSEncoder::Encode(nsICMSMessage *aMsg) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) + return NS_ERROR_NOT_AVAILABLE; + + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSEncoder::Encode\n")); + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/mailnews/mime/src/nsCMS.h b/mailnews/mime/src/nsCMS.h new file mode 100644 index 000000000..e8f2fdd4b --- /dev/null +++ b/mailnews/mime/src/nsCMS.h @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __NS_CMS_H__ +#define __NS_CMS_H__ + +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include "nsXPIDLString.h" +#include "nsIInterfaceRequestor.h" +#include "nsICMSMessage.h" +#include "nsICMSMessage2.h" +#include "nsIX509Cert.h" +#include "nsICMSEncoder.h" +#include "nsICMSDecoder.h" +#include "sechash.h" +#include "cms.h" +#include "nsNSSShutDown.h" + +#define NS_CMSMESSAGE_CID \ + { 0xa4557478, 0xae16, 0x11d5, { 0xba,0x4b,0x00,0x10,0x83,0x03,0xb1,0x17 } } + +class nsCMSMessage : public nsICMSMessage, + public nsICMSMessage2, + public nsNSSShutDownObject +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICMSMESSAGE + NS_DECL_NSICMSMESSAGE2 + + nsCMSMessage(); + nsCMSMessage(NSSCMSMessage* aCMSMsg); + nsresult Init(); + + void referenceContext(nsIInterfaceRequestor* aContext) {m_ctx = aContext;} + NSSCMSMessage* getCMS() {return m_cmsMsg;} +private: + virtual ~nsCMSMessage(); + nsCOMPtr<nsIInterfaceRequestor> m_ctx; + NSSCMSMessage * m_cmsMsg; + NSSCMSSignerInfo* GetTopLevelSignerInfo(); + nsresult CommonVerifySignature(unsigned char* aDigestData, uint32_t aDigestDataLen); + + nsresult CommonAsyncVerifySignature(nsISMimeVerificationListener *aListener, + unsigned char* aDigestData, uint32_t aDigestDataLen); + + virtual void virtualDestroyNSSReference() override; + void destructorSafeDestroyNSSReference(); + +}; + +// =============================================== +// nsCMSDecoder - implementation of nsICMSDecoder +// =============================================== + +#define NS_CMSDECODER_CID \ + { 0x9dcef3a4, 0xa3bc, 0x11d5, { 0xba, 0x47, 0x00, 0x10, 0x83, 0x03, 0xb1, 0x17 } } + +class nsCMSDecoder : public nsICMSDecoder, + public nsNSSShutDownObject +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICMSDECODER + + nsCMSDecoder(); + nsresult Init(); + +private: + virtual ~nsCMSDecoder(); + nsCOMPtr<nsIInterfaceRequestor> m_ctx; + NSSCMSDecoderContext *m_dcx; + virtual void virtualDestroyNSSReference() override; + void destructorSafeDestroyNSSReference(); +}; + +// =============================================== +// nsCMSEncoder - implementation of nsICMSEncoder +// =============================================== + +#define NS_CMSENCODER_CID \ + { 0xa15789aa, 0x8903, 0x462b, { 0x81, 0xe9, 0x4a, 0xa2, 0xcf, 0xf4, 0xd5, 0xcb } } +class nsCMSEncoder : public nsICMSEncoder, + public nsNSSShutDownObject +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICMSENCODER + + nsCMSEncoder(); + nsresult Init(); + +private: + virtual ~nsCMSEncoder(); + nsCOMPtr<nsIInterfaceRequestor> m_ctx; + NSSCMSEncoderContext *m_ecx; + virtual void virtualDestroyNSSReference() override; + void destructorSafeDestroyNSSReference(); +}; + +#endif diff --git a/mailnews/mime/src/nsCMSSecureMessage.cpp b/mailnews/mime/src/nsCMSSecureMessage.cpp new file mode 100644 index 000000000..0b7a99d7d --- /dev/null +++ b/mailnews/mime/src/nsCMSSecureMessage.cpp @@ -0,0 +1,363 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsXPIDLString.h" +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsIInterfaceRequestor.h" +#include "nsCRT.h" +#include "nsIX509CertDB.h" + +#include "nsICMSSecureMessage.h" + +#include "nsCMSSecureMessage.h" +#include "nsIX509Cert.h" +#include "nsNSSHelper.h" +#include "nsNSSCertificate.h" +#include "nsNSSShutDown.h" + +#include <string.h> +#include "plbase64.h" +#include "cert.h" +#include "cms.h" + +#include "nsIServiceManager.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" + +#include "mozilla/Logging.h" +#ifdef PR_LOGGING +extern mozilla::LazyLogModule gPIPNSSLog; +#endif + +using namespace mozilla; + +// Standard ISupports implementation +// NOTE: Should these be the thread-safe versions? + +/***** + * nsCMSSecureMessage + *****/ + +// Standard ISupports implementation +NS_IMPL_ISUPPORTS(nsCMSSecureMessage, nsICMSSecureMessage) + +// nsCMSSecureMessage constructor +nsCMSSecureMessage::nsCMSSecureMessage() +{ + // initialize superclass +} + +// nsCMSMessage destructor +nsCMSSecureMessage::~nsCMSSecureMessage() +{ +} + +nsresult nsCMSSecureMessage::Init() +{ + nsresult rv; + nsCOMPtr<nsISupports> nssInitialized = do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +/* string getCertByPrefID (in string certID); */ +NS_IMETHODIMP nsCMSSecureMessage:: +GetCertByPrefID(const char *certID, char **_retval) +{ + nsNSSShutDownPreventionLock locker; + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::GetCertByPrefID\n")); + nsresult rv = NS_OK; + CERTCertificate *cert = 0; + nsXPIDLCString nickname; + nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext(); + + *_retval = 0; + + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + goto done; + } + + rv = prefs->GetCharPref(certID, + getter_Copies(nickname)); + if (NS_FAILED(rv)) goto done; + + /* Find a good cert in the user's database */ + cert = CERT_FindUserCertByUsage(CERT_GetDefaultCertDB(), const_cast<char*>(nickname.get()), + certUsageEmailRecipient, true, ctx); + + if (!cert) { + /* Success, but no value */ + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::GetCertByPrefID - can't find user cert\n")); + goto done; + } + + /* Convert the DER to a BASE64 String */ + encode(cert->derCert.data, cert->derCert.len, _retval); + +done: + if (cert) CERT_DestroyCertificate(cert); + return rv; +} + + +// nsCMSSecureMessage::DecodeCert +nsresult nsCMSSecureMessage:: +DecodeCert(const char *value, nsIX509Cert ** _retval) +{ + nsNSSShutDownPreventionLock locker; + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::DecodeCert\n")); + nsresult rv = NS_OK; + int32_t length; + unsigned char *data = 0; + + *_retval = 0; + + if (!value) { return NS_ERROR_FAILURE; } + + rv = decode(value, &data, &length); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::DecodeCert - can't decode cert\n")); + return rv; + } + + nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID); + if (!certdb) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIX509Cert> cert; + certdb->ConstructX509(reinterpret_cast<char *>(data), length, getter_AddRefs(cert)); + + if (cert) { + *_retval = cert; + NS_ADDREF(*_retval); + } + else { + rv = NS_ERROR_FAILURE; + } + + free((char*)data); + return rv; +} + +// nsCMSSecureMessage::SendMessage +nsresult nsCMSSecureMessage:: +SendMessage(const char *msg, const char *base64Cert, char ** _retval) +{ + nsNSSShutDownPreventionLock locker; + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage\n")); + nsresult rv = NS_OK; + CERTCertificate *cert = 0; + NSSCMSMessage *cmsMsg = 0; + unsigned char *certDER = 0; + int32_t derLen; + NSSCMSEnvelopedData *env; + NSSCMSContentInfo *cinfo; + NSSCMSRecipientInfo *rcpt; + SECItem output; + PLArenaPool *arena = PORT_NewArena(1024); + SECStatus s; + nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext(); + + /* Step 0. Create a CMS Message */ + cmsMsg = NSS_CMSMessage_Create(nullptr); + if (!cmsMsg) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't create NSSCMSMessage\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + /* Step 1. Import the certificate into NSS */ + rv = decode(base64Cert, &certDER, &derLen); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't decode / import cert into NSS\n")); + goto done; + } + + cert = CERT_DecodeCertFromPackage((char *)certDER, derLen); + if (!cert) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't decode cert from package\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + /* Step 2. Get a signature cert */ + + /* Step 3. Build inner (signature) content */ + + /* Step 4. Build outer (enveloped) content */ + env = NSS_CMSEnvelopedData_Create(cmsMsg, SEC_OID_DES_EDE3_CBC, 0); + if (!env) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't create envelope data\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + cinfo = NSS_CMSEnvelopedData_GetContentInfo(env); + s = NSS_CMSContentInfo_SetContent_Data(cmsMsg, cinfo, 0, false); + if (s != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't set content data\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + rcpt = NSS_CMSRecipientInfo_Create(cmsMsg, cert); + if (!rcpt) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't create recipient info\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + s = NSS_CMSEnvelopedData_AddRecipient(env, rcpt); + if (s != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't add recipient\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + /* Step 5. Add content to message */ + cinfo = NSS_CMSMessage_GetContentInfo(cmsMsg); + s = NSS_CMSContentInfo_SetContent_EnvelopedData(cmsMsg, cinfo, env); + if (s != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't set content enveloped data\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + /* Step 6. Encode */ + NSSCMSEncoderContext *ecx; + + output.data = 0; output.len = 0; + ecx = NSS_CMSEncoder_Start(cmsMsg, 0, 0, &output, arena, + 0, ctx, 0, 0, 0, 0); + if (!ecx) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't start cms encoder\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + s = NSS_CMSEncoder_Update(ecx, msg, strlen(msg)); + if (s != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't update encoder\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + s = NSS_CMSEncoder_Finish(ecx); + if (s != SECSuccess) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::SendMessage - can't finish encoder\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + /* Step 7. Base64 encode and return the result */ + rv = encode(output.data, output.len, _retval); + +done: + if (certDER) free((char *)certDER); + if (cert) CERT_DestroyCertificate(cert); + if (cmsMsg) NSS_CMSMessage_Destroy(cmsMsg); + if (arena) PORT_FreeArena(arena, false); /* false? */ + + return rv; +} + +/* + * nsCMSSecureMessage::ReceiveMessage + */ +nsresult nsCMSSecureMessage:: +ReceiveMessage(const char *msg, char **_retval) +{ + nsNSSShutDownPreventionLock locker; + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage\n")); + nsresult rv = NS_OK; + NSSCMSDecoderContext *dcx; + unsigned char *der = 0; + int32_t derLen; + NSSCMSMessage *cmsMsg = 0; + SECItem *content; + nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext(); + + /* Step 1. Decode the base64 wrapper */ + rv = decode(msg, &der, &derLen); + if (NS_FAILED(rv)) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage - can't base64 decode\n")); + goto done; + } + + dcx = NSS_CMSDecoder_Start(0, 0, 0, /* pw */ 0, ctx, /* key */ 0, 0); + if (!dcx) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage - can't start decoder\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + (void)NSS_CMSDecoder_Update(dcx, (char *)der, derLen); + cmsMsg = NSS_CMSDecoder_Finish(dcx); + if (!cmsMsg) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage - can't finish decoder\n")); + rv = NS_ERROR_FAILURE; + /* Memory leak on dcx?? */ + goto done; + } + + content = NSS_CMSMessage_GetContent(cmsMsg); + if (!content) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::ReceiveMessage - can't get content\n")); + rv = NS_ERROR_FAILURE; + goto done; + } + + /* Copy the data */ + *_retval = (char*)malloc(content->len+1); + memcpy(*_retval, content->data, content->len); + (*_retval)[content->len] = 0; + +done: + if (der) free(der); + if (cmsMsg) NSS_CMSMessage_Destroy(cmsMsg); + + return rv; +} + +nsresult nsCMSSecureMessage:: +encode(const unsigned char *data, int32_t dataLen, char **_retval) +{ + nsresult rv = NS_OK; + + *_retval = PL_Base64Encode((const char *)data, dataLen, nullptr); + if (!*_retval) { rv = NS_ERROR_OUT_OF_MEMORY; goto loser; } + +loser: + return rv; +} + +nsresult nsCMSSecureMessage:: +decode(const char *data, unsigned char **result, int32_t * _retval) +{ + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::decode\n")); + nsresult rv = NS_OK; + uint32_t len = strlen(data); + int adjust = 0; + + /* Compute length adjustment */ + if (data[len-1] == '=') { + adjust++; + if (data[len-2] == '=') adjust++; + } + + *result = (unsigned char *)PL_Base64Decode(data, len, nullptr); + if (!*result) { + MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSSecureMessage::decode - error decoding base64\n")); + rv = NS_ERROR_ILLEGAL_VALUE; + goto loser; + } + + *_retval = (len*3)/4 - adjust; + +loser: + return rv; +} diff --git a/mailnews/mime/src/nsCMSSecureMessage.h b/mailnews/mime/src/nsCMSSecureMessage.h new file mode 100644 index 000000000..a36124ab0 --- /dev/null +++ b/mailnews/mime/src/nsCMSSecureMessage.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _NSCMSSECUREMESSAGE_H_ +#define _NSCMSSECUREMESSAGE_H_ + +#include "nsICMSSecureMessage.h" + +#include "cms.h" + +// =============================================== +// nsCMSManager - implementation of nsICMSManager +// =============================================== + +#define NS_CMSSECUREMESSAGE_CID \ + { 0x5fb907e0, 0x1dd2, 0x11b2, { 0xa7, 0xc0, 0xf1, 0x4c, 0x41, 0x6a, 0x62, 0xa1 } } + +class nsCMSSecureMessage +: public nsICMSSecureMessage +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICMSSECUREMESSAGE + + nsCMSSecureMessage(); + nsresult Init(); + +private: + virtual ~nsCMSSecureMessage(); + NS_METHOD encode(const unsigned char *data, int32_t dataLen, char **_retval); + NS_METHOD decode(const char *data, unsigned char **result, int32_t * _retval); +}; + + +#endif /* _NSCMSMESSAGE_H_ */ diff --git a/mailnews/mime/src/nsMimeObjectClassAccess.cpp b/mailnews/mime/src/nsMimeObjectClassAccess.cpp new file mode 100644 index 000000000..f1286dcc1 --- /dev/null +++ b/mailnews/mime/src/nsMimeObjectClassAccess.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include <stdio.h> +#include "mimecom.h" +#include "nscore.h" +#include "nsMimeObjectClassAccess.h" + +/* + * The following macros actually implement addref, release and + * query interface for our component. + */ +NS_IMPL_ISUPPORTS(nsMimeObjectClassAccess, nsIMimeObjectClassAccess) + +/* + * nsMimeObjectClassAccess definitions.... + */ + +/* + * Inherited methods for nsMimeObjectClassAccess + */ +nsMimeObjectClassAccess::nsMimeObjectClassAccess() +{ +} + +nsMimeObjectClassAccess::~nsMimeObjectClassAccess() +{ +} + +nsresult +nsMimeObjectClassAccess::MimeObjectWrite(void *mimeObject, + char *data, + int32_t length, + bool user_visible_p) +{ + int rc = XPCOM_MimeObject_write(mimeObject, data, length, user_visible_p); + NS_ENSURE_FALSE(rc < 0, NS_ERROR_FAILURE); + + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeInlineTextClass(void **ptr) +{ + *ptr = XPCOM_GetmimeInlineTextClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeLeafClass(void **ptr) +{ + *ptr = XPCOM_GetmimeLeafClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeObjectClass(void **ptr) +{ + *ptr = XPCOM_GetmimeObjectClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeContainerClass(void **ptr) +{ + *ptr = XPCOM_GetmimeContainerClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeMultipartClass(void **ptr) +{ + *ptr = XPCOM_GetmimeMultipartClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeMultipartSignedClass(void **ptr) +{ + *ptr = XPCOM_GetmimeMultipartSignedClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::GetmimeEncryptedClass(void **ptr) +{ + *ptr = XPCOM_GetmimeEncryptedClass(); + return NS_OK; +} + +nsresult +nsMimeObjectClassAccess::MimeCreate(char * content_type, void * hdrs, void * opts, void **ptr) +{ + *ptr = XPCOM_Mime_create(content_type, hdrs, opts); + return NS_OK; +} diff --git a/mailnews/mime/src/nsMimeObjectClassAccess.h b/mailnews/mime/src/nsMimeObjectClassAccess.h new file mode 100644 index 000000000..d4a189aac --- /dev/null +++ b/mailnews/mime/src/nsMimeObjectClassAccess.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This interface is implemented by libmime. This interface is used by + * a Content-Type handler "Plug In" (i.e. vCard) for accessing various + * internal information about the object class system of libmime. When + * libmime progresses to a C++ object class, this would probably change. + */ +#ifndef nsMimeObjectClassAccess_h_ +#define nsMimeObjectClassAccess_h_ + +#include "mozilla/Attributes.h" +#include "nsISupports.h" +#include "nsIMimeObjectClassAccess.h" + +class nsMimeObjectClassAccess : public nsIMimeObjectClassAccess { +public: + nsMimeObjectClassAccess(); + + /* this macro defines QueryInterface, AddRef and Release for this class */ + NS_DECL_ISUPPORTS + + // These methods are all implemented by libmime to be used by + // content type handler plugins for processing stream data. + + // This is the write call for outputting processed stream data. + NS_IMETHOD MimeObjectWrite(void *mimeObject, + char *data, + int32_t length, + bool user_visible_p) override; + + // The following group of calls expose the pointers for the object + // system within libmime. + NS_IMETHOD GetmimeInlineTextClass(void **ptr) override; + NS_IMETHOD GetmimeLeafClass(void **ptr) override; + NS_IMETHOD GetmimeObjectClass(void **ptr) override; + NS_IMETHOD GetmimeContainerClass(void **ptr) override; + NS_IMETHOD GetmimeMultipartClass(void **ptr) override; + NS_IMETHOD GetmimeMultipartSignedClass(void **ptr) override; + NS_IMETHOD GetmimeEncryptedClass(void **ptr) override; + + NS_IMETHOD MimeCreate(char *content_type, void * hdrs, + void * opts, void**ptr) override; + +private: + virtual ~nsMimeObjectClassAccess(); +}; + +#endif /* nsMimeObjectClassAccess_h_ */ diff --git a/mailnews/mime/src/nsMimeStringResources.h b/mailnews/mime/src/nsMimeStringResources.h new file mode 100644 index 000000000..4177c7863 --- /dev/null +++ b/mailnews/mime/src/nsMimeStringResources.h @@ -0,0 +1,40 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _NAME_OF_THIS_HEADER_FILE__ +#define _NAME_OF_THIS_HEADER_FILE__ + +/* Note that the negative values are not actually strings: they are error + * codes masquerading as strings. Do not pass them to MimeGetStringByID() + * expecting to get anything back for your trouble. + */ +#define MIME_OUT_OF_MEMORY -1000 +#define MIME_UNABLE_TO_OPEN_TMP_FILE -1001 +#define MIME_ERROR_WRITING_FILE -1002 +#define MIME_MHTML_SUBJECT 1000 +#define MIME_MHTML_RESENT_COMMENTS 1001 +#define MIME_MHTML_RESENT_DATE 1002 +#define MIME_MHTML_RESENT_SENDER 1003 +#define MIME_MHTML_RESENT_FROM 1004 +#define MIME_MHTML_RESENT_TO 1005 +#define MIME_MHTML_RESENT_CC 1006 +#define MIME_MHTML_DATE 1007 +#define MIME_MHTML_SENDER 1008 +#define MIME_MHTML_FROM 1009 +#define MIME_MHTML_REPLY_TO 1010 +#define MIME_MHTML_ORGANIZATION 1011 +#define MIME_MHTML_TO 1012 +#define MIME_MHTML_CC 1013 +#define MIME_MHTML_NEWSGROUPS 1014 +#define MIME_MHTML_FOLLOWUP_TO 1015 +#define MIME_MHTML_REFERENCES 1016 +#define MIME_MHTML_MESSAGE_ID 1021 +#define MIME_MHTML_BCC 1023 +#define MIME_MSG_LINK_TO_DOCUMENT 1026 +#define MIME_MSG_DOCUMENT_INFO 1027 +#define MIME_MSG_ATTACHMENT 1028 +#define MIME_MSG_DEFAULT_ATTACHMENT_NAME 1040 +#define MIME_FORWARDED_MESSAGE_HTML_USER_WROTE 1041 + +#endif /* _NAME_OF_THIS_HEADER_FILE__ */ diff --git a/mailnews/mime/src/nsSimpleMimeConverterStub.cpp b/mailnews/mime/src/nsSimpleMimeConverterStub.cpp new file mode 100644 index 000000000..50dcf27fd --- /dev/null +++ b/mailnews/mime/src/nsSimpleMimeConverterStub.cpp @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimecth.h" +#include "mimeobj.h" +#include "mimetext.h" +#include "mimemoz2.h" +#include "mimecom.h" +#include "nsStringGlue.h" +#include "nsComponentManagerUtils.h" +#include "nsICategoryManager.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsISimpleMimeConverter.h" +#include "nsServiceManagerUtils.h" +#include "nsSimpleMimeConverterStub.h" + +typedef struct MimeSimpleStub MimeSimpleStub; +typedef struct MimeSimpleStubClass MimeSimpleStubClass; + +struct MimeSimpleStubClass { + MimeInlineTextClass text; +}; + +struct MimeSimpleStub { + MimeInlineText text; + nsCString *buffer; + nsCOMPtr<nsISimpleMimeConverter> innerScriptable; +}; + +#define MimeSimpleStubClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextClassInitializer(ITYPE,CSUPER) } + +MimeDefClass(MimeSimpleStub, MimeSimpleStubClass, mimeSimpleStubClass, NULL); + +static int +BeginGather(MimeObject *obj) +{ + MimeSimpleStub *ssobj = (MimeSimpleStub *)obj; + int status = ((MimeObjectClass *)XPCOM_GetmimeLeafClass())->parse_begin(obj); + + if (status < 0) + return status; + + if (!obj->output_p || + !obj->options || + !obj->options->write_html_p) { + return 0; + } + + ssobj->buffer->Truncate(); + return 0; +} + +static int +GatherLine(const char *line, int32_t length, MimeObject *obj) +{ + MimeSimpleStub *ssobj = (MimeSimpleStub *)obj; + + if (!obj->output_p || + !obj->options || + !obj->options->output_fn) { + return 0; + } + + if (!obj->options->write_html_p) + return MimeObject_write(obj, line, length, true); + + ssobj->buffer->Append(line); + return 0; +} + +static int +EndGather(MimeObject *obj, bool abort_p) +{ + MimeSimpleStub *ssobj = (MimeSimpleStub *)obj; + + if (obj->closed_p) + return 0; + + int status = ((MimeObjectClass *)MIME_GetmimeInlineTextClass())->parse_eof(obj, abort_p); + if (status < 0) + return status; + + if (ssobj->buffer->IsEmpty()) + return 0; + + mime_stream_data *msd = (mime_stream_data *) (obj->options->stream_closure); + nsIChannel *channel = msd->channel; // note the lack of ref counting... + if (channel) + { + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + ssobj->innerScriptable->SetUri(uri); + } + nsCString asHTML; + nsresult rv = ssobj->innerScriptable->ConvertToHTML(nsDependentCString(obj->content_type), + *ssobj->buffer, + asHTML); + if (NS_FAILED(rv)) { + NS_ASSERTION(NS_SUCCEEDED(rv), "converter failure"); + return -1; + } + + // MimeObject_write wants a non-const string for some reason, but it doesn't mutate it + status = MimeObject_write(obj, asHTML.get(), + asHTML.Length(), true); + if (status < 0) + return status; + return 0; +} + +static int +Initialize(MimeObject *obj) +{ + MimeSimpleStub *ssobj = (MimeSimpleStub *)obj; + + nsresult rv; + nsCOMPtr<nsICategoryManager> catman = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return -1; + + nsAutoCString contentType; // lowercase + ToLowerCase(nsDependentCString(obj->content_type), contentType); + + nsCString value; + rv = catman->GetCategoryEntry(NS_SIMPLEMIMECONVERTERS_CATEGORY, + contentType.get(), getter_Copies(value)); + if (NS_FAILED(rv) || value.IsEmpty()) + return -1; + + ssobj->innerScriptable = do_CreateInstance(value.get(), &rv); + if (NS_FAILED(rv) || !ssobj->innerScriptable) + return -1; + ssobj->buffer = new nsCString(); + ((MimeObjectClass *)XPCOM_GetmimeLeafClass())->initialize(obj); + + return 0; +} + +static void +Finalize(MimeObject *obj) +{ + MimeSimpleStub *ssobj = (MimeSimpleStub *)obj; + ssobj->innerScriptable = nullptr; + delete ssobj->buffer; +} + +static int +MimeSimpleStubClassInitialize(MimeSimpleStubClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *)clazz; + oclass->parse_begin = BeginGather; + oclass->parse_line = GatherLine; + oclass->parse_eof = EndGather; + oclass->initialize = Initialize; + oclass->finalize = Finalize; + return 0; +} + +class nsSimpleMimeConverterStub : public nsIMimeContentTypeHandler +{ +public: + nsSimpleMimeConverterStub(const char *aContentType) : mContentType(aContentType) { } + + NS_DECL_ISUPPORTS + + NS_IMETHOD GetContentType(char **contentType) override + { + *contentType = ToNewCString(mContentType); + return *contentType ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + NS_IMETHOD CreateContentTypeHandlerClass(const char *contentType, + contentTypeHandlerInitStruct *initString, + MimeObjectClass **objClass) override; +private: + virtual ~nsSimpleMimeConverterStub() { } + nsCString mContentType; +}; + +NS_IMPL_ISUPPORTS(nsSimpleMimeConverterStub, nsIMimeContentTypeHandler) + +NS_IMETHODIMP +nsSimpleMimeConverterStub::CreateContentTypeHandlerClass(const char *contentType, + contentTypeHandlerInitStruct *initStruct, + MimeObjectClass **objClass) +{ + NS_ENSURE_ARG_POINTER(objClass); + + *objClass = (MimeObjectClass *)&mimeSimpleStubClass; + (*objClass)->superclass = (MimeObjectClass *)XPCOM_GetmimeInlineTextClass(); + NS_ENSURE_TRUE((*objClass)->superclass, NS_ERROR_UNEXPECTED); + + initStruct->force_inline_display = true; + return NS_OK;; +} + +nsresult +MIME_NewSimpleMimeConverterStub(const char *aContentType, + nsIMimeContentTypeHandler **aResult) +{ + RefPtr<nsSimpleMimeConverterStub> inst = new nsSimpleMimeConverterStub(aContentType); + NS_ENSURE_TRUE(inst, NS_ERROR_OUT_OF_MEMORY); + + return CallQueryInterface(inst.get(), aResult); +} diff --git a/mailnews/mime/src/nsSimpleMimeConverterStub.h b/mailnews/mime/src/nsSimpleMimeConverterStub.h new file mode 100644 index 000000000..bdc12e5e3 --- /dev/null +++ b/mailnews/mime/src/nsSimpleMimeConverterStub.h @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NS_SIMPLE_MIME_CONVERTER_STUB_H_ +#define NS_SIMPLE_MIME_CONVERTER_STUB_H_ + +nsresult +MIME_NewSimpleMimeConverterStub(const char *aContentType, + nsIMimeContentTypeHandler **aResult); + +#endif /* NS_SIMPLE_MIME_CONVERTER_STUB_H_ */ diff --git a/mailnews/mime/src/nsStreamConverter.cpp b/mailnews/mime/src/nsStreamConverter.cpp new file mode 100644 index 000000000..0d1781498 --- /dev/null +++ b/mailnews/mime/src/nsStreamConverter.cpp @@ -0,0 +1,1157 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include <stdio.h> +#include "mimecom.h" +#include "modmimee.h" +#include "nscore.h" +#include "nsStreamConverter.h" +#include "prmem.h" +#include "prprf.h" +#include "prlog.h" +#include "plstr.h" +#include "mimemoz2.h" +#include "nsMimeTypes.h" +#include "nsIComponentManager.h" +#include "nsIURL.h" +#include "nsStringGlue.h" +#include "nsUnicharUtils.h" +#include "nsIServiceManager.h" +#include "nsMemory.h" +#include "nsIPipe.h" +#include "nsMimeStringResources.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsNetUtil.h" +#include "nsIMsgQuote.h" +#include "nsIScriptSecurityManager.h" +#include "nsNetUtil.h" +#include "mozITXTToHTMLConv.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIMsgWindow.h" +#include "nsICategoryManager.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsMsgUtils.h" +#include "mozilla/ArrayUtils.h" + +#define PREF_MAIL_DISPLAY_GLYPH "mail.display_glyph" +#define PREF_MAIL_DISPLAY_STRUCT "mail.display_struct" + +//////////////////////////////////////////////////////////////// +// Bridge routines for new stream converter XP-COM interface +//////////////////////////////////////////////////////////////// + +extern "C" void * +mime_bridge_create_draft_stream(nsIMimeEmitter *newEmitter, + nsStreamConverter *newPluginObj2, + nsIURI *uri, + nsMimeOutputType format_out); + +extern "C" void * +bridge_create_stream(nsIMimeEmitter *newEmitter, + nsStreamConverter *newPluginObj2, + nsIURI *uri, + nsMimeOutputType format_out, + uint32_t whattodo, + nsIChannel *aChannel) +{ + if ( (format_out == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (format_out == nsMimeOutput::nsMimeMessageEditorTemplate) ) + return mime_bridge_create_draft_stream(newEmitter, newPluginObj2, uri, format_out); + else + return mime_bridge_create_display_stream(newEmitter, newPluginObj2, uri, format_out, whattodo, + aChannel); +} + +void +bridge_destroy_stream(void *newStream) +{ + nsMIMESession *stream = (nsMIMESession *)newStream; + if (!stream) + return; + + PR_FREEIF(stream); +} + +void +bridge_set_output_type(void *bridgeStream, nsMimeOutputType aType) +{ + nsMIMESession *session = (nsMIMESession *)bridgeStream; + + if (session) + { + // BAD ASSUMPTION!!!! NEED TO CHECK aType + mime_stream_data *msd = (mime_stream_data *)session->data_object; + if (msd) + msd->format_out = aType; // output format type + } +} + +nsresult +bridge_new_new_uri(void *bridgeStream, nsIURI *aURI, int32_t aOutputType) +{ + nsMIMESession *session = (nsMIMESession *)bridgeStream; + const char **fixup_pointer = nullptr; + + if (session) + { + if (session->data_object) + { + bool *override_charset = nullptr; + char **default_charset = nullptr; + char **url_name = nullptr; + + if ( (aOutputType == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (aOutputType == nsMimeOutput::nsMimeMessageEditorTemplate) ) + { + mime_draft_data *mdd = (mime_draft_data *)session->data_object; + if (mdd->options) + { + default_charset = &(mdd->options->default_charset); + override_charset = &(mdd->options->override_charset); + url_name = &(mdd->url_name); + } + } + else + { + mime_stream_data *msd = (mime_stream_data *)session->data_object; + + if (msd->options) + { + default_charset = &(msd->options->default_charset); + override_charset = &(msd->options->override_charset); + url_name = &(msd->url_name); + fixup_pointer = &(msd->options->url); + } + } + + if ( (default_charset) && (override_charset) && (url_name) ) + { + // + // set the default charset to be the folder charset if we have one associated with + // this url... + nsCOMPtr<nsIMsgI18NUrl> i18nUrl (do_QueryInterface(aURI)); + if (i18nUrl) + { + nsCString charset; + + // check to see if we have a charset override...and if we do, set that field appropriately too... + nsresult rv = i18nUrl->GetCharsetOverRide(getter_Copies(charset)); + if (NS_SUCCEEDED(rv) && !charset.IsEmpty() ) { + *override_charset = true; + *default_charset = ToNewCString(charset); + } + else + { + i18nUrl->GetFolderCharset(getter_Copies(charset)); + if (!charset.IsEmpty()) + *default_charset = ToNewCString(charset); + } + + // if there is no manual override and a folder charset exists + // then check if we have a folder level override + if (!(*override_charset) && *default_charset && **default_charset) + { + bool folderCharsetOverride; + rv = i18nUrl->GetFolderCharsetOverride(&folderCharsetOverride); + if (NS_SUCCEEDED(rv) && folderCharsetOverride) + *override_charset = true; + + // notify the default to msgWindow (for the menu check mark) + // do not set the default in case of nsMimeMessageDraftOrTemplate + // or nsMimeMessageEditorTemplate because it is already set + // when the message is displayed and doing it again may overwrite + // the correct MIME charset parsed from the message header + if (aOutputType != nsMimeOutput::nsMimeMessageDraftOrTemplate && + aOutputType != nsMimeOutput::nsMimeMessageEditorTemplate) + { + nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(aURI)); + if (msgurl) + { + nsCOMPtr<nsIMsgWindow> msgWindow; + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + { + msgWindow->SetMailCharacterSet(nsDependentCString(*default_charset)); + msgWindow->SetCharsetOverride(*override_charset); + } + } + } + + // if the pref says always override and no manual override then set the folder charset to override + if (!*override_charset) { + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + { + bool force_override; + rv = pPrefBranch->GetBoolPref("mailnews.force_charset_override", &force_override); + if (NS_SUCCEEDED(rv) && force_override) + { + *override_charset = true; + } + } + } + } + } + nsAutoCString urlString; + if (NS_SUCCEEDED(aURI->GetSpec(urlString))) + { + if (!urlString.IsEmpty()) + { + NS_Free(*url_name); + *url_name = ToNewCString(urlString); + if (!(*url_name)) + return NS_ERROR_OUT_OF_MEMORY; + + // rhp: Ugh, this is ugly...but it works. + if (fixup_pointer) + *fixup_pointer = (const char *)*url_name; + } + } + } + } + } + + return NS_OK; +} + +static int +mime_headers_callback ( void *closure, MimeHeaders *headers ) +{ + // We get away with this because this doesn't get called on draft operations. + mime_stream_data *msd = (mime_stream_data *)closure; + + NS_ASSERTION(msd && headers, "null mime stream data or headers"); + if ( !msd || ! headers ) + return 0; + + NS_ASSERTION(!msd->headers, "non-null mime stream data headers"); + msd->headers = MimeHeaders_copy ( headers ); + return 0; +} + +nsresult +bridge_set_mime_stream_converter_listener(void *bridgeStream, nsIMimeStreamConverterListener* listener, + nsMimeOutputType aOutputType) +{ + nsMIMESession *session = (nsMIMESession *)bridgeStream; + + if ( (session) && (session->data_object) ) + { + if ( (aOutputType == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (aOutputType == nsMimeOutput::nsMimeMessageEditorTemplate) ) + { + mime_draft_data *mdd = (mime_draft_data *)session->data_object; + if (mdd->options) + { + if (listener) + { + mdd->options->caller_need_root_headers = true; + mdd->options->decompose_headers_info_fn = mime_headers_callback; + } + else + { + mdd->options->caller_need_root_headers = false; + mdd->options->decompose_headers_info_fn = nullptr; + } + } + } + else + { + mime_stream_data *msd = (mime_stream_data *)session->data_object; + + if (msd->options) + { + if (listener) + { + msd->options->caller_need_root_headers = true; + msd->options->decompose_headers_info_fn = mime_headers_callback; + } + else + { + msd->options->caller_need_root_headers = false; + msd->options->decompose_headers_info_fn = nullptr; + } + } + } + } + + return NS_OK; +} + +// find a query element in a url and return a pointer to its data +// (query must be in the form "query=") +static const char * +FindQueryElementData(const char * aUrl, const char * aQuery) +{ + if (aUrl && aQuery) + { + size_t queryLen = 0; // we don't call strlen until we need to + aUrl = PL_strcasestr(aUrl, aQuery); + while (aUrl) + { + if (!queryLen) + queryLen = strlen(aQuery); + if (*(aUrl-1) == '&' || *(aUrl-1) == '?') + return aUrl + queryLen; + aUrl = PL_strcasestr(aUrl + queryLen, aQuery); + } + } + return nullptr; +} + +// case-sensitive test for string prefixing. If |string| is prefixed +// by |prefix| then a pointer to the next character in |string| following +// the prefix is returned. If it is not a prefix then |nullptr| is returned. +static const char * +SkipPrefix(const char *aString, const char *aPrefix) +{ + while (*aPrefix) + if (*aPrefix++ != *aString++) + return nullptr; + return aString; +} + +// +// Utility routines needed by this interface +// +nsresult +nsStreamConverter::DetermineOutputFormat(const char *aUrl, nsMimeOutputType *aNewType) +{ + // sanity checking + NS_ENSURE_ARG_POINTER(aNewType); + if (!aUrl || !*aUrl) + { + // default to html for the entire document + *aNewType = nsMimeOutput::nsMimeMessageQuoting; + mOutputFormat = "text/html"; + return NS_OK; + } + + // shorten the url that we test for the query strings by skipping directly + // to the part where the query strings begin. + const char *queryPart = PL_strchr(aUrl, '?'); + + // First, did someone pass in a desired output format. They will be able to + // pass in any content type (i.e. image/gif, text/html, etc...but the "/" will + // have to be represented via the "%2F" value + const char *format = FindQueryElementData(queryPart, "outformat="); + if (format) + { + //NOTE: I've done a file contents search of every file (*.*) in the mozilla + // directory tree and there is not a single location where the string "outformat" + // is added to any URL. It appears that this code has been orphaned off by a change + // elsewhere and is no longer required. It will be removed in the future unless + // someone complains. + MOZ_ASSERT(false, "Is this code actually being used?"); + + while (*format == ' ') + ++format; + + if (*format) + { + mOverrideFormat = "raw"; + + // set mOutputFormat to the supplied format, ensure that we replace any + // %2F strings with the slash character + const char *nextField = PL_strpbrk(format, "&; "); + mOutputFormat.Assign(format, nextField ? nextField - format : -1); + MsgReplaceSubstring(mOutputFormat, "%2F", "/"); + MsgReplaceSubstring(mOutputFormat, "%2f", "/"); + + // Don't muck with this data! + *aNewType = nsMimeOutput::nsMimeMessageRaw; + return NS_OK; + } + } + + // is this is a part that should just come out raw + const char *part = FindQueryElementData(queryPart, "part="); + if (part && !mToType.Equals("application/vnd.mozilla.xul+xml")) + { + // default for parts + mOutputFormat = "raw"; + *aNewType = nsMimeOutput::nsMimeMessageRaw; + + // if we are being asked to fetch a part....it should have a + // content type appended to it...if it does, we want to remember + // that as mOutputFormat + const char * typeField = FindQueryElementData(queryPart, "type="); + if (typeField && !strncmp(typeField, "application/x-message-display", sizeof("application/x-message-display") - 1)) + { + const char *secondTypeField = FindQueryElementData(typeField, "type="); + if (secondTypeField) + typeField = secondTypeField; + } + if (typeField) + { + // store the real content type...mOutputFormat gets deleted later on... + // and make sure we only get our own value. + char *nextField = PL_strchr(typeField, '&'); + mRealContentType.Assign(typeField, nextField ? nextField - typeField : -1); + if (mRealContentType.Equals("message/rfc822")) + { + mRealContentType = "application/x-message-display"; + mOutputFormat = "text/html"; + *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay; + } + else if (mRealContentType.Equals("application/x-message-display")) + { + mRealContentType = ""; + mOutputFormat = "text/html"; + *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay; + } + } + + return NS_OK; + } + + const char *emitter = FindQueryElementData(queryPart, "emitter="); + if (emitter) + { + const char *remainder = SkipPrefix(emitter, "js"); + if (remainder && (!*remainder || *remainder == '&')) + mOverrideFormat = "application/x-js-mime-message"; + } + + // if using the header query + const char *header = FindQueryElementData(queryPart, "header="); + if (header) + { + struct HeaderType { + const char * headerType; + const char * outputFormat; + nsMimeOutputType mimeOutputType; + }; + + // place most commonly used options at the top + static const struct HeaderType rgTypes[] = + { + { "filter", "text/html", nsMimeOutput::nsMimeMessageFilterSniffer }, + { "quotebody", "text/html", nsMimeOutput::nsMimeMessageBodyQuoting }, + { "print", "text/html", nsMimeOutput::nsMimeMessagePrintOutput }, + { "only", "text/xml", nsMimeOutput::nsMimeMessageHeaderDisplay }, + { "none", "text/html", nsMimeOutput::nsMimeMessageBodyDisplay }, + { "quote", "text/html", nsMimeOutput::nsMimeMessageQuoting }, + { "saveas", "text/html", nsMimeOutput::nsMimeMessageSaveAs }, + { "src", "text/plain", nsMimeOutput::nsMimeMessageSource }, + { "attach", "raw", nsMimeOutput::nsMimeMessageAttach } + }; + + // find the requested header in table, ensure that we don't match on a prefix + // by checking that the following character is either null or the next query element + const char * remainder; + for (uint32_t n = 0; n < MOZ_ARRAY_LENGTH(rgTypes); ++n) + { + remainder = SkipPrefix(header, rgTypes[n].headerType); + if (remainder && (*remainder == '\0' || *remainder == '&')) + { + mOutputFormat = rgTypes[n].outputFormat; + *aNewType = rgTypes[n].mimeOutputType; + return NS_OK; + } + } + } + + // default to html for just the body + mOutputFormat = "text/html"; + *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay; + + return NS_OK; +} + +nsresult +nsStreamConverter::InternalCleanup(void) +{ + if (mBridgeStream) + { + bridge_destroy_stream(mBridgeStream); + mBridgeStream = nullptr; + } + + return NS_OK; +} + +/* + * Inherited methods for nsMimeConverter + */ +nsStreamConverter::nsStreamConverter() +{ + // Init member variables... + mWrapperOutput = false; + mBridgeStream = nullptr; + mOutputFormat = "text/html"; + mAlreadyKnowOutputType = false; + mForwardInline = false; + mForwardInlineFilter = false; + mOverrideComposeFormat = false; + + mPendingRequest = nullptr; + mPendingContext = nullptr; +} + +nsStreamConverter::~nsStreamConverter() +{ + InternalCleanup(); +} + +NS_IMPL_ISUPPORTS(nsStreamConverter, nsIStreamListener, nsIRequestObserver, + nsIStreamConverter, nsIMimeStreamConverter) + +/////////////////////////////////////////////////////////////// +// nsStreamConverter definitions.... +/////////////////////////////////////////////////////////////// + +NS_IMETHODIMP nsStreamConverter::Init(nsIURI *aURI, nsIStreamListener * aOutListener, nsIChannel *aChannel) +{ + NS_ENSURE_ARG_POINTER(aURI); + + nsresult rv = NS_OK; + mOutListener = aOutListener; + + // mscott --> we need to look at the url and figure out what the correct output type is... + nsMimeOutputType newType = mOutputType; + if (!mAlreadyKnowOutputType) + { + nsAutoCString urlSpec; + rv = aURI->GetSpec(urlSpec); + DetermineOutputFormat(urlSpec.get(), &newType); + mAlreadyKnowOutputType = true; + mOutputType = newType; + } + + switch (newType) + { + case nsMimeOutput::nsMimeMessageSplitDisplay: // the wrapper HTML output to produce the split header/body display + mWrapperOutput = true; + mOutputFormat = "text/html"; + break; + case nsMimeOutput::nsMimeMessageHeaderDisplay: // the split header/body display + mOutputFormat = "text/xml"; + break; + case nsMimeOutput::nsMimeMessageBodyDisplay: // the split header/body display + mOutputFormat = "text/html"; + break; + + case nsMimeOutput::nsMimeMessageQuoting: // all HTML quoted output + case nsMimeOutput::nsMimeMessageSaveAs: // Save as operation + case nsMimeOutput::nsMimeMessageBodyQuoting: // only HTML body quoted output + case nsMimeOutput::nsMimeMessagePrintOutput: // all Printing output + mOutputFormat = "text/html"; + break; + + case nsMimeOutput::nsMimeMessageAttach: + case nsMimeOutput::nsMimeMessageDecrypt: + case nsMimeOutput::nsMimeMessageRaw: // the raw RFC822 data and attachments + mOutputFormat = "raw"; + break; + + case nsMimeOutput::nsMimeMessageSource: // the raw RFC822 data (view source) and attachments + mOutputFormat = "text/plain"; + mOverrideFormat = "raw"; + break; + + case nsMimeOutput::nsMimeMessageDraftOrTemplate: // Loading drafts & templates + mOutputFormat = "message/draft"; + break; + + case nsMimeOutput::nsMimeMessageEditorTemplate: // Loading templates into editor + mOutputFormat = "text/html"; + break; + + case nsMimeOutput::nsMimeMessageFilterSniffer: // output all displayable part as raw + mOutputFormat = "text/html"; + break; + + default: + NS_ERROR("this means I made a mistake in my assumptions"); + } + + + // the following output channel stream is used to fake the content type for people who later + // call into us.. + nsCString contentTypeToUse; + GetContentType(getter_Copies(contentTypeToUse)); + // mscott --> my theory is that we don't need this fake outgoing channel. Let's use the + // original channel and just set our content type ontop of the original channel... + + aChannel->SetContentType(contentTypeToUse); + + //rv = NS_NewInputStreamChannel(getter_AddRefs(mOutgoingChannel), aURI, nullptr, contentTypeToUse, -1); + //if (NS_FAILED(rv)) + // return rv; + + // Set system principal for this document, which will be dynamically generated + + // We will first find an appropriate emitter in the repository that supports + // the requested output format...note, the special exceptions are nsMimeMessageDraftOrTemplate + // or nsMimeMessageEditorTemplate where we don't need any emitters + // + + if ( (newType != nsMimeOutput::nsMimeMessageDraftOrTemplate) && + (newType != nsMimeOutput::nsMimeMessageEditorTemplate) ) + { + nsAutoCString categoryName ("@mozilla.org/messenger/mimeemitter;1?type="); + if (!mOverrideFormat.IsEmpty()) + categoryName += mOverrideFormat; + else + categoryName += mOutputFormat; + + nsCOMPtr<nsICategoryManager> catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCString contractID; + catman->GetCategoryEntry("mime-emitter", categoryName.get(), getter_Copies(contractID)); + if (!contractID.IsEmpty()) + categoryName = contractID; + } + + mEmitter = do_CreateInstance(categoryName.get(), &rv); + + if ((NS_FAILED(rv)) || (!mEmitter)) + { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + // initialize our emitter + if (mEmitter) + { + // Now we want to create a pipe which we'll use for converting the data. + nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1"); + rv = pipe->Init(true, true, 4096, 8); + NS_ENSURE_SUCCESS(rv, rv); + + // These always succeed because the pipe is initialized above. + MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(getter_AddRefs(mInputStream))); + MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(getter_AddRefs(mOutputStream))); + + mEmitter->Initialize(aURI, aChannel, newType); + mEmitter->SetPipe(mInputStream, mOutputStream); + mEmitter->SetOutputListener(aOutListener); + } + + uint32_t whattodo = mozITXTToHTMLConv::kURLs; + bool enable_emoticons = true; + bool enable_structs = true; + + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + { + rv = pPrefBranch->GetBoolPref(PREF_MAIL_DISPLAY_GLYPH,&enable_emoticons); + if (NS_FAILED(rv) || enable_emoticons) + { + whattodo = whattodo | mozITXTToHTMLConv::kGlyphSubstitution; + } + rv = pPrefBranch->GetBoolPref(PREF_MAIL_DISPLAY_STRUCT,&enable_structs); + if (NS_FAILED(rv) || enable_structs) + { + whattodo = whattodo | mozITXTToHTMLConv::kStructPhrase; + } + } + + if (mOutputType == nsMimeOutput::nsMimeMessageSource) + return NS_OK; + else + { + mBridgeStream = bridge_create_stream(mEmitter, this, aURI, newType, whattodo, aChannel); + if (!mBridgeStream) + return NS_ERROR_OUT_OF_MEMORY; + else + { + SetStreamURI(aURI); + + //Do we need to setup an Mime Stream Converter Listener? + if (mMimeStreamConverterListener) + bridge_set_mime_stream_converter_listener((nsMIMESession *)mBridgeStream, mMimeStreamConverterListener, mOutputType); + + return NS_OK; + } + } +} + +NS_IMETHODIMP nsStreamConverter::GetContentType(char **aOutputContentType) +{ + if (!aOutputContentType) + return NS_ERROR_NULL_POINTER; + + // since this method passes a string through an IDL file we need to use nsMemory to allocate it + // and not strdup! + // (1) check to see if we have a real content type...use it first... + if (!mRealContentType.IsEmpty()) + *aOutputContentType = ToNewCString(mRealContentType); + else if (mOutputFormat.Equals("raw")) + *aOutputContentType = (char *) nsMemory::Clone(UNKNOWN_CONTENT_TYPE, sizeof(UNKNOWN_CONTENT_TYPE)); + else + *aOutputContentType = ToNewCString(mOutputFormat); + return NS_OK; +} + +// +// This is the type of output operation that is being requested by libmime. The types +// of output are specified by nsIMimeOutputType enum +// +nsresult +nsStreamConverter::SetMimeOutputType(nsMimeOutputType aType) +{ + mAlreadyKnowOutputType = true; + mOutputType = aType; + if (mBridgeStream) + bridge_set_output_type(mBridgeStream, aType); + return NS_OK; +} + +NS_IMETHODIMP nsStreamConverter::GetMimeOutputType(nsMimeOutputType *aOutFormat) +{ + nsresult rv = NS_OK; + if (aOutFormat) + *aOutFormat = mOutputType; + else + rv = NS_ERROR_NULL_POINTER; + + return rv; +} + +// +// This is needed by libmime for MHTML link processing...this is the URI associated +// with this input stream +// +nsresult +nsStreamConverter::SetStreamURI(nsIURI *aURI) +{ + mURI = aURI; + if (mBridgeStream) + return bridge_new_new_uri((nsMIMESession *)mBridgeStream, aURI, mOutputType); + else + return NS_OK; +} + +nsresult +nsStreamConverter::SetMimeHeadersListener(nsIMimeStreamConverterListener *listener, nsMimeOutputType aType) +{ + mMimeStreamConverterListener = listener; + bridge_set_mime_stream_converter_listener((nsMIMESession *)mBridgeStream, listener, aType); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetForwardInline(bool aForwardInline) +{ + mForwardInline = aForwardInline; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetForwardToAddress(nsAString &aAddress) +{ + aAddress = mForwardToAddress; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetForwardToAddress(const nsAString &aAddress) +{ + mForwardToAddress = aAddress; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetOverrideComposeFormat(bool *aResult) +{ + if (!aResult) + return NS_ERROR_NULL_POINTER; + *aResult = mOverrideComposeFormat; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetOverrideComposeFormat(bool aOverrideComposeFormat) +{ + mOverrideComposeFormat = aOverrideComposeFormat; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetForwardInline(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mForwardInline; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetForwardInlineFilter(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mForwardInlineFilter; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetForwardInlineFilter(bool aForwardInlineFilter) +{ + mForwardInlineFilter = aForwardInlineFilter; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetIdentity(nsIMsgIdentity * *aIdentity) +{ + if (!aIdentity) return NS_ERROR_NULL_POINTER; + /* + We don't have an identity for the local folders account, + we will return null but it is not an error! + */ + *aIdentity = mIdentity; + NS_IF_ADDREF(*aIdentity); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetIdentity(nsIMsgIdentity * aIdentity) +{ + mIdentity = aIdentity; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetOriginalMsgURI(const char * originalMsgURI) +{ + mOriginalMsgURI = originalMsgURI; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetOriginalMsgURI(char ** result) +{ + if (!result) return NS_ERROR_NULL_POINTER; + *result = ToNewCString(mOriginalMsgURI); + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::SetOrigMsgHdr(nsIMsgDBHdr *aMsgHdr) +{ + mOrigMsgHdr = aMsgHdr; + return NS_OK; +} + +NS_IMETHODIMP +nsStreamConverter::GetOrigMsgHdr(nsIMsgDBHdr * *aMsgHdr) +{ + if (!aMsgHdr) return NS_ERROR_NULL_POINTER; + NS_IF_ADDREF(*aMsgHdr = mOrigMsgHdr); + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////// +// Methods for nsIStreamListener... +///////////////////////////////////////////////////////////////////////////// +// +// Notify the client that data is available in the input stream. This +// method is called whenver data is written into the input stream by the +// networking library... +// +nsresult +nsStreamConverter::OnDataAvailable(nsIRequest *request, + nsISupports *ctxt, + nsIInputStream *aIStream, + uint64_t sourceOffset, + uint32_t aLength) +{ + nsresult rc=NS_OK; // should this be an error instead? + uint32_t readLen = aLength; + uint32_t written; + + // If this is the first time through and we are supposed to be + // outputting the wrapper two pane URL, then do it now. + if (mWrapperOutput) + { + char outBuf[1024]; +const char output[] = "\ +<HTML>\ +<FRAMESET ROWS=\"30%%,70%%\">\ +<FRAME NAME=messageHeader SRC=\"%s?header=only\">\ +<FRAME NAME=messageBody SRC=\"%s?header=none\">\ +</FRAMESET>\ +</HTML>"; + + nsAutoCString url; + if (NS_FAILED(mURI->GetSpec(url))) + return NS_ERROR_FAILURE; + + PR_snprintf(outBuf, sizeof(outBuf), output, url.get(), url.get()); + + if (mEmitter) + mEmitter->Write(nsDependentCString(outBuf), &written); + + // rhp: will this stop the stream???? Not sure. + return NS_ERROR_FAILURE; + } + + char *buf = (char *)PR_Malloc(aLength); + if (!buf) + return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */ + + readLen = aLength; + aIStream->Read(buf, aLength, &readLen); + + // We need to filter out any null characters else we will have a lot of trouble + // as we use c string everywhere in mime + char * readPtr; + char * endPtr = buf + readLen; + + // First let see if the stream contains null characters + for (readPtr = buf; readPtr < endPtr && *readPtr; readPtr ++) + ; + + // Did we find a null character? Then, we need to cleanup the stream + if (readPtr < endPtr) + { + char * writePtr = readPtr; + for (readPtr ++; readPtr < endPtr; readPtr ++) + { + if (!*readPtr) + continue; + + *writePtr = *readPtr; + writePtr ++; + } + readLen = writePtr - buf; + } + + if (mOutputType == nsMimeOutput::nsMimeMessageSource) + { + rc = NS_OK; + if (mEmitter) + { + rc = mEmitter->Write(Substring(buf, buf+readLen), &written); + } + } + else if (mBridgeStream) + { + nsMIMESession *tSession = (nsMIMESession *) mBridgeStream; + // XXX Casting int to nsresult + rc = static_cast<nsresult>( + tSession->put_block((nsMIMESession *)mBridgeStream, buf, readLen)); + } + + PR_FREEIF(buf); + return rc; +} + +///////////////////////////////////////////////////////////////////////////// +// Methods for nsIRequestObserver +///////////////////////////////////////////////////////////////////////////// +// +// Notify the observer that the URL has started to load. This method is +// called only once, at the beginning of a URL load. +// +nsresult +nsStreamConverter::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ +#ifdef DEBUG_rhp + printf("nsStreamConverter::OnStartRequest()\n"); +#endif + +#ifdef DEBUG_mscott + mConvertContentTime = PR_IntervalNow(); +#endif + + // here's a little bit of hackery.... + // since the mime converter is now between the channel + // and the + if (request) + { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + if (channel) + { + nsCString contentType; + GetContentType(getter_Copies(contentType)); + + channel->SetContentType(contentType); + } + } + + // forward the start request to any listeners + if (mOutListener) + { + if (mOutputType == nsMimeOutput::nsMimeMessageRaw) + { + //we need to delay the on start request until we have figure out the real content type + mPendingRequest = request; + mPendingContext = ctxt; + } + else + mOutListener->OnStartRequest(request, ctxt); + } + + return NS_OK; +} + +// +// Notify the observer that the URL has finished loading. This method is +// called once when the networking library has finished processing the +// +nsresult +nsStreamConverter::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) +{ + // Make sure we fire any pending OnStartRequest before we do OnStop. + FirePendingStartRequest(); +#ifdef DEBUG_rhp + printf("nsStreamConverter::OnStopRequest()\n"); +#endif + + // + // Now complete the stream! + // + if (mBridgeStream) + { + nsMIMESession *tSession = (nsMIMESession *) mBridgeStream; + + if (mMimeStreamConverterListener) + { + + MimeHeaders **workHeaders = nullptr; + + if ( (mOutputType == nsMimeOutput::nsMimeMessageDraftOrTemplate) || + (mOutputType == nsMimeOutput::nsMimeMessageEditorTemplate) ) + { + mime_draft_data *mdd = (mime_draft_data *)tSession->data_object; + if (mdd) + workHeaders = &(mdd->headers); + } + else + { + mime_stream_data *msd = (mime_stream_data *)tSession->data_object; + if (msd) + workHeaders = &(msd->headers); + } + + if (workHeaders) + { + nsresult rv; + nsCOMPtr<nsIMimeHeaders> mimeHeaders = do_CreateInstance(NS_IMIMEHEADERS_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) + { + if (*workHeaders) + mimeHeaders->Initialize(Substring((*workHeaders)->all_headers, + (*workHeaders)->all_headers_fp)); + mMimeStreamConverterListener->OnHeadersReady(mimeHeaders); + } + else + mMimeStreamConverterListener->OnHeadersReady(nullptr); + } + + mMimeStreamConverterListener = nullptr; // release our reference + } + + tSession->complete((nsMIMESession *)mBridgeStream); + } + + // + // Now complete the emitter and do necessary cleanup! + // + if (mEmitter) + { + mEmitter->Complete(); + } + + // First close the output stream... + if (mOutputStream) + mOutputStream->Close(); + + // Make sure to do necessary cleanup! + InternalCleanup(); + +#if 0 + // print out the mime timing information BEFORE we flush to layout + // otherwise we'll be including layout information. + printf("Time Spent in mime: %d ms\n", PR_IntervalToMilliseconds(PR_IntervalNow() - mConvertContentTime)); +#endif + + // forward on top request to any listeners + if (mOutListener) + mOutListener->OnStopRequest(request, ctxt, status); + + + mAlreadyKnowOutputType = false; + + // since we are done converting data, lets close all the objects we own... + // this helps us fix some circular ref counting problems we are running into... + Close(); + + // Time to return... + return NS_OK; +} + +nsresult nsStreamConverter::Close() +{ + mOutgoingChannel = nullptr; + mEmitter = nullptr; + mOutListener = nullptr; + return NS_OK; +} + +// nsIStreamConverter implementation + +// No syncronous conversion at this time. +NS_IMETHODIMP nsStreamConverter::Convert(nsIInputStream *aFromStream, + const char *aFromType, + const char *aToType, + nsISupports *aCtxt, + nsIInputStream **_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +// Stream converter service calls this to initialize the actual stream converter (us). +NS_IMETHODIMP nsStreamConverter::AsyncConvertData(const char *aFromType, + const char *aToType, + nsIStreamListener *aListener, + nsISupports *aCtxt) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgQuote> aMsgQuote = do_QueryInterface(aCtxt, &rv); + nsCOMPtr<nsIChannel> aChannel; + + if (aMsgQuote) + { + nsCOMPtr<nsIMimeStreamConverterListener> quoteListener; + rv = aMsgQuote->GetQuoteListener(getter_AddRefs(quoteListener)); + if (quoteListener) + SetMimeHeadersListener(quoteListener, nsMimeOutput::nsMimeMessageQuoting); + rv = aMsgQuote->GetQuoteChannel(getter_AddRefs(aChannel)); + } + else + { + aChannel = do_QueryInterface(aCtxt, &rv); + } + + mFromType = aFromType; + mToType = aToType; + + NS_ASSERTION(aChannel && NS_SUCCEEDED(rv), "mailnews mime converter has to have the channel passed in..."); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIURI> aUri; + aChannel->GetURI(getter_AddRefs(aUri)); + return Init(aUri, aListener, aChannel); +} + +NS_IMETHODIMP nsStreamConverter::FirePendingStartRequest() +{ + if (mPendingRequest && mOutListener) + { + mOutListener->OnStartRequest(mPendingRequest, mPendingContext); + mPendingRequest = nullptr; + mPendingContext = nullptr; + } + return NS_OK; +} diff --git a/mailnews/mime/src/nsStreamConverter.h b/mailnews/mime/src/nsStreamConverter.h new file mode 100644 index 000000000..0bd11d1d9 --- /dev/null +++ b/mailnews/mime/src/nsStreamConverter.h @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsStreamConverter_h_ +#define nsStreamConverter_h_ + +#include "nsIStreamConverter.h" +#include "nsIMimeStreamConverter.h" +#include "nsIMimeEmitter.h" +#include "nsIURI.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIChannel.h" +#include "nsStringGlue.h" +#include "nsCOMPtr.h" + +#define MIME_FORWARD_HTML_PREFIX "<HTML><BODY><BR><BR>" + +class nsStreamConverter : public nsIStreamConverter, public nsIMimeStreamConverter { +public: + nsStreamConverter(); + + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIMimeStreamConverter support + NS_DECL_NSIMIMESTREAMCONVERTER + // nsIStreamConverter methods + NS_DECL_NSISTREAMCONVERTER + // nsIStreamListener methods + NS_DECL_NSISTREAMLISTENER + + // nsIRequestObserver methods + NS_DECL_NSIREQUESTOBSERVER + + //////////////////////////////////////////////////////////////////////////// + // nsStreamConverter specific methods: + //////////////////////////////////////////////////////////////////////////// + NS_IMETHOD Init(nsIURI *aURI, nsIStreamListener * aOutListener, nsIChannel *aChannel); + NS_IMETHOD GetContentType(char **aOutputContentType); + NS_IMETHOD InternalCleanup(void); + NS_IMETHOD DetermineOutputFormat(const char *url, nsMimeOutputType *newType); + NS_IMETHOD FirePendingStartRequest(void); + +private: + virtual ~nsStreamConverter(); + nsresult Close(); + + // the input and output streams form a pipe...they need to be passed around together.. + nsCOMPtr<nsIAsyncOutputStream> mOutputStream; // output stream + nsCOMPtr<nsIAsyncInputStream> mInputStream; + + nsCOMPtr<nsIStreamListener> mOutListener; // output stream listener + nsCOMPtr<nsIChannel> mOutgoingChannel; + + nsCOMPtr<nsIMimeEmitter> mEmitter; // emitter being used... + nsCOMPtr<nsIURI> mURI; // URI being processed + nsMimeOutputType mOutputType; // the output type we should use for the operation + bool mAlreadyKnowOutputType; + + void *mBridgeStream; // internal libmime data stream + + // Type of output, entire message, header only, body only + nsCString mOutputFormat; + nsCString mRealContentType; // if we know the content type for real, this will be set (used by attachments) + + nsCString mOverrideFormat; // this is a possible override for emitter creation + bool mWrapperOutput; // Should we output the frame split message display + + nsCOMPtr<nsIMimeStreamConverterListener> mMimeStreamConverterListener; + bool mForwardInline; + bool mForwardInlineFilter; + bool mOverrideComposeFormat; + nsString mForwardToAddress; + nsCOMPtr<nsIMsgIdentity> mIdentity; + nsCString mOriginalMsgURI; + nsCOMPtr<nsIMsgDBHdr> mOrigMsgHdr; + + nsCString mFromType; + nsCString mToType; +#ifdef DEBUG_mscott + PRTime mConvertContentTime; +#endif + nsIRequest * mPendingRequest; // used when we need to delay to fire onStartRequest + nsISupports * mPendingContext; // used when we need to delay to fire onStartRequest +}; + +#endif /* nsStreamConverter_h_ */ |