summaryrefslogtreecommitdiffstats
path: root/mailnews/mime
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/mime')
-rw-r--r--mailnews/mime/cthandlers/glue/mimexpcom.cpp132
-rw-r--r--mailnews/mime/cthandlers/glue/mimexpcom.h93
-rw-r--r--mailnews/mime/cthandlers/glue/moz.build18
-rw-r--r--mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.cpp60
-rw-r--r--mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.h47
-rw-r--r--mailnews/mime/cthandlers/moz.build12
-rw-r--r--mailnews/mime/cthandlers/pgpmime/moz.build20
-rw-r--r--mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.cpp634
-rw-r--r--mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.h69
-rw-r--r--mailnews/mime/cthandlers/vcard/mimevcrd.cpp378
-rw-r--r--mailnews/mime/cthandlers/vcard/mimevcrd.h33
-rw-r--r--mailnews/mime/cthandlers/vcard/moz.build14
-rw-r--r--mailnews/mime/emitters/moz.build21
-rw-r--r--mailnews/mime/emitters/nsEmitterUtils.cpp67
-rw-r--r--mailnews/mime/emitters/nsEmitterUtils.h14
-rw-r--r--mailnews/mime/emitters/nsMimeBaseEmitter.cpp1092
-rw-r--r--mailnews/mime/emitters/nsMimeBaseEmitter.h147
-rw-r--r--mailnews/mime/emitters/nsMimeEmitterCID.h51
-rw-r--r--mailnews/mime/emitters/nsMimeHtmlEmitter.cpp543
-rw-r--r--mailnews/mime/emitters/nsMimeHtmlEmitter.h64
-rw-r--r--mailnews/mime/emitters/nsMimePlainEmitter.cpp64
-rw-r--r--mailnews/mime/emitters/nsMimePlainEmitter.h31
-rw-r--r--mailnews/mime/emitters/nsMimeRawEmitter.cpp34
-rw-r--r--mailnews/mime/emitters/nsMimeRawEmitter.h29
-rw-r--r--mailnews/mime/emitters/nsMimeRebuffer.cpp50
-rw-r--r--mailnews/mime/emitters/nsMimeRebuffer.h29
-rw-r--r--mailnews/mime/emitters/nsMimeXmlEmitter.cpp184
-rw-r--r--mailnews/mime/emitters/nsMimeXmlEmitter.h47
-rw-r--r--mailnews/mime/jsmime/LICENSE19
-rw-r--r--mailnews/mime/jsmime/README.md59
-rw-r--r--mailnews/mime/jsmime/jsmime.js3300
-rw-r--r--mailnews/mime/moz.build15
-rw-r--r--mailnews/mime/public/MimeEncoder.h44
-rw-r--r--mailnews/mime/public/MimeHeaderParser.h174
-rw-r--r--mailnews/mime/public/moz.build36
-rw-r--r--mailnews/mime/public/msgIStructuredHeaders.idl209
-rw-r--r--mailnews/mime/public/nsICMSDecoder.idl29
-rw-r--r--mailnews/mime/public/nsICMSEncoder.idl30
-rw-r--r--mailnews/mime/public/nsICMSMessage.idl39
-rw-r--r--mailnews/mime/public/nsICMSMessage2.idl64
-rw-r--r--mailnews/mime/public/nsICMSMessageErrors.idl35
-rw-r--r--mailnews/mime/public/nsICMSSecureMessage.idl42
-rw-r--r--mailnews/mime/public/nsIMimeContentTypeHandler.h65
-rw-r--r--mailnews/mime/public/nsIMimeConverter.idl75
-rw-r--r--mailnews/mime/public/nsIMimeEmitter.idl81
-rw-r--r--mailnews/mime/public/nsIMimeHeaders.idl41
-rw-r--r--mailnews/mime/public/nsIMimeMiscStatus.idl76
-rw-r--r--mailnews/mime/public/nsIMimeObjectClassAccess.h49
-rw-r--r--mailnews/mime/public/nsIMimeStreamConverter.idl93
-rw-r--r--mailnews/mime/public/nsIMsgHeaderParser.idl235
-rw-r--r--mailnews/mime/public/nsIPgpMimeProxy.idl69
-rw-r--r--mailnews/mime/public/nsISimpleMimeConverter.idl22
-rw-r--r--mailnews/mime/public/nsMailHeaders.h90
-rw-r--r--mailnews/mime/public/nsMsgMimeCID.h33
-rw-r--r--mailnews/mime/src/MimeHeaderParser.cpp229
-rw-r--r--mailnews/mime/src/comi18n.cpp108
-rw-r--r--mailnews/mime/src/comi18n.h42
-rw-r--r--mailnews/mime/src/extraMimeParsers.jsm29
-rw-r--r--mailnews/mime/src/jsmime.jsm90
-rw-r--r--mailnews/mime/src/mime.def7
-rw-r--r--mailnews/mime/src/mimeJSComponents.js512
-rw-r--r--mailnews/mime/src/mimeParser.jsm258
-rw-r--r--mailnews/mime/src/mimeTextHTMLParsed.cpp150
-rw-r--r--mailnews/mime/src/mimeTextHTMLParsed.h28
-rw-r--r--mailnews/mime/src/mimebuf.cpp249
-rw-r--r--mailnews/mime/src/mimebuf.h39
-rw-r--r--mailnews/mime/src/mimecms.cpp716
-rw-r--r--mailnews/mime/src/mimecms.h36
-rw-r--r--mailnews/mime/src/mimecom.cpp74
-rw-r--r--mailnews/mime/src/mimecom.h38
-rw-r--r--mailnews/mime/src/mimecont.cpp218
-rw-r--r--mailnews/mime/src/mimecont.h43
-rw-r--r--mailnews/mime/src/mimecryp.cpp571
-rw-r--r--mailnews/mime/src/mimecryp.h140
-rw-r--r--mailnews/mime/src/mimecth.cpp51
-rw-r--r--mailnews/mime/src/mimecth.h135
-rw-r--r--mailnews/mime/src/mimedrft.cpp2084
-rw-r--r--mailnews/mime/src/mimeebod.cpp509
-rw-r--r--mailnews/mime/src/mimeebod.h37
-rw-r--r--mailnews/mime/src/mimeenc.cpp1107
-rw-r--r--mailnews/mime/src/mimeeobj.cpp236
-rw-r--r--mailnews/mime/src/mimeeobj.h34
-rw-r--r--mailnews/mime/src/mimefilt.cpp399
-rw-r--r--mailnews/mime/src/mimehdrs.cpp888
-rw-r--r--mailnews/mime/src/mimehdrs.h88
-rw-r--r--mailnews/mime/src/mimei.cpp1920
-rw-r--r--mailnews/mime/src/mimei.h422
-rw-r--r--mailnews/mime/src/mimeiimg.cpp249
-rw-r--r--mailnews/mime/src/mimeiimg.h35
-rw-r--r--mailnews/mime/src/mimeleaf.cpp221
-rw-r--r--mailnews/mime/src/mimeleaf.h59
-rw-r--r--mailnews/mime/src/mimemalt.cpp580
-rw-r--r--mailnews/mime/src/mimemalt.h48
-rw-r--r--mailnews/mime/src/mimemapl.cpp189
-rw-r--r--mailnews/mime/src/mimemapl.h32
-rw-r--r--mailnews/mime/src/mimemcms.cpp470
-rw-r--r--mailnews/mime/src/mimemcms.h35
-rw-r--r--mailnews/mime/src/mimemdig.cpp24
-rw-r--r--mailnews/mime/src/mimemdig.h33
-rw-r--r--mailnews/mime/src/mimemmix.cpp21
-rw-r--r--mailnews/mime/src/mimemmix.h32
-rw-r--r--mailnews/mime/src/mimemoz2.cpp2211
-rw-r--r--mailnews/mime/src/mimemoz2.h196
-rw-r--r--mailnews/mime/src/mimempar.cpp21
-rw-r--r--mailnews/mime/src/mimempar.h32
-rw-r--r--mailnews/mime/src/mimemrel.cpp1199
-rw-r--r--mailnews/mime/src/mimemrel.h66
-rw-r--r--mailnews/mime/src/mimemsg.cpp977
-rw-r--r--mailnews/mime/src/mimemsg.h43
-rw-r--r--mailnews/mime/src/mimemsig.cpp775
-rw-r--r--mailnews/mime/src/mimemsig.h134
-rw-r--r--mailnews/mime/src/mimemult.cpp748
-rw-r--r--mailnews/mime/src/mimemult.h102
-rw-r--r--mailnews/mime/src/mimeobj.cpp327
-rw-r--r--mailnews/mime/src/mimeobj.h186
-rw-r--r--mailnews/mime/src/mimepbuf.cpp296
-rw-r--r--mailnews/mime/src/mimepbuf.h64
-rw-r--r--mailnews/mime/src/mimesun.cpp342
-rw-r--r--mailnews/mime/src/mimesun.h59
-rw-r--r--mailnews/mime/src/mimetenr.cpp28
-rw-r--r--mailnews/mime/src/mimetenr.h32
-rw-r--r--mailnews/mime/src/mimetext.cpp544
-rw-r--r--mailnews/mime/src/mimetext.h82
-rw-r--r--mailnews/mime/src/mimethpl.cpp165
-rw-r--r--mailnews/mime/src/mimethpl.h35
-rw-r--r--mailnews/mime/src/mimethsa.cpp143
-rw-r--r--mailnews/mime/src/mimethsa.h28
-rw-r--r--mailnews/mime/src/mimethtm.cpp254
-rw-r--r--mailnews/mime/src/mimethtm.h35
-rw-r--r--mailnews/mime/src/mimetpfl.cpp630
-rw-r--r--mailnews/mime/src/mimetpfl.h52
-rw-r--r--mailnews/mime/src/mimetpla.cpp451
-rw-r--r--mailnews/mime/src/mimetpla.h39
-rw-r--r--mailnews/mime/src/mimetric.cpp353
-rw-r--r--mailnews/mime/src/mimetric.h33
-rw-r--r--mailnews/mime/src/mimeunty.cpp588
-rw-r--r--mailnews/mime/src/mimeunty.h70
-rw-r--r--mailnews/mime/src/modlmime.h398
-rw-r--r--mailnews/mime/src/modmimee.h56
-rw-r--r--mailnews/mime/src/moz.build92
-rw-r--r--mailnews/mime/src/msgMime.manifest9
-rw-r--r--mailnews/mime/src/nsCMS.cpp966
-rw-r--r--mailnews/mime/src/nsCMS.h104
-rw-r--r--mailnews/mime/src/nsCMSSecureMessage.cpp363
-rw-r--r--mailnews/mime/src/nsCMSSecureMessage.h37
-rw-r--r--mailnews/mime/src/nsMimeObjectClassAccess.cpp97
-rw-r--r--mailnews/mime/src/nsMimeObjectClassAccess.h52
-rw-r--r--mailnews/mime/src/nsMimeStringResources.h40
-rw-r--r--mailnews/mime/src/nsSimpleMimeConverterStub.cpp209
-rw-r--r--mailnews/mime/src/nsSimpleMimeConverterStub.h13
-rw-r--r--mailnews/mime/src/nsStreamConverter.cpp1157
-rw-r--r--mailnews/mime/src/nsStreamConverter.h88
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, "&nbsp;&lt;<a href=""mailto:%s"" private>%s</a>&gt;", 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 "&lt;", 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 += "--&nbsp;<br>";
+ } else {
+ exdata->isSig = true;
+ preface += "<div class=\"moz-txt-sig\"><span class=\"moz-txt-tag\">"
+ "--&nbsp;<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 &lt;
+ if ('<' == a_current_char) {
+ *a_in_tag = true;
+ *a_in_quote_in_tag = false;
+ }
+}
+
+
+/**
+ * Converts whitespace to |&nbsp;|, 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 &nbsp;.
+ 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("&nbsp;");
+ }
+
+ while(number_of_space--) {
+ // a_out_string += ' '; gives error
+ a_out_string.AppendLiteral(" ");
+ }
+
+ return;
+}
+
+/**
+ * Passes over the line and converts whitespace to |&nbsp;|, 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, &quote_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, "&amp;", outlen);
+ addedlen = strlen(out);
+ outlen -= addedlen;
+ out += addedlen;
+ }
+ else if (*this_start == '>')
+ {
+ PL_strncpyz (out, "&gt;", 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, "&lt;", 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 = "&lt;", 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 "&lt;", 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_ */