From 302bf1b523012e11b60425d6eee1221ebc2724eb Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Sun, 3 Nov 2019 00:17:46 -0400 Subject: Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1 --- mailnews/mime/cthandlers/glue/mimexpcom.cpp | 132 + mailnews/mime/cthandlers/glue/mimexpcom.h | 93 + mailnews/mime/cthandlers/glue/moz.build | 18 + .../cthandlers/glue/nsMimeContentTypeHandler.cpp | 60 + .../cthandlers/glue/nsMimeContentTypeHandler.h | 47 + mailnews/mime/cthandlers/moz.build | 12 + mailnews/mime/cthandlers/pgpmime/moz.build | 20 + .../mime/cthandlers/pgpmime/nsPgpMimeProxy.cpp | 634 ++++ mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.h | 69 + mailnews/mime/cthandlers/vcard/mimevcrd.cpp | 378 +++ mailnews/mime/cthandlers/vcard/mimevcrd.h | 33 + mailnews/mime/cthandlers/vcard/moz.build | 14 + mailnews/mime/emitters/moz.build | 21 + mailnews/mime/emitters/nsEmitterUtils.cpp | 67 + mailnews/mime/emitters/nsEmitterUtils.h | 14 + mailnews/mime/emitters/nsMimeBaseEmitter.cpp | 1092 +++++++ mailnews/mime/emitters/nsMimeBaseEmitter.h | 147 + mailnews/mime/emitters/nsMimeEmitterCID.h | 51 + mailnews/mime/emitters/nsMimeHtmlEmitter.cpp | 543 ++++ mailnews/mime/emitters/nsMimeHtmlEmitter.h | 64 + mailnews/mime/emitters/nsMimePlainEmitter.cpp | 64 + mailnews/mime/emitters/nsMimePlainEmitter.h | 31 + mailnews/mime/emitters/nsMimeRawEmitter.cpp | 34 + mailnews/mime/emitters/nsMimeRawEmitter.h | 29 + mailnews/mime/emitters/nsMimeRebuffer.cpp | 50 + mailnews/mime/emitters/nsMimeRebuffer.h | 29 + mailnews/mime/emitters/nsMimeXmlEmitter.cpp | 184 ++ mailnews/mime/emitters/nsMimeXmlEmitter.h | 47 + mailnews/mime/jsmime/LICENSE | 19 + mailnews/mime/jsmime/README.md | 59 + mailnews/mime/jsmime/jsmime.js | 3300 ++++++++++++++++++++ mailnews/mime/moz.build | 15 + mailnews/mime/public/MimeEncoder.h | 44 + mailnews/mime/public/MimeHeaderParser.h | 174 ++ mailnews/mime/public/moz.build | 36 + mailnews/mime/public/msgIStructuredHeaders.idl | 209 ++ mailnews/mime/public/nsICMSDecoder.idl | 29 + mailnews/mime/public/nsICMSEncoder.idl | 30 + mailnews/mime/public/nsICMSMessage.idl | 39 + mailnews/mime/public/nsICMSMessage2.idl | 64 + mailnews/mime/public/nsICMSMessageErrors.idl | 35 + mailnews/mime/public/nsICMSSecureMessage.idl | 42 + mailnews/mime/public/nsIMimeContentTypeHandler.h | 65 + mailnews/mime/public/nsIMimeConverter.idl | 75 + mailnews/mime/public/nsIMimeEmitter.idl | 81 + mailnews/mime/public/nsIMimeHeaders.idl | 41 + mailnews/mime/public/nsIMimeMiscStatus.idl | 76 + mailnews/mime/public/nsIMimeObjectClassAccess.h | 49 + mailnews/mime/public/nsIMimeStreamConverter.idl | 93 + mailnews/mime/public/nsIMsgHeaderParser.idl | 235 ++ mailnews/mime/public/nsIPgpMimeProxy.idl | 69 + mailnews/mime/public/nsISimpleMimeConverter.idl | 22 + mailnews/mime/public/nsMailHeaders.h | 90 + mailnews/mime/public/nsMsgMimeCID.h | 33 + mailnews/mime/src/MimeHeaderParser.cpp | 229 ++ mailnews/mime/src/comi18n.cpp | 108 + mailnews/mime/src/comi18n.h | 42 + mailnews/mime/src/extraMimeParsers.jsm | 29 + mailnews/mime/src/jsmime.jsm | 90 + mailnews/mime/src/mime.def | 7 + mailnews/mime/src/mimeJSComponents.js | 512 +++ mailnews/mime/src/mimeParser.jsm | 258 ++ mailnews/mime/src/mimeTextHTMLParsed.cpp | 150 + mailnews/mime/src/mimeTextHTMLParsed.h | 28 + mailnews/mime/src/mimebuf.cpp | 249 ++ mailnews/mime/src/mimebuf.h | 39 + mailnews/mime/src/mimecms.cpp | 716 +++++ mailnews/mime/src/mimecms.h | 36 + mailnews/mime/src/mimecom.cpp | 74 + mailnews/mime/src/mimecom.h | 38 + mailnews/mime/src/mimecont.cpp | 218 ++ mailnews/mime/src/mimecont.h | 43 + mailnews/mime/src/mimecryp.cpp | 571 ++++ mailnews/mime/src/mimecryp.h | 140 + mailnews/mime/src/mimecth.cpp | 51 + mailnews/mime/src/mimecth.h | 135 + mailnews/mime/src/mimedrft.cpp | 2084 ++++++++++++ mailnews/mime/src/mimeebod.cpp | 509 +++ mailnews/mime/src/mimeebod.h | 37 + mailnews/mime/src/mimeenc.cpp | 1107 +++++++ mailnews/mime/src/mimeeobj.cpp | 236 ++ mailnews/mime/src/mimeeobj.h | 34 + mailnews/mime/src/mimefilt.cpp | 399 +++ mailnews/mime/src/mimehdrs.cpp | 888 ++++++ mailnews/mime/src/mimehdrs.h | 88 + mailnews/mime/src/mimei.cpp | 1920 ++++++++++++ mailnews/mime/src/mimei.h | 422 +++ mailnews/mime/src/mimeiimg.cpp | 249 ++ mailnews/mime/src/mimeiimg.h | 35 + mailnews/mime/src/mimeleaf.cpp | 221 ++ mailnews/mime/src/mimeleaf.h | 59 + mailnews/mime/src/mimemalt.cpp | 580 ++++ mailnews/mime/src/mimemalt.h | 48 + mailnews/mime/src/mimemapl.cpp | 189 ++ mailnews/mime/src/mimemapl.h | 32 + mailnews/mime/src/mimemcms.cpp | 470 +++ mailnews/mime/src/mimemcms.h | 35 + mailnews/mime/src/mimemdig.cpp | 24 + mailnews/mime/src/mimemdig.h | 33 + mailnews/mime/src/mimemmix.cpp | 21 + mailnews/mime/src/mimemmix.h | 32 + mailnews/mime/src/mimemoz2.cpp | 2211 +++++++++++++ mailnews/mime/src/mimemoz2.h | 196 ++ mailnews/mime/src/mimempar.cpp | 21 + mailnews/mime/src/mimempar.h | 32 + mailnews/mime/src/mimemrel.cpp | 1199 +++++++ mailnews/mime/src/mimemrel.h | 66 + mailnews/mime/src/mimemsg.cpp | 977 ++++++ mailnews/mime/src/mimemsg.h | 43 + mailnews/mime/src/mimemsig.cpp | 775 +++++ mailnews/mime/src/mimemsig.h | 134 + mailnews/mime/src/mimemult.cpp | 748 +++++ mailnews/mime/src/mimemult.h | 102 + mailnews/mime/src/mimeobj.cpp | 327 ++ mailnews/mime/src/mimeobj.h | 186 ++ mailnews/mime/src/mimepbuf.cpp | 296 ++ mailnews/mime/src/mimepbuf.h | 64 + mailnews/mime/src/mimesun.cpp | 342 ++ mailnews/mime/src/mimesun.h | 59 + mailnews/mime/src/mimetenr.cpp | 28 + mailnews/mime/src/mimetenr.h | 32 + mailnews/mime/src/mimetext.cpp | 544 ++++ mailnews/mime/src/mimetext.h | 82 + mailnews/mime/src/mimethpl.cpp | 165 + mailnews/mime/src/mimethpl.h | 35 + mailnews/mime/src/mimethsa.cpp | 143 + mailnews/mime/src/mimethsa.h | 28 + mailnews/mime/src/mimethtm.cpp | 254 ++ mailnews/mime/src/mimethtm.h | 35 + mailnews/mime/src/mimetpfl.cpp | 630 ++++ mailnews/mime/src/mimetpfl.h | 52 + mailnews/mime/src/mimetpla.cpp | 451 +++ mailnews/mime/src/mimetpla.h | 39 + mailnews/mime/src/mimetric.cpp | 353 +++ mailnews/mime/src/mimetric.h | 33 + mailnews/mime/src/mimeunty.cpp | 588 ++++ mailnews/mime/src/mimeunty.h | 70 + mailnews/mime/src/modlmime.h | 398 +++ mailnews/mime/src/modmimee.h | 56 + mailnews/mime/src/moz.build | 92 + mailnews/mime/src/msgMime.manifest | 9 + mailnews/mime/src/nsCMS.cpp | 966 ++++++ mailnews/mime/src/nsCMS.h | 104 + mailnews/mime/src/nsCMSSecureMessage.cpp | 363 +++ mailnews/mime/src/nsCMSSecureMessage.h | 37 + mailnews/mime/src/nsMimeObjectClassAccess.cpp | 97 + mailnews/mime/src/nsMimeObjectClassAccess.h | 52 + mailnews/mime/src/nsMimeStringResources.h | 40 + mailnews/mime/src/nsSimpleMimeConverterStub.cpp | 209 ++ mailnews/mime/src/nsSimpleMimeConverterStub.h | 13 + mailnews/mime/src/nsStreamConverter.cpp | 1157 +++++++ mailnews/mime/src/nsStreamConverter.h | 88 + 152 files changed, 37537 insertions(+) create mode 100644 mailnews/mime/cthandlers/glue/mimexpcom.cpp create mode 100644 mailnews/mime/cthandlers/glue/mimexpcom.h create mode 100644 mailnews/mime/cthandlers/glue/moz.build create mode 100644 mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.cpp create mode 100644 mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.h create mode 100644 mailnews/mime/cthandlers/moz.build create mode 100644 mailnews/mime/cthandlers/pgpmime/moz.build create mode 100644 mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.cpp create mode 100644 mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.h create mode 100644 mailnews/mime/cthandlers/vcard/mimevcrd.cpp create mode 100644 mailnews/mime/cthandlers/vcard/mimevcrd.h create mode 100644 mailnews/mime/cthandlers/vcard/moz.build create mode 100644 mailnews/mime/emitters/moz.build create mode 100644 mailnews/mime/emitters/nsEmitterUtils.cpp create mode 100644 mailnews/mime/emitters/nsEmitterUtils.h create mode 100644 mailnews/mime/emitters/nsMimeBaseEmitter.cpp create mode 100644 mailnews/mime/emitters/nsMimeBaseEmitter.h create mode 100644 mailnews/mime/emitters/nsMimeEmitterCID.h create mode 100644 mailnews/mime/emitters/nsMimeHtmlEmitter.cpp create mode 100644 mailnews/mime/emitters/nsMimeHtmlEmitter.h create mode 100644 mailnews/mime/emitters/nsMimePlainEmitter.cpp create mode 100644 mailnews/mime/emitters/nsMimePlainEmitter.h create mode 100644 mailnews/mime/emitters/nsMimeRawEmitter.cpp create mode 100644 mailnews/mime/emitters/nsMimeRawEmitter.h create mode 100644 mailnews/mime/emitters/nsMimeRebuffer.cpp create mode 100644 mailnews/mime/emitters/nsMimeRebuffer.h create mode 100644 mailnews/mime/emitters/nsMimeXmlEmitter.cpp create mode 100644 mailnews/mime/emitters/nsMimeXmlEmitter.h create mode 100644 mailnews/mime/jsmime/LICENSE create mode 100644 mailnews/mime/jsmime/README.md create mode 100644 mailnews/mime/jsmime/jsmime.js create mode 100644 mailnews/mime/moz.build create mode 100644 mailnews/mime/public/MimeEncoder.h create mode 100644 mailnews/mime/public/MimeHeaderParser.h create mode 100644 mailnews/mime/public/moz.build create mode 100644 mailnews/mime/public/msgIStructuredHeaders.idl create mode 100644 mailnews/mime/public/nsICMSDecoder.idl create mode 100644 mailnews/mime/public/nsICMSEncoder.idl create mode 100644 mailnews/mime/public/nsICMSMessage.idl create mode 100644 mailnews/mime/public/nsICMSMessage2.idl create mode 100644 mailnews/mime/public/nsICMSMessageErrors.idl create mode 100644 mailnews/mime/public/nsICMSSecureMessage.idl create mode 100644 mailnews/mime/public/nsIMimeContentTypeHandler.h create mode 100644 mailnews/mime/public/nsIMimeConverter.idl create mode 100644 mailnews/mime/public/nsIMimeEmitter.idl create mode 100644 mailnews/mime/public/nsIMimeHeaders.idl create mode 100644 mailnews/mime/public/nsIMimeMiscStatus.idl create mode 100644 mailnews/mime/public/nsIMimeObjectClassAccess.h create mode 100644 mailnews/mime/public/nsIMimeStreamConverter.idl create mode 100644 mailnews/mime/public/nsIMsgHeaderParser.idl create mode 100644 mailnews/mime/public/nsIPgpMimeProxy.idl create mode 100644 mailnews/mime/public/nsISimpleMimeConverter.idl create mode 100644 mailnews/mime/public/nsMailHeaders.h create mode 100644 mailnews/mime/public/nsMsgMimeCID.h create mode 100644 mailnews/mime/src/MimeHeaderParser.cpp create mode 100644 mailnews/mime/src/comi18n.cpp create mode 100644 mailnews/mime/src/comi18n.h create mode 100644 mailnews/mime/src/extraMimeParsers.jsm create mode 100644 mailnews/mime/src/jsmime.jsm create mode 100644 mailnews/mime/src/mime.def create mode 100644 mailnews/mime/src/mimeJSComponents.js create mode 100644 mailnews/mime/src/mimeParser.jsm create mode 100644 mailnews/mime/src/mimeTextHTMLParsed.cpp create mode 100644 mailnews/mime/src/mimeTextHTMLParsed.h create mode 100644 mailnews/mime/src/mimebuf.cpp create mode 100644 mailnews/mime/src/mimebuf.h create mode 100644 mailnews/mime/src/mimecms.cpp create mode 100644 mailnews/mime/src/mimecms.h create mode 100644 mailnews/mime/src/mimecom.cpp create mode 100644 mailnews/mime/src/mimecom.h create mode 100644 mailnews/mime/src/mimecont.cpp create mode 100644 mailnews/mime/src/mimecont.h create mode 100644 mailnews/mime/src/mimecryp.cpp create mode 100644 mailnews/mime/src/mimecryp.h create mode 100644 mailnews/mime/src/mimecth.cpp create mode 100644 mailnews/mime/src/mimecth.h create mode 100644 mailnews/mime/src/mimedrft.cpp create mode 100644 mailnews/mime/src/mimeebod.cpp create mode 100644 mailnews/mime/src/mimeebod.h create mode 100644 mailnews/mime/src/mimeenc.cpp create mode 100644 mailnews/mime/src/mimeeobj.cpp create mode 100644 mailnews/mime/src/mimeeobj.h create mode 100644 mailnews/mime/src/mimefilt.cpp create mode 100644 mailnews/mime/src/mimehdrs.cpp create mode 100644 mailnews/mime/src/mimehdrs.h create mode 100644 mailnews/mime/src/mimei.cpp create mode 100644 mailnews/mime/src/mimei.h create mode 100644 mailnews/mime/src/mimeiimg.cpp create mode 100644 mailnews/mime/src/mimeiimg.h create mode 100644 mailnews/mime/src/mimeleaf.cpp create mode 100644 mailnews/mime/src/mimeleaf.h create mode 100644 mailnews/mime/src/mimemalt.cpp create mode 100644 mailnews/mime/src/mimemalt.h create mode 100644 mailnews/mime/src/mimemapl.cpp create mode 100644 mailnews/mime/src/mimemapl.h create mode 100644 mailnews/mime/src/mimemcms.cpp create mode 100644 mailnews/mime/src/mimemcms.h create mode 100644 mailnews/mime/src/mimemdig.cpp create mode 100644 mailnews/mime/src/mimemdig.h create mode 100644 mailnews/mime/src/mimemmix.cpp create mode 100644 mailnews/mime/src/mimemmix.h create mode 100644 mailnews/mime/src/mimemoz2.cpp create mode 100644 mailnews/mime/src/mimemoz2.h create mode 100644 mailnews/mime/src/mimempar.cpp create mode 100644 mailnews/mime/src/mimempar.h create mode 100644 mailnews/mime/src/mimemrel.cpp create mode 100644 mailnews/mime/src/mimemrel.h create mode 100644 mailnews/mime/src/mimemsg.cpp create mode 100644 mailnews/mime/src/mimemsg.h create mode 100644 mailnews/mime/src/mimemsig.cpp create mode 100644 mailnews/mime/src/mimemsig.h create mode 100644 mailnews/mime/src/mimemult.cpp create mode 100644 mailnews/mime/src/mimemult.h create mode 100644 mailnews/mime/src/mimeobj.cpp create mode 100644 mailnews/mime/src/mimeobj.h create mode 100644 mailnews/mime/src/mimepbuf.cpp create mode 100644 mailnews/mime/src/mimepbuf.h create mode 100644 mailnews/mime/src/mimesun.cpp create mode 100644 mailnews/mime/src/mimesun.h create mode 100644 mailnews/mime/src/mimetenr.cpp create mode 100644 mailnews/mime/src/mimetenr.h create mode 100644 mailnews/mime/src/mimetext.cpp create mode 100644 mailnews/mime/src/mimetext.h create mode 100644 mailnews/mime/src/mimethpl.cpp create mode 100644 mailnews/mime/src/mimethpl.h create mode 100644 mailnews/mime/src/mimethsa.cpp create mode 100644 mailnews/mime/src/mimethsa.h create mode 100644 mailnews/mime/src/mimethtm.cpp create mode 100644 mailnews/mime/src/mimethtm.h create mode 100644 mailnews/mime/src/mimetpfl.cpp create mode 100644 mailnews/mime/src/mimetpfl.h create mode 100644 mailnews/mime/src/mimetpla.cpp create mode 100644 mailnews/mime/src/mimetpla.h create mode 100644 mailnews/mime/src/mimetric.cpp create mode 100644 mailnews/mime/src/mimetric.h create mode 100644 mailnews/mime/src/mimeunty.cpp create mode 100644 mailnews/mime/src/mimeunty.h create mode 100644 mailnews/mime/src/modlmime.h create mode 100644 mailnews/mime/src/modmimee.h create mode 100644 mailnews/mime/src/moz.build create mode 100644 mailnews/mime/src/msgMime.manifest create mode 100644 mailnews/mime/src/nsCMS.cpp create mode 100644 mailnews/mime/src/nsCMS.h create mode 100644 mailnews/mime/src/nsCMSSecureMessage.cpp create mode 100644 mailnews/mime/src/nsCMSSecureMessage.h create mode 100644 mailnews/mime/src/nsMimeObjectClassAccess.cpp create mode 100644 mailnews/mime/src/nsMimeObjectClassAccess.h create mode 100644 mailnews/mime/src/nsMimeStringResources.h create mode 100644 mailnews/mime/src/nsSimpleMimeConverterStub.cpp create mode 100644 mailnews/mime/src/nsSimpleMimeConverterStub.h create mode 100644 mailnews/mime/src/nsStreamConverter.cpp create mode 100644 mailnews/mime/src/nsStreamConverter.h (limited to 'mailnews/mime') diff --git a/mailnews/mime/cthandlers/glue/mimexpcom.cpp b/mailnews/mime/cthandlers/glue/mimexpcom.cpp new file mode 100644 index 000000000..094f61e37 --- /dev/null +++ b/mailnews/mime/cthandlers/glue/mimexpcom.cpp @@ -0,0 +1,132 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsIComponentManager.h" +#include "nsIMimeObjectClassAccess.h" +#include "nsMsgMimeCID.h" +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +static NS_DEFINE_CID(kMimeObjectClassAccessCID, NS_MIME_OBJECT_CLASS_ACCESS_CID); + +/* + * These calls are necessary to expose the object class hierarchy + * to externally developed content type handlers. + */ +extern "C" void * +COM_GetmimeInlineTextClass(void) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->GetmimeInlineTextClass(&ptr); + + return ptr; +} + +extern "C" void * +COM_GetmimeLeafClass(void) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->GetmimeLeafClass(&ptr); + + return ptr; +} + +extern "C" void * +COM_GetmimeObjectClass(void) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->GetmimeObjectClass(&ptr); + + return ptr; +} + +extern "C" void * +COM_GetmimeContainerClass(void) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->GetmimeContainerClass(&ptr); + + return ptr; +} + +extern "C" void * +COM_GetmimeMultipartClass(void) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->GetmimeMultipartClass(&ptr); + + return ptr; +} + +extern "C" void * +COM_GetmimeMultipartSignedClass(void) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->GetmimeMultipartSignedClass(&ptr); + + return ptr; +} + +extern "C" int +COM_MimeObject_write(void *mimeObject, char *data, int32_t length, + bool user_visible_p) +{ + int32_t rc = -1; + + nsresult res; + nsCOMPtr objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + { + if (NS_SUCCEEDED(objAccess->MimeObjectWrite(mimeObject, data, length, user_visible_p))) + rc = length; + else + rc = -1; + } + + return rc; +} + +extern "C" void * +COM_MimeCreate(char * content_type, void * hdrs, void * opts) +{ + void *ptr = NULL; + + nsresult res; + nsCOMPtr objAccess = + do_CreateInstance(kMimeObjectClassAccessCID, &res); + if (NS_SUCCEEDED(res) && objAccess) + objAccess->MimeCreate(content_type, hdrs, opts, &ptr); + + return ptr; +} diff --git a/mailnews/mime/cthandlers/glue/mimexpcom.h b/mailnews/mime/cthandlers/glue/mimexpcom.h new file mode 100644 index 000000000..9468b22b4 --- /dev/null +++ b/mailnews/mime/cthandlers/glue/mimexpcom.h @@ -0,0 +1,93 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This is the definitions for the Content Type Handler plugins to + * access internals of libmime via XP-COM calls + */ +#ifndef _MIMEXPCOM_H_ +#define _MIMEXPCOM_H_ + +/* + This header exposes functions that are necessary to access the + object hierarchy for the mime chart. The class hierarchy is: + + MimeObject (abstract) + | + |--- MimeContainer (abstract) + | | + | |--- MimeMultipart (abstract) + | | | + | | |--- MimeMultipartMixed + | | | + | | |--- MimeMultipartDigest + | | | + | | |--- MimeMultipartParallel + | | | + | | |--- MimeMultipartAlternative + | | | + | | |--- MimeMultipartRelated + | | | + | | |--- MimeMultipartAppleDouble + | | | + | | |--- MimeSunAttachment + | | | + | | |--- MimeMultipartSigned (abstract) + | | | + | | |--- MimeMultipartSigned + | | + | |--- MimeXlateed (abstract) + | | | + | | |--- MimeXlateed + | | + | |--- MimeMessage + | | + | |--- MimeUntypedText + | + |--- MimeLeaf (abstract) + | | + | |--- MimeInlineText (abstract) + | | | + | | |--- MimeInlineTextPlain + | | | + | | |--- MimeInlineTextHTML + | | | + | | |--- MimeInlineTextRichtext + | | | | + | | | |--- MimeInlineTextEnriched + | | | + | | |--- MimeInlineTextVCard + | | + | |--- MimeInlineImage + | | + | |--- MimeExternalObject + | + |--- MimeExternalBody + */ + +/* + * These functions are exposed by libmime to be used by content type + * handler plugins for processing stream data. + */ +/* + * This is the write call for outputting processed stream data. + */ +extern "C" int COM_MimeObject_write(void *mimeObject, const char *data, + int32_t length, + bool user_visible_p); +/* + * The following group of calls expose the pointers for the object + * system within libmime. + */ +extern "C" void *COM_GetmimeInlineTextClass(void); +extern "C" void *COM_GetmimeLeafClass(void); +extern "C" void *COM_GetmimeObjectClass(void); +extern "C" void *COM_GetmimeContainerClass(void); +extern "C" void *COM_GetmimeMultipartClass(void); +extern "C" void *COM_GetmimeMultipartSignedClass(void); + +extern "C" void *COM_MimeCreate(char * content_type, void * hdrs, void * opts); + +#endif /* _MIMEXPCOM_H_ */ diff --git a/mailnews/mime/cthandlers/glue/moz.build b/mailnews/mime/cthandlers/glue/moz.build new file mode 100644 index 000000000..f51518ca9 --- /dev/null +++ b/mailnews/mime/cthandlers/glue/moz.build @@ -0,0 +1,18 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsMimeContentTypeHandler.h', +] + +SOURCES += [ + 'mimexpcom.cpp', + 'nsMimeContentTypeHandler.cpp', +] + +FINAL_LIBRARY = 'mail' + +Library('mimecthglue_s') + diff --git a/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.cpp b/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.cpp new file mode 100644 index 000000000..369f8c4bf --- /dev/null +++ b/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include +#include "nscore.h" +#include "plstr.h" +//#include "mimecth.h" +#include "nsMimeContentTypeHandler.h" + +/* + * The following macros actually implement addref, release and + * query interface for our component. + */ +NS_IMPL_ISUPPORTS(nsMimeContentTypeHandler, nsIMimeContentTypeHandler) + +/* + * nsIMimeEmitter definitions.... + */ +nsMimeContentTypeHandler::nsMimeContentTypeHandler(const char *aMimeType, + MCTHCreateCTHClass callback) +{ + NS_ASSERTION(aMimeType, "nsMimeContentTypeHandler should be initialized with non-null mime type"); + NS_ASSERTION(callback, "nsMimeContentTypeHandler should be initialized with non-null callback"); + mimeType = PL_strdup(aMimeType); + realCreateContentTypeHandlerClass = callback; +} + +nsMimeContentTypeHandler::~nsMimeContentTypeHandler(void) +{ + if (mimeType) { + NS_Free(mimeType); + mimeType = 0; + } + realCreateContentTypeHandlerClass = 0; +} + +// Get the content type if necessary +nsresult +nsMimeContentTypeHandler::GetContentType(char **contentType) +{ + *contentType = PL_strdup(mimeType); + return NS_OK; +} + +// Set the output stream for processed data. +nsresult +nsMimeContentTypeHandler::CreateContentTypeHandlerClass(const char *content_type, + contentTypeHandlerInitStruct *initStruct, + MimeObjectClass **objClass) +{ + *objClass = realCreateContentTypeHandlerClass(content_type, initStruct); + if (!*objClass) + return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */ + else + return NS_OK; +} + + + diff --git a/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.h b/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.h new file mode 100644 index 000000000..8cc142268 --- /dev/null +++ b/mailnews/mime/cthandlers/glue/nsMimeContentTypeHandler.h @@ -0,0 +1,47 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This interface is implemented by content type handlers that will be + * called upon by libmime to process various attachments types. The primary + * purpose of these handlers will be to represent the attached data in a + * viewable HTML format that is useful for the user + * + * Note: These will all register by their content type prefixed by the + * following: mimecth:text/vcard + * + * libmime will then use the XPCOM Component Manager to + * locate the appropriate Content Type handler + */ +#ifndef nsMimeContentTypeHandler_h_ +#define nsMimeContentTypeHandler_h_ + +#include "mozilla/Attributes.h" +#include "nsIMimeContentTypeHandler.h" + +typedef MimeObjectClass * +(* MCTHCreateCTHClass)(const char *content_type, + contentTypeHandlerInitStruct *initStruct); + +class nsMimeContentTypeHandler : public nsIMimeContentTypeHandler { +public: + nsMimeContentTypeHandler (const char *aMimeType, + MCTHCreateCTHClass callback); + + /* this macro defines QueryInterface, AddRef and Release for this class */ + NS_DECL_ISUPPORTS + + NS_IMETHOD GetContentType(char **contentType) override; + + NS_IMETHOD CreateContentTypeHandlerClass(const char *content_type, + contentTypeHandlerInitStruct *initStruct, + MimeObjectClass **objClass) override; + private: + virtual ~nsMimeContentTypeHandler(); + char *mimeType; + MCTHCreateCTHClass realCreateContentTypeHandlerClass; +}; + +#endif /* nsMimeContentTypeHandler_h_ */ diff --git a/mailnews/mime/cthandlers/moz.build b/mailnews/mime/cthandlers/moz.build new file mode 100644 index 000000000..31807499d --- /dev/null +++ b/mailnews/mime/cthandlers/moz.build @@ -0,0 +1,12 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# pgpmime depends on glue. +DIRS += [ + 'glue', + 'vcard', + 'pgpmime', +] + diff --git a/mailnews/mime/cthandlers/pgpmime/moz.build b/mailnews/mime/cthandlers/pgpmime/moz.build new file mode 100644 index 000000000..878bd5bfa --- /dev/null +++ b/mailnews/mime/cthandlers/pgpmime/moz.build @@ -0,0 +1,20 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsPgpMimeProxy.h', +] + +SOURCES += [ + 'nsPgpMimeProxy.cpp', +] + +FINAL_LIBRARY = 'mail' + +Library('pgpmime_s') + +LOCAL_INCLUDES += [ + '../glue', +] diff --git a/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.cpp b/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.cpp new file mode 100644 index 000000000..de4ec3174 --- /dev/null +++ b/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.cpp @@ -0,0 +1,634 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsPgpMimeProxy.h" +#include "nspr.h" +#include "plstr.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "mozilla/Services.h" +#include "nsIRequest.h" +#include "nsIStringBundle.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIURI.h" +#include "mimexpcom.h" +#include "nsMsgUtils.h" + +#include "nsMsgMimeCID.h" + +#include "mimecth.h" +#include "mimemoz2.h" +#include "nspr.h" +#include "plstr.h" +#include "nsIPgpMimeProxy.h" +#include "nsComponentManagerUtils.h" + +#define MIME_SUPERCLASS mimeEncryptedClass +MimeDefClass(MimeEncryptedPgp, MimeEncryptedPgpClass, + mimeEncryptedPgpClass, &MIME_SUPERCLASS); + +#define kCharMax 1024 + +extern "C" MimeObjectClass * +MIME_PgpMimeCreateContentTypeHandlerClass( + const char *content_type, + contentTypeHandlerInitStruct *initStruct) +{ + MimeObjectClass *objClass = (MimeObjectClass *) &mimeEncryptedPgpClass; + + initStruct->force_inline_display = false; + + return objClass; +} + +static void *MimePgpe_init(MimeObject *, + int (*output_fn) (const char *, int32_t, void *), + void *); +static int MimePgpe_write (const char *, int32_t, void *); +static int MimePgpe_eof (void *, bool); +static char* MimePgpe_generate (void *); +static void MimePgpe_free (void *); + +/* Returns a string describing the location of the part (like "2.5.3"). + This is not a full URL, just a part-number. + */ +static nsCString determineMimePart(MimeObject* obj); + + +#define PGPMIME_PROPERTIES_URL "chrome://messenger/locale/pgpmime.properties" +#define PGPMIME_STR_NOT_SUPPORTED_ID u"pgpMimeNeedsAddon" +#define PGPMIME_URL_PREF "mail.pgpmime.addon_url" + +static void PgpMimeGetNeedsAddonString(nsCString &aResult) +{ + aResult.AssignLiteral("???"); + + nsCOMPtr stringBundleService = + mozilla::services::GetStringBundleService(); + + nsCOMPtr stringBundle; + nsresult rv = stringBundleService->CreateBundle(PGPMIME_PROPERTIES_URL, + getter_AddRefs(stringBundle)); + if (NS_FAILED(rv)) + return; + + nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + nsCString url; + if (NS_FAILED(prefs->GetCharPref("mail.pgpmime.addon_url", + getter_Copies(url)))) + return; + + NS_ConvertUTF8toUTF16 url16(url); + const char16_t *formatStrings[] = { url16.get() }; + + nsString result; + rv = stringBundle->FormatStringFromName(PGPMIME_STR_NOT_SUPPORTED_ID, + formatStrings, 1, getter_Copies(result)); + if (NS_FAILED(rv)) + return; + aResult = NS_ConvertUTF16toUTF8(result); +} + +static int +MimeEncryptedPgpClassInitialize(MimeEncryptedPgpClass *clazz) +{ + mozilla::DebugOnly oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "oclass is not initialized"); + + MimeEncryptedClass *eclass = (MimeEncryptedClass *) clazz; + + eclass->crypto_init = MimePgpe_init; + eclass->crypto_write = MimePgpe_write; + eclass->crypto_eof = MimePgpe_eof; + eclass->crypto_generate_html = MimePgpe_generate; + eclass->crypto_free = MimePgpe_free; + + return 0; +} + +class MimePgpeData : public nsISupports +{ +public: + NS_DECL_ISUPPORTS + + int (*output_fn) (const char *buf, int32_t buf_size, void *output_closure); + void *output_closure; + MimeObject *self; + + nsCOMPtr mimeDecrypt; + + MimePgpeData() + : output_fn(nullptr), + output_closure(nullptr) + { + } + +private: + virtual ~MimePgpeData() + { + } +}; + +NS_IMPL_ISUPPORTS0(MimePgpeData) + +static void* +MimePgpe_init(MimeObject *obj, + int (*output_fn) (const char *buf, int32_t buf_size, + void *output_closure), + void *output_closure) +{ + if (!(obj && obj->options && output_fn)) + return nullptr; + + MimePgpeData* data = new MimePgpeData(); + NS_ENSURE_TRUE(data, nullptr); + + data->self = obj; + data->output_fn = output_fn; + data->output_closure = output_closure; + data->mimeDecrypt = nullptr; + + nsresult rv; + data->mimeDecrypt = do_CreateInstance(NS_PGPMIMEPROXY_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return data; + + char *ct = MimeHeaders_get(obj->headers, HEADER_CONTENT_TYPE, false, false); + + rv = (ct ? data->mimeDecrypt->SetContentType(nsDependentCString(ct)) + : data->mimeDecrypt->SetContentType(EmptyCString())); + + PR_Free(ct); + + if (NS_FAILED(rv)) + return nullptr; + + nsCString mimePart = determineMimePart(obj); + + rv = data->mimeDecrypt->SetMimePart(mimePart); + if (NS_FAILED(rv)) + return nullptr; + + mime_stream_data *msd = (mime_stream_data *) (data->self->options->stream_closure); + nsIChannel *channel = msd->channel; + + nsCOMPtr uri; + if (channel) + channel->GetURI(getter_AddRefs(uri)); + + if (NS_FAILED(data->mimeDecrypt->SetMimeCallback(output_fn, output_closure, uri))) + return nullptr; + + return data; +} + +static int +MimePgpe_write(const char *buf, int32_t buf_size, void *output_closure) +{ + MimePgpeData* data = (MimePgpeData *) output_closure; + + if (!data || !data->output_fn) + return -1; + + if (!data->mimeDecrypt) + return 0; + + return (NS_SUCCEEDED(data->mimeDecrypt->Write(buf, buf_size)) ? 0 : -1); +} + +static int +MimePgpe_eof(void* output_closure, bool abort_p) +{ + MimePgpeData* data = (MimePgpeData *) output_closure; + + if (!data || !data->output_fn) + return -1; + + if (NS_FAILED(data->mimeDecrypt->Finish())) + return -1; + + data->mimeDecrypt = nullptr; + return 0; +} + +static char* +MimePgpe_generate(void *output_closure) +{ + const char htmlMsg[] = "GEN MSG"; + char* msg = (char *) PR_MALLOC(strlen(htmlMsg) + 1); + if (msg) + PL_strcpy(msg, htmlMsg); + + return msg; +} + +static void +MimePgpe_free(void *output_closure) +{ +} + +/* Returns a string describing the location of the part (like "2.5.3"). + This is not a full URL, just a part-number. + */ +static nsCString +determineMimePart(MimeObject* obj) +{ + char mimePartNum[20]; + MimeObject *kid; + MimeContainer *cont; + int32_t i; + + nsCString mimePart; + + while (obj->parent) { + cont = (MimeContainer *) obj->parent; + for (i = 0; i < cont->nchildren; i++) { + kid = cont->children[i]; + if (kid == obj) { + sprintf(mimePartNum, ".%d", i + 1); + mimePart.Insert(mimePartNum, 0); + } + } + obj = obj->parent; + } + + // remove leading "." + if (mimePart.Length() > 0) + mimePart.Cut(0, 1); + + return mimePart; +} + + +//////////////////////////////////////////////////////////////////////////// +NS_IMPL_ISUPPORTS(nsPgpMimeProxy, + nsIPgpMimeProxy, + nsIRequestObserver, + nsIStreamListener, + nsIRequest, + nsIInputStream) + +// nsPgpMimeProxy implementation +nsPgpMimeProxy::nsPgpMimeProxy() + : mInitialized(false), + mDecryptor(nullptr), + mLoadGroup(nullptr), + mLoadFlags(LOAD_NORMAL), + mCancelStatus(NS_OK) +{ +} + +nsPgpMimeProxy::~nsPgpMimeProxy() +{ + Finalize(); +} + +nsresult +nsPgpMimeProxy::Finalize() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::SetMimeCallback(MimeDecodeCallbackFun outputFun, + void* outputClosure, + nsIURI* myUri) +{ + if (!outputFun || !outputClosure) + return NS_ERROR_NULL_POINTER; + + mOutputFun = outputFun; + mOutputClosure = outputClosure; + mInitialized = true; + + mStreamOffset = 0; + mByteBuf.Truncate(); + + if (mDecryptor) + return mDecryptor->OnStartRequest((nsIRequest*) this, myUri); + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Init() +{ + mByteBuf.Truncate(); + + nsresult rv; + nsCOMPtr pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + mDecryptor = do_CreateInstance(PGPMIME_JS_DECRYPTOR_CONTRACTID, &rv); + if (NS_FAILED(rv)) + mDecryptor = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Write(const char *buf, uint32_t buf_size) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + mByteBuf.Assign(buf, buf_size); + mStreamOffset = 0; + + if (mDecryptor) + return mDecryptor->OnDataAvailable((nsIRequest*) this, nullptr, (nsIInputStream*) this, + 0, buf_size); + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Finish() { + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + if (mDecryptor) { + return mDecryptor->OnStopRequest((nsIRequest*) this, nullptr, NS_OK); + } + else { + nsCString temp; + temp.Append("Content-Type: text/html\r\nCharset: UTF-8\r\n\r\n"); + temp.Append("
"); + temp.Append("
"); + + nsCString tString; + PgpMimeGetNeedsAddonString(tString); + temp.Append(tString); + temp.Append("

\r\n"); + + PR_SetError(0,0); + int status = mOutputFun(temp.get(), temp.Length(), mOutputClosure); + if (status < 0) { + PR_SetError(status, 0); + mOutputFun = nullptr; + return NS_ERROR_FAILURE; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::GetDecryptor(nsIStreamListener **aDecryptor) +{ + NS_IF_ADDREF(*aDecryptor = mDecryptor); + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::SetDecryptor(nsIStreamListener *aDecryptor) +{ + mDecryptor = aDecryptor; + + return NS_OK; +} + + +NS_IMETHODIMP +nsPgpMimeProxy::GetContentType(nsACString &aContentType) +{ + aContentType = mContentType; + return NS_OK; +} + + +NS_IMETHODIMP +nsPgpMimeProxy::SetContentType(const nsACString &aContentType) +{ + mContentType = aContentType; + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::GetMimePart(nsACString &aMimePart) +{ + aMimePart = mMimePart; + return NS_OK; +} + + +NS_IMETHODIMP +nsPgpMimeProxy::SetMimePart(const nsACString &aMimePart) +{ + mMimePart = aMimePart; + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIRequest methods +/////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsPgpMimeProxy::GetName(nsACString &result) +{ + result = "pgpmimeproxy"; + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::IsPending(bool *result) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + *result = NS_SUCCEEDED(mCancelStatus); + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::GetStatus(nsresult *status) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + *status = mCancelStatus; + return NS_OK; +} + +// NOTE: We assume that OnStopRequest should not be called if +// request is canceled. This may be wrong! +NS_IMETHODIMP +nsPgpMimeProxy::Cancel(nsresult status) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + // Need a non-zero status code to cancel + if (NS_SUCCEEDED(status)) + return NS_ERROR_FAILURE; + + if (NS_SUCCEEDED(mCancelStatus)) + mCancelStatus = status; + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Suspend(void) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Resume(void) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPgpMimeProxy::GetLoadGroup(nsILoadGroup * *aLoadGroup) +{ + NS_IF_ADDREF(*aLoadGroup = mLoadGroup); + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::SetLoadGroup(nsILoadGroup* aLoadGroup) +{ + mLoadGroup = aLoadGroup; + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::GetLoadFlags(nsLoadFlags *aLoadFlags) +{ + *aLoadFlags = mLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::SetLoadFlags(nsLoadFlags aLoadFlags) +{ + mLoadFlags = aLoadFlags; + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIInputStream methods +/////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsPgpMimeProxy::Available(uint64_t* _retval) +{ + NS_ENSURE_ARG(_retval); + + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + *_retval = (mByteBuf.Length() > mStreamOffset) ? + mByteBuf.Length() - mStreamOffset : 0; + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Read(char* buf, uint32_t count, + uint32_t *readCount) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + if (!buf || !readCount) + return NS_ERROR_NULL_POINTER; + + int32_t avail = (mByteBuf.Length() > mStreamOffset) ? + mByteBuf.Length() - mStreamOffset : 0; + + uint32_t readyCount = ((uint32_t) avail > count) ? count : avail; + + if (readyCount) { + memcpy(buf, mByteBuf.get()+mStreamOffset, readyCount); + *readCount = readyCount; + } + + mStreamOffset += *readCount; + + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::ReadSegments(nsWriteSegmentFun writer, + void * aClosure, uint32_t count, + uint32_t *readCount) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPgpMimeProxy::IsNonBlocking(bool *aNonBlocking) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + *aNonBlocking = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::Close() +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + mStreamOffset = 0; + mByteBuf.Truncate(); + + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIStreamListener methods +/////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsPgpMimeProxy::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsPgpMimeProxy::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, + nsresult aStatus) +{ + return NS_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// nsIStreamListener method +/////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsPgpMimeProxy::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, + nsIInputStream *aInputStream, + uint64_t aSourceOffset, + uint32_t aLength) +{ + NS_ENSURE_TRUE(mInitialized, NS_ERROR_NOT_INITIALIZED); + + NS_ENSURE_ARG(aInputStream); + + char buf[kCharMax]; + uint32_t readCount, readMax; + + while (aLength > 0) { + readMax = (aLength < kCharMax) ? aLength : kCharMax; + + nsresult rv; + rv = aInputStream->Read((char *) buf, readMax, &readCount); + NS_ENSURE_SUCCESS(rv, rv); + + int status = mOutputFun(buf, readCount, mOutputClosure); + if (status < 0) { + PR_SetError(status, 0); + mOutputFun = nullptr; + return NS_ERROR_FAILURE; + } + + aLength -= readCount; + } + + return NS_OK; +} diff --git a/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.h b/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.h new file mode 100644 index 000000000..41e7f59cc --- /dev/null +++ b/mailnews/mime/cthandlers/pgpmime/nsPgpMimeProxy.h @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _nsPgpmimeDecrypt_h_ +#define _nsPgpmimeDecrypt_h_ + +#include "mimecth.h" +#include "nsIPgpMimeProxy.h" +#include "nsCOMPtr.h" +#include "nsIStreamListener.h" +#include "nsIInputStream.h" +#include "nsILoadGroup.h" + +#define PGPMIME_JS_DECRYPTOR_CONTRACTID "@mozilla.org/mime/pgp-mime-js-decrypt;1" + +typedef struct MimeEncryptedPgpClass MimeEncryptedPgpClass; +typedef struct MimeEncryptedPgp MimeEncryptedPgp; + +struct MimeEncryptedPgpClass { + MimeEncryptedClass encrypted; +}; + +struct MimeEncryptedPgp { + MimeEncrypted encrypted; +}; + +class nsPgpMimeProxy : public nsIPgpMimeProxy, + public nsIRequest, + public nsIInputStream +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPGPMIMEPROXY + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUEST + NS_DECL_NSIINPUTSTREAM + + nsPgpMimeProxy(); + + // Define a Create method to be used with a factory: + static NS_METHOD + Create(nsISupports *aOuter, REFNSIID aIID, void **aResult); + +protected: + virtual ~nsPgpMimeProxy(); + bool mInitialized; + nsCOMPtr mDecryptor; + + MimeDecodeCallbackFun mOutputFun; + void* mOutputClosure; + + nsCOMPtr mLoadGroup; + nsLoadFlags mLoadFlags; + nsresult mCancelStatus; + + uint32_t mStreamOffset; + nsCString mByteBuf; + nsCString mContentType; + nsCString mMimePart; + + nsresult Finalize(); +}; + +#define MimeEncryptedPgpClassInitializer(ITYPE,CSUPER) \ + { MimeEncryptedClassInitializer(ITYPE,CSUPER) } + +#endif diff --git a/mailnews/mime/cthandlers/vcard/mimevcrd.cpp b/mailnews/mime/cthandlers/vcard/mimevcrd.cpp new file mode 100644 index 000000000..140afb5aa --- /dev/null +++ b/mailnews/mime/cthandlers/vcard/mimevcrd.cpp @@ -0,0 +1,378 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mimevcrd.h" + +#include "mimecth.h" +#include "mimexpcom.h" +#include "nsIMsgVCardService.h" +#include "nsINetUtil.h" +#include "nsMsgUtils.h" +#include "prmem.h" +#include "prprf.h" +#include "nsServiceManagerUtils.h" + +static int MimeInlineTextVCard_parse_line (const char *, int32_t, MimeObject *); +static int MimeInlineTextVCard_parse_eof (MimeObject *, bool); +static int MimeInlineTextVCard_parse_begin (MimeObject *obj); + +static int s_unique = 0; + +static int BeginVCard (MimeObject *obj); +static int EndVCard (MimeObject *obj); +static int WriteOutVCard (MimeObject *obj, VObject* v); + +static int GenerateVCardData(MimeObject * aMimeObj, VObject* aVcard); +static int OutputVcardAttribute(MimeObject *aMimeObj, VObject *aVcard, const char* id, nsACString& vCardOutput); +static int OutputBasicVcard(MimeObject *aMimeObj, VObject *aVcard, nsACString& vCardOutput); + +typedef struct + { + const char *attributeName; + int resourceId; + } AttributeName; + +#define kNumAttributes 12 + +#define MSGVCARDSERVICE_CONTRACT_ID "@mozilla.org/addressbook/msgvcardservice;1" + +/* This is the object definition. Note: we will set the superclass + to NULL and manually set this on the class creation */ +MimeDefClass(MimeInlineTextVCard, MimeInlineTextVCardClass, + mimeInlineTextVCardClass, NULL); + +extern "C" MimeObjectClass * +MIME_VCardCreateContentTypeHandlerClass(const char *content_type, + contentTypeHandlerInitStruct *initStruct) +{ + MimeObjectClass *clazz = (MimeObjectClass *)&mimeInlineTextVCardClass; + /* + * Must set the superclass by hand. + */ + if (!COM_GetmimeInlineTextClass()) + return NULL; + + clazz->superclass = (MimeObjectClass *)COM_GetmimeInlineTextClass(); + initStruct->force_inline_display = true; + return clazz; +} + +/* + * Implementation of VCard clazz + */ +static int +MimeInlineTextVCardClassInitialize(MimeInlineTextVCardClass *clazz) +{ + MimeObjectClass *oclass = (MimeObjectClass *) clazz; + NS_ASSERTION(!oclass->class_initialized, "1.1 19 Mar 1999 12:11"); + oclass->parse_begin = MimeInlineTextVCard_parse_begin; + oclass->parse_line = MimeInlineTextVCard_parse_line; + oclass->parse_eof = MimeInlineTextVCard_parse_eof; + return 0; +} + +static int +MimeInlineTextVCard_parse_begin (MimeObject *obj) +{ + int status = ((MimeObjectClass*)COM_GetmimeLeafClass())->parse_begin(obj); + MimeInlineTextVCardClass *clazz; + if (status < 0) return status; + + if (!obj->output_p) return 0; + if (!obj->options || !obj->options->write_html_p) return 0; + + /* This is a fine place to write out any HTML before the real meat begins. + In this sample code, we tell it to start a table. */ + + clazz = ((MimeInlineTextVCardClass *) obj->clazz); + /* initialize vcard string to empty; */ + NS_MsgSACopy(&(clazz->vCardString), ""); + + obj->options->state->separator_suppressed_p = true; + return 0; +} + +char *strcpySafe (char *dest, const char *src, size_t destLength) +{ + char *result = strncpy (dest, src, --destLength); + dest[destLength] = '\0'; + return result; +} + +static int +MimeInlineTextVCard_parse_line (const char *line, int32_t length, MimeObject *obj) +{ + // This routine gets fed each line of data, one at a time. + char* linestring; + MimeInlineTextVCardClass *clazz = ((MimeInlineTextVCardClass *) obj->clazz); + + if (!obj->output_p) return 0; + if (!obj->options || !obj->options->output_fn) return 0; + if (!obj->options->write_html_p) + { + return COM_MimeObject_write(obj, line, length, true); + } + + linestring = (char *) PR_MALLOC (length + 1); + memset(linestring, 0, (length + 1)); + + if (linestring) + { + strcpySafe((char *)linestring, line, length + 1); + NS_MsgSACat (&clazz->vCardString, linestring); + PR_Free (linestring); + } + + return 0; +} + + +//////////////////////////////////////////////////////////////////////////////// +static int +MimeInlineTextVCard_parse_eof (MimeObject *obj, bool abort_p) +{ + nsCOMPtr vCardService = + do_GetService(MSGVCARDSERVICE_CONTRACT_ID); + if (!vCardService) + return -1; + + int status = 0; + MimeInlineTextVCardClass *clazz = ((MimeInlineTextVCardClass *) obj->clazz); + + VObject *t, *v; + + if (obj->closed_p) return 0; + + /* Run parent method first, to flush out any buffered data. */ + // status = ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_eof(obj, abort_p); + status = ((MimeObjectClass*)COM_GetmimeInlineTextClass())->parse_eof(obj, abort_p); + if (status < 0) return status; + + // Don't quote vCards... + if ( (obj->options) && + ((obj->options->format_out == nsMimeOutput::nsMimeMessageQuoting) || + (obj->options->format_out == nsMimeOutput::nsMimeMessageBodyQuoting)) + ) + return 0; + + if (!clazz->vCardString) return 0; + + v = vCardService->Parse_MIME(clazz->vCardString, strlen(clazz->vCardString)); + NS_ASSERTION(v, "parse of vCard failed"); + + if (clazz->vCardString) { + PR_Free ((char*) clazz->vCardString); + clazz->vCardString = NULL; + } + + if (obj->output_p && obj->options && obj->options->write_html_p && + obj->options->headers != MimeHeadersCitation) { + /* This is a fine place to write any closing HTML. In fact, you may + want all the writing to be here, and all of the above would just + collect data into datastructures, though that isn't very + "streaming". */ + t = v; + while (v && status >= 0) { + /* write out html */ + status = WriteOutVCard (obj, v); + /* parse next vcard incase they're embedded */ + v = vCardService->NextVObjectInList(v); + } + + (void)vCardService->CleanVObject(t); + } + + if (status < 0) + return status; + + return 0; +} + +static int EndVCard (MimeObject *obj) +{ + int status = 0; + + /* Scribble HTML-ending stuff into the stream */ + char htmlFooters[32]; + PR_snprintf (htmlFooters, sizeof(htmlFooters), "%s%s", MSG_LINEBREAK, MSG_LINEBREAK); + status = COM_MimeObject_write(obj, htmlFooters, strlen(htmlFooters), false); + + if (status < 0) return status; + + return 0; +} + +static int BeginVCard (MimeObject *obj) +{ + int status = 0; + + /* Scribble HTML-starting stuff into the stream */ + char htmlHeaders[32]; + + s_unique++; + PR_snprintf (htmlHeaders, sizeof(htmlHeaders), "%s%s", MSG_LINEBREAK, MSG_LINEBREAK); + status = COM_MimeObject_write(obj, htmlHeaders, strlen(htmlHeaders), true); + + if (status < 0) return status; + + return 0; +} + + +static int WriteOutVCard (MimeObject * aMimeObj, VObject* aVcard) +{ + BeginVCard (aMimeObj); + + GenerateVCardData(aMimeObj, aVcard); + + return EndVCard (aMimeObj); +} + + +static int GenerateVCardData(MimeObject * aMimeObj, VObject* aVcard) +{ + // style is driven from CSS not here. Just layout the minimal vCard data + nsCString vCardOutput; + + vCardOutput = " "; // outer table plus the first (and only row) we use for this table + + // we need to get an escaped vCard url to bind to our add to address book button + nsCOMPtr vCardService = do_GetService(MSGVCARDSERVICE_CONTRACT_ID); + if (!vCardService) + return -1; + + nsAutoCString vCard; + nsAutoCString vEscCard; + int len = 0; + + vCard.Adopt(vCardService->WriteMemoryVObjects(0, &len, aVcard, false)); + MsgEscapeString(vCard, nsINetUtil::ESCAPE_XALPHAS, vEscCard); + + // first cell in the outer table row is a clickable image which brings up the rich address book UI for the vcard + vCardOutput += ""; + + // the 2nd cell in the outer table row is a nested table containing the actual vCard properties + vCardOutput += " "; + + // 2nd cell in the outer table is our vCard image + + vCardOutput += "
"; + + OutputBasicVcard(aMimeObj, aVcard, vCardOutput); + + // close the properties table + vCardOutput += "
"; + + // now write out the vCard + return COM_MimeObject_write(aMimeObj, (char *) vCardOutput.get(), vCardOutput.Length(), true); +} + + +static int OutputBasicVcard(MimeObject *aMimeObj, VObject *aVcard, nsACString& vCardOutput) +{ + VObject *prop = NULL; + nsAutoCString urlstring; + nsAutoCString namestring; + nsAutoCString emailstring; + + nsCOMPtr vCardService = do_GetService(MSGVCARDSERVICE_CONTRACT_ID); + if (!vCardService) + return -1; + + /* get the name and email */ + prop = vCardService->IsAPropertyOf(aVcard, VCFullNameProp); + if (prop) + { + if (VALUE_TYPE(prop)) + { + if (VALUE_TYPE(prop) != VCVT_RAW) + namestring.Adopt(vCardService->FakeCString(prop)); + else + namestring.Adopt(vCardService->VObjectAnyValue(prop)); + + if (!namestring.IsEmpty()) + { + vCardOutput += " "; + + prop = vCardService->IsAPropertyOf(aVcard, VCURLProp); + if (prop) + { + urlstring.Adopt(vCardService->FakeCString(prop)); + if (urlstring.IsEmpty()) + vCardOutput += namestring; + else + { + char buf[512]; + PR_snprintf(buf, 512, "%s", urlstring.get(), namestring.get()); + vCardOutput.Append(buf); + } + } + else + vCardOutput += namestring; + + /* get the email address */ + prop = vCardService->IsAPropertyOf(aVcard, VCEmailAddressProp); + if (prop) + { + emailstring.Adopt(vCardService->FakeCString(prop)); + if (!emailstring.IsEmpty()) + { + char buf[512]; + PR_snprintf(buf, 512, " <%s>", emailstring.get(), emailstring.get()); + vCardOutput.Append(buf); + } + } // if email address property + + vCardOutput += " "; // end the cell for the name/email address + } // if we have a name property + } + } // if full name property + + // now each basic property goes on its own line + + // title + (void) OutputVcardAttribute (aMimeObj, aVcard, VCTitleProp, vCardOutput); + + // org name and company name + prop = vCardService->IsAPropertyOf(aVcard, VCOrgProp); + if (prop) + { + OutputVcardAttribute (aMimeObj, prop, VCOrgUnitProp, vCardOutput); + OutputVcardAttribute (aMimeObj, prop, VCOrgNameProp, vCardOutput); + } + + return 0; +} + +static int OutputVcardAttribute(MimeObject *aMimeObj, VObject *aVcard, const char* id, nsACString& vCardOutput) +{ + VObject *prop = NULL; + nsAutoCString string; + + nsCOMPtr vCardService = do_GetService(MSGVCARDSERVICE_CONTRACT_ID); + if (!vCardService) + return -1; + + prop = vCardService->IsAPropertyOf(aVcard, id); + if (prop) + if (VALUE_TYPE(prop)) + { + if (VALUE_TYPE(prop) != VCVT_RAW) + string.Adopt(vCardService->FakeCString(prop)); + else + string.Adopt(vCardService->VObjectAnyValue(prop)); + + if (!string.IsEmpty()) + { + vCardOutput += " "; + vCardOutput += string; + vCardOutput += " "; + } + } + + return 0; +} diff --git a/mailnews/mime/cthandlers/vcard/mimevcrd.h b/mailnews/mime/cthandlers/vcard/mimevcrd.h new file mode 100644 index 000000000..6e731f555 --- /dev/null +++ b/mailnews/mime/cthandlers/vcard/mimevcrd.h @@ -0,0 +1,33 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _MIMEVCRD_H_ +#define _MIMEVCRD_H_ + +#include "mimetext.h" +#include "nsCOMPtr.h" + +/* The MimeInlineTextHTML class implements the text/x-vcard and (maybe? + someday?) the application/directory MIME content types. + */ + +typedef struct MimeInlineTextVCardClass MimeInlineTextVCardClass; +typedef struct MimeInlineTextVCard MimeInlineTextVCard; + +struct MimeInlineTextVCardClass { + MimeInlineTextClass text; + char *vCardString; +}; + +extern MimeInlineTextVCardClass mimeInlineTextVCardClass; + +struct MimeInlineTextVCard { + MimeInlineText text; +}; + +#define MimeInlineTextVCardClassInitializer(ITYPE,CSUPER) \ + { MimeInlineTextClassInitializer(ITYPE,CSUPER) } + +#endif /* _MIMEVCRD_H_ */ diff --git a/mailnews/mime/cthandlers/vcard/moz.build b/mailnews/mime/cthandlers/vcard/moz.build new file mode 100644 index 000000000..55de22391 --- /dev/null +++ b/mailnews/mime/cthandlers/vcard/moz.build @@ -0,0 +1,14 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +SOURCES += [ + 'mimevcrd.cpp', +] + +FINAL_LIBRARY = 'mail' + +LOCAL_INCLUDES += [ + '../glue', +] diff --git a/mailnews/mime/emitters/moz.build b/mailnews/mime/emitters/moz.build new file mode 100644 index 000000000..b1e3390bd --- /dev/null +++ b/mailnews/mime/emitters/moz.build @@ -0,0 +1,21 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsMimeEmitterCID.h', +] + +SOURCES += [ + 'nsEmitterUtils.cpp', + 'nsMimeBaseEmitter.cpp', + 'nsMimeHtmlEmitter.cpp', + 'nsMimePlainEmitter.cpp', + 'nsMimeRawEmitter.cpp', + 'nsMimeRebuffer.cpp', + 'nsMimeXmlEmitter.cpp', +] + +FINAL_LIBRARY = 'mail' + diff --git a/mailnews/mime/emitters/nsEmitterUtils.cpp b/mailnews/mime/emitters/nsEmitterUtils.cpp new file mode 100644 index 000000000..551bf5b31 --- /dev/null +++ b/mailnews/mime/emitters/nsEmitterUtils.cpp @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "prmem.h" +#include "plstr.h" +#include "nsMailHeaders.h" +#include "nsIMimeEmitter.h" +#include "nsIStringBundle.h" +#include "nsIServiceManager.h" +#include "nsIIOService.h" +#include "nsIURI.h" +#include "prprf.h" + + +extern "C" bool +EmitThisHeaderForPrefSetting(int32_t dispType, const char *header) +{ + if (nsMimeHeaderDisplayTypes::AllHeaders == dispType) + return true; + + if ((!header) || (!*header)) + return false; + + if (nsMimeHeaderDisplayTypes::MicroHeaders == dispType) + { + if ( + (!strcmp(header, HEADER_SUBJECT)) || + (!strcmp(header, HEADER_FROM)) || + (!strcmp(header, HEADER_DATE)) + ) + return true; + else + return false; + } + + if (nsMimeHeaderDisplayTypes::NormalHeaders == dispType) + { + if ( + (!strcmp(header, HEADER_DATE)) || + (!strcmp(header, HEADER_TO)) || + (!strcmp(header, HEADER_SUBJECT)) || + (!strcmp(header, HEADER_SENDER)) || + (!strcmp(header, HEADER_RESENT_TO)) || + (!strcmp(header, HEADER_RESENT_SENDER)) || + (!strcmp(header, HEADER_RESENT_FROM)) || + (!strcmp(header, HEADER_RESENT_CC)) || + (!strcmp(header, HEADER_REPLY_TO)) || + (!strcmp(header, HEADER_REFERENCES)) || + (!strcmp(header, HEADER_NEWSGROUPS)) || + (!strcmp(header, HEADER_MESSAGE_ID)) || + (!strcmp(header, HEADER_FROM)) || + (!strcmp(header, HEADER_FOLLOWUP_TO)) || + (!strcmp(header, HEADER_CC)) || + (!strcmp(header, HEADER_ORGANIZATION)) || + (!strcmp(header, HEADER_REPLY_TO)) || + (!strcmp(header, HEADER_BCC)) + ) + return true; + else + return false; + } + + return true; +} + diff --git a/mailnews/mime/emitters/nsEmitterUtils.h b/mailnews/mime/emitters/nsEmitterUtils.h new file mode 100644 index 000000000..965c1e33c --- /dev/null +++ b/mailnews/mime/emitters/nsEmitterUtils.h @@ -0,0 +1,14 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsEmitterUtils_h_ +#define _nsEmitterUtils_h_ + +#include "prmem.h" +#include "plstr.h" + +extern "C" bool EmitThisHeaderForPrefSetting(int32_t dispType, const char *header); + +#endif // _nsEmitterUtils_h_ + diff --git a/mailnews/mime/emitters/nsMimeBaseEmitter.cpp b/mailnews/mime/emitters/nsMimeBaseEmitter.cpp new file mode 100644 index 000000000..223eef433 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeBaseEmitter.cpp @@ -0,0 +1,1092 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include +#include "nsMimeBaseEmitter.h" +#include "nsMailHeaders.h" +#include "nscore.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIServiceManager.h" +#include "prmem.h" +#include "nsEmitterUtils.h" +#include "nsMimeStringResources.h" +#include "msgCore.h" +#include "nsIComponentManager.h" +#include "nsEmitterUtils.h" +#include "nsIMimeStreamConverter.h" +#include "nsIMimeConverter.h" +#include "nsMsgMimeCID.h" +#include "mozilla/Logging.h" +#include "prprf.h" +#include "nsIMimeHeaders.h" +#include "nsIMsgWindow.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsDateTimeFormatCID.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" +#include "nsTextFormatter.h" +#include "mozilla/Services.h" +#include + +static PRLogModuleInfo * gMimeEmitterLogModule = nullptr; + +#define MIME_HEADER_URL "chrome://messenger/locale/mimeheader.properties" +#define MIME_URL "chrome://messenger/locale/mime.properties" + +NS_IMPL_ISUPPORTS(nsMimeBaseEmitter, nsIMimeEmitter, nsIInterfaceRequestor) + +nsMimeBaseEmitter::nsMimeBaseEmitter() +{ + // Initialize data output vars... + mFirstHeaders = true; + + mBufferMgr = nullptr; + mTotalWritten = 0; + mTotalRead = 0; + mInputStream = nullptr; + mOutStream = nullptr; + mOutListener = nullptr; + + // Display output control vars... + mDocHeader = false; + m_stringBundle = nullptr; + mURL = nullptr; + mHeaderDisplayType = nsMimeHeaderDisplayTypes::NormalHeaders; + + // Setup array for attachments + mAttachCount = 0; + mAttachArray = new nsTArray(); + mCurrentAttachment = nullptr; + + // Header cache... + mHeaderArray = new nsTArray(); + + // Embedded Header Cache... + mEmbeddedHeaderArray = nullptr; + + // HTML Header Data... +// mHTMLHeaders = ""; +// mCharset = ""; + + // Init the body... + mBodyStarted = false; +// mBody = ""; + + // This is needed for conversion of I18N Strings... + mUnicodeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID); + + if (!gMimeEmitterLogModule) + gMimeEmitterLogModule = PR_NewLogModule("MIME"); + + // Do prefs last since we can live without this if it fails... + nsCOMPtr pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (pPrefBranch) + pPrefBranch->GetIntPref("mail.show_headers", &mHeaderDisplayType); +} + +nsMimeBaseEmitter::~nsMimeBaseEmitter(void) +{ + // Delete the buffer manager... + if (mBufferMgr) + { + delete mBufferMgr; + mBufferMgr = nullptr; + } + + // Clean up the attachment array structures... + if (mAttachArray) + { + for (size_t i = 0; i < mAttachArray->Length(); i++) + { + attachmentInfoType *attachInfo = mAttachArray->ElementAt(i); + if (!attachInfo) + continue; + + PR_FREEIF(attachInfo->contentType); + if (attachInfo->displayName) + NS_Free(attachInfo->displayName); + PR_FREEIF(attachInfo->urlSpec); + PR_FREEIF(attachInfo); + } + delete mAttachArray; + } + + // Cleanup allocated header arrays... + CleanupHeaderArray(mHeaderArray); + mHeaderArray = nullptr; + + CleanupHeaderArray(mEmbeddedHeaderArray); + mEmbeddedHeaderArray = nullptr; +} + +NS_IMETHODIMP nsMimeBaseEmitter::GetInterface(const nsIID & aIID, void * *aInstancePtr) +{ + NS_ENSURE_ARG_POINTER(aInstancePtr); + return QueryInterface(aIID, aInstancePtr); +} + +void +nsMimeBaseEmitter::CleanupHeaderArray(nsTArray *aArray) +{ + if (!aArray) + return; + + for (size_t i = 0; i < aArray->Length(); i++) + { + headerInfoType *headerInfo = aArray->ElementAt(i); + if (!headerInfo) + continue; + + PR_FREEIF(headerInfo->name); + PR_FREEIF(headerInfo->value); + PR_FREEIF(headerInfo); + } + + delete aArray; +} + +static int32_t MapHeaderNameToID(const char *header) +{ + // emitter passes UPPERCASE for header names + if (!strcmp(header, "DATE")) + return MIME_MHTML_DATE; + else if (!strcmp(header, "FROM")) + return MIME_MHTML_FROM; + else if (!strcmp(header, "SUBJECT")) + return MIME_MHTML_SUBJECT; + else if (!strcmp(header, "TO")) + return MIME_MHTML_TO; + else if (!strcmp(header, "SENDER")) + return MIME_MHTML_SENDER; + else if (!strcmp(header, "RESENT-TO")) + return MIME_MHTML_RESENT_TO; + else if (!strcmp(header, "RESENT-SENDER")) + return MIME_MHTML_RESENT_SENDER; + else if (!strcmp(header, "RESENT-FROM")) + return MIME_MHTML_RESENT_FROM; + else if (!strcmp(header, "RESENT-CC")) + return MIME_MHTML_RESENT_CC; + else if (!strcmp(header, "REPLY-TO")) + return MIME_MHTML_REPLY_TO; + else if (!strcmp(header, "REFERENCES")) + return MIME_MHTML_REFERENCES; + else if (!strcmp(header, "NEWSGROUPS")) + return MIME_MHTML_NEWSGROUPS; + else if (!strcmp(header, "MESSAGE-ID")) + return MIME_MHTML_MESSAGE_ID; + else if (!strcmp(header, "FOLLOWUP-TO")) + return MIME_MHTML_FOLLOWUP_TO; + else if (!strcmp(header, "CC")) + return MIME_MHTML_CC; + else if (!strcmp(header, "ORGANIZATION")) + return MIME_MHTML_ORGANIZATION; + else if (!strcmp(header, "BCC")) + return MIME_MHTML_BCC; + + return 0; +} + +char * +nsMimeBaseEmitter::MimeGetStringByName(const char *aHeaderName) +{ + nsresult res = NS_OK; + + if (!m_headerStringBundle) + { + static const char propertyURL[] = MIME_HEADER_URL; + + nsCOMPtr sBundleService = + mozilla::services::GetStringBundleService(); + if (sBundleService) + { + res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(m_headerStringBundle)); + } + } + + if (m_headerStringBundle) + { + nsString val; + + res = m_headerStringBundle->GetStringFromName(NS_ConvertASCIItoUTF16(aHeaderName).get(), + getter_Copies(val)); + + if (NS_FAILED(res)) + return nullptr; + + // Here we need to return a new copy of the string + // This returns a UTF-8 string so the caller needs to perform a conversion + // if this is used as UCS-2 (e.g. cannot do nsString(utfStr); + // + return ToNewUTF8String(val); + } + else + { + return nullptr; + } +} + +char * +nsMimeBaseEmitter::MimeGetStringByID(int32_t aID) +{ + nsresult res = NS_OK; + + if (!m_stringBundle) + { + static const char propertyURL[] = MIME_URL; + + nsCOMPtr sBundleService = + mozilla::services::GetStringBundleService(); + if (sBundleService) + res = sBundleService->CreateBundle(propertyURL, getter_AddRefs(m_stringBundle)); + } + + if (m_stringBundle) + { + nsString val; + + res = m_stringBundle->GetStringFromID(aID, getter_Copies(val)); + + if (NS_FAILED(res)) + return nullptr; + + return ToNewUTF8String(val); + } + else + return nullptr; +} + +// +// This will search a string bundle (eventually) to find a descriptive header +// name to match what was found in the mail message. aHeaderName is passed in +// in all caps and a dropback default name is provided. The caller needs to free +// the memory returned by this function. +// +char * +nsMimeBaseEmitter::LocalizeHeaderName(const char *aHeaderName, const char *aDefaultName) +{ + char *retVal = nullptr; + + // prefer to use translated strings if not for quoting + if (mFormat != nsMimeOutput::nsMimeMessageQuoting && + mFormat != nsMimeOutput::nsMimeMessageBodyQuoting) + { + // map name to id and get the translated string + int32_t id = MapHeaderNameToID(aHeaderName); + if (id > 0) + retVal = MimeGetStringByID(id); + } + + // get the string from the other bundle (usually not translated) + if (!retVal) + retVal = MimeGetStringByName(aHeaderName); + + if (retVal) + return retVal; + else + return strdup(aDefaultName); +} + +/////////////////////////////////////////////////////////////////////////// +// nsMimeBaseEmitter Interface +/////////////////////////////////////////////////////////////////////////// +NS_IMETHODIMP +nsMimeBaseEmitter::SetPipe(nsIInputStream * aInputStream, nsIOutputStream *outStream) +{ + mInputStream = aInputStream; + mOutStream = outStream; + return NS_OK; +} + +// Note - these is setup only...you should not write +// anything to the stream since these may be image data +// output streams, etc... +NS_IMETHODIMP +nsMimeBaseEmitter::Initialize(nsIURI *url, nsIChannel * aChannel, int32_t aFormat) +{ + // set the url + mURL = url; + mChannel = aChannel; + + // Create rebuffering object + delete mBufferMgr; + mBufferMgr = new MimeRebuffer(); + + // Counters for output stream + mTotalWritten = 0; + mTotalRead = 0; + mFormat = aFormat; + + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::SetOutputListener(nsIStreamListener *listener) +{ + mOutListener = listener; + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::GetOutputListener(nsIStreamListener **listener) +{ + NS_ENSURE_ARG_POINTER(listener); + + NS_IF_ADDREF(*listener = mOutListener); + return NS_OK; +} + + +// Attachment handling routines +nsresult +nsMimeBaseEmitter::StartAttachment(const nsACString &name, + const char *contentType, + const char *url, + bool aIsExternalAttachment) +{ + // Ok, now we will setup the attachment info + mCurrentAttachment = (attachmentInfoType *) PR_NEWZAP(attachmentInfoType); + if ( (mCurrentAttachment) && mAttachArray) + { + ++mAttachCount; + + mCurrentAttachment->displayName = ToNewCString(name); + mCurrentAttachment->urlSpec = strdup(url); + mCurrentAttachment->contentType = strdup(contentType); + mCurrentAttachment->isExternalAttachment = aIsExternalAttachment; + } + + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::EndAttachment() +{ + // Ok, add the attachment info to the attachment array... + if ( (mCurrentAttachment) && (mAttachArray) ) + { + mAttachArray->AppendElement(mCurrentAttachment); + mCurrentAttachment = nullptr; + } + + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::EndAllAttachments() +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::AddAttachmentField(const char *field, const char *value) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::UtilityWrite(const char *buf) +{ + NS_ENSURE_ARG_POINTER(buf); + + uint32_t written; + Write(nsDependentCString(buf), &written); + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::UtilityWrite(const nsACString &buf) +{ + uint32_t written; + Write(buf, &written); + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::UtilityWriteCRLF(const char *buf) +{ + NS_ENSURE_ARG_POINTER(buf); + + uint32_t written; + Write(nsDependentCString(buf), &written); + Write(NS_LITERAL_CSTRING(CRLF), &written); + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::Write(const nsACString &buf, uint32_t *amountWritten) +{ + unsigned int written = 0; + nsresult rv = NS_OK; + uint32_t needToWrite; + +#ifdef DEBUG_BenB + // If you want to see libmime output... + printf("%s", buf); +#endif + + MOZ_LOG(gMimeEmitterLogModule, mozilla::LogLevel::Info, ("%s", PromiseFlatCString(buf).get())); + // + // Make sure that the buffer we are "pushing" into has enough room + // for the write operation. If not, we have to buffer, return, and get + // it on the next time through + // + *amountWritten = 0; + + needToWrite = mBufferMgr->GetSize(); + // First, handle any old buffer data... + if (needToWrite > 0) + { + rv = WriteHelper(mBufferMgr->GetBuffer(), &written); + + mTotalWritten += written; + mBufferMgr->ReduceBuffer(written); + *amountWritten = written; + + // if we couldn't write all the old data, buffer the new data + // and return + if (mBufferMgr->GetSize() > 0) + { + mBufferMgr->IncreaseBuffer(buf); + return rv; + } + } + + + // if we get here, we are dealing with new data...try to write + // and then do the right thing... + rv = WriteHelper(buf, &written); + *amountWritten = written; + mTotalWritten += written; + + if (written < buf.Length()) { + const nsACString &remainder = Substring(buf, written); + mBufferMgr->IncreaseBuffer(remainder); + } + + return rv; +} + +nsresult +nsMimeBaseEmitter::WriteHelper(const nsACString &buf, uint32_t *countWritten) +{ + NS_ENSURE_TRUE(mOutStream, NS_ERROR_NOT_INITIALIZED); + + nsresult rv = mOutStream->Write(buf.BeginReading(), buf.Length(), countWritten); + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + // pipe is full, push contents of pipe to listener... + uint64_t avail; + rv = mInputStream->Available(&avail); + if (NS_SUCCEEDED(rv) && avail) { + mOutListener->OnDataAvailable(mChannel, mURL, mInputStream, 0, + std::min(avail, uint64_t(PR_UINT32_MAX))); + + // try writing again... + rv = mOutStream->Write(buf.BeginReading(), buf.Length(), countWritten); + } + } + return rv; +} + +// +// Find a cached header! Note: Do NOT free this value! +// +const char * +nsMimeBaseEmitter::GetHeaderValue(const char *aHeaderName) +{ + char *retVal = nullptr; + nsTArray *array = mDocHeader? mHeaderArray : mEmbeddedHeaderArray; + + if (!array) + return nullptr; + + for (size_t i = 0; i < array->Length(); i++) + { + headerInfoType *headerInfo = array->ElementAt(i); + if ( (!headerInfo) || (!headerInfo->name) || (!(*headerInfo->name)) ) + continue; + + if (!PL_strcasecmp(aHeaderName, headerInfo->name)) + { + retVal = headerInfo->value; + break; + } + } + + return retVal; +} + +// +// This is called at the start of the header block for all header information in ANY +// AND ALL MESSAGES (yes, quoted, attached, etc...) +// +// NOTE: This will be called even when headers are will not follow. This is +// to allow us to be notified of the charset of the original message. This is +// important for forward and reply operations +// +NS_IMETHODIMP +nsMimeBaseEmitter::StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID, + const char *outCharset) +{ + NS_ENSURE_ARG_POINTER(outCharset); + + mDocHeader = rootMailHeader; + + // If this is not the mail messages header, then we need to create + // the mEmbeddedHeaderArray structure for use with this internal header + // structure. + if (!mDocHeader) + { + if (mEmbeddedHeaderArray) + CleanupHeaderArray(mEmbeddedHeaderArray); + + mEmbeddedHeaderArray = new nsTArray(); + NS_ENSURE_TRUE(mEmbeddedHeaderArray, NS_ERROR_OUT_OF_MEMORY); + } + + // If the main doc, check on updated character set + if (mDocHeader) + UpdateCharacterSet(outCharset); + CopyASCIItoUTF16(nsDependentCString(outCharset), mCharset); + return NS_OK; +} + +// Ok, if we are here, and we have a aCharset passed in that is not +// UTF-8 or US-ASCII, then we should tag the mChannel member with this +// charset. This is because replying to messages with specified charset's +// need to be tagged as that charset by default. +// +NS_IMETHODIMP +nsMimeBaseEmitter::UpdateCharacterSet(const char *aCharset) +{ + if (aCharset) + { + nsAutoCString contentType; + + if (NS_SUCCEEDED(mChannel->GetContentType(contentType)) && !contentType.IsEmpty()) + { + char *cBegin = contentType.BeginWriting(); + + const char *cPtr = PL_strcasestr(cBegin, "charset="); + + if (cPtr) + { + char *ptr = cBegin; + while (*ptr) + { + if ( (*ptr == ' ') || (*ptr == ';') ) + { + if ((ptr + 1) >= cPtr) + { + *ptr = '\0'; + break; + } + } + + ++ptr; + } + } + + // have to set content-type since it could have an embedded null byte + mChannel->SetContentType(nsDependentCString(cBegin)); + if (PL_strcasecmp(aCharset, "US-ASCII") == 0) { + mChannel->SetContentCharset(NS_LITERAL_CSTRING("ISO-8859-1")); + } else { + mChannel->SetContentCharset(nsDependentCString(aCharset)); + } + } + } + + return NS_OK; +} + +// +// This will be called for every header field regardless if it is in an +// internal body or the outer message. +// +NS_IMETHODIMP +nsMimeBaseEmitter::AddHeaderField(const char *field, const char *value) +{ + if ( (!field) || (!value) ) + return NS_OK; + + nsTArray *tPtr; + if (mDocHeader) + tPtr = mHeaderArray; + else + tPtr = mEmbeddedHeaderArray; + + // This is a header so we need to cache and output later. + // Ok, now we will setup the header info for the header array! + headerInfoType *ptr = (headerInfoType *) PR_NEWZAP(headerInfoType); + if ( (ptr) && tPtr) + { + ptr->name = strdup(field); + ptr->value = strdup(value); + tPtr->AppendElement(ptr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::AddAllHeaders(const nsACString &allheaders) +{ + if (mDocHeader) //We want to set only the main headers of a message, not the potentially embedded one + { + nsresult rv; + nsCOMPtr msgurl (do_QueryInterface(mURL)); + if (msgurl) + { + nsCOMPtr mimeHeaders = do_CreateInstance(NS_IMIMEHEADERS_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mimeHeaders->Initialize(allheaders); + msgurl->SetMimeHeaders(mimeHeaders); + } + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// The following code is responsible for formatting headers in a manner that is +// identical to the normal XUL output. +//////////////////////////////////////////////////////////////////////////////// + +nsresult +nsMimeBaseEmitter::GenerateDateString(const char * dateString, + nsACString &formattedDate, + bool showDateForToday) +{ + nsresult rv = NS_OK; + + if (!mDateFormatter) { + mDateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + /** + * See if the user wants to have the date displayed in the senders + * timezone (including the timezone offset). + * We also evaluate the pref original_date which was introduced + * as makeshift in bug 118899. + */ + bool displaySenderTimezone = false; + bool displayOriginalDate = false; + + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr dateFormatPrefs; + rv = prefs->GetBranch("mailnews.display.", getter_AddRefs(dateFormatPrefs)); + NS_ENSURE_SUCCESS(rv, rv); + + dateFormatPrefs->GetBoolPref("date_senders_timezone", &displaySenderTimezone); + dateFormatPrefs->GetBoolPref("original_date", &displayOriginalDate); + // migrate old pref to date_senders_timezone + if (displayOriginalDate && !displaySenderTimezone) + dateFormatPrefs->SetBoolPref("date_senders_timezone", true); + + PRExplodedTime explodedMsgTime; + + // Bogus date string may leave some fields uninitialized, so take precaution. + memset(&explodedMsgTime, 0, sizeof (PRExplodedTime)); + + if (PR_ParseTimeStringToExplodedTime(dateString, false, &explodedMsgTime) != PR_SUCCESS) + return NS_ERROR_FAILURE; + + /** + * To determine the date format to use, comparison of current and message + * time has to be made. If displaying in local time, both timestamps have + * to be in local time. If displaying in senders time zone, leave the compare + * time in that time zone. + * Otherwise in TZ+0100 on 2009-03-12 a message from 2009-03-11T20:49-0700 + * would be displayed as "20:49 -0700" though it in fact is not from the + * same day. + */ + PRExplodedTime explodedCompTime; + if (displaySenderTimezone) + explodedCompTime = explodedMsgTime; + else + PR_ExplodeTime(PR_ImplodeTime(&explodedMsgTime), PR_LocalTimeParameters, &explodedCompTime); + + PRExplodedTime explodedCurrentTime; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &explodedCurrentTime); + + // If we want short dates, check if the message is from today, and if so + // only show the time (e.g. 3:15 pm). + nsDateFormatSelector dateFormat = kDateFormatShort; + if (!showDateForToday && + explodedCurrentTime.tm_year == explodedCompTime.tm_year && + explodedCurrentTime.tm_month == explodedCompTime.tm_month && + explodedCurrentTime.tm_mday == explodedCompTime.tm_mday) + { + // same day... + dateFormat = kDateFormatNone; + } + + nsAutoString formattedDateString; + + rv = mDateFormatter->FormatPRExplodedTime(nullptr /* nsILocale* locale */, + dateFormat, + kTimeFormatNoSeconds, + &explodedCompTime, + formattedDateString); + + if (NS_SUCCEEDED(rv)) + { + if (displaySenderTimezone) + { + // offset of local time from UTC in minutes + int32_t senderoffset = (explodedMsgTime.tm_params.tp_gmt_offset + + explodedMsgTime.tm_params.tp_dst_offset) / 60; + // append offset to date string + char16_t *tzstring = + nsTextFormatter::smprintf(u" %+05d", + (senderoffset / 60 * 100) + + (senderoffset % 60)); + formattedDateString.Append(tzstring); + nsTextFormatter::smprintf_free(tzstring); + } + + CopyUTF16toUTF8(formattedDateString, formattedDate); + } + + return rv; +} + +char* +nsMimeBaseEmitter::GetLocalizedDateString(const char * dateString) +{ + char *i18nValue = nullptr; + + bool displayOriginalDate = false; + nsCOMPtr prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + + if (prefBranch) + prefBranch->GetBoolPref("mailnews.display.original_date", + &displayOriginalDate); + + if (!displayOriginalDate) + { + nsAutoCString convertedDateString; + nsresult rv = GenerateDateString(dateString, convertedDateString, true); + if (NS_SUCCEEDED(rv)) + i18nValue = strdup(convertedDateString.get()); + else + i18nValue = strdup(dateString); + } + else + i18nValue = strdup(dateString); + + return i18nValue; +} + +nsresult +nsMimeBaseEmitter::WriteHeaderFieldHTML(const char *field, const char *value) +{ + char *newValue = nullptr; + char *i18nValue = nullptr; + + if ( (!field) || (!value) ) + return NS_OK; + + // + // This is a check to see what the pref is for header display. If + // We should only output stuff that corresponds with that setting. + // + if (!EmitThisHeaderForPrefSetting(mHeaderDisplayType, field)) + return NS_OK; + + // + // If we encounter the 'Date' header we try to convert it's value + // into localized format. + // + if ( strcmp(field, "Date") == 0 ) + i18nValue = GetLocalizedDateString(value); + else + i18nValue = strdup(value); + + if ( (mUnicodeConverter) && (mFormat != nsMimeOutput::nsMimeMessageSaveAs) ) + { + nsCString tValue; + + // we're going to need a converter to convert + nsresult rv = mUnicodeConverter->DecodeMimeHeaderToUTF8( + nsDependentCString(i18nValue), nullptr, false, true, tValue); + if (NS_SUCCEEDED(rv) && !tValue.IsEmpty()) + newValue = MsgEscapeHTML(tValue.get()); + else + newValue = MsgEscapeHTML(i18nValue); + } + else + { + newValue = MsgEscapeHTML(i18nValue); + } + + free(i18nValue); + + if (!newValue) + return NS_OK; + + mHTMLHeaders.Append(""); + mHTMLHeaders.Append(""); + + if (mFormat == nsMimeOutput::nsMimeMessageSaveAs) + mHTMLHeaders.Append(""); + else + mHTMLHeaders.Append("
"); + + // Here is where we are going to try to L10N the tagName so we will always + // get a field name next to an emitted header value. Note: Default will always + // be the name of the header itself. + // + nsCString newTagName(field); + newTagName.StripWhitespace(); + ToUpperCase(newTagName); + + char *l10nTagName = LocalizeHeaderName(newTagName.get(), field); + if ( (!l10nTagName) || (!*l10nTagName) ) + mHTMLHeaders.Append(field); + else + { + mHTMLHeaders.Append(l10nTagName); + PR_FREEIF(l10nTagName); + } + + mHTMLHeaders.Append(": "); + if (mFormat == nsMimeOutput::nsMimeMessageSaveAs) + mHTMLHeaders.Append(""); + else + mHTMLHeaders.Append("
"); + + // Now write out the actual value itself and move on! + // + mHTMLHeaders.Append(newValue); + mHTMLHeaders.Append(""); + + mHTMLHeaders.Append(""); + + PR_FREEIF(newValue); + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::WriteHeaderFieldHTMLPrefix(const nsACString &name) +{ + if ( + ( (mFormat == nsMimeOutput::nsMimeMessageSaveAs) && (mFirstHeaders) ) || + ( (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) && (mFirstHeaders) ) + ) + /* DO NOTHING */ ; // rhp: Do nothing...leaving the conditional like this so its + // easier to see the logic of what is going on. + else { + mHTMLHeaders.Append("
"); + if (!name.IsEmpty()) { + mHTMLHeaders.Append(""); + nsCString escapedName; + escapedName.Adopt(MsgEscapeHTML(nsCString(name).get())); + mHTMLHeaders.Append(escapedName); + mHTMLHeaders.Append(""); + } + mHTMLHeaders.Append("
"); + } + + mFirstHeaders = false; + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::WriteHeaderFieldHTMLPostfix() +{ + mHTMLHeaders.Append("
"); + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::WriteHTMLHeaders(const nsACString &name) +{ + WriteHeaderFieldHTMLPrefix(name); + + // Start with the subject, from date info! + DumpSubjectFromDate(); + + // Continue with the to and cc headers + DumpToCC(); + + // Do the rest of the headers, but these will only be written if + // the user has the "show all headers" pref set + if (mHeaderDisplayType == nsMimeHeaderDisplayTypes::AllHeaders) + DumpRestOfHeaders(); + + WriteHeaderFieldHTMLPostfix(); + + // Now, we need to either append the headers we built up to the + // overall body or output to the stream. + UtilityWriteCRLF(mHTMLHeaders.get()); + + mHTMLHeaders = ""; + + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::DumpSubjectFromDate() +{ + mHTMLHeaders.Append(""); + + // This is the envelope information + OutputGenericHeader(HEADER_SUBJECT); + OutputGenericHeader(HEADER_FROM); + OutputGenericHeader(HEADER_DATE); + + // If we are Quoting a message, then we should dump the To: also + if ( ( mFormat == nsMimeOutput::nsMimeMessageQuoting ) || + ( mFormat == nsMimeOutput::nsMimeMessageBodyQuoting ) ) + OutputGenericHeader(HEADER_TO); + + mHTMLHeaders.Append("
"); + + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::DumpToCC() +{ + const char * toField = GetHeaderValue(HEADER_TO); + const char * ccField = GetHeaderValue(HEADER_CC); + const char * bccField = GetHeaderValue(HEADER_BCC); + const char * newsgroupField = GetHeaderValue(HEADER_NEWSGROUPS); + + // only dump these fields if we have at least one of them! When displaying news + // messages that didn't have a To or Cc field, we'd always get an empty box + // which looked weird. + if (toField || ccField || bccField || newsgroupField) + { + mHTMLHeaders.Append(""); + + if (toField) + WriteHeaderFieldHTML(HEADER_TO, toField); + if (ccField) + WriteHeaderFieldHTML(HEADER_CC, ccField); + if (bccField) + WriteHeaderFieldHTML(HEADER_BCC, bccField); + if (newsgroupField) + WriteHeaderFieldHTML(HEADER_NEWSGROUPS, newsgroupField); + + mHTMLHeaders.Append("
"); + } + + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::DumpRestOfHeaders() +{ + nsTArray *array = mDocHeader? mHeaderArray : mEmbeddedHeaderArray; + + mHTMLHeaders.Append(""); + + for (size_t i = 0; i < array->Length(); i++) + { + headerInfoType *headerInfo = array->ElementAt(i); + if ( (!headerInfo) || (!headerInfo->name) || (!(*headerInfo->name)) || + (!headerInfo->value) || (!(*headerInfo->value))) + continue; + + if ( (!PL_strcasecmp(HEADER_SUBJECT, headerInfo->name)) || + (!PL_strcasecmp(HEADER_DATE, headerInfo->name)) || + (!PL_strcasecmp(HEADER_FROM, headerInfo->name)) || + (!PL_strcasecmp(HEADER_TO, headerInfo->name)) || + (!PL_strcasecmp(HEADER_CC, headerInfo->name)) ) + continue; + + WriteHeaderFieldHTML(headerInfo->name, headerInfo->value); + } + + mHTMLHeaders.Append("
"); + return NS_OK; +} + +nsresult +nsMimeBaseEmitter::OutputGenericHeader(const char *aHeaderVal) +{ + const char *val = GetHeaderValue(aHeaderVal); + + if (val) + return WriteHeaderFieldHTML(aHeaderVal, val); + + return NS_ERROR_FAILURE; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +// These are the methods that should be implemented by the child class! +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +// +// This should be implemented by the child class if special processing +// needs to be done when the entire message is read. +// +NS_IMETHODIMP +nsMimeBaseEmitter::Complete() +{ + // If we are here and still have data to write, we should try + // to flush it...if we try and fail, we should probably return + // an error! + uint32_t written; + + nsresult rv = NS_OK; + while ( NS_SUCCEEDED(rv) && (mBufferMgr) && (mBufferMgr->GetSize() > 0)) + rv = Write(EmptyCString(), &written); + + if (mOutListener) + { + uint64_t bytesInStream = 0; + mozilla::DebugOnly rv2 = mInputStream->Available(&bytesInStream); + NS_ASSERTION(NS_SUCCEEDED(rv2), "Available failed"); + if (bytesInStream) + { + nsCOMPtr request = do_QueryInterface(mChannel); + mOutListener->OnDataAvailable(request, mURL, mInputStream, 0, std::min(bytesInStream, uint64_t(PR_UINT32_MAX))); + } + } + + return NS_OK; +} + +// +// This needs to do the right thing with the stored information. It only +// has to do the output functions, this base class will take care of the +// memory cleanup +// +NS_IMETHODIMP +nsMimeBaseEmitter::EndHeader(const nsACString &name) +{ + return NS_OK; +} + +// body handling routines +NS_IMETHODIMP +nsMimeBaseEmitter::StartBody(bool bodyOnly, const char *msgID, const char *outCharset) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::WriteBody(const nsACString &buf, uint32_t *amountWritten) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMimeBaseEmitter::EndBody() +{ + return NS_OK; +} diff --git a/mailnews/mime/emitters/nsMimeBaseEmitter.h b/mailnews/mime/emitters/nsMimeBaseEmitter.h new file mode 100644 index 000000000..c33bc2687 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeBaseEmitter.h @@ -0,0 +1,147 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsMimeBaseEmitter_h_ +#define _nsMimeBaseEmitter_h_ + +#include "prio.h" +#include "nsIMimeEmitter.h" +#include "nsMimeRebuffer.h" +#include "nsIStreamListener.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIAsyncInputStream.h" +#include "nsIURI.h" +#include "nsIChannel.h" +#include "nsIMimeMiscStatus.h" +#include "nsIPipe.h" +#include "nsIStringBundle.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsIMimeConverter.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIDateTimeFormat.h" + +// +// The base emitter will serve as the place to do all of the caching, +// sorting, etc... of mail headers and bodies for this internally developed +// emitter library. The other emitter classes in this file (nsMimeHTMLEmitter, etc.) +// will only be concerned with doing output processing ONLY. +// + +// +// Used for keeping track of the attachment information... +// +typedef struct { + char *displayName; + char *urlSpec; + char *contentType; + bool isExternalAttachment; +} attachmentInfoType; + +// +// For header info... +// +typedef struct { + char *name; + char *value; +} headerInfoType; + +class nsMimeBaseEmitter : public nsIMimeEmitter, + public nsIInterfaceRequestor +{ +public: + nsMimeBaseEmitter (); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIMIMEEMITTER + NS_DECL_NSIINTERFACEREQUESTOR + + // Utility output functions... + NS_IMETHOD UtilityWrite(const nsACString &buf); + NS_IMETHOD UtilityWriteCRLF(const char *buf); + + // For string bundle usage... + char *MimeGetStringByName(const char *aHeaderName); + char *MimeGetStringByID(int32_t aID); + char *LocalizeHeaderName(const char *aHeaderName, const char *aDefaultName); + + // For header processing... + const char *GetHeaderValue(const char *aHeaderName); + + // To write out a stored header array as HTML + virtual nsresult WriteHeaderFieldHTMLPrefix(const nsACString &name); + virtual nsresult WriteHeaderFieldHTML(const char *field, const char *value); + virtual nsresult WriteHeaderFieldHTMLPostfix(); + +protected: + virtual ~nsMimeBaseEmitter(); + // Internal methods... + void CleanupHeaderArray(nsTArray *aArray); + + // For header output... + nsresult DumpSubjectFromDate(); + nsresult DumpToCC(); + nsresult DumpRestOfHeaders(); + nsresult OutputGenericHeader(const char *aHeaderVal); + + nsresult WriteHelper(const nsACString &buf, uint32_t *countWritten); + + // For string bundle usage... + nsCOMPtr m_stringBundle; // for translated strings + nsCOMPtr m_headerStringBundle; // for non-translated header strings + + // For buffer management on output + MimeRebuffer *mBufferMgr; + + // mscott + // don't ref count the streams....the emitter is owned by the converter + // which owns these streams... + // + nsIOutputStream *mOutStream; + nsIInputStream *mInputStream; + nsIStreamListener *mOutListener; + nsCOMPtr mChannel; + + // For gathering statistics on processing... + uint32_t mTotalWritten; + uint32_t mTotalRead; + + // Output control and info... + bool mDocHeader; // For header determination... + nsIURI *mURL; // the url for the data being processed... + int32_t mHeaderDisplayType; // The setting for header output... + nsCString mHTMLHeaders; // HTML Header Data... + + // For attachment processing... + int32_t mAttachCount; + nsTArray *mAttachArray; + attachmentInfoType *mCurrentAttachment; + + // For header caching... + nsTArray *mHeaderArray; + nsTArray *mEmbeddedHeaderArray; + + // For body caching... + bool mBodyStarted; + nsCString mBody; + bool mFirstHeaders; + + // For the format being used... + int32_t mFormat; + + // For I18N Conversion... + nsCOMPtr mUnicodeConverter; + nsString mCharset; + nsCOMPtr mDateFormatter; + nsresult GenerateDateString(const char * dateString, nsACString& formattedDate, + bool showDateForToday); + // The caller is expected to free the result of GetLocalizedDateString + char* GetLocalizedDateString(const char * dateString); +}; + +#endif /* _nsMimeBaseEmitter_h_ */ diff --git a/mailnews/mime/emitters/nsMimeEmitterCID.h b/mailnews/mime/emitters/nsMimeEmitterCID.h new file mode 100644 index 000000000..f2e8c6039 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeEmitterCID.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsMimeEmitterCID_h__ +#define nsMimeEmitterCID_h__ + +#include "nsISupports.h" +#include "nsIFactory.h" +#include "nsIComponentManager.h" + +#define NS_MIME_EMITTER_CONTRACTID_PREFIX \ + "@mozilla.org/messenger/mimeemitter;1?type=" + +#define NS_HTML_MIME_EMITTER_CONTRACTID \ + NS_MIME_EMITTER_CONTRACTID_PREFIX "text/html" +// {F0A8AF16-DCCE-11d2-A411-00805F613C79} +#define NS_HTML_MIME_EMITTER_CID \ + { 0xf0a8af16, 0xdcce, 0x11d2, \ + { 0xa4, 0x11, 0x0, 0x80, 0x5f, 0x61, 0x3c, 0x79 } } + +#define NS_XML_MIME_EMITTER_CONTRACTID \ + NS_MIME_EMITTER_CONTRACTID_PREFIX "text/xml" +// {977E418F-E392-11d2-A2AC-00A024A7D144} +#define NS_XML_MIME_EMITTER_CID \ + { 0x977e418f, 0xe392, 0x11d2, \ + { 0xa2, 0xac, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } } + +#define NS_RAW_MIME_EMITTER_CONTRACTID \ + NS_MIME_EMITTER_CONTRACTID_PREFIX "raw" +// {F0A8AF16-DCFF-11d2-A411-00805F613C79} +#define NS_RAW_MIME_EMITTER_CID \ + { 0xf0a8af16, 0xdcff, 0x11d2, \ + { 0xa4, 0x11, 0x0, 0x80, 0x5f, 0x61, 0x3c, 0x79 } } + +#define NS_XUL_MIME_EMITTER_CONTRACTID \ + NS_MIME_EMITTER_CONTRACTID_PREFIX "application/vnd.mozilla.xul+xml" +// {FAA8AF16-DCFF-11d2-A411-00805F613C19} +#define NS_XUL_MIME_EMITTER_CID \ + { 0xfaa8af16, 0xdcff, 0x11d2, \ + { 0xa4, 0x11, 0x0, 0x80, 0x5f, 0x61, 0x3c, 0x19 } } + +#define NS_PLAIN_MIME_EMITTER_CONTRACTID \ + NS_MIME_EMITTER_CONTRACTID_PREFIX "text/plain" +// {E8892265-7653-46c5-A290-307F3404D0F3} +#define NS_PLAIN_MIME_EMITTER_CID \ + { 0xe8892265, 0x7653, 0x46c5, \ + { 0xa2, 0x90, 0x30, 0x7f, 0x34, 0x4, 0xd0, 0xf3 } } + +#endif // nsMimeEmitterCID_h__ diff --git a/mailnews/mime/emitters/nsMimeHtmlEmitter.cpp b/mailnews/mime/emitters/nsMimeHtmlEmitter.cpp new file mode 100644 index 000000000..d68d7f15c --- /dev/null +++ b/mailnews/mime/emitters/nsMimeHtmlEmitter.cpp @@ -0,0 +1,543 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include +#include "nsMimeRebuffer.h" +#include "nsMimeHtmlEmitter.h" +#include "plstr.h" +#include "nsMailHeaders.h" +#include "nscore.h" +#include "nsEmitterUtils.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIMimeStreamConverter.h" +#include "nsIMsgWindow.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsMimeTypes.h" +#include "prtime.h" +#include "prprf.h" +#include "nsIStringEnumerator.h" +#include "nsServiceManagerUtils.h" +// hack: include this to fix opening news attachments. +#include "nsINntpUrl.h" +#include "nsComponentManagerUtils.h" +#include "nsIMimeConverter.h" +#include "nsMsgMimeCID.h" +#include "nsMsgUtils.h" +#include "nsAutoPtr.h" +#include "nsINetUtil.h" +#include "nsMemory.h" +#include "mozilla/Services.h" + +#define VIEW_ALL_HEADERS 2 + +/** + * A helper class to implement nsIUTF8StringEnumerator + */ + +class nsMimeStringEnumerator final : public nsIUTF8StringEnumerator { +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIUTF8STRINGENUMERATOR + + nsMimeStringEnumerator() : mCurrentIndex(0) {} + + template + nsCString* Append(T value) { return mValues.AppendElement(value); } + +protected: + ~nsMimeStringEnumerator() {} + nsTArray mValues; + uint32_t mCurrentIndex; // consumers expect first-in first-out enumeration +}; + +NS_IMPL_ISUPPORTS(nsMimeStringEnumerator, nsIUTF8StringEnumerator) + +NS_IMETHODIMP +nsMimeStringEnumerator::HasMore(bool *result) +{ + NS_ENSURE_ARG_POINTER(result); + *result = mCurrentIndex < mValues.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsMimeStringEnumerator::GetNext(nsACString& result) +{ + if (mCurrentIndex >= mValues.Length()) + return NS_ERROR_UNEXPECTED; + + result = mValues[mCurrentIndex++]; + return NS_OK; +} + +/* + * nsMimeHtmlEmitter definitions.... + */ +nsMimeHtmlDisplayEmitter::nsMimeHtmlDisplayEmitter() : nsMimeBaseEmitter() +{ + mFirst = true; + mSkipAttachment = false; +} + +nsMimeHtmlDisplayEmitter::~nsMimeHtmlDisplayEmitter(void) +{ +} + +nsresult nsMimeHtmlDisplayEmitter::Init() +{ + return NS_OK; +} + +bool nsMimeHtmlDisplayEmitter::BroadCastHeadersAndAttachments() +{ + // try to get a header sink if there is one.... + nsCOMPtr headerSink; + nsresult rv = GetHeaderSink(getter_AddRefs(headerSink)); + if (NS_SUCCEEDED(rv) && headerSink && mDocHeader) + return true; + else + return false; +} + +nsresult +nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTMLPrefix(const nsACString &name) +{ + if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)) + return nsMimeBaseEmitter::WriteHeaderFieldHTMLPrefix(name); + else + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTML(const char *field, const char *value) +{ + if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)) + return nsMimeBaseEmitter::WriteHeaderFieldHTML(field, value); + else + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::WriteHeaderFieldHTMLPostfix() +{ + if (!BroadCastHeadersAndAttachments() || (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)) + return nsMimeBaseEmitter::WriteHeaderFieldHTMLPostfix(); + else + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::GetHeaderSink(nsIMsgHeaderSink ** aHeaderSink) +{ + nsresult rv = NS_OK; + if ( (mChannel) && (!mHeaderSink) ) + { + nsCOMPtr uri; + mChannel->GetURI(getter_AddRefs(uri)); + if (uri) + { + nsCOMPtr msgurl (do_QueryInterface(uri)); + if (msgurl) + { + msgurl->GetMsgHeaderSink(getter_AddRefs(mHeaderSink)); + if (!mHeaderSink) // if the url is not overriding the header sink, then just get the one from the msg window + { + nsCOMPtr msgWindow; + msgurl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + msgWindow->GetMsgHeaderSink(getter_AddRefs(mHeaderSink)); + } + } + } + } + + *aHeaderSink = mHeaderSink; + NS_IF_ADDREF(*aHeaderSink); + return rv; +} + +nsresult nsMimeHtmlDisplayEmitter::BroadcastHeaders(nsIMsgHeaderSink * aHeaderSink, int32_t aHeaderMode, bool aFromNewsgroup) +{ + // two string enumerators to pass out to the header sink + RefPtr headerNameEnumerator = new nsMimeStringEnumerator(); + NS_ENSURE_TRUE(headerNameEnumerator, NS_ERROR_OUT_OF_MEMORY); + RefPtr headerValueEnumerator = new nsMimeStringEnumerator(); + NS_ENSURE_TRUE(headerValueEnumerator, NS_ERROR_OUT_OF_MEMORY); + + nsCString extraExpandedHeaders; + nsTArray extraExpandedHeadersArray; + nsAutoCString convertedDateString; + + nsresult rv; + nsCOMPtr pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + { + pPrefBranch->GetCharPref("mailnews.headers.extraExpandedHeaders", getter_Copies(extraExpandedHeaders)); + // todo - should make this upper case + if (!extraExpandedHeaders.IsEmpty()) + { + ToLowerCase(extraExpandedHeaders); + ParseString(extraExpandedHeaders, ' ', extraExpandedHeadersArray); + } + } + + for (size_t i = 0; i < mHeaderArray->Length(); i++) + { + headerInfoType * headerInfo = mHeaderArray->ElementAt(i); + if ( (!headerInfo) || (!headerInfo->name) || (!(*headerInfo->name)) || (!headerInfo->value) || (!(*headerInfo->value))) + continue; + + const char * headerValue = headerInfo->value; + + // optimization: if we aren't in view all header view mode, we only show a small set of the total # of headers. + // don't waste time sending those out to the UI since the UI is going to ignore them anyway. + if (aHeaderMode != VIEW_ALL_HEADERS && (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer)) + { + nsDependentCString headerStr(headerInfo->name); + if (PL_strcasecmp("to", headerInfo->name) && PL_strcasecmp("from", headerInfo->name) && + PL_strcasecmp("cc", headerInfo->name) && PL_strcasecmp("newsgroups", headerInfo->name) && + PL_strcasecmp("bcc", headerInfo->name) && PL_strcasecmp("followup-to", headerInfo->name) && + PL_strcasecmp("reply-to", headerInfo->name) && PL_strcasecmp("subject", headerInfo->name) && + PL_strcasecmp("organization", headerInfo->name) && PL_strcasecmp("user-agent", headerInfo->name) && + PL_strcasecmp("content-base", headerInfo->name) && PL_strcasecmp("sender", headerInfo->name) && + PL_strcasecmp("date", headerInfo->name) && PL_strcasecmp("x-mailer", headerInfo->name) && + PL_strcasecmp("content-type", headerInfo->name) && PL_strcasecmp("message-id", headerInfo->name) && + PL_strcasecmp("x-newsreader", headerInfo->name) && PL_strcasecmp("x-mimeole", headerInfo->name) && + PL_strcasecmp("references", headerInfo->name) && PL_strcasecmp("in-reply-to", headerInfo->name) && + PL_strcasecmp("list-post", headerInfo->name) && PL_strcasecmp("delivered-to", headerInfo->name) && + // make headerStr lower case because IndexOf is case-sensitive + (!extraExpandedHeadersArray.Length() || (ToLowerCase(headerStr), + !extraExpandedHeadersArray.Contains(headerStr)))) + continue; + } + + headerNameEnumerator->Append(headerInfo->name); + headerValueEnumerator->Append(headerValue); + + // Add a localized version of the date header if we encounter it. + if (!PL_strcasecmp("Date", headerInfo->name)) + { + headerNameEnumerator->Append("X-Mozilla-LocalizedDate"); + GenerateDateString(headerValue, convertedDateString, false); + headerValueEnumerator->Append(convertedDateString); + } + } + + aHeaderSink->ProcessHeaders(headerNameEnumerator, headerValueEnumerator, aFromNewsgroup); + return rv; +} + +NS_IMETHODIMP nsMimeHtmlDisplayEmitter::WriteHTMLHeaders(const nsACString &name) +{ + // if we aren't broadcasting headers OR printing...just do whatever + // our base class does... + if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) + { + return nsMimeBaseEmitter::WriteHTMLHeaders(name); + } + else if (!BroadCastHeadersAndAttachments() || !mDocHeader) + { + // This needs to be here to correct the output format if we are + // not going to broadcast headers to the XUL document. + if (mFormat == nsMimeOutput::nsMimeMessageBodyDisplay) + mFormat = nsMimeOutput::nsMimeMessagePrintOutput; + + return nsMimeBaseEmitter::WriteHTMLHeaders(name); + } + else + mFirstHeaders = false; + + bool bFromNewsgroups = false; + for (size_t j = 0; j < mHeaderArray->Length(); j++) + { + headerInfoType *headerInfo = mHeaderArray->ElementAt(j); + if (!(headerInfo && headerInfo->name && *headerInfo->name)) + continue; + + if (!PL_strcasecmp("Newsgroups", headerInfo->name)) + { + bFromNewsgroups = true; + break; + } + } + + // try to get a header sink if there is one.... + nsCOMPtr headerSink; + nsresult rv = GetHeaderSink(getter_AddRefs(headerSink)); + + if (headerSink) + { + int32_t viewMode = 0; + nsCOMPtr pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (pPrefBranch) + rv = pPrefBranch->GetIntPref("mail.show_headers", &viewMode); + + rv = BroadcastHeaders(headerSink, viewMode, bFromNewsgroups); + } // if header Sink + + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::EndHeader(const nsACString &name) +{ + if (mDocHeader && (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer)) + { + UtilityWriteCRLF(""); + UtilityWriteCRLF(""); + + const char * val = GetHeaderValue(HEADER_SUBJECT); // do not free this value + if (val) + { + char * subject = MsgEscapeHTML(val); + if (subject) + { + int32_t bufLen = strlen(subject) + 16; + char *buf = new char[bufLen]; + if (!buf) + return NS_ERROR_OUT_OF_MEMORY; + PR_snprintf(buf, bufLen, "%s", subject); + UtilityWriteCRLF(buf); + delete [] buf; + free(subject); + } + } + + // Stylesheet info! + UtilityWriteCRLF(""); + + UtilityWriteCRLF(""); + UtilityWriteCRLF(""); + } + + WriteHTMLHeaders(name); + + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::StartAttachment(const nsACString &name, + const char *contentType, + const char *url, + bool aIsExternalAttachment) +{ + nsresult rv = NS_OK; + nsCOMPtr headerSink; + rv = GetHeaderSink(getter_AddRefs(headerSink)); + + if (NS_SUCCEEDED(rv) && headerSink) + { + nsCString uriString; + + nsCOMPtr msgurl (do_QueryInterface(mURL, &rv)); + if (NS_SUCCEEDED(rv)) + { + // HACK: news urls require us to use the originalSpec. Everyone + // else uses GetURI to get the RDF resource which describes the message. + nsCOMPtr nntpUrl (do_QueryInterface(mURL, &rv)); + if (NS_SUCCEEDED(rv) && nntpUrl) + rv = msgurl->GetOriginalSpec(getter_Copies(uriString)); + else + rv = msgurl->GetUri(getter_Copies(uriString)); + } + + // we need to convert the attachment name from UTF-8 to unicode before + // we emit it. The attachment name has already been rfc2047 processed + // upstream of us. (Namely, mime_decode_filename has been called, deferring + // to nsIMimeHeaderParam.decodeParameter.) + nsString unicodeHeaderValue; + CopyUTF8toUTF16(name, unicodeHeaderValue); + + headerSink->HandleAttachment(contentType, url /* was escapedUrl */, + unicodeHeaderValue.get(), uriString.get(), + aIsExternalAttachment); + + mSkipAttachment = false; + } + else if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) + { + // then we need to deal with the attachments in the body by inserting + // them into a table.. + rv = StartAttachmentInBody(name, contentType, url); + } + else + { + // If we don't need or cannot broadcast attachment info, just ignore it + mSkipAttachment = true; + rv = NS_OK; + } + + return rv; +} + +// Attachment handling routines +// Ok, we are changing the way we handle these now...It used to be that we output +// HTML to make a clickable link, etc... but now, this should just be informational +// and only show up during printing +// XXX should they also show up during quoting? +nsresult +nsMimeHtmlDisplayEmitter::StartAttachmentInBody(const nsACString &name, + const char *contentType, + const char *url) +{ + mSkipAttachment = false; + bool p7mExternal = false; + + nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) + prefs->GetBoolPref("mailnews.p7m_external", &p7mExternal); + + if ( (contentType) && + ((!p7mExternal && !strcmp(contentType, APPLICATION_XPKCS7_MIME)) || + (!p7mExternal && !strcmp(contentType, APPLICATION_PKCS7_MIME)) || + (!strcmp(contentType, APPLICATION_XPKCS7_SIGNATURE)) || + (!strcmp(contentType, APPLICATION_PKCS7_SIGNATURE)) || + (!strcmp(contentType, TEXT_VCARD))) + ) + { + mSkipAttachment = true; + return NS_OK; + } + + if (mFirst) + { + UtilityWrite("
"); + if (!name.IsEmpty()) + { + nsresult rv; + + nsCOMPtr bundleSvc = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED); + + nsCOMPtr bundle; + rv = bundleSvc->CreateBundle("chrome://messenger/locale/messenger.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString attachmentsHeader; + bundle->GetStringFromName(u"attachmentsPrintHeader", + getter_Copies(attachmentsHeader)); + + UtilityWrite(""); + nsCString escapedName; + escapedName.Adopt(MsgEscapeHTML(NS_ConvertUTF16toUTF8(attachmentsHeader).get())); + UtilityWrite(escapedName.get()); + UtilityWrite(""); + } + UtilityWrite("
"); + UtilityWrite("
"); + UtilityWrite(""); + } + + UtilityWrite(""); + + UtilityWrite(""); + + mFirst = false; + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::AddAttachmentField(const char *field, const char *value) +{ + if (mSkipAttachment) + return NS_OK; + + // Don't let bad things happen + if ( !value || !*value ) + return NS_OK; + + // Don't output this ugly header... + if (!strcmp(field, HEADER_X_MOZILLA_PART_URL)) + return NS_OK; + + nsCOMPtr headerSink; + nsresult rv = GetHeaderSink(getter_AddRefs(headerSink)); + if (NS_SUCCEEDED(rv) && headerSink) + { + headerSink->AddAttachmentField(field, value); + } + else + { + // Currently, we only care about the part size. + if (strcmp(field, HEADER_X_MOZILLA_PART_SIZE)) + return NS_OK; + + uint64_t size = atoi(value); + nsAutoString sizeString; + rv = FormatFileSize(size, false, sizeString); + UtilityWrite(""); + } + + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::EndAttachment() +{ + if (mSkipAttachment) + return NS_OK; + + mSkipAttachment = false; // reset it for next attachment round + + if (BroadCastHeadersAndAttachments()) + return NS_OK; + + if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) + UtilityWrite(""); + + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::EndAllAttachments() +{ + nsresult rv = NS_OK; + nsCOMPtr headerSink; + rv = GetHeaderSink(getter_AddRefs(headerSink)); + if (headerSink) + headerSink->OnEndAllAttachments(); + + if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput) + { + UtilityWrite("
"); + UtilityWrite(name); + UtilityWrite(""); + UtilityWrite(NS_ConvertUTF16toUTF8(sizeString).get()); + UtilityWrite("
"); + UtilityWrite("
"); + } + + return rv; +} + +nsresult +nsMimeHtmlDisplayEmitter::WriteBody(const nsACString &buf, + uint32_t *amountWritten) +{ + Write(buf, amountWritten); + return NS_OK; +} + +nsresult +nsMimeHtmlDisplayEmitter::EndBody() +{ + if (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer) + { + UtilityWriteCRLF(""); + UtilityWriteCRLF(""); + } + nsCOMPtr headerSink; + nsresult rv = GetHeaderSink(getter_AddRefs(headerSink)); + nsCOMPtr mailnewsUrl (do_QueryInterface(mURL, &rv)); + if (headerSink) + headerSink->OnEndMsgHeaders(mailnewsUrl); + + return NS_OK; +} + + diff --git a/mailnews/mime/emitters/nsMimeHtmlEmitter.h b/mailnews/mime/emitters/nsMimeHtmlEmitter.h new file mode 100644 index 000000000..886687763 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeHtmlEmitter.h @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsMimeHtmlEmitter_h_ +#define _nsMimeHtmlEmitter_h_ + +#include "mozilla/Attributes.h" +#include "prio.h" +#include "nsMimeBaseEmitter.h" +#include "nsMimeRebuffer.h" +#include "nsIStreamListener.h" +#include "nsIOutputStream.h" +#include "nsIInputStream.h" +#include "nsIURI.h" +#include "nsIChannel.h" +#include "nsIMimeMiscStatus.h" +#include "nsIMimeConverter.h" + +class nsMimeHtmlDisplayEmitter : public nsMimeBaseEmitter { +public: + nsMimeHtmlDisplayEmitter (); + nsresult Init(); + + virtual ~nsMimeHtmlDisplayEmitter (void); + + // Header handling routines. + NS_IMETHOD EndHeader(const nsACString &name) override; + + // Attachment handling routines + NS_IMETHOD StartAttachment(const nsACString &name, + const char *contentType, const char *url, + bool aIsExternalAttachment) override; + NS_IMETHOD AddAttachmentField(const char *field, const char *value) override; + NS_IMETHOD EndAttachment() override; + NS_IMETHOD EndAllAttachments() override; + + // Body handling routines + NS_IMETHOD WriteBody(const nsACString &buf, uint32_t *amountWritten) override; + NS_IMETHOD EndBody() override; + NS_IMETHOD WriteHTMLHeaders(const nsACString &name) override; + + virtual nsresult WriteHeaderFieldHTMLPrefix(const nsACString &name + ) override; + virtual nsresult WriteHeaderFieldHTML(const char *field, + const char *value) override; + virtual nsresult WriteHeaderFieldHTMLPostfix() override; + +protected: + bool mFirst; // Attachment flag... + bool mSkipAttachment; // attachments we shouldn't show... + + nsCOMPtr mHeaderSink; + + nsresult GetHeaderSink(nsIMsgHeaderSink ** aHeaderSink); + bool BroadCastHeadersAndAttachments(); + nsresult StartAttachmentInBody(const nsACString &name, + const char *contentType, const char *url); + + nsresult BroadcastHeaders(nsIMsgHeaderSink * aHeaderSink, int32_t aHeaderMode, bool aFromNewsgroup); +}; + + +#endif /* _nsMimeHtmlEmitter_h_ */ diff --git a/mailnews/mime/emitters/nsMimePlainEmitter.cpp b/mailnews/mime/emitters/nsMimePlainEmitter.cpp new file mode 100644 index 000000000..8e6fae742 --- /dev/null +++ b/mailnews/mime/emitters/nsMimePlainEmitter.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "nsMimeRebuffer.h" +#include "nsMimePlainEmitter.h" +#include "plstr.h" +#include "nsMailHeaders.h" +#include "nscore.h" +#include "prmem.h" +#include "nsEmitterUtils.h" +#include "nsCOMPtr.h" +#include "nsUnicharUtils.h" + +/* + * nsMimePlainEmitter definitions.... + */ +nsMimePlainEmitter::nsMimePlainEmitter() +{ +} + + +nsMimePlainEmitter::~nsMimePlainEmitter(void) +{ +} + + +// Header handling routines. +nsresult +nsMimePlainEmitter::StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID, + const char *outCharset) +{ + mDocHeader = rootMailHeader; + return NS_OK; +} + +nsresult +nsMimePlainEmitter::AddHeaderField(const char *field, const char *value) +{ + if ( (!field) || (!value) ) + return NS_OK; + + UtilityWrite(field); + UtilityWrite(":\t"); + UtilityWriteCRLF(value); + return NS_OK; +} + +nsresult +nsMimePlainEmitter::EndHeader(const nsACString &name) +{ + UtilityWriteCRLF(""); + return NS_OK; +} + +NS_IMETHODIMP +nsMimePlainEmitter::WriteBody(const nsACString &buf, uint32_t *amountWritten) +{ + Write(buf, amountWritten); + return NS_OK; +} + diff --git a/mailnews/mime/emitters/nsMimePlainEmitter.h b/mailnews/mime/emitters/nsMimePlainEmitter.h new file mode 100644 index 000000000..94cc0cc47 --- /dev/null +++ b/mailnews/mime/emitters/nsMimePlainEmitter.h @@ -0,0 +1,31 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsMimePlainEmitter_h_ +#define _nsMimePlainEmitter_h_ + +#include "mozilla/Attributes.h" +#include "prio.h" +#include "nsMimeBaseEmitter.h" +#include "nsMimeRebuffer.h" +#include "nsIStreamListener.h" +#include "nsIOutputStream.h" +#include "nsIURI.h" +#include "nsIChannel.h" + +class nsMimePlainEmitter : public nsMimeBaseEmitter { +public: + nsMimePlainEmitter (); + virtual ~nsMimePlainEmitter (void); + + // Header handling routines. + NS_IMETHOD StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID, + const char *outCharset) override; + NS_IMETHOD AddHeaderField(const char *field, const char *value) override; + NS_IMETHOD EndHeader(const nsACString &buf) override; + + NS_IMETHOD WriteBody(const nsACString &buf, uint32_t *amountWritten) override; +}; + +#endif /* _nsMimePlainEmitter_h_ */ diff --git a/mailnews/mime/emitters/nsMimeRawEmitter.cpp b/mailnews/mime/emitters/nsMimeRawEmitter.cpp new file mode 100644 index 000000000..28e5f53ec --- /dev/null +++ b/mailnews/mime/emitters/nsMimeRawEmitter.cpp @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include +#include "nsMimeRebuffer.h" +#include "nsMimeRawEmitter.h" +#include "plstr.h" +#include "nsIMimeEmitter.h" +#include "nsMailHeaders.h" +#include "nscore.h" +#include "prmem.h" +#include "nsEmitterUtils.h" + +/* + * nsMimeRawEmitter definitions.... + */ +nsMimeRawEmitter::nsMimeRawEmitter() +{ +} + + +nsMimeRawEmitter::~nsMimeRawEmitter(void) +{ +} + +NS_IMETHODIMP +nsMimeRawEmitter::WriteBody(const nsACString &buf, uint32_t *amountWritten) +{ + Write(buf, amountWritten); + return NS_OK; +} + diff --git a/mailnews/mime/emitters/nsMimeRawEmitter.h b/mailnews/mime/emitters/nsMimeRawEmitter.h new file mode 100644 index 000000000..07542efb9 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeRawEmitter.h @@ -0,0 +1,29 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsMimeRawEmitter_h_ +#define _nsMimeRawEmitter_h_ + +#include "mozilla/Attributes.h" +#include "prio.h" +#include "nsMimeBaseEmitter.h" +#include "nsMimeRebuffer.h" +#include "nsIStreamListener.h" +#include "nsIOutputStream.h" +#include "nsIURI.h" +#include "nsIChannel.h" + +class nsMimeRawEmitter : public nsMimeBaseEmitter { +public: + nsMimeRawEmitter (); + virtual ~nsMimeRawEmitter (void); + + NS_IMETHOD WriteBody(const nsACString &buf, + uint32_t *amountWritten) override; + +protected: +}; + + +#endif /* _nsMimeRawEmitter_h_ */ diff --git a/mailnews/mime/emitters/nsMimeRebuffer.cpp b/mailnews/mime/emitters/nsMimeRebuffer.cpp new file mode 100644 index 000000000..0e68a586c --- /dev/null +++ b/mailnews/mime/emitters/nsMimeRebuffer.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include +#include "nsMimeRebuffer.h" +#include "prmem.h" + +MimeRebuffer::MimeRebuffer(void) +{ +} + +MimeRebuffer::~MimeRebuffer(void) +{ +} + +uint32_t +MimeRebuffer::GetSize() +{ + return mBuf.Length(); +} + +uint32_t +MimeRebuffer::IncreaseBuffer(const nsACString &addBuf) +{ + mBuf.Append(addBuf); + return mBuf.Length(); +} + +uint32_t +MimeRebuffer::ReduceBuffer(uint32_t numBytes) +{ + if (numBytes == 0) + return mBuf.Length(); + + if (numBytes >= mBuf.Length()) + { + mBuf.Truncate(); + return 0; + } + + mBuf.Cut(0, numBytes); + return mBuf.Length(); +} + +nsACString & +MimeRebuffer::GetBuffer() +{ + return mBuf; +} diff --git a/mailnews/mime/emitters/nsMimeRebuffer.h b/mailnews/mime/emitters/nsMimeRebuffer.h new file mode 100644 index 000000000..568960206 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeRebuffer.h @@ -0,0 +1,29 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _rebuffer_h_ +#define _rebuffer_h_ + +#include +#include "nsStringGlue.h" + +////////////////////////////////////////////////////////////// +// A rebuffering class necessary for stream output buffering +////////////////////////////////////////////////////////////// + +class MimeRebuffer { +public: + MimeRebuffer (void); + virtual ~MimeRebuffer (void); + + uint32_t GetSize(); + uint32_t IncreaseBuffer(const nsACString &addBuf); + uint32_t ReduceBuffer(uint32_t numBytes); + nsACString & GetBuffer(); + +protected: + nsCString mBuf; +}; + +#endif /* _rebuffer_h_ */ diff --git a/mailnews/mime/emitters/nsMimeXmlEmitter.cpp b/mailnews/mime/emitters/nsMimeXmlEmitter.cpp new file mode 100644 index 000000000..f9cd1ece2 --- /dev/null +++ b/mailnews/mime/emitters/nsMimeXmlEmitter.cpp @@ -0,0 +1,184 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "nsMimeRebuffer.h" +#include "nsMimeXmlEmitter.h" +#include "plstr.h" +#include "nsMailHeaders.h" +#include "nscore.h" +#include "prmem.h" +#include "nsEmitterUtils.h" +#include "nsCOMPtr.h" +#include "nsUnicharUtils.h" +#include "nsMsgUtils.h" + +/* + * nsMimeXmlEmitter definitions.... + */ +nsMimeXmlEmitter::nsMimeXmlEmitter() +{ +} + + +nsMimeXmlEmitter::~nsMimeXmlEmitter(void) +{ +} + + +// Note - this is teardown only...you should not write +// anything to the stream since these may be image data +// output streams, etc... +nsresult +nsMimeXmlEmitter::Complete() +{ + char buf[16]; + + // Now write out the total count of attachments for this message + UtilityWrite(""); + sprintf(buf, "%d", mAttachCount); + UtilityWrite(buf); + UtilityWrite(""); + + UtilityWrite(""); + + return nsMimeBaseEmitter::Complete(); + +} + +nsresult +nsMimeXmlEmitter::WriteXMLHeader(const char *msgID) +{ + if ( (!msgID) || (!*msgID) ) + msgID = "none"; + + char *newValue = MsgEscapeHTML(msgID); + if (!newValue) + return NS_ERROR_OUT_OF_MEMORY; + + UtilityWrite(""); + + UtilityWriteCRLF(""); + + UtilityWrite(""); + + mXMLHeaderStarted = true; + PR_FREEIF(newValue); + return NS_OK; +} + +nsresult +nsMimeXmlEmitter::WriteXMLTag(const char *tagName, const char *value) +{ + if ( (!value) || (!*value) ) + return NS_OK; + + char *upCaseTag = NULL; + char *newValue = MsgEscapeHTML(value); + if (!newValue) + return NS_OK; + + nsCString newTagName(tagName); + newTagName.StripWhitespace(); + ToUpperCase(newTagName); + upCaseTag = ToNewCString(newTagName); + + UtilityWrite("
"); + + // Here is where we are going to try to L10N the tagName so we will always + // get a field name next to an emitted header value. Note: Default will always + // be the name of the header itself. + // + UtilityWrite(""); + char *l10nTagName = LocalizeHeaderName(upCaseTag, tagName); + if ( (!l10nTagName) || (!*l10nTagName) ) + UtilityWrite(tagName); + else + { + UtilityWrite(l10nTagName); + } + PR_FREEIF(l10nTagName); + + UtilityWrite(": "); + UtilityWrite(""); + + // Now write out the actual value itself and move on! + // + UtilityWrite(newValue); + UtilityWrite("
"); + + NS_Free(upCaseTag); + PR_FREEIF(newValue); + + return NS_OK; +} + +// Header handling routines. +nsresult +nsMimeXmlEmitter::StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID, + const char *outCharset) +{ + mDocHeader = rootMailHeader; + WriteXMLHeader(msgID); + UtilityWrite(""); + + return NS_OK; +} + +nsresult +nsMimeXmlEmitter::AddHeaderField(const char *field, const char *value) +{ + if ( (!field) || (!value) ) + return NS_OK; + + WriteXMLTag(field, value); + return NS_OK; +} + +nsresult +nsMimeXmlEmitter::EndHeader(const nsACString &name) +{ + UtilityWrite(""); + return NS_OK; +} + + +// Attachment handling routines +nsresult +nsMimeXmlEmitter::StartAttachment(const nsACString &name, + const char *contentType, + const char *url, + bool aIsExternalAttachment) +{ + char buf[128]; + + ++mAttachCount; + + sprintf(buf, "", mAttachCount); + UtilityWrite(buf); + + AddAttachmentField(HEADER_PARM_FILENAME, PromiseFlatCString(name).get()); + return NS_OK; +} + +nsresult +nsMimeXmlEmitter::AddAttachmentField(const char *field, const char *value) +{ + WriteXMLTag(field, value); + return NS_OK; +} + +nsresult +nsMimeXmlEmitter::EndAttachment() +{ + UtilityWrite(""); + return NS_OK; +} + + diff --git a/mailnews/mime/emitters/nsMimeXmlEmitter.h b/mailnews/mime/emitters/nsMimeXmlEmitter.h new file mode 100644 index 000000000..f9e83948d --- /dev/null +++ b/mailnews/mime/emitters/nsMimeXmlEmitter.h @@ -0,0 +1,47 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _nsMimeXmlEmitter_h_ +#define _nsMimeXmlEmitter_h_ + +#include "mozilla/Attributes.h" +#include "prio.h" +#include "nsMimeBaseEmitter.h" +#include "nsMimeRebuffer.h" +#include "nsIStreamListener.h" +#include "nsIOutputStream.h" +#include "nsIURI.h" +#include "nsIChannel.h" + +class nsMimeXmlEmitter : public nsMimeBaseEmitter { +public: + nsMimeXmlEmitter (); + virtual ~nsMimeXmlEmitter (void); + + NS_IMETHOD Complete() override; + + // Header handling routines. + NS_IMETHOD StartHeader(bool rootMailHeader, bool headerOnly, const char *msgID, + const char *outCharset) override; + NS_IMETHOD AddHeaderField(const char *field, const char *value) override; + NS_IMETHOD EndHeader(const nsACString &buf) override; + + // Attachment handling routines + NS_IMETHOD StartAttachment(const nsACString &name, + const char *contentType, const char *url, + bool aIsExternalAttachment) override; + NS_IMETHOD AddAttachmentField(const char *field, const char *value) override; + NS_IMETHOD EndAttachment() override; + + NS_IMETHOD WriteXMLHeader(const char *msgID); + NS_IMETHOD WriteXMLTag(const char *tagName, const char *value); + +protected: + + // For header determination... + bool mXMLHeaderStarted; + int32_t mAttachCount; +}; + +#endif /* _nsMimeXmlEmitter_h_ */ diff --git a/mailnews/mime/jsmime/LICENSE b/mailnews/mime/jsmime/LICENSE new file mode 100644 index 000000000..9ddc547d9 --- /dev/null +++ b/mailnews/mime/jsmime/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013 Joshua Cranmer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/mailnews/mime/jsmime/README.md b/mailnews/mime/jsmime/README.md new file mode 100644 index 000000000..a418516ea --- /dev/null +++ b/mailnews/mime/jsmime/README.md @@ -0,0 +1,59 @@ +Code Layout +=========== + +JSMime is a MIME parsing and composition library that is written completely in +JavaScript using ES6 functionality and WebAPIs (where such APIs exist). There +are a few features for which a standardized WebAPI does not exist; for these, +external JavaScript libraries are used. + +The MIME parser consists of three logical phases of translation: + +1. Build the MIME (and pseudo-MIME) tree. +2. Convert the MIME tree into a list of body parts and attachments. +3. Use the result to drive a displayed version of the message. + +The first stage is located in `mimeparser.js`. The latter stages have yet to be +implemented. + +Dependencies +============ + +This code depends on the following ES6 features and Web APIs: +* ES6 generators +* ES6 Map and Set +* ES6 @@iterator support (especially for Map and Set) +* ES6 let +* ES6 let-destructuring +* ES6 const +* Typed arrays (predominantly Uint8Array) +* btoa, atob (found on global Windows or WorkerScopes) +* TextDecoder + +Versions and API stability +========================== + +As APIs require some use and experimentation to get a feel for what works best, +the APIs may change between successive version updates as uses indicate +substandard or error-prone APIs. Therefore, there will be no guarantee of API +stability until version 1.0 is released. + +This code is being initially developed as an effort to replace the MIME library +within Thunderbird. New versions will be released as needed to bring new support +into the Thunderbird codebase; version 1.0 will correspond to the version where +feature-parity with the old MIME library is reached. The set of features which +will be added before 1.0 are the following: +* S/MIME encryption and decryption +* PGP encryption and decryption +* IMAP parts-on-demand support +* Support for text/plain to HTML conversion for display +* Support for HTML downgrading and sanitization for display +* Support for all major multipart types +* Ability to convert HTML documents to text/plain and multipart/related +* Support for building outgoing messages +* Support for IDN and EAI +* yEnc and uuencode decoding support +* Support for date and Message-ID/References-like headers + +Other features than these may be added before version 1.0 is released (most +notably TNEF decoding support), but they are not considered necessary to release +a version 1.0. diff --git a/mailnews/mime/jsmime/jsmime.js b/mailnews/mime/jsmime/jsmime.js new file mode 100644 index 000000000..253b5da0f --- /dev/null +++ b/mailnews/mime/jsmime/jsmime.js @@ -0,0 +1,3300 @@ +(function (root, fn) { + if (typeof define === 'function' && define.amd) { + define(fn); + } else if (typeof module !== 'undefined' && module.exports) { + module.exports = fn(); + } else { + root.jsmime = fn(); + } +}(this, function() { + var mods = {}; + function req(id) { + return mods[id.replace(/^\.\//, '')]; + } + + function def(id, fn) { + mods[id] = fn(req); + } +def('mimeutils', function() { +"use strict"; + +/** + * Decode a quoted-printable buffer into a binary string. + * + * @param buffer {BinaryString} The string to decode. + * @param more {Boolean} This argument is ignored. + * @returns {Array(BinaryString, BinaryString)} The first element of the array + * is the decoded string. The second element is always the empty + * string. + */ +function decode_qp(buffer, more) { + // Unlike base64, quoted-printable isn't stateful across multiple lines, so + // there is no need to buffer input, so we can always ignore more. + let decoded = buffer.replace( + // Replace either = or =CRLF + /=([0-9A-F][0-9A-F]|[ \t]*(\r\n|[\r\n]|$))/gi, + function replace_chars(match, param) { + // If trailing text matches [ \t]*CRLF, drop everything, since it's a + // soft line break. + if (param.trim().length == 0) + return ''; + return String.fromCharCode(parseInt(param, 16)); + }); + return [decoded, '']; +} + +/** + * Decode a base64 buffer into a binary string. Unlike window.atob, the buffer + * may contain non-base64 characters that will be ignored. + * + * @param buffer {BinaryString} The string to decode. + * @param more {Boolean} If true, we expect that this function could be + * called again and should retain extra data. If + * false, we should flush all pending output. + * @returns {Array(BinaryString, BinaryString)} The first element of the array + * is the decoded string. The second element contains the data that + * could not be decoded and needs to be retained for the next call. + */ +function decode_base64(buffer, more) { + // Drop all non-base64 characters + let sanitize = buffer.replace(/[^A-Za-z0-9+\/=]/g,''); + // Remove harmful `=' chars in the middle. + sanitize = sanitize.replace(/=+([A-Za-z0-9+\/])/g, '$1'); + // We need to encode in groups of 4 chars. If we don't have enough, leave the + // excess for later. If there aren't any more, drop enough to make it 4. + let excess = sanitize.length % 4; + if (excess != 0 && more) + buffer = sanitize.slice(-excess); + else + buffer = ''; + sanitize = sanitize.substring(0, sanitize.length - excess); + // Use the atob function we (ought to) have in global scope. + return [atob(sanitize), buffer]; +} + +/** + * Converts a binary string into a Uint8Array buffer. + * + * @param buffer {BinaryString} The string to convert. + * @returns {Uint8Array} The converted data. + */ +function stringToTypedArray(buffer) { + var typedarray = new Uint8Array(buffer.length); + for (var i = 0; i < buffer.length; i++) + typedarray[i] = buffer.charCodeAt(i); + return typedarray; +} + +/** + * Converts a Uint8Array buffer to a binary string. + * + * @param buffer {BinaryString} The string to convert. + * @returns {Uint8Array} The converted data. + */ +function typedArrayToString(buffer) { + var string = ''; + for (var i = 0; i < buffer.length; i+= 100) + string += String.fromCharCode.apply(undefined, buffer.subarray(i, i + 100)); + return string; +} + +/** A list of month names for Date parsing. */ +var kMonthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec"]; + +return { + decode_base64: decode_base64, + decode_qp: decode_qp, + kMonthNames: kMonthNames, + stringToTypedArray: stringToTypedArray, + typedArrayToString: typedArrayToString, +}; +}); +/** + * This file implements knowledge of how to encode or decode structured headers + * for several key headers. It is not meant to be used externally to jsmime. + */ + +def('structuredHeaders', function (require) { +"use strict"; + +var structuredDecoders = new Map(); +var structuredEncoders = new Map(); +var preferredSpellings = new Map(); + +function addHeader(name, decoder, encoder) { + var lowerName = name.toLowerCase(); + structuredDecoders.set(lowerName, decoder); + structuredEncoders.set(lowerName, encoder); + preferredSpellings.set(lowerName, name); +} + + +// Addressing headers: We assume that they can be specified in 1* form (this is +// false for From, but it's close enough to the truth that it shouldn't matter). +// There is no need to specialize the results for the header, so just pun it +// back to parseAddressingHeader. +function parseAddress(value) { + let results = []; + let headerparser = this; + return value.reduce(function (results, header) { + return results.concat(headerparser.parseAddressingHeader(header, true)); + }, []); +} +function writeAddress(value) { + // Make sure the input is an array (accept a single entry) + if (!Array.isArray(value)) + value = [value]; + this.addAddresses(value); +} + +// Addressing headers from RFC 5322: +addHeader("Bcc", parseAddress, writeAddress); +addHeader("Cc", parseAddress, writeAddress); +addHeader("From", parseAddress, writeAddress); +addHeader("Reply-To", parseAddress, writeAddress); +addHeader("Resent-Bcc", parseAddress, writeAddress); +addHeader("Resent-Cc", parseAddress, writeAddress); +addHeader("Resent-From", parseAddress, writeAddress); +addHeader("Resent-Reply-To", parseAddress, writeAddress); +addHeader("Resent-Sender", parseAddress, writeAddress); +addHeader("Resent-To", parseAddress, writeAddress); +addHeader("Sender", parseAddress, writeAddress); +addHeader("To", parseAddress, writeAddress); +// From RFC 5536: +addHeader("Approved", parseAddress, writeAddress); +// From RFC 3798: +addHeader("Disposition-Notification-To", parseAddress, writeAddress); +// Non-standard headers: +addHeader("Delivered-To", parseAddress, writeAddress); +addHeader("Return-Receipt-To", parseAddress, writeAddress); + +// http://cr.yp.to/proto/replyto.html +addHeader("Mail-Reply-To", parseAddress, writeAddress); +addHeader("Mail-Followup-To", parseAddress, writeAddress); + +// Parameter-based headers. Note that all parameters are slightly different, so +// we use slightly different variants here. +function parseParameterHeader(value, do2231, do2047) { + // Only use the first header for parameters; ignore subsequent redefinitions. + return this.parseParameterHeader(value[0], do2231, do2047); +} + +// RFC 2045 +function parseContentType(value) { + let params = parseParameterHeader.call(this, value, false, false); + let origtype = params.preSemi; + let parts = origtype.split('/'); + if (parts.length != 2) { + // Malformed. Return to text/plain. Evil, ain't it? + params = new Map(); + parts = ["text", "plain"]; + } + let mediatype = parts[0].toLowerCase(); + let subtype = parts[1].toLowerCase(); + let type = mediatype + '/' + subtype; + let structure = new Map(); + structure.mediatype = mediatype; + structure.subtype = subtype; + structure.type = type; + params.forEach(function (value, name) { + structure.set(name.toLowerCase(), value); + }); + return structure; +} +structuredDecoders.set("Content-Type", parseContentType); + +// Unstructured headers (just decode RFC 2047 for the first header value) +function parseUnstructured(values) { + return this.decodeRFC2047Words(values[0]); +} +function writeUnstructured(value) { + this.addUnstructured(value); +} + +// Message-ID headers. +function parseMessageID(values) { + // TODO: Proper parsing support for these headers is currently unsupported). + return this.decodeRFC2047Words(values[0]); +} +function writeMessageID(value) { + // TODO: Proper parsing support for these headers is currently unsupported). + this.addUnstructured(value); +} + +// RFC 5322 +addHeader("Comments", parseUnstructured, writeUnstructured); +addHeader("Keywords", parseUnstructured, writeUnstructured); +addHeader("Subject", parseUnstructured, writeUnstructured); + +// RFC 2045 +addHeader("MIME-Version", parseUnstructured, writeUnstructured); +addHeader("Content-Description", parseUnstructured, writeUnstructured); + +// RFC 7231 +addHeader("User-Agent", parseUnstructured, writeUnstructured); + +// Date headers +function parseDate(values) { return this.parseDateHeader(values[0]); } +function writeDate(value) { this.addDate(value); } + +// RFC 5322 +addHeader("Date", parseDate, writeDate); +addHeader("Resent-Date", parseDate, writeDate); +// RFC 5536 +addHeader("Expires", parseDate, writeDate); +addHeader("Injection-Date", parseDate, writeDate); +addHeader("NNTP-Posting-Date", parseDate, writeDate); + +// RFC 5322 +addHeader("Message-ID", parseMessageID, writeMessageID); +addHeader("Resent-Message-ID", parseMessageID, writeMessageID); + +// Miscellaneous headers (those that don't fall under the above schemes): + +// RFC 2047 +structuredDecoders.set("Content-Transfer-Encoding", function (values) { + return values[0].toLowerCase(); +}); +structuredEncoders.set("Content-Transfer-Encoding", writeUnstructured); + +// Some clients like outlook.com send non-compliant References headers that +// separate values using commas. Also, some clients don't separate References +// with spaces, since these are optional according to RFC2822. So here we +// preprocess these headers (see bug 1154521 and bug 1197686). +function preprocessMessageIDs(values) { + let msgId = /<[^>]*>/g; + let match, ids = []; + while ((match = msgId.exec(values)) !== null) { + ids.push(match[0]); + } + return ids.join(' '); +} +structuredDecoders.set("References", preprocessMessageIDs); +structuredDecoders.set("In-Reply-To", preprocessMessageIDs); + +return Object.freeze({ + decoders: structuredDecoders, + encoders: structuredEncoders, + spellings: preferredSpellings, +}); + +}); +def('headerparser', function(require) { +/** + * This file implements the structured decoding of message header fields. It is + * part of the same system as found in mimemimeutils.js, and occasionally makes + * references to globals defined in that file or other dependencies thereof. See + * documentation in that file for more information about external dependencies. + */ + +"use strict"; +var mimeutils = require('./mimeutils'); + +/** + * This is the API that we ultimately return. + * + * We define it as a global here, because we need to pass it as a |this| + * argument to a few functions. + */ +var headerparser = {}; + +/** + * Tokenizes a message header into a stream of tokens as a generator. + * + * The low-level tokens are meant to be loosely correspond to the tokens as + * defined in RFC 5322. For reasons of saner error handling, however, the two + * definitions are not exactly equivalent. The tokens we emit are the following: + * 1. Special delimiters: Any char in the delimiters string is emitted as a + * string by itself. Parsing parameter headers, for example, would use ";=" + * for the delimiter string. + * 2. Quoted-strings (if opt.qstring is true): A string which is surrounded by + * double quotes. Escapes in the string are omitted when returning. + * 3. Domain Literals (if opt.dliteral is true): A string which matches the + * dliteral construct in RFC 5322. Escapes here are NOT omitted. + * 4. Comments (if opt.comments is true): Comments are handled specially. In + * practice, decoding the comments in To headers appears to be necessary, so + * comments are not stripped in the output value. Instead, they are emitted + * as if they are a special delimiter. However, all delimiters found within a + * comment are returned as if they were a quoted string, so that consumers + * ignore delimiters within comments. If ignoring comment text completely is + * desired, upon seeing a "(" token, consumers should ignore all tokens until + * a matching ")" is found (note that comments can be nested). + * 5. RFC 2047 encoded-words (if opts.rfc2047 is true): These are strings which + * are the decoded contents of RFC 2047's =?UTF-8?Q?blah?=-style words. + * 6. Atoms: Atoms are defined not in the RFC 5322 sense, but rather as the + * longest sequence of characters that is neither whitespace nor any of the + * special characters above. + * + * The intended interpretation of the stream of output tokens is that they are + * the portions of text which can be safely wrapped in whitespace with no ill + * effect. The output tokens are either strings (which represent individual + * delimiter tokens) or instances of a class that has a customized .toString() + * for output (for quoted strings, atoms, domain literals, and encoded-words). + * Checking for a delimiter MUST use the strictly equals operator (===). For + * example, the proper way to call this method is as follows: + * + * for (let token of getHeaderTokens(rest, ";=", opts)) { + * if (token === ';') { + * // This represents a literal ';' in the string + * } else if (token === '=') { + * // This represents a literal '=' in the string + * } else { + * // If a ";" qstring was parsed, we fall through to here! + * token = token.toString(); + * } + * } + * + * This method does not properly tokenize 5322 in all corner cases; however, + * this is equivalent in those corner cases to an older header parsing + * algorithm, so the algorithm should be correct for all real-world cases. The + * corner cases are as follows: + * 1. Quoted-strings and domain literals are parsed even if they are within a + * comment block (we effectively treat ctext as containing qstring). + * 2. WSP need not be between a qstring and an atom (a"b" produces two tokens, + * a and b). This is an error case, though. + * 3. Legacy comments as display names: We recognize address fields with + * comments, and (a) either drop them if inside addr-spec or (b) preserve + * them as part of the display-name if not. If the display-name is empty + * while the last comment is not, we assume it's the legacy form above and + * take the comment content as the display-name. + * + * @param {String} value The header value, post charset conversion but + * before RFC 2047 decoding, to be parsed. + * @param {String} delimiters A set of delimiters to include as individual + * tokens. + * @param {Object} opts A set of options selecting what to parse. + * @param {Boolean} [opts.qstring] If true, recognize quoted strings. + * @param {Boolean} [opts.dliteral] If true, recognize domain literals. + * @param {Boolean} [opts.comments] If true, recognize comments. + * @param {Boolean} [opts.rfc2047] If true, parse and decode RFC 2047 + * encoded-words. + * @returns {(Token|String)[]} An array of Token objects (which have a toString + * method returning their value) or String objects + * (representing delimiters). + */ +function getHeaderTokens(value, delimiters, opts) { + // The array of parsed tokens. This method used to be a generator, but it + // appears that generators are poorly optimized in current engines, so it was + // converted to not be one. + let tokenList = []; + + /// Represents a non-delimiter token + function Token(token) { + // Unescape all quoted pairs. Any trailing \ is deleted. + this.token = token.replace(/\\(.?)/g, "$1"); + } + Token.prototype.toString = function () { return this.token; }; + + // The start of the current token (e.g., atoms, strings) + let tokenStart = undefined; + // The set of whitespace characters, as defined by RFC 5322 + let wsp = " \t\r\n"; + // If we are a domain literal ([]) or a quoted string ("), this is set to the + // character to look for at the end. + let endQuote = undefined; + // The current depth of comments, since they can be nested. A value 0 means we + // are not in a comment. + let commentDepth = 0; + + // Iterate over every character one character at a time. + let length = value.length; + for (let i = 0; i < length; i++) { + let ch = value[i]; + // If we see a \, no matter what context we are in, ignore the next + // character. + if (ch == '\\') { + i++; + continue; + } + + // If we are in a qstring or a dliteral, process the character only if it is + // what we are looking for to end the quote. + if (endQuote !== undefined) { + if (ch == endQuote && ch == '"') { + // Quoted strings don't include their delimiters. + let text = value.slice(tokenStart + 1, i); + + // If RFC 2047 is enabled, always decode the qstring. + if (opts.rfc2047) + text = decodeRFC2047Words(text); + + tokenList.push(new Token(text)); + endQuote = undefined; + tokenStart = undefined; + } else if (ch == endQuote && ch == ']') { + // Domain literals include their delimiters. + tokenList.push(new Token(value.slice(tokenStart, i + 1))); + endQuote = undefined; + tokenStart = undefined; + } + // Avoid any further processing. + continue; + } + + // If we can match the RFC 2047 encoded-word pattern, we need to decode the + // entire word or set of words. + if (opts.rfc2047 && ch == '=' && i + 1 < value.length && value[i + 1] == '?') { + // RFC 2047 tokens separated only by whitespace are conceptually part of + // the same output token, so we need to decode them all at once. + let encodedWordsRE = /([ \t\r\n]*=\?[^?]*\?[BbQq]\?[^?]*\?=)+/; + let result = encodedWordsRE.exec(value.slice(i)); + if (result !== null) { + // If we were in the middle of a prior token (i.e., something like + // foobar=?UTF-8?Q?blah?=), yield the previous segment as a token. + if (tokenStart !== undefined) { + tokenList.push(new Token(value.slice(tokenStart, i))); + tokenStart = undefined; + } + + // Find out how much we need to decode... + let encWordsLen = result[0].length; + let string = decodeRFC2047Words(value.slice(i, i + encWordsLen), + "UTF-8"); + // Don't make a new Token variable, since we do not want to unescape the + // decoded string. + tokenList.push({ toString: function() { return string; }}); + + // Skip everything we decoded. The -1 is because we don't want to + // include the starting character. + i += encWordsLen - 1; + continue; + } + + // If we are here, then we failed to match the simple 2047 encoded-word + // regular expression, despite the fact that it matched the =? at the + // beginning. Fall through and treat the text as if we aren't trying to + // decode RFC 2047. + } + + // If we reach this point, we're not inside of quoted strings, domain + // literals, or RFC 2047 encoded-words. This means that the characters we + // parse are potential delimiters (unless we're in comments, where + // everything starts to go really wonky). Several things could happen, + // depending on the kind of character we read and whether or not we were in + // the middle of a token. The three values here tell us what we could need + // to do at this point: + // tokenIsEnding: The current character is not able to be accumulated to an + // atom, so we need to flush the atom if there is one. + // tokenIsStarting: The current character could begin an atom (or + // anything that requires us to mark the starting point), so we need to save + // the location. + // isSpecial: The current character is a delimiter that needs to be output. + let tokenIsEnding = false, tokenIsStarting = false, isSpecial = false; + if (wsp.includes(ch)) { + // Whitespace ends current tokens, doesn't emit anything. + tokenIsEnding = true; + } else if (commentDepth == 0 && delimiters.includes(ch)) { + // Delimiters end the current token, and need to be output. They do not + // apply within comments. + tokenIsEnding = true; + isSpecial = true; + } else if (opts.qstring && ch == '"') { + // Quoted strings end the last token and start a new one. + tokenIsEnding = true; + tokenIsStarting = true; + endQuote = ch; + } else if (opts.dliteral && ch == '[') { + // Domain literals end the last token and start a new one. + tokenIsEnding = true; + tokenIsStarting = true; + endQuote = ']'; + } else if (opts.comments && ch == '(') { + // Comments are nested (oh joy). We only really care for the outer + // delimiter, though, which also ends the prior token and needs to be + // output if the consumer requests it. + commentDepth++; + if (commentDepth == 1) { + tokenIsEnding = true; + isSpecial = true; + } else { + tokenIsStarting = true; + } + } else if (opts.comments && ch == ')') { + // Comments are nested (oh joy). We only really care for the outer + // delimiter, though, which also ends the prior token and needs to be + // output if the consumer requests it. + if (commentDepth > 0) + commentDepth--; + if (commentDepth == 0) { + tokenIsEnding = true; + isSpecial = true; + } else { + tokenIsStarting = true; + } + } else { + // Not a delimiter, whitespace, comment, domain literal, or quoted string. + // Must be part of an atom then! + tokenIsStarting = true; + } + + // If our analysis concluded that we closed an open token, and there is an + // open token, then yield that token. + if (tokenIsEnding && tokenStart !== undefined) { + tokenList.push(new Token(value.slice(tokenStart, i))); + tokenStart = undefined; + } + // If we need to output a delimiter, do so. + if (isSpecial) + tokenList.push(ch); + // If our analysis concluded that we could open a token, and no token is + // opened yet, then start the token. + if (tokenIsStarting && tokenStart === undefined) { + tokenStart = i; + } + } + + // That concludes the loop! If there is a currently open token, close that + // token now. + if (tokenStart !== undefined) { + // Error case: a partially-open quoted string is assumed to have a trailing + // " character. + if (endQuote == '"') + tokenList.push(new Token(value.slice(tokenStart + 1))); + else + tokenList.push(new Token(value.slice(tokenStart))); + } + + return tokenList; +} + +/** + * Convert a header value into UTF-16 strings by attempting to decode as UTF-8 + * or another legacy charset. If the header is valid UTF-8, it will be decoded + * as UTF-8; if it is not, the fallbackCharset will be attempted instead. + * + * @param {String} headerValue The header (as a binary string) to attempt + * to convert to UTF-16. + * @param {String} [fallbackCharset] The optional charset to try if UTF-8 + * doesn't work. + * @returns {String} The UTF-16 representation of the string above. + */ +function convert8BitHeader(headerValue, fallbackCharset) { + // Only attempt to convert the headerValue if it contains non-ASCII + // characters. + if (/[\x80-\xff]/.exec(headerValue)) { + // First convert the value to a typed-array for TextDecoder. + let typedarray = mimeutils.stringToTypedArray(headerValue); + + // Don't try UTF-8 as fallback (redundant), and don't try UTF-16 or UTF-32 + // either, since they radically change header interpretation. + // If we have a fallback charset, we want to know if decoding will fail; + // otherwise, we want to replace with substitution chars. + let hasFallback = fallbackCharset && + !fallbackCharset.toLowerCase().startsWith("utf"); + let utf8Decoder = new TextDecoder("utf-8", {fatal: hasFallback}); + try { + headerValue = utf8Decoder.decode(typedarray); + } catch (e) { + // Failed, try the fallback + let decoder = new TextDecoder(fallbackCharset, {fatal: false}); + headerValue = decoder.decode(typedarray); + } + } + return headerValue; +} + +/** + * Decodes all RFC 2047 encoded-words in the input string. The string does not + * necessarily have to contain any such words. This is useful, for example, for + * parsing unstructured headers. + * + * @param {String} headerValue The header which may contain RFC 2047 encoded- + * words. + * @returns {String} A full UTF-16 string with all encoded words expanded. + */ +function decodeRFC2047Words(headerValue) { + // Unfortunately, many implementations of RFC 2047 encoding are actually wrong + // in that they split over-long encoded words without regard for whether or + // not the split point is in the middle of a multibyte character. Therefore, + // we need to be able to handle these situations gracefully. This is done by + // using the decoder in streaming mode so long as the next token is another + // 2047 token with the same charset. + let lastCharset = '', currentDecoder = undefined; + + /** + * Decode a single RFC 2047 token. This function is inline so that we can + * easily close over the lastCharset/currentDecoder variables, needed for + * handling bad RFC 2047 productions properly. + */ + function decode2047Token(token, isLastToken) { + let tokenParts = token.split("?"); + + // If it's obviously not a valid token, return false immediately. + if (tokenParts.length != 5 || tokenParts[4] != '=') + return false; + + // The charset parameter is defined in RFC 2231 to be charset or + // charset*language. We only care about the charset here, so ignore any + // language parameter that gets passed in. + let charset = tokenParts[1].split('*', 1)[0]; + let encoding = tokenParts[2], text = tokenParts[3]; + + let buffer; + if (encoding == 'B' || encoding == 'b') { + // Decode base64. If there's any non-base64 data, treat the string as + // an illegal token. + if (/[^A-Za-z0-9+\/=]/.exec(text)) + return false; + + // Decode the string + buffer = mimeutils.decode_base64(text, false)[0]; + } else if (encoding == 'Q' || encoding == 'q') { + // Q encoding here looks a lot like quoted-printable text. The differences + // between quoted-printable and this are that quoted-printable allows you + // to quote newlines (this doesn't), while this replaces spaces with _. + // We can reuse the decode_qp code here, since newlines are already + // stripped from the header. There is one edge case that could trigger a + // false positive, namely when you have a single = or an = followed by + // whitespace at the end of the string. Such an input string is already + // malformed to begin with, so stripping the = and following input in that + // case should not be an important loss. + buffer = mimeutils.decode_qp(text.replace(/_/g, ' '), false)[0]; + } else { + return false; + } + + // Make the buffer be a typed array for what follows + let stringBuffer = buffer; + buffer = mimeutils.stringToTypedArray(buffer); + + // If we cannot reuse the last decoder, flush out whatever remains. + var output = ''; + if (charset != lastCharset && currentDecoder) { + output += currentDecoder.decode(); + currentDecoder = null; + } + + // Initialize the decoder for this token. + lastCharset = charset; + if (!currentDecoder) { + try { + currentDecoder = new TextDecoder(charset, {fatal: false}); + } catch (e) { + // We don't recognize the charset, so give up. + return false; + } + } + + // Convert this token with the buffer. Note the stream parameter--although + // RFC 2047 tokens aren't supposed to break in the middle of a multibyte + // character, a lot of software messes up and does so because it's hard not + // to (see headeremitter.js for exactly how hard!). + // We must not stream ISO-2022-JP if the buffer switches back to + // the ASCII state, that is, ends in "ESC(B". + // Also, we shouldn't do streaming on the last token. + let doStreaming; + if (isLastToken || + (charset.toUpperCase() == "ISO-2022-JP" && + stringBuffer.endsWith("\x1B(B"))) + doStreaming = {stream: false}; + else + doStreaming = {stream: true}; + return output + currentDecoder.decode(buffer, doStreaming); + } + + // The first step of decoding is to split the string into RFC 2047 and + // non-RFC 2047 tokens. RFC 2047 tokens look like the following: + // =?charset?c?text?=, where c is one of B, b, Q, and q. The split regex does + // some amount of semantic checking, so that malformed RFC 2047 tokens will + // get ignored earlier. + let components = headerValue.split(/(=\?[^?]*\?[BQbq]\?[^?]*\?=)/); + + // Find last RFC 2047 token. + let lastRFC2047Index = -1; + for (let i = 0; i < components.length; i++) { + if (components[i].substring(0, 2) == "=?") + lastRFC2047Index = i; + } + for (let i = 0; i < components.length; i++) { + if (components[i].substring(0, 2) == "=?") { + let decoded = decode2047Token(components[i], i == lastRFC2047Index); + if (decoded !== false) { + // If 2047 decoding succeeded for this bit, rewrite the original value + // with the proper decoding. + components[i] = decoded; + + // We're done processing, so continue to the next link. + continue; + } + } else if (/^[ \t\r\n]*$/.exec(components[i])) { + // Whitespace-only tokens get squashed into nothing, so 2047 tokens will + // be concatenated together. + components[i] = ''; + continue; + } + + // If there was stuff left over from decoding the last 2047 token, flush it + // out. + lastCharset = ''; + if (currentDecoder) { + components[i] = currentDecoder.decode() + components[i]; + currentDecoder = null; + } + } + + // After the for loop, we'll have a set of decoded strings. Concatenate them + // together to make the return value. + return components.join(''); +} + +/////////////////////////////// +// Structured field decoders // +/////////////////////////////// + +/** + * Extract a list of addresses from a header which matches the RFC 5322 + * address-list production, possibly doing RFC 2047 decoding along the way. + * + * The output of this method is an array of elements corresponding to the + * addresses and the groups in the input header. An address is represented by + * an object of the form: + * { + * name: The display name of the address + * email: The address of the object + * } + * while a group is represented by an object of the form: + * { + * name: The display name of the group + * group: An array of address object for members in the group. + * } + * + * @param {String} header The MIME header text to be parsed + * @param {Boolean} doRFC2047 If true, decode RFC 2047 parameters found in the + * header. + * @returns {(Address|Group)[]} An array of the addresses found in the header, + * where each element is of the form mentioned + * above. + */ +function parseAddressingHeader(header, doRFC2047) { + // Default to true + if (doRFC2047 === undefined) + doRFC2047 = true; + + // The final (top-level) results list to append to. + let results = []; + // Temporary results + let addrlist = []; + + // Build up all of the values + let name = '', groupName = '', localPart = '', address = '', comment = ''; + // Indicators of current state + let inAngle = false, inComment = false, needsSpace = false; + let preserveSpace = false; + let commentClosed = false; + + // RFC 5322 §3.4 notes that legacy implementations exist which use a simple + // recipient form where the addr-spec appears without the angle brackets, + // but includes the name of the recipient in parentheses as a comment + // following the addr-spec. While we do not create this format, we still + // want to recognize it, though. + // Furthermore, despite allowing comments in addresses, RFC 5322 §3.4 notes + // that legacy implementations may interpret the comment, and thus it + // recommends not to use them. (Also, they may be illegal as per RFC 5321.) + // While we do not create address fields with comments, we recognize such + // comments during parsing and (a) either drop them if inside addr-spec or + // (b) preserve them as part of the display-name if not. + // If the display-name is empty while the last comment is not, we assume it's + // the legacy form above and take the comment content as the display-name. + // + // When parsing the address field, we at first do not know whether any + // strings belong to the display-name (which may include comments) or to the + // local-part of an addr-spec (where we ignore comments) until we find an + // '@' or an '<' token. Thus, we collect both variants until the fog lifts, + // plus the last comment seen. + let lastComment = ''; + + /** + * Add the parsed mailbox object to the address list. + * If it's in the legacy form above, correct the display-name. + * Also reset any faked flags. + * @param {String} displayName display-name as per RFC 5322 + * @param {String} addrSpec addr-spec as per RFC 5322 + */ + function addToAddrList(displayName, addrSpec) { + // Keep the local-part quoted if it needs to be. + let lp = addrSpec.substring(0, addrSpec.lastIndexOf("@")); + if (/[ !()<>\[\]:;@\\,"]/.exec(lp) !== null) { + addrSpec = '"' + lp.replace(/([\\"])/g, "\\$1") + '"' + + addrSpec.substring(addrSpec.lastIndexOf("@")); + } + + if (displayName === '' && lastComment !== '') { + // Take last comment content as the display-name. + let offset = lastComment[0] === ' ' ? 2 : 1; + displayName = lastComment.substr(offset, lastComment.length - offset - 1); + } + if (displayName !== '' || addrSpec !== '') + addrlist.push({name: displayName, email: addrSpec}); + // Clear pending flags and variables. + name = localPart = address = lastComment = ''; + inAngle = inComment = needsSpace = false; + } + + // Main parsing loop + for (let token of getHeaderTokens(header, ":,;<>@", + {qstring: true, comments: true, dliteral: true, rfc2047: doRFC2047})) { + if (token === ':') { + groupName = name; + name = ''; + localPart = ''; + // If we had prior email address results, commit them to the top-level. + if (addrlist.length > 0) + results = results.concat(addrlist); + addrlist = []; + } else if (token === '<') { + if (inAngle) { + // Interpret the address we were parsing as a name. + if (address.length > 0) { + name = address; + } + localPart = address = ''; + } else { + inAngle = true; + } + } else if (token === '>') { + inAngle = false; + // Forget addr-spec comments. + lastComment = ''; + } else if (token === '(') { + inComment = true; + // The needsSpace flag may not always be set even if it should be, + // e.g. for a comment behind an angle-addr. + // Also, we need to restore the needsSpace flag if we ignore the comment. + preserveSpace = needsSpace; + if (!needsSpace) + needsSpace = name !== '' && name.substr(-1) !== ' '; + comment = needsSpace ? ' (' : '('; + commentClosed = false; + } else if (token === ')') { + inComment = false; + comment += ')'; + lastComment = comment; + // The comment may be part of the name, but not of the local-part. + // Enforce a space behind the comment only when not ignoring it. + if (inAngle) { + needsSpace = preserveSpace; + } else { + name += comment; + needsSpace = true; + } + commentClosed = true; + continue; + } else if (token === '@') { + // An @ means we see an email address. If we're not within <> brackets, + // then we just parsed an email address instead of a display name. Empty + // out the display name for the current production. + if (!inAngle) { + address = localPart; + name = ''; + localPart = ''; + // The remainder of this mailbox is part of an addr-spec. + inAngle = true; + } + address += '@'; + } else if (token === ',') { + // A comma ends the current name. If we have something that's kind of a + // name, add it to the result list. If we don't, then our input looks like + // To: , , -> don't bother adding an empty entry. + addToAddrList(name, address); + } else if (token === ';') { + // Add pending name to the list + addToAddrList(name, address); + + // If no group name was found, treat the ';' as a ','. In any case, we + // need to copy the results of addrlist into either a new group object or + // the main list. + if (groupName === '') { + results = results.concat(addrlist); + } else { + results.push({ + name: groupName, + group: addrlist + }); + } + // ... and reset every other variable. + addrlist = []; + groupName = ''; + } else { + // This is either comment content, a quoted-string, or some span of + // dots and atoms. + + // Ignore the needs space if we're a "close" delimiter token. + let spacedToken = token; + if (needsSpace && token.toString()[0] != '.') + spacedToken = ' ' + spacedToken; + + // Which field do we add this data to? + if (inComment) { + comment += spacedToken; + } else if (inAngle) { + address += spacedToken; + } else { + name += spacedToken; + // Never add a space to the local-part, if we just ignored a comment. + if (commentClosed) { + localPart += token; + commentClosed = false; + } else { + localPart += spacedToken; + } + } + + // We need space for the next token if we aren't some kind of comment or + // . delimiter. + needsSpace = token.toString()[0] != '.'; + // The fall-through case after this resets needsSpace to false, and we + // don't want that! + continue; + } + + // If we just parsed a delimiter, we don't need any space for the next + // token. + needsSpace = false; + } + + // If we're missing the final ';' of a group, assume it was present. Also, add + // in the details of any email/address that we previously saw. + addToAddrList(name, address); + if (groupName !== '') { + results.push({name: groupName, group: addrlist}); + addrlist = []; + } + + // Add the current address list build-up to the list of addresses, and return + // the whole array to the caller. + return results.concat(addrlist); +} + +/** + * Extract parameters from a header which is a series of ;-separated + * attribute=value tokens. + * + * @param {String} headerValue The MIME header value to parse. + * @param {Boolean} doRFC2047 If true, decode RFC 2047 encoded-words. + * @param {Boolean} doRFC2231 If true, decode RFC 2231 encoded parameters. + * @return {Map(String -> String)} A map of parameter names to parameter values. + * The property preSemi is set to the token that + * precedes the first semicolon. + */ +function parseParameterHeader(headerValue, doRFC2047, doRFC2231) { + // The basic syntax of headerValue is token [; token = token-or-qstring]* + // Copying more or less liberally from nsMIMEHeaderParamImpl: + // The first token is the text to the first whitespace or semicolon. + var semi = headerValue.indexOf(";"); + if (semi < 0) { + var start = headerValue; + var rest = ''; + } else { + var start = headerValue.substring(0, semi); + var rest = headerValue.substring(semi); // Include the semicolon + } + // Strip start to be . + start = start.trim().split(/[ \t\r\n]/)[0]; + + // Decode the the parameter tokens. + let opts = {qstring: true, rfc2047: doRFC2047}; + // Name is the name of the parameter, inName is true iff we don't have a name + // yet. + let name = '', inName = true; + // Matches is a list of [name, value] pairs, where we found something that + // looks like name=value in the input string. + let matches = []; + for (let token of getHeaderTokens(rest, ";=", opts)) { + if (token === ';') { + // If we didn't find a name yet (we have ... tokenA; tokenB), push the + // name with an empty token instead. + if (name != '' && inName == false) + matches.push([name, '']); + name = ''; + inName = true; + } else if (token === '=') { + inName = false; + } else if (inName && name == '') { + name = token.toString(); + } else if (!inName && name != '') { + token = token.toString(); + // RFC 2231 doesn't make it clear if %-encoding is supposed to happen + // within a quoted string, but this is very much required in practice. If + // it ends with a '*', then the string is an extended-value, which means + // that its value may be %-encoded. + if (doRFC2231 && name.endsWith('*')) { + token = token.replace(/%([0-9A-Fa-f]{2})/g, + function percent_deencode(match, hexchars) { + return String.fromCharCode(parseInt(hexchars, 16)); + }); + } + matches.push([name, token]); + // Clear the name, so we ignore anything afterwards. + name = ''; + } else if (inName) { + // We have ...; tokenA tokenB ... -> ignore both tokens + name = ''; // Error recovery, ignore this one + } + } + // If we have a leftover ...; tokenA, push the tokenA + if (name != '' && inName == false) + matches.push([name, '']); + + // Now matches holds the parameters, so clean up for RFC 2231. There are three + // cases: param=val, param*=us-ascii'en-US'blah, and param*n= variants. The + // order of preference is to pick the middle, then the last, then the first. + // Note that we already unpacked %-encoded values. + + // simpleValues is just a straight parameter -> value map. + // charsetValues is the parameter -> value map, although values are stored + // before charset decoding happens. + // continuationValues maps parameter -> array of values, with extra properties + // valid (if we decided we couldn't do anything anymore) and hasCharset (which + // records if we need to decode the charset parameter or not). + var simpleValues = new Map(), charsetValues = new Map(), + continuationValues = new Map(); + for (let pair of matches) { + let name = pair[0]; + let value = pair[1]; + // Get first index, not last index, so we match param*0*= like param*0=. + let star = name.indexOf('*'); + if (star == -1) { + // This is the case of param=val. Select the first value here, if there + // are multiple ones. + if (!simpleValues.has(name)) + simpleValues.set(name, value); + } else if (star == name.length - 1) { + // This is the case of param*=us-ascii'en-US'blah. + name = name.substring(0, star); + // Again, select only the first value here. + if (!charsetValues.has(name)) + charsetValues.set(name, value); + } else { + // This is the case of param*0= or param*0*=. + let param = name.substring(0, star); + let entry = continuationValues.get(param); + // Did we previously find this one to be bungled? Then ignore it. + if (continuationValues.has(param) && !entry.valid) + continue; + + // If we haven't seen it yet, set up entry already. Note that entries are + // not straight string values but rather [valid, hasCharset, param0, ... ] + if (!continuationValues.has(param)) { + entry = new Array(); + entry.valid = true; + entry.hasCharset = undefined; + continuationValues.set(param, entry); + } + + // When the string ends in *, we need to charset decoding. + // Note that the star is only meaningful for the *0*= case. + let lastStar = name[name.length - 1] == '*'; + let number = name.substring(star + 1, name.length - (lastStar ? 1 : 0)); + if (number == '0') + entry.hasCharset = lastStar; + + // Is the continuation number illegal? + else if ((number[0] == '0' && number != '0') || + !(/^[0-9]+$/.test(number))) { + entry.valid = false; + continue; + } + // Normalize to an integer + number = parseInt(number, 10); + + // Is this a repeat? If so, bail. + if (entry[number] !== undefined) { + entry.valid = false; + continue; + } + + // Set the value for this continuation index. JS's magic array setter will + // expand the array if necessary. + entry[number] = value; + } + } + + // Build the actual parameter array from the parsed values + var values = new Map(); + // Simple values have lowest priority, so just add everything into the result + // now. + for (let pair of simpleValues) { + values.set(pair[0], pair[1]); + } + + if (doRFC2231) { + // Continuation values come next + for (let pair of continuationValues) { + let name = pair[0]; + let entry = pair[1]; + // If we never saw a param*0= or param*0*= value, then we can't do any + // reasoning about what it looks like, so bail out now. + if (entry.hasCharset === undefined) continue; + + // Use as many entries in the array as are valid--if we are missing an + // entry, stop there. + let valid = true; + for (var i = 0; valid && i < entry.length; i++) + if (entry[i] === undefined) + valid = false; + + // Concatenate as many parameters as are valid. If we need to decode thec + // charset, do so now. + var value = entry.slice(0, i).join(''); + if (entry.hasCharset) { + try { + value = decode2231Value(value); + } catch (e) { + // Bad charset, don't add anything. + continue; + } + } + // Finally, add this to the output array. + values.set(name, value); + } + + // Highest priority is the charset conversion. + for (let pair of charsetValues) { + try { + values.set(pair[0], decode2231Value(pair[1])); + } catch (e) { + // Bad charset, don't add anything. + } + } + } + + // Finally, return the values computed above. + values.preSemi = start; + return values; +} + +/** + * Convert a RFC 2231-encoded string parameter into a Unicode version of the + * string. This assumes that percent-decoding has already been applied. + * + * @param {String} value The RFC 2231-encoded string to decode. + * @return The Unicode version of the string. + */ +function decode2231Value(value) { + let quote1 = value.indexOf("'"); + let quote2 = quote1 >= 0 ? value.indexOf("'", quote1 + 1) : -1; + + let charset = (quote1 >= 0 ? value.substring(0, quote1) : ""); + // It turns out that the language isn't useful anywhere in our codebase for + // the present time, so we will safely ignore it. + //var language = (quote2 >= 0 ? value.substring(quote1 + 2, quote2) : ""); + value = value.substring(Math.max(quote1, quote2) + 1); + + // Convert the value into a typed array for decoding + let typedarray = mimeutils.stringToTypedArray(value); + + // Decode the charset. If the charset isn't found, we throw an error. Try to + // fallback in that case. + return new TextDecoder(charset, {fatal: true}) + .decode(typedarray, {stream: false}); +} + +// This is a map of known timezone abbreviations, for fallback in obsolete Date +// productions. +var kKnownTZs = { + // The following timezones are explicitly listed in RFC 5322. + "UT": "+0000", "GMT": "+0000", + "EST": "-0500", "EDT": "-0400", + "CST": "-0600", "CDT": "-0500", + "MST": "-0700", "MDT": "-0600", + "PST": "-0800", "PDT": "-0700", + // The following are time zones copied from NSPR's prtime.c + "AST": "-0400", // Atlantic Standard Time + "NST": "-0330", // Newfoundland Standard Time + "BST": "+0100", // British Summer Time + "MET": "+0100", // Middle Europe Time + "EET": "+0200", // Eastern Europe Time + "JST": "+0900" // Japan Standard Time +}; + +/** + * Parse a header that contains a date-time definition according to RFC 5322. + * The result is a JS date object with the same timestamp as the header. + * + * The dates returned by this parser cannot be reliably converted back into the + * original header for two reasons. First, JS date objects cannot retain the + * timezone information they were initialized with, so reserializing a date + * header would necessarily produce a date in either the current timezone or in + * UTC. Second, JS dates measure time as seconds elapsed from the POSIX epoch + * excluding leap seconds. Any timestamp containing a leap second is instead + * converted into one that represents the next second. + * + * Dates that do not match the RFC 5322 production are instead attempted to + * parse using the Date.parse function. The strings that are accepted by + * Date.parse are not fully defined by the standard, but most implementations + * should accept strings that look rather close to RFC 5322 strings. Truly + * invalid dates produce a formulation that results in an invalid date, + * detectable by having its .getTime() method return NaN. + * + * @param {String} header The MIME header value to parse. + * @returns {Date} The date contained within the header, as described + * above. + */ +function parseDateHeader(header) { + let tokens = getHeaderTokens(header, ",:", {}).map(x => x.toString()); + // What does a Date header look like? In practice, most date headers devolve + // into Date: [dow ,] dom mon year hh:mm:ss tzoff [(abbrev)], with the day of + // week mostly present and the timezone abbreviation mostly absent. + + // First, ignore the day-of-the-week if present. This would be the first two + // tokens. + if (tokens.length > 1 && tokens[1] === ',') + tokens = tokens.slice(2); + + // If there are too few tokens, the date is obviously invalid. + if (tokens.length < 8) + return new Date(NaN); + + // Save off the numeric tokens + let day = parseInt(tokens[0]); + // month is tokens[1] + let year = parseInt(tokens[2]); + let hours = parseInt(tokens[3]); + // tokens[4] === ':' + let minutes = parseInt(tokens[5]); + // tokens[6] === ':' + let seconds = parseInt(tokens[7]); + + // Compute the month. Check only the first three digits for equality; this + // allows us to accept, e.g., "January" in lieu of "Jan." + let month = mimeutils.kMonthNames.indexOf(tokens[1].slice(0, 3)); + // If the month name is not recognized, make the result illegal. + if (month < 0) + month = NaN; + + // Compute the full year if it's only 2 digits. RFC 5322 states that the + // cutoff is 50 instead of 70. + if (year < 100) { + year += year < 50 ? 2000 : 1900; + } + + // Compute the timezone offset. If it's not in the form ±hhmm, convert it to + // that form. + let tzoffset = tokens[8]; + if (tzoffset in kKnownTZs) + tzoffset = kKnownTZs[tzoffset]; + let decompose = /^([+-])(\d\d)(\d\d)$/.exec(tzoffset); + // Unknown? Make it +0000 + if (decompose === null) + decompose = ['+0000', '+', '00', '00']; + let tzOffsetInMin = parseInt(decompose[2]) * 60 + parseInt(decompose[3]); + if (decompose[1] == '-') + tzOffsetInMin = -tzOffsetInMin; + + // How do we make the date at this point? Well, the JS date's constructor + // builds the time in terms of the local timezone. To account for the offset + // properly, we need to build in UTC. + let finalDate = new Date(Date.UTC(year, month, day, hours, minutes, seconds) + - tzOffsetInMin * 60 * 1000); + + // Suppose our header was mangled and we couldn't read it--some of the fields + // became undefined. In that case, the date would become invalid, and the + // indication that it is so is that the underlying number is a NaN. In that + // scenario, we could build attempt to use JS Date parsing as a last-ditch + // attempt. But it's not clear that such messages really exist in practice, + // and the valid formats for Date in ES6 are unspecified. + return finalDate; +} + +//////////////////////////////////////// +// Structured header decoding support // +//////////////////////////////////////// + +// Load the default structured decoders +var structuredDecoders = new Map(); +var structuredHeaders = require('./structuredHeaders'); +var preferredSpellings = structuredHeaders.spellings; +var forbiddenHeaders = new Set(); +for (let pair of structuredHeaders.decoders) { + addStructuredDecoder(pair[0], pair[1]); + forbiddenHeaders.add(pair[0].toLowerCase()); +} + +/** + * Use an already-registered structured decoder to parse the value of the header + * into a structured representation. + * + * As this method is designed to be used for the internal MIME Parser to convert + * the raw header values to well-structured values, value is intended to be an + * array consisting of all occurences of the header in order. However, for ease + * of use by other callers, it can also be treated as a string. + * + * If the decoder for the header is not found, an exception will be thrown. + * + * A large set of headers have pre-defined structured decoders; these decoders + * cannot be overrided with addStructuredDecoder, as doing so could prevent the + * MIME or message parsers from working properly. The pre-defined structured + * headers break down into five clases of results, plus some ad-hoc + * representations. They are: + * + * Addressing headers (results are the same as parseAddressingHeader): + * - Approved + * - Bcc + * - Cc + * - Delivered-To + * - Disposition-Notification-To + * - From + * - Mail-Reply-To + * - Mail-Followup-To + * - Reply-To + * - Resent-Bcc + * - Resent-Cc + * - Resent-From + * - Resent-Reply-To + * - Resent-Sender + * - Resent-To + * - Return-Receipt-To + * - Sender + * - To + * + * Date headers (results are the same as parseDateHeader): + * - Date + * - Expires + * - Injection-Date + * - NNTP-Posting-Date + * - Resent-Date + * + * References headers (results are the same as parseReferencesHeader): + * - (TODO: Parsing support for these headers is currently unsupported) + * + * Message-ID headers (results are the first entry of the result of + * parseReferencesHeader): + * - (TODO: Parsing support for these headers is currently unsupported) + * + * Unstructured headers (results are merely decoded according to RFC 2047): + * - Comments + * - Content-Description + * - Keywords + * - Subject + * + * The ad-hoc headers and their resulting formats are as follows: + * Content-Type: returns a JS Map of parameter names (in lower case) to their + * values, along with the following extra properties defined on the map: + * - mediatype: the type to the left of '/' (e.g., 'text', 'message') + * - subtype: the type to the right of '/' (e.g., 'plain', 'rfc822') + * - type: the full typename (e.g., 'text/plain') + * RFC 2047 and RFC 2231 decoding is applied where appropriate. The values of + * the type, mediatype, and subtype attributes are all normalized to lower-case, + * as are the names of all parameters. + * + * Content-Transfer-Encoding: the first value is converted to lower-case. + * + * @param {String} header The name of the header of the values. + * @param {String|Array} value The value(s) of the headers, after charset + * conversion (if any) has been applied. If it is + * an array, the headers are listed in the order + * they appear in the message. + * @returns {Object} A structured representation of the header values. + */ +function parseStructuredHeader(header, value) { + // Enforce that the parameter is an array. If it's a string, make it a + // 1-element array. + if (typeof value === "string" || value instanceof String) + value = [value]; + if (!Array.isArray(value)) + throw new TypeError("Header value is not an array: " + value); + + // Lookup the header in our decoders; if present, use that to decode the + // header. + let lowerHeader = header.toLowerCase(); + if (structuredDecoders.has(lowerHeader)) { + return structuredDecoders.get(lowerHeader).call(headerparser, value); + } + + // If not present, throw an exception. + throw new Error("Unknown structured header: " + header); +} + +/** + * Add a custom structured MIME decoder to the set of known decoders. These + * decoders are used for {@link parseStructuredHeader} and similar functions to + * encode richer, more structured values instead of relying on string + * representations everywhere. + * + * Structured decoders are functions which take in a single parameter consisting + * of an array of the string values of the header, in order that they appear in + * the message. These headers have had the charset conversion (if necessary) + * applied to them already. The this parameter of the function is set to be the + * jsmime.headerparser module. + * + * There is a large set of structured decoders built-in to the jsmime library + * already. As these headers are fundamental to the workings of jsmime, + * attempting to replace them with a custom version will instead produce an + * exception. + * + * @param {String} header The header name (in any case) + * for which the decoder will be + * used. + * @param {Function(String[] -> Object)} decoder The structured decoder + * function. + */ +function addStructuredDecoder(header, decoder) { + let lowerHeader = header.toLowerCase(); + if (forbiddenHeaders.has(lowerHeader)) + throw new Error("Cannot override header: " + header); + structuredDecoders.set(lowerHeader, decoder); + if (!preferredSpellings.has(lowerHeader)) + preferredSpellings.set(lowerHeader, header); +} + +headerparser.addStructuredDecoder = addStructuredDecoder; +headerparser.convert8BitHeader = convert8BitHeader; +headerparser.decodeRFC2047Words = decodeRFC2047Words; +headerparser.getHeaderTokens = getHeaderTokens; +headerparser.parseAddressingHeader = parseAddressingHeader; +headerparser.parseDateHeader = parseDateHeader; +headerparser.parseParameterHeader = parseParameterHeader; +headerparser.parseStructuredHeader = parseStructuredHeader; +return Object.freeze(headerparser); + +}); + +//////////////////////////////////////////////////////////////////////////////// +// JavaScript Raw MIME Parser // +//////////////////////////////////////////////////////////////////////////////// + +/** + * The parser implemented in this file produces a MIME part tree for a given + * input message via a streaming callback interface. It does not, by itself, + * understand concepts like attachments (hence the term 'Raw'); the consumer + * must translate output into such a format. + * + * Charsets: + * The MIME specifications permit a single message to contain multiple charsets + * (or perhaps none) as raw octets. As JavaScript strings are implicitly + * implemented in UTF-16, it is possible that some engines will attempt to + * convert these strings using an incorrect charset or simply fail to convert + * them at all. This parser assumes that its input is in the form of a "binary + * string", a string that uses only the first 256 characters of Unicode to + * represent the individual octets. To verify that charsets are not getting + * mangled elsewhere in the pipeline, the auxiliary test file test/data/charsets + * can be used. + * + * This parser attempts to hide the charset details from clients as much as + * possible. The resulting values of structured headers are always converted + * into proper Unicode strings before being exposed to clients; getting at the + * raw binary string data can only be done via getRawHeader. The .charset + * parameter on header objects, if changed, changes the fallback charset used + * for headers. It is initialized to the presumed charset of the corresponding + * part, taking into account the charset and force-charset options of the + * parser. Body parts are only converted into Unicode strings if the strformat + * option is set to Unicode. Even then, only the bodies of parts with a media + * type of text are converted to Unicode strings using available charset data; + * other parts are retained as Uint8Array objects. + * + * Part numbering: + * Since the output is a streaming format, individual parts are identified by a + * numbering scheme. The intent of the numbering scheme for parts is to comply + * with the part numbers as dictated by RFC 3501 as much possible; however, + * that scheme does have several edge cases which would, if strictly followed, + * make it impossible to refer to certain parts of the message. In addition, we + * wish to make it possible to refer to parts which are not discoverable in the + * original MIME tree but are still viewable as parts. The part numbering + * scheme is as follows: + * - Individual sections of a multipart/* body are numbered in increasing order + * sequentially, starting from 1. Note that the prologue and the epilogue of + * a multipart/* body are not considered entities and are therefore not + * included in the part numbering scheme (there is no way to refer to them). + * - The numbers of multipart/* parts are separated by `.' characters. + * - The outermost message is referred to by use of the empty string. + * --> The following segments are not accounted for by IMAP part numbering. <-- + * - The body of any message/rfc822 or similar part is distinguished from the + * message part as a whole by appending a `$' character. This does not apply + * to the outermost message/rfc822 envelope. + */ + +def('mimeparser', function(require) { +"use strict"; + +var mimeutils = require('./mimeutils'); +var headerparser = require('./headerparser'); +var spellings = require('./structuredHeaders').spellings; + +/** + * An object that represents the structured MIME headers for a message. + * + * This class is primarily used as the 'headers' parameter in the startPart + * callback on handlers for MimeParser. As such, it is designed to do the right + * thing in common cases as much as possible, with some advanced customization + * possible for clients that need such flexibility. + * + * In a nutshell, this class stores the raw headers as an internal Map. The + * structured headers are not computed until they are actually used, which means + * that potentially expensive structuring (e.g., doing manual DKIM validation) + * can be performed as a structured decoder without impeding performance for + * those who just want a few common headers. + * + * The outer API of this class is intended to be similar to a read-only Map + * object (complete with iterability support), with a few extra properties to + * represent things that are hard to determine properly from headers. The keys + * used are "preferred spellings" of the headers, although the get and has + * methods will accept header parameters of any case. Preferred spellings are + * derived from the name passed to addStructuredDecoder/addStructuredEncoder; if + * no structured decoder has been registered, then the name capitalizes the + * first letter of every word in the header name. + * + * Extra properties compared to a Map object are: + * - charset: This field represents the assumed charset of the associated MIME + * body. It is prefilled using a combination of the charset and force-charset + * options on the associated MimeParser instance as well as attempting to find + * a charset parameter in the Content-Type header. + * + * If the force-charset option is false, the charset is guessed first using + * the Content-Type header's charset parameter, falling back to the charset + * option if it is present. If the force-charset option is true, the charset + * is initially set to the charset option. This initial guessed value can be + * overridden at any time by simply setting the field on this object. + * + * The charset is better reflected as a parameter of the body rather than the + * headers; this is ultimately the charset parameter that will be used if a + * body part is being converted to a Unicode strformat. Headers are converted + * using headerparser.convert8BitHeader, and this field is used as the + * fallbackCharset parameter, which will always to attempt to decode as UTF-8 + * first (in accordance with RFC 6532) and will refuse to decode as UTF-16 or + * UTF-32, as ASCII is not a subset of those charsets. + * + * - rawHeaderText: This read-only field contains the original header text from + * which headers were parsed, preserving case and whitespace (including + * alternate line endings instead of CRLF) exactly. If the header text begins + * with the mbox delimiter (i.e., a line that begins with "From "), then that + * is excluded from the rawHeaderText value and is not reflected anywhere in + * this object. + * + * - contentType: This field contains the structured representation of the + * Content-Type header, if it is present. If it is not present, it is set to + * the structured representation of the default Content-Type for a part (as + * this data is not easily guessed given only MIME tree events). + * + * The constructor for these objects is not externally exported, and thus they + * can only be created via MimeParser. + * + * @param rawHeaderText {BinaryString} The contents of the MIME headers to be + * parsed. + * @param options {Object} Options for the header parser. + * @param options.stripcontinuations {Boolean} If true, elide CRLFs from the + * raw header output. + */ +function StructuredHeaders(rawHeaderText, options) { + // An individual header is terminated by a CRLF, except if the CRLF is + // followed by a SP or TAB. Use negative lookahead to capture the latter case, + // and don't capture the strings or else split results get nasty. + let values = rawHeaderText.split(/(?:\r\n|\n)(?![ \t])|\r(?![ \t\n])/); + + // Ignore the first "header" if it begins with an mbox delimiter + if (values.length > 0 && values[0].substring(0, 5) == "From ") { + values.shift(); + // Elide the mbox delimiter from this._headerData + if (values.length == 0) + rawHeaderText = ''; + else + rawHeaderText = rawHeaderText.substring(rawHeaderText.indexOf(values[0])); + } + + let headers = new Map(); + for (let i = 0; i < values.length; i++) { + // Look for a colon. If it's not present, this header line is malformed, + // perhaps by premature EOF or similar. + let colon = values[i].indexOf(":"); + if (colon >= 0) { + var header = values[i].substring(0, colon); + var val = values[i].substring(colon + 1).trim(); + if (options.stripcontinuations) + val = val.replace(/[\r\n]/g, ''); + } else { + var header = values[i]; + var val = ''; + } + + // Canonicalize the header in lower-case form. + header = header.trim().toLowerCase(); + // Omit "empty" headers + if (header == '') + continue; + + // We keep an array of values for each header, since a given header may be + // repeated multiple times. + if (headers.has(header)) { + headers.get(header).push(val); + } else { + headers.set(header, [val]); + } + } + + /** + * A map of header names to arrays of raw values found in this header block. + * @private + */ + this._rawHeaders = headers; + /** + * Cached results of structured header parsing. + * @private + */ + this._cachedHeaders = new Map(); + Object.defineProperty(this, "rawHeaderText", + {get: function () { return rawHeaderText; }}); + Object.defineProperty(this, "size", + {get: function () { return this._rawHeaders.size; }}); + Object.defineProperty(this, "charset", { + get: function () { return this._charset; }, + set: function (value) { + this._charset = value; + // Clear the cached headers, since this could change their values + this._cachedHeaders.clear(); + } + }); + + // Default to the charset, until the message parser overrides us. + if ('charset' in options) + this._charset = options.charset; + else + this._charset = null; + + // If we have a Content-Type header, set contentType to return the structured + // representation. We don't set the value off the bat, since we want to let + // someone who changes the charset affect the values of 8-bit parameters. + Object.defineProperty(this, "contentType", { + configurable: true, + get: function () { return this.get('Content-Type'); } + }); +} + +/** + * Get a raw header. + * + * Raw headers are an array of the header values, listed in order that they were + * specified in the header block, and without any attempt to convert charsets or + * apply RFC 2047 decoding. For example, in the following message (where the + * is meant to represent binary-octets): + * + * X-Header: Value A + * X-Header: Vlue B + * Header2: Q + * + * the result of calling getRawHeader('X-Header') or getRawHeader('x-header') + * would be ['Value A', 'V\xC3\xA5lue B'] and the result of + * getRawHeader('Header2') would be ['Q']. + * + * @param headerName {String} The header name for which to get header values. + * @returns {BinaryString[]} The raw header values (with no charset conversion + * applied). + */ +StructuredHeaders.prototype.getRawHeader = function (headerName) { + return this._rawHeaders.get(headerName.toLowerCase()); +}; + +/** + * Retrieve a structured version of the header. + * + * If there is a registered structured decoder (registration happens via + * headerparser.addStructuredDecoder), then the result of calling that decoder + * on the charset-corrected version of the header is returned. Otherwise, the + * values are charset-corrected and RFC 2047 decoding is applied as if the + * header were an unstructured header. + * + * A substantial set of headers have pre-registed structured decoders, which, in + * some cases, are unable to be overridden due to their importance in the + * functioning of the parser code itself. + * + * @param headerName {String} The header name for which to get the header value. + * @returns The structured header value of the output. + */ +StructuredHeaders.prototype.get = function (headerName) { + // Normalize the header name to lower case + headerName = headerName.toLowerCase(); + + // First, check the cache for the header value + if (this._cachedHeaders.has(headerName)) + return this._cachedHeaders.get(headerName); + + // Not cached? Grab it [propagating lack of header to caller] + let headerValue = this._rawHeaders.get(headerName); + if (headerValue === undefined) + return headerValue; + + // Convert the header to Unicode + let charset = this.charset; + headerValue = headerValue.map(function (value) { + return headerparser.convert8BitHeader(value, charset); + }); + + // If there is a structured decoder, use that; otherwise, assume that the + // header is unstructured and only do RFC 2047 conversion + let structured; + try { + structured = headerparser.parseStructuredHeader(headerName, headerValue); + } catch (e) { + structured = headerValue.map(function (value) { + return headerparser.decodeRFC2047Words(value); + }); + } + + // Cache the result and return it + this._cachedHeaders.set(headerName, structured); + return structured; +}; + +/** + * Check if the message has the given header. + * + * @param headerName {String} The header name for which to get the header value. + * @returns {Boolean} True if the header is present in this header block. + */ +StructuredHeaders.prototype.has = function (headerName) { + // Check for presence in the raw headers instead of cached headers. + return this._rawHeaders.has(headerName.toLowerCase()); +}; + +// Make a custom iterator. Presently, support for Symbol isn't yet present in +// SpiderMonkey (or V8 for that matter), so type-pun the name for now. +var JS_HAS_SYMBOLS = typeof Symbol === "function"; +var ITERATOR_SYMBOL = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator"; + +/** + * An equivalent of Map.@@iterator, applied to the structured header + * representations. This is the function that makes + * for (let [header, value] of headers) work properly. + */ +StructuredHeaders.prototype[ITERATOR_SYMBOL] = function*() { + // Iterate over all the raw headers, and use the cached headers to retrieve + // them. + for (let headerName of this.keys()) { + yield [headerName, this.get(headerName)]; + } +}; + +/** + * An equivalent of Map.forEach, applied to the structured header + * representations. + * + * @param callback {Function(value, name, headers)} The callback to call for + * each header/value combo. + * @param thisarg {Object} The parameter that will be + * the |this| of the callback. + */ +StructuredHeaders.prototype.forEach = function (callback, thisarg) { + for (let [header, value] of this) { + callback.call(thisarg, value, header, this); + } +}; + +/** + * An equivalent of Map.entries, applied to the structured header + * representations. + */ +StructuredHeaders.prototype.entries = + StructuredHeaders.prototype[Symbol.iterator]; + +/// This function maps lower case names to a pseudo-preferred spelling. +function capitalize(headerName) { + return headerName.replace(/\b[a-z]/g, function (match) { + return match.toUpperCase(); + }); +} + +/** + * An equivalent of Map.keys, applied to the structured header representations. + */ +StructuredHeaders.prototype.keys = function*() { + for (let name of this._rawHeaders.keys()) { + yield spellings.get(name) || capitalize(name); + } +}; + +/** + * An equivalent of Map.values, applied to the structured header + * representations. + */ +StructuredHeaders.prototype.values = function* () { + for (let [, value] of this) { + yield value; + } +}; + + +/** + * A MIME parser. + * + * The inputs to the constructor consist of a callback object which receives + * information about the output data and an optional object containing the + * settings for the parser. + * + * The first parameter, emitter, is an object which contains several callbacks. + * Note that any and all of these methods are optional; the parser will not + * crash if one is missing. The callbacks are as follows: + * startMessage() + * Called when the stream to be parsed has started delivering data. This + * will be called exactly once, before any other call. + * endMessage() + * Called after all data has been delivered and the message parsing has + * been completed. This will be called exactly once, after any other call. + * startPart(string partNum, object headers) + * Called after the headers for a body part (including the top-level + * message) have been parsed. The first parameter is the part number (see + * the discussion on part numbering). The second parameter is an instance + * of StructuredHeaders that represents all of the headers for the part. + * endPart(string partNum) + * Called after all of the data for a body part (including sub-parts) has + * been parsed. The first parameter is the part number. + * deliverPartData(string partNum, {string,typedarray} data) + * Called when some data for a body part has been delivered. The first + * parameter is the part number. The second parameter is the data which is + * being delivered; the exact type of this data depends on the options + * used. Note that data is only delivered for leaf body parts. + * + * The second parameter, options, is an optional object containing the options + * for the parser. The following are the options that the parser may use: + * pruneat: [default=""] + * Treat the message as starting at the given part number, so that no parts + * above are returned. + * bodyformat: one of {none, raw, nodecode, decode} [default=nodecode] + * How to return the bodies of parts: + * none: no part data is returned + * raw: the body of the part is passed through raw + * nodecode: the body is passed through without decoding QP/Base64 + * decode: quoted-printable and base64 are fully decoded + * strformat: one of {binarystring, unicode, typedarray} [default=binarystring] + * How to treat output strings: + * binarystring: Data is a JS string with chars in the range [\x00-\xff] + * unicode: Data for text parts is converted to UTF-16; data for other + * parts is a typed array buffer, akin to typedarray. + * typedarray: Data is a JS typed array buffer + * charset: [default=""] + * What charset to assume if no charset information is explicitly provided. + * This only matters if strformat is unicode. See above note on charsets + * for more details. + * force-charset: [default=false] + * If true, this coerces all types to use the charset option, even if the + * message specifies a different content-type. + * stripcontinuations: [default=true] + * If true, then the newlines in headers are removed in the returned + * header objects. + * onerror: [default = nop-function] + * An error function that is called if an emitter callback throws an error. + * By default, such errors are swallowed by the parser. If you want the + * parser itself to throw an error, rethrow it via the onerror function. + */ +function MimeParser(emitter, options) { + /// The actual emitter + this._emitter = emitter; + /// Options for the parser (those listed here are defaults) + this._options = { + pruneat: "", + bodyformat: "nodecode", + strformat: "binarystring", + stripcontinuations: true, + charset: "", + "force-charset": false, + onerror: function swallow(error) {} + }; + // Load the options as a copy here (prevents people from changing on the fly). + if (options) + for (var opt in options) { + this._options[opt] = options[opt]; + } + + // Ensure that the error function is in fact a function + if (typeof this._options.onerror != "function") + throw new Exception("onerror callback must be a function"); + + // Reset the parser + this.resetParser(); +} + +/** + * Resets the parser to read a new message. This method need not be called + * immediately after construction. + */ +MimeParser.prototype.resetParser = function () { + /// Current parser state + this._state = PARSING_HEADERS; + /// Input data that needs to be held for buffer conditioning + this._holdData = ''; + /// Complete collection of headers (also used to accumulate _headerData) + this._headerData = ''; + /// Whether or not emitter.startMessage has been called + this._triggeredCall = false; + + /// Splitting input + this._splitRegex = this._handleSplit = undefined; + /// Subparsing + this._subparser = this._subPartNum = undefined; + /// Data that has yet to be consumed by _convertData + this._savedBuffer = ''; + /// Convert data + this._convertData = undefined; + /// String decoder + this._decoder = undefined; +}; + +/** + * Deliver a buffer of data to the parser. + * + * @param buffer {BinaryString} The raw data to add to the message. + */ +MimeParser.prototype.deliverData = function (buffer) { + // In ideal circumstances, we'd like to parse the message all at once. In + // reality, though, data will be coming to us in packets. To keep the amount + // of saved state low, we want to make basic guarantees about how packets get + // delivered. Our basic model is a twist on line-buffering, as the format of + // MIME and messages make it hard to not do so: we can handle multiple lines + // at once. To ensure this, we start by conditioning the packet by + // withholding data to make sure that the internal deliveries have the + // guarantees. This implies that we need to do the following steps: + // 1. We don't know if a `\r' comes from `\r\n' or the old mac line ending + // until we see the next character. So withhold the last `\r'. + // 2. Ensure that every packet ends on a newline. So scan for the end of the + // line and withhold until the \r\n comes through. + // [Note that this means that an input message that uses \r line endings and + // is being passed to us via a line-buffered input is going to have most of + // its data being withhold until the next buffer. Since \r is so uncommon of + // a line ending in modern times, this is acceptable lossage.] + // 3. Eliminate empty packets. + + // Add in previously saved data + if (this._holdData) { + buffer = this._holdData + buffer; + this._holdData = ''; + } + + // Condition the input, so that we get the multiline-buffering mentioned in + // the above comment. + if (buffer.length > 0) { + [buffer, this._holdData] = conditionToEndOnCRLF(buffer); + } + + // Ignore 0-length buffers. + if (buffer.length == 0) + return; + + // Signal the beginning, if we haven't done so. + if (!this._triggeredCall) { + this._callEmitter("startMessage"); + this._triggeredCall = true; + } + + // Finally, send it the internal parser. + this._dispatchData("", buffer, true); +} + +/** + * Ensure that a set of data always ends in an end-of-line character. + * + * @param buffer {BinaryString} The data with no guarantees about where it ends. + * @returns {BinaryString[]} An array of 2 binary strings where the first string + * ends in a newline and the last string contains the + * text in buffer following the first string. + */ +function conditionToEndOnCRLF(buffer) { + // Find the last occurrence of '\r' or '\n' to split the string. However, we + // don't want to consider '\r' if it is the very last character, as we need + // the next packet to tell if the '\r' is the beginning of a CRLF or a line + // ending by itself. + let lastCR = buffer.lastIndexOf('\r', buffer.length - 2); + let lastLF = buffer.lastIndexOf('\n'); + let end = lastLF > lastCR ? lastLF : lastCR; + return [buffer.substring(0, end + 1), buffer.substring(end + 1)]; +}; + +/** + * Tell the parser that all of the data has been delivered. + * + * This will flush all of the internal state of the parser. + */ +MimeParser.prototype.deliverEOF = function () { + // Start of input buffered too long? Call start message now. + if (!this._triggeredCall) { + this._triggeredCall = true; + this._callEmitter("startMessage"); + } + // Force a flush of all of the data. + if (this._holdData) + this._dispatchData("", this._holdData, true); + this._dispatchEOF(""); + // Signal to the emitter that we're done. + this._callEmitter("endMessage"); +}; + +/** + * Calls a method on the emitter safely. + * + * This method ensures that errors in the emitter call won't cause the parser + * to exit with an error, unless the user wants it to. + * + * @param funcname {String} The function name to call on the emitter. + * @param args... Extra arguments to pass into the emitter callback. + */ +MimeParser.prototype._callEmitter = function (funcname) { + if (this._emitter && funcname in this._emitter) { + let args = Array.prototype.splice.call(arguments, 1); + if (args.length > 0 && this._willIgnorePart(args[0])) { + // partNum is always the first argument, so check to make sure that it + // satisfies our emitter's pruneat requirement. + return; + } + try { + this._emitter[funcname].apply(this._emitter, args); + } catch (e) { + // We ensure that the onerror attribute in options is a function, so this + // is always safe. + this._options.onerror(e); + } + } +}; + +/** + * Helper function to decide if a part's output will never be seen. + * + * @param part {String} The number of the part. + * @returns {Boolean} True if the emitter is not interested in this part. + */ +MimeParser.prototype._willIgnorePart = function (part) { + if (this._options["pruneat"]) { + let match = this._options["pruneat"]; + let start = part.substr(0, match.length); + // It needs to start with and follow with a new part indicator + // (i.e., don't let 10 match with 1, but let 1.1 or 1$ do so) + if (start != match || (match.length < part.length && + "$.".indexOf(part[match.length]) == -1)) + return true; + } + return false; +}; + +////////////////////// +// MIME parser core // +////////////////////// + +// This MIME parser is a stateful parser; handling of the MIME tree is mostly +// done by creating new parsers and feeding data to them manually. In parallel +// to the externally-visible deliverData and deliverEOF, the two methods +// _dispatchData and _dispatchEOF are the internal counterparts that do the +// main work of moving data to where it needs to go; helper functions are used +// to handle translation. +// +// The overall flow of the parser is this. First, it buffers all of the data +// until the dual-CRLF pattern is noticed. Once that is found, it parses the +// entire header chunk at once. As a result of header parsing, the parser enters +// one of three modes for handling data, and uses a special regex to change +// modes and handle state changes. Specific details about the states the parser +// can be in are as follows: +// PARSING_HEADERS: The input buffer is concatenated to the currently-received +// text, which is then searched for the CRLFCRLF pattern. If found, the data +// is split at this boundary; the first chunk is parsed using _parseHeaders, +// and the second chunk will fall through to buffer processing. After +// splitting, the headers are deliverd via the emitter, and _startBody is +// called to set up state for the parser. +// SEND_TO_BLACK_HOLE: All data in the input is ignored. +// SEND_TO_EMITTER: All data is passed into the emitter, if it is desired. +// Data can be optionally converted with this._convertData. +// SEND_TO_SUBPARSER: All data is passed into the subparser's _dispatchData +// method, using _subPartNum as the part number and _subparser as the object +// to call. Data can be optionally converted first with this._convertData. +// +// Additional state modifications can be done using a regex in _splitRegex and +// the callback method this._handleSplit(partNum, regexResult). The _handleSplit +// callback is free to do any modification to the current parser, including +// modifying the _splitRegex value. Packet conditioning guarantees that every +// buffer string passed into _dispatchData will have started immediately after a +// newline character in the fully assembled message. +// +// The this._convertData method, if present, is expected to return an array of +// two values, [{typedarray, string} decoded_buffer, string unused_buffer], and +// has as its arguments (string buffer, bool moreToCome). +// +// The header parsing by itself does very little parsing, only parsing as if all +// headers were unstructured fields. Values are munged so that embedded newlines +// are stripped and the result is also trimmed. Headers themselves are +// canonicalized into lower-case. + + +// Parser states. See the large comment above. +var PARSING_HEADERS = 1; +var SEND_TO_BLACK_HOLE = 2; +var SEND_TO_EMITTER = 3; +var SEND_TO_SUBPARSER = 4; + +/** + * Main dispatch for incoming packet data. + * + * The incoming data needs to have been sanitized so that each packet begins on + * a newline boundary. The part number for the current parser also needs to be + * passed in. The checkSplit parameter controls whether or not the data in + * buffer needs to be checked against _splitRegex; this is used internally for + * the mechanics of splitting and should otherwise always be true. + * + * @param partNum {String} The part number being currently parsed. + * @param buffer {BinaryString} The text (conditioned as mentioned above) to + * pass to the parser. + * @param checkSplit {Boolean} If true, split the text using _splitRegex. + * This is set to false internally to handle + * low-level splitting details. + */ +MimeParser.prototype._dispatchData = function (partNum, buffer, checkSplit) { + // Are we parsing headers? + if (this._state == PARSING_HEADERS) { + this._headerData += buffer; + // Find the end of the headers--either it's a CRLF at the beginning (in + // which case we have no headers), or it's a pair of CRLFs. + let result = /(?:^(?:\r\n|[\r\n]))|(\r\n|[\r\n])\1/.exec(this._headerData); + if (result != null) { + // If we found the end of headers, split the data at this point and send + // the stuff after the double-CRLF into the later body parsing. + let headers = this._headerData.substr(0, result.index); + buffer = this._headerData.substring(result.index + result[0].length); + this._headerData = headers; + this._headers = this._parseHeaders(); + this._callEmitter("startPart", partNum, this._headers); + this._startBody(partNum); + } else { + return; + } + } + + // We're in the middle of the body. Start by testing the split regex, to see + // if there are many things that need to be done. + if (checkSplit && this._splitRegex) { + let splitResult = this._splitRegex.exec(buffer); + if (splitResult) { + // Pass the text before the split through the current state. + let start = splitResult.index, len = splitResult[0].length; + if (start > 0) + this._dispatchData(partNum, buffer.substr(0, start), false); + + // Tell the handler that we've seen the split. Note that this can change + // any method on `this'. + this._handleSplit(partNum, splitResult); + + // Send the rest of the data to where it needs to go. There could be more + // splits in the data, so watch out! + buffer = buffer.substring(start + len); + if (buffer.length > 0) + this._dispatchData(partNum, buffer, true); + return; + } + } + + // Where does the data go? + if (this._state == SEND_TO_BLACK_HOLE) { + // Don't send any data when going to the black hole. + return; + } else if (this._state == SEND_TO_EMITTER) { + // Don't pass body data if the format is to be none + let passData = this._options["bodyformat"] != "none"; + if (!passData || this._willIgnorePart(partNum)) + return; + buffer = this._applyDataConversion(buffer, this._options["strformat"]); + if (buffer.length > 0) + this._callEmitter("deliverPartData", partNum, buffer); + } else if (this._state == SEND_TO_SUBPARSER) { + buffer = this._applyDataConversion(buffer, "binarystring"); + if (buffer.length > 0) + this._subparser._dispatchData(this._subPartNum, buffer, true); + } +}; + +/** + * Output data using the desired output format, saving data if data conversion + * needs extra data to be saved. + * + * @param buf {BinaryString} The data to be sent to the output. + * @param type {String} The type of the data to output. Valid values are + * the same as the strformat option. + * @returns Coerced and converted data that can be sent to the emitter or + * subparser. + */ +MimeParser.prototype._applyDataConversion = function (buf, type) { + // If we need to convert data, do so. + if (this._convertData) { + // Prepend leftover data from the last conversion. + buf = this._savedBuffer + buf; + [buf, this._savedBuffer] = this._convertData(buf, true); + } + return this._coerceData(buf, type, true); +}; + +/** + * Coerce the input buffer into the given output type. + * + * @param buffer {BinaryString|Uint8Array} The data to be converted. + * @param type {String} The type to convert the data to. + * @param more {boolean} If true, this function will never be + * called again. + * @returns {BinaryString|String|Uint8Array} The desired output format. + */ +/// Coerces the buffer (a string or typedarray) into a given type +MimeParser.prototype._coerceData = function (buffer, type, more) { + if (typeof buffer == "string") { + // string -> binarystring is a nop + if (type == "binarystring") + return buffer; + // Either we're going to array or unicode. Both people need the array + var typedarray = mimeutils.stringToTypedArray(buffer); + // If it's unicode, do the coercion from the array + // If its typedarray, just return the synthesized one + return type == "unicode" ? this._coerceData(typedarray, "unicode", more) + : typedarray; + } else if (type == "binarystring") { + // Doing array -> binarystring + return mimeutils.typedArrayToString(buffer); + } else if (type == "unicode") { + // Doing array-> unicode: Use the decoder set up earlier to convert + if (this._decoder) + return this._decoder.decode(buffer, {stream: more}); + // If there is no charset, just return the typed array instead. + return buffer; + } + throw new Error("Invalid type: " + type); +}; + +/** + * Signal that no more data will be dispatched to this parser. + * + * @param partNum {String} The part number being currently parsed. + */ +MimeParser.prototype._dispatchEOF = function (partNum) { + if (this._state == PARSING_HEADERS) { + // Unexpected EOF in headers. Parse them now and call startPart/endPart + this._headers = this._parseHeaders(); + this._callEmitter("startPart", partNum, this._headers); + } else if (this._state == SEND_TO_SUBPARSER) { + // Pass in any lingering data + if (this._convertData && this._savedBuffer) + this._subparser._dispatchData(this._subPartNum, + this._convertData(this._savedBuffer, false)[0], true); + this._subparser._dispatchEOF(this._subPartNum); + // Clean up after ourselves + this._subparser = null; + } else if (this._convertData && this._savedBuffer) { + // Convert lingering data + let [buffer, ] = this._convertData(this._savedBuffer, false); + buffer = this._coerceData(buffer, this._options["strformat"], false); + if (buffer.length > 0) + this._callEmitter("deliverPartData", partNum, buffer); + } + + // We've reached EOF for this part; tell the emitter + this._callEmitter("endPart", partNum); +}; + +/** + * Produce a dictionary of all headers as if they were unstructured fields. + * + * @returns {StructuredHeaders} The structured header objects for the header + * block. + */ +MimeParser.prototype._parseHeaders = function () { + let headers = new StructuredHeaders(this._headerData, this._options); + + // Fill the headers.contentType parameter of headers. + let contentType = headers.get('Content-Type'); + if (typeof contentType === "undefined") { + contentType = headerparser.parseStructuredHeader('Content-Type', + this._defaultContentType || 'text/plain'); + Object.defineProperty(headers, "contentType", { + get: function () { return contentType; } + }); + } else { + Object.defineProperty(headers, "contentType", { configurable: false }); + } + + // Find the charset for the current part. If the user requested a forced + // conversion, use that first. Otherwise, check the content-type for one and + // fallback to a default if it is not present. + let charset = ''; + if (this._options["force-charset"]) + charset = this._options["charset"]; + else if (contentType.has("charset")) + charset = contentType.get("charset"); + else + charset = this._options["charset"]; + headers.charset = charset; + + // Retain a copy of the charset so that users don't override our decision for + // decoding body parts. + this._charset = charset; + return headers; +}; + +/** + * Initialize the parser state for the body of this message. + * + * @param partNum {String} The part number being currently parsed. + */ +MimeParser.prototype._startBody = function Parser_startBody(partNum) { + let contentType = this._headers.contentType; + + // Should the bodyformat be raw, we just want to pass through all data without + // trying to interpret it. + if (this._options["bodyformat"] == "raw" && + partNum == this._options["pruneat"]) { + this._state = SEND_TO_EMITTER; + return; + } + + // The output depents on the content-type. Basic rule of thumb: + // 1. Discrete media types (text, video, audio, image, application) are passed + // through with no alterations beyond Content-Transfer-Encoding unpacking. + // 2. Everything with a media type of multipart is treated the same. + // 3. Any message/* type that acts like a mail message (rfc822, news, global) + // is parsed as a header/body pair again. Most of the other message/* types + // have similar structures, but they don't have cascading child subparts, + // so it's better to pass their entire contents to the emitter and let the + // consumer deal with them. + // 4. For untyped data, there needs to be no Content-Type header. This helps + // avoid false positives. + if (contentType.mediatype == 'multipart') { + // If there's no boundary type, everything will be part of the prologue of + // the multipart message, so just feed everything into a black hole. + if (!contentType.has('boundary')) { + this._state = SEND_TO_BLACK_HOLE; + return; + } + // The boundary of a multipart message needs to start with -- and be at the + // beginning of the line. If -- is after the boundary, it represents the + // terminator of the multipart. After the line, there may be only whitespace + // and then the CRLF at the end. Since the CRLFs in here are necessary for + // distinguishing the parts, they are not included in the subparts, so we + // need to capture them in the regex as well to prevent them leaking out. + this._splitRegex = new RegExp('(\r\n|[\r\n]|^)--' + + contentType.get('boundary').replace(/[\\^$*+?.()|{}[\]]/g, '\\$&') + + '(--)?[ \t]*(?:\r\n|[\r\n]|$)'); + this._handleSplit = this._whenMultipart; + this._subparser = new MimeParser(this._emitter, this._options); + // multipart/digest defaults to message/rfc822 instead of text/plain + if (contentType.subtype == "digest") + this._subparser._defaultContentType = "message/rfc822"; + + // All text before the first boundary and after the closing boundary are + // supposed to be ignored ("must be ignored", according to RFC 2046 §5.1.1); + // in accordance with these wishes, ensure they don't get passed to any + // deliverPartData. + this._state = SEND_TO_BLACK_HOLE; + + // Multipart MIME messages stipulate that the final CRLF before the boundary + // delimiter is not matched. When the packet ends on a CRLF, we don't know + // if the next text could be the boundary. Therefore, we need to withhold + // the last line of text to be sure of what's going on. The _convertData is + // how we do this, even though we're not really converting any data. + this._convertData = function mpart_no_leak_crlf(buffer, more) { + let splitPoint = buffer.length; + if (more) { + if (buffer.charAt(splitPoint - 1) == '\n') + splitPoint--; + if (splitPoint >= 0 && buffer.charAt(splitPoint - 1) == '\r') + splitPoint--; + } + let res = conditionToEndOnCRLF(buffer.substring(0, splitPoint)); + let preLF = res[0]; + let rest = res[1]; + return [preLF, rest + buffer.substring(splitPoint)]; + } + } else if (contentType.type == 'message/rfc822' || + contentType.type == 'message/global' || + contentType.type == 'message/news') { + // The subpart is just another header/body pair that goes to EOF, so just + // return the parse from that blob + this._state = SEND_TO_SUBPARSER; + this._subPartNum = partNum + "$"; + this._subparser = new MimeParser(this._emitter, this._options); + + // So, RFC 6532 happily allows message/global types to have CTE applied. + // This means that subparts would need to be decoded to determine their + // contents properly. There seems to be some evidence that message/rfc822 + // that is illegally-encoded exists in the wild, so be lenient and decode + // for any message/* type that gets here. + let cte = this._extractHeader('content-transfer-encoding', ''); + if (cte in ContentDecoders) + this._convertData = ContentDecoders[cte]; + } else { + // Okay, we just have to feed the data into the output + this._state = SEND_TO_EMITTER; + if (this._options["bodyformat"] == "decode") { + // If we wish to decode, look it up in one of our decoders. + let cte = this._extractHeader('content-transfer-encoding', ''); + if (cte in ContentDecoders) + this._convertData = ContentDecoders[cte]; + } + } + + // Set up the encoder for charset conversions; only do this for text parts. + // Other parts are almost certainly binary, so no translation should be + // applied to them. + if (this._options["strformat"] == "unicode" && + contentType.mediatype == "text") { + // If the charset is nonempty, initialize the decoder + if (this._charset !== "") { + this._decoder = new TextDecoder(this._charset); + } else { + // There's no charset we can use for decoding, so pass through as an + // identity encoder or otherwise this._coerceData will complain. + this._decoder = { + decode: function identity_decoder(buffer) { + return MimeParser.prototype._coerceData(buffer, "binarystring", true); + } + }; + } + } else { + this._decoder = null; + } +}; + +// Internal split handling for multipart messages. +/** + * When a multipary boundary is found, handle the process of managing the + * subparser state. This is meant to be used as a value for this._handleSplit. + * + * @param partNum {String} The part number being currently parsed. + * @param lastResult {Array} The result of the regular expression match. + */ +MimeParser.prototype._whenMultipart = function (partNum, lastResult) { + // Fix up the part number (don't do '' -> '.4' and don't do '1' -> '14') + if (partNum != "") partNum += "."; + if (!this._subPartNum) { + // No count? This means that this is the first time we've seen the boundary, + // so do some initialization for later here. + this._count = 1; + } else { + // If we did not match a CRLF at the beginning of the line, strip CRLF from + // the saved buffer. We do this in the else block because it is not + // necessary for the prologue, since that gets ignored anyways. + if (this._savedBuffer != '' && lastResult[1] === '') { + let useEnd = this._savedBuffer.length - 1; + if (this._savedBuffer[useEnd] == '\n') + useEnd--; + if (useEnd >= 0 && this._savedBuffer[useEnd] == '\r') + useEnd--; + this._savedBuffer = this._savedBuffer.substring(0, useEnd + 1); + } + // If we have saved data and we matched a CRLF, pass the saved data in. + if (this._savedBuffer != '') + this._subparser._dispatchData(this._subPartNum, this._savedBuffer, true); + // We've seen the boundary at least once before, so this must end a subpart. + // Tell that subpart that it has reached EOF. + this._subparser._dispatchEOF(this._subPartNum); + } + this._savedBuffer = ''; + + // The regex feeder has a capture on the (--)?, so if its result is present, + // then we have seen the terminator. Alternatively, the message may have been + // mangled to exclude the terminator, so also check if EOF has occurred. + if (lastResult[2] == undefined) { + this._subparser.resetParser(); + this._state = SEND_TO_SUBPARSER; + this._subPartNum = partNum + this._count; + this._count += 1; + } else { + // Ignore the epilogue + this._splitRegex = null; + this._state = SEND_TO_BLACK_HOLE; + } +}; + +/** + * Return the structured header from the current header block, or a default if + * it is not present. + * + * @param name {String} The header name to get. + * @param dflt {String} The default MIME value of the header. + * @returns The structured representation of the header. + */ +MimeParser.prototype._extractHeader = function (name, dflt) { + name = name.toLowerCase(); // Normalize name + return this._headers.has(name) ? this._headers.get(name) : + headerparser.parseStructuredHeader(name, [dflt]); +}; + +var ContentDecoders = {}; +ContentDecoders['quoted-printable'] = mimeutils.decode_qp; +ContentDecoders['base64'] = mimeutils.decode_base64; + +return MimeParser; +}); +def('headeremitter', function(require) { +/** + * This module implements the code for emitting structured representations of + * MIME headers into their encoded forms. The code here is a companion to, + * but completely independent of, jsmime.headerparser: the structured + * representations that are used as input to the functions in this file are the + * same forms that would be parsed. + */ + +"use strict"; + +var mimeutils = require('./mimeutils'); + +// Get the default structured encoders and add them to the map +var structuredHeaders = require('./structuredHeaders'); +var encoders = new Map(); +var preferredSpellings = structuredHeaders.spellings; +for (let [header, encoder] of structuredHeaders.encoders) { + addStructuredEncoder(header, encoder); +} + +/// Clamp a value in the range [min, max], defaulting to def if it is undefined. +function clamp(value, min, max, def) { + if (value === undefined) + return def; + if (value < min) + return min; + if (value > max) + return max; + return value; +} + +/** + * An object that can assemble structured header representations into their MIME + * representation. + * + * The character-counting portion of this class operates using individual JS + * characters as its representation of logical character, which is not the same + * as the number of octets used as UTF-8. If non-ASCII characters are to be + * included in headers without some form of encoding, then care should be taken + * to set the maximum line length to account for the mismatch between character + * counts and octet counts: the maximum line is 998 octets, which could be as + * few as 332 JS characters (non-BMP characters, although they take up 4 octets + * in UTF-8, count as 2 in JS strings). + * + * This code takes care to only insert line breaks at the higher-level breaking + * points in a header (as recommended by RFC 5322), but it may need to resort to + * including them more aggressively if this is not possible. If even aggressive + * line-breaking cannot allow a header to be emitted without violating line + * length restrictions, the methods will throw an exception to indicate this + * situation. + * + * In general, this code does not attempt to modify its input; for example, it + * does not attempt to change the case of any input characters, apply any + * Unicode normalization algorithms, or convert email addresses to ACE where + * applicable. The biggest exception to this rule is that most whitespace is + * collapsed to a single space, even in unstructured headers, while most leading + * and trailing whitespace is trimmed from inputs. + * + * @param {StreamHandler} handler The handler to which all output is sent. + * @param {Function(String)} handler.deliverData Receives encoded data. + * @param {Function()} handler.deliverEOF Sent when all text is sent. + * @param {Object} options Options for the emitter. + * @param [options.softMargin=78] {30 <= Integer <= 900} + * The ideal maximum number of logical characters to include in a line, not + * including the final CRLF pair. Lines may exceed this margin if parameters + * are excessively long. + * @param [options.hardMargin=332] {softMargin <= Integer <= 998} + * The maximum number of logical characters that can be included in a line, + * not including the final CRLF pair. If this count would be exceeded, then + * an error will be thrown and encoding will not be possible. + * @param [options.useASCII=true] {Boolean} + * If true, then RFC 2047 and RFC 2231 encoding of headers will be performed + * as needed to retain headers as ASCII. + */ +function HeaderEmitter(handler, options) { + /// The inferred value of options.useASCII + this._useASCII = options.useASCII === undefined ? true : options.useASCII; + /// The handler to use. + this._handler = handler; + /** + * The current line being built; note that we may insert a line break in the + * middle to keep under the maximum line length. + * + * @type String + * @private + */ + this._currentLine = ""; + + // Our bounds for soft and margins are not completely arbitrary. The minimum + // amount we need to encode is 20 characters, which can encode a single + // non-BMP character with RFC 2047. The value of 30 is chosen to give some + // breathing room for delimiters or other unbreakable characters. The maximum + // length is 998 octets, per RFC 5322; soft margins are slightly lower to + // allow for breathing room as well. The default of 78 for the soft margin is + // recommended by RFC 5322; the default of 332 for the hard margin ensures + // that UTF-8 encoding the output never violates the 998 octet limit. + this._softMargin = clamp(options.softMargin, 30, 900, 78); + this._hardMargin = clamp(options.hardMargin, this._softMargin, 998, 332); + + /** + * The index of the last preferred breakable position in the current line. + * + * @type Integer + * @private + */ + this._preferredBreakpoint = 0; +} + + +/////////////////////// +// Low-level methods // +/////////////////////// + +// Explanation of the emitter internals: +// RFC 5322 requires that we wrap our lines, ideally at 78 characters and at +// least by 998 octets. We can't wrap in arbitrary places, but wherever CFWS is +// valid... and ideally wherever clients are likely to expect it. In theory, we +// can break between every token (this is how RFC 822 operates), but, in RFC +// 5322, many of those breaks are relegated to obsolete productions, mostly +// because it is common to not properly handle breaks in those locations. +// +// So how do we do line breaking? The algorithm we implement is greedy, to +// simplify implementation. There are two margins: the soft margin, which we +// want to keep within, and the hard margin, which we absolutely have to keep +// within. There are also two kinds of break points: preferred and emergency. +// As long as we keep the line within the hard margin, we will only break at +// preferred breakpoints; emergency breakpoints are only used if we would +// otherwise exceed the hard margin. +// +// For illustration, here is an example header and where these break points are +// located: +// +// To: John "The Rock" Smith +// Preferred: ^ ^ ^ +// Emergency: ^ ^ ^ ^^ ^ ^ ^ ^ ^ +// +// Preferred breakpoints are indicated by setting the mayBreakAfter parameter of +// addText to true, while emergency breakpoints are set after every token passed +// into addText. This is handled implicitly by only adding text to _currentLine +// if it ends in an emergency breakpoint. +// +// Internally, the code keeps track of margins by use of two variables. The +// _softMargin and _hardMargin variables encode the positions at which code must +// absolutely break, and are set up from the initial options parameter. Breaking +// happens when _currentLine.length approaches these values, as mentioned above. + +/** + * Send a header line consisting of the first N characters to the handler. + * + * If the count parameter is missing, then we presume that the current header + * value being emitted is done and therefore we should not send a continuation + * space. Otherwise, we presume that we're still working, so we will send the + * continuation space. + * + * @private + * @param [count] {Integer} The number of characters in the current line to + * include before wrapping. + */ +HeaderEmitter.prototype._commitLine = function (count) { + let isContinuing = typeof count !== "undefined"; + + // Split at the point, and lop off whitespace immediately before and after. + if (isContinuing) { + var firstN = this._currentLine.slice(0, count).trimRight(); + var lastN = this._currentLine.slice(count).trimLeft(); + } else { + var firstN = this._currentLine.trimRight(); + var lastN = ""; + } + + // How many characters do we need to shift preferred/emergency breakpoints? + let shift = this._currentLine.length - lastN.length; + + // Send the line plus the final CRLF. + this._handler.deliverData(firstN + '\r\n'); + + // Fill the start of the line with the new data. + this._currentLine = lastN; + + // If this is a continuation, add an extra space at the beginning of the line. + // Adjust the breakpoint shift amount as well. + if (isContinuing) { + this._currentLine = ' ' + this._currentLine; + shift++; + } + + // We will always break at a point at or after the _preferredBreakpoint, if it + // exists, so this always gets reset to 0. + this._preferredBreakpoint = 0; +}; + +/** + * Reserve at least length characters in the current line. If there aren't + * enough characters, insert a line break. + * + * @private + * @param length {Integer} The number of characters to reserve space for. + * @return {Boolean} Whether or not there is enough space for length characters. + */ +HeaderEmitter.prototype._reserveTokenSpace = function (length) { + // We are not going to do a sanity check that length is within the wrap + // margins. The rationale is that this lets code simply call this function to + // force a higher-level line break than normal preferred line breaks (see + // addAddress for an example use). The text that would be added may need to be + // itself broken up, so it might not need all the length anyways, but it + // starts the break already. + + // If we have enough space, we don't need to do anything. + if (this._currentLine.length + length <= this._softMargin) + return true; + + // If we have a preferred breakpoint, commit the line at that point, and see + // if that is sufficient line-breaking. + if (this._preferredBreakpoint > 0) { + this._commitLine(this._preferredBreakpoint); + if (this._currentLine.length + length <= this._softMargin) + return true; + } + + // At this point, we can no longer keep within the soft margin. Let us see if + // we can fit within the hard margin. + if (this._currentLine.length + length <= this._hardMargin) { + return true; + } + + // Adding the text to length would violate the hard margin as well. Break at + // the last emergency breakpoint. + if (this._currentLine.length > 0) { + this._commitLine(this._currentLine.length); + } + + // At this point, if there is still insufficient room in the hard margin, we + // can no longer do anything to encode this word. Bail. + return this._currentLine.length + length <= this._hardMargin; +}; + +/** + * Adds a block of text to the current header, inserting a break if necessary. + * If mayBreakAfter is true and text does not end in whitespace, a single space + * character may be added to the output. If the text could not be added without + * violating line length restrictions, an error is thrown instead. + * + * @protected + * @param {String} text The text to add to the output. + * @param {Boolean} mayBreakAfter If true, the end of this text is a preferred + * breakpoint. + */ +HeaderEmitter.prototype.addText = function (text, mayBreakAfter) { + // Try to reserve space for the tokens. If we can't, give up. + if (!this._reserveTokenSpace(text.length)) + throw new Error("Cannot encode " + text + " due to length."); + + this._currentLine += text; + if (mayBreakAfter) { + // Make sure that there is an extra space if text could break afterwards. + this._preferredBreakpoint = this._currentLine.length; + if (text[text.length - 1] != ' ') { + this._currentLine += ' '; + } + } +}; + +/** + * Adds a block of text that may need quoting if it contains some character in + * qchars. If it is already quoted, no quoting will be applied. If the text + * cannot be added without violating maximum line length, an error is thrown + * instead. + * + * @protected + * @param {String} text The text to add to the output. + * @param {String} qchars The set of characters that cannot appear + * outside of a quoted string. + * @param {Boolean} mayBreakAfter If true, the end of this text is a preferred + * breakpoint. + */ +HeaderEmitter.prototype.addQuotable = function (text, qchars, mayBreakAfter) { + // No text -> no need to be quoted (prevents strict warning errors). + if (text.length == 0) + return; + + // Figure out if we need to quote the string. Don't quote a string which + // already appears to be quoted. + let needsQuote = false; + + if (!(text[0] == '"' && text[text.length - 1] == '"') && qchars != '') { + for (let i = 0; i < text.length; i++) { + if (qchars.includes(text[i])) { + needsQuote = true; + break; + } + } + } + + if (needsQuote) + text = '"' + text.replace(/["\\]/g, "\\$&") + '"'; + this.addText(text, mayBreakAfter); +}; + +/** + * Adds a block of text that corresponds to the phrase production in RFC 5322. + * Such text is a sequence of atoms, quoted-strings, or RFC-2047 encoded-words. + * This method will preprocess input to normalize all space sequences to a + * single space. If the text cannot be added without violating maximum line + * length, an error is thrown instead. + * + * @protected + * @param {String} text The text to add to the output. + * @param {String} qchars The set of characters that cannot appear + * outside of a quoted string. + * @param {Boolean} mayBreakAfter If true, the end of this text is a preferred + * breakpoint. + */ +HeaderEmitter.prototype.addPhrase = function (text, qchars, mayBreakAfter) { + // Collapse all whitespace spans into a single whitespace node. + text = text.replace(/[ \t\r\n]+/g, " "); + + // If we have non-ASCII text, encode it using RFC 2047. + if (this._useASCII && nonAsciiRe.test(text)) { + this.encodeRFC2047Phrase(text, mayBreakAfter); + return; + } + + // If quoting the entire string at once could fit in the line length, then do + // so. The check here is very loose, but this will inform is if we are going + // to definitely overrun the soft margin. + if ((this._currentLine.length + text.length) < this._softMargin) { + try { + this.addQuotable(text, qchars, mayBreakAfter); + // If we don't have a breakpoint, and the text is encoded as a sequence of + // atoms (and not a quoted-string), then make the last space we added a + // breakpoint, regardless of the mayBreakAfter setting. + if (this._preferredBreakpoint == 0 && text.includes(" ")) { + if (this._currentLine[this._currentLine.length - 1] != '"') + this._preferredBreakpoint = this._currentLine.lastIndexOf(" "); + } + return; + } catch (e) { + // If we get an error at this point, we failed to add the quoted string + // because the string was too long. Fall through to the case where we know + // that the input was too long to begin with. + } + } + + // If the text is too long, split the quotable string at space boundaries and + // add each word invidually. If we still can't add all those words, there is + // nothing that we can do. + let words = text.split(' '); + for (let i = 0; i < words.length; i++) { + this.addQuotable(words[i], qchars, + i == words.length - 1 ? mayBreakAfter : true); + } +}; + +/// A regular expression for characters that need to be encoded. +var nonAsciiRe = /[^\x20-\x7e]/; + +/// The beginnings of RFC 2047 encoded-word +var b64Prelude = "=?UTF-8?B?", qpPrelude = "=?UTF-8?Q?"; + +/// A list of ASCII characters forbidden in RFC 2047 encoded-words +var qpForbidden = "=?_()\","; + +var hexString = "0123456789abcdef"; + +/** + * Add a block of text as a single RFC 2047 encoded word. This does not try to + * split words if they are too long. + * + * @private + * @param {Uint8Array} encodedText The octets to encode. + * @param {Boolean} useQP If true, use quoted-printable; if false, + * use base64. + * @param {Boolean} mayBreakAfter If true, the end of this text is a + * preferred breakpoint. + */ +HeaderEmitter.prototype._addRFC2047Word = function (encodedText, useQP, + mayBreakAfter) { + let binaryString = mimeutils.typedArrayToString(encodedText); + if (useQP) { + var token = qpPrelude; + for (let i = 0; i < encodedText.length; i++) { + if (encodedText[i] < 0x20 || encodedText[i] >= 0x7F || + qpForbidden.includes(binaryString[i])) { + let ch = encodedText[i]; + token += "=" + hexString[(ch & 0xf0) >> 4] + hexString[ch & 0x0f]; + } else if (binaryString[i] == " ") { + token += "_"; + } else { + token += binaryString[i]; + } + } + token += "?="; + } else { + var token = b64Prelude + btoa(binaryString) + "?="; + } + this.addText(token, mayBreakAfter); +}; + +/** + * Add a block of text as potentially several RFC 2047 encoded-word tokens. + * + * @protected + * @param {String} text The text to add to the output. + * @param {Boolean} mayBreakAfter If true, the end of this text is a preferred + * breakpoint. + */ +HeaderEmitter.prototype.encodeRFC2047Phrase = function (text, mayBreakAfter) { + // Start by encoding the text into UTF-8 directly. + let encodedText = new TextEncoder("UTF-8").encode(text); + + // Make sure there's enough room for a single token. + let minLineLen = b64Prelude.length + 10; // Eight base64 characters plus ?= + if (!this._reserveTokenSpace(minLineLen)) { + this._commitLine(this._currentLine.length); + } + + // Try to encode as much UTF-8 text as possible in each go. + let b64Len = 0, qpLen = 0, start = 0; + let maxChars = (this._softMargin - this._currentLine.length) - + (b64Prelude.length + 2); + for (let i = 0; i < encodedText.length; i++) { + let b64Inc = 0, qpInc = 0; + // The length we need for base64 is ceil(length / 3) * 4... + if ((i - start) % 3 == 0) + b64Inc += 4; + + // The length for quoted-printable is 3 chars only if encoded + if (encodedText[i] < 0x20 || encodedText[i] >= 0x7f || + qpForbidden.includes(String.fromCharCode(encodedText[i]))) { + qpInc = 3; + } else { + qpInc = 1; + } + + if (b64Len + b64Inc > maxChars && qpLen + qpInc > maxChars) { + // Oops, we have too many characters! We need to encode everything through + // the current character. However, we can't split in the middle of a + // multibyte character. In UTF-8, characters that start with 10xx xxxx are + // the middle of multibyte characters, so backtrack until the start + // character is legal. + while ((encodedText[i] & 0xC0) == 0x80) + --i; + + // Add this part of the word and then make a continuation. + this._addRFC2047Word(encodedText.subarray(start, i), b64Len >= qpLen, + true); + + // Reset the array for parsing. + start = i; + --i; // Reparse this character as well + b64Len = qpLen = 0; + maxChars = this._softMargin - b64Prelude.length - 3; + } else { + // Add the counts for the current variable to the count to encode. + b64Len += b64Inc; + qpLen += qpInc; + } + } + + // Add the entire array at this point. + this._addRFC2047Word(encodedText.subarray(start), b64Len >= qpLen, + mayBreakAfter); +}; + +//////////////////////// +// High-level methods // +//////////////////////// + +/** + * Add the header name, with the colon and trailing space, to the output. + * + * @public + * @param {String} name The name of the header. + */ +HeaderEmitter.prototype.addHeaderName = function (name) { + this._currentLine = this._currentLine.trimRight(); + if (this._currentLine.length > 0) { + this._commitLine(); + } + this.addText(name + ": ", false); +}; + +/** + * Add a header and its structured value to the output. + * + * The name can be any case-insensitive variant of a known structured header; + * the output will include the preferred name of the structure instead of the + * case put into the name. If no structured encoder can be found, and the input + * value is a string, then the header is assumed to be unstructured and the + * value is added as if {@link addUnstructured} were called. + * + * @public + * @param {String} name The name of the header. + * @param value The structured value of the header. + */ +HeaderEmitter.prototype.addStructuredHeader = function (name, value) { + let lowerName = name.toLowerCase(); + if (encoders.has(lowerName)) { + this.addHeaderName(preferredSpellings.get(lowerName)); + encoders.get(lowerName).call(this, value); + } else if (typeof value === "string") { + // Assume it's an unstructured header. + // All-lower-case-names are ugly, so capitalize first letters. + name = name.replace(/(^|-)[a-z]/g, function(match) { + return match.toUpperCase(); + }); + this.addHeaderName(name); + this.addUnstructured(value); + } else { + throw new Error("Unknown header " + name); + } +}; + +/** + * Add a single address to the header. The address is an object consisting of a + * possibly-empty display name and an email address. + * + * @public + * @param Address addr The address to be added. + * @param {String} addr.name The (possibly-empty) name of the address to add. + * @param {String} addr.email The email of the address to add. + * @see headerparser.parseAddressingHeader + */ +HeaderEmitter.prototype.addAddress = function (addr) { + // If we have a display name, add that first. + if (addr.name) { + // This is a simple estimate that keeps names on one line if possible. + this._reserveTokenSpace(addr.name.length + addr.email.length + 3); + this.addPhrase(addr.name, ",()<>[]:;@.\"", true); + + // If we don't have an email address, don't write out the angle brackets for + // the address. It's already an abnormal situation should this appear, and + // this has better round-tripping properties. + if (!addr.email) + return; + + this.addText("<", false); + } + + // Find the local-part and domain of the address, since the local-part may + // need to be quoted separately. Note that the @ goes to the domain, so that + // the local-part may be quoted if it needs to be. + let at = addr.email.lastIndexOf("@"); + let localpart = "", domain = "" + if (at == -1) + localpart = addr.email; + else { + localpart = addr.email.slice(0, at); + domain = addr.email.slice(at); + } + + this.addQuotable(localpart, "()<>[]:;@\\,\" !", false); + this.addText(domain + (addr.name ? ">" : ""), false); +}; + +/** + * Add an array of addresses and groups to the output. Such an array may be + * found as the output of {@link headerparser.parseAddressingHeader}. Each + * element is either an address (an object with properties name and email), or a + * group (an object with properties name and group). + * + * @public + * @param {(Address|Group)[]} addrs A collection of addresses to add. + * @param {String} addrs[i].name The (possibly-empty) name of the + * address or the group to add. + * @param {String} [addrs[i].email] The email of the address to add. + * @param {Address[]} [addrs[i].group] A list of email addresses in the group. + * @see HeaderEmitter.addAddress + * @see headerparser.parseAddressingHeader + */ +HeaderEmitter.prototype.addAddresses = function (addresses) { + let needsComma = false; + for (let addr of addresses) { + // Add a comma if this is not the first element. + if (needsComma) + this.addText(", ", true); + needsComma = true; + + if ("email" in addr) { + this.addAddress(addr); + } else { + // A group has format name: member, member; + // Note that we still add a comma after the group is completed. + this.addPhrase(addr.name, ",()<>[]:;@.\"", false); + this.addText(":", true); + + this.addAddresses(addr.group); + this.addText(";", true); + } + } +}; + +/** + * Add an unstructured header value to the output. This effectively means only + * inserting line breaks were necessary, and using RFC 2047 encoding where + * necessary. + * + * @public + * @param {String} text The text to add to the output. + */ +HeaderEmitter.prototype.addUnstructured = function (text) { + if (text.length == 0) + return; + + // Unstructured text is basically a phrase that can't be quoted. So, if we + // have nothing in qchars, nothing should be quoted. + this.addPhrase(text, "", false); +}; + +/** RFC 822 labels for days of the week. */ +var kDaysOfWeek = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +/** + * Formatting helper to output numbers between 0-9 as 00-09 instead. + */ +function padTo2Digits(num) { + return num < 10 ? "0" + num : num.toString(); +} + +/** + * Add a date/time field to the output, using the JS date object as the time + * representation. The value will be output using the timezone offset of the + * date object, which is usually the timezone of the user (modulo timezone and + * DST changes). + * + * Note that if the date is an invalid date (its internal date parameter is a + * NaN value), this method throws an error instead of generating an invalid + * string. + * + * @public + * @param {Date} date The date to be added to the output string. + */ +HeaderEmitter.prototype.addDate = function (date) { + // Rather than make a header plastered with NaN values, throw an error on + // specific invalid dates. + if (isNaN(date.getTime())) + throw new Error("Cannot encode an invalid date"); + + // RFC 5322 says years can't be before 1900. The after 9999 is a bit that + // derives from the specification saying that years have 4 digits. + if (date.getFullYear() < 1900 || date.getFullYear() > 9999) + throw new Error("Date year is out of encodable range"); + + // Start by computing the timezone offset for a day. We lack a good format, so + // the the 0-padding is done by hand. Note that the tzoffset we output is in + // the form ±hhmm, so we need to separate the offset (in minutes) into an hour + // and minute pair. + let tzOffset = date.getTimezoneOffset(); + let tzOffHours = Math.abs(Math.trunc(tzOffset / 60)); + let tzOffMinutes = Math.abs(tzOffset) % 60; + let tzOffsetStr = (tzOffset > 0 ? "-" : "+") + + padTo2Digits(tzOffHours) + padTo2Digits(tzOffMinutes); + + // Convert the day-time figure into a single value to avoid unwanted line + // breaks in the middle. + let dayTime = [ + kDaysOfWeek[date.getDay()] + ",", + date.getDate(), + mimeutils.kMonthNames[date.getMonth()], + date.getFullYear(), + padTo2Digits(date.getHours()) + ":" + + padTo2Digits(date.getMinutes()) + ":" + + padTo2Digits(date.getSeconds()), + tzOffsetStr + ].join(" "); + this.addText(dayTime, false); +}; + +/** + * Signal that the current header has been finished encoding. + * + * @public + * @param {Boolean} deliverEOF If true, signal to the handler that no more text + * will be arriving. + */ +HeaderEmitter.prototype.finish = function (deliverEOF) { + this._commitLine(); + if (deliverEOF) + this._handler.deliverEOF(); +}; + +/** + * Make a streaming header emitter that outputs on the given handler. + * + * @param {StreamHandler} handler The handler to consume output + * @param options Options to pass into the HeaderEmitter + * constructor. + * @returns {HeaderEmitter} A header emitter constructed with the given options. + */ +function makeStreamingEmitter(handler, options) { + return new HeaderEmitter(handler, options); +} + +function StringHandler() { + this.value = ""; + this.deliverData = function (str) { this.value += str; }; + this.deliverEOF = function () { }; +} + +/** + * Given a header name and its structured value, output a string containing its + * MIME-encoded value. The trailing CRLF for the header is included. + * + * @param {String} name The name of the structured header. + * @param value The value of the structured header. + * @param options Options for the HeaderEmitter constructor. + * @returns {String} A MIME-encoded representation of the structured header. + * @see HeaderEmitter.addStructuredHeader + */ +function emitStructuredHeader(name, value, options) { + let handler = new StringHandler(); + let emitter = new HeaderEmitter(handler, options); + emitter.addStructuredHeader(name, value); + emitter.finish(true); + return handler.value; +} + +/** + * Given a map of header names and their structured values, output a string + * containing all of their headers and their MIME-encoded values. + * + * This method is designed to be able to emit header values given the headerData + * values produced by MIME parsing. Thus, the values of the map are arrays + * corresponding to header multiplicity. + * + * @param {Map(String->Object[])} headerValues A map of header names to arrays + * of their structured values. + * @param options Options for the HeaderEmitter + * constructor. + * @returns {String} A MIME-encoded representation of the structured header. + * @see HeaderEmitter.addStructuredHeader + */ +function emitStructuredHeaders(headerValues, options) { + let handler = new StringHandler(); + let emitter = new HeaderEmitter(handler, options); + for (let instance of headerValues) { + instance[1].forEach(function (e) { + emitter.addStructuredHeader(instance[0], e) + }); + } + emitter.finish(true); + return handler.value; +} + +/** + * Add a custom structured MIME encoder to the set of known encoders. These + * encoders are used for {@link emitStructuredHeader} and similar functions to + * encode richer, more structured values instead of relying on string + * representations everywhere. + * + * Structured encoders are functions which take in a single parameter + * representing their structured value. The this parameter is set to be an + * instance of {@link HeaderEmitter}, and it is intended that the several public + * or protected methods on that class are useful for encoding values. + * + * There is a large set of structured encoders built-in to the jsmime library + * already. + * + * @param {String} header The header name (in its preferred case) for + * which the encoder will be used. + * @param {Function(Value)} encoder The structured encoder function. + */ +function addStructuredEncoder(header, encoder) { + let lowerName = header.toLowerCase(); + encoders.set(lowerName, encoder); + if (!preferredSpellings.has(lowerName)) + preferredSpellings.set(lowerName, header); +} + +return Object.freeze({ + addStructuredEncoder: addStructuredEncoder, + emitStructuredHeader: emitStructuredHeader, + emitStructuredHeaders: emitStructuredHeaders, + makeStreamingEmitter: makeStreamingEmitter +}); + +}); + +def('jsmime', function(require) { + return { + MimeParser: require('./mimeparser'), + headerparser: require('./headerparser'), + headeremitter: require('./headeremitter') + } +}); + return mods['jsmime']; +})); diff --git a/mailnews/mime/moz.build b/mailnews/mime/moz.build new file mode 100644 index 000000000..0a7865a3d --- /dev/null +++ b/mailnews/mime/moz.build @@ -0,0 +1,15 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += [ + 'public', + 'src', + 'emitters', + 'cthandlers', +] + +EXTRA_JS_MODULES.jsmime += [ + 'jsmime/jsmime.js', +] diff --git a/mailnews/mime/public/MimeEncoder.h b/mailnews/mime/public/MimeEncoder.h new file mode 100644 index 000000000..7883af70e --- /dev/null +++ b/mailnews/mime/public/MimeEncoder.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MimeEncoder_h__ +#define MimeEncoder_h__ + +#include "nscore.h" + +namespace mozilla { +namespace mailnews { + +/// A class for encoding the bodies of MIME parts. +class MimeEncoder { +public: + virtual ~MimeEncoder() {} + + /// A callback for writing the encoded output + typedef nsresult (*OutputCallback) + (const char *buf, int32_t size, void *closure); + + /// Encodes the string in the buffer and sends it to the callback + virtual nsresult Write(const char *buffer, int32_t size) = 0; + /// Flush all pending data when no more data exists + virtual nsresult Flush() { return NS_OK; } + + /// Get an encoder that outputs Base64-encoded data + static MimeEncoder *GetBase64Encoder(OutputCallback callback, void *closure); + /// Get an encoder that outputs quoted-printable data + static MimeEncoder *GetQPEncoder(OutputCallback callback, void *closure); + +protected: + MimeEncoder(OutputCallback callback, void *closure); + OutputCallback mCallback; + void *mClosure; + uint32_t mCurrentColumn; +}; + +} // namespace mailnews +} // namespace mozilla + +#endif diff --git a/mailnews/mime/public/MimeHeaderParser.h b/mailnews/mime/public/MimeHeaderParser.h new file mode 100644 index 000000000..429e759b1 --- /dev/null +++ b/mailnews/mime/public/MimeHeaderParser.h @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MimeHeaderParser_h__ +#define MimeHeaderParser_h__ + +#include "nsCOMArray.h" +#include "nsStringGlue.h" +#include "nsTArray.h" + +class msgIAddressObject; + +namespace mozilla { +namespace mailnews { + +/** + * This is used to signal that the input header value has already been decoded + * according to RFC 2047 and is in UTF-16 form. + */ +nsCOMArray DecodedHeader(const nsAString &aHeader); + +/** + * This is used to signal that the input header value needs to be decoded + * according to RFC 2047. The charset parameter indicates the charset to assume + * that non-ASCII data is in; if the value is null (the default), then the + * charset is assumed to be UTF-8. + */ +nsCOMArray EncodedHeader(const nsACString &aHeader, + const char *aCharset = nullptr); + +namespace detail { +void DoConversion(const nsTArray &aUTF16, nsTArray &aUTF8); +}; +/** + * This is a class designed for use as temporaries so that methods can pass + * an nsTArray into methods that expect nsTArray for out + * parameters (this does not work for in-parameters). + * + * It works by internally providing an nsTArray which it uses for its + * external API operations. If the user requests an array of nsCString elements + * instead, it converts the UTF-16 array to a UTF-8 array on destruction. + */ +template +class UTF16ArrayAdapter +{ +public: + UTF16ArrayAdapter(nsTArray &aUTF8Array) + : mUTF8Array(aUTF8Array) {} + ~UTF16ArrayAdapter() { detail::DoConversion(mUTF16Array, mUTF8Array); } + operator nsTArray&() { return mUTF16Array; } +private: + nsTArray &mUTF8Array; + AutoTArray mUTF16Array; +}; + +/** + * Given a name and an email, both encoded in UTF-8, produce a string suitable + * for writing in an email header by quoting where necessary. + * + * If name is not empty, the output string will be name . If it is empty, + * the output string is just the email. Note that this DOES NOT do any RFC 2047 + * encoding. + */ +void MakeMimeAddress(const nsACString &aName, const nsACString &aEmail, + nsACString &full); + +/** + * Given a name and an email, produce a string suitable for writing in an email + * header by quoting where necessary. + * + * If name is not empty, the output string will be name . If it is empty, + * the output string is just the email. Note that this DOES NOT do any RFC 2047 + * encoding. + */ +void MakeMimeAddress(const nsAString &aName, const nsAString &aEmail, + nsAString &full); + +/** + * Given a name and an email, both encoded in UTF-8, produce a string suitable + * for displaying in UI. + * + * If name is not empty, the output string will be name . If it is empty, + * the output string is just the email. + */ +void MakeDisplayAddress(const nsAString &aName, const nsAString &aEmail, + nsAString &full); + +/** + * Returns a copy of the input which may have had some addresses removed. + * Addresses are removed if they are already in either of the supplied + * address lists. + * + * Addresses are considered to be the same if they contain the same email + * address parts, ignoring case. Display names or comments are not compared. + * + * @param aHeader The addresses to remove duplicates from. + * @param aOtherEmails Other addresses that the duplicate removal process also + * checks for duplicates against. Addresses in this list + * will not be added to the result. + * @return The original header with duplicate addresses removed. + */ +void RemoveDuplicateAddresses(const nsACString &aHeader, + const nsACString &aOtherEmails, + nsACString &result); + +/** + * Given a message header, extract all names and email addresses found in that + * header into the two arrays. + */ +void ExtractAllAddresses(const nsCOMArray &aHeader, + nsTArray &names, nsTArray &emails); + +/** + * Given a raw message header value, extract display names for every address + * found in the header. + */ +void ExtractDisplayAddresses(const nsCOMArray &aHeader, + nsTArray &addresses); + +/** + * Given a raw message header value, extract all the email addresses into an + * array. + * + * Duplicate email addresses are not removed from the output list. + */ +void ExtractEmails(const nsCOMArray &aHeader, + nsTArray &emails); + +/** + * Given a raw message header value, extract the first name/email address found + * in the header. This is essentially equivalent to grabbing the first entry of + * ExtractAllAddresses. + */ +void ExtractFirstAddress(const nsCOMArray &aHeader, + nsACString &name, nsACString &email); + +/** + * Given an RFC 2047-decoded message header value, extract the first name/email + * address found in the header. This is essentially equivalent to grabbing the + * first entry of ExtractAllAddresses. + */ +void ExtractFirstAddress(const nsCOMArray &aHeader, + nsAString &name, nsACString &email); + +/** + * Given a raw message header value, extract the first email address found in + * the header. + */ +void ExtractEmail(const nsCOMArray &aHeader, + nsACString &email); + +/** + * Given a raw message header value, extract and clean up the first display + * name found in the header. If there is no display name, the email address is + * used instead. + */ +void ExtractName(const nsCOMArray &aHeader, + nsACString &name); + +/** + * Given an RFC 2047-decoded message header value, extract the first display + * name found in the header. If there is no display name, the email address is + * returned instead. + */ +void ExtractName(const nsCOMArray &aDecodedHeader, + nsAString &name); + +} // namespace mailnews +} // namespace mozilla + +#endif diff --git a/mailnews/mime/public/moz.build b/mailnews/mime/public/moz.build new file mode 100644 index 000000000..58243068e --- /dev/null +++ b/mailnews/mime/public/moz.build @@ -0,0 +1,36 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'msgIStructuredHeaders.idl', + 'nsICMSDecoder.idl', + 'nsICMSEncoder.idl', + 'nsICMSMessage.idl', + 'nsICMSMessage2.idl', + 'nsICMSMessageErrors.idl', + 'nsICMSSecureMessage.idl', + 'nsIMimeConverter.idl', + 'nsIMimeEmitter.idl', + 'nsIMimeHeaders.idl', + 'nsIMimeMiscStatus.idl', + 'nsIMimeStreamConverter.idl', + 'nsIMsgHeaderParser.idl', + 'nsIPgpMimeProxy.idl', + 'nsISimpleMimeConverter.idl', +] + +XPIDL_MODULE = 'mime' + +EXPORTS += [ + 'nsIMimeContentTypeHandler.h', + 'nsIMimeObjectClassAccess.h', + 'nsMailHeaders.h', + 'nsMsgMimeCID.h', +] + +EXPORTS.mozilla.mailnews += [ + 'MimeEncoder.h', + 'MimeHeaderParser.h', +] diff --git a/mailnews/mime/public/msgIStructuredHeaders.idl b/mailnews/mime/public/msgIStructuredHeaders.idl new file mode 100644 index 000000000..9d3b3aa9a --- /dev/null +++ b/mailnews/mime/public/msgIStructuredHeaders.idl @@ -0,0 +1,209 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +#include "nsCOMArray.h" + +#define NS_ISTRUCTUREDHEADERS_CONTRACTID \ + "@mozilla.org/messenger/structuredheaders;1" +%} + +interface msgIAddressObject; +interface nsIUTF8StringEnumerator; + +/** + * A collection of MIME headers that are stored in a rich, structured format. + * + * The structured forms defined in this method use the structured decoder and + * encoder functionality found in jsmime to interconvert between the raw string + * forms found in the actual message text and the structured forms supported by + * this interface. Extensions can register their own custom structured headers + * by listing the source URL of their code under the category + * "custom-mime-encoder". + * + * The alternative modes of access for specific headers are expected to only + * work for the headers for which that mode of access is the correct one. For + * example, retrieving the "To" header from getUnstructuredHeader would fail, + * since the To header is not an unstructured header but an addressing header. + * They are provided mostly as a convenience to C++ which is much less able to + * utilize a fully generic format. + * + * With the exception of mismatched headers, the methods do not throw an + * exception if the header is missing but rather return an appropriate default + * value as indicated in their documentation. + */ +[scriptable, uuid(e109bf4f-788f-47ba-bfa8-1236ede05597)] +interface msgIStructuredHeaders : nsISupports { + /** + * Retrieve the value of the header stored in this set of headers. If the + * header is not present, then undefined is returned. + * + * @param aHeaderName The name of the header to retrieve. + */ + jsval getHeader(in string aHeaderName); + + /** + * Return true if and only if the given header is already set. + * + * @param aHeaderName The name of the header to retrieve. + */ + bool hasHeader(in string aHeaderName); + + /** + * Retrieve the value of the header as if it is an unstructured header. Such + * headers include most notably the Subject header. If the header is not + * present, then null is returned. This is reflected in C++ as an empty string + * with IsVoid() set to true (distinguishing it from a header that is present + * but contains an empty string). + * + * @param aHeaderName The name of the header to retrieve. + */ + AString getUnstructuredHeader(in string aHeaderName); + + /** + * Retrieve the value of the header if it is an addressing header, such as the + * From or To headers. If the header is not present, then an empty array is + * returned. + * + * @param aHeaderName The name of the header to retrieve. + * @param aPreserveGroups If false (the default), then the result is a flat + * list of addresses, with all group annotations + * removed. + * If true, then some address objects may represent + * groups in the header, preserving the original header + * structure. + */ + void getAddressingHeader(in string aHeaderName, + [optional] in boolean aPreserveGroups, [optional] out unsigned long count, + [array, size_is(count), retval] out msgIAddressObject addresses); + + /** + * Retrieve a raw version of the header value as would be represented in MIME. + * This form does not include the header name and colon, trailing whitespace, + * nor embedded CRLF pairs in the case of very long header names. + * + * @param aHeaderName The name of the header to retrieve. + */ + AUTF8String getRawHeader(in string aHeaderName); + + /** + * Retrieve an enumerator of the names of all headers in this set of headers. + * The header names returned may be in different cases depending on the + * precise implementation of this interface, so implementations should not + * rely on an exact kind of case being returned. + */ + readonly attribute nsIUTF8StringEnumerator headerNames; + + /** + * Retrieve the MIME representation of all of the headers. + * + * The header values are emitted in an ASCII form, unless internationalized + * email addresses are involved. The extra CRLF indicating the end of headers + * is not included in this representation. + * + * This accessor is provided mainly for the benefit of C++ consumers of this + * interface, since the JSMime headeremitter functionality allows more + * fine-grained customization of the results. + */ + AUTF8String buildMimeText(); + +%{C++ + /** + * A special variant of getAddressingHeader that is specialized better for C++ + * users of this API. + */ + nsresult GetAddressingHeader(const char *aPropertyName, + nsCOMArray &addrs, + bool aPreserveGroups = false) + { + msgIAddressObject **addrPtr; + uint32_t length; + nsresult rv = GetAddressingHeader(aPropertyName, aPreserveGroups, &length, + &addrPtr); + NS_ENSURE_SUCCESS(rv, rv); + addrs.Adopt(addrPtr, length); + return NS_OK; + } +%} + +}; + +/** + * An interface that enhances msgIStructuredHeaders by allowing the values of + * headers to be modified. + */ +[scriptable, uuid(5dcbbef6-2356-45d8-86d7-b3e73f9c9a0c)] +interface msgIWritableStructuredHeaders : msgIStructuredHeaders { + /** + * Store the given value for the given header, overwriting any previous value + * that was stored for said header. + * + * @param aHeaderName The name of the header to store. + * @param aValue The rich, structured value of the header to store. + */ + void setHeader(in string aHeaderName, in jsval aValue); + + /** + * Forget any previous value that was stored for the given header. + * + * @param aHeaderName The name of the header to delete. + */ + void deleteHeader(in string aHeaderName); + + /** + * Copy all of the structured values from another set of structured headers to + * the current one, overwriting any values that may have been specified + * locally. Note that the copy is a shallow copy of the value. + * + * @param aOtherHeaders A set of header values to be copied. + */ + void addAllHeaders(in msgIStructuredHeaders aOtherHeaders); + + /** + * Set the value of the header as if it were an unstructured header. Such + * headers include most notably the Subject header. + * + * @param aHeaderName The name of the header to store. + * @param aValue The value to store. + */ + void setUnstructuredHeader(in string aHeaderName, in AString aValue); + + /** + * Set the value of the header as if it were an addressing header, such as the + * From or To headers. + * + * @param aHeaderName The name of the header to store. + */ + void setAddressingHeader(in string aHeaderName, + [array, size_is(aCount)] in msgIAddressObject aAddresses, + in unsigned long aCount); + + /** + * Store the value of the header using a raw version as would be represented + * in MIME. So as to handle 8-bit headers properly, the charset needs to be + * specified, although it may be null. + * + * @param aHeaderName The name of the header to store. + * @param aValue The raw MIME header value to store. + * @param aCharset The expected charset of aValue. + */ + void setRawHeader(in string aHeaderName, in ACString aValue, + in string aCharset); + +%{C++ + /** + * A special variant of setAddressingHeader that is specialized better for C++ + * users of this API. + */ + nsresult SetAddressingHeader(const char *aPropertyName, + const nsCOMArray &addrs) + { + return SetAddressingHeader(aPropertyName, + const_cast&>(addrs).Elements(), + addrs.Length()); + } +%} +}; diff --git a/mailnews/mime/public/nsICMSDecoder.idl b/mailnews/mime/public/nsICMSDecoder.idl new file mode 100644 index 000000000..f67f1e640 --- /dev/null +++ b/mailnews/mime/public/nsICMSDecoder.idl @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{ C++ +typedef void (*NSSCMSContentCallback)(void *arg, const char *buf, unsigned long len); + +#define NS_CMSDECODER_CONTRACTID "@mozilla.org/nsCMSDecoder;1" +%} + +native NSSCMSContentCallback(NSSCMSContentCallback); + +interface nsICMSMessage; + +/** + * nsICMSDecoder + * Interface to decode an CMS message + */ +[uuid(c7c7033b-f341-4065-aadd-7eef55ce0dda)] +interface nsICMSDecoder : nsISupports +{ + void start(in NSSCMSContentCallback cb, in voidPtr arg); + void update(in string aBuf, in long aLen); + void finish(out nsICMSMessage msg); +}; + diff --git a/mailnews/mime/public/nsICMSEncoder.idl b/mailnews/mime/public/nsICMSEncoder.idl new file mode 100644 index 000000000..a82f5e0de --- /dev/null +++ b/mailnews/mime/public/nsICMSEncoder.idl @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{ C++ +typedef void (*NSSCMSContentCallback)(void *arg, const char *buf, unsigned long len); + +#define NS_CMSENCODER_CONTRACTID "@mozilla.org/nsCMSEncoder;1" +%} + +native NSSCMSContentCallback(NSSCMSContentCallback); + +interface nsICMSMessage; + +/** + * nsICMSEncoder + * Interface to Encode an CMS message + */ +[uuid(17dc4fb4-e379-4e56-a4a4-57cdcc74816f)] +interface nsICMSEncoder : nsISupports +{ + void start(in nsICMSMessage aMsg, in NSSCMSContentCallback cb, in voidPtr arg); + void update(in string aBuf, in long aLen); + void finish(); + void encode(in nsICMSMessage aMsg); +}; + diff --git a/mailnews/mime/public/nsICMSMessage.idl b/mailnews/mime/public/nsICMSMessage.idl new file mode 100644 index 000000000..1d67bf51d --- /dev/null +++ b/mailnews/mime/public/nsICMSMessage.idl @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{ C++ +#define NS_CMSMESSAGE_CONTRACTID "@mozilla.org/nsCMSMessage;1" +%} + +[ptr] native UnsignedCharPtr(unsigned char); + +interface nsIX509Cert; +interface nsIArray; + +/** + * nsICMSMessage + * Interface to a CMS Message + */ +[uuid(c6d51c22-73e9-4dad-86b9-bde584e33c63)] +interface nsICMSMessage : nsISupports +{ + void contentIsSigned(out boolean aSigned); + void contentIsEncrypted(out boolean aEncrypted); + void getSignerCommonName(out string aName); + void getSignerEmailAddress(out string aEmail); + void getSignerCert(out nsIX509Cert scert); + void getEncryptionCert(out nsIX509Cert ecert); + void verifySignature(); + void verifyDetachedSignature(in UnsignedCharPtr aDigestData, in unsigned long aDigestDataLen); + void CreateEncrypted(in nsIArray aRecipientCerts); + + /* The parameter aDigestType must be one of the values in nsICryptoHash */ + void CreateSigned(in nsIX509Cert scert, in nsIX509Cert ecert, + in UnsignedCharPtr aDigestData, + in unsigned long aDigestDataLen, in int16_t aDigestType); +}; + diff --git a/mailnews/mime/public/nsICMSMessage2.idl b/mailnews/mime/public/nsICMSMessage2.idl new file mode 100644 index 000000000..9360279c6 --- /dev/null +++ b/mailnews/mime/public/nsICMSMessage2.idl @@ -0,0 +1,64 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsISMimeVerificationListener; + +[ptr] native UnsignedCharPtr(unsigned char); + +/* + * This interface is currently not marked scriptable, + * because its verification functions are meant to look like those + * in nsICMSMessage. At the time the ptr type is eliminated in both + * interfaces, both should be made scriptable. + */ + +[uuid(b21a3636-2287-4b9f-9a22-25f245981ef0)] +interface nsICMSMessage2 : nsISupports +{ + /** + * Async version of nsICMSMessage::VerifySignature. + * Code will be executed on a background thread and + * availability of results will be notified using a + * call to nsISMimeVerificationListener. + */ + void asyncVerifySignature(in nsISMimeVerificationListener listener); + + /** + * Async version of nsICMSMessage::VerifyDetachedSignature. + * Code will be executed on a background thread and + * availability of results will be notified using a + * call to nsISMimeVerificationListener. + * + * We are using "native unsigned char" ptr, because the function + * signatures of this one and nsICMSMessage::verifyDetachedSignature + * should be the identical. Cleaning up nsICMSMessages needs to be + * postponed, because this async version is needed on MOZILLA_1_8_BRANCH. + * + * Once both interfaces get cleaned up, the function signature should + * look like: + * [array, length_is(aDigestDataLen)] + * in octet aDigestData, + * in unsigned long aDigestDataLen); + */ + void asyncVerifyDetachedSignature(in nsISMimeVerificationListener listener, + in UnsignedCharPtr aDigestData, + in unsigned long aDigestDataLen); +}; + +[uuid(5226d698-0773-4f25-b94c-7944b3fc01d3)] +interface nsISMimeVerificationListener : nsISupports { + + /** + * Notify that results are ready, that have been requested + * using nsICMSMessage2::asyncVerify[Detached]Signature() + * + * verificationResultCode matches synchronous result code from + * nsICMSMessage::verify[Detached]Signature + */ + void notify(in nsICMSMessage2 verifiedMessage, + in nsresult verificationResultCode); +}; + diff --git a/mailnews/mime/public/nsICMSMessageErrors.idl b/mailnews/mime/public/nsICMSMessageErrors.idl new file mode 100644 index 000000000..d429cc671 --- /dev/null +++ b/mailnews/mime/public/nsICMSMessageErrors.idl @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * nsICMSMessageErrors + * Scriptable error constants for nsICMSMessage + */ +[scriptable,uuid(267f1a5b-88f7-413b-bc49-487e745282f1)] +interface nsICMSMessageErrors : nsISupports +{ + const long SUCCESS = 0; + const long GENERAL_ERROR = 1; + const long VERIFY_NOT_SIGNED = 1024; + const long VERIFY_NO_CONTENT_INFO = 1025; + const long VERIFY_BAD_DIGEST = 1026; + const long VERIFY_NOCERT = 1028; + const long VERIFY_UNTRUSTED = 1029; + const long VERIFY_ERROR_UNVERIFIED = 1031; + const long VERIFY_ERROR_PROCESSING = 1032; + const long VERIFY_BAD_SIGNATURE = 1033; + const long VERIFY_DIGEST_MISMATCH = 1034; + const long VERIFY_UNKNOWN_ALGO = 1035; + const long VERIFY_UNSUPPORTED_ALGO = 1036; + const long VERIFY_MALFORMED_SIGNATURE = 1037; + const long VERIFY_HEADER_MISMATCH = 1038; + const long VERIFY_NOT_YET_ATTEMPTED = 1039; + const long VERIFY_CERT_WITHOUT_ADDRESS = 1040; + + const long ENCRYPT_NO_BULK_ALG = 1056; + const long ENCRYPT_INCOMPLETE = 1057; +}; diff --git a/mailnews/mime/public/nsICMSSecureMessage.idl b/mailnews/mime/public/nsICMSSecureMessage.idl new file mode 100644 index 000000000..6442e319a --- /dev/null +++ b/mailnews/mime/public/nsICMSSecureMessage.idl @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIX509Cert; + +/** + * nsICMSManager (service) + * Interface to access users certificate store + */ +[scriptable, uuid(17103436-0111-4819-a751-0fc4aa6e3d79)] +interface nsICMSSecureMessage : nsISupports +{ + /** + * getCertByPrefID - a BASE64 string representing a user's + * certificate (or NULL if there isn't one) + */ + string getCertByPrefID(in string certID); + + /** + * decodeCert - decode a BASE64 string into an X509Certificate object + */ + nsIX509Cert decodeCert(in string value); + + /** + * sendMessage - send a text message to the recipient indicated + * by the base64-encoded cert. + */ + string sendMessage(in string msg, in string cert); + + /** + * receiveMessage - receive an encrypted (enveloped) message + */ + string receiveMessage(in string msg); +}; + +%{C++ +#define NS_CMSSECUREMESSAGE_CONTRACTID "@mozilla.org/nsCMSSecureMessage;1" +%} diff --git a/mailnews/mime/public/nsIMimeContentTypeHandler.h b/mailnews/mime/public/nsIMimeContentTypeHandler.h new file mode 100644 index 000000000..f1b828673 --- /dev/null +++ b/mailnews/mime/public/nsIMimeContentTypeHandler.h @@ -0,0 +1,65 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This interface is implemented by content type handlers that will be + * called upon by libmime to process various attachments types. The primary + * purpose of these handlers will be to represent the attached data in a + * viewable HTML format that is useful for the user + * + * Note: These will all register by their content type prefixed by the + * following: mimecth:text/vcard + * + * libmime will then use the XPCOM Component Manager to + * locate the appropriate Content Type handler + */ +#ifndef nsIMimeContentTypeHandler_h_ +#define nsIMimeContentTypeHandler_h_ + +typedef struct { + bool force_inline_display; +} contentTypeHandlerInitStruct; + +#include "nsISupports.h" +#include "mimecth.h" + +// {20DABD99-F8B5-11d2-8EE0-00A024A7D144} +#define NS_IMIME_CONTENT_TYPE_HANDLER_IID \ + { 0x20dabd99, 0xf8b5, 0x11d2, \ + { 0x8e, 0xe0, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } } + +// {20DABDA1-F8B5-11d2-8EE0-00A024A7D144} +#define NS_VCARD_CONTENT_TYPE_HANDLER_CID \ + { 0x20dabda1, 0xf8b5, 0x11d2, \ + { 0x8e, 0xe0, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } } + +#define NS_SMIME_CONTENT_TYPE_HANDLER_CID \ + { 0x20dabdac, 0xf8b5, 0x11d2, \ + { 0xFF, 0xe0, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } } + +#define NS_SIGNED_CONTENT_TYPE_HANDLER_CID \ + { 0x20dabdac, 0xf8b5, 0x11d2, \ + { 0xFF, 0xe0, 0x0, 0xaf, 0x19, 0xa7, 0xd1, 0x44 } } + +#define NS_PGPMIME_CONTENT_TYPE_HANDLER_CID \ + { 0x212f415f, 0xf8b5, 0x11d2, \ + { 0xFF, 0xe0, 0x0, 0xaf, 0x19, 0xa7, 0xd1, 0x44 } } + + +class nsIMimeContentTypeHandler : public nsISupports { +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMIME_CONTENT_TYPE_HANDLER_IID) + + NS_IMETHOD GetContentType(char **contentType) = 0; + + NS_IMETHOD CreateContentTypeHandlerClass(const char *content_type, + contentTypeHandlerInitStruct *initStruct, + MimeObjectClass **objClass) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIMimeContentTypeHandler, + NS_IMIME_CONTENT_TYPE_HANDLER_IID) + +#endif /* nsIMimeContentTypeHandler_h_ */ diff --git a/mailnews/mime/public/nsIMimeConverter.idl b/mailnews/mime/public/nsIMimeConverter.idl new file mode 100644 index 000000000..05aae7662 --- /dev/null +++ b/mailnews/mime/public/nsIMimeConverter.idl @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * Encode/decode mail headers (via libmime). + */ +[scriptable, uuid(0d3f5531-2dbe-40d3-9280-f6ac45a6f5e0)] +interface nsIMimeConverter : nsISupports { + /** + * Suggested byte length limit for use when calling encodeMimePartIIStr_UTF8. + */ + const long MIME_ENCODED_WORD_SIZE = 72; + const long MAX_CHARSET_NAME_LENGTH = 64; + + /** + * Encode a UTF-8 string into a form containing only ASCII characters using + * RFC 2047 encoded words where necessary. + * + * Note that, although allowed for the present time, encoding to charsets + * other than UTF-8 is considered deprecated. + * + * @param aHeader UTF-8 header to encode. + * @param aAddressingHeader Is the header a list of email addresses? + * @param aMailCharset Charset to use when encoding (see above for note). + * @param aFieldNameLen Header field name length (ex: "From: " = 6) + * @param aMaxLineLen Maximum length of an individual line. Use + * MIME_ENCODED_WORD_SIZE for best results. + * + * @return The encoded header. + */ + AUTF8String encodeMimePartIIStr_UTF8(in AUTF8String aHeader, + in boolean aAddressingHeader, + in string aMailCharset, + in long aFieldNameLen, + in long aMaxLineLen); + + /** + * Decode a MIME header to UTF-8 if conversion is required. Marked as + * noscript because the return value may contain non-ASCII characters. + * + * @param header A (possibly encoded) header to decode. + * @param default_charset The charset to apply to un-labeled non-UTF-8 data. + * @param override_charset If true, default_charset is used instead of any + * charset labeling other than UTF-8. + * @param eatContinuations If true, unfold headers. + * + * @return UTF-8 encoded value if conversion was required, nullptr if no + * conversion was required. + */ + AUTF8String decodeMimeHeaderToUTF8(in ACString header, + in string default_charset, + in boolean override_charset, + in boolean eatContinuations); + + /** + * Decode a MIME header to UTF-16. + * + * @param header A (possibly encoded) header to decode. + * @param default_charset The charset to apply to un-labeled non-UTF-8 data. + * @param override_charset If true, default_charset is used instead of any + * charset labeling other than UTF-8. + * @param eatContinuations If true, unfold headers. + * + * @return UTF-16 encoded value as an AString. + */ + AString decodeMimeHeader(in string header, + in string default_charset, + in boolean override_charset, + in boolean eatContinuations); +}; + diff --git a/mailnews/mime/public/nsIMimeEmitter.idl b/mailnews/mime/public/nsIMimeEmitter.idl new file mode 100644 index 000000000..b129b4e86 --- /dev/null +++ b/mailnews/mime/public/nsIMimeEmitter.idl @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsISupports.idl" +#include "nsrootidl.idl" + +interface nsIOutputStream; +interface nsIInputStream; +interface nsIURI; +interface nsIStreamListener; +interface nsIChannel; + +[scriptable, uuid(eb9beb09-44de-4ad2-a560-f572b1afd534)] +interface nsMimeHeaderDisplayTypes +{ + const long MicroHeaders = 0; + const long NormalHeaders = 1; + const long AllHeaders = 2; +}; + +%{C++ +#define NS_IMIME_MISC_STATUS_KEY "@mozilla.org/MimeMiscStatus;1?type=" +%} + +[scriptable, uuid(7a57166f-2891-4122-9a74-6c3fab0caac3)] +interface nsIMimeEmitter : nsISupports { + + // Output listener to allow access to it from mime. + attribute nsIStreamListener outputListener; + + // These will be called to start and stop the total operation. + void initialize(in nsIURI url, in nsIChannel aChannel, in long aFormat); + void complete(); + + // Set the output stream/listener for processed data. + void setPipe(in nsIInputStream inputStream, in nsIOutputStream outStream); + + // Header handling routines. + void startHeader(in boolean rootMailHeader, in boolean headerOnly, + [const] in string msgID, [const] in string outCharset); + void addHeaderField([const] in string field, [const] in string value); + void addAllHeaders(in ACString allheaders); + + /** + * Write the HTML Headers for the current attachment. + * Note: Book case this with an EndHeader call. + * + * @param name The name of this attachment. + */ + void writeHTMLHeaders([const] in AUTF8String name); + + /** + * Finish writing the headers for the current attachment. + * + * @param name The name of this attachment. + */ + void endHeader([const] in AUTF8String name); + + void updateCharacterSet([const] in string aCharset); + + // Attachment handling routines. + void startAttachment([const] in AUTF8String name, + [const] in string contentType, + [const] in string url, in boolean aNotDownloaded); + void addAttachmentField([const] in string field, [const] in string value); + void endAttachment(); + + void endAllAttachments(); + + // Body handling routines. + void startBody(in boolean bodyOnly, [const] in string msgID, [const] in string outCharset); + void writeBody([const] in AUTF8String buf, out uint32_t amountWritten); + void endBody(); + + // Generic write routine. This is necessary for output that + // libmime needs to pass through without any particular parsing + // involved (i.e. decoded images, HTML Body Text, etc... + void write([const] in ACString buf, out uint32_t amountWritten); + void utilityWrite([const] in string buf); +}; diff --git a/mailnews/mime/public/nsIMimeHeaders.idl b/mailnews/mime/public/nsIMimeHeaders.idl new file mode 100644 index 000000000..3a0d05c49 --- /dev/null +++ b/mailnews/mime/public/nsIMimeHeaders.idl @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgIStructuredHeaders.idl" + +%{C++ +#define NS_IMIMEHEADERS_CONTRACTID \ + "@mozilla.org/messenger/mimeheaders;1" +%} + +/** + * An interface that can extract individual headers from a body of headers. + */ +[scriptable, uuid(a9222679-b991-4786-8314-f8819c3a2ba3)] +interface nsIMimeHeaders : msgIStructuredHeaders { + /// Feed in the text of headers + void initialize(in ACString allHeaders); + + /** + * Get the text of a header. + * + * Leading and trailing whitespace from headers will be stripped from the + * return value. If getAllOfThem is set to true, then the returned string will + * have all of the values of the header, in order, joined with the ',\r\n\t'. + * + * If the header is not present, then the returned value is NULL. + */ + ACString extractHeader(in string headerName, in boolean getAllOfThem); + + /** + * The current text of all header data. + * + * Unlike the asMimeText property, this result preserves the original + * representation of the header text, including alternative line endings or + * custom, non-8-bit text. For instances of this interface, this attribute is + * usually preferable to asMimeText. + */ + readonly attribute ACString allHeaders; +}; diff --git a/mailnews/mime/public/nsIMimeMiscStatus.idl b/mailnews/mime/public/nsIMimeMiscStatus.idl new file mode 100644 index 000000000..3621cdb85 --- /dev/null +++ b/mailnews/mime/public/nsIMimeMiscStatus.idl @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsISupports.idl" +#include "nsrootidl.idl" + +interface nsIChannel; +interface nsIMsgMailNewsUrl; +interface nsIUTF8StringEnumerator; +interface nsIMsgDBHdr; +interface nsIURI; +interface nsIWritablePropertyBag2; + +[scriptable, uuid(4644FB25-5255-11d3-82B8-444553540002)] +interface nsIMimeMiscStatus : nsISupports{ + + string GetWindowXULandJS(); + string GetGlobalXULandJS(); + string GetIndividualXUL(in string aName, in string aHeader, in string aEmail); + + long GetMiscStatus(in string aName, in string aEmail); + string GetImageURL(in long aStatus); +}; + +// this is a simple interface which allows someone to listen to all the headers +// that are discovered by mime. We can use this when displaying a message to update +// the msg header in JS. +[scriptable, uuid(e0e821f0-cecf-4cb3-be5b-ee58b6868343)] +interface nsIMsgHeaderSink : nsISupports +{ + // You must finish consuming the iterators before returning from processHeaders. aHeaderNames and aHeaderValues will ALWAYS have the same + // number of elements in them + void processHeaders(in nsIUTF8StringEnumerator aHeaderNames, in nsIUTF8StringEnumerator aHeaderValues, in boolean dontCollectAddress); + + void handleAttachment(in string contentType, in string url, in wstring displayName, in string uri, + in boolean aNotDownloaded); + + /** + * Add a metadata field to the current attachment, e.g. "X-Mozilla-PartSize". + * + * @param field The field to add + * @param value The value of the field + */ + void addAttachmentField(in string field, in string value); + void onEndAllAttachments(); + + // onEndMsgHeaders is called after libmime is done processing a message. At this point it is safe for + // elements like the UI to update junk status, process return receipts, etc. + void onEndMsgHeaders(in nsIMsgMailNewsUrl url); + + // onEndMsgDownload is triggered when layout says it is actually done rendering + // the message body in the UI. + void onEndMsgDownload(in nsIMsgMailNewsUrl url); + + attribute nsISupports securityInfo; + + /** + * onMsgHasRemoteContent is called each time content policy encounters remote + * content that it will block from loading. + * @param aMsgHdr header of the message the content is located in + * @param aContentURI location that will be blocked. + * @param aCanOverride can the blocking be overridden or not + */ + void onMsgHasRemoteContent(in nsIMsgDBHdr aMsgHdr, in nsIURI aContentURI, in boolean aCanOverride); + + readonly attribute nsIMsgDBHdr dummyMsgHeader; + + // used as a hook for extension mime content handlers to store data that can later + // be accessed by other parts of the code, e.g., UI code. + // TODO - Should replace securityInfo + readonly attribute nsIWritablePropertyBag2 properties; + // When streaming a new message, properties should be reset, so that there are + // not previous values lurking around. + void resetProperties(); +}; diff --git a/mailnews/mime/public/nsIMimeObjectClassAccess.h b/mailnews/mime/public/nsIMimeObjectClassAccess.h new file mode 100644 index 000000000..55d7a86ad --- /dev/null +++ b/mailnews/mime/public/nsIMimeObjectClassAccess.h @@ -0,0 +1,49 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This interface is implemented by libmime. This interface is used by + * a Content-Type handler "Plug In" (i.e. vCard) for accessing various + * internal information about the object class system of libmime. When + * libmime progresses to a C++ object class, this would probably change. + */ +#ifndef nsIMimeObjectClassAccess_h_ +#define nsIMimeObjectClassAccess_h_ + +// {C09EDB23-B7AF-11d2-B35E-525400E2D63A} +#define NS_IMIME_OBJECT_CLASS_ACCESS_IID \ + { 0xc09edb23, 0xb7af, 0x11d2, \ + { 0xb3, 0x5e, 0x52, 0x54, 0x0, 0xe2, 0xd6, 0x3a } } + +class nsIMimeObjectClassAccess : public nsISupports { +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMIME_OBJECT_CLASS_ACCESS_IID) + + // These methods are all implemented by libmime to be used by + // content type handler plugins for processing stream data. + + // This is the write call for outputting processed stream data. + NS_IMETHOD MimeObjectWrite(void *mimeObject, + char *data, + int32_t length, + bool user_visible_p) = 0; + + // The following group of calls expose the pointers for the object + // system within libmime. + NS_IMETHOD GetmimeInlineTextClass(void **ptr) = 0; + NS_IMETHOD GetmimeLeafClass(void **ptr) = 0; + NS_IMETHOD GetmimeObjectClass(void **ptr) = 0; + NS_IMETHOD GetmimeContainerClass(void **ptr) = 0; + NS_IMETHOD GetmimeMultipartClass(void **ptr) = 0; + NS_IMETHOD GetmimeMultipartSignedClass(void **ptr) = 0; + NS_IMETHOD GetmimeEncryptedClass(void **ptr) = 0; + + NS_IMETHOD MimeCreate(char* content_type, void * hdrs, void * opts, void **ptr) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIMimeObjectClassAccess, + NS_IMIME_OBJECT_CLASS_ACCESS_IID) + +#endif /* nsIMimeObjectClassAccess_h_ */ diff --git a/mailnews/mime/public/nsIMimeStreamConverter.idl b/mailnews/mime/public/nsIMimeStreamConverter.idl new file mode 100644 index 000000000..bde6175c4 --- /dev/null +++ b/mailnews/mime/public/nsIMimeStreamConverter.idl @@ -0,0 +1,93 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsrootidl.idl" +#include "nsIMimeHeaders.idl" +#include "nsIMsgIdentity.idl" +#include "nsIMsgHdr.idl" + +interface nsIURI; + +typedef long nsMimeOutputType; + +[scriptable, uuid(fdc2956e-d558-43fb-bfdd-fb9511229aa5)] +interface nsMimeOutput +{ + const long nsMimeMessageSplitDisplay = 0; + const long nsMimeMessageHeaderDisplay = 1; + const long nsMimeMessageBodyDisplay = 2; + const long nsMimeMessageQuoting = 3; + const long nsMimeMessageBodyQuoting = 4; + const long nsMimeMessageRaw = 5; + const long nsMimeMessageDraftOrTemplate = 6; + const long nsMimeMessageEditorTemplate = 7; + const long nsMimeMessagePrintOutput = 9; + const long nsMimeMessageSaveAs = 10; + const long nsMimeMessageSource = 11; + const long nsMimeMessageFilterSniffer = 12; + const long nsMimeMessageDecrypt = 13; + const long nsMimeMessageAttach = 14; + const long nsMimeUnknown = 15; +}; + +[scriptable, uuid(FA81CAA0-6261-11d3-8311-00805F2A0107)] +interface nsIMimeStreamConverterListener : nsISupports{ + void onHeadersReady(in nsIMimeHeaders headers); +}; + +/** + * This interface contains mailnews mime specific information for stream + * converters. Most of the code is just stuff that has been moved out + * of nsIStreamConverter.idl to make it more generic. + */ +[scriptable, uuid(d894c833-29c5-495b-880c-9a9f847bfdc9)] +interface nsIMimeStreamConverter : nsISupports { + + /** + * Set the desired mime output type on the converer. + */ + void SetMimeOutputType(in nsMimeOutputType aType); + + void GetMimeOutputType(out nsMimeOutputType aOutFormat); + + /** + * This is needed by libmime for MHTML link processing...the url is the URL + * string associated with this input stream. + */ + void SetStreamURI(in nsIURI aURI); + + /** + * Used to extract headers while parsing a message. + */ + void SetMimeHeadersListener(in nsIMimeStreamConverterListener listener, in nsMimeOutputType aType); + + /** + * This is used for forward inline, both as a filter action, and from the UI. + */ + attribute boolean forwardInline; + + /** + * This is used for a forward inline filter action. When streaming is done, + * we won't open a compose window with the editor contents. + */ + attribute boolean forwardInlineFilter; + + /** + * Address for the forward inline filter to forward the message to. + */ + attribute AString forwardToAddress; + /** + * Use the opposite compose format, used for forward inline. + */ + attribute boolean overrideComposeFormat; + + /** + * This is used for OpenDraft, OpenEditorTemplate and Forward inline (which use OpenDraft) + */ + attribute nsIMsgIdentity identity; + attribute string originalMsgURI; + attribute nsIMsgDBHdr origMsgHdr; +}; diff --git a/mailnews/mime/public/nsIMsgHeaderParser.idl b/mailnews/mime/public/nsIMsgHeaderParser.idl new file mode 100644 index 000000000..2512623d8 --- /dev/null +++ b/mailnews/mime/public/nsIMsgHeaderParser.idl @@ -0,0 +1,235 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +#define NS_MAILNEWS_MIME_HEADER_PARSER_CONTRACTID \ + "@mozilla.org/messenger/headerparser;1" +%} + +/** + * A structured representation of an address. + * + * This is meant to correspond to the address production from RFC 5322. As a + * result, an instance of this interface is either a single mailbox or a group + * of mailboxes. The difference between the two forms is in which attribute is + * undefined: mailboxes leave the members attribute undefined while groups leave + * the email attribute undefined. + * + * For example, an address like "John Doe " will, when + * parsed, result in an instance with the name attribute set to "John Doe", the + * email attribute set to "jdoe@machine.example", and the members variable left + * undefined. + * + * A group like "undisclosed-recipients:;" will, when parsed, result in an + * instance with the name attribute set to "undisclosed-recipients", the email + * attribute left defined, and the members variable set to an empty array. + * + * In general, the attributes of this interface are always meant to be in a form + * suitable for display purposes, and not in a form usable for MIME emission. In + * particular, email addresses could be fully internationalized and non-ASCII, + * RFC 2047-encoded words may appear in names, and the name or email parameters + * are unquoted. + */ +[scriptable, uuid(b19f5636-ebc4-470e-b46c-98b5fc7e88c9)] +interface msgIAddressObject : nsISupports { + /// The name of the mailbox or group. + readonly attribute AString name; + + /// The email of the mailbox. + readonly attribute AString email; + + /** + * The member mailboxes of this group, which may be an empty list. + * + * Due to the limitations of XPIDL, the type of this attribute cannot be + * properly reflected. It is actually an array of msgIAddressObject instances, + * although it is instead undefined if this object does not represent a group. + */ + readonly attribute jsval group; + + /// Return a string form of this object that is suitable for display. + AString toString(); +}; + +/** + * A utility service for manipulating addressing headers in email messages. + * + * This interface is designed primarily for use from JavaScript code; code in + * C++ should use the methods in MimeHeaderParser.h instead, as it is better + * designed to take advantage of C++'s features, particularly with respect to + * arrays. + * + * There are two methods for parsing MIME headers, one for RFC 2047-decoded + * strings, and one for non-RFC 2047-decoded strings. + * + * In general, this API attempts to preserve the format of addresses as + * faithfully as possible. No case normalization is performed at any point. + * However, internationalized email addresses generally need extra processing to + * work properly, so while this API should handle them without issues, consumers + * of this API may fail to work properly when presented with such addresses. To + * ease use for such cases, future versions of the API may incorporate necessary + * normalization steps to make oblivious consumers more likely to work properly. + */ +[scriptable, uuid(af2f9dd1-0226-4835-b981-a4f88b5e97cc)] +interface nsIMsgHeaderParser : nsISupports { + /** + * Parse an address-based header that has not yet been 2047-decoded. + * + * The result of this method is an array of objects described in the above + * comment. Note that the header is a binary string that will be decoded as if + * passed into nsIMimeConverter. + * + * @param aEncodedHeader The RFC 2047-encoded header to parse. + * @param aHeaderCharset The charset to assume for raw octets. + * @param aPreserveGroups If false (the default), the result is a flat array + * of mailbox objects, containing no group objects. + * @return An array corresponding to the header description. + */ + void parseEncodedHeader(in ACString aEncodedHeader, + in string aHeaderCharset, + [optional] in bool aPreserveGroups, + [optional] out unsigned long length, + [retval, array, size_is(length)] + out msgIAddressObject addresses); + + /** + * Parse an address-based header that has been 2047-decoded. + * + * The result of this method is an array of objects described in the above + * comment. Note that the header is a binary string that will be decoded as if + * passed into nsIMimeConverter. + * + * @param aDecodedHeader The non-RFC 2047-encoded header to parse. + * @param aPreserveGroups If false (the default), the result is a flat array + * of mailbox objects, containing no group objects. + * @return An array corresponding to the header description. + */ + void parseDecodedHeader(in AString aDecodedHeader, + [optional] in bool aPreserveGroups, + [optional] out unsigned long length, + [retval, array, size_is(length)] + out msgIAddressObject addresses); + + /** + * Given an array of addresses, make a MIME header suitable for emission. + * + * The return value of this method is not directly suitable for use in a MIME + * message but rather needs to be passed through nsIMimeConverter first to + * have RFC-2047 encoding applied and the resulting output wrapped to adhere + * to maximum line length formats. + * + * @param aAddresses An array corresponding to the header description. + * @param aLength The length of said array of addresses. + * @return A string that is suitable for writing in a MIME message. + */ + AString makeMimeHeader([array, size_is(aLength)] + in msgIAddressObject aAddresses, + in unsigned long aLength); + + /** + * Return the first address in the list in a format suitable for display. + * + * This is largely a convience method for handling From headers (or similar), + * which are expected to only have a single element in them. It is exactly + * equivalent to saying (parseDecodedHeader(decodedHeader))[0].toString(). + * + * @param decodedHeader The non-RFC 2047-encoded header to parse. + * @return The first address, suitable for display. + */ + AString extractFirstName(in AString aDecodedHeader); + + /** + * Returns a copy of the input which may have had some addresses removed. + * Addresses are removed if they are already in either of the supplied + * address lists. + * + * Addresses are considered to be the same if they contain the same email + * part (case-insensitive). Since the email part should never be RFC + * 2047-encoded, this method should work whether or not the header is + * RFC 2047-encoded. + * + * @param aAddrs The addresses to remove duplicates from. + * @param aOtherAddrs Other addresses that the duplicate removal process also + * checks for duplicates against. Addresses in this list + * will not be added to the result. + * @return The original header with duplicate addresses removed. + */ + AUTF8String removeDuplicateAddresses(in AUTF8String aAddrs, + [optional] in AUTF8String aOtherAddrs); + + /// Return a structured mailbox object having the given name and email. + msgIAddressObject makeMailboxObject(in AString aName, in AString aEmail); + + /// Return a structured group object having the given name and members. + msgIAddressObject makeGroupObject(in AString aName, + [array, size_is(aLength)] in msgIAddressObject aMembers, + in unsigned long aLength); + + /** + * Return an array of structured mailbox objects for the given display name + * string. + * + * The string is expected to be a comma-separated sequence of strings that + * would be produced by msgIAddressObject::toString(). For example, the string + * "Bond, James " would produce one address object, + * while the string "webmaster@nowhere.invalid, child@nowhere.invalid" would + * produce two address objects. + * + * Note that the input string is RFC 2231 and RFC 2047 decoded but no UTF-8 + * decoding takes place. + */ + void makeFromDisplayAddress(in AString aDisplayAddresses, + [optional] out unsigned long count, + [retval, array, size_is(count)] out msgIAddressObject addresses); + + [deprecated] void parseHeadersWithArray(in wstring aLine, + [array, size_is(count)] out wstring aEmailAddresses, + [array, size_is(count)] out wstring aNames, + [array, size_is(count)] out wstring aFullNames, + [retval] out unsigned long count); + + + /** + * Given a string which contains a list of Header addresses, returns a + * comma-separated list of just the `mailbox' portions. + * + * @param aLine The header line to parse. + * @return A comma-separated list of just the mailbox parts + * of the email-addresses. + */ + [deprecated] ACString extractHeaderAddressMailboxes(in ACString aLine); + + /** + * Given a string which contains a list of Header addresses, returns a + * comma-separated list of just the `user name' portions. If any of + * the addresses doesn't have a name, then the mailbox is used instead. + * + * @param aLine The header line to parse. + * @return A comma-separated list of just the name parts + * of the addresses. + */ + [deprecated] AUTF8String extractHeaderAddressNames(in AUTF8String aLine); + + /* + * Like extractHeaderAddressNames, but only returns the first name in the + * header if there is one. This function will return unquoted strings suitable + * for display. + * + * @param aLine The header line to parse. + * @return The first name found in the list. + */ + [deprecated] AUTF8String extractHeaderAddressName(in AUTF8String aLine); + + /** + * Given a name and email address, produce a string that is suitable for + * emitting in a MIME header (after applying RFC 2047 encoding). + * + * @note This is a temporary method. + */ + [deprecated] AString makeMimeAddress(in AString aName, in AString aEmail); +}; + diff --git a/mailnews/mime/public/nsIPgpMimeProxy.idl b/mailnews/mime/public/nsIPgpMimeProxy.idl new file mode 100644 index 000000000..375a7b776 --- /dev/null +++ b/mailnews/mime/public/nsIPgpMimeProxy.idl @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIStreamListener.idl" +#include "nsIURI.idl" + +%{C++ +typedef int (*MimeDecodeCallbackFun)(const char *buf, int32_t buf_size, void *output_closure); + +#define NS_PGPMIMEPROXY_CLASSNAME "PGP/Mime Decryption" +#define NS_PGPMIMEPROXY_CONTRACTID "@mozilla.org/mime/pgp-mime-decrypt;1" + +#define NS_PGPMIMEPROXY_CID \ +{ /* 6b7e094f-536b-40dc-b3a4-e3d729205ce1 */ \ + 0x6b7e094f, 0x536b, 0x40dc, \ +{0xb3, 0xa4, 0xe3, 0xd7, 0x29, 0x20, 0x5c, 0xe1 } } +%} + +native MimeDecodeCallbackFun(MimeDecodeCallbackFun); + +/** + * nsIPgpMimeProxy is a proxy for a (JS-)addon for OpenPGP/MIME decryption + */ + +[scriptable, uuid(6b7e094f-536b-40dc-b3a4-e3d729205ce1)] +interface nsIPgpMimeProxy : nsIStreamListener +{ + /** + * set the decoder callback into mimelib + */ + [noscript] void setMimeCallback(in MimeDecodeCallbackFun outputFun, + in voidPtr outputClosure, + in nsIURI myUri); + + /** + * init function + */ + void init(); + + /** + * process encoded data received from mimelib + */ + void write(in string buf, in unsigned long count); + + /** + * finish writing (EOF) from mimelib + */ + void finish(); + + /** + * the listener that receives the OpenPGP/MIME data stream and decrypts + * the message + */ + attribute nsIStreamListener decryptor; + + attribute ACString contentType; + + /** + * The particular part number of the multipart object we are working on. The + * numbering is the same as in URLs that use the form "...?part=1.1.2". + * + * The value stored in mimePart is only the number, e.g. "1" or "1.1.2" + */ + attribute ACString mimePart; +}; + + +/////////////////////////////////////////////////////////////////////////////// diff --git a/mailnews/mime/public/nsISimpleMimeConverter.idl b/mailnews/mime/public/nsISimpleMimeConverter.idl new file mode 100644 index 000000000..907a1bb0e --- /dev/null +++ b/mailnews/mime/public/nsISimpleMimeConverter.idl @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +interface nsIURI; + +[scriptable, uuid(FC6E8234-BBF3-44A1-9802-5F023A929173)] +interface nsISimpleMimeConverter : nsISupports +{ + // uri of message getting displayed + attribute nsIURI uri; + AUTF8String convertToHTML(in ACString contentType, + in AUTF8String data); +}; + +%{C++ + +#define NS_SIMPLEMIMECONVERTERS_CATEGORY "simple-mime-converters" + +%} diff --git a/mailnews/mime/public/nsMailHeaders.h b/mailnews/mime/public/nsMailHeaders.h new file mode 100644 index 000000000..d8a3131a5 --- /dev/null +++ b/mailnews/mime/public/nsMailHeaders.h @@ -0,0 +1,90 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * This interface allows any module to access the encoder/decoder + * routines for RFC822 headers. This will allow any mail/news module + * to call on these routines. + */ +#ifndef nsMailHeaders_h_ +#define nsMailHeaders_h_ + +/* + * These are the defines for standard header field names. + */ +#define HEADER_BCC "BCC" +#define HEADER_CC "CC" +#define HEADER_CONTENT_BASE "Content-Base" +#define HEADER_CONTENT_LOCATION "Content-Location" +#define HEADER_CONTENT_ID "Content-ID" +#define HEADER_CONTENT_DESCRIPTION "Content-Description" +#define HEADER_CONTENT_DISPOSITION "Content-Disposition" +#define HEADER_CONTENT_ENCODING "Content-Encoding" +#define HEADER_CONTENT_LANGUAGE "Content-Language" +#define HEADER_CONTENT_LENGTH "Content-Length" +#define HEADER_CONTENT_NAME "Content-Name" +#define HEADER_CONTENT_TRANSFER_ENCODING "Content-Transfer-Encoding" +#define HEADER_CONTENT_TYPE "Content-Type" +#define HEADER_DATE "Date" +#define HEADER_DISTRIBUTION "Distribution" +#define HEADER_FCC "FCC" +#define HEADER_FOLLOWUP_TO "Followup-To" +#define HEADER_FROM "From" +#define HEADER_STATUS "Status" +#define HEADER_LINES "Lines" +#define HEADER_LIST_POST "List-Post" +#define HEADER_MAIL_FOLLOWUP_TO "Mail-Followup-To" +#define HEADER_MAIL_REPLY_TO "Mail-Reply-To" +#define HEADER_MESSAGE_ID "Message-ID" +#define HEADER_MIME_VERSION "MIME-Version" +#define HEADER_NEWSGROUPS "Newsgroups" +#define HEADER_ORGANIZATION "Organization" +#define HEADER_REFERENCES "References" +#define HEADER_REPLY_TO "Reply-To" +#define HEADER_RESENT_COMMENTS "Resent-Comments" +#define HEADER_RESENT_DATE "Resent-Date" +#define HEADER_RESENT_FROM "Resent-From" +#define HEADER_RESENT_MESSAGE_ID "Resent-Message-ID" +#define HEADER_RESENT_SENDER "Resent-Sender" +#define HEADER_RESENT_TO "Resent-To" +#define HEADER_RESENT_CC "Resent-CC" +#define HEADER_SENDER "Sender" +#define HEADER_SUBJECT "Subject" +#define HEADER_TO "To" +#define HEADER_APPROVED_BY "Approved-By" +#define HEADER_X_MAILER "X-Mailer" +#define HEADER_USER_AGENT "User-Agent" +#define HEADER_X_NEWSREADER "X-Newsreader" +#define HEADER_X_POSTING_SOFTWARE "X-Posting-Software" +#define HEADER_X_MOZILLA_STATUS "X-Mozilla-Status" +#define HEADER_X_MOZILLA_STATUS2 "X-Mozilla-Status2" +#define HEADER_X_MOZILLA_NEWSHOST "X-Mozilla-News-Host" +#define HEADER_X_MOZILLA_DRAFT_INFO "X-Mozilla-Draft-Info" +#define HEADER_X_UIDL "X-UIDL" +#define HEADER_XREF "XREF" +#define HEADER_X_SUN_CHARSET "X-Sun-Charset" +#define HEADER_X_SUN_CONTENT_LENGTH "X-Sun-Content-Length" +#define HEADER_X_SUN_CONTENT_LINES "X-Sun-Content-Lines" +#define HEADER_X_SUN_DATA_DESCRIPTION "X-Sun-Data-Description" +#define HEADER_X_SUN_DATA_NAME "X-Sun-Data-Name" +#define HEADER_X_SUN_DATA_TYPE "X-Sun-Data-Type" +#define HEADER_X_SUN_ENCODING_INFO "X-Sun-Encoding-Info" +#define HEADER_X_PRIORITY "X-Priority" + +#define HEADER_PARM_CHARSET "charset" +#define HEADER_PARM_START "start" +#define HEADER_PARM_BOUNDARY "BOUNDARY" +#define HEADER_PARM_FILENAME "FILENAME" +#define HEADER_PARM_NAME "NAME" +#define HEADER_PARM_TYPE "TYPE" + +#define HEADER_X_MOZILLA_PART_URL "X-Mozilla-PartURL" +#define HEADER_X_MOZILLA_PART_SIZE "X-Mozilla-PartSize" +#define HEADER_X_MOZILLA_PART_DOWNLOADED "X-Mozilla-PartDownloaded" +#define HEADER_X_MOZILLA_CLOUD_PART "X-Mozilla-Cloud-Part" +#define HEADER_X_MOZILLA_IDENTITY_KEY "X-Identity-Key" +#define HEADER_X_MOZILLA_ACCOUNT_KEY "X-Account-Key" +#define HEADER_X_MOZILLA_KEYWORDS "X-Mozilla-Keys" +#endif /* nsMailHeaders_h_ */ diff --git a/mailnews/mime/public/nsMsgMimeCID.h b/mailnews/mime/public/nsMsgMimeCID.h new file mode 100644 index 000000000..07dec95e5 --- /dev/null +++ b/mailnews/mime/public/nsMsgMimeCID.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsMessageMimeCID_h__ +#define nsMessageMimeCID_h__ + +#define NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID \ + NS_ISTREAMCONVERTER_KEY "?from=message/rfc822&to=application/vnd.mozilla.xul+xml" + +#define NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID1 \ + NS_ISTREAMCONVERTER_KEY "?from=message/rfc822&to=text/html" + +#define NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID2 \ + NS_ISTREAMCONVERTER_KEY "?from=message/rfc822&to=*/*" + +#define NS_MAILNEWS_MIME_STREAM_CONVERTER_CID \ +{ /* FAF4F9A6-60AD-11d3-989A-001083010E9B */ \ + 0xfaf4f9a6, 0x60ad, 0x11d3, { 0x98, 0x9a, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b } } + +#define NS_MIME_CONVERTER_CONTRACTID \ + "@mozilla.org/messenger/mimeconverter;1" + +// {403B0540-B7C3-11d2-B35E-525400E2D63A} +#define NS_MIME_OBJECT_CLASS_ACCESS_CID \ + { 0x403b0540, 0xb7c3, 0x11d2, \ + { 0xb3, 0x5e, 0x52, 0x54, 0x0, 0xe2, 0xd6, 0x3a } } + +#define NS_MIME_OBJECT_CONTRACTID \ + "@mozilla.org/messenger/mimeobject;1" + +#endif // nsMessageMimeCID_h__ diff --git a/mailnews/mime/src/MimeHeaderParser.cpp b/mailnews/mime/src/MimeHeaderParser.cpp new file mode 100644 index 000000000..18d81023e --- /dev/null +++ b/mailnews/mime/src/MimeHeaderParser.cpp @@ -0,0 +1,229 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "mozilla/mailnews/Services.h" +#include "mozilla/DebugOnly.h" +#include "nsMemory.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsIMimeConverter.h" +#include "nsIMsgHeaderParser.h" + +namespace mozilla { +namespace mailnews { + +void detail::DoConversion(const nsTArray &aUTF16Array, + nsTArray &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 headerParser(services::GetHeaderParser()); + + nsCOMPtr address; + headerParser->MakeMailboxObject(aName, aEmail, getter_AddRefs(address)); + msgIAddressObject *obj = address; + headerParser->MakeMimeHeader(&obj, 1, full); +} + +void MakeDisplayAddress(const nsAString &aName, const nsAString &aEmail, + nsAString &full) +{ + nsCOMPtr headerParser(services::GetHeaderParser()); + + nsCOMPtr object; + headerParser->MakeMailboxObject(aName, aEmail, getter_AddRefs(object)); + object->ToString(full); +} + +void RemoveDuplicateAddresses(const nsACString &aHeader, + const nsACString &aOtherEmails, + nsACString &result) +{ + nsCOMPtr headerParser(services::GetHeaderParser()); + + headerParser->RemoveDuplicateAddresses(aHeader, aOtherEmails, result); +} + +///////////////////////////////////////////// +// These are the core shim methods we need // +///////////////////////////////////////////// + +nsCOMArray DecodedHeader(const nsAString &aHeader) +{ + nsCOMArray retval; + if (aHeader.IsEmpty()) { + return retval; + } + nsCOMPtr headerParser(services::GetHeaderParser()); + msgIAddressObject **addresses = nullptr; + uint32_t length; + nsresult rv = headerParser->ParseDecodedHeader(aHeader, false, + &length, &addresses); + MOZ_ASSERT(NS_SUCCEEDED(rv), "Javascript jsmime returned an error!"); + if (NS_SUCCEEDED(rv) && length > 0 && addresses) { + retval.Adopt(addresses, length); + } + return retval; +} + +nsCOMArray EncodedHeader(const nsACString &aHeader, + const char *aCharset) +{ + nsCOMArray retval; + if (aHeader.IsEmpty()) { + return retval; + } + nsCOMPtr headerParser(services::GetHeaderParser()); + msgIAddressObject **addresses = nullptr; + uint32_t length; + nsresult rv = headerParser->ParseEncodedHeader(aHeader, aCharset, + false, &length, &addresses); + MOZ_ASSERT(NS_SUCCEEDED(rv), "This should never fail!"); + if (NS_SUCCEEDED(rv) && length > 0 && addresses) { + retval.Adopt(addresses, length); + } + return retval; +} + +void ExtractAllAddresses(const nsCOMArray &aHeader, + nsTArray &names, nsTArray &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 &aHeader, + nsTArray &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 &aHeader, + nsTArray &emails) +{ + nsTArray names; + ExtractAllAddresses(aHeader, names, emails); +} + +void ExtractEmail(const nsCOMArray &aHeader, + nsACString &email) +{ + AutoTArray names; + AutoTArray emails; + ExtractAllAddresses(aHeader, names, emails); + + if (emails.Length() > 0) + CopyUTF16toUTF8(emails[0], email); + else + email.Truncate(); +} + +void ExtractFirstAddress(const nsCOMArray &aHeader, + nsACString &name, nsACString &email) +{ + AutoTArray 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 &aHeader, + nsAString &name, nsACString &email) +{ + AutoTArray 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 &aHeader, nsACString &name) +{ + nsCString email; + ExtractFirstAddress(aHeader, name, email); + if (name.IsEmpty()) + name = email; +} + +void ExtractName(const nsCOMArray &aHeader, nsAString &name) +{ + AutoTArray names; + AutoTArray emails; + ExtractAllAddresses(aHeader, names, emails); + if (names.Length() > 0) + { + if (names[0].IsEmpty()) + name = emails[0]; + else + name = names[0]; + } + else + { + name.Truncate(); + } +} + +} // namespace mailnews +} // namespace mozilla diff --git a/mailnews/mime/src/comi18n.cpp b/mailnews/mime/src/comi18n.cpp new file mode 100644 index 000000000..7425e32ff --- /dev/null +++ b/mailnews/mime/src/comi18n.cpp @@ -0,0 +1,108 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "comi18n.h" +#include "nsIStringCharsetDetector.h" +#include "nsMsgUtils.h" +#include "nsICharsetConverterManager.h" +#include "nsIMIMEHeaderParam.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgMimeCID.h" +#include "nsIMimeConverter.h" + + +//////////////////////////////////////////////////////////////////////////////// +// BEGIN PUBLIC INTERFACE +extern "C" { + + +void MIME_DecodeMimeHeader(const char *header, const char *default_charset, + bool override_charset, bool eatContinuations, + nsACString &result) +{ + nsresult rv; + nsCOMPtr mimeConverter = + do_GetService(NS_MIME_CONVERTER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + result.Truncate(); + return; + } + mimeConverter->DecodeMimeHeaderToUTF8(nsDependentCString(header), + default_charset, override_charset, + eatContinuations, result); +} + +// UTF-8 utility functions. +//detect charset soly based on aBuf. return in aCharset +nsresult +MIME_detect_charset(const char *aBuf, int32_t aLength, const char** aCharset) +{ + nsresult res = NS_ERROR_UNEXPECTED; + nsString detector_name; + *aCharset = nullptr; + + NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, "intl.charset.detector", EmptyString(), detector_name); + + if (!detector_name.IsEmpty()) { + nsAutoCString detector_contractid; + detector_contractid.AssignLiteral(NS_STRCDETECTOR_CONTRACTID_BASE); + detector_contractid.Append(NS_ConvertUTF16toUTF8(detector_name)); + nsCOMPtr detector = do_CreateInstance(detector_contractid.get(), &res); + if (NS_SUCCEEDED(res)) { + nsDetectionConfident oConfident; + res = detector->DoIt(aBuf, aLength, aCharset, oConfident); + if (NS_SUCCEEDED(res) && (eBestAnswer == oConfident || eSureAnswer == oConfident)) { + return NS_OK; + } + } + } + return res; +} + +//Get unicode decoder(from inputcharset to unicode) for aInputCharset +nsresult +MIME_get_unicode_decoder(const char* aInputCharset, nsIUnicodeDecoder **aDecoder) +{ + nsresult res; + + // get charset converters. + nsCOMPtr ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res); + if (NS_SUCCEEDED(res)) { + + // create a decoder (conv to unicode), ok if failed if we do auto detection + if (!*aInputCharset || !PL_strcasecmp("us-ascii", aInputCharset)) + res = ccm->GetUnicodeDecoderRaw("ISO-8859-1", aDecoder); + else + // GetUnicodeDecoderInternal in order to support UTF-7 messages + // + // XXX this means that even HTML messages in UTF-7 will be decoded + res = ccm->GetUnicodeDecoderInternal(aInputCharset, aDecoder); + } + + return res; +} + +//Get unicode encoder(from unicode to inputcharset) for aOutputCharset +nsresult +MIME_get_unicode_encoder(const char* aOutputCharset, nsIUnicodeEncoder **aEncoder) +{ + nsresult res; + + // get charset converters. + nsCOMPtr ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res); + if (NS_SUCCEEDED(res) && *aOutputCharset) { + // create a encoder (conv from unicode) + res = ccm->GetUnicodeEncoder(aOutputCharset, aEncoder); + } + + return res; +} + +} /* end of extern "C" */ +// END PUBLIC INTERFACE + diff --git a/mailnews/mime/src/comi18n.h b/mailnews/mime/src/comi18n.h new file mode 100644 index 000000000..6be89cfc5 --- /dev/null +++ b/mailnews/mime/src/comi18n.h @@ -0,0 +1,42 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef _COMI18N_LOADED_H_ +#define _COMI18N_LOADED_H_ + +#include "msgCore.h" + +class nsIUnicodeDecoder; +class nsIUnicodeEncoder; + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Decode MIME header to UTF-8. + * Uses MIME_ConvertCharset if the decoded string needs a conversion. + * + * + * @param header [IN] A header to decode. + * @param default_charset [IN] Default charset to apply to ulabeled non-UTF-8 8bit data + * @param override_charset [IN] If true, default_charset used instead of any charset labeling other than UTF-8 + * @param eatContinuations [IN] If true, unfold headers + * @param result [OUT] Decoded buffer + */ +void MIME_DecodeMimeHeader(const char *header, const char *default_charset, + bool override_charset, bool eatContinuations, + nsACString &result); + +nsresult MIME_detect_charset(const char *aBuf, int32_t aLength, const char** aCharset); +nsresult MIME_get_unicode_decoder(const char* aInputCharset, nsIUnicodeDecoder **aDecoder); +nsresult MIME_get_unicode_encoder(const char* aOutputCharset, nsIUnicodeEncoder **aEncoder); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif // _COMI18N_LOADED_H_ + diff --git a/mailnews/mime/src/extraMimeParsers.jsm b/mailnews/mime/src/extraMimeParsers.jsm new file mode 100644 index 000000000..101eddc20 --- /dev/null +++ b/mailnews/mime/src/extraMimeParsers.jsm @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function parseNewsgroups(headers) { + let ng = []; + for (let header of headers) { + ng = ng.concat(header.split(/\s*,\s*/)); + } + return ng; +} + +function emitNewsgroups(groups) { + // Don't encode the newsgroups names in RFC 2047... + if (groups.length == 1) + this.addText(groups[0], false); + else { + this.addText(groups[0], false); + for (let i = 1; i < groups.length; i++) { + this.addText(",", false); // only comma, no space! + this.addText(groups[i], false); + } + } +} + +jsmime.headerparser.addStructuredDecoder("Newsgroups", parseNewsgroups); +jsmime.headerparser.addStructuredDecoder("Followup-To", parseNewsgroups); +jsmime.headeremitter.addStructuredEncoder("Newsgroups", emitNewsgroups); +jsmime.headeremitter.addStructuredEncoder("Followup-To", emitNewsgroups); diff --git a/mailnews/mime/src/jsmime.jsm b/mailnews/mime/src/jsmime.jsm new file mode 100644 index 000000000..70728fe9c --- /dev/null +++ b/mailnews/mime/src/jsmime.jsm @@ -0,0 +1,90 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +// vim:set ts=2 sw=2 sts=2 et ft=javascript: + +Components.utils.import("resource://gre/modules/Services.jsm"); + +/** + * This file exports the JSMime code, polyfilling code as appropriate for use in + * Gecko. + */ + +// Load the core MIME parser. Since it doesn't define EXPORTED_SYMBOLS, we must +// use the subscript loader instead. +Services.scriptloader.loadSubScript("resource:///modules/jsmime/jsmime.js"); + +var EXPORTED_SYMBOLS = ["jsmime"]; + + +// A polyfill to support non-encoding-spec charsets. Since the only converter +// available to us from JavaScript has a very, very weak and inflexible API, we +// choose to rely on the regular text decoder unless absolutely necessary. +// support non-encoding-spec charsets. +function FakeTextDecoder(label="UTF-8", options = {}) { + this._reset(label); + // So nsIScriptableUnicodeConverter only gives us fatal=false, unless we are + // using UTF-8, where we only get fatal=true. The internals of said class tell + // us to use a C++-only class if we need better behavior. +} +FakeTextDecoder.prototype = { + _reset: function (label) { + this._encoder = Components.classes[ + "@mozilla.org/intl/scriptableunicodeconverter"] + .createInstance(Components.interfaces.nsIScriptableUnicodeConverter); + this._encoder.isInternal = true; + let manager = Components.classes[ + "@mozilla.org/charset-converter-manager;1"] + .createInstance(Components.interfaces.nsICharsetConverterManager); + this._encoder.charset = manager.getCharsetAlias(label); + }, + get encoding() { return this._encoder.charset; }, + decode: function (input, options = {}) { + let more = 'stream' in options ? options.stream : false; + let result = ""; + if (input !== undefined) { + let data = new Uint8Array(input); + result = this._encoder.convertFromByteArray(data, data.length); + } + // This isn't quite right--it won't handle errors if there are a few + // remaining bytes in the buffer, but it's the best we can do. + if (!more) + this._reset(this.encoding); + return result; + }, +}; + +var RealTextDecoder = TextDecoder; +function FallbackTextDecoder(charset, options) { + try { + return new RealTextDecoder(charset, options); + } catch (e) { + return new FakeTextDecoder(charset, options); + } +} + +TextDecoder = FallbackTextDecoder; + + +// The following code loads custom MIME encoders. +var CATEGORY_NAME = "custom-mime-encoder"; +Services.obs.addObserver(function (subject, topic, data) { + subject = subject.QueryInterface(Components.interfaces.nsISupportsCString) + .data; + if (data == CATEGORY_NAME) { + let url = catman.getCategoryEntry(CATEGORY_NAME, subject); + Services.scriptloader.loadSubScript(url, {}, "UTF-8"); + } +}, "xpcom-category-entry-added", false); + +var catman = Components.classes["@mozilla.org/categorymanager;1"] + .getService(Components.interfaces.nsICategoryManager); + +var entries = catman.enumerateCategory(CATEGORY_NAME); +while (entries.hasMoreElements()) { + let string = entries.getNext() + .QueryInterface(Components.interfaces.nsISupportsCString) + .data; + let url = catman.getCategoryEntry(CATEGORY_NAME, string); + Services.scriptloader.loadSubScript(url, {}, "UTF-8"); +} diff --git a/mailnews/mime/src/mime.def b/mailnews/mime/src/mime.def new file mode 100644 index 000000000..6b1c36bf9 --- /dev/null +++ b/mailnews/mime/src/mime.def @@ -0,0 +1,7 @@ +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. + +LIBRARY mime.dll + +EXPORTS diff --git a/mailnews/mime/src/mimeJSComponents.js b/mailnews/mime/src/mimeJSComponents.js new file mode 100644 index 000000000..5ba7ff084 --- /dev/null +++ b/mailnews/mime/src/mimeJSComponents.js @@ -0,0 +1,512 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +Components.utils.import("resource://gre/modules/Deprecated.jsm"); +Components.utils.import("resource:///modules/jsmime.jsm"); +Components.utils.import("resource:///modules/mimeParser.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function HeaderHandler() { + this.value = ""; + this.deliverData = function (str) { this.value += str; }; + this.deliverEOF = function () {}; +} + +function StringEnumerator(iterator) { + this._iterator = iterator; + this._next = undefined; +} +StringEnumerator.prototype = { + QueryInterface: XPCOMUtils.generateQI([ + Components.interfaces.nsIUTF8StringEnumerator]), + _setNext: function () { + if (this._next !== undefined) + return; + this._next = this._iterator.next(); + }, + hasMore: function () { + this._setNext(); + return !this._next.done; + }, + getNext: function () { + this._setNext(); + let result = this._next; + this._next = undefined; + if (result.done) + throw Components.results.NS_ERROR_UNEXPECTED; + return result.value; + } +}; + +/** + * If we get XPConnect-wrapped objects for msgIAddressObjects, we will have + * properties defined for 'group' that throws off jsmime. This function converts + * the addresses into the form that jsmime expects. + */ +function fixXpconnectAddresses(addrs) { + return addrs.map((addr) => { + // This is ideally !addr.group, but that causes a JS strict warning, if + // group is not in addr, since that's enabled in all chrome code now. + if (!('group' in addr) || addr.group === undefined || addr.group === null) { + return MimeAddressParser.prototype.makeMailboxObject(addr.name, + addr.email); + } else { + return MimeAddressParser.prototype.makeGroupObject(addr.name, + fixXpconnectAddresses(addr.group)); + } + }); +} + +/** + * This is a base handler for supporting msgIStructuredHeaders, since we have + * two implementations that need the readable aspects of the interface. + */ +function MimeStructuredHeaders() { +} +MimeStructuredHeaders.prototype = { + getHeader: function (aHeaderName) { + let name = aHeaderName.toLowerCase(); + return this._headers.get(name); + }, + + hasHeader: function (aHeaderName) { + return this._headers.has(aHeaderName.toLowerCase()); + }, + + getUnstructuredHeader: function (aHeaderName) { + let result = this.getHeader(aHeaderName); + if (result === undefined || typeof result == "string") + return result; + throw Components.results.NS_ERROR_ILLEGAL_VALUE; + }, + + getAddressingHeader: function (aHeaderName, aPreserveGroups, count) { + let addrs = this.getHeader(aHeaderName); + if (addrs === undefined) { + addrs = []; + } else if (!Array.isArray(addrs)) { + throw Components.results.NS_ERROR_ILLEGAL_VALUE; + } + return fixArray(addrs, aPreserveGroups, count); + }, + + getRawHeader: function (aHeaderName) { + let result = this.getHeader(aHeaderName); + if (result === undefined) + return result; + + let value = jsmime.headeremitter.emitStructuredHeader(aHeaderName, + result, {}); + // Strip off the header name and trailing whitespace before returning... + value = value.substring(aHeaderName.length + 2).trim(); + // ... as well as embedded newlines. + value = value.replace(/\r\n/g, ''); + return value; + }, + + get headerNames() { + return new StringEnumerator(this._headers.keys()); + }, + + buildMimeText: function () { + if (this._headers.size == 0) { + return ""; + } + let handler = new HeaderHandler(); + let emitter = jsmime.headeremitter.makeStreamingEmitter(handler, { + useASCII: true + }); + for (let [value, header] of this._headers) { + emitter.addStructuredHeader(value, header); + } + emitter.finish(); + return handler.value; + }, +}; + + +function MimeHeaders() { +} +MimeHeaders.prototype = { + __proto__: MimeStructuredHeaders.prototype, + classDescription: "Mime headers implementation", + classID: Components.ID("d1258011-f391-44fd-992e-c6f4b461a42f"), + contractID: "@mozilla.org/messenger/mimeheaders;1", + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMimeHeaders, + Components.interfaces.msgIStructuredHeaders]), + + initialize: function MimeHeaders_initialize(allHeaders) { + this._headers = MimeParser.extractHeaders(allHeaders); + }, + + extractHeader: function MimeHeaders_extractHeader(header, getAll) { + if (!this._headers) + throw Components.results.NS_ERROR_NOT_INITIALIZED; + // Canonicalized to lower-case form + header = header.toLowerCase(); + if (!this._headers.has(header)) + return null; + var values = this._headers.getRawHeader(header); + if (getAll) + return values.join(",\r\n\t"); + else + return values[0]; + }, + + get allHeaders() { + return this._headers.rawHeaderText; + } +}; + +function MimeWritableStructuredHeaders() { + this._headers = new Map(); +} +MimeWritableStructuredHeaders.prototype = { + __proto__: MimeStructuredHeaders.prototype, + classID: Components.ID("c560806a-425f-4f0f-bf69-397c58c599a7"), + QueryInterface: XPCOMUtils.generateQI([ + Components.interfaces.msgIStructuredHeaders, + Components.interfaces.msgIWritableStructuredHeaders]), + + setHeader: function (aHeaderName, aValue) { + this._headers.set(aHeaderName.toLowerCase(), aValue); + }, + + deleteHeader: function (aHeaderName) { + this._headers.delete(aHeaderName.toLowerCase()); + }, + + addAllHeaders: function (aHeaders) { + let headerList = aHeaders.headerNames; + while (headerList.hasMore()) { + let header = headerList.getNext(); + this.setHeader(header, aHeaders.getHeader(header)); + } + }, + + setUnstructuredHeader: function (aHeaderName, aValue) { + this.setHeader(aHeaderName, aValue); + }, + + setAddressingHeader: function (aHeaderName, aAddresses, aCount) { + this.setHeader(aHeaderName, fixXpconnectAddresses(aAddresses)); + }, + + setRawHeader: function (aHeaderName, aValue, aCharset) { + aValue = jsmime.headerparser.convert8BitHeader(aValue, aCharset); + try { + this.setHeader(aHeaderName, + jsmime.headerparser.parseStructuredHeader(aHeaderName, aValue)); + } catch (e) { + // This means we don't have a structured encoder. Just assume it's a raw + // string value then. + this.setHeader(aHeaderName, aValue); + } + } +}; + +// These are prototypes for nsIMsgHeaderParser implementation +var Mailbox = { + toString: function () { + return this.name ? this.name + " <" + this.email + ">" : this.email; + } +}; + +var EmailGroup = { + toString: function () { + return this.name + ": " + this.group.map(x => x.toString()).join(", "); + } +}; + +// A helper method for parse*Header that takes into account the desire to +// preserve group and also tweaks the output to support the prototypes for the +// XPIDL output. +function fixArray(addresses, preserveGroups, count) { + function resetPrototype(obj, prototype) { + let prototyped = Object.create(prototype); + for (let key of Object.getOwnPropertyNames(obj)) { + if (typeof obj[key] == "string") { + prototyped[key] = obj[key].replace(/\x00/g, ''); + } else { + prototyped[key] = obj[key]; + } + } + return prototyped; + } + let outputArray = []; + for (let element of addresses) { + if ('group' in element) { + // Fix up the prototypes of the group and the list members + element = resetPrototype(element, EmailGroup); + element.group = element.group.map(e => resetPrototype(e, Mailbox)); + + // Add to the output array + if (preserveGroups) + outputArray.push(element); + else + outputArray = outputArray.concat(element.group); + } else { + element = resetPrototype(element, Mailbox); + outputArray.push(element); + } + } + + if (count) + count.value = outputArray.length; + return outputArray; +} + +function MimeAddressParser() { +} +MimeAddressParser.prototype = { + classID: Components.ID("96bd8769-2d0e-4440-963d-22b97fb3ba77"), + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMsgHeaderParser]), + + parseEncodedHeader: function (aHeader, aCharset, aPreserveGroups, count) { + aHeader = aHeader || ""; + let value = MimeParser.parseHeaderField(aHeader, + MimeParser.HEADER_ADDRESS | MimeParser.HEADER_OPTION_ALL_I18N, aCharset); + return fixArray(value, aPreserveGroups, count); + }, + parseDecodedHeader: function (aHeader, aPreserveGroups, count) { + aHeader = aHeader || ""; + let value = MimeParser.parseHeaderField(aHeader, MimeParser.HEADER_ADDRESS); + return fixArray(value, aPreserveGroups, count); + }, + + makeMimeHeader: function (addresses, length) { + addresses = fixXpconnectAddresses(addresses); + // Don't output any necessary continuations, so make line length as large as + // possible first. + let options = { + softMargin: 900, + hardMargin: 900, + useASCII: false // We don't want RFC 2047 encoding here. + }; + let handler = new HeaderHandler(); + let emitter = new jsmime.headeremitter.makeStreamingEmitter(handler, + options); + emitter.addAddresses(addresses); + emitter.finish(true); + return handler.value.replace(/\r\n( |$)/g, ''); + }, + + extractFirstName: function (aHeader) { + let address = this.parseDecodedHeader(aHeader, false)[0]; + return address.name || address.email; + }, + + removeDuplicateAddresses: function (aAddrs, aOtherAddrs) { + // This is actually a rather complicated algorithm, especially if we want to + // preserve group structure. Basically, we use a set to identify which + // headers we have seen and therefore want to remove. To work in several + // various forms of edge cases, we need to normalize the entries in that + // structure. + function normalize(email) { + // XXX: This algorithm doesn't work with IDN yet. It looks like we have to + // convert from IDN then do lower case, but I haven't confirmed yet. + return email.toLowerCase(); + } + + // The filtration function, which removes email addresses that are + // duplicates of those we have already seen. + function filterAccept(e) { + if ('email' in e) { + // If we've seen the address, don't keep this one; otherwise, add it to + // the list. + let key = normalize(e.email); + if (allAddresses.has(key)) + return false; + allAddresses.add(key); + } else { + // Groups -> filter out all the member addresses. + e.group = e.group.filter(filterAccept); + } + return true; + } + + // First, collect all of the emails to forcibly delete. + let allAddresses = new Set(); + for (let element of this.parseDecodedHeader(aOtherAddrs, false)) { + allAddresses.add(normalize(element.email)); + } + + // The actual data to filter + let filtered = this.parseDecodedHeader(aAddrs, true).filter(filterAccept); + return this.makeMimeHeader(filtered); + }, + + makeMailboxObject: function (aName, aEmail) { + let object = Object.create(Mailbox); + object.name = aName; + object.email = aEmail ? aEmail.trim() : aEmail; + return object; + }, + + makeGroupObject: function (aName, aMembers) { + let object = Object.create(EmailGroup); + object.name = aName; + object.group = aMembers; + return object; + }, + + makeFromDisplayAddress: function (aDisplay, count) { + // The basic idea is to split on every comma, so long as there is a + // preceding @. + let output = []; + while (aDisplay.length) { + let at = aDisplay.indexOf('@'); + let comma = aDisplay.indexOf(',', at + 1); + let addr; + if (comma > 0) { + addr = aDisplay.substr(0, comma); + aDisplay = aDisplay.substr(comma + 1); + } else { + addr = aDisplay; + aDisplay = ""; + } + output.push(this._makeSingleAddress(addr.trimLeft())); + } + if (count) + count.value = output.length; + return output; + }, + + /// Construct a single email address from a name token. + _makeSingleAddress: function (aDisplayName) { + if (aDisplayName.includes('<')) { + let lbracket = aDisplayName.lastIndexOf('<'); + let rbracket = aDisplayName.lastIndexOf('>'); + return this.makeMailboxObject( + lbracket == 0 ? '' : aDisplayName.slice(0, lbracket).trim(), + aDisplayName.slice(lbracket + 1, rbracket)); + } else { + return this.makeMailboxObject('', aDisplayName); + } + }, + + // What follows is the deprecated API that will be removed shortly. + + parseHeadersWithArray: function (aHeader, aAddrs, aNames, aFullNames) { + let addrs = [], names = [], fullNames = []; + // Parse header, but without HEADER_OPTION_ALLOW_RAW. + let value = MimeParser.parseHeaderField(aHeader || "", + MimeParser.HEADER_ADDRESS | + MimeParser.HEADER_OPTION_DECODE_2231 | + MimeParser.HEADER_OPTION_DECODE_2047, + undefined); + let allAddresses = fixArray(value, false); + + // Don't index the dummy empty address. + if (aHeader.trim() == "") + allAddresses = []; + for (let address of allAddresses) { + addrs.push(address.email); + names.push(address.name || null); + fullNames.push(address.toString()); + } + + aAddrs.value = addrs; + aNames.value = names; + aFullNames.value = fullNames; + return allAddresses.length; + }, + + extractHeaderAddressMailboxes: function (aLine) { + return this.parseDecodedHeader(aLine).map(addr => addr.email).join(", "); + }, + + extractHeaderAddressNames: function (aLine) { + return this.parseDecodedHeader(aLine).map(addr => addr.name || addr.email) + .join(", "); + }, + + extractHeaderAddressName: function (aLine) { + let addrs = this.parseDecodedHeader(aLine).map(addr => + addr.name || addr.email); + return addrs.length == 0 ? "" : addrs[0]; + }, + + makeMimeAddress: function (aName, aEmail) { + let object = this.makeMailboxObject(aName, aEmail); + return this.makeMimeHeader([object]); + }, +}; + +function MimeConverter() { +} +MimeConverter.prototype = { + classID: Components.ID("93f8c049-80ed-4dda-9000-94ad8daba44c"), + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMimeConverter]), + + encodeMimePartIIStr_UTF8: function (aHeader, aStructured, aCharset, + aFieldNameLen, aLineLength) { + // The JSMime encoder only works in UTF-8, so if someone requests to not do + // it, they need to change their code. + if (aCharset.toLowerCase() != "utf-8") { + Deprecated.warning("Encoding to non-UTF-8 values is obsolete", + "http://bugzilla.mozilla.org/show_bug.cgi?id=790855"); + } + + // Compute the encoding options. The way our API is structured in this + // method is really horrendous and does not align with the way that JSMime + // handles it. Instead, we'll need to create a fake header to take into + // account the aFieldNameLen parameter. + let fakeHeader = '-'.repeat(aFieldNameLen); + let options = { + softMargin: aLineLength, + useASCII: true, + }; + let handler = new HeaderHandler(); + let emitter = new jsmime.headeremitter.makeStreamingEmitter(handler, + options); + + // Add the text to the be encoded. + emitter.addHeaderName(fakeHeader); + if (aStructured) { + // Structured really means "this is an addressing header" + let addresses = MimeParser.parseHeaderField(aHeader, + MimeParser.HEADER_ADDRESS | MimeParser.HEADER_OPTION_DECODE_2047); + // This happens in one of our tests if there is a "bare" email but no + // @ sign. Without it, the result disappears since our emission code + // assumes that an empty email is not worth emitting. + if (addresses.length === 1 && addresses[0].email === "" && + addresses[0].name !== "") { + addresses[0].email = addresses[0].name; + addresses[0].name = ""; + } + emitter.addAddresses(addresses); + } else { + emitter.addUnstructured(aHeader); + } + + // Compute the output. We need to strip off the fake prefix added earlier + // and the extra CRLF at the end. + emitter.finish(true); + let value = handler.value; + value = value.replace(new RegExp(fakeHeader + ":\\s*"), ""); + return value.substring(0, value.length - 2); + }, + + decodeMimeHeader: function (aHeader, aDefaultCharset, aOverride, aUnfold) { + let value = MimeParser.parseHeaderField(aHeader, + MimeParser.HEADER_UNSTRUCTURED | MimeParser.HEADER_OPTION_ALL_I18N, + aDefaultCharset); + if (aUnfold) { + value = value.replace(/[\r\n]\t/g, ' ') + .replace(/[\r\n]/g, ''); + } + return value; + }, + + // This is identical to the above, except for factors that are handled by the + // xpconnect conversion process + decodeMimeHeaderToUTF8: function () { + return this.decodeMimeHeader.apply(this, arguments); + }, +}; + +var components = [MimeHeaders, MimeWritableStructuredHeaders, MimeAddressParser, + MimeConverter]; +var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/mailnews/mime/src/mimeParser.jsm b/mailnews/mime/src/mimeParser.jsm new file mode 100644 index 000000000..904675f10 --- /dev/null +++ b/mailnews/mime/src/mimeParser.jsm @@ -0,0 +1,258 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +// vim:set ts=2 sw=2 sts=2 et ft=javascript: + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource:///modules/jsmime.jsm"); + +var EXPORTED_SYMBOLS = ["MimeParser"]; + +// Emitter helpers, for internal functions later on. +var ExtractHeadersEmitter = { + startPart: function (partNum, headers) { + if (partNum == '') { + this.headers = headers; + } + } +}; + +var ExtractHeadersAndBodyEmitter = { + body: '', + startPart: ExtractHeadersEmitter.startPart, + deliverPartData: function (partNum, data) { + if (partNum == '') + this.body += data; + } +}; + +var Ci = Components.interfaces; +var Cc = Components.classes; + +/// Sets appropriate default options for chrome-privileged environments +function setDefaultParserOptions(opts) { + if (!("onerror" in opts)) { + opts.onerror = Components.utils.reportError; + } +} + +var MimeParser = { + /** + * Triggers an asynchronous parse of the given input. + * + * The input is an input stream; the stream will be read until EOF and then + * closed upon completion. Both blocking and nonblocking streams are + * supported by this implementation, but it is still guaranteed that the first + * callback will not happen before this method returns. + * + * @param input An input stream of text to parse. + * @param emitter The emitter to receive callbacks on. + * @param opts A set of options for the parser. + */ + parseAsync: function MimeParser_parseAsync(input, emitter, opts) { + // Normalize the input into an input stream. + if (!(input instanceof Ci.nsIInputStream)) { + throw new Error("input is not a recognizable type!"); + } + + // We need a pump for the listener + var pump = Cc["@mozilla.org/network/input-stream-pump;1"] + .createInstance(Ci.nsIInputStreamPump); + pump.init(input, -1, -1, 0, 0, true); + + // Make a stream listener with the given emitter and use it to read from + // the pump. + var parserListener = MimeParser.makeStreamListenerParser(emitter, opts); + pump.asyncRead(parserListener, null); + }, + + /** + * Triggers an synchronous parse of the given input. + * + * The input is a string that is immediately parsed, calling all functions on + * the emitter before this function returns. + * + * @param input A string or input stream of text to parse. + * @param emitter The emitter to receive callbacks on. + * @param opts A set of options for the parser. + */ + parseSync: function MimeParser_parseSync(input, emitter, opts) { + // We only support string parsing if we are trying to do this parse + // synchronously. + if (typeof input != "string") { + throw new Error("input is not a recognizable type!"); + } + setDefaultParserOptions(opts); + var parser = new jsmime.MimeParser(emitter, opts); + parser.deliverData(input); + parser.deliverEOF(); + return; + }, + + /** + * Returns a stream listener that feeds data into a parser. + * + * In addition to the functions on the emitter that the parser may use, the + * generated stream listener will also make calls to onStartRequest and + * onStopRequest on the emitter (if they exist). + * + * @param emitter The emitter to receive callbacks on. + * @param opts A set of options for the parser. + */ + makeStreamListenerParser: function MimeParser_makeSLParser(emitter, opts) { + var StreamListener = { + onStartRequest: function SLP_onStartRequest(aRequest, aContext) { + try { + if ("onStartRequest" in emitter) + emitter.onStartRequest(aRequest, aContext); + } finally { + this._parser.resetParser(); + } + }, + onStopRequest: function SLP_onStopRequest(aRequest, aContext, aStatus) { + this._parser.deliverEOF(); + if ("onStopRequest" in emitter) + emitter.onStopRequest(aRequest, aContext, aStatus); + }, + onDataAvailable: function SLP_onData(aRequest, aContext, aStream, + aOffset, aCount) { + var scriptIn = Cc["@mozilla.org/scriptableinputstream;1"] + .createInstance(Ci.nsIScriptableInputStream); + scriptIn.init(aStream); + // Use readBytes instead of read to handle embedded NULs properly. + this._parser.deliverData(scriptIn.readBytes(aCount)); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener, + Ci.nsIRequestObserver]) + }; + setDefaultParserOptions(opts); + StreamListener._parser = new jsmime.MimeParser(emitter, opts); + return StreamListener; + }, + + /** + * Returns a new raw MIME parser. + * + * Prefer one of the other methods where possible, since the input here must + * be driven manually. + * + * @param emitter The emitter to receive callbacks on. + * @param opts A set of options for the parser. + */ + makeParser: function MimeParser_makeParser(emitter, opts) { + setDefaultParserOptions(opts); + return new jsmime.MimeParser(emitter, opts); + }, + + /** + * Returns a dictionary of headers for the given input. + * + * The input is any type of input that would be accepted by parseSync. What + * is returned is a JS object that represents the headers of the entire + * envelope as would be received by startPart when partNum is the empty + * string. + * + * @param input A string of text to parse. + */ + extractHeaders: function MimeParser_extractHeaders(input) { + var emitter = Object.create(ExtractHeadersEmitter); + MimeParser.parseSync(input, emitter, {pruneat: '', bodyformat: 'none'}); + return emitter.headers; + }, + + /** + * Returns the headers and body for the given input message. + * + * The return value is an array whose first element is the dictionary of + * headers (as would be returned by extractHeaders) and whose second element + * is a binary string of the entire body of the message. + * + * @param input A string of text to parse. + */ + extractHeadersAndBody: function MimeParser_extractHeaders(input) { + var emitter = Object.create(ExtractHeadersAndBodyEmitter); + MimeParser.parseSync(input, emitter, {pruneat: '', bodyformat: 'raw'}); + return [emitter.headers, emitter.body]; + }, + + // Parameters for parseHeaderField + + /** + * Parse the header as if it were unstructured. + * + * This results in the same string if no other options are specified. If other + * options are specified, this causes the string to be modified appropriately. + */ + HEADER_UNSTRUCTURED: 0x00, + /** + * Parse the header as if it were in the form text; attr=val; attr=val. + * + * Such headers include Content-Type, Content-Disposition, and most other + * headers used by MIME as opposed to messages. + */ + HEADER_PARAMETER: 0x02, + /** + * Parse the header as if it were a sequence of mailboxes. + */ + HEADER_ADDRESS: 0x03, + + /** + * This decodes parameter values according to RFC 2231. + * + * This flag means nothing if HEADER_PARAMETER is not specified. + */ + HEADER_OPTION_DECODE_2231: 0x10, + /** + * This decodes the inline encoded-words that are in RFC 2047. + */ + HEADER_OPTION_DECODE_2047: 0x20, + /** + * This converts the header from a raw string to proper Unicode. + */ + HEADER_OPTION_ALLOW_RAW: 0x40, + + /// Convenience for all three of the above. + HEADER_OPTION_ALL_I18N: 0x70, + + /** + * Parse a header field according to the specification given by flags. + * + * Permissible flags begin with one of the HEADER_* flags, which may be or'd + * with any of the HEADER_OPTION_* flags to modify the result appropriately. + * + * If the option HEADER_OPTION_ALLOW_RAW is passed, the charset parameter, if + * present, is the charset to fallback to if the header is not decodable as + * UTF-8 text. If HEADER_OPTION_ALLOW_RAW is passed but the charset parameter + * is not provided, then no fallback decoding will be done. If + * HEADER_OPTION_ALLOW_RAW is not passed, then no attempt will be made to + * convert charsets. + * + * @param text The value of a MIME or message header to parse. + * @param flags A set of flags that controls interpretation of the header. + * @param charset A default charset to assume if no information may be found. + */ + parseHeaderField: function MimeParser_parseHeaderField(text, flags, charset) { + // If we have a raw string, convert it to Unicode first + if (flags & MimeParser.HEADER_OPTION_ALLOW_RAW) + text = jsmime.headerparser.convert8BitHeader(text, charset); + + // The low 4 bits indicate the type of the header we are parsing. All of the + // higher-order bits are flags. + switch (flags & 0x0f) { + case MimeParser.HEADER_UNSTRUCTURED: + if (flags & MimeParser.HEADER_OPTION_DECODE_2047) + text = jsmime.headerparser.decodeRFC2047Words(text); + return text; + case MimeParser.HEADER_PARAMETER: + return jsmime.headerparser.parseParameterHeader(text, + (flags & MimeParser.HEADER_OPTION_DECODE_2047) != 0, + (flags & MimeParser.HEADER_OPTION_DECODE_2231) != 0); + case MimeParser.HEADER_ADDRESS: + return jsmime.headerparser.parseAddressingHeader(text, + (flags & MimeParser.HEADER_OPTION_DECODE_2047) != 0); + default: + throw "Illegal type of header field"; + } + }, +}; diff --git a/mailnews/mime/src/mimeTextHTMLParsed.cpp b/mailnews/mime/src/mimeTextHTMLParsed.cpp new file mode 100644 index 000000000..2d45bd2c1 --- /dev/null +++ b/mailnews/mime/src/mimeTextHTMLParsed.cpp @@ -0,0 +1,150 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Most of this code is copied from mimethsa. If you find a bug here, check that class, too. */ + +/* This runs the entire HTML document through the Mozilla HTML parser, and + then outputs it as string again. This ensures that the HTML document is + syntactically correct and complete and all tags and attributes are closed. + + That prevents "MIME in the middle" attacks like efail.de. + The base problem is that we concatenate different MIME parts in the output + and render them all together as a single HTML document in the display. + + The better solution would be to put each MIME part into its own