summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/mime/src
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/mime/src')
-rw-r--r--comm/mailnews/mime/src/MimeHeaderParser.cpp232
-rw-r--r--comm/mailnews/mime/src/MimeJSComponents.jsm547
-rw-r--r--comm/mailnews/mime/src/comi18n.cpp49
-rw-r--r--comm/mailnews/mime/src/comi18n.h39
-rw-r--r--comm/mailnews/mime/src/components.conf87
-rw-r--r--comm/mailnews/mime/src/extraMimeParsers.jsm31
-rw-r--r--comm/mailnews/mime/src/jsmime.jsm72
-rw-r--r--comm/mailnews/mime/src/mime.def7
-rw-r--r--comm/mailnews/mime/src/mimeParser.jsm546
-rw-r--r--comm/mailnews/mime/src/mimeTextHTMLParsed.cpp171
-rw-r--r--comm/mailnews/mime/src/mimeTextHTMLParsed.h28
-rw-r--r--comm/mailnews/mime/src/mimebuf.cpp214
-rw-r--r--comm/mailnews/mime/src/mimebuf.h35
-rw-r--r--comm/mailnews/mime/src/mimecms.cpp772
-rw-r--r--comm/mailnews/mime/src/mimecms.h36
-rw-r--r--comm/mailnews/mime/src/mimecom.cpp53
-rw-r--r--comm/mailnews/mime/src/mimecom.h37
-rw-r--r--comm/mailnews/mime/src/mimecont.cpp209
-rw-r--r--comm/mailnews/mime/src/mimecont.h43
-rw-r--r--comm/mailnews/mime/src/mimecryp.cpp507
-rw-r--r--comm/mailnews/mime/src/mimecryp.h139
-rw-r--r--comm/mailnews/mime/src/mimecth.cpp33
-rw-r--r--comm/mailnews/mime/src/mimecth.h131
-rw-r--r--comm/mailnews/mime/src/mimedrft.cpp2135
-rw-r--r--comm/mailnews/mime/src/mimeebod.cpp441
-rw-r--r--comm/mailnews/mime/src/mimeebod.h37
-rw-r--r--comm/mailnews/mime/src/mimeenc.cpp999
-rw-r--r--comm/mailnews/mime/src/mimeeobj.cpp215
-rw-r--r--comm/mailnews/mime/src/mimeeobj.h50
-rw-r--r--comm/mailnews/mime/src/mimefilt.cpp349
-rw-r--r--comm/mailnews/mime/src/mimehdrs.cpp785
-rw-r--r--comm/mailnews/mime/src/mimehdrs.h85
-rw-r--r--comm/mailnews/mime/src/mimei.cpp1716
-rw-r--r--comm/mailnews/mime/src/mimei.h405
-rw-r--r--comm/mailnews/mime/src/mimeiimg.cpp220
-rw-r--r--comm/mailnews/mime/src/mimeiimg.h35
-rw-r--r--comm/mailnews/mime/src/mimeleaf.cpp187
-rw-r--r--comm/mailnews/mime/src/mimeleaf.h60
-rw-r--r--comm/mailnews/mime/src/mimemalt.cpp549
-rw-r--r--comm/mailnews/mime/src/mimemalt.h50
-rw-r--r--comm/mailnews/mime/src/mimemapl.cpp173
-rw-r--r--comm/mailnews/mime/src/mimemapl.h32
-rw-r--r--comm/mailnews/mime/src/mimemcms.cpp501
-rw-r--r--comm/mailnews/mime/src/mimemcms.h35
-rw-r--r--comm/mailnews/mime/src/mimemdig.cpp22
-rw-r--r--comm/mailnews/mime/src/mimemdig.h33
-rw-r--r--comm/mailnews/mime/src/mimemmix.cpp19
-rw-r--r--comm/mailnews/mime/src/mimemmix.h32
-rw-r--r--comm/mailnews/mime/src/mimemoz2.cpp1862
-rw-r--r--comm/mailnews/mime/src/mimemoz2.h207
-rw-r--r--comm/mailnews/mime/src/mimempar.cpp20
-rw-r--r--comm/mailnews/mime/src/mimempar.h32
-rw-r--r--comm/mailnews/mime/src/mimemrel.cpp1113
-rw-r--r--comm/mailnews/mime/src/mimemrel.h61
-rw-r--r--comm/mailnews/mime/src/mimemsg.cpp847
-rw-r--r--comm/mailnews/mime/src/mimemsg.h38
-rw-r--r--comm/mailnews/mime/src/mimemsig.cpp716
-rw-r--r--comm/mailnews/mime/src/mimemsig.h136
-rw-r--r--comm/mailnews/mime/src/mimemult.cpp676
-rw-r--r--comm/mailnews/mime/src/mimemult.h101
-rw-r--r--comm/mailnews/mime/src/mimeobj.cpp301
-rw-r--r--comm/mailnews/mime/src/mimeobj.h182
-rw-r--r--comm/mailnews/mime/src/mimepbuf.cpp251
-rw-r--r--comm/mailnews/mime/src/mimepbuf.h63
-rw-r--r--comm/mailnews/mime/src/mimesun.cpp313
-rw-r--r--comm/mailnews/mime/src/mimesun.h59
-rw-r--r--comm/mailnews/mime/src/mimetenr.cpp27
-rw-r--r--comm/mailnews/mime/src/mimetenr.h32
-rw-r--r--comm/mailnews/mime/src/mimetext.cpp442
-rw-r--r--comm/mailnews/mime/src/mimetext.h79
-rw-r--r--comm/mailnews/mime/src/mimethpl.cpp151
-rw-r--r--comm/mailnews/mime/src/mimethpl.h36
-rw-r--r--comm/mailnews/mime/src/mimethsa.cpp127
-rw-r--r--comm/mailnews/mime/src/mimethsa.h30
-rw-r--r--comm/mailnews/mime/src/mimethtm.cpp229
-rw-r--r--comm/mailnews/mime/src/mimethtm.h34
-rw-r--r--comm/mailnews/mime/src/mimetpfl.cpp613
-rw-r--r--comm/mailnews/mime/src/mimetpfl.h51
-rw-r--r--comm/mailnews/mime/src/mimetpla.cpp409
-rw-r--r--comm/mailnews/mime/src/mimetpla.h39
-rw-r--r--comm/mailnews/mime/src/mimetric.cpp356
-rw-r--r--comm/mailnews/mime/src/mimetric.h33
-rw-r--r--comm/mailnews/mime/src/mimeunty.cpp523
-rw-r--r--comm/mailnews/mime/src/mimeunty.h70
-rw-r--r--comm/mailnews/mime/src/modlmime.h388
-rw-r--r--comm/mailnews/mime/src/modmimee.h54
-rw-r--r--comm/mailnews/mime/src/moz.build88
-rw-r--r--comm/mailnews/mime/src/msgMime.manifest1
-rw-r--r--comm/mailnews/mime/src/nsMimeObjectClassAccess.cpp75
-rw-r--r--comm/mailnews/mime/src/nsMimeObjectClassAccess.h50
-rw-r--r--comm/mailnews/mime/src/nsMimeStringResources.h40
-rw-r--r--comm/mailnews/mime/src/nsSimpleMimeConverterStub.cpp191
-rw-r--r--comm/mailnews/mime/src/nsSimpleMimeConverterStub.h12
-rw-r--r--comm/mailnews/mime/src/nsStreamConverter.cpp981
-rw-r--r--comm/mailnews/mime/src/nsStreamConverter.h94
95 files changed, 25416 insertions, 0 deletions
diff --git a/comm/mailnews/mime/src/MimeHeaderParser.cpp b/comm/mailnews/mime/src/MimeHeaderParser.cpp
new file mode 100644
index 0000000000..cc489e2ec5
--- /dev/null
+++ b/comm/mailnews/mime/src/MimeHeaderParser.cpp
@@ -0,0 +1,232 @@
+/* -*- Mode: C++; tab-width: 2; 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/DebugOnly.h"
+#include "nsMemory.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgHeaderParser.h"
+#include "mozilla/Components.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(
+ components::HeaderParser::Service());
+
+ nsCOMPtr<msgIAddressObject> address;
+ headerParser->MakeMailboxObject(aName, aEmail, getter_AddRefs(address));
+ nsTArray<RefPtr<msgIAddressObject>> addresses;
+ addresses.AppendElement(address);
+ headerParser->MakeMimeHeader(addresses, full);
+}
+
+void MakeDisplayAddress(const nsAString& aName, const nsAString& aEmail,
+ nsAString& full) {
+ nsCOMPtr<nsIMsgHeaderParser> headerParser(
+ components::HeaderParser::Service());
+
+ 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(
+ components::HeaderParser::Service());
+
+ 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(
+ components::HeaderParser::Service());
+ NS_ENSURE_TRUE(headerParser, retval);
+ nsTArray<RefPtr<msgIAddressObject>> addresses;
+ nsresult rv = headerParser->ParseDecodedHeader(aHeader, false, addresses);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Javascript jsmime returned an error!");
+ if (NS_SUCCEEDED(rv) && addresses.Length() > 0) {
+ retval.SetCapacity(addresses.Length());
+ for (auto& addr : addresses) {
+ retval.AppendElement(addr);
+ }
+ }
+ return retval;
+}
+
+nsCOMArray<msgIAddressObject> EncodedHeader(const nsACString& aHeader,
+ const char* aCharset) {
+ nsCOMArray<msgIAddressObject> retval;
+ if (aHeader.IsEmpty()) {
+ return retval;
+ }
+ nsCOMPtr<nsIMsgHeaderParser> headerParser(
+ components::HeaderParser::Service());
+ NS_ENSURE_TRUE(headerParser, retval);
+ nsTArray<RefPtr<msgIAddressObject>> addresses;
+ nsresult rv =
+ headerParser->ParseEncodedHeader(aHeader, aCharset, false, addresses);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "This should never fail!");
+ if (NS_SUCCEEDED(rv) && addresses.Length() > 0) {
+ retval.SetCapacity(addresses.Length());
+ for (auto& addr : addresses) {
+ retval.AppendElement(addr);
+ }
+ }
+ return retval;
+}
+
+nsCOMArray<msgIAddressObject> EncodedHeaderW(const nsAString& aHeader) {
+ nsCOMArray<msgIAddressObject> retval;
+ if (aHeader.IsEmpty()) {
+ return retval;
+ }
+ nsCOMPtr<nsIMsgHeaderParser> headerParser(
+ components::HeaderParser::Service());
+ NS_ENSURE_TRUE(headerParser, retval);
+ nsTArray<RefPtr<msgIAddressObject>> addresses;
+ nsresult rv = headerParser->ParseEncodedHeaderW(aHeader, addresses);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "This should never fail!");
+ if (NS_SUCCEEDED(rv) && addresses.Length() > 0) {
+ retval.SetCapacity(addresses.Length());
+ for (auto& addr : addresses) {
+ retval.AppendElement(addr);
+ }
+ }
+ 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/comm/mailnews/mime/src/MimeJSComponents.jsm b/comm/mailnews/mime/src/MimeJSComponents.jsm
new file mode 100644
index 0000000000..35acba7c26
--- /dev/null
+++ b/comm/mailnews/mime/src/MimeJSComponents.jsm
@@ -0,0 +1,547 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var EXPORTED_SYMBOLS = [
+ "MimeHeaders",
+ "MimeWritableStructuredHeaders",
+ "MimeAddressParser",
+ "MimeConverter",
+];
+
+var { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm");
+var { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.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: ChromeUtils.generateQI(["nsIUTF8StringEnumerator"]),
+ [Symbol.iterator]() {
+ return this._iterator;
+ },
+ _setNext() {
+ if (this._next !== undefined) {
+ return;
+ }
+ this._next = this._iterator.next();
+ },
+ hasMore() {
+ this._setNext();
+ return !this._next.done;
+ },
+ getNext() {
+ this._setNext();
+ let result = this._next;
+ this._next = undefined;
+ if (result.done) {
+ throw Components.Exception("", Cr.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
+ );
+ }
+ 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(aHeaderName) {
+ let name = aHeaderName.toLowerCase();
+ return this._headers.get(name);
+ },
+
+ hasHeader(aHeaderName) {
+ return this._headers.has(aHeaderName.toLowerCase());
+ },
+
+ getUnstructuredHeader(aHeaderName) {
+ let result = this.getHeader(aHeaderName);
+ if (result === undefined || typeof result == "string") {
+ return result;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
+ },
+
+ getAddressingHeader(aHeaderName, aPreserveGroups) {
+ let addrs = this.getHeader(aHeaderName);
+ if (addrs === undefined) {
+ addrs = [];
+ } else if (!Array.isArray(addrs)) {
+ throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+ return fixArray(addrs, aPreserveGroups);
+ },
+
+ getRawHeader(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(sanitizeDate) {
+ if (this._headers.size == 0) {
+ return "";
+ }
+ let handler = new HeaderHandler();
+ let emitter = jsmime.headeremitter.makeStreamingEmitter(handler, {
+ useASCII: true,
+ sanitizeDate,
+ });
+ 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",
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIMimeHeaders",
+ "msgIStructuredHeaders",
+ ]),
+
+ initialize(allHeaders) {
+ this._headers = MimeParser.extractHeaders(allHeaders);
+ },
+
+ extractHeader(header, getAll) {
+ if (!this._headers) {
+ throw Components.Exception("", Cr.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");
+ }
+ return values[0];
+ },
+
+ get allHeaders() {
+ return this._headers.rawHeaderText;
+ },
+};
+
+function MimeWritableStructuredHeaders() {
+ this._headers = new Map();
+}
+MimeWritableStructuredHeaders.prototype = {
+ __proto__: MimeStructuredHeaders.prototype,
+ QueryInterface: ChromeUtils.generateQI([
+ "msgIStructuredHeaders",
+ "msgIWritableStructuredHeaders",
+ ]),
+
+ setHeader(aHeaderName, aValue) {
+ this._headers.set(aHeaderName.toLowerCase(), aValue);
+ },
+
+ deleteHeader(aHeaderName) {
+ this._headers.delete(aHeaderName.toLowerCase());
+ },
+
+ addAllHeaders(aHeaders) {
+ for (let header of aHeaders.headerNames) {
+ this.setHeader(header, aHeaders.getHeader(header));
+ }
+ },
+
+ setUnstructuredHeader(aHeaderName, aValue) {
+ this.setHeader(aHeaderName, aValue);
+ },
+
+ setAddressingHeader(aHeaderName, aAddresses) {
+ this.setHeader(aHeaderName, fixXpconnectAddresses(aAddresses));
+ },
+
+ setRawHeader(aHeaderName, aValue) {
+ 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.trim());
+ }
+ },
+};
+
+// These are prototypes for nsIMsgHeaderParser implementation
+var Mailbox = {
+ toString() {
+ return this.name ? this.name + " <" + this.email + ">" : this.email;
+ },
+};
+
+var EmailGroup = {
+ toString() {
+ 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") {
+ // eslint-disable-next-line no-control-regex
+ 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 = {
+ QueryInterface: ChromeUtils.generateQI(["nsIMsgHeaderParser"]),
+
+ parseEncodedHeader(aHeader, aCharset, aPreserveGroups) {
+ aHeader = aHeader || "";
+ let value = MimeParser.parseHeaderField(
+ aHeader,
+ MimeParser.HEADER_ADDRESS | MimeParser.HEADER_OPTION_ALL_I18N,
+ aCharset
+ );
+ return fixArray(value, aPreserveGroups);
+ },
+ parseEncodedHeaderW(aHeader) {
+ 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);
+ },
+ parseDecodedHeader(aHeader, aPreserveGroups) {
+ aHeader = aHeader || "";
+ let value = MimeParser.parseHeaderField(aHeader, MimeParser.HEADER_ADDRESS);
+ return fixArray(value, aPreserveGroups);
+ },
+
+ makeMimeHeader(addresses) {
+ 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(aHeader) {
+ let addresses = this.parseDecodedHeader(aHeader, false);
+ return addresses.length > 0 ? addresses[0].name || addresses[0].email : "";
+ },
+
+ removeDuplicateAddresses(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(aName, aEmail) {
+ let object = Object.create(Mailbox);
+ object.name = aName;
+ object.email = aEmail ? aEmail.trim() : aEmail;
+ return object;
+ },
+
+ makeGroupObject(aName, aMembers) {
+ let object = Object.create(EmailGroup);
+ object.name = aName;
+ object.group = aMembers;
+ return object;
+ },
+
+ makeFromDisplayAddress(aDisplay) {
+ if (aDisplay.includes(";") && !/:.*;/.test(aDisplay)) {
+ // Using semicolons as mailbox separators in against the standard, but
+ // used in the wild by some clients.
+ // Looks like this isn't using group syntax, so let's assume it's a
+ // non-standards compliant input string, and fix it.
+ // Replace semicolons with commas, unless the semicolon is inside a quote.
+ // The regexp uses tricky lookahead, see bug 1059988 comment #70 for details.
+ aDisplay = aDisplay.replace(/;(?=(?:(?:[^"]*"){2})*[^"]*$)/g, ",");
+ }
+
+ // The basic idea is to split on every comma, so long as there is a
+ // preceding @ or <> pair.
+ let output = [];
+ while (aDisplay.length > 0) {
+ let lt = aDisplay.indexOf("<");
+ let gt = aDisplay.indexOf(">");
+ let at = aDisplay.indexOf("@");
+ let start = 0;
+ // An address doesn't always contain both <> and @, the goal is to find
+ // the first comma after <> or @.
+ if (lt != -1 && gt > lt) {
+ start = gt;
+ }
+ if (at != -1) {
+ start = Math.min(start, at);
+ }
+ let comma = aDisplay.indexOf(",", start);
+ let addr;
+ if (comma > 0) {
+ addr = aDisplay.substr(0, comma);
+ aDisplay = aDisplay.substr(comma + 1);
+
+ // Make sure we don't have any "empty addresses" (multiple commas).
+ comma = 0;
+ while (/[,\s]/.test(aDisplay.charAt(comma))) {
+ comma++;
+ }
+ aDisplay = aDisplay.substr(comma);
+ } else {
+ addr = aDisplay;
+ aDisplay = "";
+ }
+ addr = addr.trimLeft();
+ if (addr) {
+ output.push(this._makeSingleAddress(addr));
+ }
+ }
+ return output;
+ },
+
+ /**
+ * Construct a single email address from an |name <local@domain>| token.
+ *
+ * @param {string} aInput - a string to be parsed to a mailbox object.
+ * @returns {msgIAddressObject} the mailbox parsed from the input.
+ */
+ _makeSingleAddress(aInput) {
+ // If the whole string is within quotes, unquote it first.
+ aInput = aInput.trim().replace(/^"(.*)"$/, "$1");
+
+ if (/<.*>/.test(aInput)) {
+ // We don't want to look for the address within quotes, so first remove
+ // all quoted strings containing angle chars.
+ let cleanedInput = aInput.replace(/".*[<>]+.*"/g, "");
+
+ // Extract the address from within the quotes.
+ let addrMatch = cleanedInput.match(/<([^><]*)>/);
+
+ let addr = addrMatch ? addrMatch[1] : "";
+ let addrIdx = aInput.indexOf("<" + addr + ">");
+ return this.makeMailboxObject(aInput.slice(0, addrIdx).trim(), addr);
+ }
+ return this.makeMailboxObject("", aInput);
+ },
+
+ extractHeaderAddressMailboxes(aLine) {
+ return this.parseDecodedHeader(aLine)
+ .map(addr => addr.email)
+ .join(", ");
+ },
+
+ makeMimeAddress(aName, aEmail) {
+ let object = this.makeMailboxObject(aName, aEmail);
+ return this.makeMimeHeader([object]);
+ },
+};
+
+function MimeConverter() {}
+MimeConverter.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIMimeConverter"]),
+
+ encodeMimePartIIStr_UTF8(aHeader, aStructured, aFieldNameLen, aLineLength) {
+ // 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(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(...aArgs) {
+ return this.decodeMimeHeader(...aArgs);
+ },
+};
diff --git a/comm/mailnews/mime/src/comi18n.cpp b/comm/mailnews/mime/src/comi18n.cpp
new file mode 100644
index 0000000000..90bfd17abf
--- /dev/null
+++ b/comm/mailnews/mime/src/comi18n.cpp
@@ -0,0 +1,49 @@
+/* -*- 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 "comi18n.h"
+#include "nsMsgUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMimeConverter.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/EncodingDetector.h"
+
+using namespace mozilla;
+
+////////////////////////////////////////////////////////////////////////////////
+// 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("@mozilla.org/messenger/mimeconverter;1", &rv);
+ if (NS_FAILED(rv)) {
+ result.Truncate();
+ return;
+ }
+ mimeConverter->DecodeMimeHeaderToUTF8(nsDependentCString(header),
+ default_charset, override_charset,
+ eatContinuations, result);
+}
+
+nsresult MIME_detect_charset(const char* aBuf, int32_t aLength,
+ nsACString& aCharset) {
+ mozilla::UniquePtr<mozilla::EncodingDetector> detector =
+ mozilla::EncodingDetector::Create();
+ mozilla::Span<const uint8_t> src =
+ mozilla::AsBytes(mozilla::Span(aBuf, aLength));
+ Unused << detector->Feed(src, true);
+ auto encoding = detector->Guess(nullptr, true);
+ encoding->Name(aCharset);
+ return NS_OK;
+}
+
+} /* end of extern "C" */
+// END PUBLIC INTERFACE
diff --git a/comm/mailnews/mime/src/comi18n.h b/comm/mailnews/mime/src/comi18n.h
new file mode 100644
index 0000000000..0c9f7cbaf7
--- /dev/null
+++ b/comm/mailnews/mime/src/comi18n.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/. */
+#ifndef _COMI18N_LOADED_H_
+#define _COMI18N_LOADED_H_
+
+#include "msgCore.h"
+#include "mozilla/Encoding.h"
+
+#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,
+ nsACString& aCharset);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif // _COMI18N_LOADED_H_
diff --git a/comm/mailnews/mime/src/components.conf b/comm/mailnews/mime/src/components.conf
new file mode 100644
index 0000000000..cfd19671ca
--- /dev/null
+++ b/comm/mailnews/mime/src/components.conf
@@ -0,0 +1,87 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# 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/.
+
+Classes = [
+ {
+ "cid": "{d1258011-f391-44fd-992e-c6f4b461a42f}",
+ "contract_ids": ["@mozilla.org/messenger/mimeheaders;1"],
+ "jsm": "resource:///modules/MimeJSComponents.jsm",
+ "constructor": "MimeHeaders",
+ },
+ {
+ "cid": "{c560806a-425f-4f0f-bf69-397c58c599a7}",
+ "contract_ids": ["@mozilla.org/messenger/structuredheaders;1"],
+ "jsm": "resource:///modules/MimeJSComponents.jsm",
+ "constructor": "MimeWritableStructuredHeaders",
+ },
+ {
+ "cid": "{96bd8769-2d0e-4440-963d-22b97fb3ba77}",
+ "contract_ids": ["@mozilla.org/messenger/headerparser;1"],
+ "jsm": "resource:///modules/MimeJSComponents.jsm",
+ "constructor": "MimeAddressParser",
+ "name": "HeaderParser",
+ "interfaces": ["nsIMsgHeaderParser"],
+ },
+ {
+ "cid": "{93f8c049-80ed-4dda-9000-94ad8daba44c}",
+ "contract_ids": ["@mozilla.org/messenger/mimeconverter;1"],
+ "jsm": "resource:///modules/MimeJSComponents.jsm",
+ "constructor": "MimeConverter",
+ "name": "MimeConverter",
+ "interfaces": ["nsIMimeConverter"],
+ },
+ {
+ "cid": "{403b0540-b7c3-11d2-b35e-525400e2d63a}",
+ "contract_ids": ["@mozilla.org/messenger/mimeobject;1"],
+ "type": "nsMimeObjectClassAccess",
+ "headers": ["/comm/mailnews/mime/src/nsMimeObjectClassAccess.h"],
+ },
+ {
+ "cid": "{faf4f9a6-60ad-11d3-989a-001083010e9b}",
+ "contract_ids": [
+ "@mozilla.org/streamconv;1?from=message/rfc822&to=application/xhtml+xml",
+ "@mozilla.org/streamconv;1?from=message/rfc822&to=text/html",
+ "@mozilla.org/streamconv;1?from=message/rfc822&to=*/*",
+ ],
+ "type": "nsStreamConverter",
+ "headers": ["/comm/mailnews/mime/src/nsStreamConverter.h"],
+ },
+ {
+ "cid": "{f0a8af16-dcce-11d2-a411-00805f613c79}",
+ "contract_ids": ["@mozilla.org/messenger/mimeemitter;1?type=text/html"],
+ "type": "nsMimeHtmlDisplayEmitter",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/mime/emitters/nsMimeHtmlEmitter.h"],
+ "categories": {
+ "mime-emitter": "@mozilla.org/messenger/mimeemitter;1?type=text/html"
+ },
+ },
+ {
+ "cid": "{977e418f-e392-11d2-a2ac-00a024a7d144}",
+ "contract_ids": ["@mozilla.org/messenger/mimeemitter;1?type=text/xml"],
+ "type": "nsMimeXmlEmitter",
+ "headers": ["/comm/mailnews/mime/emitters/nsMimeXmlEmitter.h"],
+ "categories": {
+ "mime-emitter": "@mozilla.org/messenger/mimeemitter;1?type=text/xml"
+ },
+ },
+ {
+ "cid": "{e8892265-7653-46c5-a290-307f3404d0f3}",
+ "contract_ids": ["@mozilla.org/messenger/mimeemitter;1?type=text/plain"],
+ "type": "nsMimePlainEmitter",
+ "headers": ["/comm/mailnews/mime/emitters/nsMimePlainEmitter.h"],
+ "categories": {
+ "mime-emitter": "@mozilla.org/messenger/mimeemitter;1?type=text/plain"
+ },
+ },
+ {
+ "cid": "{f0a8af16-dcff-11d2-a411-00805f613c79}",
+ "contract_ids": ["@mozilla.org/messenger/mimeemitter;1?type=raw"],
+ "type": "nsMimeRawEmitter",
+ "headers": ["/comm/mailnews/mime/emitters/nsMimeRawEmitter.h"],
+ "categories": {"mime-emitter": "@mozilla.org/messenger/mimeemitter;1?type=raw"},
+ },
+]
diff --git a/comm/mailnews/mime/src/extraMimeParsers.jsm b/comm/mailnews/mime/src/extraMimeParsers.jsm
new file mode 100644
index 0000000000..b20bd543a1
--- /dev/null
+++ b/comm/mailnews/mime/src/extraMimeParsers.jsm
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* globals jsmime */
+
+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/comm/mailnews/mime/src/jsmime.jsm b/comm/mailnews/mime/src/jsmime.jsm
new file mode 100644
index 0000000000..407c2aacd1
--- /dev/null
+++ b/comm/mailnews/mime/src/jsmime.jsm
@@ -0,0 +1,72 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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:
+
+/**
+ * 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"];
+
+function bytesToString(buffer) {
+ var string = "";
+ for (var i = 0; i < buffer.length; i++) {
+ string += String.fromCharCode(buffer[i]);
+ }
+ return string;
+}
+
+// Our UTF-7 decoder.
+function UTF7TextDecoder(options = {}, manager) {
+ this.manager = manager;
+ this.collectInput = "";
+}
+UTF7TextDecoder.prototype = {
+ // Since the constructor checked, this will only be called for UTF-7.
+ decode(input, options = {}) {
+ let more = "stream" in options ? options.stream : false;
+ // There are cases where this is called without input.
+ if (!input) {
+ return "";
+ }
+ this.collectInput += bytesToString(input);
+ if (more) {
+ return "";
+ }
+ return this.manager.utf7ToUnicode(this.collectInput);
+ },
+};
+
+/* exported MimeTextDecoder */
+function MimeTextDecoder(charset, options) {
+ let manager = Cc["@mozilla.org/charset-converter-manager;1"].createInstance(
+ Ci.nsICharsetConverterManager
+ );
+ // The following will throw if the charset is unknown.
+ let newCharset = manager.getCharsetAlias(charset);
+ if (newCharset.toLowerCase() == "utf-7") {
+ return new UTF7TextDecoder(options, manager);
+ }
+ return new TextDecoder(newCharset, options);
+}
+
+// The following code loads custom MIME encoders.
+var CATEGORY_NAME = "custom-mime-encoder";
+Services.obs.addObserver(function (subject, topic, data) {
+ subject = subject.QueryInterface(Ci.nsISupportsCString).data;
+ if (data == CATEGORY_NAME) {
+ let url = Services.catMan.getCategoryEntry(CATEGORY_NAME, subject);
+ Services.scriptloader.loadSubScript(url, {}, "UTF-8");
+ }
+}, "xpcom-category-entry-added");
+
+for (let { data } of Services.catMan.enumerateCategory(CATEGORY_NAME)) {
+ let url = Services.catMan.getCategoryEntry(CATEGORY_NAME, data);
+ Services.scriptloader.loadSubScript(url, {}, "UTF-8");
+}
diff --git a/comm/mailnews/mime/src/mime.def b/comm/mailnews/mime/src/mime.def
new file mode 100644
index 0000000000..6b1c36bf9e
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimeParser.jsm b/comm/mailnews/mime/src/mimeParser.jsm
new file mode 100644
index 0000000000..95256ba41c
--- /dev/null
+++ b/comm/mailnews/mime/src/mimeParser.jsm
@@ -0,0 +1,546 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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:
+
+var EXPORTED_SYMBOLS = ["MimeParser"];
+
+var { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm");
+var { MailStringUtils } = ChromeUtils.import(
+ "resource:///modules/MailStringUtils.jsm"
+);
+
+// Emitter helpers, for internal functions later on.
+var ExtractMimeMsgEmitter = {
+ getAttachmentName(part) {
+ if (!part || !part.hasOwnProperty("headers")) {
+ return "";
+ }
+
+ if (part.headers.hasOwnProperty("content-disposition")) {
+ let filename = MimeParser.getParameter(
+ part.headers["content-disposition"][0],
+ "filename"
+ );
+ if (filename) {
+ return filename;
+ }
+ }
+
+ if (part.headers.hasOwnProperty("content-type")) {
+ let name = MimeParser.getParameter(
+ part.headers["content-type"][0],
+ "name"
+ );
+ if (name) {
+ return name;
+ }
+ }
+
+ return "";
+ },
+
+ // All parts of content-disposition = "attachment" are returned as attachments.
+ // For content-disposition = "inline", all parts except those with content-type
+ // text/plain, text/html and text/enriched are returned as attachments.
+ isAttachment(part) {
+ if (!part) {
+ return false;
+ }
+
+ let contentType = part.contentType || "text/plain";
+ if (contentType.search(/^multipart\//i) === 0) {
+ return false;
+ }
+
+ let contentDisposition = "";
+ if (
+ Array.isArray(part.headers["content-disposition"]) &&
+ part.headers["content-disposition"].length > 0
+ ) {
+ contentDisposition = part.headers["content-disposition"][0];
+ }
+
+ if (
+ contentDisposition.search(/^attachment/i) === 0 ||
+ contentType.search(/^text\/plain|^text\/html|^text\/enriched/i) === -1
+ ) {
+ return true;
+ }
+
+ return false;
+ },
+
+ /** JSMime API */
+ startMessage() {
+ this.mimeTree = {
+ partName: "",
+ contentType: "message/rfc822",
+ parts: [],
+ size: 0,
+ headers: {},
+ attachments: [],
+ // No support for encryption.
+ isEncrypted: false,
+ };
+ // partsPath is a hierarchical stack of parts from the root to the
+ // current part.
+ this.partsPath = [this.mimeTree];
+ this.options = this.options || {};
+ },
+
+ endMessage() {
+ // Prepare the mimeMsg object, which is the final output of the emitter.
+ this.mimeMsg = null;
+ if (this.mimeTree.parts.length == 0) {
+ return;
+ }
+
+ // Check if only a specific mime part has been requested.
+ if (this.options.getMimePart) {
+ if (this.mimeTree.parts[0].partName == this.options.getMimePart) {
+ this.mimeMsg = this.mimeTree.parts[0];
+ }
+ return;
+ }
+
+ this.mimeTree.attachments.sort((a, b) => a.partName > b.partName);
+ this.mimeMsg = this.mimeTree;
+ },
+
+ startPart(partNum, headerMap) {
+ let contentType = headerMap.contentType?.type
+ ? headerMap.contentType.type
+ : "text/plain";
+
+ let headers = {};
+ for (let [headerName, headerValue] of headerMap._rawHeaders) {
+ // MsgHdrToMimeMessage always returns an array, even for single values.
+ let valueArray = Array.isArray(headerValue) ? headerValue : [headerValue];
+ // Return a binary string, to mimic MsgHdrToMimeMessage.
+ headers[headerName] = valueArray.map(value => {
+ return MailStringUtils.stringToByteString(value);
+ });
+ }
+
+ // Get the most recent part from the hierarchical parts stack, which is the
+ // parent of the new part to by added.
+ let parentPart = this.partsPath[this.partsPath.length - 1];
+
+ // Add a leading 1 to the partNum and convert the "$" sub-message deliminator.
+ let partName = "1" + (partNum ? "." : "") + partNum.replaceAll("$", ".1");
+
+ // MsgHdrToMimeMessage differentiates between the message headers and the
+ // headers of the first part. jsmime.js however returns all headers of
+ // the message in the first multipart/* part: Merge all headers into the
+ // parent part and only keep content-* headers.
+ if (parentPart.contentType.startsWith("message/")) {
+ for (let [k, v] of Object.entries(headers)) {
+ if (!parentPart.headers[k]) {
+ parentPart.headers[k] = v;
+ }
+ }
+ headers = Object.fromEntries(
+ Object.entries(headers).filter(h => h[0].startsWith("content-"))
+ );
+ }
+
+ // Add default content-type header.
+ if (!headers.hasOwnProperty("content-type")) {
+ headers["content-type"] = ["text/plain"];
+ }
+
+ let newPart = {
+ partName,
+ body: "",
+ headers,
+ contentType,
+ size: 0,
+ parts: [],
+ // No support for encryption.
+ isEncrypted: false,
+ };
+
+ // Add nested new part.
+ parentPart.parts.push(newPart);
+ // Push the newly added part into the hierarchical parts stack.
+ this.partsPath.push(newPart);
+ },
+
+ endPart(partNum) {
+ let deleteBody = false;
+ // Get the most recent part from the hierarchical parts stack.
+ let currentPart = this.partsPath[this.partsPath.length - 1];
+
+ // Add size.
+ let size = currentPart.body.length;
+ currentPart.size += size;
+ let partSize = currentPart.size;
+
+ if (this.isAttachment(currentPart)) {
+ currentPart.name = this.getAttachmentName(currentPart);
+ this.mimeTree.attachments.push({ ...currentPart });
+ deleteBody = !this.options.getMimePart;
+ }
+
+ if (deleteBody || currentPart.body == "") {
+ delete currentPart.body;
+ }
+
+ // Remove content-disposition and content-transfer-encoding headers.
+ currentPart.headers = Object.fromEntries(
+ Object.entries(currentPart.headers).filter(
+ h =>
+ !["content-disposition", "content-transfer-encoding"].includes(h[0])
+ )
+ );
+
+ // Set the parent of this part to be the new current part.
+ this.partsPath.pop();
+
+ // Add the size of this part to its parent as well.
+ currentPart = this.partsPath[this.partsPath.length - 1];
+ currentPart.size += partSize;
+ },
+
+ /**
+ * The data parameter is either a string or a Uint8Array.
+ */
+ deliverPartData(partNum, data) {
+ // Get the most recent part from the hierarchical parts stack.
+ let currentPart = this.partsPath[this.partsPath.length - 1];
+
+ if (typeof data === "string") {
+ currentPart.body += data;
+ } else {
+ currentPart.body += MailStringUtils.uint8ArrayToByteString(data);
+ }
+ },
+};
+
+var ExtractHeadersEmitter = {
+ startPart(partNum, headers) {
+ if (partNum == "") {
+ this.headers = headers;
+ }
+ },
+};
+
+var ExtractHeadersAndBodyEmitter = {
+ body: "",
+ startPart: ExtractHeadersEmitter.startPart,
+ deliverPartData(partNum, data) {
+ if (partNum == "") {
+ this.body += data;
+ }
+ },
+};
+
+// Sets appropriate default options for chrome-privileged environments
+function setDefaultParserOptions(opts) {
+ if (!("onerror" in opts)) {
+ opts.onerror = Cu.reportError;
+ }
+}
+
+var MimeParser = {
+ /***
+ * Determine an arbitrary "parameter" part of a mail header.
+ *
+ * @param {string} headerStr - The string containing all parts of the header.
+ * @param {string} parameter - The parameter we are looking for.
+ *
+ *
+ * 'multipart/signed; protocol="xyz"', 'protocol' --> returns "xyz"
+ *
+ * @return {string} String containing the value of the parameter; or "".
+ */
+
+ getParameter(headerStr, parameter) {
+ parameter = parameter.toLowerCase();
+ headerStr = headerStr.replace(/[\r\n]+[ \t]+/g, "");
+
+ let hdrMap = jsmime.headerparser.parseParameterHeader(
+ ";" + headerStr,
+ true,
+ true
+ );
+
+ for (let [key, value] of hdrMap.entries()) {
+ if (parameter == key.toLowerCase()) {
+ return value;
+ }
+ }
+
+ return "";
+ },
+
+ /**
+ * 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(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, 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);
+ },
+
+ /**
+ * 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(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();
+ },
+
+ /**
+ * 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(emitter, opts) {
+ var StreamListener = {
+ onStartRequest(aRequest) {
+ try {
+ if ("onStartRequest" in emitter) {
+ emitter.onStartRequest(aRequest);
+ }
+ } finally {
+ this._parser.resetParser();
+ }
+ },
+ onStopRequest(aRequest, aStatus) {
+ this._parser.deliverEOF();
+ if ("onStopRequest" in emitter) {
+ emitter.onStopRequest(aRequest, aStatus);
+ }
+ },
+ onDataAvailable(aRequest, 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: ChromeUtils.generateQI([
+ "nsIStreamListener",
+ "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(emitter, opts) {
+ setDefaultParserOptions(opts);
+ return new jsmime.MimeParser(emitter, opts);
+ },
+
+ /**
+ * Returns a mimeMsg object for the given input. The returned object tries to
+ * be compatible with the return value of MsgHdrToMimeMessage. Differences:
+ * - no support for encryption
+ * - returned attachments include the body and not the URL
+ * - returned attachments match either allInlineAttachments or
+ * allUserAttachments (decodeSubMessages = false)
+ * - does not eat TABs in headers, if they follow a CRLF
+ *
+ * The input is any type of input that would be accepted by parseSync.
+ *
+ * @param input A string of text to parse.
+ */
+ extractMimeMsg(input, options) {
+ var emitter = Object.create(ExtractMimeMsgEmitter);
+ // Set default options.
+ emitter.options = {
+ getMimePart: "",
+ decodeSubMessages: true,
+ };
+ // Override default options.
+ for (let option of Object.keys(options)) {
+ emitter.options[option] = options[option];
+ }
+
+ MimeParser.parseSync(input, emitter, {
+ // jsmime does not use the "1." prefix for the partName.
+ // jsmime uses "$." as sub-message deliminator.
+ pruneat: emitter.options.getMimePart
+ .split(".")
+ .slice(1)
+ .join(".")
+ .replaceAll(".1.", "$."),
+ decodeSubMessages: emitter.options.decodeSubMessages,
+ bodyformat: "decode",
+ stripcontinuations: true,
+ strformat: "unicode",
+ });
+ return emitter.mimeMsg;
+ },
+
+ /**
+ * 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(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(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(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 new Error("Illegal type of header field");
+ }
+ },
+};
diff --git a/comm/mailnews/mime/src/mimeTextHTMLParsed.cpp b/comm/mailnews/mime/src/mimeTextHTMLParsed.cpp
new file mode 100644
index 0000000000..225b63f1ba
--- /dev/null
+++ b/comm/mailnews/mime/src/mimeTextHTMLParsed.cpp
@@ -0,0 +1,171 @@
+/* -*- 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/. */
+
+/* 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 "nsContentUtils.h"
+#include "mozilla/dom/DOMParser.h"
+#include "mozilla/dom/Document.h"
+#include "nsGenericHTMLElement.h"
+#include "mozilla/Preferences.h"
+#include "nsIParserUtils.h"
+#include "nsIDocumentEncoder.h"
+#include "mozilla/ErrorResult.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.
+ mozilla::ErrorResult rv2;
+ RefPtr<mozilla::dom::DOMParser> parser =
+ mozilla::dom::DOMParser::CreateWithoutGlobal(rv2);
+ nsCOMPtr<mozilla::dom::Document> document = parser->ParseFromString(
+ rawHTML, mozilla::dom::SupportedType::Text_html, rv2);
+ if (rv2.Failed()) return -1;
+
+ // Remove meta http-equiv="refresh".
+ RefPtr<nsContentList> metas = document->GetElementsByTagName(u"meta"_ns);
+ uint32_t length = metas->Length(true);
+ for (uint32_t i = length; i > 0; i--) {
+ RefPtr<nsGenericHTMLElement> node =
+ nsGenericHTMLElement::FromNodeOrNull(metas->Item(i - 1));
+ nsAutoString header;
+ node->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header);
+ nsContentUtils::ASCIIToLower(header);
+ if (nsGkAtoms::refresh->Equals(header)) {
+ node->Remove();
+ }
+ }
+
+ // Serialize it back to HTML source again.
+ nsCOMPtr<nsIDocumentEncoder> encoder = do_createDocumentEncoder("text/html");
+ NS_ENSURE_TRUE(encoder, -1);
+ uint32_t aFlags = nsIDocumentEncoder::OutputRaw |
+ nsIDocumentEncoder::OutputDisallowLineBreaking;
+ rv = encoder->Init(document, u"text/html"_ns, aFlags);
+ NS_ENSURE_SUCCESS(rv, -1);
+ rv = encoder->EncodeToString(parsed);
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ bool stripConditionalCSS = mozilla::Preferences::GetBool(
+ "mail.html_sanitize.drop_conditional_css", true);
+
+ nsCString resultCStr;
+ if (stripConditionalCSS) {
+ nsString cssCondStripped;
+ nsCOMPtr<nsIParserUtils> parserUtils =
+ do_GetService(NS_PARSERUTILS_CONTRACTID);
+ parserUtils->RemoveConditionalCSS(parsed, cssCondStripped);
+ parsed.Truncate();
+ resultCStr = NS_ConvertUTF16toUTF8(cssCondStripped);
+ } else {
+ resultCStr = NS_ConvertUTF16toUTF8(parsed);
+ }
+
+ // Write it out.
+
+ // XXX: adding the doc source resultCStr to what we have here is not nice:
+ // We already have the stuff up to and including <body> written.
+ // So we are dumping <head> content into <body>. Tagsoup ohoy!
+
+ 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/comm/mailnews/mime/src/mimeTextHTMLParsed.h b/comm/mailnews/mime/src/mimeTextHTMLParsed.h
new file mode 100644
index 0000000000..99a84d0dd8
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimebuf.cpp b/comm/mailnews/mime/src/mimebuf.cpp
new file mode 100644
index 0000000000..d1db4d67d5
--- /dev/null
+++ b/comm/mailnews/mime/src/mimebuf.cpp
@@ -0,0 +1,214 @@
+/* -*- 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/.
+ * 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 && (*buffer_fpP < (uint32_t)*buffer_sizeP) &&
+ (*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. */
+ 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/comm/mailnews/mime/src/mimebuf.h b/comm/mailnews/mime/src/mimebuf.h
new file mode 100644
index 0000000000..3cd52fac2c
--- /dev/null
+++ b/comm/mailnews/mime/src/mimebuf.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/.
+ * 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/comm/mailnews/mime/src/mimecms.cpp b/comm/mailnews/mime/src/mimecms.cpp
new file mode 100644
index 0000000000..e9d2d33ae5
--- /dev/null
+++ b/comm/mailnews/mime/src/mimecms.cpp
@@ -0,0 +1,772 @@
+/* -*- 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 "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 "nsIMsgSMIMEHeaderSink.h"
+#include "nsCOMPtr.h"
+#include "nsIX509Cert.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "nsIMailChannel.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,
+ const char* aMsgDate,
+ nsIMsgSMIMEHeaderSink* aHeaderSink,
+ int32_t aMimeNestingLevel,
+ const nsCString& aMsgNeckoURL,
+ const nsCString& aOriginMimePartNumber);
+
+ 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 mOriginMimePartNumber;
+
+ nsCString mFromAddr;
+ nsCString mFromName;
+ nsCString mSenderAddr;
+ nsCString mSenderName;
+ nsCString mMsgDate;
+};
+
+class SignedStatusRunnable : public mozilla::Runnable {
+ public:
+ SignedStatusRunnable(
+ const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink>& aSink,
+ int32_t aNestingLevel, int32_t aSignatureStatus, nsIX509Cert* aSignerCert,
+ const nsCString& aMsgNeckoURL, const nsCString& aOriginMimePartNumber);
+ NS_DECL_NSIRUNNABLE
+ nsresult mResult;
+
+ protected:
+ nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink> m_sink;
+ int32_t m_nestingLevel;
+ int32_t m_signatureStatus;
+ nsCOMPtr<nsIX509Cert> m_signerCert;
+ nsCString m_msgNeckoURL;
+ nsCString m_originMimePartNumber;
+};
+
+SignedStatusRunnable::SignedStatusRunnable(
+ const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink>& aSink,
+ int32_t aNestingLevel, int32_t aSignatureStatus, nsIX509Cert* aSignerCert,
+ const nsCString& aMsgNeckoURL, const nsCString& aOriginMimePartNumber)
+ : mozilla::Runnable("SignedStatusRunnable"),
+ mResult(NS_ERROR_UNEXPECTED),
+ m_sink(aSink),
+ m_nestingLevel(aNestingLevel),
+ m_signatureStatus(aSignatureStatus),
+ m_signerCert(aSignerCert),
+ m_msgNeckoURL(aMsgNeckoURL),
+ m_originMimePartNumber(aOriginMimePartNumber) {}
+
+NS_IMETHODIMP SignedStatusRunnable::Run() {
+ mResult =
+ m_sink->SignedStatus(m_nestingLevel, m_signatureStatus, m_signerCert,
+ m_msgNeckoURL, m_originMimePartNumber);
+ return NS_OK;
+}
+
+nsresult ProxySignedStatus(
+ const nsMainThreadPtrHandle<nsIMsgSMIMEHeaderSink>& aSink,
+ int32_t aNestingLevel, int32_t aSignatureStatus, nsIX509Cert* aSignerCert,
+ const nsCString& aMsgNeckoURL, const nsCString& aOriginMimePartNumber) {
+ RefPtr<SignedStatusRunnable> signedStatus = new SignedStatusRunnable(
+ aSink, aNestingLevel, aSignatureStatus, aSignerCert, aMsgNeckoURL,
+ aOriginMimePartNumber);
+ nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete(
+ "ProxySignedStatus"_ns, mozilla::GetMainThreadSerialEventTarget(),
+ do_AddRef(signedStatus));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return signedStatus->mResult;
+}
+
+NS_IMPL_ISUPPORTS(nsSMimeVerificationListener, nsISMimeVerificationListener)
+
+nsSMimeVerificationListener::nsSMimeVerificationListener(
+ const char* aFromAddr, const char* aFromName, const char* aSenderAddr,
+ const char* aSenderName, const char* aMsgDate,
+ nsIMsgSMIMEHeaderSink* aHeaderSink, int32_t aMimeNestingLevel,
+ const nsCString& aMsgNeckoURL, const nsCString& aOriginMimePartNumber)
+ : mMsgNeckoURL(aMsgNeckoURL), mOriginMimePartNumber(aOriginMimePartNumber) {
+ mHeaderSink = new nsMainThreadPtrHolder<nsIMsgSMIMEHeaderSink>(
+ "nsSMimeVerificationListener::mHeaderSink", aHeaderSink);
+ mSinkIsNull = !aHeaderSink;
+ mMimeNestingLevel = aMimeNestingLevel;
+
+ mFromAddr = aFromAddr;
+ mFromName = aFromName;
+ mSenderAddr = aSenderAddr;
+ mSenderName = aSenderName;
+ mMsgDate = aMsgDate;
+}
+
+NS_IMETHODIMP nsSMimeVerificationListener::Notify(
+ nsICMSMessage* 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<nsIX509Cert> signerCert;
+ aVerifiedMessage->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(
+ aVerifiedMessage, 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 {
+ PRTime sigTime;
+ if (NS_FAILED(aVerifiedMessage->GetSigningTime(&sigTime))) {
+ // Signing time attribute is optional in CMS messages.
+ signature_status = nsICMSMessageErrors::SUCCESS;
+ } else {
+ // If it's present, check for a rough match with the message date.
+ PRTime msgTime;
+ if (PR_ParseTimeString(mMsgDate.get(), false, &msgTime) != PR_SUCCESS) {
+ signature_status = nsICMSMessageErrors::VERIFY_TIME_MISMATCH;
+ } else {
+ PRTime delta;
+
+ if (sigTime > msgTime) {
+ delta = sigTime - msgTime;
+ } else {
+ delta = msgTime - sigTime;
+ }
+
+ if (delta / PR_USEC_PER_SEC > 60 * 60 * 1) {
+ signature_status = nsICMSMessageErrors::VERIFY_TIME_MISMATCH;
+ } else {
+ signature_status = nsICMSMessageErrors::SUCCESS;
+ }
+ }
+ }
+ }
+ }
+
+ if (NS_IsMainThread()) {
+ mHeaderSink->SignedStatus(mMimeNestingLevel, signature_status, signerCert,
+ mMsgNeckoURL, mOriginMimePartNumber);
+ } else {
+ ProxySignedStatus(mHeaderSink, mMimeNestingLevel, signature_status,
+ signerCert, mMsgNeckoURL, mOriginMimePartNumber);
+ }
+
+ 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) {
+ char* addr = mime_part_address(walker);
+ if (!strcmp(addr, walker->options->part_to_load)) {
+ aAlreadyFoundTop = true;
+ aTopShownObject = walker;
+ }
+ PR_FREEIF(addr);
+ }
+ 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;
+ 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")) {
+ nsCOMPtr<nsIMailChannel> mailChannel = do_QueryInterface(channel);
+ if (mailChannel) {
+ mailChannel->GetSmimeHeaderSink(
+ getter_AddRefs(data->smimeHeaderSink));
+ }
+ }
+ }
+ } // 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, nsCString& msg_date) {
+ 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);
+
+ msg_date.Adopt(MimeHeaders_get(msg_headers, HEADER_DATE, false, true));
+}
+
+void MimeCMSRequestAsyncSignatureVerification(
+ nsICMSMessage* aCMSMsg, const char* aFromAddr, const char* aFromName,
+ const char* aSenderAddr, const char* aSenderName, const char* aMsgDate,
+ nsIMsgSMIMEHeaderSink* aHeaderSink, int32_t aMimeNestingLevel,
+ const nsCString& aMsgNeckoURL, const nsCString& aOriginMimePartNumber,
+ const nsTArray<uint8_t>& aDigestData, int16_t aDigestType) {
+ RefPtr<nsSMimeVerificationListener> listener =
+ new nsSMimeVerificationListener(
+ aFromAddr, aFromName, aSenderAddr, aSenderName, aMsgDate, aHeaderSink,
+ aMimeNestingLevel, aMsgNeckoURL, aOriginMimePartNumber);
+
+ long verifyFlags = 0;
+ if (mozilla::Preferences::GetBool(
+ "mail.smime.accept_insecure_sha1_message_signatures", false)) {
+ verifyFlags |= nsICMSVerifyFlags::VERIFY_ALLOW_WEAK_SHA1;
+ }
+
+ if (aDigestData.IsEmpty())
+ aCMSMsg->AsyncVerifySignature(verifyFlags, listener);
+ else
+ aCMSMsg->AsyncVerifyDetachedSignature(verifyFlags, listener, aDigestData,
+ aDigestType);
+}
+
+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;
+
+ // maxWantedNesting 1: only want outermost nesting level
+ if (aRelativeNestLevel > 1) return 0;
+
+ if (data->decoding_failed) status = nsICMSMessageErrors::GENERAL_ERROR;
+
+ nsAutoCString partnum;
+ partnum.Adopt(mime_part_address(data->self));
+
+ 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, partnum);
+ }
+ if (data->any_parent_is_encrypted_p) {
+ data->smimeHeaderSink->EncryptionStatus(
+ aRelativeNestLevel, nsICMSMessageErrors::GENERAL_ERROR, nullptr,
+ data->url, partnum);
+ }
+ 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;
+ nsCString msg_date;
+
+ MimeCMSGetFromSender(data->self, from_addr, from_name, sender_addr,
+ sender_name, msg_date);
+
+ MimeCMSRequestAsyncSignatureVerification(
+ data->content_info, from_addr.get(), from_name.get(),
+ sender_addr.get(), sender_name.get(), msg_date.get(),
+ data->smimeHeaderSink, aRelativeNestLevel, data->url, partnum, {}, 0);
+ }
+ }
+
+ if (data->ci_is_encrypted) {
+ data->smimeHeaderSink->EncryptionStatus(aRelativeNestLevel, status,
+ certOfInterest, data->url, partnum);
+ }
+
+ 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/comm/mailnews/mime/src/mimecms.h b/comm/mailnews/mime/src/mimecms.h
new file mode 100644
index 0000000000..bb4746dfd7
--- /dev/null
+++ b/comm/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; // for function arguments in mimecms.h
+
+/* 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/comm/mailnews/mime/src/mimecom.cpp b/comm/mailnews/mime/src/mimecom.cpp
new file mode 100644
index 0000000000..17438302bb
--- /dev/null
+++ b/comm/mailnews/mime/src/mimecom.cpp
@@ -0,0 +1,53 @@
+/* -*- 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 "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/comm/mailnews/mime/src/mimecom.h b/comm/mailnews/mime/src/mimecom.h
new file mode 100644
index 0000000000..d170a70829
--- /dev/null
+++ b/comm/mailnews/mime/src/mimecom.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/. */
+
+/*
+ * 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/comm/mailnews/mime/src/mimecont.cpp b/comm/mailnews/mime/src/mimecont.cpp
new file mode 100644
index 0000000000..8b139e1fbf
--- /dev/null
+++ b/comm/mailnews/mime/src/mimecont.cpp
@@ -0,0 +1,209 @@
+/* -*- 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 "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 we're being destroyed, prior to deleting any data, mark
+ * flush data in all children and mark them as closed, to avoid
+ * flushing during subsequent mime_free of the children.
+ * This also helps if this (parent) object is already marked as
+ * closed, but a child is not yet marked as closed.
+ */
+ ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(object, true);
+ if (cont->children) {
+ int i;
+ for (i = 0; i < cont->nchildren; i++) {
+ MimeObject* kid = cont->children[i];
+ if (kid && !kid->closed_p) {
+ kid->clazz->parse_eof(kid, true);
+ }
+ }
+ }
+
+ 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/comm/mailnews/mime/src/mimecont.h b/comm/mailnews/mime/src/mimecont.h
new file mode 100644
index 0000000000..71120c8c6e
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimecryp.cpp b/comm/mailnews/mime/src/mimecryp.cpp
new file mode 100644
index 0000000000..b985c53fbe
--- /dev/null
+++ b/comm/mailnews/mime/src/mimecryp.cpp
@@ -0,0 +1,507 @@
+/* -*- 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/. */
+
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+#endif
+#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 "modmimee.h" // for MimeConverterOutputCallback
+
+using namespace mozilla;
+
+static mozilla::LazyLogModule gMimeCryptLog("MIMECRYPT");
+
+#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 only 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 && 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);
+}
+
+#ifdef MOZ_LOGGING
+static int DebugOut(const char* buf, int32_t size, void* closure) {
+ MOZ_LOG(gMimeCryptLog, LogLevel::Debug,
+ ("MimeEncrypted_emit_buffered_child: (partial) decrypted body\n%.*s",
+ size, buf));
+ return 0;
+}
+#endif
+
+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");
+
+#ifdef MOZ_LOGGING
+ if (enc->hdrs && enc->hdrs->all_headers) {
+ MOZ_LOG(gMimeCryptLog, LogLevel::Debug,
+ ("MimeEncrypted_emit_buffered_child: decrypted headers:\n%.*s",
+ enc->hdrs->all_headers_fp, enc->hdrs->all_headers));
+ }
+
+ if (enc->part_buffer) {
+ status = MimePartBufferRead(enc->part_buffer, DebugOut, 0);
+ if (status < 0) return status;
+ }
+#endif
+
+ /* 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) {
+ /* 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/comm/mailnews/mime/src/mimecryp.h b/comm/mailnews/mime/src/mimecryp.h
new file mode 100644
index 0000000000..dd8c846350
--- /dev/null
+++ b/comm/mailnews/mime/src/mimecryp.h
@@ -0,0 +1,139 @@
+/* -*- 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/comm/mailnews/mime/src/mimecth.cpp b/comm/mailnews/mime/src/mimecth.cpp
new file mode 100644
index 0000000000..0a091b77fb
--- /dev/null
+++ b/comm/mailnews/mime/src/mimecth.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#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/comm/mailnews/mime/src/mimecth.h b/comm/mailnews/mime/src/mimecth.h
new file mode 100644
index 0000000000..ed65163452
--- /dev/null
+++ b/comm/mailnews/mime/src/mimecth.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/comm/mailnews/mime/src/mimedrft.cpp b/comm/mailnews/mime/src/mimedrft.cpp
new file mode 100644
index 0000000000..07d05cf19b
--- /dev/null
+++ b/comm/mailnews/mime/src/mimedrft.cpp
@@ -0,0 +1,2135 @@
+/* -*- 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/.
+ * 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 "nsNetUtil.h"
+#include "comi18n.h"
+#include "nsIMsgAttachment.h"
+#include "nsIMsgCompFields.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 "modmimee.h" // for MimeConverterOutputCallback
+#include "mozilla/dom/Promise.h"
+#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);
+
+#define NS_MSGCOMPOSESERVICE_CID \
+ { /* 588595FE-1ADA-11d3-A715-0060B0EB39B5 */ \
+ 0x588595fe, 0x1ada, 0x11d3, { \
+ 0xa7, 0x15, 0x0, 0x60, 0xb0, 0xeb, 0x39, 0xb5 \
+ } \
+ }
+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),
+ autodetectCharset(false) {}
+////////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////////
+// 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 nsACString& 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(
+ "@mozilla.org/messengercompose/attachment;1", &rv);
+ if (NS_SUCCEEDED(rv) && attachment) {
+ nsAutoString nameStr;
+ rv = nsMsgI18NConvertToUnicode("UTF-8"_ns, curAttachment->m_realName,
+ 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;
+ nsCString cloudPartHeaderData;
+
+ provider.Adopt(
+ MimeHeaders_get_parameter(curAttachment->m_cloudPartInfo.get(),
+ "provider", nullptr, nullptr));
+ cloudUrl.Adopt(MimeHeaders_get_parameter(
+ curAttachment->m_cloudPartInfo.get(), "url", nullptr, nullptr));
+ cloudPartHeaderData.Adopt(
+ MimeHeaders_get_parameter(curAttachment->m_cloudPartInfo.get(),
+ "data", nullptr, nullptr));
+
+ attachment->SetSendViaCloud(true);
+ attachment->SetCloudFileAccountKey(provider);
+ attachment->SetContentLocation(cloudUrl);
+ attachment->SetCloudPartHeaderData(cloudPartHeaderData);
+ }
+ 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("@mozilla.org/messengercompose/composeparams;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ pMsgComposeParams->SetType(composeType);
+ pMsgComposeParams->SetFormat(format);
+ pMsgComposeParams->SetIdentity(identity);
+ pMsgComposeParams->SetComposeFields(compFields);
+ if (!originalMsgURI.IsEmpty())
+ 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 nsACString& 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 nsACString& 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("@mozilla.org/messengercompose/compose;1", &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);
+
+ RefPtr<mozilla::dom::Promise> promise;
+ rv = pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, identity, nullptr,
+ nullptr, nullptr, getter_AddRefs(promise));
+ 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,
+ const nsTArray<nsString>& otherHeaders, char* charset,
+ nsIMsgCompFields** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv;
+ *_retval = nullptr;
+
+ nsCOMPtr<nsIMsgCompFields> cFields =
+ do_CreateInstance("@mozilla.org/messengercompose/composefields;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(cFields, NS_ERROR_OUT_OF_MEMORY);
+
+ nsAutoCString val;
+ nsAutoString outString;
+
+ if (from) {
+ nsMsgI18NConvertRawBytesToUTF16(
+ nsDependentCString(from),
+ charset ? nsDependentCString(charset) : EmptyCString(), 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) {
+ nsMsgI18NConvertRawBytesToUTF16(
+ nsDependentCString(reply_to),
+ charset ? nsDependentCString(charset) : EmptyCString(), outString);
+ cFields->SetReplyTo(outString);
+ }
+
+ if (to) {
+ nsMsgI18NConvertRawBytesToUTF16(
+ nsDependentCString(to),
+ charset ? nsDependentCString(charset) : EmptyCString(), outString);
+ cFields->SetTo(outString);
+ }
+
+ if (cc) {
+ nsMsgI18NConvertRawBytesToUTF16(
+ nsDependentCString(cc),
+ charset ? nsDependentCString(charset) : EmptyCString(), outString);
+ cFields->SetCc(outString);
+ }
+
+ if (bcc) {
+ nsMsgI18NConvertRawBytesToUTF16(
+ nsDependentCString(bcc),
+ charset ? nsDependentCString(charset) : EmptyCString(), 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);
+ }
+
+ nsTArray<nsString> cFieldsOtherHeaders;
+ cFields->GetOtherHeaders(cFieldsOtherHeaders);
+ for (auto otherHeader : otherHeaders) {
+ if (!otherHeader.IsEmpty()) {
+ MIME_DecodeMimeHeader(NS_ConvertUTF16toUTF8(otherHeader).get(), charset,
+ false, true, val);
+ cFieldsOtherHeaders.AppendElement(NS_ConvertUTF8toUTF16(val));
+ } else {
+ cFieldsOtherHeaders.AppendElement(u""_ns);
+ }
+ }
+ cFields->SetOtherHeaders(cFieldsOtherHeaders);
+ cFields.forget(_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.LowerCaseFindASCII("text/html") == kNotFound &&
+ mdd->messageBody->m_type.LowerCaseFindASCII("text/plain") == kNotFound &&
+ !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/vcard") ||
+ 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.LowerCaseFindASCII(MESSAGE_RFC822) != kNotFound)
+ // 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()) {
+ if (htmlEdit) {
+ nsCString escaped;
+ nsAppendEscapedHTML(utf8Value, escaped);
+ NS_MsgSACat(body, escaped.get());
+ } else {
+ NS_MsgSACat(body, utf8Value.get());
+ }
+ } 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 convert_plaintext_body_to_html(char** body) {
+ // We need to convert the plain/text to HTML in order to escape any HTML
+ // markup.
+ nsCString escapedBody;
+ nsAppendEscapedHTML(nsDependentCString(*body), escapedBody);
+
+ nsCString newBody;
+ char* q = escapedBody.BeginWriting();
+ char* p;
+ int prevQuoteLevel = 0;
+ bool isFlowed = false;
+ bool haveSig = false;
+
+ // First detect whether this appears to be flowed or not.
+ p = q;
+ while (*p) {
+ // At worst we read the null byte terminator.
+ if (*p == ' ' && (*(p + 1) == '\r' || *(p + 1) == '\n')) {
+ // This looks flowed, but don't get fooled by a signature separator:
+ // --space
+ if (p - 3 >= q && (*(p - 3) == '\r' || *(p - 3) == '\n') &&
+ *(p - 2) == '-' && *(p - 1) == '-') {
+ p++;
+ continue;
+ }
+ if (p - 2 == q && *(p - 2) == '-' && *(p - 1) == '-') {
+ p++;
+ continue;
+ }
+ isFlowed = true;
+ break;
+ }
+ p++;
+ }
+
+ while (*q) {
+ p = q;
+ // Detect quotes. A quote character is a ">" which was escaped to &gt;.
+ // In non-flowed messages the quote character can be optionally followed by
+ // a space. Examples: Level 0
+ // > Level 0 (with leading space)
+ // > Level 1
+ // > > Level 1 (with leading space, note the two spaces between the quote
+ // characters)
+ // >> Level 2
+ // > > Level 2 (only when non-flowed, otherwise Level 1 with leading space)
+ // >>> Level 3
+ // > > > Level 3 (with leading space, only when non-flowed, otherwise Level
+ // 1)
+ int quoteLevel = 0;
+ while (strncmp(p, "&gt;", 4) == 0) {
+ p += 4;
+ if (!isFlowed && *p == ' ') p++;
+ quoteLevel++;
+ }
+
+ // Eat space following quote character, for non-flowed already eaten above.
+ if (quoteLevel > 0 && isFlowed && *p == ' ') p++;
+
+ // Close any open signatures if we find a quote. Strange, that shouldn't
+ // happen.
+ if (quoteLevel > 0 && haveSig) {
+ newBody.AppendLiteral("</pre>");
+ haveSig = false;
+ }
+ if (quoteLevel > prevQuoteLevel) {
+ while (prevQuoteLevel < quoteLevel) {
+ if (isFlowed)
+ newBody.AppendLiteral("<blockquote type=\"cite\">");
+ else
+ newBody.AppendLiteral(
+ "<blockquote type=\"cite\"><pre wrap class=\"moz-quote-pre\">");
+ prevQuoteLevel++;
+ }
+ } else if (quoteLevel < prevQuoteLevel) {
+ while (prevQuoteLevel > quoteLevel) {
+ if (isFlowed)
+ newBody.AppendLiteral("</blockquote>");
+ else
+ newBody.AppendLiteral("</pre></blockquote>");
+ prevQuoteLevel--;
+ }
+ }
+ // Position after the quote.
+ q = p;
+
+ // Detect signature.
+ bool forceBR = false;
+ if (quoteLevel == 0) {
+ if (strncmp(q, "-- \r", 4) == 0 || strncmp(q, "-- \n", 4) == 0) {
+ haveSig = true;
+ forceBR = true;
+ newBody.AppendLiteral("<pre class=\"moz-signature\">");
+ }
+ }
+
+ bool seenSpace = false;
+ while (*p && *p != '\r' && *p != '\n') {
+ seenSpace = (*p == ' ');
+ p++;
+ continue;
+ }
+ if (!*p) {
+ // We're at the end of the string.
+ if (p > q) {
+ // Copy last bit over.
+ newBody.Append(q);
+ }
+ break;
+ }
+ if (*p == '\r' &&
+ *(p + 1) == '\n') { // At worst we read the null byte terminator.
+ // Skip the CR in CRLF.
+ *p = 0; // don't copy skipped \r.
+ p++;
+ }
+ *p = 0;
+ newBody.Append(q);
+ if (!isFlowed || !seenSpace || forceBR) newBody.AppendLiteral("<br>");
+ q = p + 1;
+ }
+
+ // Close all open quotes.
+ while (prevQuoteLevel > 0) {
+ if (isFlowed)
+ newBody.AppendLiteral("</blockquote>");
+ else
+ newBody.AppendLiteral("</pre></blockquote>");
+ prevQuoteLevel--;
+ }
+
+ // Close any open signatures.
+ if (haveSig) {
+ newBody.AppendLiteral("</pre>");
+ haveSig = false;
+ }
+
+ PR_Free(*body);
+ *body = ToNewCString(newBody);
+}
+
+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;
+ nsTArray<nsString> readOtherHeaders;
+
+ 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;
+ // Override the charset only if requested. If the message doesn't have
+ // one and we're not overriding, we'll detect it later.
+ if (charsetOverride && mdd->options->default_charset) {
+ PR_FREEIF(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", 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);
+ }
+ }
+
+ // Other headers via pref.
+ nsCString otherHeaders;
+ nsTArray<nsCString> otherHeadersArray;
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ pPrefBranch->GetCharPref("mail.compose.other.header", otherHeaders);
+ if (!otherHeaders.IsEmpty()) {
+ ToLowerCase(otherHeaders);
+ ParseString(otherHeaders, ',', otherHeadersArray);
+ for (auto otherHeader : otherHeadersArray) {
+ otherHeader.Trim(" ");
+ nsAutoCString result;
+ result.Assign(
+ MimeHeaders_get(mdd->headers, otherHeader.get(), false, false));
+ readOtherHeaders.AppendElement(NS_ConvertUTF8toUTF16(result));
+ }
+ }
+ }
+
+ CreateCompositionFields(from, repl, to, cc, bcc, fcc, grps, foll, org, subj,
+ refs, priority, news_host, readOtherHeaders,
+ 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);
+
+ // We always preserve an existing message ID, if present, apart from some
+ // exceptions.
+ bool keepID = fields != nullptr;
+
+ // Don't keep ID when forwarding inline.
+ if (forward_inline) keepID = false;
+
+ // nsMimeOutput::nsMimeMessageEditorTemplate is used for editing a message
+ // "as new", creating a message from a template or editing a template.
+ // Only in the latter case we want to preserve the ID.
+ if (mdd->format_out == nsMimeOutput::nsMimeMessageEditorTemplate &&
+ !PL_strstr(mdd->url_name, "&edittempl=true"))
+ keepID = false;
+
+ if (keepID) 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::Unset;
+ 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("@mozilla.org/messenger/account-manager;1", &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.LowerCaseFindASCII("text/html") !=
+ kNotFound)
+ composeFormat = nsIMsgCompFormat::HTML;
+ else if (mdd->messageBody->m_type.LowerCaseFindASCII("text/plain") !=
+ kNotFound ||
+ 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;
+
+ if (!bodyAsAttachment && mdd->messageBody->m_tmpFile) {
+ int64_t fileSize;
+ nsCOMPtr<nsIFile> tempFileCopy;
+ mdd->messageBody->m_tmpFile->Clone(getter_AddRefs(tempFileCopy));
+ mdd->messageBody->m_tmpFile = tempFileCopy;
+ tempFileCopy = nullptr;
+ mdd->messageBody->m_tmpFile->GetFileSize(&fileSize);
+ uint32_t bodyLen = 0;
+
+ // 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.
+ nsAutoCString bodyCharset;
+ if (mimeCharset) {
+ bodyCharset.Adopt(mimeCharset);
+ } else if (mdd->mailcharset) {
+ bodyCharset.Assign(mdd->mailcharset);
+ }
+ if (bodyCharset.IsEmpty()) {
+ nsAutoCString detectedCharset;
+ // We need to detect it.
+ rv = MIME_detect_charset(body, bodyLen, detectedCharset);
+ if (NS_SUCCEEDED(rv) && !detectedCharset.IsEmpty()) {
+ bodyCharset = detectedCharset;
+ }
+ }
+ if (!bodyCharset.IsEmpty()) {
+ nsAutoString tmpUnicodeBody;
+ rv = nsMsgI18NConvertToUnicode(
+ bodyCharset, nsDependentCString(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;
+ }
+ }
+ }
+ }
+
+ 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.
+ convert_plaintext_body_to_html(&body);
+ }
+ // 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);
+ }
+
+ MSG_ComposeType msgComposeType = 0; // Keep compilers happy.
+ if (mdd->format_out == nsMimeOutput::nsMimeMessageEditorTemplate) {
+ if (PL_strstr(mdd->url_name, "?redirect=true") ||
+ PL_strstr(mdd->url_name, "&redirect=true"))
+ msgComposeType = nsIMsgCompType::Redirect;
+ else if (PL_strstr(mdd->url_name, "?editasnew=true") ||
+ PL_strstr(mdd->url_name, "&editasnew=true"))
+ msgComposeType = nsIMsgCompType::EditAsNew;
+ else if (PL_strstr(mdd->url_name, "?edittempl=true") ||
+ PL_strstr(mdd->url_name, "&edittempl=true"))
+ msgComposeType = nsIMsgCompType::EditTemplate;
+ else
+ msgComposeType = nsIMsgCompType::Template;
+ }
+
+ if (body && msgComposeType == nsIMsgCompType::EditAsNew) {
+ // When editing as new, we respect the identities preferred format
+ // which can be overridden.
+ if (mdd->identity) {
+ bool identityComposeHTML;
+ mdd->identity->GetComposeHtml(&identityComposeHTML);
+
+ if (composeFormat == nsIMsgCompFormat::HTML &&
+ identityComposeHTML == mdd->overrideComposeFormat) {
+ // We we have HTML:
+ // If they want HTML and they want to override it (true == true)
+ // or they don't want HTML and they don't want to override it
+ // (false == false), then convert. Conversion happens below.
+ convertToPlainText = true;
+ composeFormat = nsIMsgCompFormat::PlainText;
+ } else if (composeFormat == nsIMsgCompFormat::PlainText &&
+ identityComposeHTML != mdd->overrideComposeFormat) {
+ // We have plain text:
+ // If they want HTML and they don't want to override it (true !=
+ // false) or they don't want HTML and they want to override it
+ // (false != true), then convert.
+ convert_plaintext_body_to_html(&body);
+ composeFormat = nsIMsgCompFormat::HTML;
+ }
+ }
+ } else if (body && mdd->overrideComposeFormat &&
+ (msgComposeType == nsIMsgCompType::Template ||
+ msgComposeType == nsIMsgCompType::EditTemplate ||
+ !mdd->forwardInline)) // Draft processing.
+ {
+ // When using a template and overriding, the user gets the
+ // "other" format.
+ if (composeFormat == nsIMsgCompFormat::PlainText) {
+ convert_plaintext_body_to_html(&body);
+ composeFormat = nsIMsgCompFormat::HTML;
+ } else {
+ // Conversion happens below.
+ convertToPlainText = true;
+ composeFormat = nsIMsgCompFormat::PlainText;
+ }
+ }
+
+ // 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) {
+ // Set the draft ID when editing a template so the original is
+ // overwritten when saving the template again.
+ // Note that always setting the draft ID here would cause drafts to be
+ // overwritten when edited "as new", which is undesired.
+ if (msgComposeType == nsIMsgCompType::EditTemplate) {
+ fields->SetDraftId(nsDependentCString(mdd->url_name));
+ fields->SetTemplateId(nsDependentCString(
+ mdd->url_name)); // Remember original template ID.
+ }
+
+ if (convertToPlainText) fields->ConvertBodyToPlainText();
+
+ 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 {
+ if (convertToPlainText) fields->ConvertBodyToPlainText();
+ fields->SetDraftId(nsDependentCString(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,
+ EmptyCString(), 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(nsDependentCString(mdd->url_name));
+ CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::Draft,
+ nsIMsgCompFormat::Default, mdd->identity,
+ EmptyCString(), mdd->origMsgHdr);
+ }
+ }
+ }
+ } else {
+ CreateCompositionFields(from, repl, to, cc, bcc, fcc, grps, foll, org, subj,
+ refs, priority, news_host, readOtherHeaders,
+ mdd->mailcharset, getter_AddRefs(fields));
+ if (fields)
+ CreateTheComposeWindow(fields, newAttachData, nsIMsgCompType::New,
+ nsIMsgCompFormat::Default, mdd->identity,
+ EmptyCString(), 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);
+ 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) {
+ if (mdd->options->default_charset)
+ mdd->mailcharset = strdup(mdd->options->default_charset);
+ else {
+ mdd->mailcharset = strdup("");
+ mdd->autodetectCharset = true;
+ }
+ } 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));
+
+ nsCOMPtr<nsIFile> tmpFile = nullptr;
+ {
+ // Let's build a temp file with an extension based on the content-type:
+ // nsmail.<extension>
+
+ nsAutoCString newAttachName("nsmail");
+ nsAutoCString fileExtension;
+ // 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);
+ int32_t extLoc = newAttachment->m_realName.RFindChar('.');
+ int32_t specLength = newAttachment->m_realName.Length();
+ // @see nsExternalHelperAppService::GetTypeFromURI()
+ if (extLoc != -1 && extLoc != specLength - 1 &&
+ // nothing over 20 chars long can be sanely considered an
+ // extension.... Dat dere would be just data.
+ specLength - extLoc < 20) {
+ fileExtension = Substring(newAttachment->m_realName, extLoc + 1);
+ } else {
+ nsCOMPtr<nsIMIMEService> mimeFinder(
+ do_GetService(NS_MIMESERVICE_CONTRACTID));
+ if (mimeFinder) {
+ mimeFinder->GetPrimaryExtension(contentType, ""_ns, fileExtension);
+ }
+ }
+
+ if (fileExtension.IsEmpty()) {
+ newAttachName.AppendLiteral(".tmp");
+ } else {
+ newAttachName.Append('.');
+ newAttachName.Append(fileExtension);
+ }
+
+ 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 = 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->autodetectCharset) {
+ nsAutoCString detectedCharset;
+ nsresult res = NS_OK;
+ res = MIME_detect_charset(buf, size, detectedCharset);
+ if (NS_SUCCEEDED(res) && !detectedCharset.IsEmpty()) {
+ mdd->mailcharset = ToNewCString(detectedCharset);
+ mdd->autodetectCharset = false;
+ }
+ }
+
+ 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, nullptr, getter_AddRefs(aURL));
+ 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);
+ if (mdd->options) delete mdd->options;
+ PR_Free(mdd);
+ }
+ PR_Free(stream);
+ PR_Free(obj);
+
+ return nullptr;
+}
diff --git a/comm/mailnews/mime/src/mimeebod.cpp b/comm/mailnews/mime/src/mimeebod.cpp
new file mode 100644
index 0000000000..755574b1b0
--- /dev/null
+++ b/comm/mailnews/mime/src/mimeebod.cpp
@@ -0,0 +1,441 @@
+/* -*- 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 "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;
+#ifdef XP_UNIX
+ } else if (!PL_strcasecmp(at, "local-file") || !PL_strcasecmp(at, "afs")) {
+ if (!name) return 0;
+
+ 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("/afs/."_ns);
+ fs->Exists(&exists);
+ }
+ if (!exists) return 0;
+ }
+
+ 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;
+#endif
+ } 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) {
+ const char* pre = "<P><PRE>";
+ const char* suf = "</PRE>";
+ int32_t i;
+ // The end condition requires i to be negative, so it's ok to
+ // allow the starting value to be negative.
+ for (i = strlen(s) - 1; i >= 0 && IS_SPACE(s[i]); i--) s[i] = 0;
+ nsCString s2;
+ nsAppendEscapedHTML(nsDependentCString(s), s2);
+ body = (char*)PR_MALLOC(strlen(pre) + s2.Length() + strlen(suf) + 1);
+ if (!body) {
+ goto FAIL;
+ }
+ PL_strcpy(body, pre);
+ PL_strcat(body, s2.get());
+ 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("/afs/."_ns);
+ 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/comm/mailnews/mime/src/mimeebod.h b/comm/mailnews/mime/src/mimeebod.h
new file mode 100644
index 0000000000..fcb33ee664
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimeenc.cpp b/comm/mailnews/mime/src/mimeenc.cpp
new file mode 100644
index 0000000000..1c9df3794b
--- /dev/null
+++ b/comm/mailnews/mime/src/mimeenc.cpp
@@ -0,0 +1,999 @@
+/* -*- 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 "mimei.h"
+#include "prmem.h"
+#include "mimeobj.h"
+#include "mozilla/RangedPtr.h"
+#include "mozilla/mailnews/MimeEncoder.h"
+#include "modmimee.h" // for MimeConverterOutputCallback
+
+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) {
+ if (outSize) *outSize = 0;
+
+ // Without input there is nothing to do.
+ if (length == 0) return 1;
+
+ /* 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);
+ if (outSize) *outSize += n;
+ 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/comm/mailnews/mime/src/mimeeobj.cpp b/comm/mailnews/mime/src/mimeeobj.cpp
new file mode 100644
index 0000000000..2bc858b4b4
--- /dev/null
+++ b/comm/mailnews/mime/src/mimeeobj.cpp
@@ -0,0 +1,215 @@
+/* -*- 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 "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/comm/mailnews/mime/src/mimeeobj.h b/comm/mailnews/mime/src/mimeeobj.h
new file mode 100644
index 0000000000..069a94f60c
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimefilt.cpp b/comm/mailnews/mime/src/mimefilt.cpp
new file mode 100644
index 0000000000..61b1ac6ae8
--- /dev/null
+++ b/comm/mailnews/mime/src/mimefilt.cpp
@@ -0,0 +1,349 @@
+/* -*- 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/. */
+
+/* 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;
+ size_t s_len = strlen(s);
+ if (s_len && s[slen - 1] == '\r' || s[slen - 1] == '\n') s[slen - 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/comm/mailnews/mime/src/mimehdrs.cpp b/comm/mailnews/mime/src/mimehdrs.cpp
new file mode 100644
index 0000000000..6df9028765
--- /dev/null
+++ b/comm/mailnews/mime/src/mimehdrs.cpp
@@ -0,0 +1,785 @@
+/* -*- 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 "msgCore.h"
+#include "mimei.h"
+#include "prmem.h"
+#include "prlog.h"
+#include "plstr.h"
+#include "mimebuf.h"
+#include "mimemoz2.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"
+#include "mozilla/Unused.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;
+ nsMsgI18NConvertRawBytesToUTF8(
+ value,
+ opt->default_charset ? nsDependentCString(opt->default_charset)
+ : EmptyCString(),
+ 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;
+}
+
+static bool find_header_starts(MimeHeaders* hdrs, bool counting) {
+ const char* end = hdrs->all_headers + hdrs->all_headers_fp;
+ char* s = hdrs->all_headers;
+ int i = 0;
+
+ if (counting) {
+ // For the start pointer
+ hdrs->heads_size = 1;
+ } else {
+ hdrs->heads[i++] = 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) {
+ if (counting) {
+ hdrs->heads_size++;
+ } else {
+ NS_ASSERTION(i < hdrs->heads_size,
+ "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
+ if (i >= hdrs->heads_size) return false;
+ hdrs->heads[i++] = s;
+ }
+ }
+ }
+ if (!counting) {
+ NS_ASSERTION(i == hdrs->heads_size, "unexpected");
+ }
+ return true;
+}
+
+int MimeHeaders_build_heads_list(MimeHeaders* hdrs) {
+ 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;
+ }
+ }
+
+ find_header_starts(hdrs, true);
+
+ /* Now allocate storage for the pointers to each of those headers.
+ */
+ hdrs->heads = (char**)PR_MALLOC((hdrs->heads_size) * sizeof(char*));
+ if (!hdrs->heads) return MIME_OUT_OF_MEMORY;
+ memset(hdrs->heads, 0, (hdrs->heads_size) * sizeof(char*));
+
+ /* Now make another pass through the headers, and this time, record the
+ starting position of each header.
+ */
+ if (!find_header_starts(hdrs, false)) {
+ return -1;
+ }
+
+ 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;
+ size_t headLen = end - head;
+
+ /* Quick hack to skip over BSD Mailbox delimiter. */
+ if (i == 0 && head[0] == 'F' && headLen >= 5 && !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 occurrence 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(nsDependentCString(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;
+ size_t headLen = end - head;
+
+ /* Hack for BSD Mailbox delimiter. */
+ if (i == 0 && head[0] == 'F' && headLen >= 5 &&
+ !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 = name.LowerCaseEqualsLiteral("to") ||
+ name.LowerCaseEqualsLiteral("from") ||
+ name.LowerCaseEqualsLiteral("cc") ||
+ name.LowerCaseEqualsLiteral("bcc") ||
+ name.LowerCaseEqualsLiteral("reply-to") ||
+ name.LowerCaseEqualsLiteral("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(nsMsgI18NConvertFromUnicode(
+ nsDependentCString(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) {
+ mozilla::Unused << 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/comm/mailnews/mime/src/mimehdrs.h b/comm/mailnews/mime/src/mimehdrs.h
new file mode 100644
index 0000000000..028092246a
--- /dev/null
+++ b/comm/mailnews/mime/src/mimehdrs.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/comm/mailnews/mime/src/mimei.cpp b/comm/mailnews/mime/src/mimei.cpp
new file mode 100644
index 0000000000..3001189f8e
--- /dev/null
+++ b/comm/mailnews/mime/src/mimei.cpp
@@ -0,0 +1,1716 @@
+/* -*- 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/.
+ * 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.
+ */
+
+// clang-format off
+#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 */
+// clang-format on
+
+#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 "mimemoz2.h"
+#include "nsIMimeContentTypeHandler.h"
+#include "nsICategoryManager.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"
+#include "nsIMailChannel.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 = nullptr;
+
+/*
+ * 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 = nullptr;
+ 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 specific 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 specific 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 = nullptr;
+ 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,
+ 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(rdfURI);
+ if (!rdfURI.IsEmpty()) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ GetMsgDBHdrFromURI(rdfURI, 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", 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)) != nullptr) {
+#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", nullptr, nullptr);
+
+ mime_stream_data* msd = (mime_stream_data*)(opts->stream_closure);
+ nsCOMPtr<nsIMailChannel> mailChannel = do_QueryInterface(msd->channel);
+ if (mailChannel) {
+ mailChannel->SetImipMethod(
+ nsCString(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/vcard", 10) ||
+ !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 truly 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",
+ nullptr, nullptr)
+ : 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, nullptr, nullptr)
+ : 0;
+ char* micalg =
+ ct ? MimeHeaders_get_parameter(ct, PARAM_MICALG, nullptr, nullptr)
+ : 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(
+ nsDependentCString(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 (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;
+ } else {
+ char* ct =
+ hdrs ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, false, false)
+ : nullptr;
+ char* st =
+ ct ? MimeHeaders_get_parameter(ct, "smime-type", nullptr, nullptr)
+ : 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);
+
+ // Workaround 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*)&mimeMessageClass) &&
+ (clazz != (MimeObjectClass*)&mimeInlineImageClass) &&
+ (!opts->show_attachment_inline_text ||
+ ((clazz != (MimeObjectClass*)&mimeInlineTextHTMLClass) &&
+ (clazz != (MimeObjectClass*)&mimeInlineTextHTMLParsedClass) &&
+ (clazz != (MimeObjectClass*)&mimeInlineTextHTMLSanitizedClass) &&
+ (clazz != (MimeObjectClass*)&mimeInlineTextHTMLAsPlaintextClass) &&
+ (clazz != (MimeObjectClass*)&mimeInlineTextRichtextClass) &&
+ (clazz != (MimeObjectClass*)&mimeInlineTextEnrichedClass) &&
+ (clazz != (MimeObjectClass*)&mimeInlineTextClass) &&
+ (clazz != (MimeObjectClass*)&mimeInlineTextPlainClass) &&
+ (clazz != (MimeObjectClass*)&mimeInlineTextPlainFlowedClass)))) {
+ // 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;
+ if (!child->superclass) return false;
+ 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");
+
+ /* 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);
+
+ 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;
+ 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;
+ 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;
+
+ if (clearsigned_counts &&
+ clazz == ((MimeObjectClass*)&mimeMultipartSignedCMSClass))
+ return true;
+
+ 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) {
+ while (*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;
+ end = q;
+ while (*end && *end != '&') end++;
+ value = q;
+ while (*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=\"moz-mime-attachment-header\">";
+ 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=\"moz-mime-attachment-header-name\">";
+ lstatus = opt->output_fn(sep, strlen(sep), closure);
+ opt->state->separator_suppressed_p = false;
+ if (lstatus < 0) return lstatus;
+
+ nsCString escapedName;
+ nsAppendEscapedHTML(name, escapedName);
+
+ 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>";
+ 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 output
+ if (obj->options &&
+ 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 && !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, nullptr, nullptr);
+ x_mac_creator = MimeHeaders_get_parameter(ct, PARAM_X_MAC_CREATOR,
+ nullptr, nullptr);
+ /* 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,
+ nullptr, nullptr);
+ x_mac_creator = MimeHeaders_get_parameter(ctp, PARAM_X_MAC_CREATOR,
+ nullptr, nullptr);
+ 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, '&');
+ // strlen(s) cannot be zero, because it matches the above text
+ 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/comm/mailnews/mime/src/mimei.h b/comm/mailnews/mime/src/mimei.h
new file mode 100644
index 0000000000..d3a0684224
--- /dev/null
+++ b/comm/mailnews/mime/src/mimei.h
@@ -0,0 +1,405 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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:
+ This 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:
+ This 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 knowledge 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;
+
+/* (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/comm/mailnews/mime/src/mimeiimg.cpp b/comm/mailnews/mime/src/mimeiimg.cpp
new file mode 100644
index 0000000000..f2ac8a0e09
--- /dev/null
+++ b/comm/mailnews/mime/src/mimeiimg.cpp
@@ -0,0 +1,220 @@
+/* -*- 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 "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/comm/mailnews/mime/src/mimeiimg.h b/comm/mailnews/mime/src/mimeiimg.h
new file mode 100644
index 0000000000..b95400b967
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimeleaf.cpp b/comm/mailnews/mime/src/mimeleaf.cpp
new file mode 100644
index 0000000000..24d8c8989f
--- /dev/null
+++ b/comm/mailnews/mime/src/mimeleaf.cpp
@@ -0,0 +1,187 @@
+/* -*- 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 "modmimee.h"
+#include "mimeleaf.h"
+#include "nsMimeTypes.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prlog.h"
+#include "nsMimeStringResources.h"
+#include "modmimee.h" // for MimeConverterOutputCallback
+
+#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 attachment parts if headers are also written
+ // via the parent, so that the header matches the encoding.
+ (obj->options->format_out == nsMimeOutput::nsMimeMessageRaw &&
+ obj->parent && obj->parent->output_p))
+ /* 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/comm/mailnews/mime/src/mimeleaf.h b/comm/mailnews/mime/src/mimeleaf.h
new file mode 100644
index 0000000000..cca651e3d0
--- /dev/null
+++ b/comm/mailnews/mime/src/mimeleaf.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#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/comm/mailnews/mime/src/mimemalt.cpp b/comm/mailnews/mime/src/mimemalt.cpp
new file mode 100644
index 0000000000..82e56ee6e8
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemalt.cpp
@@ -0,0 +1,549 @@
+/* -*- 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/. */
+
+/*
+ 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 immediately, 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 achieve 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 chosen.)
+
+ - 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
+#include "modmimee.h" // for MimeConverterOutputCallback
+
+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) {
+ for (int32_t i = 0; i < malt->pending_parts; i++) {
+ MimeHeaders* hdrs = malt->buffered_hdrs[i];
+ char* ct =
+ (hdrs ? MimeHeaders_get(hdrs, HEADER_CONTENT_TYPE, true, false) : 0);
+ bool display_part =
+ (i == 0) || (ct && !PL_strncasecmp(ct, "text/calendar", 13));
+ MimeMultipartAlternative_display_cached_part(obj, malt->buffered_hdrs[i],
+ malt->part_buffers[i],
+ do_display && display_part);
+ 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) {
+ if (obj->options) obj->options->is_child = true;
+
+ 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 pending_parts = malt->pending_parts;
+ int32_t max_parts = malt->max_parts;
+
+ int32_t i = pending_parts++;
+
+ if (i == 0) {
+ malt->buffered_priority = priority;
+ }
+
+ if (pending_parts > max_parts) {
+ max_parts = pending_parts;
+ MimeHeaders** newBuf = (MimeHeaders**)PR_REALLOC(
+ malt->buffered_hdrs, 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, 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);
+
+ malt->pending_parts = pending_parts;
+ malt->max_parts = max_parts;
+ 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;
+ }
+
+ /* 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, "rtf", 3)) {
+ return PRIORITY_HIGH;
+ }
+
+ if (!PL_strncasecmp(text_type, "calendar", 8)) {
+ // Prioritise text/calendar below text/html, etc. since we always show
+ // it anyway.
+ 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/comm/mailnews/mime/src/mimemalt.h b/comm/mailnews/mime/src/mimemalt.h
new file mode 100644
index 0000000000..622704d8f4
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemalt.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 _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/comm/mailnews/mime/src/mimemapl.cpp b/comm/mailnews/mime/src/mimemapl.cpp
new file mode 100644
index 0000000000..4fca1defe1
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemapl.cpp
@@ -0,0 +1,173 @@
+/* -*- 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 "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/comm/mailnews/mime/src/mimemapl.h b/comm/mailnews/mime/src/mimemapl.h
new file mode 100644
index 0000000000..28d29bf5f7
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimemcms.cpp b/comm/mailnews/mime/src/mimemcms.cpp
new file mode 100644
index 0000000000..d2e5b18f19
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemcms.cpp
@@ -0,0 +1,501 @@
+/* -*- 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 "nsIMsgSMIMEHeaderSink.h"
+#include "nsCOMPtr.h"
+#include "nsIX509Cert.h"
+#include "plstr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMailChannel.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;
+ bool reject_signature;
+ 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),
+ reject_signature(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, nsCString& msg_date);
+extern void MimeCMSRequestAsyncSignatureVerification(
+ nsICMSMessage* aCMSMsg, const char* aFromAddr, const char* aFromName,
+ const char* aSenderAddr, const char* aSenderName, const char* aMsgDate,
+ nsIMsgSMIMEHeaderSink* aHeaderSink, int32_t aMimeNestingLevel,
+ const nsCString& aMsgNeckoURL, const nsCString& aOriginMimePartNumber,
+ const nsTArray<uint8_t>& aDigestData, int16_t aDigestType);
+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;
+ 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")) {
+ nsCOMPtr<nsIMailChannel> mailChannel = do_QueryInterface(channel);
+ if (mailChannel) {
+ mailChannel->GetSmimeHeaderSink(
+ getter_AddRefs(data->smimeHeaderSink));
+ }
+ }
+ }
+ } // 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?
+ // We skip signature verfication, and return bad signature status.
+
+ // Note: We must return a valid pointer to ourselve's data,
+ // otherwise the parent will attempt to re-init us.
+
+ data->reject_signature = true;
+ if (data->smimeHeaderSink) {
+ int aRelativeNestLevel = MIMEGetRelativeCryptoNestLevel(data->self);
+ nsAutoCString partnum;
+ partnum.Adopt(mime_part_address(data->self));
+ data->smimeHeaderSink->SignedStatus(aRelativeNestLevel,
+ nsICMSMessageErrors::GENERAL_ERROR,
+ nullptr, data->url, partnum);
+ }
+ return data;
+ }
+
+ 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? */
+ }
+
+ bool allowSha1 = mozilla::Preferences::GetBool(
+ "mail.smime.accept_insecure_sha1_message_signatures", false);
+
+ if (allowSha1 && (!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 {
+ data->reject_signature = true;
+ if (data->smimeHeaderSink) {
+ int aRelativeNestLevel = MIMEGetRelativeCryptoNestLevel(data->self);
+ nsAutoCString partnum;
+ partnum.Adopt(mime_part_address(data->self));
+ data->smimeHeaderSink->SignedStatus(aRelativeNestLevel,
+ nsICMSMessageErrors::GENERAL_ERROR,
+ nullptr, data->url, partnum);
+ }
+ PR_Free(micalg);
+ return data;
+ }
+
+ PR_Free(micalg);
+ micalg = 0;
+
+ 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) {
+ return -1;
+ }
+
+ if (data->reject_signature) {
+ return 0;
+ }
+
+ if (!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) {
+ return -1;
+ }
+
+ if (data->reject_signature) {
+ return 0;
+ }
+
+ if (!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 (data->reject_signature) {
+ return 0;
+ }
+
+ 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) {
+ return -1;
+ }
+
+ if (data->reject_signature) {
+ return 0;
+ }
+
+ if (!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;
+ }
+
+ if (data->reject_signature) {
+ return 0;
+ }
+
+ /* 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) {
+ if (data->reject_signature) {
+ return;
+ }
+
+ nsAutoCString partnum;
+ partnum.Adopt(mime_part_address(data->self));
+ data->smimeHeaderSink->SignedStatus(
+ MIMEGetRelativeCryptoNestLevel(data->self),
+ nsICMSMessageErrors::GENERAL_ERROR, nullptr, data->url, partnum);
+ }
+}
+
+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;
+
+ if (aRelativeNestLevel >= 0) {
+ // maxWantedNesting 1: only want outermost nesting level
+ if (aRelativeNestLevel > 1) return nullptr;
+ }
+
+ nsAutoCString partnum;
+ partnum.Adopt(mime_part_address(data->self));
+
+ 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, partnum);
+ }
+ 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;
+ nsCString msg_date;
+
+ MimeCMSGetFromSender(data->self, from_addr, from_name, sender_addr,
+ sender_name, msg_date);
+
+ nsTArray<uint8_t> digest;
+ digest.AppendElements(data->item_data, data->item_len);
+
+ if (!data->reject_signature && data->smimeHeaderSink) {
+ MimeCMSRequestAsyncSignatureVerification(
+ data->content_info, from_addr.get(), from_name.get(), sender_addr.get(),
+ sender_name.get(), msg_date.get(), data->smimeHeaderSink,
+ aRelativeNestLevel, data->url, partnum, digest, 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/comm/mailnews/mime/src/mimemcms.h b/comm/mailnews/mime/src/mimemcms.h
new file mode 100644
index 0000000000..e8bfc6aac0
--- /dev/null
+++ b/comm/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; // for function arguments in mimemcms.c
+
+/* 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/comm/mailnews/mime/src/mimemdig.cpp b/comm/mailnews/mime/src/mimemdig.cpp
new file mode 100644
index 0000000000..89500e3074
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemdig.cpp
@@ -0,0 +1,22 @@
+/* -*- 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 "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/comm/mailnews/mime/src/mimemdig.h b/comm/mailnews/mime/src/mimemdig.h
new file mode 100644
index 0000000000..1701004611
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimemmix.cpp b/comm/mailnews/mime/src/mimemmix.cpp
new file mode 100644
index 0000000000..a4c6d22d6c
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemmix.cpp
@@ -0,0 +1,19 @@
+/* -*- 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 "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/comm/mailnews/mime/src/mimemmix.h b/comm/mailnews/mime/src/mimemmix.h
new file mode 100644
index 0000000000..6d24939ff6
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimemoz2.cpp b/comm/mailnews/mime/src/mimemoz2.cpp
new file mode 100644
index 0000000000..e5e4a6a644
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemoz2.cpp
@@ -0,0 +1,1862 @@
+/* -*- 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 "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 "nsIStringBundle.h"
+#include "nsString.h"
+#include "nsMimeStringResources.h"
+#include "nsStreamConverter.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 "nsMsgUtils.h"
+#include "nsIChannel.h"
+#include "nsIMailChannel.h"
+#include "mimeebod.h"
+#include "mimeeobj.h"
+// <for functions="HTML2Plaintext,HTMLSantinize">
+#include "nsXPCOM.h"
+#include "nsLayoutCID.h"
+#include "nsIParserUtils.h"
+// </for>
+#include "mozilla/Components.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) {}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Attachment handling routines
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+MimeObject* mime_get_main_object(MimeObject* obj);
+
+// Appends a "filename" parameter with the attachment name to the object url.
+void AppendFilenameParameterToAttachmentDataUrl(
+ const nsMsgAttachmentData* attachmentData, nsCString& url) {
+ url.AppendLiteral("&filename=");
+ nsAutoCString aResult;
+ if (NS_SUCCEEDED(MsgEscapeString(attachmentData->m_realName,
+ nsINetUtil::ESCAPE_XALPHAS, aResult))) {
+ url.Append(aResult);
+ } else {
+ url.Append(attachmentData->m_realName);
+ }
+ if (attachmentData->m_realType.EqualsLiteral("message/rfc822") &&
+ !StringEndsWith(url, ".eml"_ns, nsCaseInsensitiveCStringComparator)) {
+ url.AppendLiteral(".eml");
+ }
+}
+
+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 believe 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, "text"_ns,
+ 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);
+ nsCString urlString(tmpURL);
+ if (!tmp->m_realName.IsEmpty()) {
+ AppendFilenameParameterToAttachmentDataUrl(tmp, urlString);
+ }
+ rv = nsMimeNewURI(getter_AddRefs(tmp->m_url), urlString.get(), 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, "multipart"_ns,
+ 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, "http"_ns,
+ 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) {
+ AppendFilenameParameterToAttachmentDataUrl(tmp, urlString);
+ } else if (tmp->m_isExternalAttachment) {
+ // Allows the JS mime emitter to figure out the part information.
+ urlString.AppendLiteral("?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.AppendLiteral(".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.
+ *data = nullptr;
+ return rv;
+ }
+ }
+ rv = BuildAttachmentList((MimeObject*)cobj, *data, aMessageURL);
+ if (NS_FAILED(rv)) {
+ delete[] * data; // release data in case of error return.
+ *data = nullptr;
+ }
+ return rv;
+}
+
+extern "C" void NotifyEmittersOfAttachmentList(MimeDisplayOptions* opt,
+ nsMsgAttachmentData* data) {
+ 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.EqualsLiteral("attachment") &&
+ tmp->m_displayableInline &&
+ (tmp->m_realName.IsEmpty() ||
+ (!tmp->m_hasFilename &&
+ (opt->html_as_p != 4 || opt->metadata_only)))) {
+ ++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);
+ ++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::components::IO::Service();
+ 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) {
+ nsCOMPtr<nsIMailChannel> mailChannel = do_QueryInterface(msd->channel);
+ if (mailChannel) {
+ if (!PL_strcasecmp(aCharacterSet, "us-ascii")) {
+ mailChannel->SetMailCharacterSet("ISO-8859-1"_ns);
+ } else {
+ mailChannel->SetMailCharacterSet(nsCString(aCharacterSet));
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+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 ConvertToUTF8(const char* stringToUse, int32_t inLength,
+ const char* input_charset, nsACString& outString) {
+ nsresult rv = NS_OK;
+
+ // Look up Thunderbird's special aliases from charsetalias.properties.
+ nsCOMPtr<nsICharsetConverterManager> ccm =
+ do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ nsCString newCharset;
+ rv = ccm->GetCharsetAlias(input_charset, newCharset);
+ NS_ENSURE_SUCCESS(rv, -1);
+
+ if (newCharset.Equals("UTF-7", nsCaseInsensitiveCStringComparator)) {
+ nsAutoString utf16;
+ rv = CopyUTF7toUTF16(nsDependentCSubstring(stringToUse, inLength), utf16);
+ if (NS_FAILED(rv)) return -1;
+ CopyUTF16toUTF8(utf16, outString);
+ return 0;
+ }
+
+ auto encoding = mozilla::Encoding::ForLabel(newCharset);
+ NS_ENSURE_TRUE(encoding,
+ -1); // Impossible since GetCharsetAlias() already checked.
+
+ rv = encoding->DecodeWithoutBOMHandling(
+ nsDependentCSubstring(stringToUse, inLength), outString);
+ return NS_SUCCEEDED(rv) ? 0 : -1;
+}
+
+static int mime_convert_charset(const char* input_line, int32_t input_length,
+ const char* input_charset,
+ nsACString& convertedString,
+ void* stream_closure) {
+ return ConvertToUTF8(input_line, input_length, input_charset,
+ convertedString);
+}
+
+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;
+
+ 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);
+ }
+ delete[] 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) free(msd->url_name);
+
+ if (msd->orig_url_name) 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) free(msd->url_name);
+
+ if (msd->orig_url_name) 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;
+
+ PR_ASSERT(mid);
+ if (!mid) return 0;
+
+ /* Internal-external-reconnect only works when going to the screen. */
+ if (!mid->istream)
+ return strdup(
+ "<DIV CLASS=\"moz-attached-image-container\"><IMG "
+ "SRC=\"resource://gre-resources/loading-image.png\" "
+ "ALT=\"[Image]\"></DIV>");
+
+ const char* prefix;
+ const char* url;
+ char* buf;
+ /* Wouldn't it be nice if attributes were case-sensitive? */
+ const char* scaledPrefix =
+ "<DIV CLASS=\"moz-attached-image-container\"><IMG "
+ "CLASS=\"moz-attached-image\" shrinktofit=\"yes\" SRC=\"";
+ const char* suffix = "\"></DIV>";
+ // Thunderbird doesn't have this pref.
+#ifdef MOZ_SUITE
+ const char* unscaledPrefix =
+ "<DIV CLASS=\"moz-attached-image-container\"><IMG "
+ "CLASS=\"moz-attached-image\" SRC=\"";
+ 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;
+#else
+ prefix = scaledPrefix;
+#endif
+
+ 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 connection 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;
+ show_attachment_inline_text = 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;
+
+ // 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) {
+ nsAutoCString orgSpec;
+ msgUrl->GetOriginalSpec(orgSpec);
+ msd->orig_url_name = ToNewCString(orgSpec);
+ }
+ }
+ }
+
+ 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.inline_attachments.text",
+ &(msd->options->show_attachment_inline_text));
+ 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;
+ }
+
+ 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 ||
+ format_out == nsMimeOutput::nsMimeMessageQuoting ||
+ format_out == nsMimeOutput::nsMimeMessageBodyQuoting)
+ 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::components::StringBundle::Service();
+
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ stringBundleService->CreateBundle(MIME_URL, getter_AddRefs(stringBundle));
+ if (stringBundle) {
+ nsString v;
+ if (NS_SUCCEEDED(stringBundle->GetStringFromID(stringID, 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(
+ NS_ConvertUTF16toUTF8(stringName).get(), 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* cSet = MimeHeaders_get_parameter(ct, "charset", nullptr, nullptr);
+ if (cSet) {
+ // The content-type does specify a charset. 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.
+ if (msd->format_out == nsMimeOutput::nsMimeMessageSaveAs) {
+ // The previous version of this code would have entered an infinite
+ // loop. But it never showed up, so it's not clear that we ever get
+ // here... See bug #1597891.
+ PR_FREEIF(obj->options->default_charset);
+ obj->options->default_charset = cSet;
+ cSet = nullptr; // Ownership was transferred.
+ obj->options->override_charset = true;
+ MOZ_DIAGNOSTIC_ASSERT(
+ false, "Ahh. So this code _is_ run after all! (see bug 1597891)");
+ }
+ 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.AssignLiteral("us-ascii");
+ else
+ charset.Assign(text->charset);
+
+ nsCOMPtr<nsICharsetConverterManager> charSetConverterManager2;
+ 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(), fontLang);
+ if (NS_FAILED(rv)) return rv;
+
+ // get a font size from pref
+ prefStr.Assign(!styleFixed ? "font.size.variable."
+ : "font.size.monospace.");
+ 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/comm/mailnews/mime/src/mimemoz2.h b/comm/mailnews/mime/src/mimemoz2.h
new file mode 100644
index 0000000000..daec700240
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemoz2.h
@@ -0,0 +1,207 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "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 */
+};
+
+//
+// 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;
+ nsCString originalMsgURI; // the original URI of the message we are currently
+ // processing
+ nsCOMPtr<nsIMsgDBHdr> origMsgHdr;
+ bool autodetectCharset; // Used to indicate pending autodetection while
+ // streaming contents.
+};
+
+////////////////////////////////////////////////////////////////
+// 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 connection 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/comm/mailnews/mime/src/mimempar.cpp b/comm/mailnews/mime/src/mimempar.cpp
new file mode 100644
index 0000000000..2c27e1e95d
--- /dev/null
+++ b/comm/mailnews/mime/src/mimempar.cpp
@@ -0,0 +1,20 @@
+/* -*- 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 "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/comm/mailnews/mime/src/mimempar.h b/comm/mailnews/mime/src/mimempar.h
new file mode 100644
index 0000000000..7c29f67331
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimemrel.cpp b/comm/mailnews/mime/src/mimemrel.cpp
new file mode 100644
index 0000000000..b3efb3323d
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemrel.cpp
@@ -0,0 +1,1113 @@
+/* -*- 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/.
+ * 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 "nsString.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;
+
+ 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* base_url =
+ MimeHeaders_get(child->headers, HEADER_CONTENT_BASE, false, false);
+ char* 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 therefore we will have
+ to hope the datafork is the second one!
+ */
+ if (mime_typep(child,
+ (MimeObjectClass*)&mimeMultipartAppleDoubleClass))
+ partnum.AppendLiteral(".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) {
+ // Mozilla platform code will correct the file extension
+ // when copying the embedded image. That doesn't work
+ // since our MailNews URLs don't allow setting the file
+ // extension. So provide a filename and valid extension.
+ char* ct = MimeHeaders_get(child->headers, HEADER_CONTENT_TYPE,
+ false, false);
+ if (ct) {
+ name = ct;
+ char* slash = strchr(name, '/');
+ if (slash) *slash = '.';
+ char* semi = strchr(name, ';');
+ if (semi) *semi = 0;
+ }
+ }
+ 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 careful 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. */
+ relobj->head_loaded = true;
+ relobj->headobj = child;
+ relobj->buffered_hdrs = MimeHeaders_copy(child->headers);
+ char* base_url =
+ MimeHeaders_get(child->headers, HEADER_CONTENT_BASE, false, false);
+ if (!base_url) {
+ base_url = MimeHeaders_get(child->headers, HEADER_CONTENT_LOCATION, false,
+ false);
+ }
+
+ 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);
+ nsCOMPtr<nsIURI> url;
+ nsresult rv = nsMimeNewURI(getter_AddRefs(url), base_url, nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ 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;
+ }
+
+ // Don't actually parse this child; we'll handle all that at eof time.
+ return false;
+}
+
+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 = 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 = 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/comm/mailnews/mime/src/mimemrel.h b/comm/mailnews/mime/src/mimemrel.h
new file mode 100644
index 0000000000..e861e7268c
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemrel.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "modmimee.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/comm/mailnews/mime/src/mimemsg.cpp b/comm/mailnews/mime/src/mimemsg.cpp
new file mode 100644
index 0000000000..fcd86651f7
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemsg.cpp
@@ -0,0 +1,847 @@
+/* -*- 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 "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 "nsString.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)) {
+ 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, 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 (without 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/comm/mailnews/mime/src/mimemsg.h b/comm/mailnews/mime/src/mimemsg.h
new file mode 100644
index 0000000000..ec8c45336c
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemsg.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/. */
+
+#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/comm/mailnews/mime/src/mimemsig.cpp b/comm/mailnews/mime/src/mimemsig.cpp
new file mode 100644
index 0000000000..5f390abd94
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemsig.cpp
@@ -0,0 +1,716 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "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 "modmimee.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) {
+ obj->closed_p = true;
+ 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 preceding 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. */
+ [[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 preceded 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 preceding 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. */
+ [[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");
+ [[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 */
+ [[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 preceded 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 preceding 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");
+ [[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/comm/mailnews/mime/src/mimemsig.h b/comm/mailnews/mime/src/mimemsig.h
new file mode 100644
index 0000000000..5581778f80
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimemult.cpp b/comm/mailnews/mime/src/mimemult.cpp
new file mode 100644
index 0000000000..29caf5f5b0
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemult.cpp
@@ -0,0 +1,676 @@
+/* -*- 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 "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, nsLiteralCString(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));
+ // clang-format off
+ if (detachingPart) {
+ char *contentType =
+ MimeHeaders_get(mult->hdrs, "Content-Type", false, false);
+ if (contentType) {
+ MimeWriteAString(obj, "Content-Type: "_ns);
+ MimeWriteAString(obj, nsDependentCString(contentType));
+ PR_Free(contentType);
+ }
+ MimeWriteAString(obj, nsLiteralCString(MSG_LINEBREAK));
+ MimeWriteAString(obj, "Content-Disposition: attachment; filename=\""_ns);
+ MimeWriteAString(obj, fileName);
+ MimeWriteAString(obj, "\""_ns MSG_LINEBREAK);
+ MimeWriteAString(obj, "X-Mozilla-External-Attachment-URL: "_ns);
+ MimeWriteAString(obj, obj->options->state->detachedFilePath);
+ MimeWriteAString(obj, nsLiteralCString(MSG_LINEBREAK));
+ MimeWriteAString(obj, "X-Mozilla-Altered: AttachmentDetached; date=\""_ns);
+ } else {
+ nsAutoCString header("Content-Type: text/x-moz-deleted; name=\"Deleted: ");
+ header.Append(fileName);
+ MimeWriteAString(obj, header);
+ MimeWriteAString(obj, "\""_ns MSG_LINEBREAK
+ "Content-Transfer-Encoding: 8bit"_ns MSG_LINEBREAK);
+ MimeWriteAString(obj, "Content-Disposition: inline; filename=\"Deleted: "_ns);
+ MimeWriteAString(obj, fileName);
+ MimeWriteAString(obj, "\""_ns MSG_LINEBREAK
+ "X-Mozilla-Altered: AttachmentDeleted; date=\""_ns);
+ }
+ 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 MSG_LINEBREAK);
+ MimeWriteAString(obj, MSG_LINEBREAK
+ "You deleted an attachment from this message. The original "_ns
+ "MIME headers for the attachment were:"_ns MSG_LINEBREAK);
+ MimeHeaders_write_raw_headers(mult->hdrs, obj->options, false);
+ // clang-format on
+ }
+ 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) &&
+ /* 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 termination 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) &&
+ !((mime_typep(body, (MimeObjectClass*)&mimeExternalObjectClass) ||
+ mime_typep(body, (MimeObjectClass*)&mimeSuppressedCryptoClass)) &&
+ (!strcmp(body->content_type, "text/vcard") ||
+ !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
+ * content 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(nsLiteralCString(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) {
+ // clang-format off
+ if (!mime_typep(object, (MimeObjectClass *)&mimeMultipartRelatedClass) &&
+ !mime_typep(object, (MimeObjectClass *)&mimeMultipartAlternativeClass) &&
+ !mime_typep(object, (MimeObjectClass *)&mimeMultipartSignedClass) &&
+ /* 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 termination 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) &&
+ !((mime_typep(kid, (MimeObjectClass *)&mimeExternalObjectClass) ||
+ mime_typep(kid, (MimeObjectClass *)&mimeSuppressedCryptoClass)) &&
+ (!strcmp(kid->content_type, "text/vcard") ||
+ !strcmp(kid->content_type, "text/x-vcard")))) {
+ status = object->options->decompose_file_close_fn(
+ object->options->stream_closure);
+ if (status < 0) return status;
+ }
+ // clang-format on
+ }
+#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) &&
+ /* 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 termination 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) &&
+ !((mime_typep(kid, (MimeObjectClass*)&mimeExternalObjectClass) ||
+ mime_typep(kid, (MimeObjectClass*)&mimeSuppressedCryptoClass)) &&
+ (!strcmp(kid->content_type, "text/vcard") ||
+ !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 preceded 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 preceding 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/comm/mailnews/mime/src/mimemult.h b/comm/mailnews/mime/src/mimemult.h
new file mode 100644
index 0000000000..9afa74885a
--- /dev/null
+++ b/comm/mailnews/mime/src/mimemult.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/comm/mailnews/mime/src/mimeobj.cpp b/comm/mailnews/mime/src/mimeobj.cpp
new file mode 100644
index 0000000000..7ad7e290d1
--- /dev/null
+++ b/comm/mailnews/mime/src/mimeobj.cpp
@@ -0,0 +1,301 @@
+/* -*- 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/.
+ * 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 (abort_p) {
+ obj->closed_p = true;
+ return 0;
+ }
+ 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 (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/comm/mailnews/mime/src/mimeobj.h b/comm/mailnews/mime/src/mimeobj.h
new file mode 100644
index 0000000000..9a74481c65
--- /dev/null
+++ b/comm/mailnews/mime/src/mimeobj.h
@@ -0,0 +1,182 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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
+ information 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/comm/mailnews/mime/src/mimepbuf.cpp b/comm/mailnews/mime/src/mimepbuf.cpp
new file mode 100644
index 0000000000..c428f66eb4
--- /dev/null
+++ b/comm/mailnews/mime/src/mimepbuf.cpp
@@ -0,0 +1,251 @@
+/* -*- 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 "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()
+ : part_buffer(nullptr), part_buffer_fp(0), part_buffer_size(0) {}
+};
+
+MimePartBufferData* MimePartBufferCreate(void) {
+ return new MimePartBufferData();
+}
+
+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);
+ delete 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 = 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 = 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/comm/mailnews/mime/src/mimepbuf.h b/comm/mailnews/mime/src/mimepbuf.h
new file mode 100644
index 0000000000..b0e19ca379
--- /dev/null
+++ b/comm/mailnews/mime/src/mimepbuf.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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/comm/mailnews/mime/src/mimesun.cpp b/comm/mailnews/mime/src/mimesun.cpp
new file mode 100644
index 0000000000..03e25336c9
--- /dev/null
+++ b/comm/mailnews/mime/src/mimesun.cpp
@@ -0,0 +1,313 @@
+/* -*- 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 "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) {
+ if (obj->options) obj->options->is_child = true;
+
+ 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/comm/mailnews/mime/src/mimesun.h b/comm/mailnews/mime/src/mimesun.h
new file mode 100644
index 0000000000..8d3b15bd1c
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimetenr.cpp b/comm/mailnews/mime/src/mimetenr.cpp
new file mode 100644
index 0000000000..600cec7397
--- /dev/null
+++ b/comm/mailnews/mime/src/mimetenr.cpp
@@ -0,0 +1,27 @@
+/* -*- 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 "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/comm/mailnews/mime/src/mimetenr.h b/comm/mailnews/mime/src/mimetenr.h
new file mode 100644
index 0000000000..9ff2d6eab4
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimetext.cpp b/comm/mailnews/mime/src/mimetext.cpp
new file mode 100644
index 0000000000..d13b8f8eb4
--- /dev/null
+++ b/comm/mailnews/mime/src/mimetext.cpp
@@ -0,0 +1,442 @@
+/* -*- 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/.
+ * 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 "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) {
+ if (obj->options->default_charset) {
+ text->charset = strdup(obj->options->default_charset);
+ } else {
+ text->charsetOverridable = true;
+ text->inputAutodetect = true;
+ text->needUpdateMsgWinCharset = true;
+ text->charset = strdup("");
+ }
+ } 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) {
+ text->charsetOverridable = true;
+ text->inputAutodetect = true;
+ text->needUpdateMsgWinCharset = true;
+
+ if (obj->options && obj->options->default_charset)
+ text->charset = strdup(obj->options->default_charset);
+ else
+ text->charset = strdup("UTF-8");
+ }
+ }
+ }
+
+ 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);
+
+ 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 found 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 found charset yet? Now is 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;
+ nsAutoCString converted;
+
+ MimeInlineText* text = (MimeInlineText*)obj;
+
+ // In case of charset autodetection, charset can be overridden 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.
+ 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);
+ }
+ }
+ }
+
+ status = obj->options->charset_conversion_fn(
+ line, length, text->charset, converted, obj->options->stream_closure);
+
+ if (status == 0) {
+ line = (char*)converted.get();
+ length = converted.Length();
+ }
+
+ // 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);
+
+ 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;
+ nsAutoCString detectedCharset;
+ 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 object.
+ if (NS_SUCCEEDED(res) && !detectedCharset.IsEmpty()) {
+ PR_FREEIF(text->charset);
+ text->charset = ToNewCString(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/comm/mailnews/mime/src/mimetext.h b/comm/mailnews/mime/src/mimetext.h
new file mode 100644
index 0000000000..d3a7a29677
--- /dev/null
+++ b/comm/mailnews/mime/src/mimetext.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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;
+
+ 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/comm/mailnews/mime/src/mimethpl.cpp b/comm/mailnews/mime/src/mimethpl.cpp
new file mode 100644
index 0000000000..d21efb82bb
--- /dev/null
+++ b/comm/mailnews/mime/src/mimethpl.cpp
@@ -0,0 +1,151 @@
+/* -*- 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/. */
+
+/* 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 "nsString.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 synchronously, 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 asynchronously, 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/comm/mailnews/mime/src/mimethpl.h b/comm/mailnews/mime/src/mimethpl.h
new file mode 100644
index 0000000000..8a06e6a2b4
--- /dev/null
+++ b/comm/mailnews/mime/src/mimethpl.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsString.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/comm/mailnews/mime/src/mimethsa.cpp b/comm/mailnews/mime/src/mimethsa.cpp
new file mode 100644
index 0000000000..19c1e5460a
--- /dev/null
+++ b/comm/mailnews/mime/src/mimethsa.cpp
@@ -0,0 +1,127 @@
+/* -*- 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/. */
+
+/* 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 "nsString.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/comm/mailnews/mime/src/mimethsa.h b/comm/mailnews/mime/src/mimethsa.h
new file mode 100644
index 0000000000..5d4f318fd2
--- /dev/null
+++ b/comm/mailnews/mime/src/mimethsa.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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"
+#include "nsString.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/comm/mailnews/mime/src/mimethtm.cpp b/comm/mailnews/mime/src/mimethtm.cpp
new file mode 100644
index 0000000000..e35b6a6446
--- /dev/null
+++ b/comm/mailnews/mime/src/mimethtm.cpp
@@ -0,0 +1,229 @@
+/* -*- 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 "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.LowerCaseFindASCII("<body");
+ 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' attribute 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(
+ "<div class=\"moz-text-html\" lang=\""_ns + fontLang + "\">"_ns, index);
+ } else {
+ message.Insert("<div class=\"moz-text-html\">"_ns, index);
+ }
+
+ nsACString::const_iterator begin, end;
+ message.BeginReading(begin);
+ message.EndReading(end);
+ nsACString::const_iterator messageBegin = begin;
+ if (RFindInReadable("</body>"_ns, begin, end,
+ nsCaseInsensitiveCStringComparator)) {
+ message.InsertLiteral("</div>", begin - messageBegin);
+ }
+}
+
+/*
+ * 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.LowerCaseFindASCII("<plaintext", index)) !=
+ kNotFound) {
+ message.Insert("x-", index + 1);
+ index += 12;
+ replaced = true;
+ }
+ if (replaced) {
+ index = 0;
+ while ((index = message.LowerCaseFindASCII("</plaintext", index)) !=
+ kNotFound) {
+ message.Insert("x-", index + 2);
+ index += 13;
+ }
+ }
+}
diff --git a/comm/mailnews/mime/src/mimethtm.h b/comm/mailnews/mime/src/mimethtm.h
new file mode 100644
index 0000000000..f5e178a937
--- /dev/null
+++ b/comm/mailnews/mime/src/mimethtm.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _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/comm/mailnews/mime/src/mimetpfl.cpp b/comm/mailnews/mime/src/mimetpfl.cpp
new file mode 100644
index 0000000000..d354217e55
--- /dev/null
+++ b/comm/mailnews/mime/src/mimetpfl.cpp
@@ -0,0 +1,613 @@
+/* -*- 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 "mimetpfl.h"
+#include "mimebuf.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "mozITXTToHTMLConv.h"
+#include "nsString.h"
+#include "nsMimeStringResources.h"
+#include "nsIPrefBranch.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
+ nsACString& 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.Truncate(); // 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);
+
+ // Clear mCitationColor
+ MimeInlineTextPlainFlowed* text = (MimeInlineTextPlainFlowed*)obj;
+ text->mCitationColor.Truncate();
+
+ 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) {
+ line++;
+ linep++;
+ length--;
+ } 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(nsDependentCString(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, whattodo, 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(nsDependentCString(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.Append(' ');
+ }
+
+ 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/comm/mailnews/mime/src/mimetpfl.h b/comm/mailnews/mime/src/mimetpfl.h
new file mode 100644
index 0000000000..110808e63d
--- /dev/null
+++ b/comm/mailnews/mime/src/mimetpfl.h
@@ -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/. */
+
+#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
+ nsCString 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/comm/mailnews/mime/src/mimetpla.cpp b/comm/mailnews/mime/src/mimetpla.cpp
new file mode 100644
index 0000000000..081d7951a4
--- /dev/null
+++ b/comm/mailnews/mime/src/mimetpla.cpp
@@ -0,0 +1,409 @@
+/* -*- 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 "mimetpla.h"
+#include "mimebuf.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "mozITXTToHTMLConv.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsMimeStringResources.h"
+#include "mimemoz2.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
+ nsACString& citationColor, // mail.citation_color
+ nsACString& style) {
+ switch (quotedStyleSetting) {
+ case 0: // regular
+ break;
+ case 1: // bold
+ style.AppendLiteral("font-weight: bold; ");
+ break;
+ case 2: // italic
+ style.AppendLiteral("font-style: italic; ");
+ break;
+ case 3: // bold-italic
+ style.AppendLiteral("font-weight: bold; font-style: italic; ");
+ break;
+ }
+
+ switch (quotedSizeSetting) {
+ case 0: // regular
+ break;
+ case 1: // large
+ style.AppendLiteral("font-size: large; ");
+ break;
+ case 2: // small
+ style.AppendLiteral("font-size: small; ");
+ break;
+ }
+
+ if (!citationColor.IsEmpty()) {
+ 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.Truncate(); // 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 class=\"moz-quote-pre\">\n";
+ } else
+ openingDiv = "<pre wrap class=\"moz-quote-pre\">\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.IsEmpty())
+ citationColor = 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) {
+ nsDependentCSubstring 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(nsDependentCString(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 class=\"moz-quote-pre\">\n";
+ } else if (text->mCiteLevel < oldCiteLevel) {
+ prefaceResultStr += "</pre>";
+ for (uint32_t i = 0; i < oldCiteLevel - text->mCiteLevel; i++)
+ prefaceResultStr += "</blockquote>";
+ prefaceResultStr += "<pre wrap class=\"moz-quote-pre\">\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, 0 /* no recognition */,
+ 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(Substring(lineSourceStr, logicalLineStart), whattodo,
+ 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(nsDependentCString(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/comm/mailnews/mime/src/mimetpla.h b/comm/mailnews/mime/src/mimetpla.h
new file mode 100644
index 0000000000..de70a277ec
--- /dev/null
+++ b/comm/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
+ nsCString 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/comm/mailnews/mime/src/mimetric.cpp b/comm/mailnews/mime/src/mimetric.cpp
new file mode 100644
index 0000000000..79eedea75d
--- /dev/null
+++ b/comm/mailnews/mime/src/mimetric.cpp
@@ -0,0 +1,356 @@
+/* -*- 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 "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 superseded 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 preceding 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, 6))
+ // 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=JUSTIFY>";
+ 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 (!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>";
+ }
+ 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>";
+ }
+ 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/comm/mailnews/mime/src/mimetric.h b/comm/mailnews/mime/src/mimetric.h
new file mode 100644
index 0000000000..6ec0961aed
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/mimeunty.cpp b/comm/mailnews/mime/src/mimeunty.cpp
new file mode 100644
index 0000000000..6cdea05268
--- /dev/null
+++ b/comm/mailnews/mime/src/mimeunty.cpp
@@ -0,0 +1,523 @@
+/* -*- 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 "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 going to 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++;
+
+ int name_len = (line + length) - s;
+
+ name = (char*)PR_MALLOC(name_len + 1);
+ if (!name) return false; /* grr... */
+ memcpy(name, s, name_len);
+ name[name_len] = 0;
+
+ if (name_len) {
+ /* take off newline. */
+ if (name_len && name[name_len - 1] == '\n') {
+ name[name_len] = 0;
+ name_len--;
+ }
+ if (name_len && name[name_len - 1] == '\r') {
+ name[name_len] = 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;
+
+ int name_len = endofline - s;
+
+ name = (char*)PR_MALLOC(name_len + 1);
+ if (!name) return false; /* grr... */
+ memcpy(name, s, name_len);
+ name[name_len] = 0;
+
+ if (name_len) {
+ /* take off newline. */
+ if (name_len && name[name_len - 1] == '\n') {
+ name[name_len] = 0;
+ name_len--;
+ }
+ if (name_len && name[name_len - 1] == '\r') {
+ name[name_len] = 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/comm/mailnews/mime/src/mimeunty.h b/comm/mailnews/mime/src/mimeunty.h
new file mode 100644
index 0000000000..580cbbb8db
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/modlmime.h b/comm/mailnews/mime/src/modlmime.h
new file mode 100644
index 0000000000..14d53c9cd6
--- /dev/null
+++ b/comm/mailnews/mime/src/modlmime.h
@@ -0,0 +1,388 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsString.h"
+#include "nsMailHeaders.h"
+#include "nsIMimeStreamConverter.h"
+#include "mozilla/Encoding.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 heads_size 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 number of entries (pointers) in the heads
+ array (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);
+
+// clang-format off
+/* 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"
+ */
+// clang-format on
+
+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
+
+ 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.)
+ The conversion is always to UTF-8.
+ `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,
+ nsACString& output_ret, void* stream_closure);
+
+ /* 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 show_attachment_inline_text; /* Whether or not we should display text
+ attachment inline (whatever the
+ content-disposition says) */
+
+ 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/comm/mailnews/mime/src/modmimee.h b/comm/mailnews/mime/src/modmimee.h
new file mode 100644
index 0000000000..14c6933265
--- /dev/null
+++ b/comm/mailnews/mime/src/modmimee.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/*
+ 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/comm/mailnews/mime/src/moz.build b/comm/mailnews/mime/src/moz.build
new file mode 100644
index 0000000000..7bd35ca1c6
--- /dev/null
+++ b/comm/mailnews/mime/src/moz.build
@@ -0,0 +1,88 @@
+# 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",
+ "nsMimeObjectClassAccess.cpp",
+ "nsSimpleMimeConverterStub.cpp",
+ "nsStreamConverter.cpp",
+]
+
+EXTRA_COMPONENTS += [
+ "msgMime.manifest",
+]
+
+EXTRA_JS_MODULES += [
+ "extraMimeParsers.jsm",
+ "jsmime.jsm",
+ "MimeJSComponents.jsm",
+ "mimeParser.jsm",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+FINAL_LIBRARY = "mail"
+
+DEFINES["ENABLE_SMIME"] = True
diff --git a/comm/mailnews/mime/src/msgMime.manifest b/comm/mailnews/mime/src/msgMime.manifest
new file mode 100644
index 0000000000..c6060c321b
--- /dev/null
+++ b/comm/mailnews/mime/src/msgMime.manifest
@@ -0,0 +1 @@
+category custom-mime-encoder A-extra resource:///modules/extraMimeParsers.jsm
diff --git a/comm/mailnews/mime/src/nsMimeObjectClassAccess.cpp b/comm/mailnews/mime/src/nsMimeObjectClassAccess.cpp
new file mode 100644
index 0000000000..a769ae192e
--- /dev/null
+++ b/comm/mailnews/mime/src/nsMimeObjectClassAccess.cpp
@@ -0,0 +1,75 @@
+/* -*- 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/comm/mailnews/mime/src/nsMimeObjectClassAccess.h b/comm/mailnews/mime/src/nsMimeObjectClassAccess.h
new file mode 100644
index 0000000000..d131ffa1a6
--- /dev/null
+++ b/comm/mailnews/mime/src/nsMimeObjectClassAccess.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/. */
+
+/*
+ * 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/comm/mailnews/mime/src/nsMimeStringResources.h b/comm/mailnews/mime/src/nsMimeStringResources.h
new file mode 100644
index 0000000000..7857086093
--- /dev/null
+++ b/comm/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/comm/mailnews/mime/src/nsSimpleMimeConverterStub.cpp b/comm/mailnews/mime/src/nsSimpleMimeConverterStub.cpp
new file mode 100644
index 0000000000..0ff3bd686a
--- /dev/null
+++ b/comm/mailnews/mime/src/nsSimpleMimeConverterStub.cpp
@@ -0,0 +1,191 @@
+/* -*- 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 "mimecth.h"
+#include "mimeobj.h"
+#include "mimetext.h"
+#include "mimemoz2.h"
+#include "mimecom.h"
+#include "nsString.h"
+#include "nsComponentManagerUtils.h"
+#include "nsICategoryManager.h"
+#include "nsCOMPtr.h"
+#include "nsISimpleMimeConverter.h"
+#include "nsServiceManagerUtils.h"
+#include "nsSimpleMimeConverterStub.h"
+#include "nsIMailChannel.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, length);
+ 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);
+
+ nsCOMPtr<nsIMailChannel> mailChannel = do_QueryInterface(channel);
+ ssobj->innerScriptable->SetMailChannel(mailChannel);
+ }
+ // Remove possible embedded NULL bytes.
+ // Parsers can't handle this but e.g. calendar invitation might contain such
+ // as fillers.
+ ssobj->buffer->StripChar('\0');
+ 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,
+ 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);
+
+ inst.forget(aResult);
+ return NS_OK;
+}
diff --git a/comm/mailnews/mime/src/nsSimpleMimeConverterStub.h b/comm/mailnews/mime/src/nsSimpleMimeConverterStub.h
new file mode 100644
index 0000000000..ec32091598
--- /dev/null
+++ b/comm/mailnews/mime/src/nsSimpleMimeConverterStub.h
@@ -0,0 +1,12 @@
+/* -*- 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/comm/mailnews/mime/src/nsStreamConverter.cpp b/comm/mailnews/mime/src/nsStreamConverter.cpp
new file mode 100644
index 0000000000..7311295402
--- /dev/null
+++ b/comm/mailnews/mime/src/nsStreamConverter.cpp
@@ -0,0 +1,981 @@
+/* -*- 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 "nsString.h"
+#include "nsUnicharUtils.h"
+#include "nsMemory.h"
+#include "nsIPipe.h"
+#include "nsMimeStringResources.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsNetUtil.h"
+#include "nsIMsgQuote.h"
+#include "nsNetUtil.h"
+#include "mozITXTToHTMLConv.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsINntpUrl.h"
+#include "nsIMsgWindow.h"
+#include "nsICategoryManager.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) {
+ // Check whether we need to auto-detect the charset.
+ nsCOMPtr<nsIMsgI18NUrl> i18nUrl(do_QueryInterface(aURI));
+ if (i18nUrl) {
+ bool autodetectCharset = false;
+ nsresult rv = i18nUrl->GetAutodetectCharset(&autodetectCharset);
+ if (NS_SUCCEEDED(rv) && autodetectCharset) {
+ *override_charset = true;
+ *default_charset = nullptr;
+ } else {
+ *override_charset = false;
+ // Special treatment for news: URLs. Get the server default charset.
+ nsCOMPtr<nsINntpUrl> nntpURL(do_QueryInterface(aURI));
+ if (nntpURL) {
+ nsCString charset;
+ rv = nntpURL->GetCharset(charset);
+ if (NS_SUCCEEDED(rv)) {
+ *default_charset = ToNewCString(charset);
+ } else {
+ *default_charset = strdup("UTF-8");
+ }
+ } else {
+ *default_charset = strdup("UTF-8");
+ }
+ }
+ }
+ nsAutoCString urlString;
+ if (NS_SUCCEEDED(aURI->GetSpec(urlString))) {
+ if (!urlString.IsEmpty()) {
+ 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);
+ mOutputFormat.ReplaceSubstring("%2F", "/");
+ mOutputFormat.ReplaceSubstring("%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.EqualsLiteral("application/xhtml+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.EqualsLiteral("message/rfc822")) {
+ mRealContentType = "application/x-message-display";
+ mOutputFormat = "text/html";
+ *aNewType = nsMimeOutput::nsMimeMessageBodyDisplay;
+ } else if (mRealContentType.EqualsLiteral(
+ "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;
+}
+
+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;
+ mOutgoingChannel = aChannel;
+
+ // 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->GetSpecIgnoringRef(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, 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.EqualsLiteral("raw"))
+ *aOutputContentType =
+ (char*)moz_xmemdup(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!
+ NS_IF_ADDREF(*aIdentity = mIdentity);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::SetIdentity(nsIMsgIdentity* aIdentity) {
+ mIdentity = aIdentity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::SetOriginalMsgURI(const nsACString& originalMsgURI) {
+ mOriginalMsgURI = originalMsgURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStreamConverter::GetOriginalMsgURI(nsACString& result) {
+ result = 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 whenever data is written into the input stream by the
+// networking library...
+//
+nsresult nsStreamConverter::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* aIStream,
+ uint64_t sourceOffset,
+ uint32_t aLength) {
+ nsresult rc = NS_OK; // should this be an error instead?
+ 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;
+ }
+
+ nsCOMPtr<nsIInputStream> stream = aIStream;
+ NS_ENSURE_TRUE(stream, NS_ERROR_NULL_POINTER);
+ char* buf = (char*)PR_Malloc(aLength);
+ if (!buf) return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */
+
+ uint32_t readLen;
+ stream->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) {
+#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;
+ } else
+ mOutListener->OnStartRequest(request);
+ }
+
+ 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,
+ nsresult status) {
+ // Make sure we fire any pending OnStartRequest before we do OnStop.
+ FirePendingStartRequest();
+
+ //
+ // 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();
+
+ if (mOutgoingChannel) {
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mOutgoingChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) loadGroup->RemoveRequest(mOutgoingChannel, nullptr, status);
+ }
+
+ // Make sure to do necessary cleanup!
+ InternalCleanup();
+
+ // forward on top request to any listeners
+ if (mOutListener) mOutListener->OnStopRequest(request, 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 synchronous 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);
+ mPendingRequest = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsStreamConverter::GetConvertedType(const nsACString& aFromType,
+ nsIChannel* aChannel,
+ nsACString& aToType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/comm/mailnews/mime/src/nsStreamConverter.h b/comm/mailnews/mime/src/nsStreamConverter.h
new file mode 100644
index 0000000000..1d2e1bcccc
--- /dev/null
+++ b/comm/mailnews/mime/src/nsStreamConverter.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsString.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
+};
+
+#endif /* nsStreamConverter_h_ */