summaryrefslogtreecommitdiffstats
path: root/mailnews/mime/src
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/mime/src')
-rw-r--r--mailnews/mime/src/MimeHeaderParser.cpp248
-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.js515
-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.cpp797
-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.cpp562
-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.cpp246
-rw-r--r--mailnews/mime/src/mimeeobj.h50
-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.cpp1910
-rw-r--r--mailnews/mime/src/mimei.h410
-rw-r--r--mailnews/mime/src/mimeiimg.cpp249
-rw-r--r--mailnews/mime/src/mimeiimg.h35
-rw-r--r--mailnews/mime/src/mimeleaf.cpp220
-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.cpp494
-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.cpp2212
-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.cpp965
-rw-r--r--mailnews/mime/src/mimemsg.h37
-rw-r--r--mailnews/mime/src/mimemsig.cpp783
-rw-r--r--mailnews/mime/src/mimemsig.h136
-rw-r--r--mailnews/mime/src/mimemult.cpp753
-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.cpp1048
-rw-r--r--mailnews/mime/src/nsCMS.h108
-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
98 files changed, 28741 insertions, 0 deletions
diff --git a/mailnews/mime/src/MimeHeaderParser.cpp b/mailnews/mime/src/MimeHeaderParser.cpp
new file mode 100644
index 000000000..15341de46
--- /dev/null
+++ b/mailnews/mime/src/MimeHeaderParser.cpp
@@ -0,0 +1,248 @@
+/* -*- 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 "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());
+ NS_ENSURE_TRUE(headerParser, retval);
+ 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());
+ NS_ENSURE_TRUE(headerParser, retval);
+ 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;
+}
+
+nsCOMArray<msgIAddressObject> EncodedHeaderW(const nsAString &aHeader)
+{
+ nsCOMArray<msgIAddressObject> retval;
+ if (aHeader.IsEmpty()) {
+ return retval;
+ }
+ nsCOMPtr<nsIMsgHeaderParser> headerParser(services::GetHeaderParser());
+ NS_ENSURE_TRUE(headerParser, retval);
+ msgIAddressObject **addresses = nullptr;
+ uint32_t length;
+ nsresult rv = headerParser->ParseEncodedHeaderW(aHeader, &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..8e9d0684a
--- /dev/null
+++ b/mailnews/mime/src/mimeJSComponents.js
@@ -0,0 +1,515 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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);
+ },
+ parseEncodedHeaderW: function (aHeader, count) {
+ aHeader = aHeader || "";
+ let value = MimeParser.parseHeaderField(aHeader,
+ MimeParser.HEADER_ADDRESS |
+ MimeParser.HEADER_OPTION_DECODE_2231 |
+ MimeParser.HEADER_OPTION_DECODE_2047,
+ undefined);
+ return fixArray(value, false, 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 = [];
+ let allAddresses = this.parseEncodedHeader(aHeader, undefined, 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..6f7455c0a
--- /dev/null
+++ b/mailnews/mime/src/mimecms.cpp
@@ -0,0 +1,797 @@
+/* -*- 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 "mimemcms.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;
+
+// The name "mime encrypted" is misleading, because this code is used
+// both for CMS messages that are encrypted, and also for messages that
+// aren't encrypted, but only contain a signature.
+
+#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;
+ bool skip_content;
+ uint32_t decoded_bytes;
+ MimeObject *self;
+ bool any_parent_is_encrypted_p;
+ bool any_parent_is_signed_p;
+ nsCOMPtr<nsIMsgSMIMEHeaderSink> smimeHeaderSink;
+ nsCString url;
+
+ MimeCMSdata()
+ :output_fn(nullptr),
+ output_closure(nullptr),
+ ci_is_encrypted(false),
+ sender_addr(nullptr),
+ decoding_failed(false),
+ skip_content(false),
+ decoded_bytes(0),
+ self(nullptr),
+ any_parent_is_encrypted_p(false),
+ any_parent_is_signed_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 MimeEncOrMP_CMS_signed_p(MimeObject *obj) {
+ bool is_signed;
+
+ if (!obj) return false;
+ if (mime_typep(obj, (MimeObjectClass *)&mimeMultipartSignedCMSClass)) {
+ return true;
+ }
+ 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->ContentIsSigned(&is_signed);
+ return is_signed;
+ }
+ return false;
+}
+
+bool MimeAnyParentCMSEncrypted(MimeObject *obj)
+{
+ MimeObject *o2 = obj;
+ while (o2 && o2->parent) {
+ if (MimeEncryptedCMS_encrypted_p(o2->parent)) {
+ return true;
+ }
+ o2 = o2->parent;
+ }
+ return false;
+}
+
+bool MimeAnyParentCMSSigned(MimeObject *obj)
+{
+ MimeObject *o2 = obj;
+ while (o2 && o2->parent) {
+ if (MimeEncOrMP_CMS_signed_p(o2->parent)) {
+ return true;
+ }
+ o2 = o2->parent;
+ }
+ 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,
+ const nsCString &aMsgNeckoURL);
+
+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 mMsgNeckoURL;
+
+ 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,
+ const nsCString &aMsgNeckoURL);
+ NS_DECL_NSIRUNNABLE
+protected:
+ nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> m_sink;
+ int32_t m_nestingLevel;
+ int32_t m_signatureStatus;
+ nsCOMPtr<nsIX509Cert> m_signerCert;
+ nsCString m_msgNeckoURL;
+};
+
+SignedStatusRunnable::SignedStatusRunnable(const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> &aSink,
+ int32_t aNestingLevel,
+ int32_t aSignatureStatus,
+ nsIX509Cert *aSignerCert,
+ const nsCString &aMsgNeckoURL) :
+ m_sink(aSink), m_nestingLevel(aNestingLevel),
+ m_signatureStatus(aSignatureStatus), m_signerCert(aSignerCert),
+ m_msgNeckoURL(aMsgNeckoURL)
+{
+}
+
+NS_IMETHODIMP SignedStatusRunnable::Run()
+{
+ return m_sink->SignedStatus(m_nestingLevel, m_signatureStatus, m_signerCert,
+ m_msgNeckoURL);
+}
+
+
+nsresult ProxySignedStatus(const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> &aSink,
+ int32_t aNestingLevel,
+ int32_t aSignatureStatus,
+ nsIX509Cert *aSignerCert,
+ const nsCString &aMsgNeckoURL)
+{
+ RefPtr<SignedStatusRunnable> signedStatus =
+ new SignedStatusRunnable(aSink, aNestingLevel, aSignatureStatus, aSignerCert, aMsgNeckoURL);
+ 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,
+ const nsCString &aMsgNeckoURL)
+ : mMsgNeckoURL(aMsgNeckoURL)
+{
+ 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, mMsgNeckoURL);
+
+ 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->any_parent_is_signed_p = MimeAnyParentCMSSigned(obj);
+
+ if (data->any_parent_is_signed_p) {
+ // Parent is signed.
+ // We don't know yet if this child is signed or encrypted.
+ // (We'll know after decoding has completed and EOF is called.)
+ // We don't support "inner encrypt" with outer sign, because the
+ // inner encrypted part could have been produced by an attacker who
+ // stripped away a part containing the signature (S/MIME doesn't
+ // have integrity protection).
+ // A sign-then-sign encoding is confusing, too, because it could be
+ // an attempt to influence which signature is shown.
+ data->skip_content = true;
+ }
+
+ if (!data->skip_content) {
+ 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;
+ }
+ }
+
+ data->any_parent_is_encrypted_p = MimeAnyParentCMSEncrypted(obj);
+
+ 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)
+ {
+ rv = uri->GetSpec(data->url);
+
+ // 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(data->url.get(), "?header=filter") &&
+ !strstr(data->url.get(), "&header=filter") &&
+ !strstr(data->url.get(), "?header=attach") &&
+ !strstr(data->url.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;
+
+ if (!data->decoding_failed && !data->skip_content) {
+ 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,
+ const nsCString &aMsgNeckoURL,
+ unsigned char* item_data, uint32_t item_len,
+ int16_t digest_type)
+{
+ nsCOMPtr<nsICMSMessage2> msg2 = do_QueryInterface(aCMSMsg);
+ if (!msg2)
+ return;
+
+ RefPtr<nsSMimeVerificationListener> listener =
+ new nsSMimeVerificationListener(aFromAddr, aFromName, aSenderAddr, aSenderName,
+ aHeaderSink, aMimeNestingLevel, aMsgNeckoURL);
+ if (!listener)
+ return;
+
+ if (item_data)
+ msg2->AsyncVerifyDetachedSignature(listener, item_data, item_len, digest_type);
+ 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) {
+ return -1;
+ }
+
+ if (!data->skip_content && !data->decoder_context) {
+ // If we don't skip, we should have a 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);
+ if (!data->skip_content) {
+ 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->skip_content) {
+ // Skipping content means, we detected a forbidden combination
+ // of CMS objects, so let's make sure we replace the parent status
+ // with a bad status.
+ if (data->any_parent_is_signed_p) {
+ data->smimeHeaderSink->SignedStatus(aRelativeNestLevel,
+ nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url);
+ }
+ if (data->any_parent_is_encrypted_p) {
+ data->smimeHeaderSink->EncryptionStatus(aRelativeNestLevel,
+ nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url);
+ }
+ return 0;
+ }
+
+ 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,
+ data->url, nullptr, 0, 0);
+ }
+ }
+
+ if (data->ci_is_encrypted)
+ {
+ data->smimeHeaderSink->EncryptionStatus(
+ aRelativeNestLevel,
+ status,
+ certOfInterest,
+ data->url
+ );
+ }
+
+ 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..fd4f8f8ae
--- /dev/null
+++ b/mailnews/mime/src/mimecryp.cpp
@@ -0,0 +1,562 @@
+/* -*- 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)
+ {
+ char *html;
+
+ /* 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");
+ char *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..90ca027d8
--- /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..7d8b2f8a6
--- /dev/null
+++ b/mailnews/mime/src/mimeeobj.cpp
@@ -0,0 +1,246 @@
+/* -*- 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;
+}
+
+#undef MIME_SUPERCLASS
+#define MIME_SUPERCLASS mimeExternalObjectClass
+MimeDefClass(MimeSuppressedCrypto, MimeSuppressedCryptoClass,
+ mimeSuppressedCryptoClass, &MIME_SUPERCLASS);
+
+static int MimeSuppressedCryptoClassInitialize(MimeSuppressedCryptoClass *clazz) {
+ MimeExternalObjectClass *lclass = (MimeExternalObjectClass *)clazz;
+ return MimeExternalObjectClassInitialize(lclass);
+}
diff --git a/mailnews/mime/src/mimeeobj.h b/mailnews/mime/src/mimeeobj.h
new file mode 100644
index 000000000..076f8f9bf
--- /dev/null
+++ b/mailnews/mime/src/mimeeobj.h
@@ -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/. */
+
+#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) }
+
+typedef struct MimeSuppressedCryptoClass MimeSuppressedCryptoClass;
+typedef struct MimeSuppressedCrypto MimeSuppressedCrypto;
+
+struct MimeSuppressedCryptoClass {
+ MimeExternalObjectClass eobj;
+};
+
+extern "C" MimeSuppressedCryptoClass mimeSuppressedCryptoClass;
+
+struct MimeSuppressedCrypto {
+ MimeExternalObject eobj;
+};
+
+#define MimeSuppressedCryptoClassInitializer(ITYPE, CSUPER) \
+ { MimeExternalObjectClassInitializer(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..015830a80
--- /dev/null
+++ b/mailnews/mime/src/mimei.cpp
@@ -0,0 +1,1910 @@
+/* -*- 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 *)&mimeSuppressedCryptoClass;
+ 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
+ // However, mimeSuppressedCryptoClass is treated identically as
+ // mimeExternalObjectClass, let's not lose that type information.
+ if (clazz != (MimeObjectClass *)&mimeSuppressedCryptoClass) {
+ 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;
+}
+
+#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..a024c4523
--- /dev/null
+++ b/mailnews/mime/src/mimei.h
@@ -0,0 +1,410 @@
+/* -*- 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);
+
+/* 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;
+ 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. */
+
+ 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..b76d0ce3b
--- /dev/null
+++ b/mailnews/mime/src/mimeleaf.cpp
@@ -0,0 +1,220 @@
+/* -*- 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 && obj->parent->output_p &&
+ (!PL_strcasecmp(obj->parent->content_type, MESSAGE_NEWS) ||
+ !PL_strcasecmp(obj->parent->content_type, MESSAGE_RFC822))))
+ /* 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..96aa5481f
--- /dev/null
+++ b/mailnews/mime/src/mimemcms.cpp
@@ -0,0 +1,494 @@
+/* -*- 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 *);
+static void MimeMultCMS_suppressed_child(void *crypto_closure);
+
+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_notify_suppressed_child = MimeMultCMS_suppressed_child;
+ 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;
+ nsCOMPtr<nsIMsgSMIMEHeaderSink> smimeHeaderSink;
+ nsCString url;
+
+ MimeMultCMSdata()
+ :hash_type(0),
+ sender_addr(nullptr),
+ decoding_failed(false),
+ item_data(nullptr),
+ self(nullptr) {}
+
+ ~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 MimeAnyParentCMSSigned(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,
+ const nsCString &aMsgNeckoURL,
+ unsigned char* item_data, uint32_t item_len,
+ int16_t digest_type);
+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;
+
+ data = new MimeMultCMSdata;
+ if (!data)
+ return 0;
+
+ data->self = obj;
+
+ 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)
+ {
+ rv = uri->GetSpec(data->url);
+
+ // 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(data->url.get(), "?header=filter") &&
+ !strstr(data->url.get(), "&header=filter") &&
+ !strstr(data->url.get(), "?header=attach") &&
+ !strstr(data->url.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
+
+ if (obj->parent && MimeAnyParentCMSSigned(obj)) {
+ // Parent is signed. We know this part is a signature, too, because
+ // multipart doesn't allow encryption.
+ // We don't support "inner sign" with outer sign, because the
+ // inner encrypted part could have been produced by an attacker who
+ // stripped away a part containing the signature (S/MIME doesn't
+ // have integrity protection).
+ // Also we don't want to support sign-then-sign, that's misleading,
+ // which part would be shown as having a signature?
+ // TODO: should we show all contents, without any signature info?
+
+ if (data->smimeHeaderSink) {
+ data->smimeHeaderSink->SignedStatus(
+ MIMEGetRelativeCryptoNestLevel(data->self),
+ nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url);
+ }
+ delete data;
+ PR_SetError(-1, 0);
+ return 0;
+ }
+
+ ct = MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false);
+ if (!ct) {
+ delete data;
+ return 0; /* #### bogus message? out of memory? */
+ }
+ micalg = MimeHeaders_get_parameter(ct, PARAM_MICALG, NULL, NULL);
+ PR_Free(ct);
+ ct = 0;
+ if (!micalg) {
+ delete data;
+ 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
+ hash_type = -1;
+
+ PR_Free(micalg);
+ micalg = 0;
+
+ if (hash_type == -1) {
+ delete data;
+ return 0; /* #### bogus message? */
+ }
+
+ 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);
+
+ 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 void MimeMultCMS_suppressed_child(void *crypto_closure) {
+ // I'm a multipart/signed. If one of my cryptographic child elements
+ // was suppressed, then I want my signature to be shown as invalid.
+ MimeMultCMSdata *data = (MimeMultCMSdata *)crypto_closure;
+ if (data && data->smimeHeaderSink) {
+ data->smimeHeaderSink->SignedStatus(
+ MIMEGetRelativeCryptoNestLevel(data->self),
+ nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url);
+ }
+}
+
+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, data->url);
+ }
+ 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->url,
+ data->item_data, data->item_len,
+ data->hash_type);
+
+ 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..c47c5c8a5
--- /dev/null
+++ b/mailnews/mime/src/mimemoz2.cpp
@@ -0,0 +1,2212 @@
+/* -*- 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)
+ {
+ // If it has a filename, we don't skip it regardless of the
+ // content disposition which can be "inline" or "attachment".
+ // Inline parts are not shown when attachments aren't displayed
+ // inline, so the only chance to see the part is as attachment.
+ char * name = MimeHeaders_get_name(child->headers, nullptr);
+ if (name)
+ skip = false;
+ PR_FREEIF(name);
+ }
+
+ 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..d024a7725
--- /dev/null
+++ b/mailnews/mime/src/mimemsg.cpp
@@ -0,0 +1,965 @@
+/* -*- 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 this is the outermost message, then now is the time to run the
+ // post_header_html_fn.
+ 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..9a38a6e71
--- /dev/null
+++ b/mailnews/mime/src/mimemsg.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 _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 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..5b4f8e556
--- /dev/null
+++ b/mailnews/mime/src/mimemsig.cpp
@@ -0,0 +1,783 @@
+/* -*- 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 "mimeeobj.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);
+
+extern "C" MimeSuppressedCryptoClass mimeSuppressedCryptoClass;
+
+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;
+
+ if (!sig->crypto_closure) {
+ // We might have decided to skip processing this part.
+ return 0;
+ }
+
+ 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 &&
+ sig->crypto_closure)
+ {
+ // Calling crypto_generate_html may trigger wanted side effects,
+ // but we're no longer using its results.
+ char *html = (((MimeMultipartSignedClass *) obj->clazz)
+ ->crypto_generate_html (sig->crypto_closure));
+ PR_FREEIF(html);
+
+ /* 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;
+
+ if (mime_typep(body, (MimeObjectClass *)&mimeSuppressedCryptoClass)) {
+ ((MimeMultipartSignedClass *)obj->clazz)
+ ->crypto_notify_suppressed_child(sig->crypto_closure);
+ }
+
+#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..2ec9d314d
--- /dev/null
+++ b/mailnews/mime/src/mimemsig.h
@@ -0,0 +1,136 @@
+/* -*- 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_notify_suppressed_child)(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..4695ba991
--- /dev/null
+++ b/mailnews/mime/src/mimemult.cpp
@@ -0,0 +1,753 @@
+/* -*- 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;
+extern "C" MimeSuppressedCryptoClass mimeSuppressedCryptoClass;
+
+#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) ||
+ mime_typep(body, (MimeObjectClass *)&mimeSuppressedCryptoClass)) &&
+ !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) ||
+ mime_typep(kid, (MimeObjectClass *)&mimeSuppressedCryptoClass)) &&
+ !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..93bdbad98
--- /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 += [
+ '/security/certverifier',
+ '/security/manager/ssl',
+ '/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..dd9d31c28
--- /dev/null
+++ b/mailnews/mime/src/nsCMS.cpp
@@ -0,0 +1,1048 @@
+/* -*- 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"
+#include "mozilla/StaticMutex.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, 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,
+ int16_t aDigestType)
+{
+ if (!aDigestData || !aDigestDataLen)
+ return NS_ERROR_FAILURE;
+
+ return CommonVerifySignature(aDigestData, aDigestDataLen, aDigestType);
+}
+
+nsresult
+nsCMSMessage::CommonVerifySignature(unsigned char* aDigestData,
+ uint32_t aDigestDataLen,
+ int16_t aDigestType)
+{
+ 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) {
+ switch (NSS_CMSContentInfo_GetContentTypeTag(cinfo)) {
+ case SEC_OID_PKCS7_SIGNED_DATA:
+ sigd = reinterpret_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo));
+ break;
+
+ case SEC_OID_PKCS7_ENVELOPED_DATA:
+ case SEC_OID_PKCS7_ENCRYPTED_DATA:
+ case SEC_OID_PKCS7_DIGESTED_DATA:
+ default: {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("nsCMSMessage::CommonVerifySignature - unexpected ContentTypeTag\n"));
+ rv = NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
+ goto loser;
+ }
+ }
+ }
+
+ 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)
+ {
+ SECOidTag oidTag;
+ SECItem digest;
+ digest.data = aDigestData;
+ digest.len = aDigestDataLen;
+
+ if (NSS_CMSSignedData_HasDigests(sigd)) {
+ SECAlgorithmID **existingAlgs = NSS_CMSSignedData_GetDigestAlgs(sigd);
+ if (existingAlgs) {
+ while (*existingAlgs) {
+ SECAlgorithmID *alg = *existingAlgs;
+ SECOidTag algOIDTag = SECOID_FindOIDTag(&alg->algorithm);
+ NSS_CMSSignedData_SetDigestValue(sigd, algOIDTag, NULL);
+ ++existingAlgs;
+ }
+ }
+ }
+
+ if (!GetIntHashToOidHash(aDigestType, oidTag)) {
+ rv = NS_ERROR_CMS_VERIFY_BAD_DIGEST;
+ goto loser;
+ }
+
+ if (NSS_CMSSignedData_SetDigestValue(sigd, oidTag, &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, 0);
+}
+
+NS_IMETHODIMP nsCMSMessage::AsyncVerifyDetachedSignature(
+ nsISMimeVerificationListener *aListener,
+ unsigned char* aDigestData,
+ uint32_t aDigestDataLen,
+ int16_t aDigestType)
+{
+ if (!aDigestData || !aDigestDataLen)
+ return NS_ERROR_FAILURE;
+
+ return CommonAsyncVerifySignature(aListener, aDigestData, aDigestDataLen, aDigestType);
+}
+
+class SMimeVerificationTask final : public CryptoTask
+{
+public:
+ SMimeVerificationTask(nsICMSMessage *aMessage,
+ nsISMimeVerificationListener *aListener,
+ unsigned char *aDigestData,
+ uint32_t aDigestDataLen,
+ int16_t aDigestType)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mMessage = aMessage;
+ mListener = aListener;
+ mDigestData.Assign(reinterpret_cast<char *>(aDigestData), aDigestDataLen);
+ mDigestType = aDigestType;
+ }
+
+private:
+ virtual void ReleaseNSSResources() override {}
+ virtual nsresult CalculateResult() override
+ {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ mozilla::StaticMutexAutoLock lock(sMutex);
+ nsresult rv;
+ if (!mDigestData.IsEmpty()) {
+ rv = mMessage->VerifyDetachedSignature(
+ reinterpret_cast<uint8_t*>(const_cast<char *>(mDigestData.get())),
+ mDigestData.Length(), mDigestType);
+ } 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;
+ int16_t mDigestType;
+
+ static mozilla::StaticMutex sMutex;
+};
+
+mozilla::StaticMutex SMimeVerificationTask::sMutex;
+
+nsresult nsCMSMessage::CommonAsyncVerifySignature(nsISMimeVerificationListener *aListener,
+ unsigned char* aDigestData, uint32_t aDigestDataLen,
+ int16_t aDigestType)
+{
+ RefPtr<CryptoTask> task = new SMimeVerificationTask(this, aListener, aDigestData, aDigestDataLen, aDigestType);
+ 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;
+}
+
+bool nsCMSMessage::IsAllowedHash(const int16_t aCryptoHashInt)
+{
+ switch (aCryptoHashInt) {
+ case nsICryptoHash::SHA1:
+ case nsICryptoHash::SHA256:
+ case nsICryptoHash::SHA384:
+ case nsICryptoHash::SHA512:
+ return true;
+ case nsICryptoHash::MD2:
+ case nsICryptoHash::MD5:
+ default:
+ return false;
+ }
+}
+
+bool nsCMSMessage::GetIntHashToOidHash(const int16_t aCryptoHashInt, SECOidTag &aOidTag)
+{
+ bool rv = true;
+
+ switch (aCryptoHashInt) {
+ case nsICryptoHash::MD2:
+ aOidTag = SEC_OID_MD2;
+ break;
+ case nsICryptoHash::MD5:
+ aOidTag = SEC_OID_MD5;
+ break;
+ case nsICryptoHash::SHA1:
+ aOidTag = SEC_OID_SHA1;
+ break;
+ case nsICryptoHash::SHA256:
+ aOidTag = SEC_OID_SHA256;
+ break;
+ case nsICryptoHash::SHA384:
+ aOidTag = SEC_OID_SHA384;
+ break;
+ case nsICryptoHash::SHA512:
+ aOidTag = SEC_OID_SHA512;
+ break;
+ default:
+ rv = false;
+ }
+
+ 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;
+ if (!IsAllowedHash(aDigestType) ||
+ !GetIntHashToOidHash(aDigestType, digestType)) {
+ 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..393344277
--- /dev/null
+++ b/mailnews/mime/src/nsCMS.h
@@ -0,0 +1,108 @@
+/* -*- 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();
+ explicit 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,
+ int16_t aDigestType);
+
+ nsresult CommonAsyncVerifySignature(nsISMimeVerificationListener *aListener,
+ unsigned char* aDigestData, uint32_t aDigestDataLen,
+ int16_t aDigestType);
+ bool GetIntHashToOidHash(const int16_t aCryptoHashInt, SECOidTag &aOidTag);
+ bool IsAllowedHash(const int16_t aCryptoHashInt);
+
+ 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..ca608b54c
--- /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:
+ explicit 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_ */