diff options
Diffstat (limited to 'mailnews/compose/src')
51 files changed, 29875 insertions, 0 deletions
diff --git a/mailnews/compose/src/moz.build b/mailnews/compose/src/moz.build new file mode 100644 index 000000000..33b62c444 --- /dev/null +++ b/mailnews/compose/src/moz.build @@ -0,0 +1,55 @@ +# 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 += [ + 'nsComposeStrings.h', + 'nsMsgAttachmentHandler.h', + 'nsMsgCompFields.h', + 'nsMsgCompose.h', + 'nsMsgSend.h', +] + +SOURCES += [ + 'nsComposeStrings.cpp', + 'nsMsgAttachment.cpp', + 'nsMsgAttachmentHandler.cpp', + 'nsMsgCompFields.cpp', + 'nsMsgCompose.cpp', + 'nsMsgComposeContentHandler.cpp', + 'nsMsgComposeParams.cpp', + 'nsMsgComposeProgressParams.cpp', + 'nsMsgComposeService.cpp', + 'nsMsgCompUtils.cpp', + 'nsMsgCopy.cpp', + 'nsMsgPrompts.cpp', + 'nsMsgQuote.cpp', + 'nsMsgSend.cpp', + 'nsMsgSendLater.cpp', + 'nsMsgSendPart.cpp', + 'nsMsgSendReport.cpp', + 'nsSmtpProtocol.cpp', + 'nsSmtpServer.cpp', + 'nsSmtpService.cpp', + 'nsSmtpUrl.cpp', + 'nsURLFetcher.cpp', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'nsMsgAppleDoubleEncode.cpp', + 'nsMsgAppleEncode.cpp', + ] + EXPORTS += [ + 'nsMsgAppleCodes.h', + 'nsMsgAppleDouble.h', + ] + +EXTRA_COMPONENTS += [ + 'nsSMTPProtocolHandler.js', + 'nsSMTPProtocolHandler.manifest', +] + +FINAL_LIBRARY = 'mail' + diff --git a/mailnews/compose/src/nsComposeStrings.cpp b/mailnews/compose/src/nsComposeStrings.cpp new file mode 100644 index 000000000..5c8565805 --- /dev/null +++ b/mailnews/compose/src/nsComposeStrings.cpp @@ -0,0 +1,116 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsComposeStrings.h" + +const char16_t* errorStringNameForErrorCode(nsresult aCode) +{ +#ifdef __GNUC__ +// Temporary workaroung until bug 783526 is fixed. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" +#endif + switch(aCode) + { + case NS_MSG_UNABLE_TO_OPEN_FILE: + return u"unableToOpenFile"; + case NS_MSG_UNABLE_TO_OPEN_TMP_FILE: + return u"unableToOpenTmpFile"; + case NS_MSG_UNABLE_TO_SAVE_TEMPLATE: + return u"unableToSaveTemplate"; + case NS_MSG_UNABLE_TO_SAVE_DRAFT: + return u"unableToSaveDraft"; + case NS_MSG_COULDNT_OPEN_FCC_FOLDER: + return u"couldntOpenFccFolder"; + case NS_MSG_NO_SENDER: + return u"noSender"; + case NS_MSG_NO_RECIPIENTS: + return u"noRecipients"; + case NS_MSG_ERROR_WRITING_FILE: + return u"errorWritingFile"; + case NS_ERROR_SENDING_FROM_COMMAND: + return u"errorSendingFromCommand"; + case NS_ERROR_SENDING_DATA_COMMAND: + return u"errorSendingDataCommand"; + case NS_ERROR_SENDING_MESSAGE: + return u"errorSendingMessage"; + case NS_ERROR_POST_FAILED: + return u"postFailed"; + case NS_ERROR_QUEUED_DELIVERY_FAILED: + return u"errorQueuedDeliveryFailed"; + case NS_ERROR_SEND_FAILED: + return u"sendFailed"; + case NS_ERROR_SMTP_SERVER_ERROR: + return u"smtpServerError"; + case NS_MSG_UNABLE_TO_SEND_LATER: + return u"unableToSendLater"; + case NS_ERROR_COMMUNICATIONS_ERROR: + return u"communicationsError"; + case NS_ERROR_BUT_DONT_SHOW_ALERT: + return u"dontShowAlert"; + case NS_ERROR_TCP_READ_ERROR: + return u"tcpReadError"; + case NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS: + return u"couldNotGetUsersMailAddress"; + case NS_ERROR_MIME_MPART_ATTACHMENT_ERROR: + return u"mimeMpartAttachmentError"; + case NS_MSG_FAILED_COPY_OPERATION: + return u"failedCopyOperation"; + case NS_ERROR_NNTP_NO_CROSS_POSTING: + return u"nntpNoCrossPosting"; + case NS_MSG_CANCELLING: + return u"msgCancelling"; + case NS_ERROR_SEND_FAILED_BUT_NNTP_OK: + return u"sendFailedButNntpOk"; + case NS_MSG_ERROR_READING_FILE: + return u"errorReadingFile"; + case NS_MSG_ERROR_ATTACHING_FILE: + return u"errorAttachingFile"; + case NS_ERROR_SMTP_GREETING: + return u"incorrectSmtpGreeting"; + case NS_ERROR_SENDING_RCPT_COMMAND: + return u"errorSendingRcptCommand"; + case NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS: + return u"startTlsFailed"; + case NS_ERROR_SMTP_PASSWORD_UNDEFINED: + return u"smtpPasswordUndefined"; + case NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED: + return u"smtpTempSizeExceeded"; + case NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_1: + return u"smtpPermSizeExceeded1"; + case NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2: + return u"smtpPermSizeExceeded2"; + case NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER: + return u"smtpSendFailedUnknownServer"; + case NS_ERROR_SMTP_SEND_FAILED_REFUSED: + return u"smtpSendRequestRefused"; + case NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED: + return u"smtpSendInterrupted"; + case NS_ERROR_SMTP_SEND_FAILED_TIMEOUT: + return u"smtpSendTimeout"; + case NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON: + return u"smtpSendFailedUnknownReason"; + case NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL: + return u"smtpHintAuthEncryptToPlainNoSsl"; + case NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL: + return u"smtpHintAuthEncryptToPlainSsl"; + case NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT: + return u"smtpHintAuthPlainToEncrypt"; + case NS_ERROR_SMTP_AUTH_FAILURE: + return u"smtpAuthFailure"; + case NS_ERROR_SMTP_AUTH_GSSAPI: + return u"smtpAuthGssapi"; + case NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED: + return u"smtpAuthMechNotSupported"; + case NS_ERROR_SMTP_AUTH_NOT_SUPPORTED: + return u"smtpAuthenticationNotSupported"; + case NS_ERROR_ILLEGAL_LOCALPART: + return u"illegalLocalPart"; + default: + return u"sendFailed"; + } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +} diff --git a/mailnews/compose/src/nsComposeStrings.h b/mailnews/compose/src/nsComposeStrings.h new file mode 100644 index 000000000..2a8754493 --- /dev/null +++ b/mailnews/compose/src/nsComposeStrings.h @@ -0,0 +1,77 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + String Ids used by mailnews\compose + To Do: Convert the callers to use names instead of ids and then make this file obsolete. + */ + +#ifndef _nsComposeStrings_H__ +#define _nsComposeStrings_H__ + +#include "msgCore.h" + +#define NS_MSG_UNABLE_TO_OPEN_FILE NS_MSG_GENERATE_FAILURE(12500) +#define NS_MSG_UNABLE_TO_OPEN_TMP_FILE NS_MSG_GENERATE_FAILURE(12501) +#define NS_MSG_UNABLE_TO_SAVE_TEMPLATE NS_MSG_GENERATE_FAILURE(12502) +#define NS_MSG_UNABLE_TO_SAVE_DRAFT NS_MSG_GENERATE_FAILURE(12503) +#define NS_MSG_COULDNT_OPEN_FCC_FOLDER NS_MSG_GENERATE_FAILURE(12506) +#define NS_MSG_NO_SENDER NS_MSG_GENERATE_FAILURE(12510) +#define NS_MSG_NO_RECIPIENTS NS_MSG_GENERATE_FAILURE(12511) +#define NS_MSG_ERROR_WRITING_FILE NS_MSG_GENERATE_FAILURE(12512) +#define NS_ERROR_SENDING_FROM_COMMAND NS_MSG_GENERATE_FAILURE(12514) +#define NS_ERROR_SENDING_DATA_COMMAND NS_MSG_GENERATE_FAILURE(12516) +#define NS_ERROR_SENDING_MESSAGE NS_MSG_GENERATE_FAILURE(12517) +#define NS_ERROR_POST_FAILED NS_MSG_GENERATE_FAILURE(12518) +#define NS_ERROR_QUEUED_DELIVERY_FAILED NS_MSG_GENERATE_FAILURE(12519) +#define NS_ERROR_SEND_FAILED NS_MSG_GENERATE_FAILURE(12520) +#define NS_ERROR_SMTP_SERVER_ERROR NS_MSG_GENERATE_FAILURE(12524) +#define NS_MSG_UNABLE_TO_SEND_LATER NS_MSG_GENERATE_FAILURE(12525) +#define NS_ERROR_COMMUNICATIONS_ERROR NS_MSG_GENERATE_FAILURE(12526) +#define NS_ERROR_BUT_DONT_SHOW_ALERT NS_MSG_GENERATE_FAILURE(12527) +#define NS_ERROR_TCP_READ_ERROR NS_MSG_GENERATE_FAILURE(12528) +#define NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS NS_MSG_GENERATE_FAILURE(12529) +#define NS_ERROR_MIME_MPART_ATTACHMENT_ERROR NS_MSG_GENERATE_FAILURE(12531) +#define NS_MSG_FAILED_COPY_OPERATION NS_MSG_GENERATE_FAILURE(12532) + +/* 12554 is taken by NS_ERROR_NNTP_NO_CROSS_POSTING. use 12555 as the next one */ + +#define NS_MSG_CANCELLING NS_MSG_GENERATE_SUCCESS(12555) + +// For message sending report +#define NS_ERROR_SEND_FAILED_BUT_NNTP_OK NS_MSG_GENERATE_FAILURE(12560) +#define NS_MSG_ERROR_READING_FILE NS_MSG_GENERATE_FAILURE(12563) + +#define NS_MSG_ERROR_ATTACHING_FILE NS_MSG_GENERATE_FAILURE(12570) + +#define NS_ERROR_SMTP_GREETING NS_MSG_GENERATE_FAILURE(12572) + +#define NS_ERROR_SENDING_RCPT_COMMAND NS_MSG_GENERATE_FAILURE(12575) + +#define NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS NS_MSG_GENERATE_FAILURE(12582) + +#define NS_ERROR_SMTP_PASSWORD_UNDEFINED NS_MSG_GENERATE_FAILURE(12584) +#define NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED NS_MSG_GENERATE_FAILURE(12586) +#define NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_1 NS_MSG_GENERATE_FAILURE(12587) +#define NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2 NS_MSG_GENERATE_FAILURE(12588) + +#define NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER NS_MSG_GENERATE_FAILURE(12589) +#define NS_ERROR_SMTP_SEND_FAILED_REFUSED NS_MSG_GENERATE_FAILURE(12590) +#define NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED NS_MSG_GENERATE_FAILURE(12591) +#define NS_ERROR_SMTP_SEND_FAILED_TIMEOUT NS_MSG_GENERATE_FAILURE(12592) +#define NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON NS_MSG_GENERATE_FAILURE(12593) + +#define NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL NS_MSG_GENERATE_FAILURE(12594) +#define NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL NS_MSG_GENERATE_FAILURE(12595) +#define NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT NS_MSG_GENERATE_FAILURE(12596) +#define NS_ERROR_SMTP_AUTH_FAILURE NS_MSG_GENERATE_FAILURE(12597) +#define NS_ERROR_SMTP_AUTH_GSSAPI NS_MSG_GENERATE_FAILURE(12598) +#define NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED NS_MSG_GENERATE_FAILURE(12599) +#define NS_ERROR_SMTP_AUTH_NOT_SUPPORTED NS_MSG_GENERATE_FAILURE(12600) + +#define NS_ERROR_ILLEGAL_LOCALPART NS_MSG_GENERATE_FAILURE(12601) + +const char16_t* errorStringNameForErrorCode(nsresult aCode); + +#endif /* _nsComposeStrings_H__ */ diff --git a/mailnews/compose/src/nsMsgAppleCodes.h b/mailnews/compose/src/nsMsgAppleCodes.h new file mode 100644 index 000000000..d8ca2f327 --- /dev/null +++ b/mailnews/compose/src/nsMsgAppleCodes.h @@ -0,0 +1,106 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* +** AD_Codes.h +** +** --------------- +** +** Head file for Apple Decode/Encode essential codes. +** +** +*/ + +#ifndef ad_codes_h +#define ad_codes_h + +/* +** applefile definitions used +*/ +#if PRAGMA_STRUCT_ALIGN + #pragma options align=mac68k +#endif + +#define APPLESINGLE_MAGIC 0x00051600L +#define APPLEDOUBLE_MAGIC 0x00051607L +#define VERSION 0x00020000 + +#define NUM_ENTRIES 6 + +#define ENT_DFORK 1L +#define ENT_RFORK 2L +#define ENT_NAME 3L +#define ENT_COMMENT 4L +#define ENT_DATES 8L +#define ENT_FINFO 9L +#define CONVERT_TIME 1265437696L + +/* +** data type used in the encoder/decoder. +*/ +typedef struct ap_header +{ + int32_t magic; + int32_t version; + char fill[16]; + int16_t entries; + +} ap_header; + +typedef struct ap_entry +{ + int32_t id; + int32_t offset; + int32_t length; + +} ap_entry; + +typedef struct ap_dates +{ + int32_t create, modify, backup, access; + +} ap_dates; + +typedef struct myFInfo /* the mac FInfo structure for the cross platform. */ +{ + int32_t fdType, fdCreator; + int16_t fdFlags; + int32_t fdLocation; /* it really should be a pointer, but just a place-holder */ + int16_t fdFldr; + +} myFInfo; + +PR_BEGIN_EXTERN_C +/* +** string utils. +*/ +int write_stream(appledouble_encode_object *p_ap_encode_obj, const char *s,int len); + +int fill_apple_mime_header(appledouble_encode_object *p_ap_encode_obj); +int ap_encode_file_infor(appledouble_encode_object *p_ap_encode_obj); +int ap_encode_header(appledouble_encode_object* p_ap_encode_obj, bool firstTime); +int ap_encode_data( appledouble_encode_object* p_ap_encode_obj, bool firstTime); + +/* +** the prototypes for the ap_decoder. +*/ +int fetch_a_line(appledouble_decode_object* p_ap_decode_obj, char *buff); +int ParseFileHeader(appledouble_decode_object* p_ap_decode_obj); +int ap_seek_part_start(appledouble_decode_object* p_ap_decode_obj); +void parse_param(char *p, char **param, char**define, char **np); +int ap_seek_to_boundary(appledouble_decode_object* p_ap_decode_obj, bool firstime); +int ap_parse_header(appledouble_decode_object* p_ap_decode_obj,bool firstime); +int ap_decode_file_infor(appledouble_decode_object* p_ap_decode_obj); +int ap_decode_process_header(appledouble_decode_object* p_ap_decode_obj, bool firstime); +int ap_decode_process_data( appledouble_decode_object* p_ap_decode_obj, bool firstime); + +PR_END_EXTERN_C + +#if PRAGMA_STRUCT_ALIGN + #pragma options align=reset +#endif + +#endif /* ad_codes_h */ diff --git a/mailnews/compose/src/nsMsgAppleDouble.h b/mailnews/compose/src/nsMsgAppleDouble.h new file mode 100644 index 000000000..f4ae934ad --- /dev/null +++ b/mailnews/compose/src/nsMsgAppleDouble.h @@ -0,0 +1,207 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* +* AppleDouble.h +* ------------- +* +* The header file for a stream based apple single/double encodor/decodor. +* +* 2aug95 mym +* +*/ + + +#ifndef AppleDouble_h +#define AppleDouble_h + +#include "msgCore.h" +#include "nsComposeStrings.h" +#include "nsIOutputStream.h" +#include "nsCOMPtr.h" + +#include <CoreServices/CoreServices.h> + +#define NOERR 0 +#define errDone 1 + /* Done with current operation. */ +#define errEOB 2 + /* End of a buffer. */ +#define errEOP 3 + /* End of a Part. */ + + +#define errFileOpen NS_ERROR_GET_CODE(NS_MSG_UNABLE_TO_OPEN_TMP_FILE) +#define errFileWrite -202 /*Error writing temporary file.*/ +#define errUsrCancel -2 /*MK_INTERRUPTED */ +#define errDecoding -1 + +/* +** The envirment block data type. +*/ +enum +{ + kInit, + kDoingHeaderPortion, + kDoneHeaderPortion, + kDoingDataPortion, + kDoneDataPortion +}; + +typedef struct _appledouble_encode_object +{ + char fname[256]; + FSIORefNum fileId; /* the id for the open file (data/resource fork) */ + + int state; + int text_file_type; /* if the file has a text file type with it. */ + char *boundary; /* the boundary string. */ + + int status; /* the error code if anyerror happens. */ + char b_overflow[200]; + int s_overflow; + + int state64; /* the left over state of base64 enocding */ + int ct; /* the character count of base64 encoding */ + int c1, c2; /* the left of the last base64 encoding */ + + char *outbuff; /* the outbuff by the caller. */ + int s_outbuff; /* the size of the buffer. */ + int pos_outbuff; /* the offset in the current buffer. */ + +} appledouble_encode_object; + +/* The possible content transfer encodings */ + +enum +{ + kEncodeNone, + kEncodeQP, + kEncodeBase64, + kEncodeUU +}; + +enum +{ + kGeneralMine, + kAppleDouble, + kAppleSingle +}; + +enum +{ + kInline, + kDontCare +}; + +enum +{ + kHeaderPortion, + kDataPortion +}; + +/* the decode states. */ +enum +{ + kBeginParseHeader = 3, + kParsingHeader, + kBeginSeekBoundary, + kSeekingBoundary, + kBeginHeaderPortion, + kProcessingHeaderPortion, + kBeginDataPortion, + kProcessingDataPortion, + kFinishing +}; + +/* uuencode states */ +enum +{ + kWaitingForBegin = (int) 0, + kBegin, + kMainBody, + kEnd +}; + +typedef struct _appledouble_decode_object +{ + int is_binary; + int is_apple_single; /* if the object encoded is in apple single */ + int write_as_binhex; + + int messagetype; + char* boundary0; /* the boundary for the enclosure. */ + int deposition; /* the deposition. */ + int encoding; /* the encoding method. */ + int which_part; + + char fname[256]; + // nsIOFileStream *fileSpec; /* the stream for data fork work. */ + + int state; + + int rksize; /* the resource fork size count. */ + int dksize; /* the data fork size count. */ + + int status; /* the error code if anyerror happens. */ + char b_leftover[256]; + int s_leftover; + + int encode; /* the encode type of the message. */ + int state64; /* the left over state of base64 enocding */ + int left; /* the character count of base64 encoding */ + int c[4]; /* the left of the last base64 encoding */ + int uu_starts_line; /* is decoder at the start of a line? (uuencode) */ + int uu_state; /* state w/r/t the uuencode body */ + int uu_bytes_written; /* bytes written from the current tuple (uuencode) */ + int uu_line_bytes; /* encoded bytes remaining in the current line (uuencode) */ + + char *inbuff; /* the outbuff by the caller. */ + int s_inbuff; /* the size of the buffer. */ + int pos_inbuff; /* the offset in the current buffer. */ + + + nsCOMPtr <nsIFile> tmpFile; /* the temp file to hold the decode data fork */ + /* when doing the binhex exporting. */ + nsCOMPtr <nsIOutputStream> tmpFileStream; /* The output File Stream */ + int32_t data_size; /* the size of the data in the tmp file. */ + +} appledouble_decode_object; + + +/* +** The protypes. +*/ + +PR_BEGIN_EXTERN_C + +int ap_encode_init(appledouble_encode_object *p_ap_encode_obj, + const char* fname, + char* separator); + +int ap_encode_next(appledouble_encode_object* p_ap_encode_obj, + char *to_buff, + int32_t buff_size, + int32_t* real_size); + +int ap_encode_end(appledouble_encode_object* p_ap_encode_obj, + bool is_aborting); + +int ap_decode_init(appledouble_decode_object* p_ap_decode_obj, + bool is_apple_single, + bool write_as_bin_hex, + void *closure); + +int ap_decode_next(appledouble_decode_object* p_ap_decode_obj, + char *in_buff, + int32_t buff_size); + +int ap_decode_end(appledouble_decode_object* p_ap_decode_obj, + bool is_aborting); + +PR_END_EXTERN_C + +#endif diff --git a/mailnews/compose/src/nsMsgAppleDoubleEncode.cpp b/mailnews/compose/src/nsMsgAppleDoubleEncode.cpp new file mode 100644 index 000000000..4d7178123 --- /dev/null +++ b/mailnews/compose/src/nsMsgAppleDoubleEncode.cpp @@ -0,0 +1,266 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* +* +* apple-double.c +* -------------- +* +* The codes to do apple double encoding/decoding. +* +* 02aug95 mym created. +* +*/ +#include "nsID.h" +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsMsgAppleDouble.h" +#include "nsMsgAppleCodes.h" +#include "nsMsgCompUtils.h" +#include "nsCExternalHandlerService.h" +#include "nsIMIMEService.h" +#include "nsMimeTypes.h" +#include "prmem.h" +#include "nsNetUtil.h" + + +void +MacGetFileType(nsIFile *fs, + bool *useDefault, + char **fileType, + char **encoding) +{ + if ((fs == NULL) || (fileType == NULL) || (encoding == NULL)) + return; + + bool exists = false; + fs->Exists(&exists); + if (!exists) + return; + + *useDefault = TRUE; + *fileType = NULL; + *encoding = NULL; + + nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(fs); + FSRef fsRef; + FSCatalogInfo catalogInfo; + OSErr err = errFileOpen; + if (NS_SUCCEEDED(macFile->GetFSRef(&fsRef))) + err = ::FSGetCatalogInfo(&fsRef, kFSCatInfoFinderInfo, &catalogInfo, nullptr, nullptr, nullptr); + + if ( (err != noErr) || (((FileInfo*)(&catalogInfo.finderInfo))->fileType == 'TEXT') ) + *fileType = strdup(APPLICATION_OCTET_STREAM); + else + { + // At this point, we should call the mime service and + // see what we can find out? + nsresult rv; + nsCOMPtr <nsIURI> tURI; + if (NS_SUCCEEDED(NS_NewFileURI(getter_AddRefs(tURI), fs)) && tURI) + { + nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && mimeFinder) + { + nsAutoCString mimeType; + rv = mimeFinder->GetTypeFromURI(tURI, mimeType); + if (NS_SUCCEEDED(rv)) + { + *fileType = ToNewCString(mimeType); + return; + } + } + } + + // If we hit here, return something...default to this... + *fileType = strdup(APPLICATION_OCTET_STREAM); + } +} + +//#pragma cplusplus reset + +/* +* ap_encode_init +* -------------- +* +* Setup the encode envirment +*/ + +int ap_encode_init( appledouble_encode_object *p_ap_encode_obj, + const char *fname, + char *separator) +{ + nsCOMPtr <nsIFile> myFile; + NS_NewNativeLocalFile(nsDependentCString(fname), true, getter_AddRefs(myFile)); + bool exists; + if (myFile && NS_SUCCEEDED(myFile->Exists(&exists)) && !exists) + return -1; + + nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(myFile); + nsAutoCString path; + macFile->GetNativePath(path); + + memset(p_ap_encode_obj, 0, sizeof(appledouble_encode_object)); + + /* + ** Fill out the source file inforamtion. + */ + memcpy(p_ap_encode_obj->fname, path.get(), path.Length()); + p_ap_encode_obj->fname[path.Length()] = '\0'; + + p_ap_encode_obj->boundary = strdup(separator); + return noErr; +} + +/* +** ap_encode_next +** -------------- +** +** return : +** noErr : everything is ok +** errDone : when encoding is done. +** errors : otherwise. +*/ +int ap_encode_next( + appledouble_encode_object* p_ap_encode_obj, + char *to_buff, + int32_t buff_size, + int32_t* real_size) +{ + int status; + + /* + ** install the out buff now. + */ + p_ap_encode_obj->outbuff = to_buff; + p_ap_encode_obj->s_outbuff = buff_size; + p_ap_encode_obj->pos_outbuff = 0; + + /* + ** first copy the outstandind data in the overflow buff to the out buffer. + */ + if (p_ap_encode_obj->s_overflow) + { + status = write_stream(p_ap_encode_obj, + (const char*)(p_ap_encode_obj->b_overflow), + p_ap_encode_obj->s_overflow); + if (status != noErr) + return status; + + p_ap_encode_obj->s_overflow = 0; + } + + /* + ** go the next processing stage based on the current state. + */ + switch (p_ap_encode_obj->state) + { + case kInit: + /* + ** We are in the starting position, fill out the header. + */ + status = fill_apple_mime_header(p_ap_encode_obj); + if (status != noErr) + break; /* some error happens */ + + p_ap_encode_obj->state = kDoingHeaderPortion; + status = ap_encode_header(p_ap_encode_obj, true); + /* it is the first time to calling */ + if (status == errDone) + { + p_ap_encode_obj->state = kDoneHeaderPortion; + } + else + { + break; /* we need more work on header portion. */ + } + + /* + ** we are done with the header, so let's go to the data port. + */ + p_ap_encode_obj->state = kDoingDataPortion; + status = ap_encode_data(p_ap_encode_obj, true); + /* it is first time call do data portion */ + + if (status == errDone) + { + p_ap_encode_obj->state = kDoneDataPortion; + status = noErr; + } + break; + + case kDoingHeaderPortion: + + status = ap_encode_header(p_ap_encode_obj, false); + /* continue with the header portion. */ + if (status == errDone) + { + p_ap_encode_obj->state = kDoneHeaderPortion; + } + else + { + break; /* we need more work on header portion. */ + } + + /* + ** start the data portion. + */ + p_ap_encode_obj->state = kDoingDataPortion; + status = ap_encode_data(p_ap_encode_obj, true); + /* it is the first time calling */ + if (status == errDone) + { + p_ap_encode_obj->state = kDoneDataPortion; + status = noErr; + } + break; + + case kDoingDataPortion: + + status = ap_encode_data(p_ap_encode_obj, false); + /* it is not the first time */ + + if (status == errDone) + { + p_ap_encode_obj->state = kDoneDataPortion; + status = noErr; + } + break; + + case kDoneDataPortion: + status = errDone; /* we are really done. */ + + break; + } + + *real_size = p_ap_encode_obj->pos_outbuff; + return status; +} + +/* +** ap_encode_end +** ------------- +** +** clear the apple encoding. +*/ + +int ap_encode_end( + appledouble_encode_object *p_ap_encode_obj, + bool is_aborting) +{ + /* + ** clear up the apple doubler. + */ + if (p_ap_encode_obj == NULL) + return noErr; + + if (p_ap_encode_obj->fileId) /* close the file if it is open. */ + ::FSCloseFork(p_ap_encode_obj->fileId); + + PR_FREEIF(p_ap_encode_obj->boundary); /* the boundary string. */ + + return noErr; +} diff --git a/mailnews/compose/src/nsMsgAppleEncode.cpp b/mailnews/compose/src/nsMsgAppleEncode.cpp new file mode 100644 index 000000000..27e39a8cd --- /dev/null +++ b/mailnews/compose/src/nsMsgAppleEncode.cpp @@ -0,0 +1,703 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * + * apple_double_encode.c + * --------------------- + * + * The routines doing the Apple Double Encoding. + * + * 2aug95 mym Created. + * + */ + +#include "nscore.h" +#include "nsStringGlue.h" +#include "nsMimeTypes.h" +#include "prprf.h" +#include "nsServiceManagerUtils.h" +#include "nsMsgAppleDouble.h" +#include "nsMsgAppleCodes.h" +#include "nsILocalFileMac.h" + +/* +** Local Functions prototypes. +*/ +static int output64chunk( appledouble_encode_object* p_ap_encode_obj, + int c1, int c2, int c3, int pads); + +static int to64(appledouble_encode_object* p_ap_encode_obj, + char *p, + int in_size); + +static int finish64(appledouble_encode_object* p_ap_encode_obj); + + +#define BUFF_LEFT(p) ((p)->s_outbuff - (p)->pos_outbuff) + +/* +** write_stream. +*/ +int write_stream( + appledouble_encode_object *p_ap_encode_obj, + const char *out_string, + int len) +{ + if (p_ap_encode_obj->pos_outbuff + len < p_ap_encode_obj->s_outbuff) + { + memcpy(p_ap_encode_obj->outbuff + p_ap_encode_obj->pos_outbuff, + out_string, + len); + p_ap_encode_obj->pos_outbuff += len; + return noErr; + } + else + { + /* + ** If the buff doesn't have enough space, use the overflow buffer then. + */ + int s_len = p_ap_encode_obj->s_outbuff - p_ap_encode_obj->pos_outbuff; + + memcpy(p_ap_encode_obj->outbuff + p_ap_encode_obj->pos_outbuff, + out_string, + s_len); + memcpy(p_ap_encode_obj->b_overflow + p_ap_encode_obj->s_overflow, + out_string + s_len, + p_ap_encode_obj->s_overflow += (len - s_len)); + p_ap_encode_obj->pos_outbuff += s_len; + return errEOB; + } +} + +int fill_apple_mime_header( + appledouble_encode_object *p_ap_encode_obj) +{ + int status; + + char tmpstr[266]; + +#if 0 +// strcpy(tmpstr, "Content-Type: multipart/mixed; boundary=\"-\"\n\n---\n"); +// status = write_stream(p_ap_encode_env, +// tmpstr, +// strlen(tmpstr)); +// if (status != noErr) +// return status; + + PR_snprintf(tmpstr, sizeof(tmpstr), + "Content-Type: multipart/appledouble; boundary=\"=\"; name=\""); + status = write_stream(p_ap_encode_obj, (const char*)tmpstr, strlen(tmpstr)); + if (status != noErr) + return status; + + status = write_stream(p_ap_encode_obj, + p_ap_encode_obj->fname, + strlen(p_ap_encode_obj->fname)); + if (status != noErr) + return status; + + PR_snprintf(tmpstr, sizeof(tmpstr), + "\"\r\nContent-Disposition: inline; filename=\"%s\"\r\n\r\n\r\n--=\r\n", + p_ap_encode_obj->fname); +#endif /* 0 */ + PR_snprintf(tmpstr, sizeof(tmpstr), "--%s" CRLF, p_ap_encode_obj->boundary); + status = write_stream(p_ap_encode_obj, (const char*)tmpstr, strlen(tmpstr)); + return status; +} + +int ap_encode_file_infor( + appledouble_encode_object *p_ap_encode_obj) +{ + ap_header head; + ap_entry entries[NUM_ENTRIES]; + ap_dates dates; + short i; + long comlen; + char comment[256]; + int status; + + nsCOMPtr <nsIFile> resFile; + NS_NewNativeLocalFile(nsDependentCString(p_ap_encode_obj->fname), true, + getter_AddRefs(resFile)); + if (!resFile) + return errFileOpen; + + FSRef ref; + nsCOMPtr <nsILocalFileMac> macFile = do_QueryInterface(resFile); + if (NS_FAILED(macFile->GetFSRef(&ref))) + return errFileOpen; + + FSCatalogInfo catalogInfo; + if (::FSGetCatalogInfo(&ref, kFSCatInfoFinderInfo, &catalogInfo, nullptr, nullptr, nullptr) != noErr) + { + return errFileOpen; + } + + /* get a file comment, if possible */ +#if 1 + // Carbon doesn't support GetWDInfo(). (Bug 555684) + + // not sure why working directories are needed here... + comlen = 0; +#else + long procID; + procID = 0; + GetWDInfo(p_ap_encode_obj->vRefNum, &fpb->ioVRefNum, &fpb->ioDirID, &procID); + IOParam vinfo; + memset((void *) &vinfo, '\0', sizeof (vinfo)); + GetVolParmsInfoBuffer vp; + vinfo.ioCompletion = nil; + vinfo.ioVRefNum = fpb->ioVRefNum; + vinfo.ioBuffer = (Ptr) &vp; + vinfo.ioReqCount = sizeof (vp); + comlen = 0; + if (PBHGetVolParmsSync((HParmBlkPtr) &vinfo) == noErr && + ((vp.vMAttrib >> bHasDesktopMgr) & 1)) + { + DTPBRec dtp; + memset((void *) &dtp, '\0', sizeof (dtp)); + dtp.ioVRefNum = fpb->ioVRefNum; + if (PBDTGetPath(&dtp) == noErr) + { + dtp.ioCompletion = nil; + dtp.ioDTBuffer = (Ptr) comment; + dtp.ioNamePtr = fpb->ioNamePtr; + dtp.ioDirID = fpb->ioFlParID; + if (PBDTGetCommentSync(&dtp) == noErr) + comlen = dtp.ioDTActCount; + } + } +#endif /* ! 1 */ + + /* write header */ +// head.magic = dfork ? APPLESINGLE_MAGIC : APPLEDOUBLE_MAGIC; + head.magic = APPLEDOUBLE_MAGIC; /* always do apple double */ + head.version = VERSION; + memset(head.fill, '\0', sizeof (head.fill)); + head.entries = NUM_ENTRIES - 1; + status = to64(p_ap_encode_obj, + (char *) &head, + sizeof (head)); + if (status != noErr) + return status; + + /* write entry descriptors */ + nsAutoCString leafname; + macFile->GetNativeLeafName(leafname); + entries[0].offset = sizeof (head) + sizeof (ap_entry) * head.entries; + entries[0].id = ENT_NAME; + entries[0].length = leafname.Length(); + entries[1].id = ENT_FINFO; + entries[1].length = sizeof (FInfo) + sizeof (FXInfo); + entries[2].id = ENT_DATES; + entries[2].length = sizeof (ap_dates); + entries[3].id = ENT_COMMENT; + entries[3].length = comlen; + entries[4].id = ENT_RFORK; + entries[4].length = catalogInfo.rsrcLogicalSize; + entries[5].id = ENT_DFORK; + entries[5].length = catalogInfo.dataLogicalSize; + + /* correct the link in the entries. */ + for (i = 1; i < NUM_ENTRIES; ++i) + { + entries[i].offset = entries[i-1].offset + entries[i-1].length; + } + status = to64(p_ap_encode_obj, + (char *) entries, + sizeof (ap_entry) * head.entries); + if (status != noErr) + return status; + + /* write name */ + status = to64(p_ap_encode_obj, + (char *) leafname.get(), + leafname.Length()); + if (status != noErr) + return status; + + /* write finder info */ + status = to64(p_ap_encode_obj, + (char *) &catalogInfo.finderInfo, + sizeof (FInfo)); + if (status != noErr) + return status; + + status = to64(p_ap_encode_obj, + (char *) &catalogInfo.extFinderInfo, + sizeof (FXInfo)); + if (status != noErr) + return status; + + /* write dates */ + dates.create = catalogInfo.createDate.lowSeconds + CONVERT_TIME; + dates.modify = catalogInfo.contentModDate.lowSeconds + CONVERT_TIME; + dates.backup = catalogInfo.backupDate.lowSeconds + CONVERT_TIME; + dates.access = catalogInfo.accessDate.lowSeconds + CONVERT_TIME; + status = to64(p_ap_encode_obj, + (char *) &dates, + sizeof (ap_dates)); + if (status != noErr) + return status; + + /* write comment */ + if (comlen) + { + status = to64(p_ap_encode_obj, + comment, + comlen * sizeof(char)); + } + /* + ** Get some help information on deciding the file type. + */ + if (((FileInfo*)(&catalogInfo.finderInfo))->fileType == 'TEXT' || + ((FileInfo*)(&catalogInfo.finderInfo))->fileType == 'text') + { + p_ap_encode_obj->text_file_type = true; + } + + return status; +} +/* +** ap_encode_header +** +** encode the file header and the resource fork. +** +*/ +int ap_encode_header( + appledouble_encode_object* p_ap_encode_obj, + bool firstime) +{ + char rd_buff[256]; + FSIORefNum fileId; + OSErr retval = noErr; + int status; + ByteCount inCount; + + if (firstime) + { + PL_strcpy(rd_buff, + "Content-Type: application/applefile\r\nContent-Transfer-Encoding: base64\r\n\r\n"); + status = write_stream(p_ap_encode_obj, (const char*)rd_buff, strlen(rd_buff)); + if (status != noErr) + return status; + + status = ap_encode_file_infor(p_ap_encode_obj); + if (status != noErr) + return status; + + /* + ** preparing to encode the resource fork. + */ + nsCOMPtr <nsIFile> myFile; + NS_NewNativeLocalFile(nsDependentCString(p_ap_encode_obj->fname), true, getter_AddRefs(myFile)); + if (!myFile) + return errFileOpen; + + FSRef ref; + nsCOMPtr <nsILocalFileMac> macFile = do_QueryInterface(myFile); + if (NS_FAILED(macFile->GetFSRef(&ref))) + return errFileOpen; + + HFSUniStr255 forkName; + ::FSGetResourceForkName(&forkName); + retval = ::FSOpenFork(&ref, forkName.length, forkName.unicode, fsRdPerm, &p_ap_encode_obj->fileId); + if (retval != noErr) + return retval; + } + + fileId = p_ap_encode_obj->fileId; + while (retval == noErr) + { + if (BUFF_LEFT(p_ap_encode_obj) < 400) + break; + + inCount = 0; + retval = ::FSReadFork(fileId, fsAtMark, 0, 256, rd_buff, &inCount); + if (inCount) + { + status = to64(p_ap_encode_obj, + rd_buff, + inCount); + if (status != noErr) + return status; + } + } + + if (retval == eofErr) + { + ::FSCloseFork(fileId); + p_ap_encode_obj->fileId = 0; + + status = finish64(p_ap_encode_obj); + if (status != noErr) + return status; + + /* + ** write out the boundary + */ + PR_snprintf(rd_buff, sizeof(rd_buff), + CRLF "--%s" CRLF, + p_ap_encode_obj->boundary); + + status = write_stream(p_ap_encode_obj, (const char*)rd_buff, strlen(rd_buff)); + if (status == noErr) + status = errDone; + } + return status; +} + +#if 0 +// This is unused for now and Clang complains about that is it is ifdefed out +static void replace(char *p, int len, char frm, char to) +{ + for (; len > 0; len--, p++) + if (*p == frm) *p = to; +} +#endif + +/* Description of the various file formats and their magic numbers */ +struct magic +{ + const char *name; /* Name of the file format */ + const char *num; /* The magic number */ + int len; /* Length (0 means strlen(magicnum)) */ +}; + +/* The magic numbers of the file formats we know about */ +static struct magic magic[] = +{ + { "image/gif", "GIF", 0 }, + { "image/jpeg", "\377\330\377", 0 }, + { "video/mpeg", "\0\0\001\263", 4 }, + { "application/postscript", "%!", 0 }, +}; +static int num_magic = MOZ_ARRAY_LENGTH(magic); + +static const char *text_type = TEXT_PLAIN; /* the text file type. */ +static const char *default_type = APPLICATION_OCTET_STREAM; + + +/* + * Determins the format of the file "inputf". The name + * of the file format (or NULL on error) is returned. + */ +static const char *magic_look(char *inbuff, int numread) +{ + int i, j; + + for (i=0; i<num_magic; i++) + { + if (magic[i].len == 0) + magic[i].len = strlen(magic[i].num); + } + + for (i=0; i<num_magic; i++) + { + if (numread >= magic[i].len) + { + for (j=0; j<magic[i].len; j++) + { + if (inbuff[j] != magic[i].num[j]) break; + } + + if (j == magic[i].len) + return magic[i].name; + } + } + + return default_type; +} +/* +** ap_encode_data +** +** --------------- +** +** encode on the data fork. +** +*/ +int ap_encode_data( + appledouble_encode_object* p_ap_encode_obj, + bool firstime) +{ + char rd_buff[256]; + FSIORefNum fileId; + OSErr retval = noErr; + ByteCount in_count; + int status; + + if (firstime) + { + const char* magic_type; + + /* + ** preparing to encode the data fork. + */ + nsCOMPtr <nsIFile> resFile; + NS_NewNativeLocalFile(nsDependentCString(p_ap_encode_obj->fname), true, + getter_AddRefs(resFile)); + if (!resFile) + return errFileOpen; + + FSRef ref; + nsCOMPtr <nsILocalFileMac> macFile = do_QueryInterface(resFile); + if (NS_FAILED(macFile->GetFSRef(&ref))) + return errFileOpen; + + HFSUniStr255 forkName; + ::FSGetDataForkName(&forkName); + retval = ::FSOpenFork(&ref, forkName.length, forkName.unicode, fsRdPerm, &fileId); + if (retval != noErr) + return retval; + + p_ap_encode_obj->fileId = fileId; + + + if (!p_ap_encode_obj->text_file_type) + { + /* + ** do a smart check for the file type. + */ + in_count = 0; + retval = ::FSReadFork(fileId, fsFromStart, 0, 256, rd_buff, &in_count); + magic_type = magic_look(rd_buff, in_count); + + /* don't forget to rewind the index to start point. */ + ::FSSetForkPosition(fileId, fsFromStart, 0); + /* and reset retVal just in case... */ + if (retval == eofErr) + retval = noErr; + } + else + { + magic_type = text_type; /* we already know it is a text type. */ + } + + /* + ** the data portion header information. + */ + nsAutoCString leafName; + resFile->GetNativeLeafName(leafName); + PR_snprintf(rd_buff, sizeof(rd_buff), + "Content-Type: %s; name=\"%s\"" CRLF "Content-Transfer-Encoding: base64" CRLF "Content-Disposition: inline; filename=\"%s\"" CRLF CRLF, + magic_type, + leafName.get(), + leafName.get()); + + status = write_stream(p_ap_encode_obj, (const char*)rd_buff, strlen(rd_buff)); + if (status != noErr) + return status; + } + + while (retval == noErr) + { + if (BUFF_LEFT(p_ap_encode_obj) < 400) + break; + + in_count = 0; + retval = ::FSReadFork(p_ap_encode_obj->fileId, fsAtMark, 0, 256, rd_buff, &in_count); + if (in_count) + { +#if 0 +/* replace(rd_buff, in_count, '\r', '\n'); */ +#endif +/* ** may be need to do character set conversion here for localization. ** */ + status = to64(p_ap_encode_obj, + rd_buff, + in_count); + if (status != noErr) + return status; + } + } + + if (retval == eofErr) + { + ::FSCloseFork(p_ap_encode_obj->fileId); + p_ap_encode_obj->fileId = 0; + + status = finish64(p_ap_encode_obj); + if (status != noErr) + return status; + + /* write out the boundary */ + + PR_snprintf(rd_buff, sizeof(rd_buff), + CRLF "--%s--" CRLF CRLF, + p_ap_encode_obj->boundary); + + status = write_stream(p_ap_encode_obj, (const char*)rd_buff, strlen(rd_buff)); + + if (status == noErr) + status = errDone; + } + return status; +} + +static char basis_64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* +** convert the stream in the inbuff to 64 format and put it in the out buff. +** To make the life easier, the caller will responcable of the cheking of the outbuff's bundary. +*/ +static int +to64(appledouble_encode_object* p_ap_encode_obj, + char *p, + int in_size) +{ + int status; + int c1, c2, c3, ct; + unsigned char *inbuff = (unsigned char*)p; + + ct = p_ap_encode_obj->ct; /* the char count left last time. */ + + /* + ** resume the left state of the last conversion. + */ + switch (p_ap_encode_obj->state64) + { + case 0: + p_ap_encode_obj->c1 = c1 = *inbuff ++; + if (--in_size <= 0) + { + p_ap_encode_obj->state64 = 1; + return noErr; + } + p_ap_encode_obj->c2 = c2 = *inbuff ++; + if (--in_size <= 0) + { + p_ap_encode_obj->state64 = 2; + return noErr; + } + c3 = *inbuff ++; --in_size; + break; + case 1: + c1 = p_ap_encode_obj->c1; + p_ap_encode_obj->c2 = c2 = *inbuff ++; + if (--in_size <= 0) + { + p_ap_encode_obj->state64 = 2; + return noErr; + } + c3 = *inbuff ++; --in_size; + break; + case 2: + c1 = p_ap_encode_obj->c1; + c2 = p_ap_encode_obj->c2; + c3 = *inbuff ++; --in_size; + break; + } + + while (in_size >= 0) + { + status = output64chunk(p_ap_encode_obj, + c1, + c2, + c3, + 0); + if (status != noErr) + return status; + + ct += 4; + if (ct > 71) + { + status = write_stream(p_ap_encode_obj, + CRLF, + 2); + if (status != noErr) + return status; + + ct = 0; + } + + if (in_size <= 0) + { + p_ap_encode_obj->state64 = 0; + break; + } + + c1 = (int)*inbuff++; + if (--in_size <= 0) + { + p_ap_encode_obj->c1 = c1; + p_ap_encode_obj->state64 = 1; + break; + } + c2 = *inbuff++; + if (--in_size <= 0) + { + p_ap_encode_obj->c1 = c1; + p_ap_encode_obj->c2 = c2; + p_ap_encode_obj->state64 = 2; + break; + } + c3 = *inbuff++; + in_size--; + } + p_ap_encode_obj->ct = ct; + return status; +} + +/* +** clear the left base64 encodes. +*/ +static int +finish64(appledouble_encode_object* p_ap_encode_obj) +{ + int status; + + switch (p_ap_encode_obj->state64) + { + case 0: + break; + case 1: + status = output64chunk(p_ap_encode_obj, + p_ap_encode_obj->c1, + 0, + 0, + 2); + break; + case 2: + status = output64chunk(p_ap_encode_obj, + p_ap_encode_obj->c1, + p_ap_encode_obj->c2, + 0, + 1); + break; + } + status = write_stream(p_ap_encode_obj, CRLF, 2); + p_ap_encode_obj->state64 = 0; + p_ap_encode_obj->ct = 0; + return status; +} + +static int output64chunk( + appledouble_encode_object* p_ap_encode_obj, + int c1, int c2, int c3, int pads) +{ + char tmpstr[32]; + char *p = tmpstr; + + *p++ = basis_64[c1>>2]; + *p++ = basis_64[((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)]; + if (pads == 2) + { + *p++ = '='; + *p++ = '='; + } + else if (pads) + { + *p++ = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)]; + *p++ = '='; + } + else + { + *p++ = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)]; + *p++ = basis_64[c3 & 0x3F]; + } + return write_stream(p_ap_encode_obj, (const char*) tmpstr, p-tmpstr); +} diff --git a/mailnews/compose/src/nsMsgAttachment.cpp b/mailnews/compose/src/nsMsgAttachment.cpp new file mode 100644 index 000000000..3a828c3b0 --- /dev/null +++ b/mailnews/compose/src/nsMsgAttachment.cpp @@ -0,0 +1,262 @@ +/* -*- 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 "nsMsgAttachment.h" +#include "nsIFile.h" +#include "nsNetUtil.h" + +NS_IMPL_ISUPPORTS(nsMsgAttachment, nsIMsgAttachment) + +nsMsgAttachment::nsMsgAttachment() +{ + mTemporary = false; + mSendViaCloud = false; + mSize = -1; +} + +nsMsgAttachment::~nsMsgAttachment() +{ + if (mTemporary && !mSendViaCloud) + (void)DeleteAttachment(); +} + +/* attribute wstring name; */ +NS_IMETHODIMP nsMsgAttachment::GetName(nsAString & aName) +{ + aName = mName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::SetName(const nsAString & aName) +{ + mName = aName; + return NS_OK; +} + +/* attribute string url; */ +NS_IMETHODIMP nsMsgAttachment::GetUrl(nsACString & aUrl) +{ + aUrl = mUrl; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::SetUrl(const nsACString & aUrl) +{ + mUrl = aUrl; + return NS_OK; +} + +/* attribute string urlCharset; */ +NS_IMETHODIMP nsMsgAttachment::GetUrlCharset(nsACString & aUrlCharset) +{ + aUrlCharset = mUrlCharset; + return NS_OK; +} +NS_IMETHODIMP nsMsgAttachment::SetUrlCharset(const nsACString & aUrlCharset) +{ + mUrlCharset = aUrlCharset; + return NS_OK; +} + +/* attribute boolean temporary; */ +NS_IMETHODIMP nsMsgAttachment::GetTemporary(bool *aTemporary) +{ + NS_ENSURE_ARG_POINTER(aTemporary); + + *aTemporary = mTemporary; + return NS_OK; +} +NS_IMETHODIMP nsMsgAttachment::SetTemporary(bool aTemporary) +{ + mTemporary = aTemporary; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::GetSendViaCloud(bool *aSendViaCloud) +{ + NS_ENSURE_ARG_POINTER(aSendViaCloud); + + *aSendViaCloud = mSendViaCloud; + return NS_OK; +} +NS_IMETHODIMP nsMsgAttachment::SetSendViaCloud(bool aSendViaCloud) +{ + mSendViaCloud = aSendViaCloud; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::SetHtmlAnnotation(const nsAString &aAnnotation) +{ + mHtmlAnnotation = aAnnotation; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::GetHtmlAnnotation(nsAString &aAnnotation) +{ + aAnnotation = mHtmlAnnotation; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAttachment::SetCloudProviderKey(const nsACString &aCloudProviderKey) +{ + mCloudProviderKey = aCloudProviderKey; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAttachment::GetCloudProviderKey(nsACString &aCloudProviderKey) +{ + aCloudProviderKey = mCloudProviderKey; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::GetContentLocation(nsACString &aContentLocation) +{ + aContentLocation = mContentLocation; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::SetContentLocation(const nsACString &aContentLocation) +{ + mContentLocation = aContentLocation; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::GetContentType(char * *aContentType) +{ + NS_ENSURE_ARG_POINTER(aContentType); + + *aContentType = ToNewCString(mContentType); + return (*aContentType ? NS_OK : NS_ERROR_OUT_OF_MEMORY); +} + +NS_IMETHODIMP nsMsgAttachment::SetContentType(const char * aContentType) +{ + mContentType = aContentType; + /* a full content type could also contains parameters but we need to + keep only the content type alone. Therefore we need to cleanup it. + */ + int32_t offset = mContentType.FindChar(';'); + if (offset >= 0) + mContentType.SetLength(offset); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachment::GetContentTypeParam(char * *aContentTypeParam) +{ + NS_ENSURE_ARG_POINTER(aContentTypeParam); + + *aContentTypeParam = ToNewCString(mContentTypeParam); + return (*aContentTypeParam ? NS_OK : NS_ERROR_OUT_OF_MEMORY); +} + +NS_IMETHODIMP nsMsgAttachment::SetContentTypeParam(const char * aContentTypeParam) +{ + if (aContentTypeParam) + while (*aContentTypeParam == ';' || *aContentTypeParam == ' ') + aContentTypeParam ++; + mContentTypeParam = aContentTypeParam; + + return NS_OK; +} + +/* attribute string charset; */ +NS_IMETHODIMP nsMsgAttachment::GetCharset(char * *aCharset) +{ + NS_ENSURE_ARG_POINTER(aCharset); + + *aCharset = ToNewCString(mCharset); + return (*aCharset ? NS_OK : NS_ERROR_OUT_OF_MEMORY); +} +NS_IMETHODIMP nsMsgAttachment::SetCharset(const char * aCharset) +{ + mCharset = aCharset; + return NS_OK; +} + +/* attribute string macType; */ +NS_IMETHODIMP nsMsgAttachment::GetMacType(char * *aMacType) +{ + NS_ENSURE_ARG_POINTER(aMacType); + + *aMacType = ToNewCString(mMacType); + return (*aMacType ? NS_OK : NS_ERROR_OUT_OF_MEMORY); +} +NS_IMETHODIMP nsMsgAttachment::SetMacType(const char * aMacType) +{ + mMacType = aMacType; + return NS_OK; +} + +/* attribute string macCreator; */ +NS_IMETHODIMP nsMsgAttachment::GetMacCreator(char * *aMacCreator) +{ + NS_ENSURE_ARG_POINTER(aMacCreator); + + *aMacCreator = ToNewCString(mMacCreator); + return (*aMacCreator ? NS_OK : NS_ERROR_OUT_OF_MEMORY); +} +NS_IMETHODIMP nsMsgAttachment::SetMacCreator(const char * aMacCreator) +{ + mMacCreator = aMacCreator; + return NS_OK; +} + +/* attribute int64_t size; */ +NS_IMETHODIMP nsMsgAttachment::GetSize(int64_t *aSize) +{ + NS_ENSURE_ARG_POINTER(aSize); + + *aSize = mSize; + return NS_OK; +} +NS_IMETHODIMP nsMsgAttachment::SetSize(int64_t aSize) +{ + mSize = aSize; + return NS_OK; +} + +/* boolean equalsUrl (in nsIMsgAttachment attachment); */ +NS_IMETHODIMP nsMsgAttachment::EqualsUrl(nsIMsgAttachment *attachment, bool *_retval) +{ + NS_ENSURE_ARG_POINTER(attachment); + NS_ENSURE_ARG_POINTER(_retval); + + nsAutoCString url; + attachment->GetUrl(url); + + *_retval = mUrl.Equals(url); + return NS_OK; +} + + +nsresult nsMsgAttachment::DeleteAttachment() +{ + nsresult rv; + bool isAFile = false; + + nsCOMPtr<nsIFile> urlFile; + rv = NS_GetFileFromURLSpec(mUrl, getter_AddRefs(urlFile)); + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't nsIFile from URL string"); + if (NS_SUCCEEDED(rv)) + { + bool bExists = false; + rv = urlFile->Exists(&bExists); + NS_ASSERTION(NS_SUCCEEDED(rv), "Exists() call failed!"); + if (NS_SUCCEEDED(rv) && bExists) + { + rv = urlFile->IsFile(&isAFile); + NS_ASSERTION(NS_SUCCEEDED(rv), "IsFile() call failed!"); + } + } + + // remove it if it's a valid file + if (isAFile) + rv = urlFile->Remove(false); + + return rv; +} diff --git a/mailnews/compose/src/nsMsgAttachment.h b/mailnews/compose/src/nsMsgAttachment.h new file mode 100644 index 000000000..9f0d0a3b0 --- /dev/null +++ b/mailnews/compose/src/nsMsgAttachment.h @@ -0,0 +1,41 @@ +/* -*- 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 _nsMsgAttachment_H_ +#define _nsMsgAttachment_H_ + +#include "nsIMsgAttachment.h" +#include "nsStringGlue.h" + +class nsMsgAttachment : public nsIMsgAttachment +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGATTACHMENT + + nsMsgAttachment(); + +private: + virtual ~nsMsgAttachment(); + nsresult DeleteAttachment(); + + nsString mName; + nsCString mUrl; + nsCString mUrlCharset; + bool mTemporary; + bool mSendViaCloud; + nsCString mCloudProviderKey; + nsCString mContentLocation; + nsCString mContentType; + nsCString mContentTypeParam; + nsCString mCharset; + nsCString mMacType; + nsCString mMacCreator; + nsString mHtmlAnnotation; + int64_t mSize; +}; + + +#endif /* _nsMsgAttachment_H_ */ diff --git a/mailnews/compose/src/nsMsgAttachmentHandler.cpp b/mailnews/compose/src/nsMsgAttachmentHandler.cpp new file mode 100644 index 000000000..f36f2e29b --- /dev/null +++ b/mailnews/compose/src/nsMsgAttachmentHandler.cpp @@ -0,0 +1,1383 @@ +/* -*- 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 "nsIPrefLocalizedString.h" +#include "nsILineInputStream.h" +#include "nsMsgAttachmentHandler.h" +#include "prmem.h" +#include "nsMsgCopy.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsMsgSend.h" +#include "nsMsgCompUtils.h" +#include "nsMsgI18N.h" +#include "nsURLFetcher.h" +#include "nsMimeTypes.h" +#include "nsMsgCompCID.h" +#include "nsIMsgMessageService.h" +#include "nsMsgUtils.h" +#include "nsMsgPrompts.h" +#include "nsTextFormatter.h" +#include "nsIPrompt.h" +#include "nsITextToSubURI.h" +#include "nsIURL.h" +#include "nsIFileURL.h" +#include "nsNetCID.h" +#include "nsIMimeStreamConverter.h" +#include "nsMsgMimeCID.h" +#include "nsNetUtil.h" +#include "nsNativeCharsetUtils.h" +#include "nsComposeStrings.h" +#include "nsIZipWriter.h" +#include "nsIDirectoryEnumerator.h" +#include "mozilla/Services.h" +#include "mozilla/mailnews/MimeEncoder.h" +#include "nsIPrincipal.h" + +/////////////////////////////////////////////////////////////////////////// +// Mac Specific Attachment Handling for AppleDouble Encoded Files +/////////////////////////////////////////////////////////////////////////// +#ifdef XP_MACOSX + +#define AD_WORKING_BUFF_SIZE FILE_IO_BUFFER_SIZE + +extern void MacGetFileType(nsIFile *fs, bool *useDefault, char **type, char **encoding); + +#include "nsILocalFileMac.h" + +/* static */ +nsresult nsSimpleZipper::Zip(nsIFile *aInputFile, nsIFile *aOutputFile) +{ + // create zipwriter object + nsresult rv; + nsCOMPtr<nsIZipWriter> zipWriter = do_CreateInstance("@mozilla.org/zipwriter;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = zipWriter->Open(aOutputFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AddToZip(zipWriter, aInputFile, EmptyCString()); + NS_ENSURE_SUCCESS(rv, rv); + + // we're done. + zipWriter->Close(); + return rv; +} + +/* static */ +nsresult nsSimpleZipper::AddToZip(nsIZipWriter *aZipWriter, + nsIFile *aFile, + const nsACString &aPath) +{ + // find out the path this file/dir should have in the zip + nsCString leafName; + aFile->GetNativeLeafName(leafName); + nsCString currentPath(aPath); + currentPath += leafName; + + bool isDirectory; + aFile->IsDirectory(&isDirectory); + // append slash for a directory entry + if (isDirectory) + currentPath.Append('/'); + + // add the file or directory entry to the zip + nsresult rv = aZipWriter->AddEntryFile(currentPath, nsIZipWriter::COMPRESSION_DEFAULT, aFile, false); + NS_ENSURE_SUCCESS(rv, rv); + + // if it's a directory, add all its contents too + if (isDirectory) { + nsCOMPtr<nsISimpleEnumerator> e; + nsresult rv = aFile->GetDirectoryEntries(getter_AddRefs(e)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator = do_QueryInterface(e, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> currentFile; + while (NS_SUCCEEDED(dirEnumerator->GetNextFile(getter_AddRefs(currentFile))) && currentFile) { + rv = AddToZip(aZipWriter, currentFile, currentPath); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} +#endif // XP_MACOSX + +// +// Class implementation... +// +nsMsgAttachmentHandler::nsMsgAttachmentHandler() : + mRequest(nullptr), + mCompFields(nullptr), // Message composition fields for the sender + m_bogus_attachment(false), + m_done(false), + m_already_encoded_p(false), + m_decrypted_p(false), + mDeleteFile(false), + mMHTMLPart(false), + mPartUserOmissionOverride(false), + mMainBody(false), + mSendViaCloud(false), + mNodeIndex(-1), + // For analyzing the attachment file... + m_size(0), + m_unprintable_count(0), + m_highbit_count(0), + m_ctl_count(0), + m_null_count(0), + m_have_cr(0), + m_have_lf(0), + m_have_crlf(0), + m_prev_char_was_cr(false), + m_current_column(0), + m_max_column(0), + m_lines(0), + m_file_analyzed(false), + + // Mime + m_encoder(nullptr) +{ +} + +nsMsgAttachmentHandler::~nsMsgAttachmentHandler() +{ + if (mTmpFile && mDeleteFile) + mTmpFile->Remove(false); + + if (mOutFile) + mOutFile->Close(); + + CleanupTempFile(); +} + +NS_IMPL_ISUPPORTS(nsMsgAttachmentHandler, nsIMsgAttachmentHandler) + +// nsIMsgAttachmentHandler implementation. + +NS_IMETHODIMP nsMsgAttachmentHandler::GetType(nsACString& aType) +{ + aType.Assign(m_type); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentHandler::GetUri(nsACString& aUri) +{ + nsAutoCString turl; + if (!mURL) + { + if (!m_uri.IsEmpty()) + turl = m_uri; + } + else + { + nsresult rv = mURL->GetSpec(turl); + NS_ENSURE_SUCCESS(rv, rv); + } + aUri.Assign(turl); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentHandler::GetTmpFile(nsIFile **aTmpFile) +{ + NS_ENSURE_ARG_POINTER(aTmpFile); + if (!mTmpFile) + return NS_ERROR_FAILURE; + NS_ADDREF(*aTmpFile = mTmpFile); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentHandler::GetName(nsACString& aName) +{ + aName.Assign(m_realName); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentHandler::GetSize(uint32_t *aSize) +{ + NS_ENSURE_ARG_POINTER(aSize); + *aSize = m_size; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentHandler::GetContentId(nsACString& aContentId) +{ + aContentId.Assign(m_contentId); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentHandler::GetSendViaCloud(bool* aSendViaCloud) +{ + NS_ENSURE_ARG_POINTER(aSendViaCloud); + *aSendViaCloud = mSendViaCloud; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentHandler::GetCharset(nsACString& aCharset) +{ + aCharset.Assign(m_charset); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentHandler::GetEncoding(nsACString& aEncoding) +{ + aEncoding.Assign(m_encoding); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentHandler::GetAlreadyEncoded(bool* aAlreadyEncoded) +{ + NS_ENSURE_ARG_POINTER(aAlreadyEncoded); + *aAlreadyEncoded = m_already_encoded_p; + return NS_OK; +} + +// Local methods. + +void +nsMsgAttachmentHandler::CleanupTempFile() +{ +#ifdef XP_MACOSX + if (mEncodedWorkingFile) { + mEncodedWorkingFile->Remove(false); + mEncodedWorkingFile = nullptr; + } +#endif // XP_MACOSX +} + +void +nsMsgAttachmentHandler::AnalyzeDataChunk(const char *chunk, int32_t length) +{ + unsigned char *s = (unsigned char *) chunk; + unsigned char *end = s + length; + for (; s < end; s++) + { + if (*s > 126) + { + m_highbit_count++; + m_unprintable_count++; + } + else if (*s < ' ' && *s != '\t' && *s != '\r' && *s != '\n') + { + m_unprintable_count++; + m_ctl_count++; + if (*s == 0) + m_null_count++; + } + + if (*s == '\r' || *s == '\n') + { + if (*s == '\r') + { + if (m_prev_char_was_cr) + m_have_cr = 1; + else + m_prev_char_was_cr = true; + } + else + { + if (m_prev_char_was_cr) + { + if (m_current_column == 0) + { + m_have_crlf = 1; + m_lines--; + } + else + m_have_cr = m_have_lf = 1; + m_prev_char_was_cr = false; + } + else + m_have_lf = 1; + } + if (m_max_column < m_current_column) + m_max_column = m_current_column; + m_current_column = 0; + m_lines++; + } + else + { + m_current_column++; + } + } + // Check one last time for the last line. This is also important if there + // is only one line that doesn't terminate in \n. + if (m_max_column < m_current_column) + m_max_column = m_current_column; +} + +void +nsMsgAttachmentHandler::AnalyzeSnarfedFile(void) +{ + char chunk[1024]; + uint32_t numRead = 0; + + if (m_file_analyzed) + return; + + if (mTmpFile) + { + int64_t fileSize; + mTmpFile->GetFileSize(&fileSize); + m_size = (uint32_t) fileSize; + nsCOMPtr <nsIInputStream> inputFile; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputFile), mTmpFile); + if (NS_FAILED(rv)) + return; + { + do + { + rv = inputFile->Read(chunk, sizeof(chunk), &numRead); + if (numRead) + AnalyzeDataChunk(chunk, numRead); + } + while (numRead && NS_SUCCEEDED(rv)); + if (m_prev_char_was_cr) + m_have_cr = 1; + + inputFile->Close(); + m_file_analyzed = true; + } + } +} + +// +// Given a content-type and some info about the contents of the document, +// decide what encoding it should have. +// +nsresult +nsMsgAttachmentHandler::PickEncoding(const char *charset, nsIMsgSend *mime_delivery_state) +{ + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + + bool needsB64 = false; + bool forceB64 = false; + bool isUsingQP = false; + + if (mSendViaCloud) + { + m_encoding = ENCODING_7BIT; + return NS_OK; + } + if (m_already_encoded_p) + goto DONE; + + AnalyzeSnarfedFile(); + + // Allow users to override our percentage-wise guess on whether + // the file is text or binary. + if (pPrefBranch) + pPrefBranch->GetBoolPref ("mail.file_attach_binary", &forceB64); + + // If the content-type is "image/" or something else known to be binary or + // several flavors of newlines are present, use base64 unless we're attaching + // a message (so that we don't get confused by newline conversions). + if (!mMainBody && + (forceB64 || mime_type_requires_b64_p(m_type.get()) || + m_have_cr + m_have_lf + m_have_crlf != 1) && + !m_type.LowerCaseEqualsLiteral(MESSAGE_RFC822)) + { + needsB64 = true; + } + else + { + // Otherwise, we need to pick an encoding based on the contents of the + // document. + bool encode_p; + bool force_p = false; + + // Force quoted-printable if the sender does not allow conversion to 7bit. + if (mCompFields) { + if (mCompFields->GetForceMsgEncoding()) + force_p = true; + } else if (mime_delivery_state) { + if (((nsMsgComposeAndSend *)mime_delivery_state)->mCompFields->GetForceMsgEncoding()) { + force_p = true; + } + } + + if (force_p || (m_max_column > LINELENGTH_ENCODING_THRESHOLD)) { + encode_p = true; + } else if (UseQuotedPrintable() && m_unprintable_count) { + encode_p = true; + } else if (m_null_count) { + // If there are nulls, we must always encode, because sendmail will + // blow up. + encode_p = true; + } else { + encode_p = false; + } + + // MIME requires a special case that these types never be encoded. + if (StringBeginsWith(m_type, NS_LITERAL_CSTRING("message"), + nsCaseInsensitiveCStringComparator()) || + StringBeginsWith(m_type, NS_LITERAL_CSTRING("multipart"), + nsCaseInsensitiveCStringComparator())) + { + encode_p = false; + if (m_desiredType.LowerCaseEqualsLiteral(TEXT_PLAIN)) + m_desiredType.Truncate(); + } + + // If the Mail charset is multibyte, we force it to use Base64 for attachments. + if ((!mMainBody && charset && nsMsgI18Nmultibyte_charset(charset)) && + (m_type.LowerCaseEqualsLiteral(TEXT_HTML) || + m_type.LowerCaseEqualsLiteral(TEXT_MDL) || + m_type.LowerCaseEqualsLiteral(TEXT_PLAIN) || + m_type.LowerCaseEqualsLiteral(TEXT_RICHTEXT) || + m_type.LowerCaseEqualsLiteral(TEXT_ENRICHED) || + m_type.LowerCaseEqualsLiteral(TEXT_VCARD) || + m_type.LowerCaseEqualsLiteral(APPLICATION_DIRECTORY) || /* text/x-vcard synonym */ + m_type.LowerCaseEqualsLiteral(TEXT_CSS) || + m_type.LowerCaseEqualsLiteral(TEXT_JSSS))) { + needsB64 = true; + } else if (charset && nsMsgI18Nstateful_charset(charset)) { + m_encoding = ENCODING_7BIT; + } else if (encode_p && + m_unprintable_count > (m_size / 10)) { + // If the document contains more than 10% unprintable characters, + // then that seems like a good candidate for base64 instead of + // quoted-printable. + needsB64 = true; + } else if (encode_p) { + m_encoding = ENCODING_QUOTED_PRINTABLE; + isUsingQP = true; + } else if (m_highbit_count > 0) { + m_encoding = ENCODING_8BIT; + } else { + m_encoding = ENCODING_7BIT; + } + } + + // Always base64 binary data. + if (needsB64) + m_encoding = ENCODING_BASE64; + + // According to RFC 821 we must always have lines shorter than 998 bytes. + // To encode "long lines" use a CTE that will transmit shorter lines. + // Switch to base64 if we are not already using "quoted printable". + + // We don't do this for message/rfc822 attachments, since we can't + // change the original Content-Transfer-Encoding of the message we're + // attaching. We rely on the original message complying with RFC 821, + // if it doesn't we won't either. Not ideal. + if (!m_type.LowerCaseEqualsLiteral(MESSAGE_RFC822) && + m_max_column > LINELENGTH_ENCODING_THRESHOLD && !isUsingQP) + m_encoding = ENCODING_BASE64; + + // Now that we've picked an encoding, initialize the filter. + NS_ASSERTION(!m_encoder, "not-null m_encoder"); + if (m_encoding.LowerCaseEqualsLiteral(ENCODING_BASE64)) + { + m_encoder = MimeEncoder::GetBase64Encoder(mime_encoder_output_fn, + mime_delivery_state); + } + else if (m_encoding.LowerCaseEqualsLiteral(ENCODING_QUOTED_PRINTABLE)) + { + m_encoder = MimeEncoder::GetQPEncoder(mime_encoder_output_fn, + mime_delivery_state); + } + else + { + m_encoder = nullptr; + } + + /* Do some cleanup for documents with unknown content type. + There are two issues: how they look to MIME users, and how they look to + non-MIME users. + + If the user attaches a "README" file, which has unknown type because it + has no extension, we still need to send it with no encoding, so that it + is readable to non-MIME users. + + But if the user attaches some random binary file, then base64 encoding + will have been chosen for it (above), and in this case, it won't be + immediately readable by non-MIME users. However, if we type it as + text/plain instead of application/octet-stream, it will show up inline + in a MIME viewer, which will probably be ugly, and may possibly have + bad charset things happen as well. + + So, the heuristic we use is, if the type is unknown, then the type is + set to application/octet-stream for data which needs base64 (binary data) + and is set to text/plain for data which didn't need base64 (unencoded or + lightly encoded data.) + */ +DONE: + if (m_type.IsEmpty() || m_type.LowerCaseEqualsLiteral(UNKNOWN_CONTENT_TYPE)) + { + if (m_already_encoded_p) + m_type = APPLICATION_OCTET_STREAM; + else if (m_encoding.LowerCaseEqualsLiteral(ENCODING_BASE64) || + m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE)) + m_type = APPLICATION_OCTET_STREAM; + else + m_type = TEXT_PLAIN; + } + return NS_OK; +} + +nsresult +nsMsgAttachmentHandler::PickCharset() +{ + if (!m_charset.IsEmpty() || !m_type.LowerCaseEqualsLiteral(TEXT_PLAIN)) + return NS_OK; + + nsCOMPtr<nsIFile> tmpFile = + do_QueryInterface(mTmpFile); + if (!tmpFile) + return NS_OK; + + return MsgDetectCharsetFromFile(tmpFile, m_charset); +} + +static nsresult +FetcherURLDoneCallback(nsresult aStatus, + const nsACString &aContentType, + const nsACString &aCharset, + int32_t totalSize, + const char16_t* aMsg, void *tagData) +{ + nsMsgAttachmentHandler *ma = (nsMsgAttachmentHandler *) tagData; + NS_ASSERTION(ma != nullptr, "not-null mime attachment"); + + if (ma != nullptr) + { + ma->m_size = totalSize; + if (!aContentType.IsEmpty()) + { +#ifdef XP_MACOSX + //Do not change the type if we are dealing with an encoded (e.g., appledouble or zip) file + if (!ma->mEncodedWorkingFile) +#else + // can't send appledouble on non-macs + if (!aContentType.EqualsLiteral("multipart/appledouble")) +#endif + ma->m_type = aContentType; + } + + if (!aCharset.IsEmpty()) + ma->m_charset = aCharset; + + return ma->UrlExit(aStatus, aMsg); + } + else + return NS_OK; +} + +nsresult +nsMsgAttachmentHandler::SnarfMsgAttachment(nsMsgCompFields *compFields) +{ + nsresult rv = NS_ERROR_INVALID_ARG; + nsCOMPtr <nsIMsgMessageService> messageService; + + if (m_uri.Find("-message:", CaseInsensitiveCompare) != -1) + { + nsCOMPtr <nsIFile> tmpFile; + rv = nsMsgCreateTempFile("nsmail.tmp", getter_AddRefs(tmpFile)); + NS_ENSURE_SUCCESS(rv, rv); + mTmpFile = do_QueryInterface(tmpFile); + mDeleteFile = true; + mCompFields = compFields; + m_type = MESSAGE_RFC822; + m_overrideType = MESSAGE_RFC822; + if (!mTmpFile) + { + rv = NS_ERROR_FAILURE; + goto done; + } + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mOutFile), mTmpFile, -1, 00600); + if (NS_FAILED(rv) || !mOutFile) + { + if (m_mime_delivery_state) + { + nsCOMPtr<nsIMsgSendReport> sendReport; + m_mime_delivery_state->GetSendReport(getter_AddRefs(sendReport)); + if (sendReport) + { + nsAutoString error_msg; + nsMsgBuildMessageWithTmpFile(mTmpFile, error_msg); + sendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false); + } + } + rv = NS_MSG_UNABLE_TO_OPEN_TMP_FILE; + goto done; + } + + nsCOMPtr<nsIURLFetcher> fetcher = do_CreateInstance(NS_URLFETCHER_CONTRACTID, &rv); + if (NS_FAILED(rv) || !fetcher) + { + if (NS_SUCCEEDED(rv)) + rv = NS_ERROR_UNEXPECTED; + goto done; + } + + rv = fetcher->Initialize(mTmpFile, mOutFile, FetcherURLDoneCallback, this); + rv = GetMessageServiceFromURI(m_uri, getter_AddRefs(messageService)); + if (NS_SUCCEEDED(rv) && messageService) + { + nsAutoCString uri(m_uri); + uri += (uri.FindChar('?') == kNotFound) ? '?' : '&'; + uri.Append("fetchCompleteMessage=true"); + nsCOMPtr<nsIStreamListener> strListener; + fetcher->QueryInterface(NS_GET_IID(nsIStreamListener), getter_AddRefs(strListener)); + + // initialize a new stream converter, that uses the strListener as its input + // obtain the input stream listener from the new converter, + // and pass the converter's input stream listener to DisplayMessage + + m_mime_parser = do_CreateInstance(NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + goto done; + + // Set us as the output stream for HTML data from libmime... + nsCOMPtr<nsIMimeStreamConverter> mimeConverter = do_QueryInterface(m_mime_parser); + if (mimeConverter) + { + mimeConverter->SetMimeOutputType(nsMimeOutput::nsMimeMessageDecrypt); + mimeConverter->SetForwardInline(false); + mimeConverter->SetIdentity(nullptr); + mimeConverter->SetOriginalMsgURI(nullptr); + } + + nsCOMPtr<nsIStreamListener> convertedListener = do_QueryInterface(m_mime_parser, &rv); + if (NS_FAILED(rv)) + goto done; + + nsCOMPtr<nsIURI> aURL; + rv = messageService->GetUrlForUri(uri.get(), getter_AddRefs(aURL), nullptr); + if (NS_FAILED(rv)) + goto done; + + + nsCOMPtr<nsIPrincipal> nullPrincipal = + do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipal failed."); + if (NS_FAILED(rv)) + goto done; + + rv = NS_NewInputStreamChannel(getter_AddRefs(m_converter_channel), + aURL, + nullptr, + nullPrincipal, + nsILoadInfo::SEC_NORMAL, + nsIContentPolicy::TYPE_OTHER); + if (NS_FAILED(rv)) + goto done; + + rv = m_mime_parser->AsyncConvertData("message/rfc822", "message/rfc822", + strListener, m_converter_channel); + if (NS_FAILED(rv)) + goto done; + + nsCOMPtr<nsIURI> dummyNull; + rv = messageService->DisplayMessage(uri.get(), convertedListener, nullptr, nullptr, nullptr, + getter_AddRefs(dummyNull)); + } + } +done: + if (NS_FAILED(rv)) + { + if (mOutFile) + { + mOutFile->Close(); + mOutFile = nullptr; + } + + if (mTmpFile) + { + mTmpFile->Remove(false); + mTmpFile = nullptr; + } + } + + return rv; +} + +#ifdef XP_MACOSX +bool nsMsgAttachmentHandler::HasResourceFork(FSRef *fsRef) +{ + FSCatalogInfo catalogInfo; + OSErr err = FSGetCatalogInfo(fsRef, kFSCatInfoDataSizes + kFSCatInfoRsrcSizes, &catalogInfo, nullptr, nullptr, nullptr); + return (err == noErr && catalogInfo.rsrcLogicalSize != 0); +} +#endif + +nsresult +nsMsgAttachmentHandler::SnarfAttachment(nsMsgCompFields *compFields) +{ + NS_ASSERTION (! m_done, "Already done"); + + if (!mURL) + return SnarfMsgAttachment(compFields); + + mCompFields = compFields; + + // First, get as file spec and create the stream for the + // temp file where we will save this data + nsCOMPtr <nsIFile> tmpFile; + nsresult rv = nsMsgCreateTempFile("nsmail.tmp", getter_AddRefs(tmpFile)); + NS_ENSURE_SUCCESS(rv, rv); + mTmpFile = do_QueryInterface(tmpFile); + mDeleteFile = true; + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mOutFile), mTmpFile, -1, 00600); + if (NS_FAILED(rv) || !mOutFile) + { + if (m_mime_delivery_state) + { + nsCOMPtr<nsIMsgSendReport> sendReport; + m_mime_delivery_state->GetSendReport(getter_AddRefs(sendReport)); + if (sendReport) + { + nsAutoString error_msg; + nsMsgBuildMessageWithTmpFile(mTmpFile, error_msg); + sendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false); + } + } + mTmpFile->Remove(false); + mTmpFile = nullptr; + return NS_MSG_UNABLE_TO_OPEN_TMP_FILE; + } + + nsCString sourceURISpec; + rv = mURL->GetSpec(sourceURISpec); + NS_ENSURE_SUCCESS(rv, rv); +#ifdef XP_MACOSX + if (!m_bogus_attachment && StringBeginsWith(sourceURISpec, NS_LITERAL_CSTRING("file://"))) + { + // Unescape the path (i.e. un-URLify it) before making a FSSpec + nsAutoCString filePath; + filePath.Adopt(nsMsgGetLocalFileFromURL(sourceURISpec.get())); + nsAutoCString unescapedFilePath; + MsgUnescapeString(filePath, 0, unescapedFilePath); + + nsCOMPtr<nsIFile> sourceFile; + NS_NewNativeLocalFile(unescapedFilePath, true, getter_AddRefs(sourceFile)); + if (!sourceFile) + return NS_ERROR_FAILURE; + + // check if it is a bundle. if it is, we'll zip it. + // if not, we'll apple encode it (applesingle or appledouble) + nsCOMPtr<nsILocalFileMac> macFile(do_QueryInterface(sourceFile)); + bool isPackage; + macFile->IsPackage(&isPackage); + if (isPackage) + rv = ConvertToZipFile(macFile); + else + rv = ConvertToAppleEncoding(sourceURISpec, unescapedFilePath, macFile); + + NS_ENSURE_SUCCESS(rv, rv); + } +#endif /* XP_MACOSX */ + + // + // Ok, here we are, we need to fire the URL off and get the data + // in the temp file + // + // Create a fetcher for the URL attachment... + + nsCOMPtr<nsIURLFetcher> fetcher = do_CreateInstance(NS_URLFETCHER_CONTRACTID, &rv); + if (NS_FAILED(rv) || !fetcher) + { + if (NS_SUCCEEDED(rv)) + return NS_ERROR_UNEXPECTED; + else + return rv; + } + + return fetcher->FireURLRequest(mURL, mTmpFile, mOutFile, FetcherURLDoneCallback, this); +} + +#ifdef XP_MACOSX +nsresult +nsMsgAttachmentHandler::ConvertToZipFile(nsILocalFileMac *aSourceFile) +{ + // append ".zip" to the real file name + nsAutoCString zippedName; + nsresult rv = aSourceFile->GetNativeLeafName(zippedName); + NS_ENSURE_SUCCESS(rv, rv); + zippedName.AppendLiteral(".zip"); + + // create a temporary file that we'll work on + nsCOMPtr <nsIFile> tmpFile; + rv = nsMsgCreateTempFile(zippedName.get(), getter_AddRefs(tmpFile)); + NS_ENSURE_SUCCESS(rv, rv); + mEncodedWorkingFile = do_QueryInterface(tmpFile); + + // point our URL at the zipped temp file + NS_NewFileURI(getter_AddRefs(mURL), mEncodedWorkingFile); + + // zip it! + rv = nsSimpleZipper::Zip(aSourceFile, mEncodedWorkingFile); + NS_ENSURE_SUCCESS(rv, rv); + + // set some metadata for this attachment, that will affect the MIME headers. + m_type = APPLICATION_ZIP; + m_realName = zippedName.get(); + + return NS_OK; +} + +nsresult +nsMsgAttachmentHandler::ConvertToAppleEncoding(const nsCString &aFileURI, + const nsCString &aFilePath, + nsILocalFileMac *aSourceFile) +{ + // convert the apple file to AppleDouble first, and then patch the + // address in the url. + + //We need to retrieve the file type and creator... + + char fileInfo[32]; + OSType type, creator; + + nsresult rv = aSourceFile->GetFileType(&type); + if (NS_FAILED(rv)) + return rv; + PR_snprintf(fileInfo, sizeof(fileInfo), "%X", type); + m_xMacType = fileInfo; + + rv = aSourceFile->GetFileCreator(&creator); + if (NS_FAILED(rv)) + return rv; + PR_snprintf(fileInfo, sizeof(fileInfo), "%X", creator); + m_xMacCreator = fileInfo; + + FSRef fsRef; + aSourceFile->GetFSRef(&fsRef); + bool sendResourceFork = HasResourceFork(&fsRef); + + // if we have a resource fork, check the filename extension, maybe we don't need the resource fork! + if (sendResourceFork) + { + nsCOMPtr<nsIURL> fileUrl(do_CreateInstance(NS_STANDARDURL_CONTRACTID)); + if (fileUrl) + { + rv = fileUrl->SetSpec(aFileURI); + if (NS_SUCCEEDED(rv)) + { + nsAutoCString ext; + rv = fileUrl->GetFileExtension(ext); + if (NS_SUCCEEDED(rv) && !ext.IsEmpty()) + { + sendResourceFork = + PL_strcasecmp(ext.get(), "TXT") && + PL_strcasecmp(ext.get(), "JPG") && + PL_strcasecmp(ext.get(), "GIF") && + PL_strcasecmp(ext.get(), "TIF") && + PL_strcasecmp(ext.get(), "HTM") && + PL_strcasecmp(ext.get(), "HTML") && + PL_strcasecmp(ext.get(), "ART") && + PL_strcasecmp(ext.get(), "XUL") && + PL_strcasecmp(ext.get(), "XML") && + PL_strcasecmp(ext.get(), "CSS") && + PL_strcasecmp(ext.get(), "JS"); + } + } + } + } + + // Only use appledouble if we aren't uuencoding. + if( sendResourceFork ) + { + char *separator; + + separator = mime_make_separator("ad"); + if (!separator) + return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr <nsIFile> tmpFile; + nsresult rv = nsMsgCreateTempFile("appledouble", getter_AddRefs(tmpFile)); + if (NS_SUCCEEDED(rv)) + mEncodedWorkingFile = do_QueryInterface(tmpFile); + if (!mEncodedWorkingFile) + { + PR_FREEIF(separator); + return NS_ERROR_OUT_OF_MEMORY; + } + + // + // RICHIE_MAC - ok, here's the deal, we have a file that we need + // to encode in appledouble encoding for the resource fork and put that + // into the mEncodedWorkingFile location. Then, we need to patch the new file + // spec into the array and send this as part of the 2 part appledouble/mime + // encoded mime part. + // + AppleDoubleEncodeObject *obj = new (AppleDoubleEncodeObject); + if (obj == NULL) + { + mEncodedWorkingFile = nullptr; + PR_FREEIF(separator); + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = MsgGetFileStream(mEncodedWorkingFile, getter_AddRefs(obj->fileStream)); + if (NS_FAILED(rv) || !obj->fileStream) + { + PR_FREEIF(separator); + delete obj; + return NS_ERROR_OUT_OF_MEMORY; + } + + char *working_buff = (char *) PR_Malloc(AD_WORKING_BUFF_SIZE); + if (!working_buff) + { + PR_FREEIF(separator); + delete obj; + return NS_ERROR_OUT_OF_MEMORY; + } + + obj->buff = working_buff; + obj->s_buff = AD_WORKING_BUFF_SIZE; + + // + // Setup all the need information on the apple double encoder. + // + ap_encode_init(&(obj->ap_encode_obj), aFilePath.get(), separator); + + int32_t count; + + OSErr status = noErr; + m_size = 0; + while (status == noErr) + { + status = ap_encode_next(&(obj->ap_encode_obj), obj->buff, obj->s_buff, &count); + if (status == noErr || status == errDone) + { + // + // we got the encode data, so call the next stream to write it to the disk. + // + uint32_t bytesWritten; + obj->fileStream->Write(obj->buff, count, &bytesWritten); + if (bytesWritten != (uint32_t) count) + status = errFileWrite; + } + } + + ap_encode_end(&(obj->ap_encode_obj), (status >= 0)); // if this is true, ok, false abort + if (obj->fileStream) + obj->fileStream->Close(); + + PR_FREEIF(obj->buff); /* free the working buff. */ + PR_FREEIF(obj); + + nsCOMPtr <nsIURI> fileURI; + NS_NewFileURI(getter_AddRefs(fileURI), mEncodedWorkingFile); + + nsCOMPtr<nsIFileURL> theFileURL = do_QueryInterface(fileURI, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCString newURLSpec; + rv = fileURI->GetSpec(newURLSpec); + NS_ENSURE_SUCCESS(rv, rv); + + if (newURLSpec.IsEmpty()) + { + PR_FREEIF(separator); + return NS_ERROR_OUT_OF_MEMORY; + } + + if (NS_FAILED(nsMsgNewURL(getter_AddRefs(mURL), newURLSpec.get()))) + { + PR_FREEIF(separator); + return NS_ERROR_OUT_OF_MEMORY; + } + + // Now after conversion, also patch the types. + char tmp[128]; + PR_snprintf(tmp, sizeof(tmp), MULTIPART_APPLEDOUBLE ";\r\n boundary=\"%s\"", separator); + PR_FREEIF(separator); + m_type = tmp; + } + else + { + if ( sendResourceFork ) + { + // For now, just do the encoding, but in the old world we would ask the + // user about doing this conversion + printf("...we could ask the user about this conversion, but for now, nahh..\n"); + } + + bool useDefault; + char *macType, *macEncoding; + if (m_type.IsEmpty() || m_type.LowerCaseEqualsLiteral(TEXT_PLAIN)) + { +# define TEXT_TYPE 0x54455854 /* the characters 'T' 'E' 'X' 'T' */ +# define text_TYPE 0x74657874 /* the characters 't' 'e' 'x' 't' */ + + if (type != TEXT_TYPE && type != text_TYPE) + { + MacGetFileType(aSourceFile, &useDefault, &macType, &macEncoding); + m_type = macType; + } + } + // don't bother to set the types if we failed in getting the file info. + } + + return NS_OK; +} +#endif // XP_MACOSX + +nsresult +nsMsgAttachmentHandler::LoadDataFromFile(nsIFile *file, nsString &sigData, bool charsetConversion) +{ + int32_t readSize; + char *readBuf; + + nsCOMPtr <nsIInputStream> inputFile; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputFile), file); + if (NS_FAILED(rv)) + return NS_MSG_ERROR_WRITING_FILE; + + int64_t fileSize; + file->GetFileSize(&fileSize); + readSize = (uint32_t) fileSize; + + readBuf = (char *)PR_Malloc(readSize + 1); + if (!readBuf) + return NS_ERROR_OUT_OF_MEMORY; + memset(readBuf, 0, readSize + 1); + + uint32_t bytesRead; + inputFile->Read(readBuf, readSize, &bytesRead); + inputFile->Close(); + + nsDependentCString cstringReadBuf(readBuf, bytesRead); + if (charsetConversion) + { + if (NS_FAILED(ConvertToUnicode(m_charset.get(), cstringReadBuf, sigData))) + CopyASCIItoUTF16(cstringReadBuf, sigData); + } + else + CopyASCIItoUTF16(cstringReadBuf, sigData); + + PR_FREEIF(readBuf); + return NS_OK; +} + +nsresult +nsMsgAttachmentHandler::Abort() +{ + nsCOMPtr<nsIRequest> saveRequest; + saveRequest.swap(mRequest); + + if (mTmpFile) + { + if (mDeleteFile) + mTmpFile->Remove(false); + mTmpFile = nullptr; + } + + NS_ASSERTION(m_mime_delivery_state != nullptr, "not-null m_mime_delivery_state"); + + if (m_done) + return NS_OK; + + if (saveRequest) + return saveRequest->Cancel(NS_ERROR_ABORT); + else + if (m_mime_delivery_state) + { + m_mime_delivery_state->SetStatus(NS_ERROR_ABORT); + m_mime_delivery_state->NotifyListenerOnStopSending(nullptr, NS_ERROR_ABORT, 0, nullptr); + } + return NS_OK; + +} + +nsresult +nsMsgAttachmentHandler::UrlExit(nsresult status, const char16_t* aMsg) +{ + NS_ASSERTION(m_mime_delivery_state != nullptr, "not-null m_mime_delivery_state"); + + // Close the file, but don't delete the disk file (or the file spec.) + if (mOutFile) + { + mOutFile->Close(); + mOutFile = nullptr; + } + // this silliness is because Windows nsIFile caches its file size + // so if an output stream writes to it, it will still return the original + // cached size. + if (mTmpFile) + { + nsCOMPtr <nsIFile> tmpFile; + mTmpFile->Clone(getter_AddRefs(tmpFile)); + mTmpFile = do_QueryInterface(tmpFile); + } + mRequest = nullptr; + + // First things first, we are now going to see if this is an HTML + // Doc and if it is, we need to see if we can determine the charset + // for this part by sniffing the HTML file. + // This is needed only when the charset is not set already. + // (e.g. a charset may be specified in HTTP header) + // + if (!m_type.IsEmpty() && m_charset.IsEmpty() && + m_type.LowerCaseEqualsLiteral(TEXT_HTML)) + m_charset = nsMsgI18NParseMetaCharset(mTmpFile); + + nsresult mimeDeliveryStatus; + m_mime_delivery_state->GetStatus(&mimeDeliveryStatus); + + if (mimeDeliveryStatus == NS_ERROR_ABORT) + status = NS_ERROR_ABORT; + + // If the attachment is empty, let's call that a failure. + if (!m_size) + status = NS_ERROR_FAILURE; + + if (NS_FAILED(status) && status != NS_ERROR_ABORT && NS_SUCCEEDED(mimeDeliveryStatus)) + { + // At this point, we should probably ask a question to the user + // if we should continue without this attachment. + // + bool keepOnGoing = true; + nsCString turl; + nsString msg; + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + nsMsgDeliverMode mode = nsIMsgSend::nsMsgDeliverNow; + m_mime_delivery_state->GetDeliveryMode(&mode); + nsCString params; + if (!m_realName.IsEmpty()) + params = m_realName; + else if (NS_SUCCEEDED(mURL->GetSpec(turl)) && !turl.IsEmpty()) + { + nsAutoCString unescapedUrl; + MsgUnescapeString(turl, 0, unescapedUrl); + if (unescapedUrl.IsEmpty()) + params = turl; + else + params = unescapedUrl; + } + else + params.AssignLiteral("?"); + + NS_ConvertUTF8toUTF16 UTF16params(params); + const char16_t* formatParams[] = { UTF16params.get() }; + if (mode == nsIMsgSend::nsMsgSaveAsDraft || mode == nsIMsgSend::nsMsgSaveAsTemplate) + bundle->FormatStringFromName(u"failureOnObjectEmbeddingWhileSaving", + formatParams, 1, getter_Copies(msg)); + else + bundle->FormatStringFromName(u"failureOnObjectEmbeddingWhileSending", + formatParams, 1, getter_Copies(msg)); + + nsCOMPtr<nsIPrompt> aPrompt; + if (m_mime_delivery_state) + m_mime_delivery_state->GetDefaultPrompt(getter_AddRefs(aPrompt)); + nsMsgAskBooleanQuestionByString(aPrompt, msg.get(), &keepOnGoing); + + if (keepOnGoing) + { + status = NS_OK; + m_bogus_attachment = true; //That will cause this attachment to be ignored. + } + else + { + status = NS_ERROR_ABORT; + m_mime_delivery_state->SetStatus(status); + nsresult ignoreMe; + m_mime_delivery_state->Fail(status, nullptr, &ignoreMe); + m_mime_delivery_state->NotifyListenerOnStopSending(nullptr, status, 0, nullptr); + SetMimeDeliveryState(nullptr); + return status; + } + } + + m_done = true; + + // + // Ok, now that we have the file here on disk, we need to see if there was + // a need to do conversion to plain text...if so, the magic happens here, + // otherwise, just move on to other attachments... + // + if (NS_SUCCEEDED(status) && !m_type.LowerCaseEqualsLiteral(TEXT_PLAIN) && + m_desiredType.LowerCaseEqualsLiteral(TEXT_PLAIN)) + { + // + // Conversion to plain text desired. + // Now use the converter service here to do the right + // thing and convert this data to plain text for us! + // + nsAutoString conData; + + if (NS_SUCCEEDED(LoadDataFromFile(mTmpFile, conData, true))) + { + bool flowed, delsp, formatted, disallowBreaks; + GetSerialiserFlags(m_charset.get(), &flowed, &delsp, &formatted, &disallowBreaks); + + if (NS_SUCCEEDED(ConvertBufToPlainText(conData, flowed, delsp, formatted, disallowBreaks))) + { + if (mDeleteFile) + mTmpFile->Remove(false); + + nsCOMPtr<nsIOutputStream> outputStream; + nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream), mTmpFile, + PR_WRONLY | PR_CREATE_FILE, 00600); + + if (NS_SUCCEEDED(rv)) + { + nsAutoCString tData; + if (NS_FAILED(ConvertFromUnicode(m_charset.get(), conData, tData))) + LossyCopyUTF16toASCII(conData, tData); + if (!tData.IsEmpty()) + { + uint32_t bytesWritten; + (void) outputStream->Write(tData.get(), tData.Length(), &bytesWritten); + } + outputStream->Close(); + // this silliness is because Windows nsIFile caches its file size + // so if an output stream writes to it, it will still return the original + // cached size. + if (mTmpFile) + { + nsCOMPtr <nsIFile> tmpFile; + mTmpFile->Clone(getter_AddRefs(tmpFile)); + mTmpFile = do_QueryInterface(tmpFile); + } + + } + } + } + + m_type = m_desiredType; + m_desiredType.Truncate(); + m_encoding.Truncate(); + } + + uint32_t pendingAttachmentCount = 0; + m_mime_delivery_state->GetPendingAttachmentCount(&pendingAttachmentCount); + NS_ASSERTION (pendingAttachmentCount > 0, "no more pending attachment"); + + m_mime_delivery_state->SetPendingAttachmentCount(pendingAttachmentCount - 1); + + bool processAttachmentsSynchronously = false; + m_mime_delivery_state->GetProcessAttachmentsSynchronously(&processAttachmentsSynchronously); + if (NS_SUCCEEDED(status) && processAttachmentsSynchronously) + { + /* Find the next attachment which has not yet been loaded, + if any, and start it going. + */ + uint32_t i; + nsMsgAttachmentHandler *next = 0; + nsTArray<RefPtr<nsMsgAttachmentHandler>> *attachments; + + m_mime_delivery_state->GetAttachmentHandlers(&attachments); + + for (i = 0; i < attachments->Length(); i++) + { + if (!(*attachments)[i]->m_done) + { + next = (*attachments)[i]; + // + // rhp: We need to get a little more understanding to failed URL + // requests. So, at this point if most of next is NULL, then we + // should just mark it fetched and move on! We probably ignored + // this earlier on in the send process. + // + if ( (!next->mURL) && (next->m_uri.IsEmpty()) ) + { + (*attachments)[i]->m_done = true; + (*attachments)[i]->SetMimeDeliveryState(nullptr); + m_mime_delivery_state->GetPendingAttachmentCount(&pendingAttachmentCount); + m_mime_delivery_state->SetPendingAttachmentCount(pendingAttachmentCount - 1); + next->mPartUserOmissionOverride = true; + next = nullptr; + continue; + } + + break; + } + } + + if (next) + { + nsresult status = next->SnarfAttachment(mCompFields); + if (NS_FAILED(status)) + { + nsresult ignoreMe; + m_mime_delivery_state->Fail(status, nullptr, &ignoreMe); + m_mime_delivery_state->NotifyListenerOnStopSending(nullptr, status, 0, nullptr); + SetMimeDeliveryState(nullptr); + return NS_ERROR_UNEXPECTED; + } + } + } + + m_mime_delivery_state->GetPendingAttachmentCount(&pendingAttachmentCount); + if (pendingAttachmentCount == 0) + { + // If this is the last attachment, then either complete the + // delivery (if successful) or report the error by calling + // the exit routine and terminating the delivery. + if (NS_FAILED(status)) + { + nsresult ignoreMe; + m_mime_delivery_state->Fail(status, aMsg, &ignoreMe); + m_mime_delivery_state->NotifyListenerOnStopSending(nullptr, status, aMsg, nullptr); + SetMimeDeliveryState(nullptr); + return NS_ERROR_UNEXPECTED; + } + else + { + status = m_mime_delivery_state->GatherMimeAttachments (); + if (NS_FAILED(status)) + { + nsresult ignoreMe; + m_mime_delivery_state->Fail(status, aMsg, &ignoreMe); + m_mime_delivery_state->NotifyListenerOnStopSending(nullptr, status, aMsg, nullptr); + SetMimeDeliveryState(nullptr); + return NS_ERROR_UNEXPECTED; + } + } + } + else + { + // If this is not the last attachment, but it got an error, + // then report that error and continue + if (NS_FAILED(status)) + { + nsresult ignoreMe; + m_mime_delivery_state->Fail(status, aMsg, &ignoreMe); + } + } + + SetMimeDeliveryState(nullptr); + return NS_OK; +} + + +nsresult +nsMsgAttachmentHandler::GetMimeDeliveryState(nsIMsgSend** _retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = m_mime_delivery_state; + NS_IF_ADDREF(*_retval); + return NS_OK; +} + +nsresult +nsMsgAttachmentHandler::SetMimeDeliveryState(nsIMsgSend* mime_delivery_state) +{ + /* + Because setting m_mime_delivery_state to null could destroy ourself as + m_mime_delivery_state it's our parent, we need to protect ourself against + that! + + This extra comptr is necessary, + see bug http://bugzilla.mozilla.org/show_bug.cgi?id=78967 + */ + nsCOMPtr<nsIMsgSend> temp = m_mime_delivery_state; /* Should lock our parent until the end of the function */ + m_mime_delivery_state = mime_delivery_state; + return NS_OK; +} diff --git a/mailnews/compose/src/nsMsgAttachmentHandler.h b/mailnews/compose/src/nsMsgAttachmentHandler.h new file mode 100644 index 000000000..79e526627 --- /dev/null +++ b/mailnews/compose/src/nsMsgAttachmentHandler.h @@ -0,0 +1,194 @@ +/* -*- 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 _nsMsgAttachmentHandler_H_ +#define _nsMsgAttachmentHandler_H_ + +#include "nsIURL.h" +#include "nsMsgCompFields.h" +#include "nsIMsgStatusFeedback.h" +#include "nsIChannel.h" +#include "nsIMsgSend.h" +#include "nsIFileStreams.h" +#include "nsIStreamConverter.h" +#include "nsAutoPtr.h" +#include "nsIMsgAttachmentHandler.h" + +#ifdef XP_MACOSX + +#include "nsMsgAppleDouble.h" +#include "nsILocalFileMac.h" + +class AppleDoubleEncodeObject +{ +public: + appledouble_encode_object ap_encode_obj; + char *buff; // the working buff + int32_t s_buff; // the working buff size + nsCOMPtr <nsIOutputStream> fileStream; // file to hold the encoding +}; + +class nsILocalFileMac; +class nsIZipWriter; + +/* Simple utility class that will synchronously zip any file + (or folder hierarchy) you give it. */ +class nsSimpleZipper +{ + public: + + // Synchronously zips the input file/folder and writes all + // data to the output file. + static nsresult Zip(nsIFile *aInputFile, nsIFile *aOutputFile); + + private: + + // Recursively adds the file or folder to aZipWriter. + static nsresult AddToZip(nsIZipWriter *aZipWriter, + nsIFile *aFile, + const nsACString &aPath); +}; +#endif // XP_MACOSX + +namespace mozilla { +namespace mailnews { +class MimeEncoder; +} +} + +// +// This is a class that deals with processing remote attachments. It implements +// an nsIStreamListener interface to deal with incoming data +// +class nsMsgAttachmentHandler : public nsIMsgAttachmentHandler +{ + + typedef mozilla::mailnews::MimeEncoder MimeEncoder; +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMSGATTACHMENTHANDLER + + nsMsgAttachmentHandler(); +public: + nsresult SnarfAttachment(nsMsgCompFields *compFields); + nsresult PickEncoding(const char *charset, nsIMsgSend* mime_delivery_state); + nsresult PickCharset(); + void AnalyzeSnarfedFile (); // Analyze a previously-snarfed file. + // (Currently only used for plaintext + // converted from HTML.) + nsresult Abort(); + nsresult UrlExit(nsresult status, const char16_t* aMsg); + + // if there's an intermediate temp file left, takes care to remove it from disk. + // + // NOTE: this takes care of the mEncodedWorkingFile temp file, but not mTmpFile which seems + // to be used by lots of other classes at the moment. + void CleanupTempFile(); + +private: + virtual ~nsMsgAttachmentHandler(); + + // use when a message (e.g. original message in a reply) is attached as a rfc822 attachment. + nsresult SnarfMsgAttachment(nsMsgCompFields *compFields); + bool UseUUEncode_p(void); + void AnalyzeDataChunk (const char *chunk, int32_t chunkSize); + nsresult LoadDataFromFile(nsIFile *file, nsString &sigData, bool charsetConversion); //A similar function already exist in nsMsgCompose! +#ifdef XP_MACOSX + nsresult ConvertToAppleEncoding(const nsCString &aFileSpecURI, + const nsCString &aFilePath, + nsILocalFileMac *aSourceFile); + // zips this attachment and does the work to make this attachment handler handle it properly. + nsresult ConvertToZipFile(nsILocalFileMac *aSourceFile); + bool HasResourceFork(FSRef *fsRef); +#endif + + // +public: + nsCOMPtr<nsIURI> mURL; + nsCOMPtr<nsIFile> mTmpFile; // The temp file to which we save it + nsCOMPtr<nsIOutputStream> mOutFile; + nsCOMPtr<nsIRequest> mRequest; // The live request used while fetching an attachment + nsMsgCompFields *mCompFields; // Message composition fields for the sender + bool m_bogus_attachment; // This is to catch problem children... + +#ifdef XP_MACOSX + // if we need to encode this file into for example an appledouble, or zip file, + // this file is our working file. currently only needed on mac. + nsCOMPtr<nsIFile> mEncodedWorkingFile; +#endif + + nsCString m_xMacType; // Mac file type + nsCString m_xMacCreator; // Mac file creator + + bool m_done; + nsCString m_charset; // charset name + nsCString m_contentId; // This is for mutipart/related Content-ID's + nsCString m_type; // The real type, once we know it. + nsCString m_typeParam; // Any addition parameters to add to the content-type (other than charset, macType and maccreator) + nsCString m_overrideType; // The type we should assume it to be + // or 0, if we should get it from the + // server) + nsCString m_overrideEncoding; // Goes along with override_type + + nsCString m_desiredType; // The type it should be converted to. + nsCString m_description; // For Content-Description header + nsCString m_realName; // The name for the headers, if different + // from the URL. + nsCString m_encoding; // The encoding, once we've decided. */ + bool m_already_encoded_p; // If we attach a document that is already + // encoded, we just pass it through. + + bool m_decrypted_p; /* S/MIME -- when attaching a message that was + encrypted, it's necessary to decrypt it first + (since nobody but the original recipient can + read it -- if you forward it to someone in the + raw, it will be useless to them.) This flag + indicates whether decryption occurred, so that + libmsg can issue appropriate warnings about + doing a cleartext forward of a message that was + originally encrypted. */ + + bool mDeleteFile; // If this is true, Delete the file...its + // NOT the original file! + + bool mMHTMLPart; // This is true if its an MHTML part, otherwise, false + bool mPartUserOmissionOverride; // This is true if the user send send the email without this part + bool mMainBody; // True if this is a main body. + // true if this should be sent as a link to a file. + bool mSendViaCloud; + nsString mHtmlAnnotation; + nsCString mCloudProviderKey; + nsCString mCloudUrl; + int32_t mNodeIndex; //If this is an embedded image, this is the index of the + // corresponding domNode in the editor's + //GetEmbeddedObjects. Otherwise, it will be -1. + // + // Vars for analyzing file data... + // + uint32_t m_size; /* Some state used while filtering it */ + uint32_t m_unprintable_count; + uint32_t m_highbit_count; + uint32_t m_ctl_count; + uint32_t m_null_count; + uint8_t m_have_cr, m_have_lf, m_have_crlf; + bool m_prev_char_was_cr; + uint32_t m_current_column; + uint32_t m_max_column; + uint32_t m_lines; + bool m_file_analyzed; + + nsAutoPtr<MimeEncoder> m_encoder; + nsCString m_uri; // original uri string + + nsresult GetMimeDeliveryState(nsIMsgSend** _retval); + nsresult SetMimeDeliveryState(nsIMsgSend* mime_delivery_state); +private: + nsCOMPtr<nsIMsgSend> m_mime_delivery_state; + nsCOMPtr<nsIStreamConverter> m_mime_parser; + nsCOMPtr<nsIChannel> m_converter_channel; +}; + + +#endif /* _nsMsgAttachmentHandler_H_ */ diff --git a/mailnews/compose/src/nsMsgCompFields.cpp b/mailnews/compose/src/nsMsgCompFields.cpp new file mode 100644 index 000000000..c65e6ca17 --- /dev/null +++ b/mailnews/compose/src/nsMsgCompFields.cpp @@ -0,0 +1,693 @@ +/* -*- 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 "nsMsgCompose.h" +#include "nsMsgCompFields.h" +#include "nsMsgI18N.h" +#include "nsMsgCompUtils.h" +#include "nsMsgUtils.h" +#include "prmem.h" +#include "nsIFileChannel.h" +#include "nsIMsgAttachment.h" +#include "nsIMsgMdnGenerator.h" +#include "nsServiceManagerUtils.h" +#include "nsMsgMimeCID.h" +#include "nsArrayEnumerator.h" +#include "nsMemory.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/mailnews/MimeHeaderParser.h" + +using namespace mozilla::mailnews; + +struct HeaderInfo { + /// Header name + const char *mName; + /// If true, nsMsgCompFields should reflect the raw header value instead of + /// the unstructured header value. + bool mStructured; +}; + +// This is a mapping of the m_headers local set to the actual header name we +// store on the structured header object. +static HeaderInfo kHeaders[] = { + { "From", true }, + { "Reply-To", true }, + { "To", true }, + { "Cc", true }, + { "Bcc", true }, + { nullptr, false }, // FCC + { nullptr, false }, // FCC2 + { "Newsgroups", true }, + { "Followup-To", true }, + { "Subject", false }, + { "Organization", false }, + { "References", true }, + { "X-Mozilla-News-Host", false }, + { "X-Priority", false }, + { nullptr, false }, // CHARACTER_SET + { "Message-Id", true }, + { "X-Template", true }, + { nullptr, false }, // DRAFT_ID + { "Content-Language", true }, + { nullptr, false } // CREATOR IDENTITY KEY +}; + +static_assert(MOZ_ARRAY_LENGTH(kHeaders) == + nsMsgCompFields::MSG_MAX_HEADERS, + "These two arrays need to be kept in sync or bad things will happen!"); + +NS_IMPL_ISUPPORTS(nsMsgCompFields, nsIMsgCompFields, msgIStructuredHeaders, + msgIWritableStructuredHeaders) + +nsMsgCompFields::nsMsgCompFields() +: mStructuredHeaders(do_CreateInstance(NS_ISTRUCTUREDHEADERS_CONTRACTID)) +{ + m_body.Truncate(); + + m_attachVCard = false; + m_forcePlainText = false; + m_useMultipartAlternative = false; + m_returnReceipt = false; + m_receiptHeaderType = nsIMsgMdnGenerator::eDntType; + m_DSN = false; + m_bodyIsAsciiOnly = false; + m_forceMsgEncoding = false; + m_needToCheckCharset = true; + m_attachmentReminder = false; + m_deliveryFormat = nsIMsgCompSendFormat::AskUser; + + // Get the default charset from pref, use this as a mail charset. + nsString charset; + NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, "mailnews.send_default_charset", + NS_LITERAL_STRING("UTF-8"), charset); + + LossyCopyUTF16toASCII(charset, m_DefaultCharacterSet); // Charsets better be ASCII + SetCharacterSet(m_DefaultCharacterSet.get()); +} + +nsMsgCompFields::~nsMsgCompFields() +{ +} + +nsresult nsMsgCompFields::SetAsciiHeader(MsgHeaderID header, const char *value) +{ + NS_ASSERTION(header >= 0 && header < MSG_MAX_HEADERS, + "Invalid message header index!"); + + // If we are storing this on the structured header object, we need to set the + // value on that object as well. Note that the value may be null, which we'll + // take as an attempt to delete the header. + const char *headerName = kHeaders[header].mName; + if (headerName) + { + if (!value || !*value) + return mStructuredHeaders->DeleteHeader(headerName); + + return mStructuredHeaders->SetRawHeader(headerName, + nsDependentCString(value), "UTF-8"); + } + + // Not on the structurd header object, so save it locally. + m_headers[header] = value; + + return NS_OK; +} + +const char* nsMsgCompFields::GetAsciiHeader(MsgHeaderID header) +{ + NS_ASSERTION(header >= 0 && header < MSG_MAX_HEADERS, + "Invalid message header index!"); + + const char *headerName = kHeaders[header].mName; + if (headerName) + { + // We may be out of sync with the structured header object. Retrieve the + // header value. + if (kHeaders[header].mStructured) + { + mStructuredHeaders->GetRawHeader(headerName, m_headers[header]); + } + else + { + nsString value; + mStructuredHeaders->GetUnstructuredHeader(headerName, value); + CopyUTF16toUTF8(value, m_headers[header]); + } + } + + return m_headers[header].get(); +} + +nsresult nsMsgCompFields::SetUnicodeHeader(MsgHeaderID header, const nsAString& value) +{ + return SetAsciiHeader(header, NS_ConvertUTF16toUTF8(value).get()); +} + +nsresult nsMsgCompFields::GetUnicodeHeader(MsgHeaderID header, nsAString& aResult) +{ + CopyUTF8toUTF16(nsDependentCString(GetAsciiHeader(header)), aResult); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetFrom(const nsAString &value) +{ + return SetUnicodeHeader(MSG_FROM_HEADER_ID, value); +} + + +NS_IMETHODIMP nsMsgCompFields::GetFrom(nsAString &_retval) +{ + return GetUnicodeHeader(MSG_FROM_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetReplyTo(const nsAString &value) +{ + return SetUnicodeHeader(MSG_REPLY_TO_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetReplyTo(nsAString &_retval) +{ + return GetUnicodeHeader(MSG_REPLY_TO_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetTo(const nsAString &value) +{ + return SetUnicodeHeader(MSG_TO_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetTo(nsAString &_retval) +{ + return GetUnicodeHeader(MSG_TO_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetCc(const nsAString &value) +{ + return SetUnicodeHeader(MSG_CC_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetCc(nsAString &_retval) +{ + return GetUnicodeHeader(MSG_CC_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetBcc(const nsAString &value) +{ + return SetUnicodeHeader(MSG_BCC_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetBcc(nsAString &_retval) +{ + return GetUnicodeHeader(MSG_BCC_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetFcc(const nsAString &value) +{ + return SetUnicodeHeader(MSG_FCC_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetFcc(nsAString &_retval) +{ + return GetUnicodeHeader(MSG_FCC_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetFcc2(const nsAString &value) +{ + return SetUnicodeHeader(MSG_FCC2_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetFcc2(nsAString &_retval) +{ + return GetUnicodeHeader(MSG_FCC2_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetNewsgroups(const nsAString &aValue) +{ + return SetUnicodeHeader(MSG_NEWSGROUPS_HEADER_ID, aValue); +} + +NS_IMETHODIMP nsMsgCompFields::GetNewsgroups(nsAString &aGroup) +{ + return GetUnicodeHeader(MSG_NEWSGROUPS_HEADER_ID, aGroup); +} + +NS_IMETHODIMP nsMsgCompFields::SetFollowupTo(const nsAString &aValue) +{ + return SetUnicodeHeader(MSG_FOLLOWUP_TO_HEADER_ID, aValue); +} + +NS_IMETHODIMP nsMsgCompFields::GetFollowupTo(nsAString &_retval) +{ + return GetUnicodeHeader(MSG_FOLLOWUP_TO_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::GetHasRecipients(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + *_retval = NS_SUCCEEDED(mime_sanity_check_fields_recipients( + GetTo(), GetCc(), GetBcc(), GetNewsgroups())); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetCreatorIdentityKey(const char *value) +{ + return SetAsciiHeader(MSG_CREATOR_IDENTITY_KEY_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetCreatorIdentityKey(char **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = strdup(GetAsciiHeader(MSG_CREATOR_IDENTITY_KEY_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetSubject(const nsAString &value) +{ + return SetUnicodeHeader(MSG_SUBJECT_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetSubject(nsAString &_retval) +{ + return GetUnicodeHeader(MSG_SUBJECT_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetOrganization(const nsAString &value) +{ + return SetUnicodeHeader(MSG_ORGANIZATION_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetOrganization(nsAString &_retval) +{ + return GetUnicodeHeader(MSG_ORGANIZATION_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetReferences(const char *value) +{ + return SetAsciiHeader(MSG_REFERENCES_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetReferences(char **_retval) +{ + *_retval = strdup(GetAsciiHeader(MSG_REFERENCES_HEADER_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetNewspostUrl(const char *value) +{ + return SetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetNewspostUrl(char **_retval) +{ + *_retval = strdup(GetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetPriority(const char *value) +{ + return SetAsciiHeader(MSG_PRIORITY_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetPriority(char **_retval) +{ + *_retval = strdup(GetAsciiHeader(MSG_PRIORITY_HEADER_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetCharacterSet(const char *value) +{ + return SetAsciiHeader(MSG_CHARACTER_SET_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetCharacterSet(char **_retval) +{ + *_retval = strdup(GetAsciiHeader(MSG_CHARACTER_SET_HEADER_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetMessageId(const char *value) +{ + return SetAsciiHeader(MSG_MESSAGE_ID_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetMessageId(char **_retval) +{ + *_retval = strdup(GetAsciiHeader(MSG_MESSAGE_ID_HEADER_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetTemplateName(const nsAString &value) +{ + return SetUnicodeHeader(MSG_X_TEMPLATE_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetTemplateName(nsAString &_retval) +{ + return GetUnicodeHeader(MSG_X_TEMPLATE_HEADER_ID, _retval); +} + +NS_IMETHODIMP nsMsgCompFields::SetDraftId(const char *value) +{ + return SetAsciiHeader(MSG_DRAFT_ID_HEADER_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetDraftId(char **_retval) +{ + *_retval = strdup(GetAsciiHeader(MSG_DRAFT_ID_HEADER_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetReturnReceipt(bool value) +{ + m_returnReceipt = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetReturnReceipt(bool *_retval) +{ + *_retval = m_returnReceipt; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetReceiptHeaderType(int32_t value) +{ + m_receiptHeaderType = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetReceiptHeaderType(int32_t *_retval) +{ + *_retval = m_receiptHeaderType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetDSN(bool value) +{ + m_DSN = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetDSN(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_DSN; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetAttachVCard(bool value) +{ + m_attachVCard = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetAttachVCard(bool *_retval) +{ + *_retval = m_attachVCard; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetAttachmentReminder(bool *_retval) +{ + *_retval = m_attachmentReminder; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetAttachmentReminder(bool value) +{ + m_attachmentReminder = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetDeliveryFormat(int32_t value) +{ + switch (value) { + case nsIMsgCompSendFormat::AskUser: + case nsIMsgCompSendFormat::PlainText: + case nsIMsgCompSendFormat::HTML: + case nsIMsgCompSendFormat::Both: + m_deliveryFormat = value; + break; + default: + m_deliveryFormat = nsIMsgCompSendFormat::AskUser; + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetDeliveryFormat(int32_t *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_deliveryFormat; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetContentLanguage(const char *value) +{ + return SetAsciiHeader(MSG_CONTENT_LANGUAGE_ID, value); +} + +NS_IMETHODIMP nsMsgCompFields::GetContentLanguage(char **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = strdup(GetAsciiHeader(MSG_CONTENT_LANGUAGE_ID)); + return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::SetForcePlainText(bool value) +{ + m_forcePlainText = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetForcePlainText(bool *_retval) +{ + *_retval = m_forcePlainText; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetForceMsgEncoding(bool value) +{ + m_forceMsgEncoding = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetForceMsgEncoding(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_forceMsgEncoding; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetUseMultipartAlternative(bool value) +{ + m_useMultipartAlternative = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetUseMultipartAlternative(bool *_retval) +{ + *_retval = m_useMultipartAlternative; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetBodyIsAsciiOnly(bool value) +{ + m_bodyIsAsciiOnly = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetBodyIsAsciiOnly(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + *_retval = m_bodyIsAsciiOnly; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetBody(const nsAString &value) +{ + CopyUTF16toUTF8(value, m_body); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetBody(nsAString &_retval) +{ + CopyUTF8toUTF16(m_body, _retval); + return NS_OK; +} + +nsresult nsMsgCompFields::SetBody(const char *value) +{ + if (value) + m_body = value; + else + m_body.Truncate(); + return NS_OK; +} + +const char* nsMsgCompFields::GetBody() +{ + return m_body.get(); +} + +/* readonly attribute nsISimpleEnumerator attachmentsArray; */ +NS_IMETHODIMP nsMsgCompFields::GetAttachments(nsISimpleEnumerator * *aAttachmentsEnum) +{ + return aAttachmentsEnum ? NS_NewArrayEnumerator(aAttachmentsEnum, m_attachments) : NS_ERROR_NULL_POINTER; +} + +/* void addAttachment (in nsIMsgAttachment attachment); */ +NS_IMETHODIMP nsMsgCompFields::AddAttachment(nsIMsgAttachment *attachment) +{ + int32_t attachmentCount = m_attachments.Count(); + + //Don't add twice the same attachment. + nsCOMPtr<nsIMsgAttachment> element; + bool sameUrl; + for (int32_t i = 0; i < attachmentCount; i ++) + { + m_attachments[i]->EqualsUrl(attachment, &sameUrl); + if (sameUrl) + return NS_OK; + } + m_attachments.AppendObject(attachment); + + return NS_OK; +} + +/* void removeAttachment (in nsIMsgAttachment attachment); */ +NS_IMETHODIMP nsMsgCompFields::RemoveAttachment(nsIMsgAttachment *attachment) +{ + int32_t attachmentCount = m_attachments.Count(); + + nsCOMPtr<nsIMsgAttachment> element; + bool sameUrl; + for (int32_t i = 0; i < attachmentCount; i ++) + { + m_attachments[i]->EqualsUrl(attachment, &sameUrl); + if (sameUrl) + { + m_attachments.RemoveObjectAt(i); + break; + } + } + + return NS_OK; +} + +/* void removeAttachments (); */ +NS_IMETHODIMP nsMsgCompFields::RemoveAttachments() +{ + m_attachments.Clear(); + + return NS_OK; +} + + +// This method is called during the creation of a new window. +NS_IMETHODIMP +nsMsgCompFields::SplitRecipients(const nsAString &aRecipients, + bool aEmailAddressOnly, + uint32_t *aLength, + char16_t*** aResult) +{ + NS_ENSURE_ARG_POINTER(aLength); + NS_ENSURE_ARG_POINTER(aResult); + + *aLength = 0; + *aResult = nullptr; + + nsCOMArray<msgIAddressObject> header(EncodedHeader(NS_ConvertUTF16toUTF8(aRecipients))); + nsTArray<nsString> results; + if (aEmailAddressOnly) + ExtractEmails(header, results); + else + ExtractDisplayAddresses(header, results); + + uint32_t count = results.Length(); + char16_t **result = (char16_t **)NS_Alloc(sizeof(char16_t *) * count); + for (uint32_t i = 0; i < count; ++i) + result[i] = ToNewUnicode(results[i]); + + *aResult = result; + *aLength = count; + return NS_OK; +} + + +// This method is called during the sending of message from nsMsgCompose::CheckAndPopulateRecipients() +nsresult nsMsgCompFields::SplitRecipientsEx(const nsAString &recipients, + nsTArray<nsMsgRecipient> &aResult) +{ + nsTArray<nsString> names, addresses; + ExtractAllAddresses(EncodedHeader(NS_ConvertUTF16toUTF8(recipients)), names, + addresses); + + uint32_t numAddresses = names.Length(); + for (uint32_t i = 0; i < numAddresses; ++i) + { + nsMsgRecipient msgRecipient; + msgRecipient.mEmail = addresses[i]; + msgRecipient.mName = names[i]; + aResult.AppendElement(msgRecipient); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::ConvertBodyToPlainText() +{ + nsresult rv = NS_OK; + + if (!m_body.IsEmpty()) + { + nsAutoString body; + rv = GetBody(body); + if (NS_SUCCEEDED(rv)) + { + bool flowed, delsp, formatted, disallowBreaks; + GetSerialiserFlags(GetCharacterSet(), &flowed, &delsp, &formatted, &disallowBreaks); + rv = ConvertBufToPlainText(body, flowed, delsp, formatted, disallowBreaks); + if (NS_SUCCEEDED(rv)) + rv = SetBody(body); + } + } + return rv; +} + +NS_IMETHODIMP nsMsgCompFields::GetSecurityInfo(nsISupports ** aSecurityInfo) +{ + NS_ENSURE_ARG_POINTER(aSecurityInfo); + *aSecurityInfo = mSecureCompFields; + NS_IF_ADDREF(*aSecurityInfo); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetSecurityInfo(nsISupports * aSecurityInfo) +{ + mSecureCompFields = aSecurityInfo; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::GetDefaultCharacterSet(char * *aDefaultCharacterSet) +{ + NS_ENSURE_ARG_POINTER(aDefaultCharacterSet); + *aDefaultCharacterSet = ToNewCString(m_DefaultCharacterSet); + return *aDefaultCharacterSet ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompFields::GetNeedToCheckCharset(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_needToCheckCharset; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompFields::SetNeedToCheckCharset(bool aCheck) +{ + m_needToCheckCharset = aCheck; + return NS_OK; +} diff --git a/mailnews/compose/src/nsMsgCompFields.h b/mailnews/compose/src/nsMsgCompFields.h new file mode 100644 index 000000000..ecc562499 --- /dev/null +++ b/mailnews/compose/src/nsMsgCompFields.h @@ -0,0 +1,172 @@ +/* -*- 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 _MsgCompFields_H_ +#define _MsgCompFields_H_ + +#include "nsIMsgCompFields.h" +#include "msgCore.h" +#include "nsIAbCard.h" +#include "nsIAbDirectory.h" +#include "nsTArray.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" + +struct nsMsgRecipient +{ + nsString mName; + nsString mEmail; + nsCOMPtr<nsIAbCard> mCard; + nsCOMPtr<nsIAbDirectory> mDirectory; +}; + +/* Note that all the "Get" methods never return NULL (except in case of serious + error, like an illegal parameter); rather, they return "" if things were set + to NULL. This makes it real handy for the callers. */ + +class nsMsgCompFields : public nsIMsgCompFields { +public: + nsMsgCompFields(); + + /* this macro defines QueryInterface, AddRef and Release for this class */ + NS_DECL_THREADSAFE_ISUPPORTS + NS_FORWARD_MSGISTRUCTUREDHEADERS(mStructuredHeaders->) + NS_FORWARD_MSGIWRITABLESTRUCTUREDHEADERS(mStructuredHeaders->) + NS_DECL_NSIMSGCOMPFIELDS + + // Allow the C++ utility methods for people who use a concrete class instead + // of the interfaces. + using msgIStructuredHeaders::GetAddressingHeader; + using msgIWritableStructuredHeaders::SetAddressingHeader; + + typedef enum MsgHeaderID + { + MSG_FROM_HEADER_ID = 0, + MSG_REPLY_TO_HEADER_ID, + MSG_TO_HEADER_ID, + MSG_CC_HEADER_ID, + MSG_BCC_HEADER_ID, + MSG_FCC_HEADER_ID, + MSG_FCC2_HEADER_ID, + MSG_NEWSGROUPS_HEADER_ID, + MSG_FOLLOWUP_TO_HEADER_ID, + MSG_SUBJECT_HEADER_ID, + MSG_ORGANIZATION_HEADER_ID, + MSG_REFERENCES_HEADER_ID, + MSG_NEWSPOSTURL_HEADER_ID, + MSG_PRIORITY_HEADER_ID, + MSG_CHARACTER_SET_HEADER_ID, + MSG_MESSAGE_ID_HEADER_ID, + MSG_X_TEMPLATE_HEADER_ID, + MSG_DRAFT_ID_HEADER_ID, + MSG_CONTENT_LANGUAGE_ID, + MSG_CREATOR_IDENTITY_KEY_ID, + + MSG_MAX_HEADERS //Must be the last one. + } MsgHeaderID; + + nsresult SetAsciiHeader(MsgHeaderID header, const char *value); + const char* GetAsciiHeader(MsgHeaderID header); //just return the address of the internal header variable, don't dispose it + + nsresult SetUnicodeHeader(MsgHeaderID header, const nsAString &value); + nsresult GetUnicodeHeader(MsgHeaderID header, nsAString &_retval); + + /* Convenience routines to get and set header's value... + + IMPORTANT: + all routines const char* GetXxx(void) will return a pointer to the header, please don't free it. + */ + + nsresult SetFrom(const char *value) {return SetAsciiHeader(MSG_FROM_HEADER_ID, value);} + const char* GetFrom(void) {return GetAsciiHeader(MSG_FROM_HEADER_ID);} + + nsresult SetReplyTo(const char *value) {return SetAsciiHeader(MSG_REPLY_TO_HEADER_ID, value);} + const char* GetReplyTo() {return GetAsciiHeader(MSG_REPLY_TO_HEADER_ID);} + + nsresult SetTo(const char *value) {return SetAsciiHeader(MSG_TO_HEADER_ID, value);} + const char* GetTo() {return GetAsciiHeader(MSG_TO_HEADER_ID);} + + nsresult SetCc(const char *value) {return SetAsciiHeader(MSG_CC_HEADER_ID, value);} + const char* GetCc() {return GetAsciiHeader(MSG_CC_HEADER_ID);} + + nsresult SetBcc(const char *value) {return SetAsciiHeader(MSG_BCC_HEADER_ID, value);} + const char* GetBcc() {return GetAsciiHeader(MSG_BCC_HEADER_ID);} + + nsresult SetFcc(const char *value) {return SetAsciiHeader(MSG_FCC_HEADER_ID, value);} + const char* GetFcc() {return GetAsciiHeader(MSG_FCC_HEADER_ID);} + + nsresult SetFcc2(const char *value) {return SetAsciiHeader(MSG_FCC2_HEADER_ID, value);} + const char* GetFcc2() {return GetAsciiHeader(MSG_FCC2_HEADER_ID);} + + nsresult SetNewsgroups(const char *aValue) {return SetAsciiHeader(MSG_NEWSGROUPS_HEADER_ID, aValue);} + const char* GetNewsgroups() {return GetAsciiHeader(MSG_NEWSGROUPS_HEADER_ID);} + + nsresult SetFollowupTo(const char *aValue) {return SetAsciiHeader(MSG_FOLLOWUP_TO_HEADER_ID, aValue);} + const char* GetFollowupTo() {return GetAsciiHeader(MSG_FOLLOWUP_TO_HEADER_ID);} + + nsresult SetSubject(const char *value) {return SetAsciiHeader(MSG_SUBJECT_HEADER_ID, value);} + const char* GetSubject() {return GetAsciiHeader(MSG_SUBJECT_HEADER_ID);} + + nsresult SetOrganization(const char *value) {return SetAsciiHeader(MSG_ORGANIZATION_HEADER_ID, value);} + const char* GetOrganization() {return GetAsciiHeader(MSG_ORGANIZATION_HEADER_ID);} + + const char* GetReferences() {return GetAsciiHeader(MSG_REFERENCES_HEADER_ID);} + + const char* GetNewspostUrl() {return GetAsciiHeader(MSG_NEWSPOSTURL_HEADER_ID);} + + const char* GetPriority() {return GetAsciiHeader(MSG_PRIORITY_HEADER_ID);} + + const char* GetCharacterSet() {return GetAsciiHeader(MSG_CHARACTER_SET_HEADER_ID);} + + const char* GetMessageId() {return GetAsciiHeader(MSG_MESSAGE_ID_HEADER_ID);} + + nsresult SetTemplateName(const char *value) {return SetAsciiHeader(MSG_X_TEMPLATE_HEADER_ID, value);} + const char* GetTemplateName() {return GetAsciiHeader(MSG_X_TEMPLATE_HEADER_ID);} + + const char* GetDraftId() {return GetAsciiHeader(MSG_DRAFT_ID_HEADER_ID);} + + const char* GetContentLanguage() {return GetAsciiHeader(MSG_CONTENT_LANGUAGE_ID);} + + bool GetReturnReceipt() {return m_returnReceipt;} + bool GetDSN() {return m_DSN;} + bool GetAttachVCard() {return m_attachVCard;} + bool GetAttachmentReminder() {return m_attachmentReminder;} + int32_t GetDeliveryFormat() {return m_deliveryFormat;} + bool GetForcePlainText() {return m_forcePlainText;} + bool GetUseMultipartAlternative() {return m_useMultipartAlternative;} + bool GetBodyIsAsciiOnly() {return m_bodyIsAsciiOnly;} + bool GetForceMsgEncoding() {return m_forceMsgEncoding;} + + nsresult SetBody(const char *value); + const char* GetBody(); + + nsresult SplitRecipientsEx(const nsAString &recipients, + nsTArray<nsMsgRecipient> &aResult); + +protected: + virtual ~nsMsgCompFields(); + nsCString m_headers[MSG_MAX_HEADERS]; + nsCString m_body; + nsCOMArray<nsIMsgAttachment> m_attachments; + bool m_attachVCard; + bool m_attachmentReminder; + int32_t m_deliveryFormat; + bool m_forcePlainText; + bool m_useMultipartAlternative; + bool m_returnReceipt; + bool m_DSN; + bool m_bodyIsAsciiOnly; + bool m_forceMsgEncoding; + int32_t m_receiptHeaderType; /* receipt header type */ + nsCString m_DefaultCharacterSet; + bool m_needToCheckCharset; + + nsCOMPtr<nsISupports> mSecureCompFields; + nsCOMPtr<msgIWritableStructuredHeaders> mStructuredHeaders; +}; + + +#endif /* _MsgCompFields_H_ */ diff --git a/mailnews/compose/src/nsMsgCompUtils.cpp b/mailnews/compose/src/nsMsgCompUtils.cpp new file mode 100644 index 000000000..4615f0f36 --- /dev/null +++ b/mailnews/compose/src/nsMsgCompUtils.cpp @@ -0,0 +1,1803 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "nsCOMPtr.h" +#include "nsMsgCompUtils.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "prmem.h" +#include "nsMsgSend.h" +#include "nsIIOService.h" +#include "nsIHttpProtocolHandler.h" +#include "nsMailHeaders.h" +#include "nsMsgI18N.h" +#include "nsINntpService.h" +#include "nsMimeTypes.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIURI.h" +#include "nsNetCID.h" +#include "nsMsgPrompts.h" +#include "nsMsgUtils.h" +#include "nsCExternalHandlerService.h" +#include "nsIMIMEService.h" +#include "nsComposeStrings.h" +#include "nsIMsgCompUtils.h" +#include "nsIMsgMdnGenerator.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMemory.h" +#include "nsCRTGlue.h" +#include <ctype.h> +#include "mozilla/mailnews/Services.h" +#include "mozilla/Services.h" +#include "nsIMIMEInfo.h" +#include "nsIMsgHeaderParser.h" +#include "nsIRandomGenerator.h" +#include "nsID.h" + +NS_IMPL_ISUPPORTS(nsMsgCompUtils, nsIMsgCompUtils) + +nsMsgCompUtils::nsMsgCompUtils() +{ +} + +nsMsgCompUtils::~nsMsgCompUtils() +{ +} + +NS_IMETHODIMP nsMsgCompUtils::MimeMakeSeparator(const char *prefix, + char **_retval) +{ + NS_ENSURE_ARG_POINTER(prefix); + NS_ENSURE_ARG_POINTER(_retval); + *_retval = mime_make_separator(prefix); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompUtils::MsgGenerateMessageId(nsIMsgIdentity *identity, + char **_retval) +{ + NS_ENSURE_ARG_POINTER(identity); + NS_ENSURE_ARG_POINTER(_retval); + *_retval = msg_generate_message_id(identity); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompUtils::GetMsgMimeConformToStandard(bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nsMsgMIMEGetConformToStandard(); + return NS_OK; +} + +// +// Create a file for the a unique temp file +// on the local machine. Caller must free memory +// +nsresult +nsMsgCreateTempFile(const char *tFileName, nsIFile **tFile) +{ + if ((!tFileName) || (!*tFileName)) + tFileName = "nsmail.tmp"; + + nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + tFileName, + tFile); + + NS_ENSURE_SUCCESS(rv, rv); + + rv = (*tFile)->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + if (NS_FAILED(rv)) + NS_RELEASE(*tFile); + + return rv; +} + +// +// Create a file spec for the a unique temp file +// on the local machine. Caller must free memory +// returned +// +char * +nsMsgCreateTempFileName(const char *tFileName) +{ + if ((!tFileName) || (!*tFileName)) + tFileName = "nsmail.tmp"; + + nsCOMPtr<nsIFile> tmpFile; + + nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, + tFileName, + getter_AddRefs(tmpFile)); + if (NS_FAILED(rv)) + return nullptr; + + rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600); + if (NS_FAILED(rv)) + return nullptr; + + nsCString tempString; + rv = tmpFile->GetNativePath(tempString); + if (NS_FAILED(rv)) + return nullptr; + + char *tString = ToNewCString(tempString); + if (!tString) + return PL_strdup("mozmail.tmp"); // No need to I18N + + return tString; +} + +// This is the value a caller will Get if they don't Set first (like MDN +// sending a return receipt), so init to the default value of the +// mail.strictly_mime_headers preference. +static bool mime_headers_use_quoted_printable_p = true; + +bool +nsMsgMIMEGetConformToStandard (void) +{ + return mime_headers_use_quoted_printable_p; +} + +void +nsMsgMIMESetConformToStandard (bool conform_p) +{ + /* + * If we are conforming to mime standard no matter what we set + * for the headers preference when generating mime headers we should + * also conform to the standard. Otherwise, depends the preference + * we set. For now, the headers preference is not accessible from UI. + */ + if (conform_p) + mime_headers_use_quoted_printable_p = true; + else { + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + prefs->GetBoolPref("mail.strictly_mime_headers", &mime_headers_use_quoted_printable_p); + } + } +} + +/** + * Checks if the recipient fields have sane values for message send. + */ +nsresult mime_sanity_check_fields_recipients ( + const char *to, + const char *cc, + const char *bcc, + const char *newsgroups) +{ + if (to) + while (IS_SPACE(*to)) + to++; + if (cc) + while (IS_SPACE(*cc)) + cc++; + if (bcc) + while (IS_SPACE(*bcc)) + bcc++; + if (newsgroups) + while (IS_SPACE(*newsgroups)) + newsgroups++; + + if ((!to || !*to) && (!cc || !*cc) && + (!bcc || !*bcc) && (!newsgroups || !*newsgroups)) + return NS_MSG_NO_RECIPIENTS; + + return NS_OK; +} + +/** + * Checks if the compose fields have sane values for message send. + */ +nsresult mime_sanity_check_fields ( + const char *from, + const char *reply_to, + const char *to, + const char *cc, + const char *bcc, + const char *fcc, + const char *newsgroups, + const char *followup_to, + const char * /*subject*/, + const char * /*references*/, + const char * /*organization*/, + const char * /*other_random_headers*/) +{ + if (from) + while (IS_SPACE(*from)) + from++; + if (reply_to) + while (IS_SPACE(*reply_to)) + reply_to++; + if (fcc) + while (IS_SPACE(*fcc)) + fcc++; + if (followup_to) + while (IS_SPACE(*followup_to)) + followup_to++; + + // TODO: sanity check other_random_headers for newline conventions + if (!from || !*from) + return NS_MSG_NO_SENDER; + + return mime_sanity_check_fields_recipients(to, cc, bcc, newsgroups); +} + +// +// Generate the message headers for the new RFC822 message +// +#define UA_PREF_PREFIX "general.useragent." + +// Helper macro for generating the X-Mozilla-Draft-Info header. +#define APPEND_BOOL(method, param) \ + do { \ + bool val = false; \ + fields->Get##method(&val); \ + if (val) \ + draftInfo.AppendLiteral(param "=1"); \ + else \ + draftInfo.AppendLiteral(param "=0"); \ + } while (false) + +nsresult mime_generate_headers(nsIMsgCompFields *fields, + nsMsgDeliverMode deliver_mode, + msgIWritableStructuredHeaders *finalHeaders) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isDraft = + deliver_mode == nsIMsgSend::nsMsgSaveAsDraft || + deliver_mode == nsIMsgSend::nsMsgSaveAsTemplate || + deliver_mode == nsIMsgSend::nsMsgQueueForLater || + deliver_mode == nsIMsgSend::nsMsgDeliverBackground; + + bool hasDisclosedRecipient = false; + + MOZ_ASSERT(fields, "null fields"); + NS_ENSURE_ARG_POINTER(fields); + + nsCOMArray<msgIAddressObject> from; + fields->GetAddressingHeader("From", from, true); + + // Copy all headers from the original compose field. + rv = finalHeaders->AddAllHeaders(fields); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMessageId = false; + if (NS_SUCCEEDED(fields->HasHeader("Message-ID", &hasMessageId)) && + hasMessageId) + { + /* MDN request header requires to have MessageID header presented + * in the message in order to + * coorelate the MDN reports to the original message. Here will be + * the right place + */ + + bool returnReceipt = false; + fields->GetReturnReceipt(&returnReceipt); + if (returnReceipt && + (deliver_mode != nsIMsgSend::nsMsgSaveAsDraft && + deliver_mode != nsIMsgSend::nsMsgSaveAsTemplate)) + { + int32_t receipt_header_type = nsIMsgMdnGenerator::eDntType; + fields->GetReceiptHeaderType(&receipt_header_type); + + // nsIMsgMdnGenerator::eDntType = MDN Disposition-Notification-To: ; + // nsIMsgMdnGenerator::eRrtType = Return-Receipt-To: ; + // nsIMsgMdnGenerator::eDntRrtType = both MDN DNT and RRT headers . + if (receipt_header_type != nsIMsgMdnGenerator::eRrtType) + finalHeaders->SetAddressingHeader("Disposition-Notification-To", from); + if (receipt_header_type != nsIMsgMdnGenerator::eDntType) + finalHeaders->SetAddressingHeader("Return-Receipt-To", from); + } + } + + PRExplodedTime now; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now); + int gmtoffset = (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset) / 60; + + /* Use PR_FormatTimeUSEnglish() to format the date in US English format, + then figure out what our local GMT offset is, and append it (since + PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as + per RFC 1123 (superceding RFC 822.) + */ + char dateString[130]; + PR_FormatTimeUSEnglish(dateString, sizeof(dateString), + "%a, %d %b %Y %H:%M:%S ", + &now); + + char *entryPoint = dateString + strlen(dateString); + PR_snprintf(entryPoint, sizeof(dateString) - (entryPoint - dateString), + "%c%02d%02d" CRLF, + (gmtoffset >= 0 ? '+' : '-'), + ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60), + ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60)); + finalHeaders->SetRawHeader("Date", nsDependentCString(dateString), nullptr); + + // X-Mozilla-Draft-Info + if (isDraft) + { + nsAutoCString draftInfo; + draftInfo.AppendLiteral("internal/draft; "); + APPEND_BOOL(AttachVCard, "vcard"); + draftInfo.AppendLiteral("; "); + bool hasReturnReceipt = false; + fields->GetReturnReceipt(&hasReturnReceipt); + if (hasReturnReceipt) + { + // slight change compared to 4.x; we used to use receipt= to tell + // whether the draft/template has request for either MDN or DNS or both + // return receipt; since the DNS is out of the picture we now use the + // header type + 1 to tell whether user has requested the return receipt + int32_t headerType = 0; + fields->GetReceiptHeaderType(&headerType); + draftInfo.AppendLiteral("receipt="); + draftInfo.AppendInt(headerType + 1); + } + else + draftInfo.AppendLiteral("receipt=0"); + draftInfo.AppendLiteral("; "); + APPEND_BOOL(DSN, "DSN"); + draftInfo.AppendLiteral("; "); + draftInfo.AppendLiteral("uuencode=0"); + draftInfo.AppendLiteral("; "); + APPEND_BOOL(AttachmentReminder, "attachmentreminder"); + draftInfo.AppendLiteral("; "); + int32_t deliveryFormat; + fields->GetDeliveryFormat(&deliveryFormat); + draftInfo.AppendLiteral("deliveryformat="); + draftInfo.AppendInt(deliveryFormat); + + finalHeaders->SetRawHeader(HEADER_X_MOZILLA_DRAFT_INFO, draftInfo, nullptr); + } + + nsCOMPtr<nsIHttpProtocolHandler> pHTTPHandler = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http", &rv); + if (NS_SUCCEEDED(rv) && pHTTPHandler) + { + nsAutoCString userAgentString; + pHTTPHandler->GetUserAgent(userAgentString); + + if (!userAgentString.IsEmpty()) + finalHeaders->SetUnstructuredHeader("User-Agent", + NS_ConvertUTF8toUTF16(userAgentString)); + } + + finalHeaders->SetUnstructuredHeader("MIME-Version", NS_LITERAL_STRING("1.0")); + + nsAutoCString newsgroups; + finalHeaders->GetRawHeader("Newsgroups", newsgroups); + if (!newsgroups.IsEmpty()) + { + // Since the newsgroup header can contain data in the form of: + // "news://news.mozilla.org/netscape.test,news://news.mozilla.org/netscape.junk" + // we need to turn that into: "netscape.test,netscape.junk" + // (XXX: can it really?) + nsCOMPtr<nsINntpService> nntpService = + do_GetService("@mozilla.org/messenger/nntpservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString newsgroupsHeaderVal; + nsCString newshostHeaderVal; + rv = nntpService->GenerateNewsHeaderValsForPosting(newsgroups, + getter_Copies(newsgroupsHeaderVal), getter_Copies(newshostHeaderVal)); + NS_ENSURE_SUCCESS(rv, rv); + finalHeaders->SetRawHeader("Newsgroups", newsgroupsHeaderVal, nullptr); + + // If we are here, we are NOT going to send this now. (i.e. it is a Draft, + // Send Later file, etc...). Because of that, we need to store what the user + // typed in on the original composition window for use later when rebuilding + // the headers + if (deliver_mode != nsIMsgSend::nsMsgDeliverNow && + deliver_mode != nsIMsgSend::nsMsgSendUnsent) + { + // This is going to be saved for later, that means we should just store + // what the user typed into the "Newsgroup" line in the HEADER_X_MOZILLA_NEWSHOST + // header for later use by "Send Unsent Messages", "Drafts" or "Templates" + finalHeaders->SetRawHeader(HEADER_X_MOZILLA_NEWSHOST, newshostHeaderVal, + nullptr); + } + + // Newsgroups are a recipient... + hasDisclosedRecipient = true; + } + + nsCOMArray<msgIAddressObject> recipients; + finalHeaders->GetAddressingHeader("To", recipients); + hasDisclosedRecipient |= !recipients.IsEmpty(); + finalHeaders->GetAddressingHeader("Cc", recipients); + hasDisclosedRecipient |= !recipients.IsEmpty(); + + // If we don't have disclosed recipient (only Bcc), address the message to + // undisclosed-recipients to prevent problem with some servers + + // If we are saving the message as a draft, don't bother inserting the undisclosed recipients field. We'll take care of that when we + // really send the message. + if (!hasDisclosedRecipient && !isDraft) + { + bool bAddUndisclosedRecipients = true; + prefs->GetBoolPref("mail.compose.add_undisclosed_recipients", &bAddUndisclosedRecipients); + if (bAddUndisclosedRecipients) + { + bool hasBcc = false; + fields->HasHeader("Bcc", &hasBcc); + if (hasBcc) + { + nsCOMPtr<nsIStringBundleService> stringService = + mozilla::services::GetStringBundleService(); + if (stringService) + { + nsCOMPtr<nsIStringBundle> composeStringBundle; + rv = stringService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(composeStringBundle)); + if (NS_SUCCEEDED(rv)) + { + nsString undisclosedRecipients; + rv = composeStringBundle->GetStringFromName(u"undisclosedRecipients", + getter_Copies(undisclosedRecipients)); + if (NS_SUCCEEDED(rv) && !undisclosedRecipients.IsEmpty()) + { + nsCOMPtr<nsIMsgHeaderParser> headerParser( + mozilla::services::GetHeaderParser()); + nsCOMPtr<msgIAddressObject> group; + headerParser->MakeGroupObject(undisclosedRecipients, + nullptr, 0, getter_AddRefs(group)); + recipients.AppendElement(group); + finalHeaders->SetAddressingHeader("To", recipients); + } + } + } + } + } + } + + // We don't want to emit a Bcc header to the output. If we are saving this to + // Drafts/Sent, this is readded later in nsMsgSend.cpp. + finalHeaders->DeleteHeader("bcc"); + + // Skip no or empty priority. + nsAutoCString priority; + rv = fields->GetRawHeader("X-Priority", priority); + if (NS_SUCCEEDED(rv) && !priority.IsEmpty()) + { + nsMsgPriorityValue priorityValue; + + NS_MsgGetPriorityFromString(priority.get(), priorityValue); + + // Skip default priority. + if (priorityValue != nsMsgPriority::Default) { + nsAutoCString priorityName; + nsAutoCString priorityValueString; + + NS_MsgGetPriorityValueString(priorityValue, priorityValueString); + NS_MsgGetUntranslatedPriorityName(priorityValue, priorityName); + + // Output format: [X-Priority: <pValue> (<pName>)] + priorityValueString.AppendLiteral(" ("); + priorityValueString += priorityName; + priorityValueString.AppendLiteral(")"); + finalHeaders->SetRawHeader("X-Priority", priorityValueString, nullptr); + } + } + + nsAutoCString references; + finalHeaders->GetRawHeader("References", references); + if (!references.IsEmpty()) + { + // The References header should be kept under 998 characters: if it's too + // long, trim out the earliest references to make it smaller. + if (references.Length() > 986) + { + int32_t firstRef = references.FindChar('<'); + int32_t secondRef = references.FindChar('<', firstRef + 1); + if (secondRef > 0) + { + nsAutoCString newReferences(StringHead(references, secondRef)); + int32_t bracket = references.FindChar('<', + references.Length() + newReferences.Length() - 986); + if (bracket > 0) + { + newReferences.Append(Substring(references, bracket)); + finalHeaders->SetRawHeader("References", newReferences, nullptr); + } + } + } + // The In-Reply-To header is the last entry in the references header... + int32_t bracket = references.RFind("<"); + if (bracket >= 0) + finalHeaders->SetRawHeader("In-Reply-To", Substring(references, bracket), + nullptr); + } + + return NS_OK; +} + +#undef APPEND_BOOL // X-Mozilla-Draft-Info helper macro + +static void +GenerateGlobalRandomBytes(unsigned char *buf, int32_t len) +{ + // Attempt to generate bytes from system entropy-based RNG. + nsCOMPtr<nsIRandomGenerator> randomGenerator(do_GetService("@mozilla.org/security/random-generator;1")); + MOZ_ASSERT(randomGenerator, "nsIRandomGenerator service not retrievable"); + uint8_t *tempBuffer; + nsresult rv = randomGenerator->GenerateRandomBytes(len, &tempBuffer); + if (NS_SUCCEEDED(rv)) + { + memcpy(buf, tempBuffer, len); + free(tempBuffer); + return; + } + // nsIRandomGenerator failed -- fall back to low entropy PRNG. + static bool firstTime = true; + if (firstTime) + { + // Seed the random-number generator with current time so that + // the numbers will be different every time we run. + srand( (unsigned)PR_Now() ); + firstTime = false; + } + + for( int32_t i = 0; i < len; i++ ) + buf[i] = rand() % 256; +} + +char +*mime_make_separator(const char *prefix) +{ + unsigned char rand_buf[13]; + GenerateGlobalRandomBytes(rand_buf, 12); + + return PR_smprintf("------------%s" + "%02X%02X%02X%02X" + "%02X%02X%02X%02X" + "%02X%02X%02X%02X", + prefix, + rand_buf[0], rand_buf[1], rand_buf[2], rand_buf[3], + rand_buf[4], rand_buf[5], rand_buf[6], rand_buf[7], + rand_buf[8], rand_buf[9], rand_buf[10], rand_buf[11]); +} + +static char * +RFC2231ParmFolding(const char *parmName, const nsCString& charset, + const char *language, const nsString& parmValue); + +static char * +LegacyParmFolding(const nsCString& aCharset, + const nsCString& aFileName, int32_t aParmFolding); + +char * +mime_generate_attachment_headers (const char *type, + const char *type_param, + const char *encoding, + const char *description, + const char *x_mac_type, + const char *x_mac_creator, + const char *real_name, + const char *base_url, + bool /*digest_p*/, + nsMsgAttachmentHandler * /*ma*/, + const char *attachmentCharset, + const char *bodyCharset, + bool bodyIsAsciiOnly, + const char *content_id, + bool aBodyDocument) +{ + NS_ASSERTION (encoding, "null encoding"); + + int32_t parmFolding = 2; // RFC 2231-compliant + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) + prefs->GetIntPref("mail.strictly_mime.parm_folding", &parmFolding); + + /* Let's encode the real name */ + char *encodedRealName = nullptr; + nsCString charset; // actual charset used for MIME encode + nsAutoString realName; + if (real_name) + { + // first try main body's charset to encode the file name, + // then try local file system charset if fails + CopyUTF8toUTF16(nsDependentCString(real_name), realName); + if (bodyCharset && *bodyCharset && + nsMsgI18Ncheck_data_in_charset_range(bodyCharset, realName.get())) + charset.Assign(bodyCharset); + else + { + charset.Assign(nsMsgI18NFileSystemCharset()); + if (!nsMsgI18Ncheck_data_in_charset_range(charset.get(), realName.get())) + charset.Assign("UTF-8"); // set to UTF-8 if fails again + } + + encodedRealName = RFC2231ParmFolding("filename", charset, nullptr, + realName); + // somehow RFC2231ParamFolding failed. fall back to legacy method + if (!encodedRealName || !*encodedRealName) { + PR_FREEIF(encodedRealName); + parmFolding = 0; + // Not RFC 2231 style encoding (it's not standard-compliant) + encodedRealName = + LegacyParmFolding(charset, nsDependentCString(real_name), parmFolding); + } + } + + nsCString buf; // very likely to be longer than 64 characters + buf.Append("Content-Type: "); + buf.Append(type); + if (type_param && *type_param) + { + if (*type_param != ';') + buf.Append("; "); + buf.Append(type_param); + } + + if (mime_type_needs_charset (type)) + { + + char charset_label[65] = ""; // Content-Type: charset + if (attachmentCharset) + { + PL_strncpy(charset_label, attachmentCharset, sizeof(charset_label)-1); + charset_label[sizeof(charset_label)-1] = '\0'; + } + + /* If the characters are all 7bit, arguably it's better to + claim the charset to be US-ASCII. However, it causes + a major 'interoperability problem' with MS OE, which makes it hard + to sell Mozilla/TB to people most of whose correspondents use + MS OE. MS OE turns all non-ASCII characters to question marks + in replies to messages labeled as US-ASCII if users select 'send as is' + with MIME turned on. (with MIME turned off, this happens without + any warning.) To avoid this, we use the label 'US-ASCII' only when + it's explicitly requested by setting the hidden pref. + 'mail.label_ascii_only_mail_as_us_ascii'. (bug 247958) */ + bool labelAsciiAsAscii = false; + if (prefs) + prefs->GetBoolPref("mail.label_ascii_only_mail_as_us_ascii", + &labelAsciiAsAscii); + if (labelAsciiAsAscii && encoding && + !PL_strcasecmp (encoding, "7bit") && bodyIsAsciiOnly) + PL_strcpy (charset_label, "us-ascii"); + + // If charset is multibyte then no charset to be specified (apply base64 instead). + // The list of file types match with PickEncoding() where we put base64 label. + if ( ((attachmentCharset && !nsMsgI18Nmultibyte_charset(attachmentCharset)) || + ((PL_strcasecmp(type, TEXT_HTML) == 0) || + (PL_strcasecmp(type, TEXT_MDL) == 0) || + (PL_strcasecmp(type, TEXT_PLAIN) == 0) || + (PL_strcasecmp(type, TEXT_RICHTEXT) == 0) || + (PL_strcasecmp(type, TEXT_ENRICHED) == 0) || + (PL_strcasecmp(type, TEXT_VCARD) == 0) || + (PL_strcasecmp(type, APPLICATION_DIRECTORY) == 0) || /* text/x-vcard synonym */ + (PL_strcasecmp(type, TEXT_CSS) == 0) || + (PL_strcasecmp(type, TEXT_JSSS) == 0)) || + (PL_strcasecmp(encoding, ENCODING_BASE64) != 0)) && + (*charset_label)) + { + buf.Append("; charset="); + buf.Append(charset_label); + } + } + + // Only do this if we are in the body of a message + if (aBodyDocument) + { + // Add format=flowed as in RFC 2646 if we are using that + if(type && !PL_strcasecmp(type, "text/plain")) + { + bool flowed, delsp, formatted, disallowBreaks; + GetSerialiserFlags(bodyCharset, &flowed, &delsp, &formatted, &disallowBreaks); + if(flowed) + buf.Append("; format=flowed"); + if (delsp) + buf.Append("; delsp=yes"); + // else + // { + // Don't add a markup. Could use + // PUSH_STRING ("; format=fixed"); + // but it is equivalent to nothing at all and we do want + // to save bandwidth. Don't we? + // } + } + } + + if (x_mac_type && *x_mac_type) { + buf.Append("; x-mac-type=\""); + buf.Append(x_mac_type); + buf.Append("\""); + } + + if (x_mac_creator && *x_mac_creator) { + buf.Append("; x-mac-creator=\""); + buf.Append(x_mac_creator); + buf.Append("\""); + } + +#ifdef EMIT_NAME_IN_CONTENT_TYPE + if (encodedRealName && *encodedRealName) { + // Note that we don't need to output the name field if the name encoding is + // RFC 2231. If the MUA knows the RFC 2231, it should know the RFC 2183 too. + if (parmFolding != 2) { + // The underlying JS MIME code will only handle UTF-8 here. + char *nameValue = LegacyParmFolding(NS_LITERAL_CSTRING("UTF-8"), + nsDependentCString(real_name), + parmFolding); + if (!nameValue || !*nameValue) { + PR_FREEIF(nameValue); + nameValue = encodedRealName; + } + buf.Append(";\r\n name=\""); + buf.Append(nameValue); + buf.Append("\""); + if (nameValue != encodedRealName) + PR_FREEIF(nameValue); + } + } +#endif /* EMIT_NAME_IN_CONTENT_TYPE */ + buf.Append(CRLF); + + buf.Append("Content-Transfer-Encoding: "); + buf.Append(encoding); + buf.Append(CRLF); + + if (description && *description) { + char *s = mime_fix_header (description); + if (s) { + buf.Append("Content-Description: "); + buf.Append(s); + buf.Append(CRLF); + PR_Free(s); + } + } + + if ( (content_id) && (*content_id) ) + { + buf.Append("Content-ID: <"); + buf.Append(content_id); + buf.Append(">"); + buf.Append(CRLF); + } + + if (encodedRealName && *encodedRealName) { + char *period = PL_strrchr(encodedRealName, '.'); + int32_t pref_content_disposition = 0; + + if (prefs) { + mozilla::DebugOnly<nsresult> rv = prefs->GetIntPref("mail.content_disposition_type", + &pref_content_disposition); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get mail.content_disposition_type"); + } + + buf.Append("Content-Disposition: "); + + // If this is an attachment which is part of the message body and therefore has a + // Content-ID (e.g, image in HTML msg), then Content-Disposition has to be inline + if (content_id && *content_id) + buf.Append("inline"); + else if (pref_content_disposition == 1) + buf.Append("attachment"); + else + if (pref_content_disposition == 2 && + (!PL_strcasecmp(type, TEXT_PLAIN) || + (period && !PL_strcasecmp(period, ".txt")))) + buf.Append("attachment"); + + /* If this document is an anonymous binary file or a vcard, + then always show it as an attachment, never inline. */ + else + if (!PL_strcasecmp(type, APPLICATION_OCTET_STREAM) || + !PL_strcasecmp(type, TEXT_VCARD) || + !PL_strcasecmp(type, APPLICATION_DIRECTORY)) /* text/x-vcard synonym */ + buf.Append("attachment"); + else + buf.Append("inline"); + + buf.Append(";\r\n "); + buf.Append(encodedRealName); + buf.Append(CRLF); + } + else + if (type && + (!PL_strcasecmp (type, MESSAGE_RFC822) || + !PL_strcasecmp (type, MESSAGE_NEWS))) + buf.Append("Content-Disposition: inline" CRLF); + +#ifdef GENERATE_CONTENT_BASE + /* If this is an HTML document, and we know the URL it originally + came from, write out a Content-Base header. */ + if (type && + (!PL_strcasecmp (type, TEXT_HTML) || + !PL_strcasecmp (type, TEXT_MDL)) && + base_url && *base_url) + { + int32_t col = 0; + const char *s = base_url; + const char *colon = PL_strchr (s, ':'); + bool useContentLocation = false; /* rhp - add this */ + + if (!colon) + goto GIVE_UP_ON_CONTENT_BASE; /* malformed URL? */ + + /* Don't emit a content-base that points to (or into) a news or + mail message. */ + if (!PL_strncasecmp (s, "news:", 5) || + !PL_strncasecmp (s, "snews:", 6) || + !PL_strncasecmp (s, "IMAP:", 5) || + !PL_strncasecmp (s, "file:", 5) || /* rhp: fix targets from included HTML files */ + !PL_strncasecmp (s, "mailbox:", 8)) + goto GIVE_UP_ON_CONTENT_BASE; + + /* rhp - Put in a pref for using Content-Location instead of Content-Base. + This will get tweaked to default to true in 5.0 + */ + if (prefs) + prefs->GetBoolPref("mail.use_content_location_on_send", &useContentLocation); + + if (useContentLocation) + buf.Append("Content-Location: \""); + else + buf.Append("Content-Base: \""); + /* rhp - Pref for Content-Location usage */ + +/* rhp: this is to work with the Content-Location stuff */ +CONTENT_LOC_HACK: + + while (*s != 0 && *s != '#') + { + uint32_t ot=buf.Length(); + char tmp[]="\x00\x00"; + /* URLs must be wrapped at 40 characters or less. */ + if (col >= 38) { + buf.Append(CRLF "\t"); + col = 0; + } + + if (*s == ' ') + buf.Append("%20"); + else if (*s == '\t') + buf.Append("%09"); + else if (*s == '\n') + buf.Append("%0A"); + else if (*s == '\r') + buf.Append("%0D"); + else { + tmp[0]=*s; + buf.Append(tmp); + } + s++; + col += (buf.Length() - ot); + } + buf.Append("\"" CRLF); + + /* rhp: this is to try to get around this fun problem with Content-Location */ + if (!useContentLocation) { + buf.Append("Content-Location: \""); + s = base_url; + col = 0; + useContentLocation = true; + goto CONTENT_LOC_HACK; + } + /* rhp: this is to try to get around this fun problem with Content-Location */ + +GIVE_UP_ON_CONTENT_BASE: + ; + } +#endif /* GENERATE_CONTENT_BASE */ + + /* realloc it smaller... */ + +#ifdef DEBUG_jungshik + printf ("header=%s\n", buf.get()); +#endif + PR_Free(encodedRealName); + return PL_strdup(buf.get()); +} + +static bool isValidHost( const char* host ) +{ + if ( host ) + for (const char *s = host; *s; ++s) + if ( !isalpha(*s) + && !isdigit(*s) + && *s != '-' + && *s != '_' + && *s != '.' + ) + { + host = nullptr; + break; + } + + return nullptr != host; +} + +char * +msg_generate_message_id (nsIMsgIdentity *identity) +{ + const char *host = 0; + + nsCString forcedFQDN; + nsCString from; + nsresult rv = NS_OK; + + rv = identity->GetCharAttribute("FQDN", forcedFQDN); + + if (NS_SUCCEEDED(rv) && !forcedFQDN.IsEmpty()) + host = forcedFQDN.get(); + + if (!isValidHost(host)) + { + nsresult rv = identity->GetEmail(from); + if (NS_SUCCEEDED(rv) && !from.IsEmpty()) + host = strchr(from.get(),'@'); + + // No '@'? Munged address, anti-spam? + // see bug #197203 + if (host) + ++host; + } + + if (!isValidHost(host)) + /* If we couldn't find a valid host name to use, we can't generate a + valid message ID, so bail, and let NNTP and SMTP generate them. */ + return 0; + + // Generate 128-bit UUID for the local part. We use the high-entropy + // GenerateGlobalRandomBytes to make tracking more difficult. + nsID uuid; + GenerateGlobalRandomBytes((unsigned char*) &uuid, sizeof(nsID)); + char uuidString[NSID_LENGTH]; + uuid.ToProvidedString(uuidString); + // Drop first and last characters (curly braces). + uuidString[NSID_LENGTH - 2] = 0; + return PR_smprintf("<%s@%s>", uuidString + 1, host); +} + + +inline static bool is7bitCharset(const nsCString& charset) +{ + // charset name is canonical (no worry about case-sensitivity) + return Substring(charset, 0, 8).EqualsLiteral("ISO-2022-"); +} + +#define PR_MAX_FOLDING_LEN 75 // this is to gurantee the folded line will + // never be greater than 78 = 75 + CRLFLWSP +/*static */ char * +RFC2231ParmFolding(const char *parmName, const nsCString& charset, + const char *language, const nsString& parmValue) +{ + NS_ENSURE_TRUE(parmName && *parmName && !parmValue.IsEmpty(), nullptr); + + bool needEscape; + nsCString dupParm; + + if (!NS_IsAscii(parmValue.get()) || is7bitCharset(charset)) { + needEscape = true; + nsAutoCString nativeParmValue; + ConvertFromUnicode(charset.get(), parmValue, nativeParmValue); + MsgEscapeString(nativeParmValue, nsINetUtil::ESCAPE_ALL, dupParm); + } + else { + needEscape = false; + dupParm.Adopt( + msg_make_filename_qtext(NS_LossyConvertUTF16toASCII(parmValue).get(), + true)); + } + + if (dupParm.IsEmpty()) + return nullptr; + + int32_t parmNameLen = PL_strlen(parmName); + int32_t parmValueLen = dupParm.Length(); + + parmNameLen += 5; // *=__'__'___ or *[0]*=__'__'__ or *[1]*=___ or *[0]="___" + + int32_t languageLen = language ? PL_strlen(language) : 0; + int32_t charsetLen = charset.Length(); + char *foldedParm = nullptr; + + if ((parmValueLen + parmNameLen + charsetLen + languageLen) < + PR_MAX_FOLDING_LEN) + { + foldedParm = PL_strdup(parmName); + if (needEscape) + { + NS_MsgSACat(&foldedParm, "*="); + if (charsetLen) + NS_MsgSACat(&foldedParm, charset.get()); + NS_MsgSACat(&foldedParm, "'"); + if (languageLen) + NS_MsgSACat(&foldedParm, language); + NS_MsgSACat(&foldedParm, "'"); + } + else + NS_MsgSACat(&foldedParm, "=\""); + NS_MsgSACat(&foldedParm, dupParm.get()); + if (!needEscape) + NS_MsgSACat(&foldedParm, "\""); + } + else + { + int curLineLen = 0; + int counter = 0; + char digits[32]; + char *start = dupParm.BeginWriting(); + char *end = NULL; + char tmp = 0; + + while (parmValueLen > 0) + { + curLineLen = 0; + if (counter == 0) { + PR_FREEIF(foldedParm) + foldedParm = PL_strdup(parmName); + } + else { + NS_MsgSACat(&foldedParm, ";\r\n "); + NS_MsgSACat(&foldedParm, parmName); + } + PR_snprintf(digits, sizeof(digits), "*%d", counter); + NS_MsgSACat(&foldedParm, digits); + curLineLen += PL_strlen(digits); + if (needEscape) + { + NS_MsgSACat(&foldedParm, "*="); + if (counter == 0) + { + if (charsetLen) + NS_MsgSACat(&foldedParm, charset.get()); + NS_MsgSACat(&foldedParm, "'"); + if (languageLen) + NS_MsgSACat(&foldedParm, language); + NS_MsgSACat(&foldedParm, "'"); + curLineLen += charsetLen; + curLineLen += languageLen; + } + } + else + { + NS_MsgSACat(&foldedParm, "=\""); + } + counter++; + curLineLen += parmNameLen; + if (parmValueLen <= PR_MAX_FOLDING_LEN - curLineLen) + end = start + parmValueLen; + else + end = start + (PR_MAX_FOLDING_LEN - curLineLen); + + tmp = 0; + if (*end && needEscape) + { + // check to see if we are in the middle of escaped char + if (*end == '%') + { + tmp = '%'; *end = 0; + } + else if (end-1 > start && *(end-1) == '%') + { + end -= 1; tmp = '%'; *end = 0; + } + else if (end-2 > start && *(end-2) == '%') + { + end -= 2; tmp = '%'; *end = 0; + } + else + { + tmp = *end; *end = 0; + } + } + else + { + // XXX should check if we are in the middle of escaped char (RFC 822) + tmp = *end; *end = 0; + } + NS_MsgSACat(&foldedParm, start); + if (!needEscape) + NS_MsgSACat(&foldedParm, "\""); + + parmValueLen -= (end-start); + if (tmp) + *end = tmp; + start = end; + } + } + + return foldedParm; +} + +/*static */ char * +LegacyParmFolding(const nsCString& aCharset, + const nsCString& aFileName, int32_t aParmFolding) +{ + bool usemime = nsMsgMIMEGetConformToStandard(); + char *encodedRealName = + nsMsgI18NEncodeMimePartIIStr(aFileName.get(), false, aCharset.get(), + 0, usemime); + + if (!encodedRealName || !*encodedRealName) { + PR_FREEIF(encodedRealName); + encodedRealName = (char *) PR_Malloc(aFileName.Length() + 1); + if (encodedRealName) + PL_strcpy(encodedRealName, aFileName.get()); + } + + // Now put backslashes before special characters per RFC 822 + char *qtextName = + msg_make_filename_qtext(encodedRealName, aParmFolding == 0); + if (qtextName) { + PR_FREEIF(encodedRealName); + encodedRealName = qtextName; + } + return encodedRealName; +} + +bool +mime_7bit_data_p (const char *string, uint32_t size) +{ + if ((!string) || (!*string)) + return true; + + char *ptr = (char *)string; + for (uint32_t i=0; i<size; i++) + { + if ((unsigned char) ptr[i] > 0x7F) + return false; + } + return true; +} + +/* Strips whitespace, and expands newlines into newline-tab for use in + mail headers. Returns a new string or 0 (if it would have been empty.) + If addr_p is true, the addresses will be parsed and reemitted as + rfc822 mailboxes. + */ +char * +mime_fix_header_1 (const char *string, bool addr_p, bool news_p) +{ + char *new_string; + const char *in; + char *out; + int32_t i, old_size, new_size; + + if (!string || !*string) + return 0; + + if (addr_p) { + return strdup(string); + } + + old_size = PL_strlen (string); + new_size = old_size; + for (i = 0; i < old_size; i++) + if (string[i] == '\r' || string[i] == '\n') + new_size += 2; + + new_string = (char *) PR_Malloc (new_size + 1); + if (! new_string) + return 0; + + in = string; + out = new_string; + + /* strip leading whitespace. */ + while (IS_SPACE (*in)) + in++; + + /* replace CR, LF, or CRLF with CRLF-TAB. */ + while (*in) { + if (*in == '\r' || *in == '\n') { + if (*in == '\r' && in[1] == '\n') + in++; + in++; + *out++ = '\r'; + *out++ = '\n'; + *out++ = '\t'; + } + else + if (news_p && *in == ',') { + *out++ = *in++; + /* skip over all whitespace after a comma. */ + while (IS_SPACE (*in)) + in++; + } + else + *out++ = *in++; + } + *out = 0; + + /* strip trailing whitespace. */ + while (out > in && IS_SPACE (out[-1])) + *out-- = 0; + + /* If we ended up throwing it all away, use 0 instead of "". */ + if (!*new_string) { + PR_Free (new_string); + new_string = 0; + } + + return new_string; +} + +char * +mime_fix_header (const char *string) +{ + return mime_fix_header_1 (string, false, false); +} + +char * +mime_fix_addr_header (const char *string) +{ + return mime_fix_header_1 (string, true, false); +} + +char * +mime_fix_news_header (const char *string) +{ + return mime_fix_header_1 (string, false, true); +} + +bool +mime_type_requires_b64_p (const char *type) +{ + if (!type || !PL_strcasecmp (type, UNKNOWN_CONTENT_TYPE)) + /* Unknown types don't necessarily require encoding. (Note that + "unknown" and "application/octet-stream" aren't the same.) */ + return false; + + else if (!PL_strncasecmp (type, "image/", 6) || + !PL_strncasecmp (type, "audio/", 6) || + !PL_strncasecmp (type, "video/", 6) || + !PL_strncasecmp (type, "application/", 12)) + { + /* The following types are application/ or image/ types that are actually + known to contain textual data (meaning line-based, not binary, where + CRLF conversion is desired rather than disasterous.) So, if the type + is any of these, it does not *require* base64, and if we do need to + encode it for other reasons, we'll probably use quoted-printable. + But, if it's not one of these types, then we assume that any subtypes + of the non-"text/" types are binary data, where CRLF conversion would + corrupt it, so we use base64 right off the bat. + + The reason it's desirable to ship these as text instead of just using + base64 all the time is mainly to preserve the readability of them for + non-MIME users: if I mail a /bin/sh script to someone, it might not + need to be encoded at all, so we should leave it readable if we can. + + This list of types was derived from the comp.mail.mime FAQ, section + 10.2.2, "List of known unregistered MIME types" on 2-Feb-96. + */ + static const char *app_and_image_types_which_are_really_text[] = { + "application/mac-binhex40", /* APPLICATION_BINHEX */ + "application/pgp", /* APPLICATION_PGP */ + "application/pgp-keys", + "application/x-pgp-message", /* APPLICATION_PGP2 */ + "application/postscript", /* APPLICATION_POSTSCRIPT */ + "application/x-uuencode", /* APPLICATION_UUENCODE */ + "application/x-uue", /* APPLICATION_UUENCODE2 */ + "application/uue", /* APPLICATION_UUENCODE4 */ + "application/uuencode", /* APPLICATION_UUENCODE3 */ + "application/sgml", + "application/x-csh", + "application/javascript", + "application/ecmascript", + "application/x-javascript", + "application/x-latex", + "application/x-macbinhex40", + "application/x-ns-proxy-autoconfig", + "application/x-www-form-urlencoded", + "application/x-perl", + "application/x-sh", + "application/x-shar", + "application/x-tcl", + "application/x-tex", + "application/x-texinfo", + "application/x-troff", + "application/x-troff-man", + "application/x-troff-me", + "application/x-troff-ms", + "application/x-troff-ms", + "application/x-wais-source", + "image/x-bitmap", + "image/x-pbm", + "image/x-pgm", + "image/x-portable-anymap", + "image/x-portable-bitmap", + "image/x-portable-graymap", + "image/x-portable-pixmap", /* IMAGE_PPM */ + "image/x-ppm", + "image/x-xbitmap", /* IMAGE_XBM */ + "image/x-xbm", /* IMAGE_XBM2 */ + "image/xbm", /* IMAGE_XBM3 */ + "image/x-xpixmap", + "image/x-xpm", + 0 }; + const char **s; + for (s = app_and_image_types_which_are_really_text; *s; s++) + if (!PL_strcasecmp (type, *s)) + return false; + + /* All others must be assumed to be binary formats, and need Base64. */ + return true; + } + + else + return false; +} + +// +// Some types should have a "charset=" parameter, and some shouldn't. +// This is what decides. +// +bool +mime_type_needs_charset (const char *type) +{ + /* Only text types should have charset. */ + if (!type || !*type) + return false; + else + if (!PL_strncasecmp (type, "text", 4)) + return true; + else + return false; +} + +/* Given a string, convert it to 'qtext' (quoted text) for RFC822 header purposes. */ +char * +msg_make_filename_qtext(const char *srcText, bool stripCRLFs) +{ + /* newString can be at most twice the original string (every char quoted). */ + char *newString = (char *) PR_Malloc(PL_strlen(srcText)*2 + 1); + if (!newString) return NULL; + + const char *s = srcText; + const char *end = srcText + PL_strlen(srcText); + char *d = newString; + + while(*s) + { + /* Put backslashes in front of existing backslashes, or double quote + characters. + If stripCRLFs is true, don't write out CRs or LFs. Otherwise, + write out a backslash followed by the CR but not + linear-white-space. + We might already have quoted pair of "\ " or "\\t" skip it. + */ + if (*s == '\\' || *s == '"' || + (!stripCRLFs && + (*s == '\r' && (s[1] != '\n' || + (s[1] == '\n' && (s+2) < end && !IS_SPACE(s[2])))))) + *d++ = '\\'; + + if (stripCRLFs && *s == '\r' && s[1] == '\n' && (s+2) < end && IS_SPACE(s[2])) + { + s += 3; // skip CRLFLWSP + } + else + { + *d++ = *s++; + } + } + *d = 0; + + return newString; +} + +/* Rip apart the URL and extract a reasonable value for the `real_name' slot. + */ +void +msg_pick_real_name (nsMsgAttachmentHandler *attachment, const char16_t *proposedName, const char *charset) +{ + const char *s, *s2; + + if (!attachment->m_realName.IsEmpty()) + return; + + if (proposedName && *proposedName) + { + attachment->m_realName.Adopt(ToNewUTF8String(nsAutoString(proposedName))); + } + else //Let's extract the name from the URL + { + nsCString url; + nsresult rv = attachment->mURL->GetSpec(url); + if (NS_FAILED(rv)) + return; + + s = url.get(); + s2 = PL_strchr (s, ':'); + if (s2) + s = s2 + 1; + /* If we know the URL doesn't have a sensible file name in it, + don't bother emitting a content-disposition. */ + if (StringBeginsWith (url, NS_LITERAL_CSTRING("news:"), nsCaseInsensitiveCStringComparator()) || + StringBeginsWith (url, NS_LITERAL_CSTRING("snews:"), nsCaseInsensitiveCStringComparator()) || + StringBeginsWith (url, NS_LITERAL_CSTRING("IMAP:"), nsCaseInsensitiveCStringComparator()) || + StringBeginsWith (url, NS_LITERAL_CSTRING("mailbox:"), nsCaseInsensitiveCStringComparator())) + return; + + if (StringBeginsWith(url, NS_LITERAL_CSTRING("data:"), + nsCaseInsensitiveCStringComparator())) + { + int32_t endNonData = url.FindChar(','); + if (endNonData == -1) + return; + nsCString nonDataPart(Substring(url, 5, endNonData - 5)); + int32_t filenamePos = nonDataPart.Find("filename="); + if (filenamePos != -1) + { + filenamePos += 9; + int32_t endFilename = nonDataPart.FindChar(';', filenamePos); + if (endFilename == -1) + endFilename = endNonData; + attachment->m_realName = Substring(nonDataPart, filenamePos, + endFilename - filenamePos); + } + else + { + // no filename; need to construct one based on the content type. + nsCOMPtr<nsIMIMEService> mimeService(do_GetService(NS_MIMESERVICE_CONTRACTID)); + if (!mimeService) + return; + nsCOMPtr<nsIMIMEInfo> mimeInfo; + nsCString mediaType(Substring(nonDataPart, 0, nonDataPart.FindChar(';'))); + mimeService->GetFromTypeAndExtension(mediaType, EmptyCString(), getter_AddRefs(mimeInfo)); + if (!mimeInfo) + return; + nsCString filename; + nsCString extension; + mimeInfo->GetPrimaryExtension(extension); + unsigned char filePrefixBytes[8]; + GenerateGlobalRandomBytes(filePrefixBytes, 8); + // Create a filename prefix with 16 lowercase letters, + // representing 8 bytes. + for (int32_t i = 0; i < 8; i++) + { + // A pair of letters, each any of (a-p). + filename.Append((filePrefixBytes[i] & 0xF) + 'a'); + filename.Append((filePrefixBytes[i] >> 4) + 'a'); + } + filename.Append('.'); + filename.Append(extension); + attachment->m_realName = filename; + } + } + else + { + /* Take the part of the file name after the last / or \ */ + s2 = PL_strrchr (s, '/'); + if (s2) s = s2+1; + s2 = PL_strrchr (s, '\\'); + + if (s2) s = s2+1; + /* Copy it into the attachment struct. */ + attachment->m_realName = s; + int32_t charPos = attachment->m_realName.FindChar('?'); + if (charPos != -1) + attachment->m_realName.SetLength(charPos); + /* Now trim off any named anchors or search data. */ + charPos = attachment->m_realName.FindChar('#'); + if (charPos != -1) + attachment->m_realName.SetLength(charPos); + } + /* Now lose the %XX crap. */ + nsCString unescaped_real_name; + MsgUnescapeString(attachment->m_realName, 0, unescaped_real_name); + attachment->m_realName = unescaped_real_name; + } + + /* Now a special case for attaching uuencoded files... + + If we attach a file "foo.txt.uu", we will send it out with + Content-Type: text/plain; Content-Transfer-Encoding: x-uuencode. + When saving such a file, a mail reader will generally decode it first + (thus removing the uuencoding.) So, let's make life a little easier by + removing the indication of uuencoding from the file name itself. (This + will presumably make the file name in the Content-Disposition header be + the same as the file name in the "begin" line of the uuencoded data.) + + However, since there are mailers out there (including earlier versions of + Mozilla) that will use "foo.txt.uu" as the file name, we still need to + cope with that; the code which copes with that is in the MIME parser, in + libmime/mimei.c. + */ + if (attachment->m_already_encoded_p && !attachment->m_encoding.IsEmpty()) + { + /* #### TOTAL KLUDGE. + I'd like to ask the mime.types file, "what extensions correspond + to obj->encoding (which happens to be "x-uuencode") but doing that + in a non-sphagetti way would require brain surgery. So, since + currently uuencode is the only content-transfer-encoding which we + understand which traditionally has an extension, we just special- + case it here! + + Note that it's special-cased in a similar way in libmime/mimei.c. + */ + if (attachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE) || + attachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE2) || + attachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE3) || + attachment->m_encoding.LowerCaseEqualsLiteral(ENCODING_UUENCODE4)) + { + if (StringEndsWith(attachment->m_realName, NS_LITERAL_CSTRING(".uu"))) + attachment->m_realName.Cut(attachment->m_realName.Length() - 3, 3); + else if (StringEndsWith(attachment->m_realName, NS_LITERAL_CSTRING(".uue"))) + attachment->m_realName.Cut(attachment->m_realName.Length() - 4, 4); + } + } +} + +// Utility to create a nsIURI object... +nsresult +nsMsgNewURL(nsIURI** aInstancePtrResult, const char * aSpec) +{ + nsresult rv = NS_OK; + if (nullptr == aInstancePtrResult) + return NS_ERROR_NULL_POINTER; + nsCOMPtr<nsIIOService> pNetService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(pNetService, NS_ERROR_UNEXPECTED); + if (PL_strstr(aSpec, "://") == nullptr && strncmp(aSpec, "data:", 5)) + { + //XXXjag Temporary fix for bug 139362 until the real problem(bug 70083) get fixed + nsAutoCString uri(NS_LITERAL_CSTRING("http://")); + uri.Append(aSpec); + rv = pNetService->NewURI(uri, nullptr, nullptr, aInstancePtrResult); + } + else + rv = pNetService->NewURI(nsDependentCString(aSpec), nullptr, nullptr, aInstancePtrResult); + return rv; +} + +bool +nsMsgIsLocalFile(const char *url) +{ + /* + A url is considered as a local file if it's start with file:// + But on Window, we need to filter UNC file url because there + are not really local file. Those start with file://// + */ + if (PL_strncasecmp(url, "file://", 7) == 0) + { +#ifdef XP_WIN + if (PL_strncasecmp(url, "file:////", 9) == 0) + return false; +#endif + return true; + } + else + return false; +} + +char +*nsMsgGetLocalFileFromURL(const char *url) +{ + char * finalPath; + NS_ASSERTION(PL_strncasecmp(url, "file://", 7) == 0, "invalid url"); + finalPath = (char*)PR_Malloc(strlen(url)); + if (finalPath == NULL) + return NULL; + strcpy(finalPath, url+6+1); + return finalPath; +} + +char * +nsMsgParseURLHost(const char *url) +{ + nsIURI *workURI = nullptr; + nsresult rv; + + rv = nsMsgNewURL(&workURI, url); + if (NS_FAILED(rv) || !workURI) + return nullptr; + + nsAutoCString host; + rv = workURI->GetHost(host); + NS_IF_RELEASE(workURI); + if (NS_FAILED(rv)) + return nullptr; + + return ToNewCString(host); +} + +char * +GenerateFileNameFromURI(nsIURI *aURL) +{ + nsresult rv; + nsCString file; + nsCString spec; + char *returnString; + char *cp = nullptr; + char *cp1 = nullptr; + + rv = aURL->GetPath(file); + if ( NS_SUCCEEDED(rv) && !file.IsEmpty()) + { + char *newFile = ToNewCString(file); + if (!newFile) + return nullptr; + + // strip '/' + cp = PL_strrchr(newFile, '/'); + if (cp) + ++cp; + else + cp = newFile; + + if (*cp) + { + if ((cp1 = PL_strchr(cp, '/'))) *cp1 = 0; + if ((cp1 = PL_strchr(cp, '?'))) *cp1 = 0; + if ((cp1 = PL_strchr(cp, '>'))) *cp1 = 0; + if (*cp != '\0') + { + returnString = PL_strdup(cp); + PR_FREEIF(newFile); + return returnString; + } + } + else + return nullptr; + } + + cp = nullptr; + cp1 = nullptr; + + + rv = aURL->GetSpec(spec); + if ( NS_SUCCEEDED(rv) && !spec.IsEmpty()) + { + char *newSpec = ToNewCString(spec); + if (!newSpec) + return nullptr; + + char *cp2 = NULL, *cp3=NULL ; + + // strip '"' + cp2 = newSpec; + while (*cp2 == '"') + cp2++; + if ((cp3 = PL_strchr(cp2, '"'))) + *cp3 = 0; + + char *hostStr = nsMsgParseURLHost(cp2); + if (!hostStr) + hostStr = PL_strdup(cp2); + + bool isHTTP = false; + if (NS_SUCCEEDED(aURL->SchemeIs("http", &isHTTP)) && isHTTP) + { + returnString = PR_smprintf("%s.html", hostStr); + PR_FREEIF(hostStr); + } + else + returnString = hostStr; + + PR_FREEIF(newSpec); + return returnString; + } + + return nullptr; +} + +// +// This routine will generate a content id for use in a mail part. +// It will take the part number passed in as well as the email +// address. If the email address is null or invalid, we will simply +// use netscape.com for the interesting part. The content ID's will +// look like the following: +// +// Content-ID: <part1.36DF1DCE.73B5A330@netscape.com> +// +char * +mime_gen_content_id(uint32_t aPartNum, const char *aEmailAddress) +{ + int32_t randLen = 5; + unsigned char rand_buf1[5]; + unsigned char rand_buf2[5]; + const char *domain = nullptr; + const char *defaultDomain = "@netscape.com"; + + memset(rand_buf1, 0, randLen-1); + memset(rand_buf2, 0, randLen-1); + + GenerateGlobalRandomBytes(rand_buf1, randLen); + GenerateGlobalRandomBytes(rand_buf2, randLen); + + // Find the @domain.com string... + if (aEmailAddress && *aEmailAddress) + domain = const_cast<const char*>(PL_strchr(aEmailAddress, '@')); + + if (!domain) + domain = defaultDomain; + + char *retVal = PR_smprintf("part%d." + "%02X%02X%02X%02X" + "." + "%02X%02X%02X%02X" + "%s", + aPartNum, + rand_buf1[0], rand_buf1[1], rand_buf1[2], rand_buf1[3], + rand_buf2[0], rand_buf2[1], rand_buf2[2], rand_buf2[3], + domain); + + return retVal; +} + +void +GetFolderURIFromUserPrefs(nsMsgDeliverMode aMode, nsIMsgIdentity* identity, nsCString& uri) +{ + nsresult rv; + uri.Truncate(); + + // QueueForLater (Outbox) + if (aMode == nsIMsgSend::nsMsgQueueForLater || + aMode == nsIMsgSend::nsMsgDeliverBackground) + { + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + rv = prefs->GetCharPref("mail.default_sendlater_uri", getter_Copies(uri)); + if (NS_FAILED(rv) || uri.IsEmpty()) + uri.AssignLiteral(ANY_SERVER); + else + { + // check if uri is unescaped, and if so, escape it and reset the pef. + if (uri.FindChar(' ') != kNotFound) + { + MsgReplaceSubstring(uri, " ", "%20"); + prefs->SetCharPref("mail.default_sendlater_uri", uri.get()); + } + } + return; + } + + if (!identity) + return; + + if (aMode == nsIMsgSend::nsMsgSaveAsDraft) // SaveAsDraft (Drafts) + rv = identity->GetDraftFolder(uri); + else if (aMode == nsIMsgSend::nsMsgSaveAsTemplate) // SaveAsTemplate (Templates) + rv = identity->GetStationeryFolder(uri); + else + { + bool doFcc = false; + rv = identity->GetDoFcc(&doFcc); + if (doFcc) + rv = identity->GetFccFolder(uri); + } + return; +} + +/** + * Check if we should use format=flowed (RFC 2646) for a mail. + * We will use format=flowed unless the preference tells us not to do so. + * In this function we set all the serialiser flags. + * 'formatted' is always 'true'. + */ +void GetSerialiserFlags(const char* charset, bool* flowed, bool* delsp, bool* formatted, bool* disallowBreaks) +{ + *flowed = false; + *delsp = false; + *formatted = true; + *disallowBreaks = true; + + // Set format=flowed as in RFC 2646 according to the preference. + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + prefs->GetBoolPref("mailnews.send_plaintext_flowed", flowed); + } + + // We could test statefulCharset(charset) here, but since ISO-2022-JP is the + // only one left we support, we might as well check for it directly. + if (PL_strcasecmp(charset, "ISO-2022-JP") == 0) { + // Make sure we honour RFC 1468. For encoding in ISO-2022-JP we need to + // send short lines to allow 7bit transfer encoding. + *disallowBreaks = false; + if (*flowed) + *delsp = true; + } +} diff --git a/mailnews/compose/src/nsMsgCompUtils.h b/mailnews/compose/src/nsMsgCompUtils.h new file mode 100644 index 000000000..13d6ddd83 --- /dev/null +++ b/mailnews/compose/src/nsMsgCompUtils.h @@ -0,0 +1,143 @@ +/* -*- 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 _nsMsgCompUtils_H_ +#define _nsMsgCompUtils_H_ + +#include "nscore.h" +#include "nsMsgSend.h" +#include "nsMsgCompFields.h" +#include "nsIMsgSend.h" +#include "nsIMsgCompUtils.h" + +class nsIPrompt; + +#define ANY_SERVER "anyfolder://" + +// these are msg hdr property names for storing the original +// msg uri's and disposition(replied/forwarded) when queuing +// messages to send later. +#define ORIG_URI_PROPERTY "origURIs" +#define QUEUED_DISPOSITION_PROPERTY "queuedDisposition" + +class nsMsgCompUtils : public nsIMsgCompUtils +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGCOMPUTILS + + nsMsgCompUtils(); + +private: + virtual ~nsMsgCompUtils(); +}; + +PR_BEGIN_EXTERN_C + +// +// Create a file spec or file name using the name passed +// in as a template +// +nsresult nsMsgCreateTempFile(const char *tFileName, nsIFile **tFile); +char *nsMsgCreateTempFileName(const char *tFileName); + + +// +// Various utilities for building parts of MIME encoded +// messages during message composition +// + +nsresult mime_sanity_check_fields_recipients ( + const char *to, + const char *cc, + const char *bcc, + const char *newsgroups); + +nsresult mime_sanity_check_fields ( + const char *from, + const char *reply_to, + const char *to, + const char *cc, + const char *bcc, + const char *fcc, + const char *newsgroups, + const char *followup_to, + const char * /*subject*/, + const char * /*references*/, + const char * /*organization*/, + const char * /*other_random_headers*/); + +nsresult mime_generate_headers(nsIMsgCompFields *fields, + nsMsgDeliverMode deliver_mode, + msgIWritableStructuredHeaders *headers); + +char *mime_make_separator(const char *prefix); +char *mime_gen_content_id(uint32_t aPartNum, const char *aEmailAddress); + +char *mime_generate_attachment_headers ( + const char *type, + const char *type_param, + const char *encoding, + const char *description, + const char *x_mac_type, + const char *x_mac_creator, + const char *real_name, + const char *base_url, + bool digest_p, + nsMsgAttachmentHandler *ma, + const char *attachmentCharset, // charset of the attachment (can be null) + const char *bodyCharset, // charset of the main body + bool bodyIsAsciiOnly, + const char *content_id, + bool aBodyDocument); + +char *msg_generate_message_id (nsIMsgIdentity*); + +bool mime_7bit_data_p (const char *string, uint32_t size); + +char *mime_fix_header_1 (const char *string, bool addr_p, bool news_p); +char *mime_fix_header (const char *string); +char *mime_fix_addr_header (const char *string); +char *mime_fix_news_header (const char *string); + +bool mime_type_requires_b64_p (const char *type); +bool mime_type_needs_charset (const char *type); + +char *msg_make_filename_qtext(const char *srcText, bool stripCRLFs); + +// Rip apart the URL and extract a reasonable value for the `real_name' slot. +void msg_pick_real_name (nsMsgAttachmentHandler *attachment, const char16_t *proposedName, const char *charset); + +// +// Informational calls... +// +void nsMsgMIMESetConformToStandard (bool conform_p); +bool nsMsgMIMEGetConformToStandard (void); + +// +// network service type calls... +// +nsresult nsMsgNewURL(nsIURI** aInstancePtrResult, const char * aSpec); +bool nsMsgIsLocalFile(const char *url); +char *nsMsgGetLocalFileFromURL(const char *url); + +char *nsMsgParseURLHost(const char *url); + +char *GenerateFileNameFromURI(nsIURI *aURL); + +// +// Folder calls... +// +void GetFolderURIFromUserPrefs(nsMsgDeliverMode aMode, nsIMsgIdentity *identity, nsCString& uri); + +// Check if we should use format=flowed +void GetSerialiserFlags(const char *charset, bool *flowed, bool *delsp, bool *formatted, bool *disallowBreaks); + + +PR_END_EXTERN_C + + +#endif /* _nsMsgCompUtils_H_ */ + diff --git a/mailnews/compose/src/nsMsgCompose.cpp b/mailnews/compose/src/nsMsgCompose.cpp new file mode 100644 index 000000000..58340bffa --- /dev/null +++ b/mailnews/compose/src/nsMsgCompose.cpp @@ -0,0 +1,6052 @@ +/* -*- 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 "nsMsgCompose.h" +#include "nsIDOMDocument.h" +#include "nsIDOMNode.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMText.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsIDOMHTMLLinkElement.h" +#include "nsIDOMHTMLAnchorElement.h" +#include "nsPIDOMWindow.h" +#include "mozIDOMWindow.h" +#include "nsISelectionController.h" +#include "nsMsgI18N.h" +#include "nsMsgCompCID.h" +#include "nsMsgQuote.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIDocumentEncoder.h" // for editor output flags +#include "nsMsgCompUtils.h" +#include "nsComposeStrings.h" +#include "nsIMsgSend.h" +#include "nsMailHeaders.h" +#include "nsMsgPrompts.h" +#include "nsMimeTypes.h" +#include "nsICharsetConverterManager.h" +#include "nsTextFormatter.h" +#include "nsIPlaintextEditor.h" +#include "nsIHTMLEditor.h" +#include "nsIEditorMailSupport.h" +#include "plstr.h" +#include "prmem.h" +#include "nsIDocShell.h" +#include "nsIRDFService.h" +#include "nsRDFCID.h" +#include "nsAbBaseCID.h" +#include "nsIAbMDBDirectory.h" +#include "nsCExternalHandlerService.h" +#include "nsIMIMEService.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIWindowMediator.h" +#include "nsIURL.h" +#include "nsIMsgMailSession.h" +#include "nsMsgBaseCID.h" +#include "nsMsgMimeCID.h" +#include "nsDateTimeFormatCID.h" +#include "nsIDateTimeFormat.h" +#include "nsILocaleService.h" +#include "nsILocale.h" +#include "nsIMsgComposeService.h" +#include "nsIMsgComposeProgressParams.h" +#include "nsMsgUtils.h" +#include "nsIMsgImapMailFolder.h" +#include "nsImapCore.h" +#include "nsUnicharUtils.h" +#include "nsNetUtil.h" +#include "nsIContentViewer.h" +#include "nsIMsgMdnGenerator.h" +#include "plbase64.h" +#include "nsUConvCID.h" +#include "nsIUnicodeNormalizer.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgAttachment.h" +#include "nsIMsgProgress.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgDatabase.h" +#include "nsStringStream.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "nsIMsgWindow.h" +#include "nsITextToSubURI.h" +#include "nsIAbManager.h" +#include "nsCRT.h" +#include "mozilla/Services.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "mozilla/Preferences.h" +#include "nsStreamConverter.h" +#include "nsISelection.h" +#include "nsJSEnvironment.h" +#include "nsIObserverService.h" +#include "nsIProtocolHandler.h" +#include "nsContentUtils.h" +#include "nsIFileURL.h" + +using namespace mozilla; +using namespace mozilla::mailnews; + +static nsresult GetReplyHeaderInfo(int32_t* reply_header_type, + nsString& reply_header_locale, + nsString& reply_header_authorwrote, + nsString& reply_header_ondateauthorwrote, + nsString& reply_header_authorwroteondate, + nsString& reply_header_originalmessage) +{ + nsresult rv; + *reply_header_type = 0; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // If fetching any of the preferences fails, + // we return early with header_type = 0 meaning "no header". + rv = NS_GetUnicharPreferenceWithDefault(prefBranch, "mailnews.reply_header_locale", EmptyString(), reply_header_locale); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetLocalizedUnicharPreference(prefBranch, "mailnews.reply_header_authorwrotesingle", + reply_header_authorwrote); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetLocalizedUnicharPreference(prefBranch, "mailnews.reply_header_ondateauthorwrote", + reply_header_ondateauthorwrote); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetLocalizedUnicharPreference(prefBranch, "mailnews.reply_header_authorwroteondate", + reply_header_authorwroteondate); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_GetLocalizedUnicharPreference(prefBranch, "mailnews.reply_header_originalmessage", + reply_header_originalmessage); + NS_ENSURE_SUCCESS(rv, rv); + + return prefBranch->GetIntPref("mailnews.reply_header_type", reply_header_type); +} + +static void TranslateLineEnding(nsString& data) +{ + char16_t* rPtr; //Read pointer + char16_t* wPtr; //Write pointer + char16_t* sPtr; //Start data pointer + char16_t* ePtr; //End data pointer + + rPtr = wPtr = sPtr = data.BeginWriting(); + ePtr = rPtr + data.Length(); + + while (rPtr < ePtr) + { + if (*rPtr == nsCRT::CR) { + *wPtr = nsCRT::LF; + if (rPtr + 1 < ePtr && *(rPtr + 1) == nsCRT::LF) + rPtr ++; + } + else + *wPtr = *rPtr; + + rPtr ++; + wPtr ++; + } + + data.SetLength(wPtr - sPtr); +} + +static void GetTopmostMsgWindowCharacterSet(nsCString& charset, bool* charsetOverride) +{ + // HACK: if we are replying to a message and that message used a charset over ride + // (as specified in the top most window (assuming the reply originated from that window) + // then use that over ride charset instead of the charset specified in the message + nsCOMPtr <nsIMsgMailSession> mailSession (do_GetService(NS_MSGMAILSESSION_CONTRACTID)); + if (mailSession) + { + nsCOMPtr<nsIMsgWindow> msgWindow; + mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + { + msgWindow->GetMailCharacterSet(charset); + msgWindow->GetCharsetOverride(charsetOverride); + } + } +} + +nsMsgCompose::nsMsgCompose() +{ + + mQuotingToFollow = false; + mInsertingQuotedContent = false; + mWhatHolder = 1; + m_window = nullptr; + m_editor = nullptr; + mQuoteStreamListener=nullptr; + mCharsetOverride = false; + mAnswerDefaultCharset = false; + mDeleteDraft = false; + m_compFields = nullptr; //m_compFields will be set during nsMsgCompose::Initialize + mType = nsIMsgCompType::New; + + // For TagConvertible + // Read and cache pref + mConvertStructs = false; + nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefBranch) + prefBranch->GetBoolPref("converter.html2txt.structs", &mConvertStructs); + + m_composeHTML = false; +} + + +nsMsgCompose::~nsMsgCompose() +{ + NS_IF_RELEASE(m_compFields); + NS_IF_RELEASE(mQuoteStreamListener); +} + +/* the following macro actually implement addref, release and query interface for our component. */ +NS_IMPL_ISUPPORTS(nsMsgCompose, nsIMsgCompose, nsIMsgSendListener, + nsISupportsWeakReference) + +// +// Once we are here, convert the data which we know to be UTF-8 to UTF-16 +// for insertion into the editor +// +nsresult +GetChildOffset(nsIDOMNode *aChild, nsIDOMNode *aParent, int32_t &aOffset) +{ + NS_ASSERTION((aChild && aParent), "bad args"); + nsresult result = NS_ERROR_NULL_POINTER; + if (aChild && aParent) + { + nsCOMPtr<nsIDOMNodeList> childNodes; + result = aParent->GetChildNodes(getter_AddRefs(childNodes)); + if ((NS_SUCCEEDED(result)) && (childNodes)) + { + int32_t i=0; + for ( ; NS_SUCCEEDED(result); i++) + { + nsCOMPtr<nsIDOMNode> childNode; + result = childNodes->Item(i, getter_AddRefs(childNode)); + if ((NS_SUCCEEDED(result)) && (childNode)) + { + if (childNode.get()==aChild) + { + aOffset = i; + break; + } + } + else if (!childNode) + result = NS_ERROR_NULL_POINTER; + } + } + else if (!childNodes) + result = NS_ERROR_NULL_POINTER; + } + return result; +} + +nsresult +GetNodeLocation(nsIDOMNode *inChild, nsCOMPtr<nsIDOMNode> *outParent, int32_t *outOffset) +{ + NS_ASSERTION((outParent && outOffset), "bad args"); + nsresult result = NS_ERROR_NULL_POINTER; + if (inChild && outParent && outOffset) + { + result = inChild->GetParentNode(getter_AddRefs(*outParent)); + if ( (NS_SUCCEEDED(result)) && (*outParent) ) + { + result = GetChildOffset(inChild, *outParent, *outOffset); + } + } + + return result; +} + +bool nsMsgCompose::IsEmbeddedObjectSafe(const char * originalScheme, + const char * originalHost, + const char * originalPath, + nsIDOMNode * object) +{ + nsresult rv; + + nsCOMPtr<nsIDOMHTMLImageElement> image; + nsCOMPtr<nsIDOMHTMLLinkElement> link; + nsCOMPtr<nsIDOMHTMLAnchorElement> anchor; + nsAutoString objURL; + + if (!object || !originalScheme || !originalPath) //having a null host is ok... + return false; + + if ((image = do_QueryInterface(object))) + { + if (NS_FAILED(image->GetSrc(objURL))) + return false; + } + else if ((link = do_QueryInterface(object))) + { + if (NS_FAILED(link->GetHref(objURL))) + return false; + } + else if ((anchor = do_QueryInterface(object))) + { + if (NS_FAILED(anchor->GetHref(objURL))) + return false; + } + else + return false; + + if (!objURL.IsEmpty()) + { + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), objURL); + if (NS_SUCCEEDED(rv) && uri) + { + nsAutoCString scheme; + rv = uri->GetScheme(scheme); + if (NS_SUCCEEDED(rv) && scheme.Equals(originalScheme, nsCaseInsensitiveCStringComparator())) + { + nsAutoCString host; + rv = uri->GetAsciiHost(host); + // mailbox url don't have a host therefore don't be too strict. + if (NS_SUCCEEDED(rv) && (host.IsEmpty() || originalHost || host.Equals(originalHost, nsCaseInsensitiveCStringComparator()))) + { + nsAutoCString path; + rv = uri->GetPath(path); + if (NS_SUCCEEDED(rv)) + { + const char * query = strrchr(path.get(), '?'); + if (query && PL_strncasecmp(path.get(), originalPath, query - path.get()) == 0) + return true; //This object is a part of the original message, we can send it safely. + } + } + } + } + } + + return false; +} + +/* Reset the uri's of embedded objects because we've saved the draft message, and the + original message doesn't exist anymore. + */ +nsresult nsMsgCompose::ResetUrisForEmbeddedObjects() +{ + nsCOMPtr<nsIArray> aNodeList; + uint32_t numNodes; + uint32_t i; + + nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(m_editor)); + if (!mailEditor) + return NS_ERROR_FAILURE; + + nsresult rv = mailEditor->GetEmbeddedObjects(getter_AddRefs(aNodeList)); + if (NS_FAILED(rv) || !aNodeList) + return NS_ERROR_FAILURE; + + if (NS_FAILED(aNodeList->GetLength(&numNodes))) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIDOMNode> node; + nsCString curDraftIdURL; + + rv = m_compFields->GetDraftId(getter_Copies(curDraftIdURL)); + + // Skip if no draft id (probably a new draft msg). + if (NS_SUCCEEDED(rv) && mMsgSend && !curDraftIdURL.IsEmpty()) + { + nsCOMPtr <nsIMsgDBHdr> msgDBHdr; + rv = GetMsgDBHdrFromURI(curDraftIdURL.get(), getter_AddRefs(msgDBHdr)); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg header DB interface pointer."); + if (NS_SUCCEEDED(rv) && msgDBHdr) + { + // build up the old and new ?number= parts. This code assumes it is + // called *before* RemoveCurrentDraftMessage, so that curDraftIdURL + // is the previous draft. + // This code works for both imap and local messages. + nsMsgKey newMsgKey; + nsCString folderUri; + nsCString baseMsgUri; + mMsgSend->GetMessageKey(&newMsgKey); + mMsgSend->GetFolderUri(folderUri); + nsCOMPtr<nsIMsgFolder> folder; + rv = GetExistingFolder(folderUri, getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + folder->GetBaseMessageURI(baseMsgUri); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDOMElement> domElement; + for (i = 0; i < numNodes; i ++) + { + domElement = do_QueryElementAt(aNodeList, i); + if (!domElement) + continue; + + nsCOMPtr<nsIDOMHTMLImageElement> image = do_QueryInterface(domElement); + if (!image) + continue; + nsCString partNum; + mMsgSend->GetPartForDomIndex(i, partNum); + // do we care about anything besides images? + nsAutoString objURL; + image->GetSrc(objURL); + + // First we need to make sure that the URL is associated with a message + // protocol so we don't accidentally manipulate a URL like: + // http://www.site.com/retrieve.html?C=image.jpg. + nsCOMPtr<nsIIOService> ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString scheme; + ioService->ExtractScheme(NS_ConvertUTF16toUTF8(objURL), scheme); + + // Detect message protocols where attachments can occur. + nsCOMPtr<nsIProtocolHandler> handler; + ioService->GetProtocolHandler(scheme.get(), getter_AddRefs(handler)); + if (!handler) + continue; + nsCOMPtr<nsIMsgMessageFetchPartService> mailHandler = do_QueryInterface(handler); + if (!mailHandler) + continue; + + // the objURL is the full path to the embedded content. We need + // to update it with uri for the folder we just saved to, and the new + // msg key. + int32_t restOfUrlIndex = objURL.Find("?number="); + if (restOfUrlIndex == kNotFound) + restOfUrlIndex = objURL.FindChar('?'); + else + restOfUrlIndex = objURL.FindChar('&', restOfUrlIndex); + + if (restOfUrlIndex == kNotFound) + continue; + + nsCString newURI(baseMsgUri); + newURI.Append('#'); + newURI.AppendInt(newMsgKey); + nsString restOfUrl(Substring(objURL, restOfUrlIndex, objURL.Length() - restOfUrlIndex)); + int32_t partIndex = restOfUrl.Find("part="); + if (partIndex != kNotFound) + { + partIndex += 5; + int32_t endPart = restOfUrl.FindChar('&', partIndex); + int32_t existingPartLen = (endPart == kNotFound) ? -1 : endPart - partIndex; + restOfUrl.Replace(partIndex, existingPartLen, NS_ConvertASCIItoUTF16(partNum)); + } + + nsCOMPtr<nsIMsgMessageService> msgService; + rv = GetMessageServiceFromURI(newURI, getter_AddRefs(msgService)); + if (NS_FAILED(rv)) + continue; + nsCOMPtr<nsIURI> newUrl; + rv = msgService->GetUrlForUri(newURI.get(), getter_AddRefs(newUrl), nullptr); + if (!newUrl) + continue; + nsCString spec; + rv = newUrl->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + nsString newSrc; + // mailbox urls will have ?number=xxx; imap urls won't. We need to + // handle both cases because we may be going from a mailbox url to + // and imap url, or vice versa, depending on the original folder, + // and the destination drafts folder. + bool specHasQ = (spec.FindChar('?') != kNotFound); + if (specHasQ && restOfUrl.CharAt(0) == '?') + restOfUrl.SetCharAt('&', 0); + else if (!specHasQ && restOfUrl.CharAt(0) == '&') + restOfUrl.SetCharAt('?', 0); + AppendUTF8toUTF16(spec, newSrc); + newSrc.Append(restOfUrl); + image->SetSrc(newSrc); + } + } + } + + return NS_OK; +} + + +/* The purpose of this function is to mark any embedded object that wasn't a RFC822 part + of the original message as moz-do-not-send. + That will prevent us to attach data not specified by the user or not present in the + original message. +*/ +nsresult nsMsgCompose::TagEmbeddedObjects(nsIEditorMailSupport *aEditor) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIArray> aNodeList; + uint32_t count; + uint32_t i; + + if (!aEditor) + return NS_ERROR_FAILURE; + + rv = aEditor->GetEmbeddedObjects(getter_AddRefs(aNodeList)); + if (NS_FAILED(rv) || !aNodeList) + return NS_ERROR_FAILURE; + + if (NS_FAILED(aNodeList->GetLength(&count))) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIURI> originalUrl; + nsCString originalScheme; + nsCString originalHost; + nsCString originalPath; + + // first, convert the rdf original msg uri into a url that represents the message... + nsCOMPtr <nsIMsgMessageService> msgService; + rv = GetMessageServiceFromURI(mOriginalMsgURI, getter_AddRefs(msgService)); + if (NS_SUCCEEDED(rv)) + { + rv = msgService->GetUrlForUri(mOriginalMsgURI.get(), getter_AddRefs(originalUrl), nullptr); + if (NS_SUCCEEDED(rv) && originalUrl) + { + originalUrl->GetScheme(originalScheme); + originalUrl->GetAsciiHost(originalHost); + originalUrl->GetPath(originalPath); + } + } + + // Then compare the url of each embedded objects with the original message. + // If they a not coming from the original message, they should not be sent + // with the message. + for (i = 0; i < count; i ++) + { + nsCOMPtr<nsIDOMNode> node = do_QueryElementAt(aNodeList, i); + if (!node) + continue; + if (IsEmbeddedObjectSafe(originalScheme.get(), originalHost.get(), + originalPath.get(), node)) + continue; //Don't need to tag this object, it safe to send it. + + //The source of this object should not be sent with the message + nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(node); + if (domElement) + domElement->SetAttribute(NS_LITERAL_STRING("moz-do-not-send"), NS_LITERAL_STRING("true")); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::GetInsertingQuotedContent(bool * aInsertingQuotedText) +{ + NS_ENSURE_ARG_POINTER(aInsertingQuotedText); + *aInsertingQuotedText = mInsertingQuotedContent; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::SetInsertingQuotedContent(bool aInsertingQuotedText) +{ + mInsertingQuotedContent = aInsertingQuotedText; + return NS_OK; +} + +void +nsMsgCompose::InsertDivWrappedTextAtSelection(const nsAString &aText, + const nsAString &classStr) +{ + NS_ASSERTION(m_editor, "InsertDivWrappedTextAtSelection called, but no editor exists\n"); + if (!m_editor) + return; + + nsCOMPtr<nsIDOMElement> divElem; + nsCOMPtr<nsIHTMLEditor> htmlEditor(do_QueryInterface(m_editor)); + + nsresult rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("div"), + getter_AddRefs(divElem)); + + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIDOMNode> divNode (do_QueryInterface(divElem)); + + // We need the document + nsCOMPtr<nsIDOMDocument> doc; + rv = m_editor->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_SUCCESS_VOID(rv); + + // Break up the text by newlines, and then insert text nodes followed + // by <br> nodes. + int32_t start = 0; + int32_t end = aText.Length(); + + for (;;) + { + int32_t delimiter = aText.FindChar('\n', start); + if (delimiter == kNotFound) + delimiter = end; + + nsCOMPtr<nsIDOMText> textNode; + rv = doc->CreateTextNode(Substring(aText, start, delimiter - start), getter_AddRefs(textNode)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIDOMNode> newTextNode = do_QueryInterface(textNode); + nsCOMPtr<nsIDOMNode> resultNode; + rv = divElem->AppendChild(newTextNode, getter_AddRefs(resultNode)); + NS_ENSURE_SUCCESS_VOID(rv); + + // Now create and insert a BR + nsCOMPtr<nsIDOMElement> brElem; + rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("br"), + getter_AddRefs(brElem)); + rv = divElem->AppendChild(brElem, getter_AddRefs(resultNode)); + NS_ENSURE_SUCCESS_VOID(rv); + + if (delimiter == end) + break; + start = ++delimiter; + if (start == end) + break; + } + + htmlEditor->InsertElementAtSelection(divElem, true); + nsCOMPtr<nsIDOMNode> parent; + int32_t offset; + + rv = GetNodeLocation(divNode, address_of(parent), &offset); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsISelection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + + if (selection) + selection->Collapse(parent, offset + 1); + } + if (divElem) + divElem->SetAttribute(NS_LITERAL_STRING("class"), classStr); +} + +/* + * The following function replaces <plaintext> tags with <x-plaintext>. + * <plaintext> is a funny beast: It leads to everything following it + * being displayed verbatim, even a </plaintext> tag is ignored. + */ +static void +remove_plaintext_tag(nsString &body) +{ + // Replace all <plaintext> and </plaintext> tags. + int32_t index = 0; + bool replaced = false; + while ((index = body.Find("<plaintext", /* ignoreCase = */ true, index)) != kNotFound) { + body.Insert(u"x-", index+1); + index += 12; + replaced = true; + } + if (replaced) { + index = 0; + while ((index = body.Find("</plaintext", /* ignoreCase = */ true, index)) != kNotFound) { + body.Insert(u"x-", index+2); + index += 13; + } + } +} + +NS_IMETHODIMP +nsMsgCompose::ConvertAndLoadComposeWindow(nsString& aPrefix, + nsString& aBuf, + nsString& aSignature, + bool aQuoted, + bool aHTMLEditor) +{ + NS_ASSERTION(m_editor, "ConvertAndLoadComposeWindow but no editor\n"); + NS_ENSURE_TRUE(m_editor && m_identity, NS_ERROR_NOT_INITIALIZED); + + // First, get the nsIEditor interface for future use + nsCOMPtr<nsIDOMNode> nodeInserted; + + TranslateLineEnding(aPrefix); + TranslateLineEnding(aBuf); + TranslateLineEnding(aSignature); + + m_editor->EnableUndo(false); + + // Ok - now we need to figure out the charset of the aBuf we are going to send + // into the editor shell. There are I18N calls to sniff the data and then we need + // to call the new routine in the editor that will allow us to send in the charset + // + + // Now, insert it into the editor... + nsCOMPtr<nsIHTMLEditor> htmlEditor (do_QueryInterface(m_editor)); + nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(m_editor)); + nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(m_editor)); + int32_t reply_on_top = 0; + bool sig_bottom = true; + m_identity->GetReplyOnTop(&reply_on_top); + m_identity->GetSigBottom(&sig_bottom); + bool sigOnTop = (reply_on_top == 1 && !sig_bottom); + bool isForwarded = (mType == nsIMsgCompType::ForwardInline); + + if (aQuoted) + { + mInsertingQuotedContent = true; + if (!aPrefix.IsEmpty()) + { + if (!aHTMLEditor) + aPrefix.AppendLiteral("\n"); + + int32_t reply_on_top = 0; + m_identity->GetReplyOnTop(&reply_on_top); + if (reply_on_top == 1) + { + // HTML editor eats one line break + if (aHTMLEditor) + textEditor->InsertLineBreak(); + + // add one newline if a signature comes before the quote, two otherwise + bool includeSignature = true; + bool sig_bottom = true; + bool attachFile = false; + nsString prefSigText; + + m_identity->GetSigOnReply(&includeSignature); + m_identity->GetSigBottom(&sig_bottom); + m_identity->GetHtmlSigText(prefSigText); + nsresult rv = m_identity->GetAttachSignature(&attachFile); + if (includeSignature && !sig_bottom && + ((NS_SUCCEEDED(rv) && attachFile) || !prefSigText.IsEmpty())) + textEditor->InsertLineBreak(); + else { + textEditor->InsertLineBreak(); + textEditor->InsertLineBreak(); + } + } + + InsertDivWrappedTextAtSelection(aPrefix, + NS_LITERAL_STRING("moz-cite-prefix")); + } + + if (!aBuf.IsEmpty() && mailEditor) + { + // This leaves the caret at the right place to insert a bottom signature. + if (aHTMLEditor) { + nsAutoString body(aBuf); + remove_plaintext_tag(body); + mailEditor->InsertAsCitedQuotation(body, + mCiteReference, + true, + getter_AddRefs(nodeInserted)); + } else { + mailEditor->InsertAsQuotation(aBuf, + getter_AddRefs(nodeInserted)); + } + } + + mInsertingQuotedContent = false; + + (void)TagEmbeddedObjects(mailEditor); + + if (!aSignature.IsEmpty()) + { + //we cannot add it on top earlier, because TagEmbeddedObjects will mark all images in the signature as "moz-do-not-send" + if( sigOnTop ) + m_editor->BeginningOfDocument(); + + if (aHTMLEditor && htmlEditor) + htmlEditor->InsertHTML(aSignature); + else if (htmlEditor) + { + textEditor->InsertLineBreak(); + InsertDivWrappedTextAtSelection(aSignature, + NS_LITERAL_STRING("moz-signature")); + } + + if( sigOnTop ) + m_editor->EndOfDocument(); + } + } + else + { + if (aHTMLEditor && htmlEditor) + { + mInsertingQuotedContent = true; + if (isForwarded && Substring(aBuf, 0, sizeof(MIME_FORWARD_HTML_PREFIX)-1) + .EqualsLiteral(MIME_FORWARD_HTML_PREFIX)) { + // We assign the opening tag inside "<HTML><BODY><BR><BR>" before the + // two <br> elements. + // This is a bit hacky but we know that the MIME code prepares the + // forwarded content like this: + // <HTML><BODY><BR><BR> + forwarded header + header table. + // Note: We only do this when we prepare the message to be forwarded, + // a re-opened saved draft of a forwarded message does not repeat this. + nsString newBody(aBuf); + nsString divTag; + divTag.AssignLiteral("<div class=\"moz-forward-container\">"); + newBody.Insert(divTag, sizeof(MIME_FORWARD_HTML_PREFIX)-1-8); + remove_plaintext_tag(newBody); + htmlEditor->RebuildDocumentFromSource(newBody); + } else { + htmlEditor->RebuildDocumentFromSource(aBuf); + } + mInsertingQuotedContent = false; + + // when forwarding a message as inline, tag any embedded objects + // which refer to local images or files so we know not to include + // send them + if (isForwarded) + (void)TagEmbeddedObjects(mailEditor); + + if (!aSignature.IsEmpty()) + { + if (isForwarded && sigOnTop) { + // Use our own function, nsEditor::BeginningOfDocument() would position + // into the <div class="moz-forward-container"> we've just created. + MoveToBeginningOfDocument(); + } else { + // Use our own function, nsEditor::EndOfDocument() would position + // into the <div class="moz-forward-container"> we've just created. + MoveToEndOfDocument(); + } + htmlEditor->InsertHTML(aSignature); + if (isForwarded && sigOnTop) + m_editor->EndOfDocument(); + } + else + m_editor->EndOfDocument(); + } + else if (htmlEditor) + { + bool sigOnTopInserted = false; + if (isForwarded && sigOnTop && !aSignature.IsEmpty()) + { + textEditor->InsertLineBreak(); + InsertDivWrappedTextAtSelection(aSignature, + NS_LITERAL_STRING("moz-signature")); + m_editor->EndOfDocument(); + sigOnTopInserted = true; + } + + if (!aBuf.IsEmpty()) + { + nsresult rv; + nsCOMPtr<nsIDOMElement> divElem; + nsCOMPtr<nsIDOMNode> extraBr; + + if (isForwarded) { + // Special treatment for forwarded messages: Part 1. + // Create a <div> of the required class. + rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("div"), + getter_AddRefs(divElem)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString attributeName; + nsAutoString attributeValue; + attributeName.AssignLiteral("class"); + attributeValue.AssignLiteral("moz-forward-container"); + divElem->SetAttribute(attributeName, attributeValue); + + // We can't insert an empty <div>, so fill it with something. + nsCOMPtr<nsIDOMElement> brElem; + rv = htmlEditor->CreateElementWithDefaults(NS_LITERAL_STRING("br"), + getter_AddRefs(brElem)); + NS_ENSURE_SUCCESS(rv, rv); + rv = divElem->AppendChild(brElem, getter_AddRefs(extraBr)); + NS_ENSURE_SUCCESS(rv, rv); + + // Insert the non-empty <div> into the DOM. + rv = htmlEditor->InsertElementAtSelection(divElem, false); + NS_ENSURE_SUCCESS(rv, rv); + + // Position into the div, so out content goes there. + nsCOMPtr<nsISelection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + rv = selection->Collapse(divElem, 0); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mailEditor) { + rv = mailEditor->InsertTextWithQuotations(aBuf); + } else { + // Will we ever get here? + rv = textEditor->InsertText(aBuf); + } + NS_ENSURE_SUCCESS(rv, rv); + + if (isForwarded) { + // Special treatment for forwarded messages: Part 2. + if (sigOnTopInserted) { + // Sadly the M-C editor inserts a <br> between the <div> for the signature + // and this <div>, so remove the <br> we don't want. + nsCOMPtr<nsIDOMNode> brBeforeDiv; + nsAutoString tagLocalName; + rv = divElem->GetPreviousSibling(getter_AddRefs(brBeforeDiv)); + if (NS_SUCCEEDED(rv) && brBeforeDiv) { + brBeforeDiv->GetLocalName(tagLocalName); + if (tagLocalName.EqualsLiteral("br")) { + rv = m_editor->DeleteNode(brBeforeDiv); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + // Clean up the <br> we inserted. + rv = m_editor->DeleteNode(extraBr); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Use our own function instead of nsEditor::EndOfDocument() because + // we don't want to position at the end of the div we've just created. + // It's OK to use, even if we're not forwarding and didn't create a + // <div>. + rv = MoveToEndOfDocument(); + NS_ENSURE_SUCCESS(rv, rv); + } + + if ((!isForwarded || !sigOnTop) && !aSignature.IsEmpty()) { + textEditor->InsertLineBreak(); + InsertDivWrappedTextAtSelection(aSignature, + NS_LITERAL_STRING("moz-signature")); + } + } + } + + if (aBuf.IsEmpty()) + m_editor->BeginningOfDocument(); + else + { + switch (reply_on_top) + { + // This should set the cursor after the body but before the sig + case 0: + { + if (!textEditor) + { + m_editor->BeginningOfDocument(); + break; + } + + nsCOMPtr<nsISelection> selection = nullptr; + nsCOMPtr<nsIDOMNode> parent = nullptr; + int32_t offset; + nsresult rv; + + // get parent and offset of mailcite + rv = GetNodeLocation(nodeInserted, address_of(parent), &offset); + if (NS_FAILED(rv) || (!parent)) + { + m_editor->BeginningOfDocument(); + break; + } + + // get selection + m_editor->GetSelection(getter_AddRefs(selection)); + if (!selection) + { + m_editor->BeginningOfDocument(); + break; + } + + // place selection after mailcite + selection->Collapse(parent, offset+1); + + // insert a break at current selection + textEditor->InsertLineBreak(); + + // i'm not sure if you need to move the selection back to before the + // break. expirement. + selection->Collapse(parent, offset+1); + + break; + } + + case 2: + { + m_editor->SelectAll(); + break; + } + + // This should set the cursor to the top! + default: + { + m_editor->BeginningOfDocument(); + break; + } + } + } + + nsCOMPtr<nsISelectionController> selCon; + m_editor->GetSelectionController(getter_AddRefs(selCon)); + + if (selCon) + selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL, nsISelectionController::SELECTION_ANCHOR_REGION, true); + + m_editor->EnableUndo(true); + SetBodyModified(false); + +#ifdef MSGCOMP_TRACE_PERFORMANCE + nsCOMPtr<nsIMsgComposeService> composeService (do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID)); + composeService->TimeStamp("Finished inserting data into the editor. The window is finally ready!", false); +#endif + return NS_OK; +} + +/** + * Check the identity pref to include signature on replies and forwards. + */ +bool nsMsgCompose::CheckIncludeSignaturePrefs(nsIMsgIdentity *identity) +{ + bool includeSignature = true; + switch (mType) + { + case nsIMsgCompType::ForwardInline: + case nsIMsgCompType::ForwardAsAttachment: + identity->GetSigOnForward(&includeSignature); + break; + case nsIMsgCompType::Reply: + case nsIMsgCompType::ReplyAll: + case nsIMsgCompType::ReplyToList: + case nsIMsgCompType::ReplyToGroup: + case nsIMsgCompType::ReplyToSender: + case nsIMsgCompType::ReplyToSenderAndGroup: + identity->GetSigOnReply(&includeSignature); + break; + } + return includeSignature; +} + +nsresult +nsMsgCompose::SetQuotingToFollow(bool aVal) +{ + mQuotingToFollow = aVal; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::GetQuotingToFollow(bool* quotingToFollow) +{ + NS_ENSURE_ARG(quotingToFollow); + *quotingToFollow = mQuotingToFollow; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::Initialize(nsIMsgComposeParams *aParams, + mozIDOMWindowProxy *aWindow, + nsIDocShell *aDocShell) +{ + NS_ENSURE_ARG_POINTER(aParams); + nsresult rv; + + aParams->GetIdentity(getter_AddRefs(m_identity)); + + if (aWindow) + { + m_window = aWindow; + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aWindow); + NS_ENSURE_TRUE(window, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDocShellTreeItem> treeItem = + do_QueryInterface(window->GetDocShell()); + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + rv = treeItem->GetTreeOwner(getter_AddRefs(treeOwner)); + if (NS_FAILED(rv)) return rv; + + m_baseWindow = do_QueryInterface(treeOwner); + } + + MSG_ComposeFormat format; + aParams->GetFormat(&format); + + MSG_ComposeType type; + aParams->GetType(&type); + + nsCString originalMsgURI; + aParams->GetOriginalMsgURI(getter_Copies(originalMsgURI)); + aParams->GetOrigMsgHdr(getter_AddRefs(mOrigMsgHdr)); + + nsCOMPtr<nsIMsgCompFields> composeFields; + aParams->GetComposeFields(getter_AddRefs(composeFields)); + + nsCOMPtr<nsIMsgComposeService> composeService = do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = composeService->DetermineComposeHTML(m_identity, format, &m_composeHTML); + NS_ENSURE_SUCCESS(rv,rv); + + if (composeFields) + { + nsAutoCString draftId; // will get set for drafts and templates + rv = composeFields->GetDraftId(getter_Copies(draftId)); + NS_ENSURE_SUCCESS(rv,rv); + + // Set return receipt flag and type, and if we should attach a vCard + // by checking the identity prefs - but don't clobber the values for + // drafts and templates as they were set up already by mime when + // initializing the message. + if (m_identity && draftId.IsEmpty() && type != nsIMsgCompType::Template) + { + bool requestReturnReceipt = false; + rv = m_identity->GetRequestReturnReceipt(&requestReturnReceipt); + NS_ENSURE_SUCCESS(rv, rv); + rv = composeFields->SetReturnReceipt(requestReturnReceipt); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t receiptType = nsIMsgMdnGenerator::eDntType; + rv = m_identity->GetReceiptHeaderType(&receiptType); + NS_ENSURE_SUCCESS(rv, rv); + rv = composeFields->SetReceiptHeaderType(receiptType); + NS_ENSURE_SUCCESS(rv, rv); + + bool requestDSN = false; + rv = m_identity->GetRequestDSN(&requestDSN); + NS_ENSURE_SUCCESS(rv, rv); + rv = composeFields->SetDSN(requestDSN); + NS_ENSURE_SUCCESS(rv, rv); + + bool attachVCard; + rv = m_identity->GetAttachVCard(&attachVCard); + NS_ENSURE_SUCCESS(rv, rv); + rv = composeFields->SetAttachVCard(attachVCard); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + nsCOMPtr<nsIMsgSendListener> externalSendListener; + aParams->GetSendListener(getter_AddRefs(externalSendListener)); + if(externalSendListener) + AddMsgSendListener( externalSendListener ); + + nsCString smtpPassword; + aParams->GetSmtpPassword(getter_Copies(smtpPassword)); + mSmtpPassword = smtpPassword; + + aParams->GetHtmlToQuote(mHtmlToQuote); + + if (aDocShell) + { + mDocShell = aDocShell; + // register the compose object with the compose service + rv = composeService->RegisterComposeDocShell(aDocShell, this); + NS_ENSURE_SUCCESS(rv, rv); + } + return CreateMessage(originalMsgURI.get(), type, composeFields); +} + +nsresult nsMsgCompose::SetDocumentCharset(const char *aCharset) +{ + NS_ENSURE_TRUE(m_compFields && m_editor, NS_ERROR_NOT_INITIALIZED); + + // Set charset, this will be used for the MIME charset labeling. + m_compFields->SetCharacterSet(aCharset); + + // notify the change to editor + nsCString charset; + if (aCharset) + charset = nsDependentCString(aCharset); + if (m_editor) + m_editor->SetDocumentCharacterSet(charset); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::RegisterStateListener(nsIMsgComposeStateListener *aStateListener) +{ + NS_ENSURE_ARG_POINTER(aStateListener); + + return mStateListeners.AppendElement(aStateListener) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMsgCompose::UnregisterStateListener(nsIMsgComposeStateListener *aStateListener) +{ + NS_ENSURE_ARG_POINTER(aStateListener); + + return mStateListeners.RemoveElement(aStateListener) ? NS_OK : NS_ERROR_FAILURE; +} + +// Added to allow easier use of the nsIMsgSendListener +NS_IMETHODIMP nsMsgCompose::AddMsgSendListener( nsIMsgSendListener *aMsgSendListener ) +{ + NS_ENSURE_ARG_POINTER(aMsgSendListener); + return mExternalSendListeners.AppendElement(aMsgSendListener) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgCompose::RemoveMsgSendListener( nsIMsgSendListener *aMsgSendListener ) +{ + NS_ENSURE_ARG_POINTER(aMsgSendListener); + return mExternalSendListeners.RemoveElement(aMsgSendListener) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMsgCompose::SendMsgToServer(MSG_DeliverMode deliverMode, nsIMsgIdentity *identity, + const char *accountKey) +{ + nsresult rv = NS_OK; + + // clear saved message id if sending, so we don't send out the same message-id. + if (deliverMode == nsIMsgCompDeliverMode::Now || + deliverMode == nsIMsgCompDeliverMode::Later || + deliverMode == nsIMsgCompDeliverMode::Background) + m_compFields->SetMessageId(""); + + if (m_compFields && identity) + { + // Pref values are supposed to be stored as UTF-8, so no conversion + nsCString email; + nsString fullName; + nsString organization; + + identity->GetEmail(email); + identity->GetFullName(fullName); + identity->GetOrganization(organization); + + const char* pFrom = m_compFields->GetFrom(); + if (!pFrom || !*pFrom) + { + nsCString sender; + MakeMimeAddress(NS_ConvertUTF16toUTF8(fullName), email, sender); + m_compFields->SetFrom(sender.IsEmpty() ? email.get() : sender.get()); + } + + m_compFields->SetOrganization(organization); + + // We need an nsIMsgSend instance to send the message. Allow extensions + // to override the default SMTP sender by observing mail-set-sender. + mMsgSend = nullptr; + mDeliverMode = deliverMode; // save for possible access by observer. + + // Allow extensions to specify an outgoing server. + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + NS_ENSURE_STATE(observerService); + + // Assemble a string with sending parameters. + nsAutoString sendParms; + + // First parameter: account key. This may be null. + sendParms.AppendASCII(accountKey && *accountKey ? accountKey : ""); + sendParms.AppendLiteral(","); + + // Second parameter: deliverMode. + sendParms.AppendInt(deliverMode); + sendParms.AppendLiteral(","); + + // Third parameter: identity (as identity key). + nsAutoCString identityKey; + identity->GetKey(identityKey); + sendParms.AppendASCII(identityKey.get()); + + observerService->NotifyObservers( + NS_ISUPPORTS_CAST(nsIMsgCompose*, this), + "mail-set-sender", + sendParms.get()); + + if (!mMsgSend) + mMsgSend = do_CreateInstance(NS_MSGSEND_CONTRACTID); + + if (mMsgSend) + { + nsCString bodyString(m_compFields->GetBody()); + + // Create the listener for the send operation... + nsCOMPtr<nsIMsgComposeSendListener> composeSendListener = do_CreateInstance(NS_MSGCOMPOSESENDLISTENER_CONTRACTID); + if (!composeSendListener) + return NS_ERROR_OUT_OF_MEMORY; + + // right now, AutoSaveAsDraft is identical to SaveAsDraft as + // far as the msg send code is concerned. This way, we don't have + // to add an nsMsgDeliverMode for autosaveasdraft, and add cases for + // it in the msg send code. + if (deliverMode == nsIMsgCompDeliverMode::AutoSaveAsDraft) + deliverMode = nsIMsgCompDeliverMode::SaveAsDraft; + + RefPtr<nsIMsgCompose> msgCompose(this); + composeSendListener->SetMsgCompose(msgCompose); + composeSendListener->SetDeliverMode(deliverMode); + + if (mProgress) + { + nsCOMPtr<nsIWebProgressListener> progressListener = do_QueryInterface(composeSendListener); + mProgress->RegisterListener(progressListener); + } + + // If we are composing HTML, then this should be sent as + // multipart/related which means we pass the editor into the + // backend...if not, just pass nullptr + // + nsCOMPtr<nsIMsgSendListener> sendListener = do_QueryInterface(composeSendListener); + rv = mMsgSend->CreateAndSendMessage( + m_composeHTML ? m_editor.get() : nullptr, + identity, + accountKey, + m_compFields, + false, + false, + (nsMsgDeliverMode)deliverMode, + nullptr, + m_composeHTML ? TEXT_HTML : TEXT_PLAIN, + bodyString, + nullptr, + nullptr, + m_window, + mProgress, + sendListener, + mSmtpPassword.get(), + mOriginalMsgURI, + mType); + } + else + rv = NS_ERROR_FAILURE; + } + else + rv = NS_ERROR_NOT_INITIALIZED; + + if (NS_FAILED(rv)) + NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, rv); + + return rv; +} + +NS_IMETHODIMP nsMsgCompose::SendMsg(MSG_DeliverMode deliverMode, nsIMsgIdentity *identity, const char *accountKey, nsIMsgWindow *aMsgWindow, nsIMsgProgress *progress) +{ + NS_ENSURE_TRUE(m_compFields, NS_ERROR_NOT_INITIALIZED); + nsresult rv = NS_OK; + nsCOMPtr<nsIPrompt> prompt; + + // i'm assuming the compose window is still up at this point... + if (m_window) { + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(m_window); + window->GetPrompter(getter_AddRefs(prompt)); + } + + // Set content type based on which type of compose window we had. + nsString contentType = (m_composeHTML) ? NS_LITERAL_STRING("text/html"): + NS_LITERAL_STRING("text/plain"); + nsString msgBody; + if (m_editor) + { + // Reset message body previously stored in the compose fields + // There is 2 nsIMsgCompFields::SetBody() functions using a pointer as argument, + // therefore a casting is required. + m_compFields->SetBody((const char *)nullptr); + + const char *charset = m_compFields->GetCharacterSet(); + + uint32_t flags = nsIDocumentEncoder::OutputCRLineBreak | + nsIDocumentEncoder::OutputLFLineBreak; + + if (m_composeHTML) { + flags |= nsIDocumentEncoder::OutputFormatted | + nsIDocumentEncoder::OutputDisallowLineBreaking; + } else { + bool flowed, delsp, formatted, disallowBreaks; + GetSerialiserFlags(charset, &flowed, &delsp, &formatted, &disallowBreaks); + if (flowed) + flags |= nsIDocumentEncoder::OutputFormatFlowed; + if (delsp) + flags |= nsIDocumentEncoder::OutputFormatDelSp; + if (formatted) + flags |= nsIDocumentEncoder::OutputFormatted; + if (disallowBreaks) + flags |= nsIDocumentEncoder::OutputDisallowLineBreaking; + // Don't lose NBSP in the plain text encoder. + flags |= nsIDocumentEncoder::OutputPersistNBSP; + } + rv = m_editor->OutputToString(contentType, flags, msgBody); + NS_ENSURE_SUCCESS(rv, rv); + } + else + { + m_compFields->GetBody(msgBody); + } + if (!msgBody.IsEmpty()) + { + // Convert body to mail charset + nsCString outCString; + rv = nsMsgI18NConvertFromUnicode(m_compFields->GetCharacterSet(), + msgBody, outCString, false, true); + bool isAsciiOnly = NS_IsAscii(outCString.get()) && + !nsMsgI18Nstateful_charset(m_compFields->GetCharacterSet()); + if (m_compFields->GetForceMsgEncoding()) + isAsciiOnly = false; + if (NS_SUCCEEDED(rv) && !outCString.IsEmpty()) + { + // If the body contains characters outside the repertoire of the current + // charset, just convert to UTF-8 and be done with it + // unless disable_fallback_to_utf8 is set for this charset. + if (NS_ERROR_UENC_NOMAPPING == rv) + { + bool needToCheckCharset; + m_compFields->GetNeedToCheckCharset(&needToCheckCharset); + if (needToCheckCharset) + { + bool disableFallback = false; + nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (prefBranch) + { + nsCString prefName("mailnews.disable_fallback_to_utf8."); + prefName.Append(m_compFields->GetCharacterSet()); + prefBranch->GetBoolPref(prefName.get(), &disableFallback); + } + if (!disableFallback) + { + CopyUTF16toUTF8(msgBody, outCString); + m_compFields->SetCharacterSet("UTF-8"); + SetDocumentCharset("UTF-8"); + } + } + } + m_compFields->SetBodyIsAsciiOnly(isAsciiOnly); + m_compFields->SetBody(outCString.get()); + } + else + { + m_compFields->SetBody(NS_ConvertUTF16toUTF8(msgBody).get()); + m_compFields->SetCharacterSet("UTF-8"); + SetDocumentCharset("UTF-8"); + } + } + + // Let's open the progress dialog + if (progress) + { + mProgress = progress; + + if (deliverMode != nsIMsgCompDeliverMode::AutoSaveAsDraft) + { + nsAutoString msgSubject; + m_compFields->GetSubject(msgSubject); + + bool showProgress = false; + nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefBranch) + { + prefBranch->GetBoolPref("mailnews.show_send_progress", &showProgress); + if (showProgress) + { + nsCOMPtr<nsIMsgComposeProgressParams> params = do_CreateInstance(NS_MSGCOMPOSEPROGRESSPARAMS_CONTRACTID, &rv); + if (NS_FAILED(rv) || !params) + return NS_ERROR_FAILURE; + + params->SetSubject(msgSubject.get()); + params->SetDeliveryMode(deliverMode); + + mProgress->OpenProgressDialog(m_window, aMsgWindow, + "chrome://messenger/content/messengercompose/sendProgress.xul", + false, params); + } + } + } + + mProgress->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_START, NS_OK); + } + + bool attachVCard = false; + m_compFields->GetAttachVCard(&attachVCard); + + if (attachVCard && identity && + (deliverMode == nsIMsgCompDeliverMode::Now || + deliverMode == nsIMsgCompDeliverMode::Later || + deliverMode == nsIMsgCompDeliverMode::Background)) + { + nsCString escapedVCard; + // make sure, if there is no card, this returns an empty string, or NS_ERROR_FAILURE + rv = identity->GetEscapedVCard(escapedVCard); + + if (NS_SUCCEEDED(rv) && !escapedVCard.IsEmpty()) + { + nsCString vCardUrl; + vCardUrl = "data:text/x-vcard;charset=utf-8;base64,"; + nsCString unescapedData; + MsgUnescapeString(escapedVCard, 0, unescapedData); + char *result = PL_Base64Encode(unescapedData.get(), 0, nullptr); + vCardUrl += result; + PR_Free(result); + + nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && attachment) + { + // [comment from 4.x] + // Send the vCard out with a filename which distinguishes this user. e.g. jsmith.vcf + // The main reason to do this is for interop with Eudora, which saves off + // the attachments separately from the message body + nsCString userid; + (void)identity->GetEmail(userid); + int32_t index = userid.FindChar('@'); + if (index != kNotFound) + userid.SetLength(index); + + if (userid.IsEmpty()) + attachment->SetName(NS_LITERAL_STRING("vcard.vcf")); + else + { + // Replace any dot with underscore to stop vCards + // generating false positives with some heuristic scanners + MsgReplaceChar(userid, '.', '_'); + userid.AppendLiteral(".vcf"); + attachment->SetName(NS_ConvertASCIItoUTF16(userid)); + } + + attachment->SetUrl(vCardUrl); + m_compFields->AddAttachment(attachment); + } + } + } + + // Save the identity being sent for later use. + m_identity = identity; + + rv = SendMsgToServer(deliverMode, identity, accountKey); + if (NS_FAILED(rv)) + { + nsCOMPtr<nsIMsgSendReport> sendReport; + if (mMsgSend) + mMsgSend->GetSendReport(getter_AddRefs(sendReport)); + if (sendReport) + { + nsresult theError; + sendReport->DisplayReport(prompt, true, true, &theError); + } + else + { + /* If we come here it's because we got an error before we could intialize a + send report! Let's try our best... + */ + switch (deliverMode) + { + case nsIMsgCompDeliverMode::Later: + nsMsgDisplayMessageByName(prompt, u"unableToSendLater"); + break; + case nsIMsgCompDeliverMode::AutoSaveAsDraft: + case nsIMsgCompDeliverMode::SaveAsDraft: + nsMsgDisplayMessageByName(prompt, u"unableToSaveDraft"); + break; + case nsIMsgCompDeliverMode::SaveAsTemplate: + nsMsgDisplayMessageByName(prompt, u"unableToSaveTemplate"); + break; + + default: + nsMsgDisplayMessageByName(prompt, u"sendFailed"); + break; + } + } + + if (progress) + progress->CloseProgressDialog(true); + } + + return rv; +} + +/* attribute boolean deleteDraft */ +NS_IMETHODIMP nsMsgCompose::GetDeleteDraft(bool *aDeleteDraft) +{ + NS_ENSURE_ARG_POINTER(aDeleteDraft); + *aDeleteDraft = mDeleteDraft; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::SetDeleteDraft(bool aDeleteDraft) +{ + mDeleteDraft = aDeleteDraft; + return NS_OK; +} + +bool nsMsgCompose::IsLastWindow() +{ + nsresult rv; + bool more; + nsCOMPtr<nsIWindowMediator> windowMediator = + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsISimpleEnumerator> windowEnumerator; + rv = windowMediator->GetEnumerator(nullptr, + getter_AddRefs(windowEnumerator)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsISupports> isupports; + + if (NS_SUCCEEDED(windowEnumerator->GetNext(getter_AddRefs(isupports)))) + if (NS_SUCCEEDED(windowEnumerator->HasMoreElements(&more))) + return !more; + } + } + return true; +} + +NS_IMETHODIMP nsMsgCompose::CloseWindow(void) +{ + nsresult rv; + + nsCOMPtr<nsIMsgComposeService> composeService = do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + // unregister the compose object with the compose service + rv = composeService->UnregisterComposeDocShell(mDocShell); + NS_ENSURE_SUCCESS(rv, rv); + mDocShell = nullptr; + + // ensure that the destructor of nsMsgSend is invoked to remove + // temporary files. + mMsgSend = nullptr; + + //We are going away for real, we need to do some clean up first + if (m_baseWindow) + { + if (m_editor) + { + // The editor will be destroyed during the close window. + // Set it to null to be sure we won't use it anymore. + m_editor = nullptr; + } + nsIBaseWindow * window = m_baseWindow; + m_baseWindow = nullptr; + rv = window->Destroy(); + } + + m_window = nullptr; + return rv; +} + +nsresult nsMsgCompose::Abort() +{ + if (mMsgSend) + mMsgSend->Abort(); + + if (mProgress) + mProgress->CloseProgressDialog(true); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetEditor(nsIEditor * *aEditor) +{ + NS_IF_ADDREF(*aEditor = m_editor); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::SetEditor(nsIEditor *aEditor) +{ + m_editor = aEditor; + return NS_OK; +} + +static nsresult fixCharset(nsCString &aCharset) +{ + // No matter what, we should block x-windows-949 (our internal name) + // from being used for outgoing emails (bug 234958). + if (aCharset.Equals("x-windows-949", nsCaseInsensitiveCStringComparator())) + aCharset = "EUC-KR"; + + // Convert to a canonical charset name. + // Bug 1297118 will revisit this call site. + nsresult rv; + nsCOMPtr<nsICharsetConverterManager> ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString charset(aCharset); + rv = ccm->GetCharsetAlias(charset.get(), aCharset); + + // Don't accept UTF-16 ever. UTF-16 should never be selected as an + // outgoing encoding for e-mail. MIME can't handle those messages + // encoded in ASCII-incompatible encodings. + if (NS_FAILED(rv) || + StringBeginsWith(aCharset, NS_LITERAL_CSTRING("UTF-16"))) { + aCharset.AssignLiteral("UTF-8"); + } + return NS_OK; +} + +// This used to be called BEFORE editor was created +// (it did the loadUrl that triggered editor creation) +// It is called from JS after editor creation +// (loadUrl is done in JS) +NS_IMETHODIMP nsMsgCompose::InitEditor(nsIEditor* aEditor, mozIDOMWindowProxy* aContentWindow) +{ + NS_ENSURE_ARG_POINTER(aEditor); + NS_ENSURE_ARG_POINTER(aContentWindow); + nsresult rv; + + m_editor = aEditor; + + nsAutoCString msgCharSet(m_compFields->GetCharacterSet()); + rv = fixCharset(msgCharSet); + NS_ENSURE_SUCCESS(rv, rv); + m_compFields->SetCharacterSet(msgCharSet.get()); + m_editor->SetDocumentCharacterSet(msgCharSet); + + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(aContentWindow); + + nsIDocShell *docShell = window->GetDocShell(); + NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIContentViewer> childCV; + NS_ENSURE_SUCCESS(docShell->GetContentViewer(getter_AddRefs(childCV)), NS_ERROR_FAILURE); + if (childCV) + { + // SetForceCharacterSet will complain about "UTF-7" or "x-mac-croatian" + // (see test-charset-edit.js), but we deal with this elsewhere. + rv = childCV->SetForceCharacterSet(msgCharSet); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetForceCharacterSet() failed"); + } + + // This is what used to be done in mDocumentListener, + // nsMsgDocumentStateListener::NotifyDocumentCreated() + bool quotingToFollow = false; + GetQuotingToFollow("ingToFollow); + if (quotingToFollow) + return BuildQuotedMessageAndSignature(); + else + { + NotifyStateListeners(nsIMsgComposeNotificationType::ComposeFieldsReady, NS_OK); + rv = BuildBodyMessageAndSignature(); + NotifyStateListeners(nsIMsgComposeNotificationType::ComposeBodyReady, NS_OK); + return rv; + } +} + +NS_IMETHODIMP nsMsgCompose::GetBodyRaw(nsACString& aBodyRaw) +{ + aBodyRaw.Assign((char *)m_compFields->GetBody()); + return NS_OK; +} + +nsresult nsMsgCompose::GetBodyModified(bool * modified) +{ + nsresult rv; + + if (! modified) + return NS_ERROR_NULL_POINTER; + + *modified = true; + + if (m_editor) + { + rv = m_editor->GetDocumentModified(modified); + if (NS_FAILED(rv)) + *modified = true; + } + + return NS_OK; +} + +nsresult nsMsgCompose::SetBodyModified(bool modified) +{ + nsresult rv = NS_OK; + + if (m_editor) + { + if (modified) + { + int32_t modCount = 0; + m_editor->GetModificationCount(&modCount); + if (modCount == 0) + m_editor->IncrementModificationCount(1); + } + else + m_editor->ResetModificationCount(); + } + + return rv; +} + +NS_IMETHODIMP +nsMsgCompose::GetDomWindow(mozIDOMWindowProxy * *aDomWindow) +{ + NS_IF_ADDREF(*aDomWindow = m_window); + return NS_OK; +} + +nsresult nsMsgCompose::GetCompFields(nsIMsgCompFields * *aCompFields) +{ + *aCompFields = (nsIMsgCompFields*)m_compFields; + NS_IF_ADDREF(*aCompFields); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetComposeHTML(bool *aComposeHTML) +{ + *aComposeHTML = m_composeHTML; + return NS_OK; +} + +nsresult nsMsgCompose::GetWrapLength(int32_t *aWrapLength) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return rv; + + return prefBranch->GetIntPref("mailnews.wraplength", aWrapLength); +} + +nsresult nsMsgCompose::CreateMessage(const char * originalMsgURI, + MSG_ComposeType type, + nsIMsgCompFields * compFields) +{ + nsresult rv = NS_OK; + mType = type; + mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_None; + + mDeleteDraft = (type == nsIMsgCompType::Draft); + nsAutoCString msgUri(originalMsgURI); + bool fileUrl = StringBeginsWith(msgUri, NS_LITERAL_CSTRING("file:")); + int32_t typeIndex = msgUri.Find("type=application/x-message-display"); + if (typeIndex != kNotFound && typeIndex > 0) + { + // Strip out type=application/x-message-display because it confuses libmime. + msgUri.Cut(typeIndex, sizeof("type=application/x-message-display")); + if (fileUrl) // we're dealing with an .eml file msg + { + // We have now removed the type from the uri. Make sure we don't have + // an uri with "&&" now. If we do, remove the second '&'. + if (msgUri.CharAt(typeIndex) == '&') + msgUri.Cut(typeIndex, 1); + // Remove possible trailing '?'. + if (msgUri.CharAt(msgUri.Length() - 1) == '?') + msgUri.Cut(msgUri.Length() - 1, 1); + } + else // we're dealing with a message/rfc822 attachment + { + // nsURLFetcher will check for "realtype=message/rfc822" and will set the + // content type to message/rfc822 in the forwarded message. + msgUri.Append("&realtype=message/rfc822"); + } + originalMsgURI = msgUri.get(); + } + + if (compFields) + { + NS_IF_RELEASE(m_compFields); + m_compFields = reinterpret_cast<nsMsgCompFields*>(compFields); + NS_ADDREF(m_compFields); + } + else + { + m_compFields = new nsMsgCompFields(); + if (m_compFields) + NS_ADDREF(m_compFields); + else + return NS_ERROR_OUT_OF_MEMORY; + } + + if (m_identity && mType != nsIMsgCompType::Draft) + { + // Setup reply-to field. + nsCString replyTo; + m_identity->GetReplyTo(replyTo); + if (!replyTo.IsEmpty()) + { + nsCString resultStr; + RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetReplyTo()), + replyTo, resultStr); + if (!resultStr.IsEmpty()) + { + replyTo.Append(','); + replyTo.Append(resultStr); + } + m_compFields->SetReplyTo(replyTo.get()); + } + + // Setup auto-Cc field. + bool doCc; + m_identity->GetDoCc(&doCc); + if (doCc) + { + nsCString ccList; + m_identity->GetDoCcList(ccList); + + nsCString resultStr; + RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetCc()), + ccList, resultStr); + if (!resultStr.IsEmpty()) + { + ccList.Append(','); + ccList.Append(resultStr); + } + m_compFields->SetCc(ccList.get()); + } + + // Setup auto-Bcc field. + bool doBcc; + m_identity->GetDoBcc(&doBcc); + if (doBcc) + { + nsCString bccList; + m_identity->GetDoBccList(bccList); + + nsCString resultStr; + RemoveDuplicateAddresses(nsDependentCString(m_compFields->GetBcc()), + bccList, resultStr); + if (!resultStr.IsEmpty()) + { + bccList.Append(','); + bccList.Append(resultStr); + } + m_compFields->SetBcc(bccList.get()); + } + } + + if (mType == nsIMsgCompType::Draft) + { + nsCString curDraftIdURL; + rv = m_compFields->GetDraftId(getter_Copies(curDraftIdURL)); + NS_ASSERTION(NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty(), "CreateMessage can't get draft id"); + + // Skip if no draft id (probably a new draft msg). + if (NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty()) + { + nsCOMPtr <nsIMsgDBHdr> msgDBHdr; + rv = GetMsgDBHdrFromURI(curDraftIdURL.get(), getter_AddRefs(msgDBHdr)); + NS_ASSERTION(NS_SUCCEEDED(rv), "CreateMessage can't get msg header DB interface pointer."); + if (msgDBHdr) + { + nsCString queuedDisposition; + msgDBHdr->GetStringProperty(QUEUED_DISPOSITION_PROPERTY, getter_Copies(queuedDisposition)); + // We need to retrieve the original URI from the database so we can + // set the disposition flags correctly if the draft is a reply or forwarded message. + nsCString originalMsgURIfromDB; + msgDBHdr->GetStringProperty(ORIG_URI_PROPERTY, getter_Copies(originalMsgURIfromDB)); + mOriginalMsgURI = originalMsgURIfromDB; + if (!queuedDisposition.IsEmpty()) + { + if (queuedDisposition.Equals("replied")) + mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Replied; + else if (queuedDisposition.Equals("forward")) + mDraftDisposition = nsIMsgFolder::nsMsgDispositionState_Forwarded; + } + } + } + } + + // If we don't have an original message URI, nothing else to do... + if (!originalMsgURI || *originalMsgURI == 0) + return NS_OK; + + // store the original message URI so we can extract it after we send the message to properly + // mark any disposition flags like replied or forwarded on the message. + if (mOriginalMsgURI.IsEmpty()) + mOriginalMsgURI = originalMsgURI; + + nsCOMPtr<nsIPrefBranch> prefs (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // "Forward inline" and "Reply with template" processing. + // Note the early return at the end of the block. + if (type == nsIMsgCompType::ForwardInline || + type == nsIMsgCompType::ReplyWithTemplate) + { + // Use charset set up in the compose fields by MIME unless we should + // use the default charset. + bool replyInDefault = false; + prefs->GetBoolPref("mailnews.reply_in_default_charset", + &replyInDefault); + // Use send_default_charset if reply_in_default_charset is on. + if (replyInDefault) + { + nsString str; + nsCString charset; + NS_GetLocalizedUnicharPreferenceWithDefault(prefs, "mailnews.send_default_charset", + EmptyString(), str); + if (!str.IsEmpty()) + { + LossyCopyUTF16toASCII(str, charset); + m_compFields->SetCharacterSet(charset.get()); + mAnswerDefaultCharset = true; + } + } + + // We want to treat this message as a reference too + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(originalMsgURI, getter_AddRefs(msgHdr)); + if (NS_SUCCEEDED(rv)) + { + nsAutoCString messageId; + msgHdr->GetMessageId(getter_Copies(messageId)); + + nsAutoCString reference; + // When forwarding we only use the original message for "References:" - + // recipients don't have the other messages anyway. + // For reply with template we want to preserve all the references. + if (type == nsIMsgCompType::ReplyWithTemplate) + { + uint16_t numReferences = 0; + msgHdr->GetNumReferences(&numReferences); + for (int32_t i = 0; i < numReferences; i++) + { + nsAutoCString ref; + msgHdr->GetStringReference(i, ref); + if (!ref.IsEmpty()) + { + reference.AppendLiteral("<"); + reference.Append(ref); + reference.AppendLiteral("> "); + } + } + reference.Trim(" ", false, true); + } + msgHdr->GetMessageId(getter_Copies(messageId)); + reference.AppendLiteral("<"); + reference.Append(messageId); + reference.AppendLiteral(">"); + m_compFields->SetReferences(reference.get()); + } + + // Early return for "Forward inline" and "Reply with template" processing. + return NS_OK; + } + + // All other processing. + char *uriList = PL_strdup(originalMsgURI); + if (!uriList) + return NS_ERROR_OUT_OF_MEMORY; + + // Resulting charset for this message. + nsCString charset; + + // Check for the charset of the last displayed message, it + // will be used for quoting and as override. + nsCString windowCharset; + mCharsetOverride = false; + mAnswerDefaultCharset = false; + GetTopmostMsgWindowCharacterSet(windowCharset, &mCharsetOverride); + if (!windowCharset.IsEmpty()) { + // Although the charset in which to send the message might change, + // the original message will be parsed for quoting using the charset it is + // now displayed with. + mQuoteCharset = windowCharset; + + if (mCharsetOverride) { + // Use override charset. + charset = windowCharset; + } + } + + // Note the following: + // LoadDraftOrTemplate() is run in nsMsgComposeService::OpenComposeWindow() + // for five compose types: ForwardInline, ReplyWithTemplate (both covered + // in the code block above) and Draft, Template and Redirect. For these + // compose types, the charset is already correct (incl. MIME-applied override) + // unless the default charset should be used. + + bool isFirstPass = true; + char *uri = uriList; + char *nextUri; + do + { + nextUri = strstr(uri, "://"); + if (nextUri) + { + // look for next ://, and then back up to previous ',' + nextUri = strstr(nextUri + 1, "://"); + if (nextUri) + { + *nextUri = '\0'; + char *saveNextUri = nextUri; + nextUri = strrchr(uri, ','); + if (nextUri) + *nextUri = '\0'; + *saveNextUri = ':'; + } + } + + nsCOMPtr <nsIMsgDBHdr> msgHdr; + if (mOrigMsgHdr) + msgHdr = mOrigMsgHdr; + else + { + rv = GetMsgDBHdrFromURI(uri, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv,rv); + } + if (msgHdr) + { + nsCString decodedCString; + + bool replyInDefault = false; + prefs->GetBoolPref("mailnews.reply_in_default_charset", + &replyInDefault); + // Use send_default_charset if reply_in_default_charset is on. + if (replyInDefault) + { + nsString str; + NS_GetLocalizedUnicharPreferenceWithDefault(prefs, "mailnews.send_default_charset", + EmptyString(), str); + if (!str.IsEmpty()) { + LossyCopyUTF16toASCII(str, charset); + mAnswerDefaultCharset = true; + } + } + + // Set the charset we determined, if any, in the comp fields. + // For replies, the charset will be set after processing the message + // through MIME in QuotingOutputStreamListener::OnStopRequest(). + if (isFirstPass && !charset.IsEmpty()) + m_compFields->SetCharacterSet(charset.get()); + + nsString subject; + rv = msgHdr->GetMime2DecodedSubject(subject); + if (NS_FAILED(rv)) return rv; + + // Check if (was: is present in the subject + int32_t wasOffset = subject.RFind(NS_LITERAL_STRING(" (was:")); + bool strip = true; + + if (wasOffset >= 0) { + // Check the number of references, to check if was: should be stripped + // First, assume that it should be stripped; the variable will be set to + // false later if stripping should not happen. + uint16_t numRef; + msgHdr->GetNumReferences(&numRef); + if (numRef) { + // If there are references, look for the first message in the thread + // firstly, get the database via the folder + nsCOMPtr<nsIMsgFolder> folder; + msgHdr->GetFolder(getter_AddRefs(folder)); + if (folder) { + nsCOMPtr<nsIMsgDatabase> db; + folder->GetMsgDatabase(getter_AddRefs(db)); + + if (db) { + nsAutoCString reference; + msgHdr->GetStringReference(0, reference); + + nsCOMPtr<nsIMsgDBHdr> refHdr; + db->GetMsgHdrForMessageID(reference.get(), getter_AddRefs(refHdr)); + + if (refHdr) { + nsCString refSubject; + rv = refHdr->GetSubject(getter_Copies(refSubject)); + if (NS_SUCCEEDED(rv)) { + if (refSubject.Find(" (was:") >= 0) + strip = false; + } + } + } + } + } + else + strip = false; + } + + if (strip && wasOffset >= 0) { + // Strip off the "(was: old subject)" part + subject.Assign(Substring(subject, 0, wasOffset)); + } + + switch (type) + { + default: break; + case nsIMsgCompType::Reply : + case nsIMsgCompType::ReplyAll: + case nsIMsgCompType::ReplyToList: + case nsIMsgCompType::ReplyToGroup: + case nsIMsgCompType::ReplyToSender: + case nsIMsgCompType::ReplyToSenderAndGroup: + { + if (!isFirstPass) // safeguard, just in case... + { + PR_Free(uriList); + return rv; + } + mQuotingToFollow = true; + + subject.Insert(NS_LITERAL_STRING("Re: "), 0); + m_compFields->SetSubject(subject); + + // Setup quoting callbacks for later... + mWhatHolder = 1; + break; + } + case nsIMsgCompType::ForwardAsAttachment: + { + // Add the forwarded message in the references, first + nsAutoCString messageId; + msgHdr->GetMessageId(getter_Copies(messageId)); + if (isFirstPass) + { + nsAutoCString reference; + reference.Append(NS_LITERAL_CSTRING("<")); + reference.Append(messageId); + reference.Append(NS_LITERAL_CSTRING(">")); + m_compFields->SetReferences(reference.get()); + } + else + { + nsAutoCString references; + m_compFields->GetReferences(getter_Copies(references)); + references.Append(NS_LITERAL_CSTRING(" <")); + references.Append(messageId); + references.Append(NS_LITERAL_CSTRING(">")); + m_compFields->SetReferences(references.get()); + } + + uint32_t flags; + + msgHdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::HasRe) + subject.Insert(NS_LITERAL_STRING("Re: "), 0); + + // Setup quoting callbacks for later... + mQuotingToFollow = false; //We don't need to quote the original message. + nsCOMPtr<nsIMsgAttachment> attachment = do_CreateInstance(NS_MSGATTACHMENT_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && attachment) + { + bool addExtension = true; + nsString sanitizedSubj; + prefs->GetBoolPref("mail.forward_add_extension", &addExtension); + + // copy subject string to sanitizedSubj, use default if empty + if (subject.IsEmpty()) + { + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> composeBundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", + getter_AddRefs(composeBundle)); + NS_ENSURE_SUCCESS(rv, rv); + composeBundle->GetStringFromName(u"messageAttachmentSafeName", + getter_Copies(sanitizedSubj)); + } + else + sanitizedSubj.Assign(subject); + + // set the file size + uint32_t messageSize; + msgHdr->GetMessageSize(&messageSize); + attachment->SetSize(messageSize); + + // change all '.' to '_' see bug #271211 + MsgReplaceChar(sanitizedSubj, ".", '_'); + if (addExtension) + sanitizedSubj.AppendLiteral(".eml"); + attachment->SetName(sanitizedSubj); + attachment->SetUrl(nsDependentCString(uri)); + m_compFields->AddAttachment(attachment); + } + + if (isFirstPass) + { + nsCString fwdPrefix; + prefs->GetCharPref("mail.forward_subject_prefix", getter_Copies(fwdPrefix)); + if (!fwdPrefix.IsEmpty()) + { + nsString unicodeFwdPrefix; + CopyUTF8toUTF16(fwdPrefix, unicodeFwdPrefix); + unicodeFwdPrefix.AppendLiteral(": "); + subject.Insert(unicodeFwdPrefix, 0); + } + else + { + subject.Insert(NS_LITERAL_STRING("Fwd: "), 0); + } + m_compFields->SetSubject(subject); + } + break; + } + case nsIMsgCompType::Redirect: + { + // For a redirect, set the Reply-To: header to what was in the original From: header... + nsAutoCString author; + msgHdr->GetAuthor(getter_Copies(author)); + m_compFields->SetReplyTo(author.get()); + + // ... and empty out the various recipient headers + nsAutoString empty; + m_compFields->SetTo(empty); + m_compFields->SetCc(empty); + m_compFields->SetBcc(empty); + m_compFields->SetNewsgroups(empty); + m_compFields->SetFollowupTo(empty); + break; + } + } + } + isFirstPass = false; + uri = nextUri + 1; + } + while (nextUri); + PR_Free(uriList); + return rv; +} + +NS_IMETHODIMP nsMsgCompose::GetProgress(nsIMsgProgress **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = mProgress; + NS_IF_ADDREF(*_retval); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetMessageSend(nsIMsgSend **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = mMsgSend; + NS_IF_ADDREF(*_retval); + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::SetMessageSend(nsIMsgSend* aMsgSend) +{ + mMsgSend = aMsgSend; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::ClearMessageSend() +{ + mMsgSend = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::SetCiteReference(nsString citeReference) +{ + mCiteReference = citeReference; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::SetSavedFolderURI(const char *folderURI) +{ + m_folderName = folderURI; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetSavedFolderURI(char ** folderURI) +{ + NS_ENSURE_ARG_POINTER(folderURI); + *folderURI = ToNewCString(m_folderName); + return (*folderURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP nsMsgCompose::GetOriginalMsgURI(char ** originalMsgURI) +{ + NS_ENSURE_ARG_POINTER(originalMsgURI); + *originalMsgURI = ToNewCString(mOriginalMsgURI); + return (*originalMsgURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +//////////////////////////////////////////////////////////////////////////////////// +// THIS IS THE CLASS THAT IS THE STREAM CONSUMER OF THE HTML OUPUT +// FROM LIBMIME. THIS IS FOR QUOTING +//////////////////////////////////////////////////////////////////////////////////// +QuotingOutputStreamListener::~QuotingOutputStreamListener() +{ + if (mUnicodeConversionBuffer) + free(mUnicodeConversionBuffer); +} + +QuotingOutputStreamListener::QuotingOutputStreamListener(const char * originalMsgURI, + nsIMsgDBHdr *originalMsgHdr, + bool quoteHeaders, + bool headersOnly, + nsIMsgIdentity *identity, + nsIMsgQuote* msgQuote, + bool charsetFixed, + bool quoteOriginal, + const nsACString& htmlToQuote) +{ + nsresult rv; + mQuoteHeaders = quoteHeaders; + mHeadersOnly = headersOnly; + mIdentity = identity; + mOrigMsgHdr = originalMsgHdr; + mUnicodeBufferCharacterLength = 0; + mUnicodeConversionBuffer = nullptr; + mQuoteOriginal = quoteOriginal; + mHtmlToQuote = htmlToQuote; + mQuote = msgQuote; + mCharsetFixed = charsetFixed; + + if (!mHeadersOnly || !mHtmlToQuote.IsEmpty()) + { + // Get header type, locale and strings from pref. + int32_t replyHeaderType; + nsAutoString replyHeaderLocale; + nsString replyHeaderAuthorWrote; + nsString replyHeaderOnDateAuthorWrote; + nsString replyHeaderAuthorWroteOnDate; + nsString replyHeaderOriginalmessage; + GetReplyHeaderInfo(&replyHeaderType, + replyHeaderLocale, + replyHeaderAuthorWrote, + replyHeaderOnDateAuthorWrote, + replyHeaderAuthorWroteOnDate, + replyHeaderOriginalmessage); + + // For the built message body... + if (originalMsgHdr && !quoteHeaders) + { + // Setup the cite information.... + nsCString myGetter; + if (NS_SUCCEEDED(originalMsgHdr->GetMessageId(getter_Copies(myGetter)))) + { + if (!myGetter.IsEmpty()) + { + nsAutoCString buf; + mCiteReference.AssignLiteral("mid:"); + MsgEscapeURL(myGetter, + nsINetUtil::ESCAPE_URL_FILE_BASENAME | nsINetUtil::ESCAPE_URL_FORCED, + buf); + mCiteReference.Append(NS_ConvertASCIItoUTF16(buf)); + } + } + + bool citingHeader; //Do we have a header needing to cite any info from original message? + bool headerDate; //Do we have a header needing to cite date/time from original message? + switch (replyHeaderType) + { + case 0: // No reply header at all (actually the "---- original message ----" string, + // which is kinda misleading. TODO: Should there be a "really no header" option? + mCitePrefix.Assign(replyHeaderOriginalmessage); + citingHeader = false; + headerDate = false; + break; + + case 2: // Insert both the original author and date in the reply header (date followed by author) + mCitePrefix.Assign(replyHeaderOnDateAuthorWrote); + citingHeader = true; + headerDate = true; + break; + + case 3: // Insert both the original author and date in the reply header (author followed by date) + mCitePrefix.Assign(replyHeaderAuthorWroteOnDate); + citingHeader = true; + headerDate = true; + break; + + case 4: // TODO bug 107884: implement a more featureful user specified header + case 1: + default: // Default is to only show the author. + mCitePrefix.Assign(replyHeaderAuthorWrote); + citingHeader = true; + headerDate = false; + break; + } + + if (citingHeader) + { + int32_t placeholderIndex = kNotFound; + + if (headerDate) + { + nsCOMPtr<nsIDateTimeFormat> dateFormatter = do_CreateInstance(NS_DATETIMEFORMAT_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + PRTime originalMsgDate; + rv = originalMsgHdr->GetDate(&originalMsgDate); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsILocale> locale; + nsCOMPtr<nsILocaleService> localeService(do_GetService(NS_LOCALESERVICE_CONTRACTID)); + + // Format date using "mailnews.reply_header_locale", if empty then use application default locale. + if (!replyHeaderLocale.IsEmpty()) + rv = localeService->NewLocale(replyHeaderLocale, getter_AddRefs(locale)); + if (NS_SUCCEEDED(rv)) + { + nsAutoString citeDatePart; + if ((placeholderIndex = mCitePrefix.Find("#2")) != kNotFound) + { + rv = dateFormatter->FormatPRTime(locale, + kDateFormatShort, + kTimeFormatNone, + originalMsgDate, + citeDatePart); + if (NS_SUCCEEDED(rv)) + mCitePrefix.Replace(placeholderIndex, 2, citeDatePart); + } + if ((placeholderIndex = mCitePrefix.Find("#3")) != kNotFound) + { + rv = dateFormatter->FormatPRTime(locale, + kDateFormatNone, + kTimeFormatNoSeconds, + originalMsgDate, + citeDatePart); + if (NS_SUCCEEDED(rv)) + mCitePrefix.Replace(placeholderIndex, 2, citeDatePart); + } + } + } + } + } + + if ((placeholderIndex = mCitePrefix.Find("#1")) != kNotFound) + { + nsAutoCString author; + rv = originalMsgHdr->GetAuthor(getter_Copies(author)); + if (NS_SUCCEEDED(rv)) + { + nsAutoString citeAuthor; + ExtractName(EncodedHeader(author), citeAuthor); + mCitePrefix.Replace(placeholderIndex, 2, citeAuthor); + } + } + } + } + + // This should not happen, but just in case. + if (mCitePrefix.IsEmpty()) + { + mCitePrefix.AppendLiteral("\n\n"); + mCitePrefix.Append(replyHeaderOriginalmessage); + mCitePrefix.AppendLiteral("\n"); + } + } +} + +/** + * The formatflowed parameter directs if formatflowed should be used in the conversion. + * format=flowed (RFC 2646) is a way to represent flow in a plain text mail, without + * disturbing the plain text. + */ +nsresult +QuotingOutputStreamListener::ConvertToPlainText(bool formatflowed, + bool delsp, + bool formatted, + bool disallowBreaks) +{ + nsresult rv = ConvertBufToPlainText(mMsgBody, formatflowed, + delsp, + formatted, + disallowBreaks); + NS_ENSURE_SUCCESS (rv, rv); + return ConvertBufToPlainText(mSignature, formatflowed, + delsp, + formatted, + disallowBreaks); +} + +NS_IMETHODIMP QuotingOutputStreamListener::OnStartRequest(nsIRequest *request, nsISupports * /* ctxt */) +{ + return NS_OK; +} + +NS_IMETHODIMP QuotingOutputStreamListener::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) +{ + nsresult rv = NS_OK; + + if (!mHtmlToQuote.IsEmpty()) + { + // If we had a selection in the original message to quote, we can add + // it now that we are done ignoring the original body of the message + mHeadersOnly = false; + rv = AppendToMsgBody(mHtmlToQuote); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIMsgCompose> compose = do_QueryReferent(mWeakComposeObj); + NS_ENSURE_TRUE(compose, NS_ERROR_NULL_POINTER); + + MSG_ComposeType type; + compose->GetType(&type); + + // Assign cite information if available... + if (!mCiteReference.IsEmpty()) + compose->SetCiteReference(mCiteReference); + + bool overrideReplyTo = + mozilla::Preferences::GetBool("mail.override_list_reply_to", true); + + if (mHeaders && (type == nsIMsgCompType::Reply || + type == nsIMsgCompType::ReplyAll || + type == nsIMsgCompType::ReplyToList || + type == nsIMsgCompType::ReplyToSender || + type == nsIMsgCompType::ReplyToGroup || + type == nsIMsgCompType::ReplyToSenderAndGroup) && + mQuoteOriginal) + { + nsCOMPtr<nsIMsgCompFields> compFields; + compose->GetCompFields(getter_AddRefs(compFields)); + if (compFields) + { + nsAutoString from; + nsAutoString to; + nsAutoString cc; + nsAutoString bcc; + nsAutoString replyTo; + nsAutoString mailReplyTo; + nsAutoString mailFollowupTo; + nsAutoString newgroups; + nsAutoString followUpTo; + nsAutoString messageId; + nsAutoString references; + nsAutoString listPost; + + nsCString outCString; // Temp helper string. + + bool needToRemoveDup = false; + if (!mMimeConverter) + { + mMimeConverter = do_GetService(NS_MIME_CONVERTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + nsCString charset; + compFields->GetCharacterSet(getter_Copies(charset)); + + if (!mCharsetFixed) { + // Get the charset from the channel where MIME left it. + if (mQuote) { + nsCOMPtr<nsIChannel> quoteChannel; + mQuote->GetQuoteChannel(getter_AddRefs(quoteChannel)); + if (quoteChannel) { + quoteChannel->GetContentCharset(charset); + if (!charset.IsEmpty()) { + rv = fixCharset(charset); + NS_ENSURE_SUCCESS(rv, rv); + compFields->SetCharacterSet(charset.get()); + } + } + } + } + + mHeaders->ExtractHeader(HEADER_FROM, true, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), from); + + mHeaders->ExtractHeader(HEADER_TO, true, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), to); + + mHeaders->ExtractHeader(HEADER_CC, true, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), cc); + + mHeaders->ExtractHeader(HEADER_BCC, true, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), bcc); + + mHeaders->ExtractHeader(HEADER_MAIL_FOLLOWUP_TO, true, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), mailFollowupTo); + + mHeaders->ExtractHeader(HEADER_REPLY_TO, false, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), replyTo); + + mHeaders->ExtractHeader(HEADER_MAIL_REPLY_TO, true, outCString); + ConvertRawBytesToUTF16(outCString, charset.get(), mailReplyTo); + + mHeaders->ExtractHeader(HEADER_NEWSGROUPS, false, outCString); + if (!outCString.IsEmpty()) + mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), + false, true, newgroups); + + mHeaders->ExtractHeader(HEADER_FOLLOWUP_TO, false, outCString); + if (!outCString.IsEmpty()) + mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), + false, true, followUpTo); + + mHeaders->ExtractHeader(HEADER_MESSAGE_ID, false, outCString); + if (!outCString.IsEmpty()) + mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), + false, true, messageId); + + mHeaders->ExtractHeader(HEADER_REFERENCES, false, outCString); + if (!outCString.IsEmpty()) + mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), + false, true, references); + + mHeaders->ExtractHeader(HEADER_LIST_POST, true, outCString); + if (!outCString.IsEmpty()) + mMimeConverter->DecodeMimeHeader(outCString.get(), charset.get(), + false, true, listPost); + if (!listPost.IsEmpty()) + { + int32_t startPos = listPost.Find("<mailto:"); + int32_t endPos = listPost.FindChar('>', startPos); + // Extract the e-mail address. + if (endPos > startPos) + { + const uint32_t mailtoLen = strlen("<mailto:"); + listPost = Substring(listPost, startPos + mailtoLen, endPos - (startPos + mailtoLen)); + } + } + + nsCString fromEmailAddress; + ExtractEmail(EncodedHeader(NS_ConvertUTF16toUTF8(from)), fromEmailAddress); + + nsTArray<nsCString> toEmailAddresses; + ExtractEmails(EncodedHeader(NS_ConvertUTF16toUTF8(to)), + UTF16ArrayAdapter<>(toEmailAddresses)); + + nsTArray<nsCString> ccEmailAddresses; + ExtractEmails(EncodedHeader(NS_ConvertUTF16toUTF8(cc)), + UTF16ArrayAdapter<>(ccEmailAddresses)); + + nsCOMPtr<nsIPrefBranch> prefs (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + bool replyToSelfCheckAll = false; + prefs->GetBoolPref("mailnews.reply_to_self_check_all_ident", + &replyToSelfCheckAll); + + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIArray> identities; + nsCString accountKey; + mOrigMsgHdr->GetAccountKey(getter_Copies(accountKey)); + if (replyToSelfCheckAll) + { + // Check all avaliable identities if the pref was set. + accountManager->GetAllIdentities(getter_AddRefs(identities)); + } + else if (!accountKey.IsEmpty()) + { + // Check headers to see which account the message came in from + // (only works for pop3). + nsCOMPtr<nsIMsgAccount> account; + accountManager->GetAccount(accountKey, getter_AddRefs(account)); + + if (account) + account->GetIdentities(getter_AddRefs(identities)); + } + else + { + // Check identities only for the server of the folder that the message + // is in. + nsCOMPtr <nsIMsgFolder> msgFolder; + rv = mOrigMsgHdr->GetFolder(getter_AddRefs(msgFolder)); + + if (NS_SUCCEEDED(rv) && msgFolder){ + nsCOMPtr<nsIMsgIncomingServer> nsIMsgIncomingServer; + rv = msgFolder->GetServer(getter_AddRefs(nsIMsgIncomingServer)); + + if (NS_SUCCEEDED(rv) && nsIMsgIncomingServer) + accountManager->GetIdentitiesForServer(nsIMsgIncomingServer, getter_AddRefs(identities)); + } + } + + bool isReplyToSelf = false; + nsCOMPtr<nsIMsgIdentity> selfIdentity; + if (identities) + { + // Go through the identities to see if any of them is the author of + // the email. + nsCOMPtr<nsIMsgIdentity> lookupIdentity; + + uint32_t count = 0; + identities->GetLength(&count); + + for (uint32_t i = 0; i < count; i++) + { + lookupIdentity = do_QueryElementAt(identities, i, &rv); + if (NS_FAILED(rv)) + continue; + + selfIdentity = lookupIdentity; + + nsCString curIdentityEmail; + lookupIdentity->GetEmail(curIdentityEmail); + + // See if it's a reply to own message, but not a reply between identities. + if (curIdentityEmail.Equals(fromEmailAddress)) + { + isReplyToSelf = true; + // For a true reply-to-self, none of your identities are normally in + // To or Cc. We need to avoid doing a reply-to-self for people that + // have multiple identities set and sometimes *uses* the other + // identity and sometimes *mails* the other identity. + // E.g. husband+wife or own-email+company-role-mail. + for (uint32_t j = 0; j < count; j++) + { + nsCOMPtr<nsIMsgIdentity> lookupIdentity2; + rv = identities->QueryElementAt(j, NS_GET_IID(nsIMsgIdentity), + getter_AddRefs(lookupIdentity2)); + if (NS_FAILED(rv)) + continue; + + nsCString curIdentityEmail2; + lookupIdentity2->GetEmail(curIdentityEmail2); + if (toEmailAddresses.Contains(curIdentityEmail2)) + { + // However, "From:me To:me" should be treated as + // reply-to-self if we have a Bcc. If we don't have a Bcc we + // might have the case of a generated mail of the style + // "From:me To:me Reply-To:customer". Then we need to to do a + // normal reply to the customer. + isReplyToSelf = !bcc.IsEmpty(); // true if bcc is set + break; + } + else if (ccEmailAddresses.Contains(curIdentityEmail2)) + { + // If you auto-Cc yourself your email would be in Cc - but we + // can't detect why it is in Cc so lets just treat it like a + // normal reply. + isReplyToSelf = false; + break; + } + } + break; + } + } + } + if (type == nsIMsgCompType::ReplyToSender || type == nsIMsgCompType::Reply) + { + if (isReplyToSelf) + { + // Cast to concrete class. We *only* what to change m_identity, not + // all the things compose->SetIdentity would do. + nsMsgCompose* _compose = static_cast<nsMsgCompose*>(compose.get()); + _compose->m_identity = selfIdentity; + compFields->SetFrom(from); + compFields->SetTo(to); + compFields->SetReplyTo(replyTo); + } + else if (!mailReplyTo.IsEmpty()) + { + // handle Mail-Reply-To (http://cr.yp.to/proto/replyto.html) + compFields->SetTo(mailReplyTo); + needToRemoveDup = true; + } + else if (!replyTo.IsEmpty()) + { + // default reply behaviour then + + if (overrideReplyTo && + !listPost.IsEmpty() && replyTo.Find(listPost) != kNotFound) + { + // Reply-To munging in this list post. Reply to From instead, + // as the user can choose Reply List if that's what he wants. + compFields->SetTo(from); + } + else + { + compFields->SetTo(replyTo); + } + needToRemoveDup = true; + } + else { + compFields->SetTo(from); + } + } + else if (type == nsIMsgCompType::ReplyAll) + { + if (isReplyToSelf) + { + // Cast to concrete class. We *only* what to change m_identity, not + // all the things compose->SetIdentity would do. + nsMsgCompose* _compose = static_cast<nsMsgCompose*>(compose.get()); + _compose->m_identity = selfIdentity; + compFields->SetFrom(from); + compFields->SetTo(to); + compFields->SetCc(cc); + // In case it's a reply to self, but it's not the actual source of the + // sent message, then we won't know the Bcc header. So set it only if + // it's not empty. If you have auto-bcc and removed the auto-bcc for + // the original mail, you will have to do it manually for this reply + // too. + if (!bcc.IsEmpty()) + compFields->SetBcc(bcc); + compFields->SetReplyTo(replyTo); + needToRemoveDup = true; + } + else if (mailFollowupTo.IsEmpty()) { + // default reply-all behaviour then + + nsAutoString allTo; + if (!replyTo.IsEmpty()) + { + allTo.Assign(replyTo); + needToRemoveDup = true; + if (overrideReplyTo && + !listPost.IsEmpty() && replyTo.Find(listPost) != kNotFound) + { + // Reply-To munging in this list. Add From to recipients, it's the + // lesser evil... + allTo.AppendLiteral(", "); + allTo.Append(from); + } + } + else + { + allTo.Assign(from); + } + + allTo.AppendLiteral(", "); + allTo.Append(to); + compFields->SetTo(allTo); + + nsAutoString allCc; + compFields->GetCc(allCc); // auto-cc + if (!allCc.IsEmpty()) + allCc.AppendLiteral(", "); + allCc.Append(cc); + compFields->SetCc(allCc); + + needToRemoveDup = true; + } + else + { + // Handle Mail-Followup-To (http://cr.yp.to/proto/replyto.html) + compFields->SetTo(mailFollowupTo); + needToRemoveDup = true; // To remove possible self from To. + + // If Cc is set a this point it's auto-Ccs, so we'll just keep those. + } + } + else if (type == nsIMsgCompType::ReplyToList) + { + compFields->SetTo(listPost); + } + + if (!newgroups.IsEmpty()) + { + if ((type != nsIMsgCompType::Reply) && (type != nsIMsgCompType::ReplyToSender)) + compFields->SetNewsgroups(newgroups); + if (type == nsIMsgCompType::ReplyToGroup) + compFields->SetTo(EmptyString()); + } + + if (!followUpTo.IsEmpty()) + { + // Handle "followup-to: poster" magic keyword here + if (followUpTo.EqualsLiteral("poster")) + { + nsCOMPtr<mozIDOMWindowProxy> domWindow; + nsCOMPtr<nsIPrompt> prompt; + compose->GetDomWindow(getter_AddRefs(domWindow)); + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + nsCOMPtr<nsPIDOMWindowOuter> composeWindow = nsPIDOMWindowOuter::From(domWindow); + if (composeWindow) + composeWindow->GetPrompter(getter_AddRefs(prompt)); + nsMsgDisplayMessageByName(prompt, u"followupToSenderMessage"); + + if (!replyTo.IsEmpty()) + { + compFields->SetTo(replyTo); + } + else + { + // If reply-to is empty, use the From header to fetch the original + // sender's email. + compFields->SetTo(from); + } + + // Clear the newsgroup: header field, because followup-to: poster + // only follows up to the original sender + if (!newgroups.IsEmpty()) + compFields->SetNewsgroups(EmptyString()); + } + else // Process "followup-to: newsgroup-content" here + { + if (type != nsIMsgCompType::ReplyToSender) + compFields->SetNewsgroups(followUpTo); + if (type == nsIMsgCompType::Reply) + { + compFields->SetTo(EmptyString()); + } + } + } + + if (!references.IsEmpty()) + references.Append(char16_t(' ')); + references += messageId; + compFields->SetReferences(NS_LossyConvertUTF16toASCII(references).get()); + + nsAutoCString resultStr; + + // Cast interface to concrete class that has direct field getters etc. + nsMsgCompFields* _compFields = static_cast<nsMsgCompFields*>(compFields.get()); + + // Remove duplicate addresses between To && Cc. + if (needToRemoveDup) + { + nsCString addressesToRemoveFromCc; + if (mIdentity) + { + bool removeMyEmailInCc = true; + nsCString myEmail; + mIdentity->GetEmail(myEmail); + + // Remove my own address from To, unless it's a reply to self. + if (!isReplyToSelf) { + RemoveDuplicateAddresses(nsDependentCString(_compFields->GetTo()), + myEmail, resultStr); + _compFields->SetTo(resultStr.get()); + } + addressesToRemoveFromCc.Assign(_compFields->GetTo()); + + // Remove own address from CC unless we want it in there + // through the automatic-CC-to-self (see bug 584962). There are + // three cases: + // - user has no automatic CC + // - user has automatic CC but own email is not in it + // - user has automatic CC and own email in it + // Only in the last case do we want our own email address to stay + // in the CC list. + bool automaticCc; + mIdentity->GetDoCc(&automaticCc); + if (automaticCc) + { + nsCString autoCcList; + mIdentity->GetDoCcList(autoCcList); + nsTArray<nsCString> autoCcEmailAddresses; + ExtractEmails(EncodedHeader(autoCcList), + UTF16ArrayAdapter<>(autoCcEmailAddresses)); + if (autoCcEmailAddresses.Contains(myEmail)) + { + removeMyEmailInCc = false; + } + } + + if (removeMyEmailInCc) + { + addressesToRemoveFromCc.AppendLiteral(", "); + addressesToRemoveFromCc.Append(myEmail); + } + } + RemoveDuplicateAddresses(nsDependentCString(_compFields->GetCc()), + addressesToRemoveFromCc, resultStr); + _compFields->SetCc(resultStr.get()); + if (_compFields->GetBcc()) + { + // Remove addresses already in Cc from Bcc. + RemoveDuplicateAddresses(nsDependentCString(_compFields->GetBcc()), + nsDependentCString(_compFields->GetCc()), + resultStr); + if (!resultStr.IsEmpty()) + { + // Remove addresses already in To from Bcc. + RemoveDuplicateAddresses(resultStr, + nsDependentCString(_compFields->GetTo()), + resultStr); + } + _compFields->SetBcc(resultStr.get()); + } + } + } + } + +#ifdef MSGCOMP_TRACE_PERFORMANCE + nsCOMPtr<nsIMsgComposeService> composeService (do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID)); + composeService->TimeStamp("Done with MIME. Now we're updating the UI elements", false); +#endif + + if (mQuoteOriginal) + compose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeFieldsReady, NS_OK); + +#ifdef MSGCOMP_TRACE_PERFORMANCE + composeService->TimeStamp("Addressing widget, window title and focus are now set, time to insert the body", false); +#endif + + if (! mHeadersOnly) + mMsgBody.AppendLiteral("</html>"); + + // Now we have an HTML representation of the quoted message. + // If we are in plain text mode, we need to convert this to plain + // text before we try to insert it into the editor. If we don't, we + // just get lots of HTML text in the message...not good. + // + // XXX not m_composeHTML? /BenB + bool composeHTML = true; + compose->GetComposeHTML(&composeHTML); + if (!composeHTML) + { + // Downsampling. + + // In plain text quotes we always allow line breaking to not end up with + // long lines. The quote is inserted into a span with style + // "white-space: pre;" which isn't be wrapped. + // Update: Bug 387687 changed this to "white-space: pre-wrap;". + // Note that the body of the plain text message is wrapped since it uses + // "white-space: pre-wrap; width: 72ch;". + // Look at it in the DOM Inspector to see it. + // + // If we're using format flowed, we need to pass it so the encoder + // can add a space at the end. + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + bool flowed = false; + if (pPrefBranch) { + pPrefBranch->GetBoolPref("mailnews.send_plaintext_flowed", &flowed); + } + + rv = ConvertToPlainText(flowed, + false, // delsp makes no sense in this context + true, // formatted + false); // allow line breaks + NS_ENSURE_SUCCESS(rv, rv); + } + + compose->ProcessSignature(mIdentity, true, &mSignature); + + nsCOMPtr<nsIEditor> editor; + if (NS_SUCCEEDED(compose->GetEditor(getter_AddRefs(editor))) && editor) + { + if (mQuoteOriginal) + compose->ConvertAndLoadComposeWindow(mCitePrefix, + mMsgBody, mSignature, + true, composeHTML); + else + InsertToCompose(editor, composeHTML); + } + + if (mQuoteOriginal) + compose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeBodyReady, NS_OK); + return rv; +} + +NS_IMETHODIMP QuotingOutputStreamListener::OnDataAvailable(nsIRequest *request, + nsISupports *ctxt, nsIInputStream *inStr, + uint64_t sourceOffset, uint32_t count) +{ + nsresult rv = NS_OK; + NS_ENSURE_ARG(inStr); + + if (mHeadersOnly) + return rv; + + char *newBuf = (char *)PR_Malloc(count + 1); + if (!newBuf) + return NS_ERROR_FAILURE; + + uint32_t numWritten = 0; + rv = inStr->Read(newBuf, count, &numWritten); + if (rv == NS_BASE_STREAM_WOULD_BLOCK) + rv = NS_OK; + newBuf[numWritten] = '\0'; + if (NS_SUCCEEDED(rv) && numWritten > 0) + { + rv = AppendToMsgBody(nsDependentCString(newBuf, numWritten)); + } + + PR_FREEIF(newBuf); + return rv; +} + +NS_IMETHODIMP QuotingOutputStreamListener::AppendToMsgBody(const nsCString &inStr) +{ + nsresult rv = NS_OK; + + if (!inStr.IsEmpty()) + { + // Create unicode decoder. + if (!mUnicodeDecoder) + { + nsCOMPtr<nsICharsetConverterManager> ccm = + do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + rv = ccm->GetUnicodeDecoderRaw("UTF-8", + getter_AddRefs(mUnicodeDecoder)); + } + } + + if (NS_SUCCEEDED(rv)) + { + int32_t unicharLength; + int32_t inputLength = inStr.Length(); + rv = mUnicodeDecoder->GetMaxLength(inStr.get(), inStr.Length(), &unicharLength); + if (NS_SUCCEEDED(rv)) + { + // Use this local buffer if possible. + const int32_t kLocalBufSize = 4096; + char16_t localBuf[kLocalBufSize]; + char16_t *unichars = localBuf; + + if (unicharLength > kLocalBufSize) + { + // Otherwise, use the buffer of the class. + if (!mUnicodeConversionBuffer || + unicharLength > mUnicodeBufferCharacterLength) + { + if (mUnicodeConversionBuffer) + free(mUnicodeConversionBuffer); + mUnicodeConversionBuffer = (char16_t *) moz_xmalloc(unicharLength * sizeof(char16_t)); + if (!mUnicodeConversionBuffer) + { + mUnicodeBufferCharacterLength = 0; + return NS_ERROR_OUT_OF_MEMORY; + } + mUnicodeBufferCharacterLength = unicharLength; + } + unichars = mUnicodeConversionBuffer; + } + + int32_t consumedInputLength = 0; + int32_t originalInputLength = inputLength; + const char *inputBuffer = inStr.get(); + int32_t convertedOutputLength = 0; + int32_t outputBufferLength = unicharLength; + char16_t *originalOutputBuffer = unichars; + do + { + rv = mUnicodeDecoder->Convert(inputBuffer, &inputLength, unichars, &unicharLength); + if (NS_SUCCEEDED(rv)) + { + convertedOutputLength += unicharLength; + break; + } + + // if we failed, we consume one byte, replace it with a question mark + // and try the conversion again. + unichars += unicharLength; + *unichars = (char16_t)'?'; + unichars++; + unicharLength++; + + mUnicodeDecoder->Reset(); + + inputBuffer += ++inputLength; + consumedInputLength += inputLength; + inputLength = originalInputLength - consumedInputLength; // update input length to convert + convertedOutputLength += unicharLength; + unicharLength = outputBufferLength - unicharLength; // update output length + + } while (NS_FAILED(rv) && + (originalInputLength > consumedInputLength) && + (outputBufferLength > convertedOutputLength)); + + if (convertedOutputLength > 0) + mMsgBody.Append(originalOutputBuffer, convertedOutputLength); + } + } + } + + return rv; +} + +nsresult +QuotingOutputStreamListener::SetComposeObj(nsIMsgCompose *obj) +{ + mWeakComposeObj = do_GetWeakReference(obj); + return NS_OK; +} + +nsresult +QuotingOutputStreamListener::SetMimeHeaders(nsIMimeHeaders * headers) +{ + mHeaders = headers; + return NS_OK; +} + +NS_IMETHODIMP +QuotingOutputStreamListener::InsertToCompose(nsIEditor *aEditor, + bool aHTMLEditor) +{ + // First, get the nsIEditor interface for future use + nsCOMPtr<nsIDOMNode> nodeInserted; + + TranslateLineEnding(mMsgBody); + + // Now, insert it into the editor... + if (aEditor) + aEditor->EnableUndo(true); + + nsCOMPtr<nsIMsgCompose> compose = do_QueryReferent(mWeakComposeObj); + if (!mMsgBody.IsEmpty() && compose) + { + compose->SetInsertingQuotedContent(true); + if (!mCitePrefix.IsEmpty()) + { + if (!aHTMLEditor) + mCitePrefix.AppendLiteral("\n"); + nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(aEditor)); + if (textEditor) + textEditor->InsertText(mCitePrefix); + } + + nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(aEditor)); + if (mailEditor) + { + if (aHTMLEditor) { + nsAutoString body(mMsgBody); + remove_plaintext_tag(body); + mailEditor->InsertAsCitedQuotation(body, EmptyString(), true, + getter_AddRefs(nodeInserted)); + } else { + mailEditor->InsertAsQuotation(mMsgBody, getter_AddRefs(nodeInserted)); + } + } + compose->SetInsertingQuotedContent(false); + } + + if (aEditor) + { + nsCOMPtr<nsIPlaintextEditor> textEditor = do_QueryInterface(aEditor); + if (textEditor) + { + nsCOMPtr<nsISelection> selection; + nsCOMPtr<nsIDOMNode> parent; + int32_t offset; + nsresult rv; + + // get parent and offset of mailcite + rv = GetNodeLocation(nodeInserted, address_of(parent), &offset); + NS_ENSURE_SUCCESS(rv, rv); + + // get selection + aEditor->GetSelection(getter_AddRefs(selection)); + if (selection) + { + // place selection after mailcite + selection->Collapse(parent, offset+1); + // insert a break at current selection + textEditor->InsertLineBreak(); + selection->Collapse(parent, offset+1); + } + nsCOMPtr<nsISelectionController> selCon; + aEditor->GetSelectionController(getter_AddRefs(selCon)); + + if (selCon) + // After ScrollSelectionIntoView(), the pending notifications might be + // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. + selCon->ScrollSelectionIntoView( + nsISelectionController::SELECTION_NORMAL, + nsISelectionController::SELECTION_ANCHOR_REGION, + true); + } + } + + return NS_OK; +} + +/** + * Returns true if the domain is a match for the given the domain list. + * Subdomains are also considered to match. + * @param aDomain - the domain name to check + * @param aDomainList - a comman separated string of domain names + */ +bool IsInDomainList(const nsAString &aDomain, const nsAString &aDomainList) +{ + if (aDomain.IsEmpty() || aDomainList.IsEmpty()) + return false; + + // Check plain text domains. + int32_t left = 0; + int32_t right = 0; + while (right != (int32_t)aDomainList.Length()) + { + right = aDomainList.FindChar(',', left); + if (right == kNotFound) + right = aDomainList.Length(); + nsDependentSubstring domain = Substring(aDomainList, left, right); + + if (aDomain.Equals(domain, nsCaseInsensitiveStringComparator())) + return true; + + nsAutoString dotDomain; + dotDomain.Assign(NS_LITERAL_STRING(".")); + dotDomain.Append(domain); + if (StringEndsWith(aDomain, dotDomain, nsCaseInsensitiveStringComparator())) + return true; + + left = right + 1; + } + return false; +} + +NS_IMPL_ISUPPORTS(QuotingOutputStreamListener, + nsIMsgQuotingOutputStreamListener, + nsIRequestObserver, + nsIStreamListener) + +//////////////////////////////////////////////////////////////////////////////////// +// END OF QUOTING LISTENER +//////////////////////////////////////////////////////////////////////////////////// + +/* attribute MSG_ComposeType type; */ +NS_IMETHODIMP nsMsgCompose::SetType(MSG_ComposeType aType) +{ + + mType = aType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetType(MSG_ComposeType *aType) +{ + NS_ENSURE_ARG_POINTER(aType); + + *aType = mType; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCompose::QuoteMessage(const char *msgURI) +{ + NS_ENSURE_ARG_POINTER(msgURI); + + nsresult rv; + mQuotingToFollow = false; + + // Create a mime parser (nsIStreamConverter)! + mQuote = do_CreateInstance(NS_MSGQUOTE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(msgURI, getter_AddRefs(msgHdr)); + + // Create the consumer output stream.. this will receive all the HTML from libmime + mQuoteStreamListener = + new QuotingOutputStreamListener(msgURI, + msgHdr, + false, + !mHtmlToQuote.IsEmpty(), + m_identity, + mQuote, + mCharsetOverride || mAnswerDefaultCharset, + false, + mHtmlToQuote); + + if (!mQuoteStreamListener) + return NS_ERROR_FAILURE; + NS_ADDREF(mQuoteStreamListener); + + mQuoteStreamListener->SetComposeObj(this); + + rv = mQuote->QuoteMessage(msgURI, false, mQuoteStreamListener, + mCharsetOverride ? m_compFields->GetCharacterSet() : "", + false, msgHdr); + return rv; +} + +nsresult +nsMsgCompose::QuoteOriginalMessage() // New template +{ + nsresult rv; + + mQuotingToFollow = false; + + // Create a mime parser (nsIStreamConverter)! + mQuote = do_CreateInstance(NS_MSGQUOTE_CONTRACTID, &rv); + if (NS_FAILED(rv) || !mQuote) + return NS_ERROR_FAILURE; + + bool bAutoQuote = true; + m_identity->GetAutoQuote(&bAutoQuote); + + nsCOMPtr <nsIMsgDBHdr> originalMsgHdr = mOrigMsgHdr; + if (!originalMsgHdr) + { + rv = GetMsgDBHdrFromURI(mOriginalMsgURI.get(), getter_AddRefs(originalMsgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + } + + bool fileUrl = StringBeginsWith(mOriginalMsgURI, NS_LITERAL_CSTRING("file:")); + if (fileUrl) + { + mOriginalMsgURI.Replace(0, 5, NS_LITERAL_CSTRING("mailbox:")); + mOriginalMsgURI.AppendLiteral("?number=0"); + } + + // Create the consumer output stream.. this will receive all the HTML from libmime + mQuoteStreamListener = + new QuotingOutputStreamListener(mOriginalMsgURI.get(), + originalMsgHdr, + mWhatHolder != 1, + !bAutoQuote || !mHtmlToQuote.IsEmpty(), + m_identity, + mQuote, + mCharsetOverride || mAnswerDefaultCharset, + true, + mHtmlToQuote); + + if (!mQuoteStreamListener) + return NS_ERROR_FAILURE; + NS_ADDREF(mQuoteStreamListener); + + mQuoteStreamListener->SetComposeObj(this); + + rv = mQuote->QuoteMessage(mOriginalMsgURI.get(), mWhatHolder != 1, mQuoteStreamListener, + mCharsetOverride ? mQuoteCharset.get() : "", + !bAutoQuote, originalMsgHdr); + return rv; +} + +//CleanUpRecipient will remove un-necessary "<>" when a recipient as an address without name +void nsMsgCompose::CleanUpRecipients(nsString& recipients) +{ + uint16_t i; + bool startANewRecipient = true; + bool removeBracket = false; + nsAutoString newRecipient; + char16_t aChar; + + for (i = 0; i < recipients.Length(); i ++) + { + aChar = recipients[i]; + switch (aChar) + { + case '<' : + if (startANewRecipient) + removeBracket = true; + else + newRecipient += aChar; + startANewRecipient = false; + break; + + case '>' : + if (removeBracket) + removeBracket = false; + else + newRecipient += aChar; + break; + + case ' ' : + newRecipient += aChar; + break; + + case ',' : + newRecipient += aChar; + startANewRecipient = true; + removeBracket = false; + break; + + default : + newRecipient += aChar; + startANewRecipient = false; + break; + } + } + recipients = newRecipient; +} + +NS_IMETHODIMP nsMsgCompose::RememberQueuedDisposition() +{ + // need to find the msg hdr in the saved folder and then set a property on + // the header that we then look at when we actually send the message. + nsresult rv; + nsAutoCString dispositionSetting; + + if (mType == nsIMsgCompType::Reply || + mType == nsIMsgCompType::ReplyAll || + mType == nsIMsgCompType::ReplyToList || + mType == nsIMsgCompType::ReplyToGroup || + mType == nsIMsgCompType::ReplyToSender || + mType == nsIMsgCompType::ReplyToSenderAndGroup) + { + dispositionSetting.AssignLiteral("replied"); + } + else if (mType == nsIMsgCompType::ForwardAsAttachment || + mType == nsIMsgCompType::ForwardInline) + { + dispositionSetting.AssignLiteral("forwarded"); + } + else if (mType == nsIMsgCompType::Draft) + { + nsAutoCString curDraftIdURL; + rv = m_compFields->GetDraftId(getter_Copies(curDraftIdURL)); + NS_ENSURE_SUCCESS(rv, rv); + if (!curDraftIdURL.IsEmpty()) { + nsCOMPtr <nsIMsgDBHdr> draftHdr; + rv = GetMsgDBHdrFromURI(curDraftIdURL.get(), getter_AddRefs(draftHdr)); + NS_ENSURE_SUCCESS(rv, rv); + draftHdr->GetStringProperty(QUEUED_DISPOSITION_PROPERTY, getter_Copies(dispositionSetting)); + } + } + + nsMsgKey msgKey; + if (mMsgSend) + { + mMsgSend->GetMessageKey(&msgKey); + nsAutoCString msgUri(m_folderName); + nsCString identityKey; + + m_identity->GetKey(identityKey); + + int32_t insertIndex = StringBeginsWith(msgUri, NS_LITERAL_CSTRING("mailbox")) ? 7 : 4; + msgUri.Insert("-message", insertIndex); // "mailbox/imap: -> "mailbox/imap-message:" + msgUri.Append('#'); + msgUri.AppendInt(msgKey); + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(msgUri.get(), getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t pseudoHdrProp = 0; + msgHdr->GetUint32Property("pseudoHdr", &pseudoHdrProp); + if (pseudoHdrProp) + { + // Use SetAttributeOnPendingHdr for IMAP pseudo headers, as those + // will get deleted (and properties set using SetStringProperty lost.) + nsCOMPtr<nsIMsgFolder> folder; + rv = msgHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv,rv); + nsCOMPtr<nsIMsgDatabase> msgDB; + rv = folder->GetMsgDatabase(getter_AddRefs(msgDB)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCString messageId; + mMsgSend->GetMessageId(messageId); + msgHdr->SetMessageId(messageId.get()); + if (!mOriginalMsgURI.IsEmpty()) + { + msgDB->SetAttributeOnPendingHdr(msgHdr, ORIG_URI_PROPERTY, mOriginalMsgURI.get()); + if (!dispositionSetting.IsEmpty()) + msgDB->SetAttributeOnPendingHdr(msgHdr, QUEUED_DISPOSITION_PROPERTY, + dispositionSetting.get()); + } + msgDB->SetAttributeOnPendingHdr(msgHdr, HEADER_X_MOZILLA_IDENTITY_KEY, identityKey.get()); + } + else if (msgHdr) + { + if (!mOriginalMsgURI.IsEmpty()) + { + msgHdr->SetStringProperty(ORIG_URI_PROPERTY, mOriginalMsgURI.get()); + if (!dispositionSetting.IsEmpty()) + msgHdr->SetStringProperty(QUEUED_DISPOSITION_PROPERTY, dispositionSetting.get()); + } + msgHdr->SetStringProperty(HEADER_X_MOZILLA_IDENTITY_KEY, identityKey.get()); + } + } + return NS_OK; +} + +nsresult nsMsgCompose::ProcessReplyFlags() +{ + nsresult rv; + // check to see if we were doing a reply or a forward, if we were, set the answered field flag on the message folder + // for this URI. + if (mType == nsIMsgCompType::Reply || + mType == nsIMsgCompType::ReplyAll || + mType == nsIMsgCompType::ReplyToList || + mType == nsIMsgCompType::ReplyToGroup || + mType == nsIMsgCompType::ReplyToSender || + mType == nsIMsgCompType::ReplyToSenderAndGroup || + mType == nsIMsgCompType::ForwardAsAttachment || + mType == nsIMsgCompType::ForwardInline || + mDraftDisposition != nsIMsgFolder::nsMsgDispositionState_None) + { + if (!mOriginalMsgURI.IsEmpty()) + { + nsCString msgUri (mOriginalMsgURI); + char *newStr = msgUri.BeginWriting(); + char *uri; + while (nullptr != (uri = NS_strtok(",", &newStr))) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(uri, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv,rv); + if (msgHdr) + { + // get the folder for the message resource + nsCOMPtr<nsIMsgFolder> msgFolder; + msgHdr->GetFolder(getter_AddRefs(msgFolder)); + if (msgFolder) + { + // If it's a draft with disposition, default to replied, otherwise, + // check if it's a forward. + nsMsgDispositionState dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Replied; + if (mDraftDisposition != nsIMsgFolder::nsMsgDispositionState_None) + dispositionSetting = mDraftDisposition; + else if (mType == nsIMsgCompType::ForwardAsAttachment || + mType == nsIMsgCompType::ForwardInline) + dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Forwarded; + + msgFolder->AddMessageDispositionState(msgHdr, dispositionSetting); + if (mType != nsIMsgCompType::ForwardAsAttachment) + break; // just safeguard + } + } + } + } + } + + return NS_OK; +} +NS_IMETHODIMP nsMsgCompose::OnStartSending(const char *aMsgID, uint32_t aMsgSize) +{ + nsTObserverArray<nsCOMPtr<nsIMsgSendListener> >::ForwardIterator iter(mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) + { + externalSendListener = iter.GetNext(); + externalSendListener->OnStartSending(aMsgID, aMsgSize); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::OnProgress(const char *aMsgID, uint32_t aProgress, uint32_t aProgressMax) +{ + nsTObserverArray<nsCOMPtr<nsIMsgSendListener> >::ForwardIterator iter(mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) + { + externalSendListener = iter.GetNext(); + externalSendListener->OnProgress(aMsgID, aProgress, aProgressMax); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::OnStatus(const char *aMsgID, const char16_t *aMsg) +{ + nsTObserverArray<nsCOMPtr<nsIMsgSendListener> >::ForwardIterator iter(mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) + { + externalSendListener = iter.GetNext(); + externalSendListener->OnStatus(aMsgID, aMsg); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::OnStopSending(const char *aMsgID, nsresult aStatus, const char16_t *aMsg, + nsIFile *returnFile) +{ + nsTObserverArray<nsCOMPtr<nsIMsgSendListener> >::ForwardIterator iter(mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) + { + externalSendListener = iter.GetNext(); + externalSendListener->OnStopSending(aMsgID, aStatus, aMsg, returnFile); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::OnSendNotPerformed(const char *aMsgID, nsresult aStatus) +{ + nsTObserverArray<nsCOMPtr<nsIMsgSendListener> >::ForwardIterator iter(mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) + { + externalSendListener = iter.GetNext(); + externalSendListener->OnSendNotPerformed(aMsgID, aStatus); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::OnGetDraftFolderURI(const char *aFolderURI) +{ + m_folderName = aFolderURI; + nsTObserverArray<nsCOMPtr<nsIMsgSendListener> >::ForwardIterator iter(mExternalSendListeners); + nsCOMPtr<nsIMsgSendListener> externalSendListener; + + while (iter.HasMore()) + { + externalSendListener = iter.GetNext(); + externalSendListener->OnGetDraftFolderURI(aFolderURI); + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////////// +// This is the listener class for both the send operation and the copy operation. +// We have to create this class to listen for message send completion and deal with +// failures in both send and copy operations +//////////////////////////////////////////////////////////////////////////////////// +NS_IMPL_ADDREF(nsMsgComposeSendListener) +NS_IMPL_RELEASE(nsMsgComposeSendListener) + +/* +NS_IMPL_QUERY_INTERFACE(nsMsgComposeSendListener, + nsIMsgComposeSendListener, + nsIMsgSendListener, + nsIMsgCopyServiceListener, + nsIWebProgressListener) +*/ +NS_INTERFACE_MAP_BEGIN(nsMsgComposeSendListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgComposeSendListener) + NS_INTERFACE_MAP_ENTRY(nsIMsgComposeSendListener) + NS_INTERFACE_MAP_ENTRY(nsIMsgSendListener) + NS_INTERFACE_MAP_ENTRY(nsIMsgCopyServiceListener) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) +NS_INTERFACE_MAP_END + + +nsMsgComposeSendListener::nsMsgComposeSendListener(void) +{ + mDeliverMode = 0; +} + +nsMsgComposeSendListener::~nsMsgComposeSendListener(void) +{ +} + +NS_IMETHODIMP nsMsgComposeSendListener::SetMsgCompose(nsIMsgCompose *obj) +{ + mWeakComposeObj = do_GetWeakReference(obj); + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeSendListener::SetDeliverMode(MSG_DeliverMode deliverMode) +{ + mDeliverMode = deliverMode; + return NS_OK; +} + +nsresult +nsMsgComposeSendListener::OnStartSending(const char *aMsgID, uint32_t aMsgSize) +{ + nsresult rv; + nsCOMPtr<nsIMsgSendListener> composeSendListener = do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnStartSending(aMsgID, aMsgSize); + + return NS_OK; +} + +nsresult +nsMsgComposeSendListener::OnProgress(const char *aMsgID, uint32_t aProgress, uint32_t aProgressMax) +{ + nsresult rv; + nsCOMPtr<nsIMsgSendListener> composeSendListener = do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnProgress(aMsgID, aProgress, aProgressMax); + return NS_OK; +} + +nsresult +nsMsgComposeSendListener::OnStatus(const char *aMsgID, const char16_t *aMsg) +{ + nsresult rv; + nsCOMPtr<nsIMsgSendListener> composeSendListener = do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnStatus(aMsgID, aMsg); + return NS_OK; +} + +nsresult nsMsgComposeSendListener::OnSendNotPerformed(const char *aMsgID, nsresult aStatus) +{ + // since OnSendNotPerformed is called in the case where the user aborts the operation + // by closing the compose window, we need not do the stuff required + // for closing the windows. However we would need to do the other operations as below. + + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj, &rv); + if (msgCompose) + msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, aStatus); + + nsCOMPtr<nsIMsgSendListener> composeSendListener = do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnSendNotPerformed(aMsgID, aStatus); + + return rv; +} + +nsresult nsMsgComposeSendListener::OnStopSending(const char *aMsgID, nsresult aStatus, + const char16_t *aMsg, nsIFile *returnFile) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj, &rv); + if (msgCompose) + { + nsCOMPtr<nsIMsgProgress> progress; + msgCompose->GetProgress(getter_AddRefs(progress)); + + if (NS_SUCCEEDED(aStatus)) + { + nsCOMPtr<nsIMsgCompFields> compFields; + msgCompose->GetCompFields(getter_AddRefs(compFields)); + + // only process the reply flags if we successfully sent the message + msgCompose->ProcessReplyFlags(); + + // See if there is a composer window + bool hasDomWindow = true; + nsCOMPtr<mozIDOMWindowProxy> domWindow; + rv = msgCompose->GetDomWindow(getter_AddRefs(domWindow)); + if (NS_FAILED(rv) || !domWindow) + hasDomWindow = false; + + // Close the window ONLY if we are not going to do a save operation + nsAutoString fieldsFCC; + if (NS_SUCCEEDED(compFields->GetFcc(fieldsFCC))) + { + if (!fieldsFCC.IsEmpty()) + { + if (fieldsFCC.LowerCaseEqualsLiteral("nocopy://")) + { + msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, NS_OK); + if (progress) + { + progress->UnregisterListener(this); + progress->CloseProgressDialog(false); + } + if (hasDomWindow) + msgCompose->CloseWindow(); + } + } + } + else + { + msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, NS_OK); + if (progress) + { + progress->UnregisterListener(this); + progress->CloseProgressDialog(false); + } + if (hasDomWindow) + msgCompose->CloseWindow(); // if we fail on the simple GetFcc call, close the window to be safe and avoid + // windows hanging around to prevent the app from exiting. + } + + // Remove the current draft msg when sending draft is done. + bool deleteDraft; + msgCompose->GetDeleteDraft(&deleteDraft); + if (deleteDraft) + RemoveCurrentDraftMessage(msgCompose, false); + } + else + { + msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, aStatus); + if (progress) + { + progress->CloseProgressDialog(true); + progress->UnregisterListener(this); + } + } + + } + + nsCOMPtr<nsIMsgSendListener> composeSendListener = do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnStopSending(aMsgID, aStatus, aMsg, returnFile); + + return rv; +} + +nsresult +nsMsgComposeSendListener::OnGetDraftFolderURI(const char *aFolderURI) +{ + nsresult rv; + nsCOMPtr<nsIMsgSendListener> composeSendListener = do_QueryReferent(mWeakComposeObj, &rv); + if (NS_SUCCEEDED(rv) && composeSendListener) + composeSendListener->OnGetDraftFolderURI(aFolderURI); + + return NS_OK; +} + + +nsresult +nsMsgComposeSendListener::OnStartCopy() +{ + return NS_OK; +} + +nsresult +nsMsgComposeSendListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax) +{ + return NS_OK; +} + +nsresult +nsMsgComposeSendListener::OnStopCopy(nsresult aStatus) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj, &rv); + if (msgCompose) + { + if (mDeliverMode == nsIMsgSend::nsMsgQueueForLater || + mDeliverMode == nsIMsgSend::nsMsgDeliverBackground || + mDeliverMode == nsIMsgSend::nsMsgSaveAsDraft) + { + msgCompose->RememberQueuedDisposition(); + } + + // Ok, if we are here, we are done with the send/copy operation so + // we have to do something with the window....SHOW if failed, Close + // if succeeded + + nsCOMPtr<nsIMsgProgress> progress; + msgCompose->GetProgress(getter_AddRefs(progress)); + if (progress) + { + // Unregister ourself from msg compose progress + progress->UnregisterListener(this); + progress->CloseProgressDialog(NS_FAILED(aStatus)); + } + + msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, aStatus); + + if (NS_SUCCEEDED(aStatus)) + { + // We should only close the window if we are done. Things like templates + // and drafts aren't done so their windows should stay open + if (mDeliverMode == nsIMsgSend::nsMsgSaveAsDraft || + mDeliverMode == nsIMsgSend::nsMsgSaveAsTemplate) + { + msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::SaveInFolderDone, aStatus); + // Remove the current draft msg when saving as draft/template is done. + msgCompose->SetDeleteDraft(true); + RemoveCurrentDraftMessage(msgCompose, true); + } + else + { + // Remove (possible) draft if we're in send later mode + if (mDeliverMode == nsIMsgSend::nsMsgQueueForLater || + mDeliverMode == nsIMsgSend::nsMsgDeliverBackground) + { + msgCompose->SetDeleteDraft(true); + RemoveCurrentDraftMessage(msgCompose, true); + } + msgCompose->CloseWindow(); + } + } + msgCompose->ClearMessageSend(); + } + + return rv; +} + +nsresult +nsMsgComposeSendListener::GetMsgFolder(nsIMsgCompose *compObj, nsIMsgFolder **msgFolder) +{ + nsresult rv; + nsCOMPtr<nsIMsgFolder> aMsgFolder; + nsCString folderUri; + + rv = compObj->GetSavedFolderURI(getter_Copies(folderUri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIRDFService> rdfService (do_GetService("@mozilla.org/rdf/rdf-service;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIRDFResource> resource; + rv = rdfService->GetResource(folderUri, getter_AddRefs(resource)); + NS_ENSURE_SUCCESS(rv, rv); + + aMsgFolder = do_QueryInterface(resource, &rv); + NS_ENSURE_SUCCESS(rv, rv); + NS_IF_ADDREF(*msgFolder = aMsgFolder); + return rv; +} + +nsresult +nsMsgComposeSendListener::RemoveCurrentDraftMessage(nsIMsgCompose *compObj, bool calledByCopy) +{ + nsresult rv; + nsCOMPtr <nsIMsgCompFields> compFields = nullptr; + + rv = compObj->GetCompFields(getter_AddRefs(compFields)); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get compose fields"); + if (NS_FAILED(rv) || !compFields) + return rv; + + nsCString curDraftIdURL; + nsMsgKey newUid = 0; + nsCString newDraftIdURL; + nsCOMPtr<nsIMsgFolder> msgFolder; + + rv = compFields->GetDraftId(getter_Copies(curDraftIdURL)); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get draft id"); + + // Skip if no draft id (probably a new draft msg). + if (NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty()) + { + nsCOMPtr <nsIMsgDBHdr> msgDBHdr; + rv = GetMsgDBHdrFromURI(curDraftIdURL.get(), getter_AddRefs(msgDBHdr)); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg header DB interface pointer."); + if (NS_SUCCEEDED(rv) && msgDBHdr) + { + do { // Break on failure or removal not needed. + // Get the folder for the message resource. + rv = msgDBHdr->GetFolder(getter_AddRefs(msgFolder)); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg folder interface pointer."); + if (NS_FAILED(rv) || !msgFolder) + break; + + // Only do this if it's a drafts folder. + bool isDraft; + msgFolder->GetFlag(nsMsgFolderFlags::Drafts, &isDraft); + if (!isDraft) + break; + + // Only remove if the message is actually in the db. It might have only + // been in the use cache. + nsMsgKey key; + rv = msgDBHdr->GetMessageKey(&key); + if (NS_FAILED(rv)) + break; + nsCOMPtr<nsIMsgDatabase> db; + msgFolder->GetMsgDatabase(getter_AddRefs(db)); + if (!db) + break; + bool containsKey = false; + db->ContainsKey(key, &containsKey); + if (!containsKey) + break; + + // Build the msg array. + nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't allocate array."); + if (NS_FAILED(rv) || !messageArray) + break; + rv = messageArray->AppendElement(msgDBHdr, false); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't append msg header to array."); + if (NS_FAILED(rv)) + break; + + // Ready to delete the msg. + rv = msgFolder->DeleteMessages(messageArray, nullptr, true, false, nullptr, false /*allowUndo*/); + NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't delete message."); + } while(false); + } + else + { + // If we get here we have the case where the draft folder + // is on the server and + // it's not currently open (in thread pane), so draft + // msgs are saved to the server + // but they're not in our local DB. In this case, + // GetMsgDBHdrFromURI() will never + // find the msg. If the draft folder is a local one + // then we'll not get here because + // the draft msgs are saved to the local folder and + // are in local DB. Make sure the + // msg folder is imap. Even if we get here due to + // DB errors (worst case), we should + // still try to delete msg on the server because + // that's where the master copy of the + // msgs are stored, if draft folder is on the server. + // For local case, since DB is bad + // we can't do anything with it anyway so it'll be + // noop in this case. + rv = GetMsgFolder(compObj, getter_AddRefs(msgFolder)); + if (NS_SUCCEEDED(rv) && msgFolder) + { + nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder); + NS_ASSERTION(imapFolder, "The draft folder MUST be an imap folder in order to mark the msg delete!"); + if (NS_SUCCEEDED(rv) && imapFolder) + { + const char * str = PL_strchr(curDraftIdURL.get(), '#'); + NS_ASSERTION(str, "Failed to get current draft id url"); + if (str) + { + nsAutoCString srcStr(str+1); + nsresult err; + nsMsgKey messageID = srcStr.ToInteger(&err); + if (messageID != nsMsgKey_None) + { + rv = imapFolder->StoreImapFlags(kImapMsgDeletedFlag, true, + &messageID, 1, nullptr); + } + } + } + } + } + } + + // Now get the new uid so that next save will remove the right msg + // regardless whether or not the exiting msg can be deleted. + if (calledByCopy) + { + nsCOMPtr<nsIMsgFolder> savedToFolder; + nsCOMPtr<nsIMsgSend> msgSend; + rv = compObj->GetMessageSend(getter_AddRefs(msgSend)); + NS_ASSERTION(msgSend, "RemoveCurrentDraftMessage msgSend is null."); + if (NS_FAILED(rv) || !msgSend) + return rv; + + rv = msgSend->GetMessageKey(&newUid); + NS_ENSURE_SUCCESS(rv, rv); + + // Make sure we have a folder interface pointer + rv = GetMsgFolder(compObj, getter_AddRefs(savedToFolder)); + + // Reset draft (uid) url with the new uid. + if (savedToFolder && newUid != nsMsgKey_None) + { + uint32_t folderFlags; + savedToFolder->GetFlags(&folderFlags); + if (folderFlags & nsMsgFolderFlags::Drafts) + { + rv = savedToFolder->GenerateMessageURI(newUid, newDraftIdURL); + NS_ENSURE_SUCCESS(rv, rv); + compFields->SetDraftId(newDraftIdURL.get()); + } + } + } + return rv; +} + +nsresult +nsMsgComposeSendListener::SetMessageKey(nsMsgKey aMessageKey) +{ + return NS_OK; +} + +nsresult +nsMsgComposeSendListener::GetMessageId(nsACString& messageId) +{ + return NS_OK; +} + +/* void onStateChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long aStateFlags, in nsresult aStatus); */ +NS_IMETHODIMP nsMsgComposeSendListener::OnStateChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t aStateFlags, nsresult aStatus) +{ + if (aStateFlags == nsIWebProgressListener::STATE_STOP) + { + nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(mWeakComposeObj); + if (msgCompose) + { + nsCOMPtr<nsIMsgProgress> progress; + msgCompose->GetProgress(getter_AddRefs(progress)); + + // Time to stop any pending operation... + if (progress) + { + // Unregister ourself from msg compose progress + progress->UnregisterListener(this); + + bool bCanceled = false; + progress->GetProcessCanceledByUser(&bCanceled); + if (bCanceled) + { + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + nsString msg; + bundle->GetStringFromName(u"msgCancelling", getter_Copies(msg)); + progress->OnStatusChange(nullptr, nullptr, NS_OK, msg.get()); + } + } + + nsCOMPtr<nsIMsgSend> msgSend; + msgCompose->GetMessageSend(getter_AddRefs(msgSend)); + if (msgSend) + msgSend->Abort(); + } + } + return NS_OK; +} + +/* void onProgressChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aCurSelfProgress, in long aMaxSelfProgress, in long aCurTotalProgress, in long aMaxTotalProgress); */ +NS_IMETHODIMP nsMsgComposeSendListener::OnProgressChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + /* Ignore this call */ + return NS_OK; +} + +/* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location, in unsigned long aFlags); */ +NS_IMETHODIMP nsMsgComposeSendListener::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location, uint32_t aFlags) +{ + /* Ignore this call */ + return NS_OK; +} + +/* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */ +NS_IMETHODIMP nsMsgComposeSendListener::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const char16_t *aMessage) +{ + /* Ignore this call */ + return NS_OK; +} + +/* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long state); */ +NS_IMETHODIMP nsMsgComposeSendListener::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, uint32_t state) +{ + /* Ignore this call */ + return NS_OK; +} + +nsresult +nsMsgCompose::ConvertHTMLToText(nsIFile *aSigFile, nsString &aSigData) +{ + nsAutoString origBuf; + + nsresult rv = LoadDataFromFile(aSigFile, origBuf); + NS_ENSURE_SUCCESS (rv, rv); + + ConvertBufToPlainText(origBuf, false, false, true, true); + aSigData = origBuf; + return NS_OK; +} + +nsresult +nsMsgCompose::ConvertTextToHTML(nsIFile *aSigFile, nsString &aSigData) +{ + nsresult rv; + nsAutoString origBuf; + + rv = LoadDataFromFile(aSigFile, origBuf); + if (NS_FAILED(rv)) + return rv; + + // Ok, once we are here, we need to escape the data to make sure that + // we don't do HTML stuff with plain text sigs. + // + char16_t *escaped = MsgEscapeHTML2(origBuf.get(), origBuf.Length()); + if (escaped) + { + aSigData.Append(escaped); + NS_Free(escaped); + } + else + aSigData.Append(origBuf); + return NS_OK; +} + +nsresult +nsMsgCompose::LoadDataFromFile(nsIFile *file, nsString &sigData, + bool aAllowUTF8, bool aAllowUTF16) +{ + int32_t readSize; + uint32_t nGot; + char *readBuf; + char *ptr; + + bool isDirectory = false; + file->IsDirectory(&isDirectory); + if (isDirectory) { + NS_ERROR("file is a directory"); + return NS_MSG_ERROR_READING_FILE; + } + + + nsCOMPtr <nsIInputStream> inputFile; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputFile), file); + if (NS_FAILED(rv)) + return NS_MSG_ERROR_READING_FILE; + + int64_t fileSize; + file->GetFileSize(&fileSize); + readSize = (uint32_t) fileSize; + + + ptr = readBuf = (char *)PR_Malloc(readSize + 1); if (!readBuf) + return NS_ERROR_OUT_OF_MEMORY; + memset(readBuf, 0, readSize + 1); + + while (readSize) { + inputFile->Read(ptr, readSize, &nGot); + if (nGot) { + readSize -= nGot; + ptr += nGot; + } + else { + readSize = 0; + } + } + inputFile->Close(); + + readSize = (uint32_t) fileSize; + + nsAutoCString sigEncoding(nsMsgI18NParseMetaCharset(file)); + bool removeSigCharset = !sigEncoding.IsEmpty() && m_composeHTML; + + if (sigEncoding.IsEmpty()) { + if (aAllowUTF8 && MsgIsUTF8(nsDependentCString(readBuf))) { + sigEncoding.Assign("UTF-8"); + } + else if (sigEncoding.IsEmpty() && aAllowUTF16 && + readSize % 2 == 0 && readSize >= 2 && + ((readBuf[0] == char(0xFE) && readBuf[1] == char(0xFF)) || + (readBuf[0] == char(0xFF) && readBuf[1] == char(0xFE)))) { + sigEncoding.Assign("UTF-16"); + } + else { + //default to platform encoding for plain text files w/o meta charset + nsAutoCString textFileCharset; + nsMsgI18NTextFileCharset(textFileCharset); + sigEncoding.Assign(textFileCharset); + } + } + + nsAutoCString readStr(readBuf, (int32_t) fileSize); + PR_FREEIF(readBuf); + + // XXX: ^^^ could really use nsContentUtils::SlurpFileToString instead! + + if (NS_FAILED(ConvertToUnicode(sigEncoding.get(), readStr, sigData))) + CopyASCIItoUTF16(readStr, sigData); + + //remove sig meta charset to allow user charset override during composition + if (removeSigCharset) + { + nsAutoCString metaCharset("charset="); + metaCharset.Append(sigEncoding); + int32_t pos = sigData.Find(metaCharset.BeginReading(), true); + if (pos != kNotFound) + sigData.Cut(pos, metaCharset.Length()); + } + return NS_OK; +} + +/** + * If the data contains file URLs, convert them to data URLs instead. + * This is intended to be used in for signature files, so that we can make sure + * images loaded into the editor are available on send. + */ +nsresult +nsMsgCompose::ReplaceFileURLs(nsAutoString &aData) +{ + int32_t fPos; + int32_t offset = -1; + while ((fPos = aData.RFind("file://", true, offset)) != kNotFound) { + if (fPos != kNotFound && fPos > 0) { + char16_t q = aData.CharAt(fPos - 1); + bool quoted = (q == '"' || q == '\''); + int32_t end = kNotFound; + if (quoted) { + end = aData.FindChar(q, fPos); + } + else { + int32_t spacePos = aData.FindChar(' ', fPos); + int32_t gtPos = aData.FindChar('>', fPos); + if (gtPos != kNotFound && spacePos != kNotFound) { + end = (spacePos < gtPos) ? spacePos : gtPos; + } + else if (gtPos == kNotFound && spacePos != kNotFound) { + end = spacePos; + } + else if (gtPos != kNotFound && spacePos == kNotFound) { + end = gtPos; + } + } + if (end == kNotFound) { + break; + } + nsString fileURL; + fileURL = Substring(aData, fPos, end - fPos); + nsString dataURL; + nsresult rv = DataURLForFileURL(fileURL, dataURL); + // If this one failed, maybe because the file wasn't found, + // continue to process the next one. + if (NS_SUCCEEDED(rv)) { + aData.Replace(fPos, end - fPos, dataURL); + } + offset = fPos - 1; + } + } + return NS_OK; +} + +nsresult +nsMsgCompose::DataURLForFileURL(const nsAString &aFileURL, nsAString &aDataURL) +{ + nsresult rv; + nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> fileUri; + rv = NS_NewURI(getter_AddRefs(fileUri), NS_ConvertUTF16toUTF8(aFileURL).get()); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(fileUri, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIFile> file; + rv = fileUrl->GetFile(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString type; + rv = mime->GetTypeFromFile(file, type); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString data; + rv = nsContentUtils::SlurpFileToString(file, data); + NS_ENSURE_SUCCESS(rv, rv); + + aDataURL.AssignLiteral("data:"); + AppendUTF8toUTF16(type, aDataURL); + + nsAutoString filename; + rv = file->GetLeafName(filename); + if (NS_SUCCEEDED(rv)) { + nsAutoCString fn; + MsgEscapeURL(NS_ConvertUTF16toUTF8(filename), + nsINetUtil::ESCAPE_URL_FILE_BASENAME | nsINetUtil::ESCAPE_URL_FORCED, fn); + if (!fn.IsEmpty()) { + aDataURL.AppendLiteral(";filename="); + aDataURL.Append(NS_ConvertUTF8toUTF16(fn)); + } + } + + aDataURL.AppendLiteral(";base64,"); + char *result = PL_Base64Encode(data.get(), data.Length(), nullptr); + nsDependentCString base64data(result); + NS_ENSURE_SUCCESS(rv, rv); + AppendUTF8toUTF16(base64data, aDataURL); + return NS_OK; +} + +nsresult +nsMsgCompose::BuildQuotedMessageAndSignature(void) +{ + // + // This should never happen...if it does, just bail out... + // + NS_ASSERTION(m_editor, "BuildQuotedMessageAndSignature but no editor!\n"); + if (!m_editor) + return NS_ERROR_FAILURE; + + // We will fire off the quote operation and wait for it to + // finish before we actually do anything with Ender... + return QuoteOriginalMessage(); +} + +// +// This will process the signature file for the user. This method +// will always append the results to the mMsgBody member variable. +// +nsresult +nsMsgCompose::ProcessSignature(nsIMsgIdentity *identity, bool aQuoted, nsString *aMsgBody) +{ + nsresult rv = NS_OK; + + // Now, we can get sort of fancy. This is the time we need to check + // for all sorts of user defined stuff, like signatures and editor + // types and the like! + // + // user_pref(".....sig_file", "y:\\sig.html"); + // user_pref(".....attach_signature", true); + // user_pref(".....htmlSigText", "unicode sig"); + // + // Note: We will have intelligent signature behavior in that we + // look at the signature file first...if the extension is .htm or + // .html, we assume its HTML, otherwise, we assume it is plain text + // + // ...and that's not all! What we will also do now is look and see if + // the file is an image file. If it is an image file, then we should + // insert the correct HTML into the composer to have it work, but if we + // are doing plain text compose, we should insert some sort of message + // saying "Image Signature Omitted" or something (not done yet). + // + // If there's a sig pref, it will only be used if there is no sig file defined, + // thus if attach_signature is checked, htmlSigText is ignored (bug 324495). + // Plain-text signatures may or may not have a trailing line break (bug 428040). + + nsAutoCString sigNativePath; + bool attachFile = false; + bool useSigFile = false; + bool htmlSig = false; + bool imageSig = false; + nsAutoString sigData; + nsAutoString sigOutput; + int32_t reply_on_top = 0; + bool sig_bottom = true; + bool suppressSigSep = false; + + nsCOMPtr<nsIFile> sigFile; + if (identity) + { + if (!CheckIncludeSignaturePrefs(identity)) + return NS_OK; + + identity->GetReplyOnTop(&reply_on_top); + identity->GetSigBottom(&sig_bottom); + identity->GetSuppressSigSep(&suppressSigSep); + + rv = identity->GetAttachSignature(&attachFile); + if (NS_SUCCEEDED(rv) && attachFile) + { + rv = identity->GetSignature(getter_AddRefs(sigFile)); + if (NS_SUCCEEDED(rv) && sigFile) { + rv = sigFile->GetNativePath(sigNativePath); + if (NS_SUCCEEDED(rv) && !sigNativePath.IsEmpty()) { + bool exists = false; + sigFile->Exists(&exists); + if (exists) { + useSigFile = true; // ok, there's a signature file + + // Now, most importantly, we need to figure out what the content type is for + // this signature...if we can't, we assume text + nsAutoCString sigContentType; + nsresult rv2; // don't want to clobber the other rv + nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv2)); + if (NS_SUCCEEDED(rv2)) { + rv2 = mimeFinder->GetTypeFromFile(sigFile, sigContentType); + if (NS_SUCCEEDED(rv2)) { + if (StringBeginsWith(sigContentType, NS_LITERAL_CSTRING("image/"), nsCaseInsensitiveCStringComparator())) + imageSig = true; + else if (sigContentType.Equals(TEXT_HTML, nsCaseInsensitiveCStringComparator())) + htmlSig = true; + } + } + } + } + } + } + } + + // Unless signature to be attached from file, use preference value; + // the htmlSigText value is always going to be treated as html if + // the htmlSigFormat pref is true, otherwise it is considered text + nsAutoString prefSigText; + if (identity && !attachFile) + identity->GetHtmlSigText(prefSigText); + // Now, if they didn't even want to use a signature, we should + // just return nicely. + // + if ((!useSigFile && prefSigText.IsEmpty()) || NS_FAILED(rv)) + return NS_OK; + + static const char htmlBreak[] = "<br>"; + static const char dashes[] = "-- "; + static const char htmlsigopen[] = "<div class=\"moz-signature\">"; + static const char htmlsigclose[] = "</div>"; /* XXX: Due to a bug in + 4.x' HTML editor, it will not be able to + break this HTML sig, if quoted (for the user to + interleave a comment). */ + static const char _preopen[] = "<pre class=\"moz-signature\" cols=%d>"; + char* preopen; + static const char preclose[] = "</pre>"; + + int32_t wrapLength = 72; // setup default value in case GetWrapLength failed + GetWrapLength(&wrapLength); + preopen = PR_smprintf(_preopen, wrapLength); + if (!preopen) + return NS_ERROR_OUT_OF_MEMORY; + + bool paragraphMode = + mozilla::Preferences::GetBool("mail.compose.default_to_paragraph", false); + + if (imageSig) + { + // We have an image signature. If we're using the in HTML composer, we + // should put in the appropriate HTML for inclusion, otherwise, do nothing. + if (m_composeHTML) + { + if (!paragraphMode) + sigOutput.AppendLiteral(htmlBreak); + sigOutput.AppendLiteral(htmlsigopen); + if ((mType == nsIMsgCompType::NewsPost || !suppressSigSep) && + (reply_on_top != 1 || sig_bottom || !aQuoted)) { + sigOutput.AppendLiteral(dashes); + } + + sigOutput.AppendLiteral(htmlBreak); + sigOutput.AppendLiteral("<img src='"); + + nsCOMPtr<nsIURI> fileURI; + nsresult rv = NS_NewFileURI(getter_AddRefs(fileURI), sigFile); + NS_ENSURE_SUCCESS(rv, rv); + nsCString fileURL; + fileURI->GetSpec(fileURL); + + nsString dataURL; + rv = DataURLForFileURL(NS_ConvertUTF8toUTF16(fileURL), dataURL); + if (NS_SUCCEEDED(rv)) { + sigOutput.Append(dataURL); + } + sigOutput.AppendLiteral("' border=0>"); + sigOutput.AppendLiteral(htmlsigclose); + } + } + else if (useSigFile) + { + // is this a text sig with an HTML editor? + if ( (m_composeHTML) && (!htmlSig) ) { + ConvertTextToHTML(sigFile, sigData); + } + // is this a HTML sig with a text window? + else if ( (!m_composeHTML) && (htmlSig) ) { + ConvertHTMLToText(sigFile, sigData); + } + else { // We have a match... + LoadDataFromFile(sigFile, sigData); // Get the data! + ReplaceFileURLs(sigData); + } + } + + // if we have a prefSigText, append it to sigData. + if (!prefSigText.IsEmpty()) + { + // set htmlSig if the pref is supposed to contain HTML code, defaults to false + rv = identity->GetHtmlSigFormat(&htmlSig); + if (NS_FAILED(rv)) + htmlSig = false; + + if (!m_composeHTML) + { + if (htmlSig) + ConvertBufToPlainText(prefSigText, false, false, true, true); + sigData.Append(prefSigText); + } + else + { + if (!htmlSig) + { + char16_t* escaped = MsgEscapeHTML2(prefSigText.get(), prefSigText.Length()); + if (escaped) + { + sigData.Append(escaped); + NS_Free(escaped); + } + else + sigData.Append(prefSigText); + } + else { + ReplaceFileURLs(prefSigText); + sigData.Append(prefSigText); + } + } + } + + // post-processing for plain-text signatures to ensure we end in CR, LF, or CRLF + if (!htmlSig && !m_composeHTML) + { + int32_t sigLength = sigData.Length(); + if (sigLength > 0 && !(sigData.CharAt(sigLength - 1) == '\r') + && !(sigData.CharAt(sigLength - 1) == '\n')) + sigData.AppendLiteral(CRLF); + } + + // Now that sigData holds data...if any, append it to the body in a nice + // looking manner + if (!sigData.IsEmpty()) + { + if (m_composeHTML) + { + if (!paragraphMode) + sigOutput.AppendLiteral(htmlBreak); + + if (htmlSig) + sigOutput.AppendLiteral(htmlsigopen); + else + sigOutput.Append(NS_ConvertASCIItoUTF16(preopen)); + } + + if ((reply_on_top != 1 || sig_bottom || !aQuoted) && + sigData.Find("\r-- \r", true) < 0 && + sigData.Find("\n-- \n", true) < 0 && + sigData.Find("\n-- \r", true) < 0) + { + nsDependentSubstring firstFourChars(sigData, 0, 4); + + if ((mType == nsIMsgCompType::NewsPost || !suppressSigSep) && + !(firstFourChars.EqualsLiteral("-- \n") || + firstFourChars.EqualsLiteral("-- \r"))) + { + sigOutput.AppendLiteral(dashes); + + if (!m_composeHTML || !htmlSig) + sigOutput.AppendLiteral(CRLF); + else if (m_composeHTML) + sigOutput.AppendLiteral(htmlBreak); + } + } + + // add CRLF before signature for plain-text mode if signature comes before quote + if (!m_composeHTML && reply_on_top == 1 && !sig_bottom && aQuoted) + sigOutput.AppendLiteral(CRLF); + + sigOutput.Append(sigData); + + if (m_composeHTML) + { + if (htmlSig) + sigOutput.AppendLiteral(htmlsigclose); + else + sigOutput.AppendLiteral(preclose); + } + } + + aMsgBody->Append(sigOutput); + PR_Free(preopen); + return NS_OK; +} + +nsresult +nsMsgCompose::BuildBodyMessageAndSignature() +{ + nsresult rv = NS_OK; + + // + // This should never happen...if it does, just bail out... + // + if (!m_editor) + return NS_ERROR_FAILURE; + + // + // Now, we have the body so we can just blast it into the + // composition editor window. + // + nsAutoString body; + m_compFields->GetBody(body); + + /* Some time we want to add a signature and sometime we wont. Let's figure that now...*/ + bool addSignature; + bool isQuoted = false; + switch (mType) + { + case nsIMsgCompType::ForwardInline : + addSignature = true; + isQuoted = true; + break; + case nsIMsgCompType::New : + case nsIMsgCompType::MailToUrl : /* same as New */ + case nsIMsgCompType::Reply : /* should not happen! but just in case */ + case nsIMsgCompType::ReplyAll : /* should not happen! but just in case */ + case nsIMsgCompType::ReplyToList : /* should not happen! but just in case */ + case nsIMsgCompType::ForwardAsAttachment : /* should not happen! but just in case */ + case nsIMsgCompType::NewsPost : + case nsIMsgCompType::ReplyToGroup : + case nsIMsgCompType::ReplyToSender : + case nsIMsgCompType::ReplyToSenderAndGroup : + addSignature = true; + break; + + case nsIMsgCompType::Draft : + case nsIMsgCompType::Template : + case nsIMsgCompType::Redirect : + addSignature = false; + break; + + default : + addSignature = false; + break; + } + + nsAutoString tSignature; + if (addSignature) + ProcessSignature(m_identity, isQuoted, &tSignature); + + // if type is new, but we have body, this is probably a mapi send, so we need to + // replace '\n' with <br> so that the line breaks won't be lost by html. + // if mailtourl, do the same. + if (m_composeHTML && (mType == nsIMsgCompType::New || mType == nsIMsgCompType::MailToUrl)) + MsgReplaceSubstring(body, NS_LITERAL_STRING("\n"), NS_LITERAL_STRING("<br>")); + + // Restore flowed text wrapping for Drafts/Templates. + // Look for unquoted lines - if we have an unquoted line + // that ends in a space, join this line with the next one + // by removing the end of line char(s). + int32_t wrapping_enabled = 0; + GetWrapLength(&wrapping_enabled); + if (!m_composeHTML && wrapping_enabled) + { + bool quote = false; + for (uint32_t i = 0; i < body.Length(); i ++) + { + if (i == 0 || body[i - 1] == '\n') // newline + { + if (body[i] == '>') + { + quote = true; + continue; + } + nsString s(Substring(body, i, 10)); + if (StringBeginsWith(s, NS_LITERAL_STRING("-- \r")) || + StringBeginsWith(s, NS_LITERAL_STRING("-- \n"))) + { + i += 4; + continue; + } + if (StringBeginsWith(s, NS_LITERAL_STRING("- -- \r")) || + StringBeginsWith(s, NS_LITERAL_STRING("- -- \n"))) + { + i += 6; + continue; + } + } + if (body[i] == '\n' && i > 1) + { + if (quote) + { + quote = false; + continue; // skip quoted lines + } + uint32_t j = i - 1; // look backward for space + if (body[j] == '\r') + j --; + if (body[j] == ' ') // join this line with next one + body.Cut(j + 1, i - j); // remove CRLF + } + } + } + + nsString empty; + rv = ConvertAndLoadComposeWindow(empty, body, tSignature, + false, m_composeHTML); + + return rv; +} + +nsresult nsMsgCompose::NotifyStateListeners(int32_t aNotificationType, nsresult aResult) +{ + + if (aNotificationType == nsIMsgComposeNotificationType::SaveInFolderDone) + ResetUrisForEmbeddedObjects(); + + nsTObserverArray<nsCOMPtr<nsIMsgComposeStateListener> >::ForwardIterator iter(mStateListeners); + nsCOMPtr<nsIMsgComposeStateListener> thisListener; + + while (iter.HasMore()) + { + thisListener = iter.GetNext(); + + switch (aNotificationType) + { + case nsIMsgComposeNotificationType::ComposeFieldsReady: + thisListener->NotifyComposeFieldsReady(); + break; + + case nsIMsgComposeNotificationType::ComposeProcessDone: + thisListener->ComposeProcessDone(aResult); + break; + + case nsIMsgComposeNotificationType::SaveInFolderDone: + thisListener->SaveInFolderDone(m_folderName.get()); + break; + + case nsIMsgComposeNotificationType::ComposeBodyReady: + thisListener->NotifyComposeBodyReady(); + break; + + default: + NS_NOTREACHED("Unknown notification"); + break; + } + } + + return NS_OK; +} + +nsresult nsMsgCompose::AttachmentPrettyName(const nsACString & scheme, const char* charset, nsACString& _retval) +{ + nsresult rv; + + if (MsgLowerCaseEqualsLiteral(StringHead(scheme, 5), "file:")) + { + nsCOMPtr<nsIFile> file; + rv = NS_GetFileFromURLSpec(scheme, + getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoString leafName; + rv = file->GetLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + CopyUTF16toUTF8(leafName, _retval); + return rv; + } + + // To work around a mysterious bug in VC++ 6. + const char* cset = (!charset || !*charset) ? "UTF-8" : charset; + + nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString retUrl; + rv = textToSubURI->UnEscapeURIForUI(nsDependentCString(cset), scheme, retUrl); + + if (NS_SUCCEEDED(rv)) { + CopyUTF16toUTF8(retUrl, _retval); + } else { + _retval.Assign(scheme); + } + if (MsgLowerCaseEqualsLiteral(StringHead(scheme, 5), "http:")) + _retval.Cut(0, 7); + + return NS_OK; +} + +/** + * Retrieve address book directories and mailing lists. + * + * @param aDirUri directory URI + * @param allDirectoriesArray retrieved directories and sub-directories + * @param allMailListArray retrieved maillists + */ +nsresult +nsMsgCompose::GetABDirAndMailLists(const nsACString& aDirUri, + nsCOMArray<nsIAbDirectory> &aDirArray, + nsTArray<nsMsgMailList> &aMailListArray) +{ + static bool collectedAddressbookFound; + if (aDirUri.EqualsLiteral(kMDBDirectoryRoot)) + collectedAddressbookFound = false; + + nsresult rv; + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(aDirUri, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> subDirectories; + if (NS_SUCCEEDED(directory->GetChildNodes(getter_AddRefs(subDirectories))) && subDirectories) + { + nsCOMPtr<nsISupports> item; + bool hasMore; + while (NS_SUCCEEDED(rv = subDirectories->HasMoreElements(&hasMore)) && hasMore) + { + if (NS_SUCCEEDED(subDirectories->GetNext(getter_AddRefs(item)))) + { + directory = do_QueryInterface(item, &rv); + if (NS_SUCCEEDED(rv)) + { + bool bIsMailList; + + if (NS_SUCCEEDED(directory->GetIsMailList(&bIsMailList)) && bIsMailList) + { + aMailListArray.AppendElement(directory); + continue; + } + + nsCString uri; + rv = directory->GetURI(uri); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t pos; + if (uri.EqualsLiteral(kPersonalAddressbookUri)) + pos = 0; + else + { + uint32_t count = aDirArray.Count(); + + if (uri.EqualsLiteral(kCollectedAddressbookUri)) + { + collectedAddressbookFound = true; + pos = count; + } + else + { + if (collectedAddressbookFound && count > 1) + pos = count - 1; + else + pos = count; + } + } + + aDirArray.InsertObjectAt(directory, pos); + rv = GetABDirAndMailLists(uri, aDirArray, aMailListArray); + } + } + } + } + return rv; +} + +/** + * Comparator for use with nsTArray::IndexOf to find a recipient. + * This comparator will check if an "address" is a mail list or not. + */ +struct nsMsgMailListComparator +{ + // A mail list will have one of the formats + // 1) "mName <mDescription>" when the list has a description + // 2) "mName <mName>" when the list lacks description + // A recipient is of the form "mName <mEmail>" - for equality the list + // name must be the same. The recipient "email" must match the list name for + // case 1, and the list description for case 2. + bool Equals(const nsMsgMailList &mailList, + const nsMsgRecipient &recipient) const { + if (!mailList.mName.Equals(recipient.mName, + nsCaseInsensitiveStringComparator())) + return false; + return mailList.mDescription.IsEmpty() ? + mailList.mName.Equals(recipient.mEmail, nsCaseInsensitiveStringComparator()) : + mailList.mDescription.Equals(recipient.mEmail, nsCaseInsensitiveStringComparator()); + } +}; + +/** + * Comparator for use with nsTArray::IndexOf to find a recipient. + */ +struct nsMsgRecipientComparator +{ + bool Equals(const nsMsgRecipient &recipient, + const nsMsgRecipient &recipientToFind) const { + if (!recipient.mEmail.Equals(recipientToFind.mEmail, + nsCaseInsensitiveStringComparator())) + return false; + + if (!recipient.mName.Equals(recipientToFind.mName, + nsCaseInsensitiveStringComparator())) + return false; + + return true; + } +}; + +/** + * This function recursively resolves a mailing list and returns individual + * email addresses. Nested lists are supported. It maintains an array of + * already visited mailing lists to avoid endless recursion. + * + * @param aMailList the list + * @param allDirectoriesArray all directories + * @param allMailListArray all maillists + * @param mailListProcessed maillists processed (to avoid recursive lists) + * @param aListMembers list members + */ +nsresult +nsMsgCompose::ResolveMailList(nsIAbDirectory* aMailList, + nsCOMArray<nsIAbDirectory> &allDirectoriesArray, + nsTArray<nsMsgMailList> &allMailListArray, + nsTArray<nsMsgMailList> &mailListProcessed, + nsTArray<nsMsgRecipient> &aListMembers) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIMutableArray> mailListAddresses; + rv = aMailList->GetAddressLists(getter_AddRefs(mailListAddresses)); + if (NS_FAILED(rv)) + return rv; + + uint32_t nbrAddresses = 0; + mailListAddresses->GetLength(&nbrAddresses); + for (uint32_t i = 0; i < nbrAddresses; i++) + { + nsCOMPtr<nsIAbCard> existingCard(do_QueryElementAt(mailListAddresses, i, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsMsgRecipient newRecipient; + + rv = existingCard->GetDisplayName(newRecipient.mName); + NS_ENSURE_SUCCESS(rv, rv); + rv = existingCard->GetPrimaryEmail(newRecipient.mEmail); + NS_ENSURE_SUCCESS(rv, rv); + + if (newRecipient.mName.IsEmpty() && newRecipient.mEmail.IsEmpty()) { + continue; + } + + // First check if it's a mailing list. + size_t index = allMailListArray.IndexOf(newRecipient, 0, nsMsgMailListComparator()); + if (index != allMailListArray.NoIndex && allMailListArray[index].mDirectory) + { + // Check if maillist processed. + if (mailListProcessed.Contains(newRecipient, nsMsgMailListComparator())) { + continue; + } + + nsCOMPtr<nsIAbDirectory> directory2(allMailListArray[index].mDirectory); + + // Add mailList to mailListProcessed. + mailListProcessed.AppendElement(directory2); + + // Resolve mailList members. + rv = ResolveMailList(directory2, + allDirectoriesArray, + allMailListArray, + mailListProcessed, + aListMembers); + NS_ENSURE_SUCCESS(rv, rv); + + continue; + } + + // Check if recipient is in aListMembers. + if (aListMembers.Contains(newRecipient, nsMsgRecipientComparator())) { + continue; + } + + // Now we need to insert the new address into the list of recipients. + newRecipient.mCard = existingCard; + newRecipient.mDirectory = aMailList; + + aListMembers.AppendElement(newRecipient); + } + + return rv; +} + +/** + * Lookup the recipients as specified in the compose fields (To, Cc, Bcc) + * in the address books and return an array of individual recipients. + * Mailing lists are replaced by the cards they contain, nested and recursive + * lists are taken care of, recipients contained in multiple lists are only + * added once. + * + * @param recipientsList (out) recipient array + */ +nsresult +nsMsgCompose::LookupAddressBook(RecipientsArray &recipientsList) +{ + nsresult rv = NS_OK; + + // First, build some arrays with the original recipients. + + nsAutoString originalRecipients[MAX_OF_RECIPIENT_ARRAY]; + m_compFields->GetTo(originalRecipients[0]); + m_compFields->GetCc(originalRecipients[1]); + m_compFields->GetBcc(originalRecipients[2]); + + for (uint32_t i = 0; i < MAX_OF_RECIPIENT_ARRAY; ++i) + { + if (originalRecipients[i].IsEmpty()) + continue; + + rv = m_compFields->SplitRecipientsEx(originalRecipients[i], + recipientsList[i]); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Then look them up in the Addressbooks + bool stillNeedToSearch = true; + nsCOMPtr<nsIAbDirectory> abDirectory; + nsCOMPtr<nsIAbCard> existingCard; + nsTArray<nsMsgMailList> mailListArray; + nsTArray<nsMsgMailList> mailListProcessed; + + nsCOMArray<nsIAbDirectory> addrbookDirArray; + rv = GetABDirAndMailLists(NS_LITERAL_CSTRING(kAllDirectoryRoot), + addrbookDirArray, mailListArray); + if (NS_FAILED(rv)) + return rv; + + nsString dirPath; + uint32_t nbrAddressbook = addrbookDirArray.Count(); + + for (uint32_t k = 0; k < nbrAddressbook && stillNeedToSearch; ++k) + { + // Avoid recursive mailing lists. + if (abDirectory && (addrbookDirArray[k] == abDirectory)) + { + stillNeedToSearch = false; + break; + } + + abDirectory = addrbookDirArray[k]; + if (!abDirectory) + continue; + + stillNeedToSearch = false; + for (uint32_t i = 0; i < MAX_OF_RECIPIENT_ARRAY; i ++) + { + mailListProcessed.Clear(); + + // Note: We check this each time to allow for length changes. + for (uint32_t j = 0; j < recipientsList[i].Length(); j++) + { + nsMsgRecipient &recipient = recipientsList[i][j]; + if (!recipient.mDirectory) + { + // First check if it's a mailing list. + size_t index = mailListArray.IndexOf(recipient, 0, nsMsgMailListComparator()); + if (index != mailListArray.NoIndex && mailListArray[index].mDirectory) + { + // Check mailList Processed. + if (mailListProcessed.Contains(recipient, nsMsgMailListComparator())) { + // Remove from recipientsList. + recipientsList[i].RemoveElementAt(j--); + continue; + } + + nsCOMPtr<nsIAbDirectory> directory(mailListArray[index].mDirectory); + + // Add mailList to mailListProcessed. + mailListProcessed.AppendElement(directory); + + // Resolve mailList members. + nsTArray<nsMsgRecipient> members; + rv = ResolveMailList(directory, + addrbookDirArray, + mailListArray, + mailListProcessed, + members); + NS_ENSURE_SUCCESS(rv, rv); + + // Remove mailList from recipientsList. + recipientsList[i].RemoveElementAt(j); + + // Merge members into recipientsList[i]. + uint32_t pos = 0; + for (uint32_t c = 0; c < members.Length(); c++) + { + nsMsgRecipient &member = members[c]; + if (!recipientsList[i].Contains(member, nsMsgRecipientComparator())) { + recipientsList[i].InsertElementAt(j + pos, member); + pos++; + } + } + } + else + { + // Find a card that contains this e-mail address. + rv = abDirectory->CardForEmailAddress(NS_ConvertUTF16toUTF8(recipient.mEmail), + getter_AddRefs(existingCard)); + if (NS_SUCCEEDED(rv) && existingCard) + { + recipient.mCard = existingCard; + recipient.mDirectory = abDirectory; + } + else + { + stillNeedToSearch = true; + } + } + } + } + } + } + + return rv; +} + +NS_IMETHODIMP +nsMsgCompose::ExpandMailingLists() +{ + RecipientsArray recipientsList; + nsresult rv = LookupAddressBook(recipientsList); + NS_ENSURE_SUCCESS(rv, rv); + + // Reset the final headers with the expanded mailing lists. + nsAutoString recipientsStr; + + for (int i = 0; i < MAX_OF_RECIPIENT_ARRAY; ++i) + { + uint32_t nbrRecipients = recipientsList[i].Length(); + if (nbrRecipients == 0) + continue; + recipientsStr.Truncate(); + + // Note: We check this each time to allow for length changes. + for (uint32_t j = 0; j < recipientsList[i].Length(); ++j) + { + nsMsgRecipient &recipient = recipientsList[i][j]; + + if (!recipientsStr.IsEmpty()) + recipientsStr.Append(char16_t(',')); + nsAutoString address; + MakeMimeAddress(recipient.mName, recipient.mEmail, address); + recipientsStr.Append(address); + + if (recipient.mCard) + { + bool readOnly; + rv = recipient.mDirectory->GetReadOnly(&readOnly); + NS_ENSURE_SUCCESS(rv, rv); + + // Bump the popularity index for this card since we are about to send + // e-mail to it. + if (!readOnly) + { + uint32_t popularityIndex = 0; + if (NS_FAILED(recipient.mCard->GetPropertyAsUint32( + kPopularityIndexProperty, &popularityIndex))) + { + // TB 2 wrote the popularity value as hex, so if we get here, + // then we've probably got a hex value. We'll convert it back + // to decimal, as that's the best we can do. + + nsCString hexPopularity; + if (NS_SUCCEEDED(recipient.mCard->GetPropertyAsAUTF8String( + kPopularityIndexProperty, hexPopularity))) + { + nsresult errorCode = NS_OK; + popularityIndex = hexPopularity.ToInteger(&errorCode, 16); + if (NS_FAILED(errorCode)) + // We failed, just set it to zero. + popularityIndex = 0; + } + else + // We couldn't get it as a string either, so just reset to zero. + popularityIndex = 0; + } + + recipient.mCard->SetPropertyAsUint32(kPopularityIndexProperty, + ++popularityIndex); + recipient.mDirectory->ModifyCard(recipient.mCard); + } + } + } + + switch (i) + { + case 0: m_compFields->SetTo(recipientsStr); break; + case 1: m_compFields->SetCc(recipientsStr); break; + case 2: m_compFields->SetBcc(recipientsStr); break; + } + } + + return NS_OK; +} + +/** + * This function implements the decision logic for delivery format 'Auto-Detect', + * including optional 'Auto-Downgrade' behaviour for HTML messages considered + * convertible (silent, "lossless" conversion to plain text). + * @param aConvertible the result of analysing message body convertibility: + * nsIMsgCompConvertible::Plain | Yes | Altering | No + * @return nsIMsgCompSendFormat::AskUser | PlainText | HTML | Both + */ +NS_IMETHODIMP +nsMsgCompose::DetermineHTMLAction(int32_t aConvertible, int32_t *result) +{ + NS_ENSURE_ARG_POINTER(result); + nsresult rv; + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // *** Message-centric Auto-Downgrade *** + // If the message has practically no HTML formatting, + // AND if user accepts auto-downgrading (send options pref), + // bypass auto-detection of recipients' preferences and just + // send the message as plain text (silent, "lossless" conversion); + // which will also avoid asking for newsgroups for this typical scenario. + bool autoDowngrade = true; + rv = prefBranch->GetBoolPref("mailnews.sendformat.auto_downgrade", &autoDowngrade); + NS_ENSURE_SUCCESS(rv, rv); + if (autoDowngrade && (aConvertible == nsIMsgCompConvertible::Plain)) + { + *result = nsIMsgCompSendFormat::PlainText; + return NS_OK; + } + + // *** Newsgroups *** + // Right now, we don't have logic for newsgroups for intelligent send + // preferences. Therefore, bail out early and save us a lot of work if there + // are newsgroups. + + nsAutoString newsgroups; + m_compFields->GetNewsgroups(newsgroups); + + if (!newsgroups.IsEmpty()) + { + *result = nsIMsgCompSendFormat::AskUser; + return NS_OK; + } + + // *** Recipient-Centric Auto-Detect *** + + RecipientsArray recipientsList; + rv = LookupAddressBook(recipientsList); + NS_ENSURE_SUCCESS(rv, rv); + + // Finally return the list of non-HTML recipients if requested and/or rebuilt + // the recipient field. Also, check for domain preference when preferFormat + // is unknown. + nsString plaintextDomains; + nsString htmlDomains; + + if (prefBranch) + { + NS_GetUnicharPreferenceWithDefault(prefBranch, "mailnews.plaintext_domains", + EmptyString(), plaintextDomains); + NS_GetUnicharPreferenceWithDefault(prefBranch, "mailnews.html_domains", + EmptyString(), htmlDomains); + } + + // allHTML and allPlain are summary recipient scopes of format preference + // according to address book and send options for recipient-centric Auto-Detect, + // used by Auto-Detect to determine the appropriate message delivery format. + + // allHtml: All recipients prefer HTML. + bool allHtml = true; + + // allPlain: All recipients prefer Plain Text. + bool allPlain = true; + + // Exit the loop early if allHtml and allPlain both decay to false to save us + // some work. + for (int i = 0; i < MAX_OF_RECIPIENT_ARRAY && (allHtml || allPlain); ++i) + { + uint32_t nbrRecipients = recipientsList[i].Length(); + for (uint32_t j = 0; j < nbrRecipients && (allHtml || allPlain); ++j) + { + nsMsgRecipient &recipient = recipientsList[i][j]; + uint32_t preferFormat = nsIAbPreferMailFormat::unknown; + if (recipient.mCard) + { + recipient.mCard->GetPropertyAsUint32(kPreferMailFormatProperty, + &preferFormat); + } + + // if we don't have a prefer format for a recipient, check the domain in + // case we have a format defined for it + if (preferFormat == nsIAbPreferMailFormat::unknown && + (!plaintextDomains.IsEmpty() || !htmlDomains.IsEmpty())) + { + int32_t atPos = recipient.mEmail.FindChar('@'); + if (atPos < 0) + continue; + + nsDependentSubstring emailDomain = Substring(recipient.mEmail, + atPos + 1); + if (IsInDomainList(emailDomain, plaintextDomains)) + preferFormat = nsIAbPreferMailFormat::plaintext; + else if (IsInDomainList(emailDomain, htmlDomains)) + preferFormat = nsIAbPreferMailFormat::html; + } + + // Determine the delivery format preference of this recipient and adjust + // the summary recipient scopes of the message accordingly. + switch (preferFormat) + { + case nsIAbPreferMailFormat::html: + allPlain = false; + break; + + case nsIAbPreferMailFormat::plaintext: + allHtml = false; + break; + + default: // nsIAbPreferMailFormat::unknown + allHtml = false; + allPlain = false; + break; + } + } + } + + // Here's the final part of recipient-centric Auto-Detect logic where we set + // the actual send format (aka delivery format) after analysing recipients' + // format preferences above. + + // If all recipients prefer HTML, then return HTML. + if (allHtml) + { + *result = nsIMsgCompSendFormat::HTML; + return NS_OK; + } + + // If all recipients prefer plaintext, silently strip *all* HTML formatting, + // regardless of (non-)convertibility, and send the message as plaintext. + // **ToDo: UX-error-prevention, UX-wysiwyg: warn against dataloss potential.** + if (allPlain) + { + *result = nsIMsgCompSendFormat::PlainText; + return NS_OK; + } + + // Otherwise, check the preference to see what action we should default to. + // This pref covers all recipient scopes involving prefers-plain (except allplain) + // and prefers-unknown. So we are mixing format conflict resolution options for + // prefers-plain with default format setting for prefers-unknown; not ideal. + int32_t action = nsIMsgCompSendFormat::AskUser; + rv = prefBranch->GetIntPref("mail.default_html_action", &action); + NS_ENSURE_SUCCESS(rv, rv); + + // If the action is a known send format, return the value to send in that format. + // Otherwise, ask the user. + // Note that the preference may default to 0 (Ask), which is not a valid value + // for the following enum. + if (action == nsIMsgCompSendFormat::PlainText || + action == nsIMsgCompSendFormat::HTML || + action == nsIMsgCompSendFormat::Both) + { + *result = action; + return NS_OK; + } + + // At this point, ask the user. + *result = nsIMsgCompSendFormat::AskUser; + return NS_OK; +} + +/* Decides which tags trigger which convertible mode, i.e. here is the logic + for BodyConvertible */ +// Helper function. Parameters are not checked. +nsresult nsMsgCompose::TagConvertible(nsIDOMElement *node, int32_t *_retval) +{ + nsresult rv; + + *_retval = nsIMsgCompConvertible::No; + + uint16_t nodeType; + rv = node->GetNodeType(&nodeType); + if (NS_FAILED(rv)) + return rv; + + nsAutoString element; + rv = node->GetNodeName(element); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIDOMNode> pItem; + + // style attribute on any element can change layout in any way, so that is not convertible. + nsAutoString attribValue; + if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("style"), attribValue)) && + !attribValue.IsEmpty()) + { + *_retval = nsIMsgCompConvertible::No; + return NS_OK; + } + + // moz-* classes are used internally by the editor and mail composition + // (like moz-cite or moz-signature). Those can be discarded. + // But any other ones are unconvertible. Style can be attached to them or any + // other context (e.g. in microformats). + if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("class"), attribValue)) && + !attribValue.IsEmpty() && + !StringBeginsWith(attribValue, NS_LITERAL_STRING("moz-"), nsCaseInsensitiveStringComparator())) + { + *_retval = nsIMsgCompConvertible::No; + return NS_OK; + } + // ID attributes can contain attached style/context or be target of links + // so we should preserve them. + if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("id"), attribValue)) && + !attribValue.IsEmpty()) + { + *_retval = nsIMsgCompConvertible::No; + return NS_OK; + } + if ( // some "simple" elements without "style" attribute + element.LowerCaseEqualsLiteral("br") || + element.LowerCaseEqualsLiteral("p") || + element.LowerCaseEqualsLiteral("pre") || + element.LowerCaseEqualsLiteral("tt") || + element.LowerCaseEqualsLiteral("html") || + element.LowerCaseEqualsLiteral("head") || + element.LowerCaseEqualsLiteral("meta") || + element.LowerCaseEqualsLiteral("title") + ) + { + *_retval = nsIMsgCompConvertible::Plain; + } + else if ( + //element.LowerCaseEqualsLiteral("blockquote") || // see below + element.LowerCaseEqualsLiteral("ul") || + element.LowerCaseEqualsLiteral("ol") || + element.LowerCaseEqualsLiteral("li") || + element.LowerCaseEqualsLiteral("dl") || + element.LowerCaseEqualsLiteral("dt") || + element.LowerCaseEqualsLiteral("dd") + ) + { + *_retval = nsIMsgCompConvertible::Yes; + } + else if ( + //element.LowerCaseEqualsLiteral("a") || // see below + element.LowerCaseEqualsLiteral("h1") || + element.LowerCaseEqualsLiteral("h2") || + element.LowerCaseEqualsLiteral("h3") || + element.LowerCaseEqualsLiteral("h4") || + element.LowerCaseEqualsLiteral("h5") || + element.LowerCaseEqualsLiteral("h6") || + element.LowerCaseEqualsLiteral("hr") || + ( + mConvertStructs + && + ( + element.LowerCaseEqualsLiteral("em") || + element.LowerCaseEqualsLiteral("strong") || + element.LowerCaseEqualsLiteral("code") || + element.LowerCaseEqualsLiteral("b") || + element.LowerCaseEqualsLiteral("i") || + element.LowerCaseEqualsLiteral("u") + ) + ) + ) + { + *_retval = nsIMsgCompConvertible::Altering; + } + else if (element.LowerCaseEqualsLiteral("body")) + { + *_retval = nsIMsgCompConvertible::Plain; + + bool hasAttribute; + nsAutoString color; + if (NS_SUCCEEDED(node->HasAttribute(NS_LITERAL_STRING("background"), &hasAttribute)) + && hasAttribute) // There is a background image + *_retval = nsIMsgCompConvertible::No; + else if (NS_SUCCEEDED(node->HasAttribute(NS_LITERAL_STRING("text"), &hasAttribute)) && + hasAttribute && + NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("text"), color)) && + !color.EqualsLiteral("#000000")) { + *_retval = nsIMsgCompConvertible::Altering; + } + else if (NS_SUCCEEDED(node->HasAttribute(NS_LITERAL_STRING("bgcolor"), &hasAttribute)) && + hasAttribute && + NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("bgcolor"), color)) && + !color.LowerCaseEqualsLiteral("#ffffff")) { + *_retval = nsIMsgCompConvertible::Altering; + } + else if (NS_SUCCEEDED(node->HasAttribute(NS_LITERAL_STRING("dir"), &hasAttribute)) + && hasAttribute) // dir=rtl attributes should not downconvert + *_retval = nsIMsgCompConvertible::No; + + //ignore special color setting for link, vlink and alink at this point. + } + else if (element.LowerCaseEqualsLiteral("blockquote")) + { + // Skip <blockquote type="cite"> + *_retval = nsIMsgCompConvertible::Yes; + + if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("type"), attribValue)) && + attribValue.LowerCaseEqualsLiteral("cite")) + { + *_retval = nsIMsgCompConvertible::Plain; + } + } + else if ( + element.LowerCaseEqualsLiteral("div") || + element.LowerCaseEqualsLiteral("span") || + element.LowerCaseEqualsLiteral("a") + ) + { + /* Do some special checks for these tags. They are inside this |else if| + for performance reasons */ + + // Maybe, it's an <a> element inserted by another recognizer (e.g. 4.x') + if (element.LowerCaseEqualsLiteral("a")) + { + /* Ignore anchor tag, if the URI is the same as the text + (as inserted by recognizers) */ + *_retval = nsIMsgCompConvertible::Altering; + + nsAutoString hrefValue; + bool hasChild; + if (NS_SUCCEEDED(node->GetAttribute(NS_LITERAL_STRING("href"), hrefValue)) && + NS_SUCCEEDED(node->HasChildNodes(&hasChild)) && hasChild) + { + nsCOMPtr<nsIDOMNodeList> children; + if (NS_SUCCEEDED(node->GetChildNodes(getter_AddRefs(children))) && + children && + NS_SUCCEEDED(children->Item(0, getter_AddRefs(pItem))) && + pItem) + { + nsAutoString textValue; + if (NS_SUCCEEDED(pItem->GetNodeValue(textValue)) && + textValue == hrefValue) + *_retval = nsIMsgCompConvertible::Plain; + } + } + } + + // Lastly, test, if it is just a "simple" <div> or <span> + else if ( + element.LowerCaseEqualsLiteral("div") || + element.LowerCaseEqualsLiteral("span") + ) + { + *_retval = nsIMsgCompConvertible::Plain; + } + } + + return rv; +} + +nsresult nsMsgCompose::_NodeTreeConvertible(nsIDOMElement *node, int32_t *_retval) +{ + NS_ENSURE_TRUE(node && _retval, NS_ERROR_NULL_POINTER); + + nsresult rv; + int32_t result; + + // Check this node + rv = TagConvertible(node, &result); + if (NS_FAILED(rv)) + return rv; + + // Walk tree recursively to check the children + bool hasChild; + if (NS_SUCCEEDED(node->HasChildNodes(&hasChild)) && hasChild) + { + nsCOMPtr<nsIDOMNodeList> children; + if (NS_SUCCEEDED(node->GetChildNodes(getter_AddRefs(children))) + && children) + { + uint32_t nbrOfElements; + rv = children->GetLength(&nbrOfElements); + for (uint32_t i = 0; NS_SUCCEEDED(rv) && i < nbrOfElements; i++) + { + nsCOMPtr<nsIDOMNode> pItem; + if (NS_SUCCEEDED(children->Item(i, getter_AddRefs(pItem))) + && pItem) + { + // We assume all nodes that are not elements are convertible, + // so only test elements. + nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(pItem); + if (domElement) { + int32_t curresult; + rv = _NodeTreeConvertible(domElement, &curresult); + + if (NS_SUCCEEDED(rv) && curresult > result) + result = curresult; + } + } + } + } + } + + *_retval = result; + return rv; +} + +NS_IMETHODIMP +nsMsgCompose::BodyConvertible(int32_t *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + NS_ENSURE_STATE(m_editor); + + nsCOMPtr<nsIDOMDocument> rootDocument; + nsresult rv = m_editor->GetDocument(getter_AddRefs(rootDocument)); + if (NS_FAILED(rv) || !rootDocument) + return rv; + + // get the top level element, which contains <html> + nsCOMPtr<nsIDOMElement> rootElement; + rv = rootDocument->GetDocumentElement(getter_AddRefs(rootElement)); + if (NS_FAILED(rv) || !rootElement) + return rv; + + return _NodeTreeConvertible(rootElement, _retval); +} + +NS_IMETHODIMP +nsMsgCompose::GetIdentity(nsIMsgIdentity **aIdentity) +{ + NS_ENSURE_ARG_POINTER(aIdentity); + NS_IF_ADDREF(*aIdentity = m_identity); + return NS_OK; +} + +/** + * Position above the quote, that is either <blockquote> or + * <div class="moz-cite-prefix"> or <div class="moz-forward-container"> + * in an inline-forwarded message. + */ +nsresult +nsMsgCompose::MoveToAboveQuote(void) +{ + nsCOMPtr<nsIDOMElement> rootElement; + nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement)); + if (NS_FAILED(rv) || !rootElement) { + return rv; + } + + nsCOMPtr<nsIDOMNode> node; + nsAutoString attributeName; + nsAutoString attributeValue; + nsAutoString tagLocalName; + attributeName.AssignLiteral("class"); + + rv = rootElement->GetFirstChild(getter_AddRefs(node)); + while (NS_SUCCEEDED(rv) && node) { + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(node); + if (element) { + // First check for <blockquote>. This will most likely not trigger + // since well-behaved quotes are preceded by a cite prefix. + node->GetLocalName(tagLocalName); + if (tagLocalName.EqualsLiteral("blockquote")) { + break; + } + + // Get the class value. + element->GetAttribute(attributeName, attributeValue); + + // Now check for the cite prefix, so an element with + // class="moz-cite-prefix". + if (attributeValue.Find("moz-cite-prefix", true) != kNotFound) { + break; + } + + // Next check for forwarded content. + // The forwarded part is inside an element with + // class="moz-forward-container". + if (attributeValue.Find("moz-forward-container", true) != kNotFound) { + break; + } + } + + rv = node->GetNextSibling(getter_AddRefs(node)); + if (NS_FAILED(rv) || !node) { + // No further siblings found, so we didn't find what we were looking for. + rv = NS_OK; + node = nullptr; + break; + } + } + + // Now position. If no quote was found, we position to the very front. + int32_t offset = 0; + if (node) { + rv = GetChildOffset(node, rootElement, offset); + if (NS_FAILED(rv)) { + return rv; + } + } + nsCOMPtr<nsISelection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + if (selection) + rv = selection->Collapse(rootElement, offset); + + return rv; +} + +/** + * nsEditor::BeginningOfDocument() will position to the beginning of the document + * before the first editable element. It will position into a container. + * We need to be at the very front. + */ +nsresult +nsMsgCompose::MoveToBeginningOfDocument(void) +{ + nsCOMPtr<nsIDOMElement> rootElement; + nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement)); + if (NS_FAILED(rv) || !rootElement) { + return rv; + } + + nsCOMPtr<nsISelection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + if (selection) + rv = selection->Collapse(rootElement, 0); + + return rv; +} + +/** + * M-C's nsEditor::EndOfDocument() will position to the end of the document + * but it will position into a container. We really need to position + * after the last container so we don't accidentally position into a + * <blockquote>. That's why we use our own function. + */ +nsresult +nsMsgCompose::MoveToEndOfDocument(void) +{ + int32_t offset; + nsCOMPtr<nsIDOMElement> rootElement; + nsCOMPtr<nsIDOMNode> lastNode; + nsresult rv = m_editor->GetRootElement(getter_AddRefs(rootElement)); + if (NS_FAILED(rv) || !rootElement) { + return rv; + } + + rv = rootElement->GetLastChild(getter_AddRefs(lastNode)); + if (NS_FAILED(rv) || !lastNode) { + return rv; + } + + rv = GetChildOffset(lastNode, rootElement, offset); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsISelection> selection; + m_editor->GetSelection(getter_AddRefs(selection)); + if (selection) + rv = selection->Collapse(rootElement, offset + 1); + + return rv; +} + +NS_IMETHODIMP +nsMsgCompose::SetIdentity(nsIMsgIdentity *aIdentity) +{ + NS_ENSURE_ARG_POINTER(aIdentity); + + m_identity = aIdentity; + + nsresult rv; + + if (! m_editor) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIDOMElement> rootElement; + rv = m_editor->GetRootElement(getter_AddRefs(rootElement)); + if (NS_FAILED(rv) || !rootElement) + return rv; + + //First look for the current signature, if we have one + nsCOMPtr<nsIDOMNode> lastNode; + nsCOMPtr<nsIDOMNode> node; + nsCOMPtr<nsIDOMNode> tempNode; + nsAutoString tagLocalName; + + rv = rootElement->GetLastChild(getter_AddRefs(lastNode)); + if (NS_SUCCEEDED(rv) && lastNode) + { + node = lastNode; + // In html, the signature is inside an element with + // class="moz-signature" + bool signatureFound = false; + nsAutoString attributeName; + attributeName.AssignLiteral("class"); + + do + { + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(node); + if (element) + { + nsAutoString attributeValue; + + rv = element->GetAttribute(attributeName, attributeValue); + + if (attributeValue.Find("moz-signature", true) != kNotFound) { + signatureFound = true; + break; + } + } + } while (!signatureFound && + node && + NS_SUCCEEDED(node->GetPreviousSibling(getter_AddRefs(node)))); + + if (signatureFound) + { + m_editor->BeginTransaction(); + node->GetPreviousSibling(getter_AddRefs(tempNode)); + rv = m_editor->DeleteNode(node); + if (NS_FAILED(rv)) + { + m_editor->EndTransaction(); + return rv; + } + + // Also, remove the <br> right before the signature. + if (tempNode) + { + tempNode->GetLocalName(tagLocalName); + if (tagLocalName.EqualsLiteral("br")) + m_editor->DeleteNode(tempNode); + } + m_editor->EndTransaction(); + } + } + + if (!CheckIncludeSignaturePrefs(aIdentity)) + return NS_OK; + + // Then add the new one if needed + nsAutoString aSignature; + + // No delimiter needed if not a compose window + bool isQuoted; + switch (mType) + { + case nsIMsgCompType::New : + case nsIMsgCompType::NewsPost : + case nsIMsgCompType::MailToUrl : + case nsIMsgCompType::ForwardAsAttachment : + isQuoted = false; + break; + default : + isQuoted = true; + break; + } + + ProcessSignature(aIdentity, isQuoted, &aSignature); + + if (!aSignature.IsEmpty()) + { + TranslateLineEnding(aSignature); + + m_editor->BeginTransaction(); + int32_t reply_on_top = 0; + bool sig_bottom = true; + aIdentity->GetReplyOnTop(&reply_on_top); + aIdentity->GetSigBottom(&sig_bottom); + bool sigOnTop = (reply_on_top == 1 && !sig_bottom); + if (sigOnTop && isQuoted) { + rv = MoveToAboveQuote(); + } else { + // Note: New messages aren't quoted so we always move to the end. + rv = MoveToEndOfDocument(); + } + + if (NS_SUCCEEDED(rv)) { + if (m_composeHTML) { + nsCOMPtr<nsIHTMLEditor> htmlEditor (do_QueryInterface(m_editor)); + rv = htmlEditor->InsertHTML(aSignature); + } else { + nsCOMPtr<nsIPlaintextEditor> textEditor (do_QueryInterface(m_editor)); + rv = textEditor->InsertLineBreak(); + InsertDivWrappedTextAtSelection(aSignature, NS_LITERAL_STRING("moz-signature")); + } + } + m_editor->EndTransaction(); + } + + return rv; +} + +NS_IMETHODIMP nsMsgCompose::CheckCharsetConversion(nsIMsgIdentity *identity, char **fallbackCharset, bool *_retval) +{ + NS_ENSURE_ARG_POINTER(identity); + NS_ENSURE_ARG_POINTER(_retval); + + // Kept around for legacy reasons. This method is supposed to check that the + // headers can be converted to the appropriate charset, but we don't support + // encoding headers to non-UTF-8, so this is now moot. + if (fallbackCharset) + *fallbackCharset = nullptr; + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP nsMsgCompose::GetDeliverMode(MSG_DeliverMode* aDeliverMode) +{ + NS_ENSURE_ARG_POINTER(aDeliverMode); + *aDeliverMode = mDeliverMode; + return NS_OK; +} + +nsMsgMailList::nsMsgMailList(nsIAbDirectory* directory) : + mDirectory(directory) +{ + mDirectory->GetDirName(mName); + mDirectory->GetDescription(mDescription); + + if (mDescription.IsEmpty()) + mDescription = mName; + + mDirectory = directory; +} diff --git a/mailnews/compose/src/nsMsgCompose.h b/mailnews/compose/src/nsMsgCompose.h new file mode 100644 index 000000000..19609228f --- /dev/null +++ b/mailnews/compose/src/nsMsgCompose.h @@ -0,0 +1,246 @@ +/* -*- 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 _nsMsgCompose_H_ +#define _nsMsgCompose_H_ + +#include "nsIMsgCompose.h" +#include "nsCOMArray.h" +#include "nsTObserverArray.h" +#include "nsWeakReference.h" +#include "nsMsgCompFields.h" +#include "nsIOutputStream.h" +#include "nsIMsgQuote.h" +#include "nsIMsgCopyServiceListener.h" +#include "nsIBaseWindow.h" +#include "nsIAbDirectory.h" +#include "nsIWebProgressListener.h" +#include "nsIMimeConverter.h" +#include "nsIUnicodeDecoder.h" +#include "nsIMsgFolder.h" +#include "nsIDOMNode.h" +#include "mozIDOMWindow.h" + +// Forward declares +class QuotingOutputStreamListener; +class nsMsgComposeSendListener; +class nsIEditorMailSupport; +class nsIRDFService; +class nsIArray; +struct nsMsgMailList; + +class nsMsgCompose : public nsIMsgCompose, public nsSupportsWeakReference +{ + public: + + nsMsgCompose(); + + /* this macro defines QueryInterface, AddRef and Release for this class */ + NS_DECL_THREADSAFE_ISUPPORTS + + /*** nsIMsgCompose pure virtual functions */ + NS_DECL_NSIMSGCOMPOSE + + /* nsIMsgSendListener interface */ + NS_DECL_NSIMSGSENDLISTENER + +protected: + virtual ~nsMsgCompose(); + + // Deal with quoting issues... + nsresult QuoteOriginalMessage(); // New template + nsresult SetQuotingToFollow(bool aVal); + nsresult ConvertHTMLToText(nsIFile *aSigFile, nsString &aSigData); + nsresult ConvertTextToHTML(nsIFile *aSigFile, nsString &aSigData); + bool IsEmbeddedObjectSafe(const char * originalScheme, + const char * originalHost, + const char * originalPath, + nsIDOMNode * object); + nsresult ResetUrisForEmbeddedObjects(); + nsresult TagEmbeddedObjects(nsIEditorMailSupport *aMailEditor); + + nsCString mQuoteCharset; + nsCString mOriginalMsgURI; // used so we can mark message disposition flags after we send the message + + int32_t mWhatHolder; + + nsresult LoadDataFromFile(nsIFile *file, + nsString &sigData, + bool aAllowUTF8 = true, + bool aAllowUTF16 = true); + + bool CheckIncludeSignaturePrefs(nsIMsgIdentity *identity); + //m_folderName to store the value of the saved drafts folder. + nsCString m_folderName; + void InsertDivWrappedTextAtSelection(const nsAString &aText, + const nsAString &classStr); + + protected: + nsresult CreateMessage(const char * originalMsgURI, MSG_ComposeType type, nsIMsgCompFields* compFields); + void CleanUpRecipients(nsString& recipients); + nsresult GetABDirAndMailLists(const nsACString& aDirUri, + nsCOMArray<nsIAbDirectory>& aDirArray, + nsTArray<nsMsgMailList>& aMailListArray); + nsresult ResolveMailList(nsIAbDirectory* aMailList, + nsCOMArray<nsIAbDirectory>& allDirectoriesArray, + nsTArray<nsMsgMailList>& allMailListArray, + nsTArray<nsMsgMailList>& mailListResolved, + nsTArray<nsMsgRecipient>& aListMembers); + nsresult TagConvertible(nsIDOMElement *node, int32_t *_retval); + nsresult _NodeTreeConvertible(nsIDOMElement *node, int32_t *_retval); + nsresult MoveToAboveQuote(void); + nsresult MoveToBeginningOfDocument(void); + nsresult MoveToEndOfDocument(void); + nsresult ReplaceFileURLs(nsAutoString &sigData); + nsresult DataURLForFileURL(const nsAString &aFileURL, nsAString &aDataURL); + +// 3 = To, Cc, Bcc +#define MAX_OF_RECIPIENT_ARRAY 3 + typedef nsTArray<nsMsgRecipient> RecipientsArray[MAX_OF_RECIPIENT_ARRAY]; + /** + * This method parses the compose fields and associates email addresses with + * the relevant cards from the address books. + */ + nsresult LookupAddressBook(RecipientsArray &recipientList); + bool IsLastWindow(); + + // Helper function. Parameters are not checked. + bool mConvertStructs; // for TagConvertible + + nsCOMPtr<nsIEditor> m_editor; + mozIDOMWindowProxy *m_window; + nsCOMPtr<nsIDocShell> mDocShell; + nsCOMPtr<nsIBaseWindow> m_baseWindow; + nsMsgCompFields *m_compFields; + nsCOMPtr<nsIMsgIdentity> m_identity; + bool m_composeHTML; + QuotingOutputStreamListener *mQuoteStreamListener; + nsCOMPtr<nsIOutputStream> mBaseStream; + + nsCOMPtr<nsIMsgSend> mMsgSend; // for composition back end + nsCOMPtr<nsIMsgProgress> mProgress; // use by the back end to report progress to the front end + + // Deal with quoting issues... + nsString mCiteReference; + nsCOMPtr<nsIMsgQuote> mQuote; + bool mQuotingToFollow; // Quoting indicator + MSG_ComposeType mType; // Message type + bool mCharsetOverride; + bool mAnswerDefaultCharset; + bool mDeleteDraft; + nsMsgDispositionState mDraftDisposition; + nsCOMPtr <nsIMsgDBHdr> mOrigMsgHdr; + + nsCString mSmtpPassword; + nsCString mHtmlToQuote; + + nsTObserverArray<nsCOMPtr<nsIMsgComposeStateListener> > mStateListeners; + nsTObserverArray<nsCOMPtr<nsIMsgSendListener> > mExternalSendListeners; + + bool mInsertingQuotedContent; + MSG_DeliverMode mDeliverMode; // nsIMsgCompDeliverMode long. + + friend class QuotingOutputStreamListener; + friend class nsMsgComposeSendListener; +}; + +//////////////////////////////////////////////////////////////////////////////////// +// THIS IS THE CLASS THAT IS THE STREAM Listener OF THE HTML OUPUT +// FROM LIBMIME. THIS IS FOR QUOTING +//////////////////////////////////////////////////////////////////////////////////// +class QuotingOutputStreamListener : public nsIMsgQuotingOutputStreamListener +{ +public: + QuotingOutputStreamListener(const char *originalMsgURI, + nsIMsgDBHdr *origMsgHdr, + bool quoteHeaders, + bool headersOnly, + nsIMsgIdentity *identity, + nsIMsgQuote* msgQuote, + bool charsetFixed, + bool quoteOriginal, + const nsACString& htmlToQuote); + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIMSGQUOTINGOUTPUTSTREAMLISTENER + + NS_IMETHOD SetComposeObj(nsIMsgCompose *obj); + NS_IMETHOD ConvertToPlainText(bool formatflowed, + bool delsp, + bool formatted, + bool disallowBreaks); + NS_IMETHOD InsertToCompose(nsIEditor *aEditor, bool aHTMLEditor); + NS_IMETHOD AppendToMsgBody(const nsCString &inStr); + +private: + virtual ~QuotingOutputStreamListener(); + nsWeakPtr mWeakComposeObj; + nsString mMsgBody; + nsString mCitePrefix; + nsString mSignature; + bool mQuoteHeaders; + bool mHeadersOnly; + bool mCharsetFixed; + nsCOMPtr<nsIMsgQuote> mQuote; + nsCOMPtr<nsIMimeHeaders> mHeaders; + nsCOMPtr<nsIMsgIdentity> mIdentity; + nsCOMPtr<nsIMsgDBHdr> mOrigMsgHdr; + nsString mCiteReference; + nsCOMPtr<nsIMimeConverter> mMimeConverter; + nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder; + int32_t mUnicodeBufferCharacterLength; + char16_t* mUnicodeConversionBuffer; + bool mQuoteOriginal; + nsCString mHtmlToQuote; +}; + +//////////////////////////////////////////////////////////////////////////////////// +// This is the listener class for the send operation. We have to create this class +// to listen for message send completion and eventually notify the caller +//////////////////////////////////////////////////////////////////////////////////// +class nsMsgComposeSendListener : public nsIMsgComposeSendListener, public nsIMsgSendListener, public nsIMsgCopyServiceListener, public nsIWebProgressListener +{ +public: + nsMsgComposeSendListener(void); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsIMsgComposeSendListener interface + NS_DECL_NSIMSGCOMPOSESENDLISTENER + + // nsIMsgSendListener interface + NS_DECL_NSIMSGSENDLISTENER + + // nsIMsgCopyServiceListener interface + NS_DECL_NSIMSGCOPYSERVICELISTENER + + // nsIWebProgressListener interface + NS_DECL_NSIWEBPROGRESSLISTENER + + nsresult RemoveCurrentDraftMessage(nsIMsgCompose *compObj, bool calledByCopy); + nsresult GetMsgFolder(nsIMsgCompose *compObj, nsIMsgFolder **msgFolder); + +private: + virtual ~nsMsgComposeSendListener(); + nsWeakPtr mWeakComposeObj; + MSG_DeliverMode mDeliverMode; +}; + +/****************************************************************************** + * nsMsgMailList + ******************************************************************************/ +struct nsMsgMailList +{ + explicit nsMsgMailList(nsIAbDirectory* directory); + + nsString mName; + nsString mDescription; + nsCOMPtr<nsIAbDirectory> mDirectory; +}; + +#endif /* _nsMsgCompose_H_ */ diff --git a/mailnews/compose/src/nsMsgComposeContentHandler.cpp b/mailnews/compose/src/nsMsgComposeContentHandler.cpp new file mode 100644 index 000000000..cc52bcb67 --- /dev/null +++ b/mailnews/compose/src/nsMsgComposeContentHandler.cpp @@ -0,0 +1,125 @@ +/* -*- 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 "nsMsgComposeContentHandler.h" +#include "nsMsgComposeService.h" +#include "nsMsgBaseCID.h" +#include "nsMsgCompCID.h" +#include "nsIChannel.h" +#include "nsIURI.h" +#include "plstr.h" +#include "nsServiceManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsPIDOMWindow.h" +#include "mozIDOMWindow.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsNetUtil.h" +#include "nsIMsgFolder.h" +#include "nsIMsgIncomingServer.h" +#include "nsIMsgAccountManager.h" + +static NS_DEFINE_CID(kMsgComposeServiceCID, NS_MSGCOMPOSESERVICE_CID); + +nsMsgComposeContentHandler::nsMsgComposeContentHandler() +{ +} + +// The following macro actually implement addref, release and query interface +// for our component. +NS_IMPL_ISUPPORTS(nsMsgComposeContentHandler, nsIContentHandler) + +nsMsgComposeContentHandler::~nsMsgComposeContentHandler() +{ +} + +// Try to get an appropriate nsIMsgIdentity by going through the window, getting +// the document's URI, then the corresponding nsIMsgDBHdr. Then find the server +// associated with that header and get the first identity for it. +nsresult nsMsgComposeContentHandler::GetBestIdentity( + nsIInterfaceRequestor* aWindowContext, nsIMsgIdentity **aIdentity) +{ + nsresult rv; + + nsCOMPtr<mozIDOMWindowProxy> domWindow = do_GetInterface(aWindowContext); + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + nsCOMPtr<nsPIDOMWindowOuter> window = nsPIDOMWindowOuter::From(domWindow); + + nsAutoString documentURIString; + rv = window->GetDoc()->GetDocumentURI(documentURIString); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> documentURI; + rv = NS_NewURI(getter_AddRefs(documentURI), documentURIString); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgMessageUrl> msgURI = do_QueryInterface(documentURI); + if (!msgURI) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = msgURI->GetMessageHeader(getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> folder; + rv = msgHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + + // nsIMsgDBHdrs from .eml messages have a null folder, so bail out if that's + // the case. + if (!folder) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = folder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService( + NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = accountManager->GetFirstIdentityForServer(server, aIdentity); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} + +NS_IMETHODIMP nsMsgComposeContentHandler::HandleContent(const char * aContentType, + nsIInterfaceRequestor* aWindowContext, nsIRequest *request) +{ + nsresult rv = NS_OK; + if (!request) + return NS_ERROR_NULL_POINTER; + + // First of all, get the content type and make sure it is a content type we + // know how to handle! + if (PL_strcasecmp(aContentType, "application/x-mailto") == 0) { + nsCOMPtr<nsIMsgIdentity> identity; + + if (aWindowContext) + GetBestIdentity(aWindowContext, getter_AddRefs(identity)); + + nsCOMPtr<nsIURI> aUri; + nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request); + if(!aChannel) return NS_ERROR_FAILURE; + + rv = aChannel->GetURI(getter_AddRefs(aUri)); + if (aUri) + { + nsCOMPtr<nsIMsgComposeService> composeService = + do_GetService(kMsgComposeServiceCID, &rv); + if (NS_SUCCEEDED(rv)) + rv = composeService->OpenComposeWindowWithURI(nullptr, aUri, identity); + } + } else { + // The content-type was not application/x-mailto... + return NS_ERROR_WONT_HANDLE_CONTENT; + } + + return rv; +} diff --git a/mailnews/compose/src/nsMsgComposeContentHandler.h b/mailnews/compose/src/nsMsgComposeContentHandler.h new file mode 100644 index 000000000..360a83608 --- /dev/null +++ b/mailnews/compose/src/nsMsgComposeContentHandler.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIContentHandler.h" +#include "nsIMsgIdentity.h" + +class nsMsgComposeContentHandler : public nsIContentHandler +{ +public: + nsMsgComposeContentHandler(); + + NS_DECL_ISUPPORTS + NS_DECL_NSICONTENTHANDLER +private: + virtual ~nsMsgComposeContentHandler(); + nsresult GetBestIdentity(nsIInterfaceRequestor* aWindowContext, + nsIMsgIdentity **identity); +}; diff --git a/mailnews/compose/src/nsMsgComposeParams.cpp b/mailnews/compose/src/nsMsgComposeParams.cpp new file mode 100644 index 000000000..46f964cee --- /dev/null +++ b/mailnews/compose/src/nsMsgComposeParams.cpp @@ -0,0 +1,170 @@ +/* -*- 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 "nsMsgComposeParams.h" + +nsMsgComposeParams::nsMsgComposeParams() : + mType(nsIMsgCompType::New), + mFormat(nsIMsgCompFormat::Default), + mBodyIsLink(false) +{ +} + +/* the following macro actually implement addref, release and query interface for our component. */ +NS_IMPL_ISUPPORTS(nsMsgComposeParams, nsIMsgComposeParams) + +nsMsgComposeParams::~nsMsgComposeParams() +{ +} + +/* attribute MSG_ComposeType type; */ +NS_IMETHODIMP nsMsgComposeParams::GetType(MSG_ComposeType *aType) +{ + NS_ENSURE_ARG_POINTER(aType); + + *aType = mType; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetType(MSG_ComposeType aType) +{ + mType = aType; + return NS_OK; +} + +/* attribute MSG_ComposeFormat format; */ +NS_IMETHODIMP nsMsgComposeParams::GetFormat(MSG_ComposeFormat *aFormat) +{ + NS_ENSURE_ARG_POINTER(aFormat); + + *aFormat = mFormat; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetFormat(MSG_ComposeFormat aFormat) +{ + mFormat = aFormat; + return NS_OK; +} + +/* attribute string originalMsgURI; */ +NS_IMETHODIMP nsMsgComposeParams::GetOriginalMsgURI(char * *aOriginalMsgURI) +{ + NS_ENSURE_ARG_POINTER(aOriginalMsgURI); + + *aOriginalMsgURI = ToNewCString(mOriginalMsgUri); + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetOriginalMsgURI(const char * aOriginalMsgURI) +{ + mOriginalMsgUri = aOriginalMsgURI; + return NS_OK; +} + +/* attribute nsIMsgIdentity identity; */ +NS_IMETHODIMP nsMsgComposeParams::GetIdentity(nsIMsgIdentity * *aIdentity) +{ + NS_ENSURE_ARG_POINTER(aIdentity); + NS_IF_ADDREF(*aIdentity = mIdentity); + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeParams::SetIdentity(nsIMsgIdentity * aIdentity) +{ + mIdentity = aIdentity; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeParams::SetOrigMsgHdr(nsIMsgDBHdr *aMsgHdr) +{ + mOrigMsgHdr = aMsgHdr; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeParams::GetOrigMsgHdr(nsIMsgDBHdr * *aMsgHdr) +{ + NS_ENSURE_ARG_POINTER(aMsgHdr); + NS_IF_ADDREF(*aMsgHdr = mOrigMsgHdr); + return NS_OK; +} + +/* attribute ACString htmlToQuote; */ +NS_IMETHODIMP nsMsgComposeParams::GetHtmlToQuote(nsACString& aHtmlToQuote) +{ + aHtmlToQuote = mHtmlToQuote; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetHtmlToQuote(const nsACString& aHtmlToQuote) +{ + mHtmlToQuote = aHtmlToQuote; + return NS_OK; +} + +/* attribute nsIMsgCompFields composeFields; */ +NS_IMETHODIMP nsMsgComposeParams::GetComposeFields(nsIMsgCompFields * *aComposeFields) +{ + NS_ENSURE_ARG_POINTER(aComposeFields); + + if (mComposeFields) + { + *aComposeFields = mComposeFields; + NS_ADDREF(*aComposeFields); + } + else + *aComposeFields = nullptr; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetComposeFields(nsIMsgCompFields * aComposeFields) +{ + mComposeFields = aComposeFields; + return NS_OK; +} + +/* attribute boolean bodyIsLink; */ +NS_IMETHODIMP nsMsgComposeParams::GetBodyIsLink(bool *aBodyIsLink) +{ + NS_ENSURE_ARG_POINTER(aBodyIsLink); + + *aBodyIsLink = mBodyIsLink; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetBodyIsLink(bool aBodyIsLink) +{ + mBodyIsLink = aBodyIsLink; + return NS_OK; +} + +/* attribute nsIMsgSendLisneter sendListener; */ +NS_IMETHODIMP nsMsgComposeParams::GetSendListener(nsIMsgSendListener * *aSendListener) +{ + NS_ENSURE_ARG_POINTER(aSendListener); + + if (mSendListener) + { + *aSendListener = mSendListener; + NS_ADDREF(*aSendListener); + } + else + *aSendListener = nullptr; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetSendListener(nsIMsgSendListener * aSendListener) +{ + mSendListener = aSendListener; + return NS_OK; +} + +/* attribute string smtpPassword; */ +NS_IMETHODIMP nsMsgComposeParams::GetSmtpPassword(char * *aSmtpPassword) +{ + NS_ENSURE_ARG_POINTER(aSmtpPassword); + + *aSmtpPassword = ToNewCString(mSMTPPassword); + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeParams::SetSmtpPassword(const char * aSmtpPassword) +{ + mSMTPPassword = aSmtpPassword; + return NS_OK; +} + diff --git a/mailnews/compose/src/nsMsgComposeParams.h b/mailnews/compose/src/nsMsgComposeParams.h new file mode 100644 index 000000000..00eaaa335 --- /dev/null +++ b/mailnews/compose/src/nsMsgComposeParams.h @@ -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 "nsIMsgComposeParams.h" +#include "nsStringGlue.h" +#include "nsIMsgHdr.h" +#include "nsCOMPtr.h" +class nsMsgComposeParams : public nsIMsgComposeParams +{ +public: + nsMsgComposeParams(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGCOMPOSEPARAMS + +private: + virtual ~nsMsgComposeParams(); + MSG_ComposeType mType; + MSG_ComposeFormat mFormat; + nsCString mOriginalMsgUri; + nsCOMPtr<nsIMsgIdentity> mIdentity; + nsCOMPtr<nsIMsgCompFields> mComposeFields; + bool mBodyIsLink; + nsCOMPtr<nsIMsgSendListener> mSendListener; + nsCString mSMTPPassword; + nsCOMPtr<nsIMsgDBHdr> mOrigMsgHdr; + nsCString mHtmlToQuote; +}; diff --git a/mailnews/compose/src/nsMsgComposeProgressParams.cpp b/mailnews/compose/src/nsMsgComposeProgressParams.cpp new file mode 100644 index 000000000..23c5507c5 --- /dev/null +++ b/mailnews/compose/src/nsMsgComposeProgressParams.cpp @@ -0,0 +1,46 @@ +/* -*- 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 "nsMsgComposeProgressParams.h" +#include "nsServiceManagerUtils.h" + +NS_IMPL_ISUPPORTS(nsMsgComposeProgressParams, nsIMsgComposeProgressParams) + +nsMsgComposeProgressParams::nsMsgComposeProgressParams() : + m_deliveryMode(nsIMsgCompDeliverMode::Now) +{ +} + +nsMsgComposeProgressParams::~nsMsgComposeProgressParams() +{ +} + +/* attribute wstring subject; */ +NS_IMETHODIMP nsMsgComposeProgressParams::GetSubject(char16_t * *aSubject) +{ + NS_ENSURE_ARG(aSubject); + + *aSubject = ToNewUnicode(m_subject); + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeProgressParams::SetSubject(const char16_t * aSubject) +{ + m_subject = aSubject; + return NS_OK; +} + +/* attribute MSG_DeliverMode deliveryMode; */ +NS_IMETHODIMP nsMsgComposeProgressParams::GetDeliveryMode(MSG_DeliverMode *aDeliveryMode) +{ + NS_ENSURE_ARG(aDeliveryMode); + + *aDeliveryMode = m_deliveryMode; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeProgressParams::SetDeliveryMode(MSG_DeliverMode aDeliveryMode) +{ + m_deliveryMode = aDeliveryMode; + return NS_OK; +} diff --git a/mailnews/compose/src/nsMsgComposeProgressParams.h b/mailnews/compose/src/nsMsgComposeProgressParams.h new file mode 100644 index 000000000..6d9598f16 --- /dev/null +++ b/mailnews/compose/src/nsMsgComposeProgressParams.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIMsgComposeProgressParams.h" + +class nsMsgComposeProgressParams : public nsIMsgComposeProgressParams +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGCOMPOSEPROGRESSPARAMS + + nsMsgComposeProgressParams(); + +private: + virtual ~nsMsgComposeProgressParams(); + nsString m_subject; + MSG_DeliverMode m_deliveryMode; +}; diff --git a/mailnews/compose/src/nsMsgComposeService.cpp b/mailnews/compose/src/nsMsgComposeService.cpp new file mode 100644 index 000000000..944f7dbe3 --- /dev/null +++ b/mailnews/compose/src/nsMsgComposeService.cpp @@ -0,0 +1,1479 @@ +/* -*- 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 "nsMsgComposeService.h" +#include "nsMsgCompCID.h" +#include "nsIMsgSend.h" +#include "nsIServiceManager.h" +#include "nsIObserverService.h" +#include "nsIMsgIdentity.h" +#include "nsISmtpUrl.h" +#include "nsIURI.h" +#include "nsMsgI18N.h" +#include "nsIMsgComposeParams.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIWindowWatcher.h" +#include "mozIDOMWindow.h" +#include "nsIContentViewer.h" +#include "nsIMsgWindow.h" +#include "nsIDocShell.h" +#include "nsPIDOMWindow.h" +#include "nsIDOMDocument.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIDOMElement.h" +#include "nsIXULWindow.h" +#include "nsIWindowMediator.h" +#include "nsIDocShellTreeItem.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIBaseWindow.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsMsgBaseCID.h" +#include "nsIMsgAccountManager.h" +#include "nsIMimeMiscStatus.h" +#include "nsIStreamConverter.h" +#include "nsMsgMimeCID.h" +#include "nsToolkitCompsCID.h" +#include "nsNetUtil.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIMsgDatabase.h" +#include "nsIDocumentEncoder.h" +#include "nsContentCID.h" +#include "nsISelection.h" +#include "nsUTF8Utils.h" +#include "nsILineBreaker.h" +#include "nsLWBrkCIID.h" +#include "mozilla/Services.h" +#include "mimemoz2.h" +#include "nsIArray.h" +#include "nsArrayUtils.h" + +#ifdef MSGCOMP_TRACE_PERFORMANCE +#include "mozilla/Logging.h" +#include "nsIMsgHdr.h" +#include "nsIMsgMessageService.h" +#include "nsMsgUtils.h" +#endif + +#include "nsICommandLine.h" +#include "nsIAppStartup.h" +#include "nsMsgUtils.h" +#include "nsIPrincipal.h" + +#ifdef XP_WIN32 +#include <windows.h> +#include <shellapi.h> +#include "nsIWidget.h" +#endif + +#define DEFAULT_CHROME "chrome://messenger/content/messengercompose/messengercompose.xul" + +#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION "mailnews.reply_quoting_selection" +#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION_MULTI_WORD "mailnews.reply_quoting_selection.multi_word" +#define PREF_MAILNEWS_REPLY_QUOTING_SELECTION_ONLY_IF "mailnews.reply_quoting_selection.only_if_chars" + +#define MAIL_ROOT_PREF "mail." +#define MAILNEWS_ROOT_PREF "mailnews." +#define HTMLDOMAINUPDATE_VERSION_PREF_NAME "global_html_domains.version" +#define HTMLDOMAINUPDATE_DOMAINLIST_PREF_NAME "global_html_domains" +#define USER_CURRENT_HTMLDOMAINLIST_PREF_NAME "html_domains" +#define USER_CURRENT_PLAINTEXTDOMAINLIST_PREF_NAME "plaintext_domains" +#define DOMAIN_DELIMITER ',' + +#ifdef MSGCOMP_TRACE_PERFORMANCE +static PRLogModuleInfo *MsgComposeLogModule = nullptr; + +static uint32_t GetMessageSizeFromURI(const char * originalMsgURI) +{ + uint32_t msgSize = 0; + + if (originalMsgURI && *originalMsgURI) + { + nsCOMPtr <nsIMsgDBHdr> originalMsgHdr; + GetMsgDBHdrFromURI(originalMsgURI, getter_AddRefs(originalMsgHdr)); + if (originalMsgHdr) + originalMsgHdr->GetMessageSize(&msgSize); + } + + return msgSize; +} +#endif + +nsMsgComposeService::nsMsgComposeService() +{ + +// Defaulting the value of mLogComposePerformance to FALSE to prevent logging. + mLogComposePerformance = false; +#ifdef MSGCOMP_TRACE_PERFORMANCE + if (!MsgComposeLogModule) + MsgComposeLogModule = PR_NewLogModule("msgcompose"); + + mStartTime = PR_IntervalNow(); + mPreviousTime = mStartTime; +#endif + +} + +NS_IMPL_ISUPPORTS(nsMsgComposeService, + nsIMsgComposeService, + ICOMMANDLINEHANDLER, + nsISupportsWeakReference) + +nsMsgComposeService::~nsMsgComposeService() +{ + mOpenComposeWindows.Clear(); +} + +nsresult nsMsgComposeService::Init() +{ + nsresult rv = NS_OK; + + Reset(); + + AddGlobalHtmlDomains(); + // Since the compose service should only be initialized once, we can + // be pretty sure there aren't any existing compose windows open. + MsgCleanupTempFiles("nsmail", "tmp"); + MsgCleanupTempFiles("nsemail", "html"); + MsgCleanupTempFiles("nscopy", "tmp"); + return rv; +} + +void nsMsgComposeService::Reset() +{ + mOpenComposeWindows.Clear(); + + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) + prefs->GetBoolPref("mailnews.logComposePerformance", &mLogComposePerformance); +} + +// Function to open a message compose window and pass an nsIMsgComposeParams +// parameter to it. +NS_IMETHODIMP +nsMsgComposeService::OpenComposeWindowWithParams(const char *chrome, + nsIMsgComposeParams *params) +{ + NS_ENSURE_ARG_POINTER(params); +#ifdef MSGCOMP_TRACE_PERFORMANCE + if(mLogComposePerformance) + { + TimeStamp("Start opening the window", true); + } +#endif + + nsresult rv; + + NS_ENSURE_ARG_POINTER(params); + + //Use default identity if no identity has been specified + nsCOMPtr<nsIMsgIdentity> identity; + params->GetIdentity(getter_AddRefs(identity)); + if (!identity) + { + GetDefaultIdentity(getter_AddRefs(identity)); + params->SetIdentity(identity); + } + + // Create a new window. + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + if (!wwatch) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsISupportsInterfacePointer> msgParamsWrapper = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + msgParamsWrapper->SetData(params); + msgParamsWrapper->SetDataIID(&NS_GET_IID(nsIMsgComposeParams)); + + nsCOMPtr<mozIDOMWindowProxy> newWindow; + rv = wwatch->OpenWindow(0, chrome && *chrome ? chrome : DEFAULT_CHROME, + "_blank", "all,chrome,dialog=no,status,toolbar", msgParamsWrapper, + getter_AddRefs(newWindow)); + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeService::DetermineComposeHTML(nsIMsgIdentity *aIdentity, MSG_ComposeFormat aFormat, bool *aComposeHTML) +{ + NS_ENSURE_ARG_POINTER(aComposeHTML); + + *aComposeHTML = true; + switch (aFormat) + { + case nsIMsgCompFormat::HTML: + *aComposeHTML = true; + break; + case nsIMsgCompFormat::PlainText: + *aComposeHTML = false; + break; + + default: + nsCOMPtr<nsIMsgIdentity> identity = aIdentity; + if (!identity) + GetDefaultIdentity(getter_AddRefs(identity)); + + if (identity) + { + identity->GetComposeHtml(aComposeHTML); + if (aFormat == nsIMsgCompFormat::OppositeOfDefault) + *aComposeHTML = !*aComposeHTML; + } + else + { + // default identity not found. Use the mail.html_compose pref to determine + // message compose type (HTML or PlainText). + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefs) + { + nsresult rv; + bool useHTMLCompose; + rv = prefs->GetBoolPref(MAIL_ROOT_PREF "html_compose", &useHTMLCompose); + if (NS_SUCCEEDED(rv)) + *aComposeHTML = useHTMLCompose; + } + } + break; + } + + return NS_OK; +} + +nsresult +nsMsgComposeService::GetOrigWindowSelection(MSG_ComposeType type, nsIMsgWindow *aMsgWindow, nsACString& aSelHTML) +{ + nsresult rv; + + // Good hygiene + aSelHTML.Truncate(); + + // Get the pref to see if we even should do reply quoting selection + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool replyQuotingSelection; + rv = prefs->GetBoolPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION, &replyQuotingSelection); + NS_ENSURE_SUCCESS(rv, rv); + if (!replyQuotingSelection) + return NS_ERROR_ABORT; + + // Now delve down in to the message to get the HTML representation of the selection + nsCOMPtr<nsIDocShell> rootDocShell; + rv = aMsgWindow->GetRootDocShell(getter_AddRefs(rootDocShell)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocShellTreeItem> childAsItem; + rv = rootDocShell->FindChildWithName(NS_LITERAL_STRING("messagepane"), + true, false, nullptr, nullptr, getter_AddRefs(childAsItem)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(childAsItem, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<mozIDOMWindowProxy> domWindow(do_GetInterface(childAsItem)); + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + nsCOMPtr<nsPIDOMWindowOuter> privateWindow = nsPIDOMWindowOuter::From(domWindow); + nsCOMPtr<nsISelection> sel = privateWindow->GetSelection(); + NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE); + + bool requireMultipleWords = true; + nsAutoCString charsOnlyIf; + prefs->GetBoolPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION_MULTI_WORD, &requireMultipleWords); + prefs->GetCharPref(PREF_MAILNEWS_REPLY_QUOTING_SELECTION_ONLY_IF, getter_Copies(charsOnlyIf)); + if (sel && (requireMultipleWords || !charsOnlyIf.IsEmpty())) + { + nsAutoString selPlain; + rv = sel->ToString(selPlain); + NS_ENSURE_SUCCESS(rv, rv); + + // If "mailnews.reply_quoting_selection.multi_word" is on, then there must be at least + // two words selected in order to quote just the selected text + if (requireMultipleWords) + { + if (selPlain.IsEmpty()) + return NS_ERROR_ABORT; + + nsCOMPtr<nsILineBreaker> lineBreaker = do_GetService(NS_LBRK_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) + { + const uint32_t length = selPlain.Length(); + const char16_t* unicodeStr = selPlain.get(); + int32_t endWordPos = lineBreaker->Next(unicodeStr, length, 0); + + // If there's not even one word, then there's not multiple words + if (endWordPos == NS_LINEBREAKER_NEED_MORE_TEXT) + return NS_ERROR_ABORT; + + // If after the first word is only space, then there's not multiple words + const char16_t* end; + for (end = unicodeStr + endWordPos; NS_IsSpace(*end); end++) + ; + if (!*end) + return NS_ERROR_ABORT; + } + } + + if (!charsOnlyIf.IsEmpty()) + { + if (MsgFindCharInSet(selPlain, charsOnlyIf.get()) < 0) + return NS_ERROR_ABORT; + } + } + + nsCOMPtr<nsIContentViewer> contentViewer; + rv = docShell->GetContentViewer(getter_AddRefs(contentViewer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDOMDocument> domDocument; + rv = contentViewer->GetDOMDocument(getter_AddRefs(domDocument)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocumentEncoder> docEncoder(do_CreateInstance(NS_HTMLCOPY_ENCODER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = docEncoder->Init(domDocument, NS_LITERAL_STRING("text/html"), 0); + NS_ENSURE_SUCCESS(rv, rv); + + rv = docEncoder->SetSelection(sel); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString selHTML; + rv = docEncoder->EncodeToString(selHTML); + NS_ENSURE_SUCCESS(rv, rv); + + // Now remove <span class="moz-txt-citetags">> </span>. + nsAutoCString html(NS_ConvertUTF16toUTF8(selHTML).get()); + int32_t spanInd = html.Find("<span class=\"moz-txt-citetags\">"); + while (spanInd != kNotFound) { + nsAutoCString right0(Substring(html, spanInd)); + int32_t endInd = right0.Find("</span>"); + if (endInd == kNotFound) + break; // oops, where is the closing tag gone? + nsAutoCString right1(Substring(html, spanInd + endInd + 7)); + html.SetLength(spanInd); + html.Append(right1); + spanInd = html.Find("<span class=\"moz-txt-citetags\">"); + } + + aSelHTML.Assign(html); + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeService::OpenComposeWindow(const char *msgComposeWindowURL, nsIMsgDBHdr *origMsgHdr, const char *originalMsgURI, + MSG_ComposeType type, MSG_ComposeFormat format, nsIMsgIdentity * aIdentity, nsIMsgWindow *aMsgWindow) +{ + nsresult rv; + + // Check for any reply type that wants to ignore the quote. + bool ignoreQuote = false; + if (type >= nsIMsgCompType::ReplyIgnoreQuote) { + type -= nsIMsgCompType::ReplyIgnoreQuote; + ignoreQuote = true; + } + + nsCOMPtr<nsIMsgIdentity> identity = aIdentity; + if (!identity) + GetDefaultIdentity(getter_AddRefs(identity)); + + /* Actually, the only way to implement forward inline is to simulate a template message. + Maybe one day when we will have more time we can change that + */ + if (type == nsIMsgCompType::ForwardInline || type == nsIMsgCompType::Draft || type == nsIMsgCompType::Template + || type == nsIMsgCompType::ReplyWithTemplate || type == nsIMsgCompType::Redirect) + { + nsAutoCString uriToOpen(originalMsgURI); + uriToOpen += (uriToOpen.FindChar('?') == kNotFound) ? '?' : '&'; + uriToOpen.Append("fetchCompleteMessage=true"); + if (type == nsIMsgCompType::Redirect) + uriToOpen.Append("&redirect=true"); + + return LoadDraftOrTemplate(uriToOpen, type == nsIMsgCompType::ForwardInline || type == nsIMsgCompType::Draft ? + nsMimeOutput::nsMimeMessageDraftOrTemplate : nsMimeOutput::nsMimeMessageEditorTemplate, + identity, originalMsgURI, origMsgHdr, type == nsIMsgCompType::ForwardInline, + format == nsIMsgCompFormat::OppositeOfDefault, aMsgWindow); + } + + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams (do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && pMsgComposeParams) + { + nsCOMPtr<nsIMsgCompFields> pMsgCompFields (do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && pMsgCompFields) + { + pMsgComposeParams->SetType(type); + pMsgComposeParams->SetFormat(format); + pMsgComposeParams->SetIdentity(identity); + + // When doing a reply (except with a template) see if there's a selection that we should quote + if (!ignoreQuote && + (type == nsIMsgCompType::Reply || + type == nsIMsgCompType::ReplyAll || + type == nsIMsgCompType::ReplyToSender || + type == nsIMsgCompType::ReplyToGroup || + type == nsIMsgCompType::ReplyToSenderAndGroup || + type == nsIMsgCompType::ReplyToList)) + { + nsAutoCString selHTML; + if (NS_SUCCEEDED(GetOrigWindowSelection(type, aMsgWindow, selHTML))) + pMsgComposeParams->SetHtmlToQuote(selHTML); + } + + if (originalMsgURI && *originalMsgURI) + { + if (type == nsIMsgCompType::NewsPost) + { + nsAutoCString newsURI(originalMsgURI); + nsAutoCString group; + nsAutoCString host; + + int32_t slashpos = newsURI.RFindChar('/'); + if (slashpos > 0 ) + { + // uri is "[s]news://host[:port]/group" + host = StringHead(newsURI, slashpos); + group = Substring(newsURI, slashpos + 1); + + } + else + group = originalMsgURI; + + nsAutoCString unescapedName; + MsgUnescapeString(group, + nsINetUtil::ESCAPE_URL_FILE_BASENAME | nsINetUtil::ESCAPE_URL_FORCED, + unescapedName); + pMsgCompFields->SetNewsgroups(NS_ConvertUTF8toUTF16(unescapedName)); + pMsgCompFields->SetNewspostUrl(host.get()); + } + else + { + pMsgComposeParams->SetOriginalMsgURI(originalMsgURI); + pMsgComposeParams->SetOrigMsgHdr(origMsgHdr); + } + } + + pMsgComposeParams->SetComposeFields(pMsgCompFields); + + if(mLogComposePerformance) + { +#ifdef MSGCOMP_TRACE_PERFORMANCE + // ducarroz, properly fix this in the case of new message (not a reply) + if (type != nsIMsgCompType::NewsPost) { + char buff[256]; + sprintf(buff, "Start opening the window, message size = %d", GetMessageSizeFromURI(originalMsgURI)); + TimeStamp(buff, true); + } +#endif + }//end if(mLogComposePerformance) + + rv = OpenComposeWindowWithParams(msgComposeWindowURL, pMsgComposeParams); + } + } + return rv; +} + +NS_IMETHODIMP nsMsgComposeService::GetParamsForMailto(nsIURI * aURI, nsIMsgComposeParams ** aParams) +{ + nsresult rv = NS_OK; + if (aURI) + { + nsCOMPtr<nsIMailtoUrl> aMailtoUrl; + rv = aURI->QueryInterface(NS_GET_IID(nsIMailtoUrl), getter_AddRefs(aMailtoUrl)); + if (NS_SUCCEEDED(rv)) + { + MSG_ComposeFormat requestedComposeFormat = nsIMsgCompFormat::Default; + nsCString toPart; + nsCString ccPart; + nsCString bccPart; + nsCString subjectPart; + nsCString bodyPart; + nsCString newsgroup; + nsCString refPart; + nsCString HTMLBodyPart; + + aMailtoUrl->GetMessageContents(toPart, ccPart, bccPart, subjectPart, + bodyPart, HTMLBodyPart, refPart, + newsgroup, &requestedComposeFormat); + + nsAutoString sanitizedBody; + + bool composeHTMLFormat; + DetermineComposeHTML(NULL, requestedComposeFormat, &composeHTMLFormat); + + // If there was an 'html-body' param, finding it will have requested + // HTML format in GetMessageContents, so we try to use it first. If it's + // empty, but we are composing in HTML because of the user's prefs, the + // 'body' param needs to be escaped, since it's supposed to be plain + // text, but it then doesn't need to sanitized. + nsString rawBody; + if (HTMLBodyPart.IsEmpty()) + { + if (composeHTMLFormat) + { + char *escaped = MsgEscapeHTML(bodyPart.get()); + if (!escaped) + return NS_ERROR_OUT_OF_MEMORY; + + CopyUTF8toUTF16(nsDependentCString(escaped), sanitizedBody); + free(escaped); + } + else + CopyUTF8toUTF16(bodyPart, rawBody); + } + else + CopyUTF8toUTF16(HTMLBodyPart, rawBody); + + if (!rawBody.IsEmpty() && composeHTMLFormat) + { + //For security reason, we must sanitize the message body before accepting any html... + + rv = HTMLSanitize(rawBody, sanitizedBody); // from mimemoz2.h + + if (NS_FAILED(rv)) + { + // Something went horribly wrong with parsing for html format + // in the body. Set composeHTMLFormat to false so we show the + // plain text mail compose. + composeHTMLFormat = false; + } + } + + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams (do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && pMsgComposeParams) + { + pMsgComposeParams->SetType(nsIMsgCompType::MailToUrl); + pMsgComposeParams->SetFormat(composeHTMLFormat ? nsIMsgCompFormat::HTML : nsIMsgCompFormat::PlainText); + + + nsCOMPtr<nsIMsgCompFields> pMsgCompFields (do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv)); + if (pMsgCompFields) + { + //ugghh more conversion work!!!! + pMsgCompFields->SetTo(NS_ConvertUTF8toUTF16(toPart)); + pMsgCompFields->SetCc(NS_ConvertUTF8toUTF16(ccPart)); + pMsgCompFields->SetBcc(NS_ConvertUTF8toUTF16(bccPart)); + pMsgCompFields->SetNewsgroups(NS_ConvertUTF8toUTF16(newsgroup)); + pMsgCompFields->SetReferences(refPart.get()); + pMsgCompFields->SetSubject(NS_ConvertUTF8toUTF16(subjectPart)); + pMsgCompFields->SetBody(composeHTMLFormat ? sanitizedBody : rawBody); + pMsgComposeParams->SetComposeFields(pMsgCompFields); + + NS_ADDREF(*aParams = pMsgComposeParams); + return NS_OK; + } + } // if we created msg compose params.... + } // if we had a mailto url + } // if we had a url... + + // if we got here we must have encountered an error + *aParams = nullptr; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsMsgComposeService::OpenComposeWindowWithURI(const char * aMsgComposeWindowURL, nsIURI * aURI, nsIMsgIdentity *identity) +{ + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams; + nsresult rv = GetParamsForMailto(aURI, getter_AddRefs(pMsgComposeParams)); + if (NS_SUCCEEDED(rv)) { + pMsgComposeParams->SetIdentity(identity); + rv = OpenComposeWindowWithParams(aMsgComposeWindowURL, pMsgComposeParams); + } + return rv; +} + +NS_IMETHODIMP nsMsgComposeService::InitCompose(nsIMsgComposeParams *aParams, + mozIDOMWindowProxy *aWindow, + nsIDocShell *aDocShell, + nsIMsgCompose **_retval) +{ + nsresult rv; + nsCOMPtr<nsIMsgCompose> msgCompose = + do_CreateInstance(NS_MSGCOMPOSE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = msgCompose->Initialize(aParams, aWindow, aDocShell); + NS_ENSURE_SUCCESS(rv,rv); + + NS_IF_ADDREF(*_retval = msgCompose); + return rv; +} + +NS_IMETHODIMP +nsMsgComposeService::GetDefaultIdentity(nsIMsgIdentity **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + *_retval = nullptr; + + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccount> defaultAccount; + rv = accountManager->GetDefaultAccount(getter_AddRefs(defaultAccount)); + NS_ENSURE_SUCCESS(rv, rv); + + return defaultAccount->GetDefaultIdentity(_retval); +} + +/* readonly attribute boolean logComposePerformance; */ +NS_IMETHODIMP nsMsgComposeService::GetLogComposePerformance(bool *aLogComposePerformance) +{ + *aLogComposePerformance = mLogComposePerformance; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeService::TimeStamp(const char * label, bool resetTime) +{ + if (!mLogComposePerformance) + return NS_OK; + +#ifdef MSGCOMP_TRACE_PERFORMANCE + + PRIntervalTime now; + + if (resetTime) + { + MOZ_LOG(MsgComposeLogModule, mozilla::LogLevel::Info, ("\n[process]: [totalTime][deltaTime]\n--------------------\n")); + + mStartTime = PR_IntervalNow(); + mPreviousTime = mStartTime; + now = mStartTime; + } + else + now = PR_IntervalNow(); + + PRIntervalTime totalTime = PR_IntervalToMilliseconds(now - mStartTime); + PRIntervalTime deltaTime = PR_IntervalToMilliseconds(now - mPreviousTime); + + MOZ_LOG(MsgComposeLogModule, mozilla::LogLevel::Info, ("[%3.2f][%3.2f] - %s\n", +((double)totalTime/1000.0) + 0.005, ((double)deltaTime/1000.0) + 0.005, label)); + + mPreviousTime = now; +#endif + return NS_OK; +} + +class nsMsgTemplateReplyHelper final: public nsIStreamListener, + public nsIUrlListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURLLISTENER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + + nsMsgTemplateReplyHelper(); + + nsCOMPtr<nsIMsgDBHdr> mHdrToReplyTo; + nsCOMPtr<nsIMsgDBHdr> mTemplateHdr; + nsCOMPtr<nsIMsgWindow> mMsgWindow; + nsCOMPtr<nsIMsgIdentity> mIdentity; + nsCString mTemplateBody; + bool mInMsgBody; + char mLastBlockChars[3]; + +private: + ~nsMsgTemplateReplyHelper(); +}; + +NS_IMPL_ISUPPORTS(nsMsgTemplateReplyHelper, + nsIStreamListener, + nsIRequestObserver, + nsIUrlListener) + +nsMsgTemplateReplyHelper::nsMsgTemplateReplyHelper() +{ + mInMsgBody = false; + memset(mLastBlockChars, 0, sizeof(mLastBlockChars)); +} + +nsMsgTemplateReplyHelper::~nsMsgTemplateReplyHelper() +{ +} + + +NS_IMETHODIMP nsMsgTemplateReplyHelper::OnStartRunningUrl(nsIURI *aUrl) +{ + return NS_OK; +} + +NS_IMETHODIMP nsMsgTemplateReplyHelper::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode) +{ + NS_ENSURE_SUCCESS(aExitCode, aExitCode); + nsresult rv; + nsCOMPtr<nsPIDOMWindowOuter> parentWindow; + if (mMsgWindow) + { + nsCOMPtr<nsIDocShell> docShell; + rv = mMsgWindow->GetRootDocShell(getter_AddRefs(docShell)); + NS_ENSURE_SUCCESS(rv, rv); + parentWindow = do_GetInterface(docShell); + NS_ENSURE_TRUE(parentWindow, NS_ERROR_FAILURE); + } + + // create the compose params object + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams (do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv)); + if (NS_FAILED(rv) || (!pMsgComposeParams) ) return rv ; + nsCOMPtr<nsIMsgCompFields> compFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv) ; + + nsCString replyTo; + mHdrToReplyTo->GetStringProperty("replyTo", getter_Copies(replyTo)); + if (replyTo.IsEmpty()) + mHdrToReplyTo->GetAuthor(getter_Copies(replyTo)); + compFields->SetTo(NS_ConvertUTF8toUTF16(replyTo)); + + nsString body; + nsString templateSubject, replySubject; + + mHdrToReplyTo->GetMime2DecodedSubject(replySubject); + mTemplateHdr->GetMime2DecodedSubject(templateSubject); + nsString subject(NS_LITERAL_STRING("Auto: ")); // RFC 3834 3.1.5. + subject.Append(templateSubject); + if (!replySubject.IsEmpty()) + { + subject.Append(NS_LITERAL_STRING(" (was: ")); + subject.Append(replySubject); + subject.Append(NS_LITERAL_STRING(")")); + } + + compFields->SetSubject(subject); + compFields->SetRawHeader("Auto-Submitted", NS_LITERAL_CSTRING("auto-replied"), nullptr); + + nsCString charset; + rv = mTemplateHdr->GetCharset(getter_Copies(charset)); + NS_ENSURE_SUCCESS(rv, rv); + compFields->SetCharacterSet(charset.get()); + rv = nsMsgI18NConvertToUnicode(charset.get(), mTemplateBody, body); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "couldn't convert templ body to unicode"); + compFields->SetBody(body); + + nsCString msgUri; + nsCOMPtr <nsIMsgFolder> folder; + mHdrToReplyTo->GetFolder(getter_AddRefs(folder)); + folder->GetUriForMsg(mHdrToReplyTo, msgUri); + // populate the compose params + pMsgComposeParams->SetType(nsIMsgCompType::ReplyWithTemplate); + pMsgComposeParams->SetFormat(nsIMsgCompFormat::Default); + pMsgComposeParams->SetIdentity(mIdentity); + pMsgComposeParams->SetComposeFields(compFields); + pMsgComposeParams->SetOriginalMsgURI(msgUri.get()); + + // create the nsIMsgCompose object to send the object + nsCOMPtr<nsIMsgCompose> pMsgCompose (do_CreateInstance(NS_MSGCOMPOSE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + /** initialize nsIMsgCompose, Send the message, wait for send completion response **/ + + rv = pMsgCompose->Initialize(pMsgComposeParams, parentWindow, nullptr); + NS_ENSURE_SUCCESS(rv,rv); + + return pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, mIdentity, nullptr, nullptr, nullptr) ; +} + +NS_IMETHODIMP +nsMsgTemplateReplyHelper::OnStartRequest(nsIRequest* request, nsISupports* aSupport) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgTemplateReplyHelper::OnStopRequest(nsIRequest* request, nsISupports* aSupport, + nsresult status) +{ + if (NS_SUCCEEDED(status)) + { + // now we've got the message body in mTemplateBody - + // need to set body in compose params and send the reply. + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgTemplateReplyHelper::OnDataAvailable(nsIRequest* request, + nsISupports* aSupport, + nsIInputStream* inStream, + uint64_t srcOffset, + uint32_t count) +{ + nsresult rv = NS_OK; + + char readBuf[1024]; + + uint64_t available; + uint32_t readCount; + uint32_t maxReadCount = sizeof(readBuf) - 1; + + rv = inStream->Available(&available); + while (NS_SUCCEEDED(rv) && available > 0) + { + uint32_t bodyOffset = 0, readOffset = 0; + if (!mInMsgBody && mLastBlockChars[0]) + { + memcpy(readBuf, mLastBlockChars, 3); + readOffset = 3; + maxReadCount -= 3; + } + if (maxReadCount > available) + maxReadCount = (uint32_t)available; + memset(readBuf, 0, sizeof(readBuf)); + rv = inStream->Read(readBuf + readOffset, maxReadCount, &readCount); + available -= readCount; + readCount += readOffset; + // we're mainly interested in the msg body, so we need to + // find the header/body delimiter of a blank line. A blank line + // looks like <CR><CR>, <LF><LF>, or <CRLF><CRLF> + if (!mInMsgBody) + { + for (uint32_t charIndex = 0; charIndex < readCount && !bodyOffset; charIndex++) + { + if (readBuf[charIndex] == '\r' || readBuf[charIndex] == '\n') + { + if (charIndex + 1 < readCount) + { + if (readBuf[charIndex] == readBuf[charIndex + 1]) + { + // got header+body separator + bodyOffset = charIndex + 2; + break; + } + else if ((charIndex + 3 < readCount) && !strncmp(readBuf + charIndex, "\r\n\r\n", 4)) + { + bodyOffset = charIndex + 4; + break; + } + } + } + } + mInMsgBody = bodyOffset != 0; + if (!mInMsgBody && readCount > 3) // still in msg hdrs + strncpy(mLastBlockChars, readBuf + readCount - 3, 3); + } + mTemplateBody.Append(readBuf + bodyOffset); + } + return NS_OK; +} + + +NS_IMETHODIMP nsMsgComposeService::ReplyWithTemplate(nsIMsgDBHdr *aMsgHdr, const char *templateUri, + nsIMsgWindow *aMsgWindow, nsIMsgIncomingServer *aServer) +{ + // To reply with template, we need the message body of the template. + // I think we're going to need to stream the template message to ourselves, + // and construct the body, and call setBody on the compFields. + nsresult rv; + nsCOMPtr <nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccount> account; + rv = accountManager->FindAccountForServer(aServer, getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIArray> identities; + rv = account->GetIdentities(getter_AddRefs(identities)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString recipients; + aMsgHdr->GetRecipients(getter_Copies(recipients)); + + nsAutoCString ccList; + aMsgHdr->GetCcList(getter_Copies(ccList)); + + // Go through the identities to see to whom this was addressed. + // In case we get no match, this is likely a list/bulk/bcc/spam mail and we + // shouldn't reply. RFC 3834 2. + nsCOMPtr<nsIMsgIdentity> identity; // identity to reply from + uint32_t count = 0; + identities->GetLength(&count); + for (uint32_t i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgIdentity> anIdentity(do_QueryElementAt(identities, i, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString identityEmail; + anIdentity->GetEmail(identityEmail); + + if (recipients.Find(identityEmail, CaseInsensitiveCompare) != kNotFound || + ccList.Find(identityEmail, CaseInsensitiveCompare) != kNotFound) + { + identity = anIdentity; + break; + } + } + if (!identity) // Found no match -> don't reply. + return NS_ERROR_ABORT; + + RefPtr<nsMsgTemplateReplyHelper> helper = new nsMsgTemplateReplyHelper; + + helper->mHdrToReplyTo = aMsgHdr; + helper->mMsgWindow = aMsgWindow; + helper->mIdentity = identity; + + nsAutoCString replyTo; + aMsgHdr->GetStringProperty("replyTo", getter_Copies(replyTo)); + if (replyTo.IsEmpty()) + aMsgHdr->GetAuthor(getter_Copies(replyTo)); + if (replyTo.IsEmpty()) + return NS_ERROR_FAILURE; // nowhere to send the reply + + nsCOMPtr <nsIMsgFolder> templateFolder; + nsCOMPtr <nsIMsgDatabase> templateDB; + nsCString templateMsgHdrUri; + const char * query = PL_strstr(templateUri, "?messageId="); + if (!query) + return NS_ERROR_FAILURE; + + nsAutoCString folderUri(Substring(templateUri, query)); + rv = GetExistingFolder(folderUri, getter_AddRefs(templateFolder)); + NS_ENSURE_SUCCESS(rv, rv); + rv = templateFolder->GetMsgDatabase(getter_AddRefs(templateDB)); + NS_ENSURE_SUCCESS(rv, rv); + + const char *subject = PL_strstr(templateUri, "&subject="); + if (subject) + { + const char *subjectEnd = subject + strlen(subject); + nsAutoCString messageId(Substring(query + 11, subject)); + nsAutoCString subjectString(Substring(subject + 9, subjectEnd)); + templateDB->GetMsgHdrForMessageID(messageId.get(), getter_AddRefs(helper->mTemplateHdr)); + if (helper->mTemplateHdr) + templateFolder->GetUriForMsg(helper->mTemplateHdr, templateMsgHdrUri); + // to use the subject, we'd need to expose a method to find a message by subject, + // or painfully iterate through messages...We'll try to make the message-id + // not change when saving a template first. + } + if (templateMsgHdrUri.IsEmpty()) + { + // ### probably want to return a specific error and + // have the calling code disable the filter. + NS_ASSERTION(false, "failed to get msg hdr"); + return NS_ERROR_FAILURE; + } + // we need to convert the template uri, which is of the form + // <folder uri>?messageId=<messageId>&subject=<subject> + nsCOMPtr <nsIMsgMessageService> msgService; + rv = GetMessageServiceFromURI(templateMsgHdrUri, getter_AddRefs(msgService)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> listenerSupports; + helper->QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(listenerSupports)); + + nsCOMPtr<nsIURI> dummyNull; + rv = msgService->StreamMessage(templateMsgHdrUri.get(), listenerSupports, + aMsgWindow, helper, + false, // convert data + EmptyCString(), false, getter_AddRefs(dummyNull)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> folder; + aMsgHdr->GetFolder(getter_AddRefs(folder)); + if (!folder) + return NS_ERROR_NULL_POINTER; + + // We're sending a new message. Conceptually it's a reply though, so mark the + // original message as replied. + return folder->AddMessageDispositionState(aMsgHdr, nsIMsgFolder::nsMsgDispositionState_Replied); +} + +NS_IMETHODIMP +nsMsgComposeService::ForwardMessage(const nsAString &forwardTo, + nsIMsgDBHdr *aMsgHdr, + nsIMsgWindow *aMsgWindow, + nsIMsgIncomingServer *aServer, + uint32_t aForwardType) +{ + NS_ENSURE_ARG_POINTER(aMsgHdr); + + nsresult rv; + if (aForwardType == nsIMsgComposeService::kForwardAsDefault) + { + int32_t forwardPref = 0; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + prefBranch->GetIntPref("mail.forward_message_mode", &forwardPref); + // 0=default as attachment 2=forward as inline with attachments, + // (obsolete 4.x value)1=forward as quoted (mapped to 2 in mozilla) + aForwardType = forwardPref == 0 ? nsIMsgComposeService::kForwardAsAttachment : + nsIMsgComposeService::kForwardInline; + } + nsCString msgUri; + + nsCOMPtr<nsIMsgFolder> folder; + aMsgHdr->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_TRUE(folder, NS_ERROR_NULL_POINTER); + + folder->GetUriForMsg(aMsgHdr, msgUri); + + nsAutoCString uriToOpen(msgUri); + uriToOpen += (uriToOpen.FindChar('?') == kNotFound) ? '?' : '&'; + uriToOpen.Append("fetchCompleteMessage=true"); + + // get the MsgIdentity for the above key using AccountManager + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService (NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAccount> account; + nsCOMPtr<nsIMsgIdentity> identity; + + rv = accountManager->FindAccountForServer(aServer, getter_AddRefs(account)); + NS_ENSURE_SUCCESS(rv, rv); + rv = account->GetDefaultIdentity(getter_AddRefs(identity)); + // Use default identity if no identity has been found on this account + if (NS_FAILED(rv) || !identity) + { + rv = GetDefaultIdentity(getter_AddRefs(identity)); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (aForwardType == nsIMsgComposeService::kForwardInline) + return RunMessageThroughMimeDraft(uriToOpen, + nsMimeOutput::nsMimeMessageDraftOrTemplate, + identity, + uriToOpen.get(), aMsgHdr, + true, forwardTo, + false, aMsgWindow); + + nsCOMPtr<mozIDOMWindowProxy> parentWindow; + if (aMsgWindow) + { + nsCOMPtr<nsIDocShell> docShell; + rv = aMsgWindow->GetRootDocShell(getter_AddRefs(docShell)); + NS_ENSURE_SUCCESS(rv, rv); + parentWindow = do_GetInterface(docShell); + NS_ENSURE_TRUE(parentWindow, NS_ERROR_FAILURE); + } + // create the compose params object + nsCOMPtr<nsIMsgComposeParams> pMsgComposeParams (do_CreateInstance(NS_MSGCOMPOSEPARAMS_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgCompFields> compFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv); + + compFields->SetTo(forwardTo); + // populate the compose params + pMsgComposeParams->SetType(nsIMsgCompType::ForwardAsAttachment); + pMsgComposeParams->SetFormat(nsIMsgCompFormat::Default); + pMsgComposeParams->SetIdentity(identity); + pMsgComposeParams->SetComposeFields(compFields); + pMsgComposeParams->SetOriginalMsgURI(uriToOpen.get()); + // create the nsIMsgCompose object to send the object + nsCOMPtr<nsIMsgCompose> pMsgCompose (do_CreateInstance(NS_MSGCOMPOSE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + /** initialize nsIMsgCompose, Send the message, wait for send completion response **/ + rv = pMsgCompose->Initialize(pMsgComposeParams, parentWindow, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pMsgCompose->SendMsg(nsIMsgSend::nsMsgDeliverNow, identity, nullptr, nullptr, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + // nsMsgCompose::ProcessReplyFlags usually takes care of marking messages + // as forwarded. ProcessReplyFlags is normally called from + // nsMsgComposeSendListener::OnStopSending but for this case the msgCompose + // object is not set so ProcessReplyFlags won't get called. + // Therefore, let's just mark it here instead. + return folder->AddMessageDispositionState(aMsgHdr, nsIMsgFolder::nsMsgDispositionState_Forwarded); +} + +nsresult nsMsgComposeService::AddGlobalHtmlDomains() +{ + + nsresult rv; + nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefs->GetBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIPrefBranch> defaultsPrefBranch; + rv = prefs->GetDefaultBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(defaultsPrefBranch)); + NS_ENSURE_SUCCESS(rv,rv); + + /** + * Check to see if we need to add any global domains. + * If so, make sure the following prefs are added to mailnews.js + * + * 1. pref("mailnews.global_html_domains.version", version number); + * This pref registers the current version in the user prefs file. A default value is stored + * in mailnews file. Depending the changes we plan to make we can move the default version number. + * Comparing version number from user's prefs file and the default one from mailnews.js, we + * can effect ppropriate changes. + * + * 2. pref("mailnews.global_html_domains", <comma separated domain list>); + * This pref contains the list of html domains that ISP can add to make that user's contain all + * of these under the HTML domains in the Mail&NewsGrpus|Send Format under global preferences. + */ + int32_t htmlDomainListCurrentVersion, htmlDomainListDefaultVersion; + rv = prefBranch->GetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME, &htmlDomainListCurrentVersion); + NS_ENSURE_SUCCESS(rv,rv); + + rv = defaultsPrefBranch->GetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME, &htmlDomainListDefaultVersion); + NS_ENSURE_SUCCESS(rv,rv); + + // Update the list as needed + if (htmlDomainListCurrentVersion <= htmlDomainListDefaultVersion) { + // Get list of global domains need to be added + nsCString globalHtmlDomainList; + rv = prefBranch->GetCharPref(HTMLDOMAINUPDATE_DOMAINLIST_PREF_NAME, getter_Copies(globalHtmlDomainList)); + + if (NS_SUCCEEDED(rv) && !globalHtmlDomainList.IsEmpty()) { + nsTArray<nsCString> domainArray; + + // Get user's current HTML domain set for send format + nsCString currentHtmlDomainList; + rv = prefBranch->GetCharPref(USER_CURRENT_HTMLDOMAINLIST_PREF_NAME, getter_Copies(currentHtmlDomainList)); + NS_ENSURE_SUCCESS(rv,rv); + + nsAutoCString newHtmlDomainList(currentHtmlDomainList); + // Get the current html domain list into new list var + ParseString(currentHtmlDomainList, DOMAIN_DELIMITER, domainArray); + + // Get user's current Plaintext domain set for send format + nsCString currentPlaintextDomainList; + rv = prefBranch->GetCharPref(USER_CURRENT_PLAINTEXTDOMAINLIST_PREF_NAME, getter_Copies(currentPlaintextDomainList)); + NS_ENSURE_SUCCESS(rv,rv); + + // Get the current plaintext domain list into new list var + ParseString(currentPlaintextDomainList, DOMAIN_DELIMITER, domainArray); + + size_t i = domainArray.Length(); + if (i > 0) { + // Append each domain in the preconfigured html domain list + globalHtmlDomainList.StripWhitespace(); + ParseString(globalHtmlDomainList, DOMAIN_DELIMITER, domainArray); + + // Now add each domain that does not already appear in + // the user's current html or plaintext domain lists + for (; i < domainArray.Length(); i++) { + if (domainArray.IndexOf(domainArray[i]) == i) { + if (!newHtmlDomainList.IsEmpty()) + newHtmlDomainList += DOMAIN_DELIMITER; + newHtmlDomainList += domainArray[i]; + } + } + } + else + { + // User has no domains listed either in html or plain text category. + // Assign the global list to be the user's current html domain list + newHtmlDomainList = globalHtmlDomainList; + } + + // Set user's html domain pref with the updated list + rv = prefBranch->SetCharPref(USER_CURRENT_HTMLDOMAINLIST_PREF_NAME, newHtmlDomainList.get()); + NS_ENSURE_SUCCESS(rv,rv); + + // Increase the version to avoid running the update code unless needed (based on default version) + rv = prefBranch->SetIntPref(HTMLDOMAINUPDATE_VERSION_PREF_NAME, htmlDomainListCurrentVersion + 1); + NS_ENSURE_SUCCESS(rv,rv); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeService::RegisterComposeDocShell(nsIDocShell *aDocShell, + nsIMsgCompose *aComposeObject) +{ + NS_ENSURE_ARG_POINTER(aDocShell); + NS_ENSURE_ARG_POINTER(aComposeObject); + + nsresult rv; + + // add the msg compose / dom window mapping to our hash table + nsCOMPtr<nsIWeakReference> weakDocShell = do_GetWeakReference(aDocShell, &rv); + NS_ENSURE_SUCCESS(rv,rv); + nsCOMPtr<nsIWeakReference> weakMsgComposePtr = do_GetWeakReference(aComposeObject); + NS_ENSURE_SUCCESS(rv,rv); + mOpenComposeWindows.Put(weakDocShell, weakMsgComposePtr); + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeService::UnregisterComposeDocShell(nsIDocShell *aDocShell) +{ + NS_ENSURE_ARG_POINTER(aDocShell); + + nsresult rv; + nsCOMPtr<nsIWeakReference> weakDocShell = do_GetWeakReference(aDocShell, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + mOpenComposeWindows.Remove(weakDocShell); + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeService::GetMsgComposeForDocShell(nsIDocShell *aDocShell, + nsIMsgCompose **aComposeObject) +{ + NS_ENSURE_ARG_POINTER(aDocShell); + NS_ENSURE_ARG_POINTER(aComposeObject); + + if (!mOpenComposeWindows.Count()) + return NS_ERROR_FAILURE; + + // get the weak reference for our dom window + nsresult rv; + nsCOMPtr<nsIWeakReference> weakDocShell = do_GetWeakReference(aDocShell, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIWeakReference> weakMsgComposePtr; + + if (!mOpenComposeWindows.Get(weakDocShell, + getter_AddRefs(weakMsgComposePtr))) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgCompose> msgCompose = do_QueryReferent(weakMsgComposePtr, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*aComposeObject = msgCompose); + return rv; +} + +/** + * LoadDraftOrTemplate + * Helper routine used to run msgURI through libmime in order to fetch the contents for a + * draft or template. + */ +nsresult +nsMsgComposeService::LoadDraftOrTemplate(const nsACString& aMsgURI, nsMimeOutputType aOutType, + nsIMsgIdentity * aIdentity, const char * aOriginalMsgURI, + nsIMsgDBHdr * aOrigMsgHdr, + bool aForwardInline, + bool overrideComposeFormat, + nsIMsgWindow *aMsgWindow) +{ + return RunMessageThroughMimeDraft(aMsgURI, aOutType, aIdentity, + aOriginalMsgURI, aOrigMsgHdr, + aForwardInline, EmptyString(), + overrideComposeFormat, aMsgWindow); +} + +/** + * Run the aMsgURI message through libmime. We set various attributes of the + * nsIMimeStreamConverter so mimedrft.cpp will know what to do with the message + * when its done streaming. Usually that will be opening a compose window + * with the contents of the message, but if forwardTo is non-empty, mimedrft.cpp + * will forward the contents directly. + * + * @param aMsgURI URI to stream, which is the msgUri + any extra terms, e.g., + * "redirect=true". + * @param aOutType nsMimeOutput::nsMimeMessageDraftOrTemplate or + * nsMimeOutput::nsMimeMessageEditorTemplate + * @param aIdentity identity to use for the new message + * @param aOriginalMsgURI msgURI w/o any extra terms + * @param aOrigMsgHdr nsIMsgDBHdr corresponding to aOriginalMsgURI + * @param aForwardInline true if doing a forward inline + * @param aForwardTo e-mail address to forward msg to. This is used for + * forward inline message filter actions. + * @param aOverrideComposeFormat True if the user had shift key down when + doing a command that opens the compose window, + * which means we switch the compose window used + * from the default. + * @param aMsgWindow msgWindow to pass into DisplayMessage. + */ +nsresult +nsMsgComposeService::RunMessageThroughMimeDraft( + const nsACString& aMsgURI, nsMimeOutputType aOutType, + nsIMsgIdentity * aIdentity, const char * aOriginalMsgURI, + nsIMsgDBHdr * aOrigMsgHdr, + bool aForwardInline, + const nsAString &aForwardTo, + bool aOverrideComposeFormat, + nsIMsgWindow *aMsgWindow) +{ + nsCOMPtr <nsIMsgMessageService> messageService; + nsresult rv = GetMessageServiceFromURI(aMsgURI, getter_AddRefs(messageService)); + NS_ENSURE_SUCCESS(rv, rv); + + // Create a mime parser (nsIMimeStreamConverter)to do the conversion. + nsCOMPtr<nsIMimeStreamConverter> mimeConverter = + do_CreateInstance(NS_MAILNEWS_MIME_STREAM_CONVERTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mimeConverter->SetMimeOutputType(aOutType); // Set the type of output for libmime + mimeConverter->SetForwardInline(aForwardInline); + if (!aForwardTo.IsEmpty()) + { + mimeConverter->SetForwardInlineFilter(true); + mimeConverter->SetForwardToAddress(aForwardTo); + } + mimeConverter->SetOverrideComposeFormat(aOverrideComposeFormat); + mimeConverter->SetIdentity(aIdentity); + mimeConverter->SetOriginalMsgURI(aOriginalMsgURI); + mimeConverter->SetOrigMsgHdr(aOrigMsgHdr); + + nsCOMPtr<nsIURI> url; + bool fileUrl = StringBeginsWith(aMsgURI, NS_LITERAL_CSTRING("file:")); + nsCString mailboxUri(aMsgURI); + if (fileUrl) + { + // We loaded a .eml file from a file: url. Construct equivalent mailbox url. + mailboxUri.Replace(0, 5, NS_LITERAL_CSTRING("mailbox:")); + mailboxUri.Append(NS_LITERAL_CSTRING("&number=0")); + // Need this to prevent nsMsgCompose::TagEmbeddedObjects from setting + // inline images as moz-do-not-send. + mimeConverter->SetOriginalMsgURI(mailboxUri.get()); + } + if (fileUrl || PromiseFlatCString(aMsgURI).Find("&type=application/x-message-display") >= 0) + rv = NS_NewURI(getter_AddRefs(url), mailboxUri); + else + rv = messageService->GetUrlForUri(PromiseFlatCString(aMsgURI).get(), getter_AddRefs(url), aMsgWindow); + NS_ENSURE_SUCCESS(rv, rv); + + // ignore errors here - it's not fatal, and in the case of mailbox messages, + // we're always passing in an invalid spec... + (void )url->SetSpec(mailboxUri); + + // if we are forwarding a message and that message used a charset over ride + // then use that over ride charset instead of the charset specified in the message + nsCString mailCharset; + if (aMsgWindow) + { + bool charsetOverride; + if (NS_SUCCEEDED(aMsgWindow->GetCharsetOverride(&charsetOverride)) && charsetOverride) + { + if (NS_SUCCEEDED(aMsgWindow->GetMailCharacterSet(mailCharset))) + { + nsCOMPtr<nsIMsgI18NUrl> i18nUrl(do_QueryInterface(url)); + if (i18nUrl) + (void) i18nUrl->SetCharsetOverRide(mailCharset.get()); + } + } + } + + nsCOMPtr<nsIPrincipal> nullPrincipal = + do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipal failed"); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewInputStreamChannel(getter_AddRefs(channel), + url, + nullptr, + nullPrincipal, + nsILoadInfo::SEC_NORMAL, + nsIContentPolicy::TYPE_OTHER); + NS_ASSERTION(NS_SUCCEEDED(rv), "NS_NewChannel failed."); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIStreamConverter> converter = do_QueryInterface(mimeConverter); + rv = converter->AsyncConvertData(nullptr, nullptr, nullptr, channel); + NS_ENSURE_SUCCESS(rv, rv); + + // Now, just plug the two together and get the hell out of the way! + nsCOMPtr<nsIStreamListener> streamListener = do_QueryInterface(mimeConverter); + nsCOMPtr<nsIURI> dummyNull; + return messageService->DisplayMessage(PromiseFlatCString(aMsgURI).get(), streamListener, + aMsgWindow, nullptr, mailCharset.get(), + getter_AddRefs(dummyNull)); +} + +NS_IMETHODIMP +nsMsgComposeService::Handle(nsICommandLine* aCmdLine) +{ + NS_ENSURE_ARG_POINTER(aCmdLine); + + nsresult rv; + int32_t found, end, count; + nsAutoString uristr; + bool composeShouldHandle = true; + + rv = aCmdLine->FindFlag(NS_LITERAL_STRING("compose"), false, &found); + NS_ENSURE_SUCCESS(rv, rv); + +#ifndef MOZ_SUITE + // MAC OS X passes in -url mailto:mscott@mozilla.org into the command line + // instead of -compose. + if (found == -1) + { + rv = aCmdLine->FindFlag(NS_LITERAL_STRING("url"), false, &found); + // we don't want to consume the argument for -url unless we're sure it is a mailto url and we'll + // figure that out shortly. + composeShouldHandle = false; + } +#endif + + if (found == -1) + return NS_OK; + + end = found; + + rv = aCmdLine->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + if (count > found + 1) { + aCmdLine->GetArgument(found + 1, uristr); + if (StringBeginsWith(uristr, NS_LITERAL_STRING("mailto:")) || + StringBeginsWith(uristr, NS_LITERAL_STRING("preselectid=")) || + StringBeginsWith(uristr, NS_LITERAL_STRING("to=")) || + StringBeginsWith(uristr, NS_LITERAL_STRING("cc=")) || + StringBeginsWith(uristr, NS_LITERAL_STRING("bcc=")) || + StringBeginsWith(uristr, NS_LITERAL_STRING("newsgroups=")) || + StringBeginsWith(uristr, NS_LITERAL_STRING("subject=")) || + StringBeginsWith(uristr, NS_LITERAL_STRING("format=")) || + StringBeginsWith(uristr, NS_LITERAL_STRING("body=")) || + StringBeginsWith(uristr, NS_LITERAL_STRING("attachment=")) || + StringBeginsWith(uristr, NS_LITERAL_STRING("message=")) || + StringBeginsWith(uristr, NS_LITERAL_STRING("from="))) { + composeShouldHandle = true; // the -url argument looks like mailto + end++; + // mailto: URIs are frequently passed with spaces in them. They should be + // escaped with %20, but we hack around broken clients. See bug 231032. + while (end + 1 < count) { + nsAutoString curarg; + aCmdLine->GetArgument(end + 1, curarg); + if (curarg.First() == '-') + break; + + uristr.Append(' '); + uristr.Append(curarg); + ++end; + } + } + else { + uristr.Truncate(); + } + } + if (composeShouldHandle) + { + aCmdLine->RemoveArguments(found, end); + + nsCOMPtr<nsIWindowWatcher> wwatch (do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE); + + nsCOMPtr<nsISupportsString> arg(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + if (arg) + arg->SetData(uristr); + + nsCOMPtr<mozIDOMWindowProxy> opened; + wwatch->OpenWindow(nullptr, DEFAULT_CHROME, "_blank", + "chrome,dialog=no,all", arg, getter_AddRefs(opened)); + + aCmdLine->SetPreventDefault(true); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeService::GetHelpInfo(nsACString& aResult) +{ + aResult.AssignLiteral( + " -compose [ <options> ] Compose a mail or news message. Options are specified\n" + " as string \"option='value,...',option=value,...\" and\n" + " include: from, to, cc, bcc, newsgroups, subject, body,\n" + " message (file), attachment (file), format (html | text).\n" + " Example: \"to=john@example.com,subject='Dinner tonight?'\"\n"); + return NS_OK; +} diff --git a/mailnews/compose/src/nsMsgComposeService.h b/mailnews/compose/src/nsMsgComposeService.h new file mode 100644 index 000000000..6ba029050 --- /dev/null +++ b/mailnews/compose/src/nsMsgComposeService.h @@ -0,0 +1,68 @@ +/* -*- 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/. */ + +#define MSGCOMP_TRACE_PERFORMANCE 1 + +#include "nsIMsgComposeService.h" +#include "nsCOMPtr.h" +#include "mozIDOMWindow.h" +#include "nsIXULWindow.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsIMimeStreamConverter.h" +#include "nsInterfaceHashtable.h" + +#include "nsICommandLineHandler.h" +#define ICOMMANDLINEHANDLER nsICommandLineHandler + +class nsMsgComposeService : + public nsIMsgComposeService, + public ICOMMANDLINEHANDLER, + public nsSupportsWeakReference +{ +public: + nsMsgComposeService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGCOMPOSESERVICE + NS_DECL_NSICOMMANDLINEHANDLER + + nsresult Init(); + void Reset(); + void DeleteCachedWindows(); + nsresult AddGlobalHtmlDomains(); + +private: + virtual ~nsMsgComposeService(); + bool mLogComposePerformance; + + nsresult LoadDraftOrTemplate(const nsACString& aMsgURI, nsMimeOutputType aOutType, + nsIMsgIdentity * aIdentity, const char * aOriginalMsgURI, + nsIMsgDBHdr * aOrigMsgHdr, bool aForwardInline, + bool overrideComposeFormat, + nsIMsgWindow *aMsgWindow); + + nsresult RunMessageThroughMimeDraft(const nsACString& aMsgURI, + nsMimeOutputType aOutType, + nsIMsgIdentity * aIdentity, + const char * aOriginalMsgURI, + nsIMsgDBHdr * aOrigMsgHdr, + bool aForwardInline, + const nsAString &forwardTo, + bool overrideComposeFormat, + nsIMsgWindow *aMsgWindow); + + // hash table mapping dom windows to nsIMsgCompose objects + nsInterfaceHashtable<nsISupportsHashKey, nsIWeakReference> mOpenComposeWindows; + + // When doing a reply and the settings are enabled, get the HTML of the selected text + // in the original message window so that it can be quoted instead of the entire message. + nsresult GetOrigWindowSelection(MSG_ComposeType type, nsIMsgWindow *aMsgWindow, nsACString& aSelHTML); + +#ifdef MSGCOMP_TRACE_PERFORMANCE + PRIntervalTime mStartTime; + PRIntervalTime mPreviousTime; +#endif +}; diff --git a/mailnews/compose/src/nsMsgCopy.cpp b/mailnews/compose/src/nsMsgCopy.cpp new file mode 100644 index 000000000..5c441b0c1 --- /dev/null +++ b/mailnews/compose/src/nsMsgCopy.cpp @@ -0,0 +1,553 @@ +/* -*- 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 "nsMsgCopy.h" + +#include "nsCOMPtr.h" +#include "nsMsgBaseCID.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgFolder.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgFolder.h" +#include "nsIMsgIncomingServer.h" +#include "nsIMsgProtocolInfo.h" +#include "nsISupports.h" +#include "nsIRDFService.h" +#include "nsIRDFResource.h" +#include "nsRDFCID.h" +#include "nsIURL.h" +#include "nsNetCID.h" +#include "nsMsgCompUtils.h" +#include "prcmon.h" +#include "nsIMsgImapMailFolder.h" +#include "nsThreadUtils.h" +#include "nsIMsgWindow.h" +#include "nsIMsgProgress.h" +#include "nsComposeStrings.h" +#include "prmem.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" +#include "nsArrayUtils.h" + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + +//////////////////////////////////////////////////////////////////////////////////// +// This is the listener class for the copy operation. We have to create this class +// to listen for message copy completion and eventually notify the caller +//////////////////////////////////////////////////////////////////////////////////// +NS_IMPL_ISUPPORTS(CopyListener, nsIMsgCopyServiceListener) + +CopyListener::CopyListener(void) +{ + mCopyInProgress = false; +} + +CopyListener::~CopyListener(void) +{ +} + +nsresult +CopyListener::OnStartCopy() +{ +#ifdef NS_DEBUG + printf("CopyListener::OnStartCopy()\n"); +#endif + + if (mComposeAndSend) + mComposeAndSend->NotifyListenerOnStartCopy(); + return NS_OK; +} + +nsresult +CopyListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax) +{ +#ifdef NS_DEBUG + printf("CopyListener::OnProgress() %d of %d\n", aProgress, aProgressMax); +#endif + + if (mComposeAndSend) + mComposeAndSend->NotifyListenerOnProgressCopy(aProgress, aProgressMax); + + return NS_OK; +} + +nsresult +CopyListener::SetMessageKey(nsMsgKey aMessageKey) +{ + if (mComposeAndSend) + mComposeAndSend->SetMessageKey(aMessageKey); + return NS_OK; +} + +NS_IMETHODIMP +CopyListener::GetMessageId(nsACString& aMessageId) +{ + if (mComposeAndSend) + mComposeAndSend->GetMessageId(aMessageId); + return NS_OK; +} + +nsresult +CopyListener::OnStopCopy(nsresult aStatus) +{ + if (NS_SUCCEEDED(aStatus)) + { +#ifdef NS_DEBUG + printf("CopyListener: SUCCESSFUL ON THE COPY OPERATION!\n"); +#endif + } + else + { +#ifdef NS_DEBUG + printf("CopyListener: COPY OPERATION FAILED!\n"); +#endif + } + + if (mCopyInProgress) + { + PR_CEnterMonitor(this); + PR_CNotifyAll(this); + mCopyInProgress = false; + PR_CExitMonitor(this); + } + if (mComposeAndSend) + mComposeAndSend->NotifyListenerOnStopCopy(aStatus); + + return NS_OK; +} + +nsresult +CopyListener::SetMsgComposeAndSendObject(nsIMsgSend *obj) +{ + if (obj) + mComposeAndSend = obj; + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////////// +// END END END END END END END END END END END END END END END +// This is the listener class for the copy operation. We have to create this class +// to listen for message copy completion and eventually notify the caller +//////////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsMsgCopy, nsIUrlListener) + +nsMsgCopy::nsMsgCopy() +{ + mFile = nullptr; + mMode = nsIMsgSend::nsMsgDeliverNow; + mSavePref = nullptr; +} + +nsMsgCopy::~nsMsgCopy() +{ + PR_Free(mSavePref); +} + +nsresult +nsMsgCopy::StartCopyOperation(nsIMsgIdentity *aUserIdentity, + nsIFile *aFile, + nsMsgDeliverMode aMode, + nsIMsgSend *aMsgSendObj, + const char *aSavePref, + nsIMsgDBHdr *aMsgToReplace) +{ + nsCOMPtr<nsIMsgFolder> dstFolder; + bool isDraft = false; + bool waitForUrl = false; + nsresult rv; + + if (!aMsgSendObj) + return NS_ERROR_INVALID_ARG; + + // Store away the server location... + if (aSavePref) + mSavePref = PL_strdup(aSavePref); + + // + // Vars for implementation... + // + + // QueueForLater (Outbox) + if (aMode == nsIMsgSend::nsMsgQueueForLater || + aMode == nsIMsgSend::nsMsgDeliverBackground) + { + rv = GetUnsentMessagesFolder(aUserIdentity, getter_AddRefs(dstFolder), &waitForUrl); + isDraft = false; + if (!dstFolder || NS_FAILED(rv)) { + return NS_MSG_UNABLE_TO_SEND_LATER; + } + } + else if (aMode == nsIMsgSend::nsMsgSaveAsDraft) // SaveAsDraft (Drafts) + { + rv = GetDraftsFolder(aUserIdentity, getter_AddRefs(dstFolder), &waitForUrl); + isDraft = true; + if (!dstFolder || NS_FAILED(rv)) + return NS_MSG_UNABLE_TO_SAVE_DRAFT; + } + else if (aMode == nsIMsgSend::nsMsgSaveAsTemplate) // SaveAsTemplate (Templates) + { + rv = GetTemplatesFolder(aUserIdentity, getter_AddRefs(dstFolder), &waitForUrl); + isDraft = false; + if (!dstFolder || NS_FAILED(rv)) + return NS_MSG_UNABLE_TO_SAVE_TEMPLATE; + } + else // SaveInSentFolder (Sent) - nsMsgDeliverNow or nsMsgSendUnsent + { + rv = GetSentFolder(aUserIdentity, getter_AddRefs(dstFolder), &waitForUrl); + isDraft = false; + if (!dstFolder || NS_FAILED(rv)) + return NS_MSG_COULDNT_OPEN_FCC_FOLDER; + } + + nsCOMPtr <nsIMsgWindow> msgWindow; + + if (aMsgSendObj) + { + nsCOMPtr <nsIMsgProgress> progress; + aMsgSendObj->GetProgress(getter_AddRefs(progress)); + if (progress) + progress->GetMsgWindow(getter_AddRefs(msgWindow)); + } + + mMode = aMode; + mFile = aFile; + mDstFolder = dstFolder; + mMsgToReplace = aMsgToReplace; + mIsDraft = isDraft; + mMsgSendObj = aMsgSendObj; + if (!waitForUrl) + { + // cache info needed for DoCopy and call DoCopy when OnStopUrl is called. + rv = DoCopy(aFile, dstFolder, aMsgToReplace, isDraft, msgWindow, aMsgSendObj); + // N.B. "this" may be deleted when this call returns. + } + return rv; +} + +nsresult +nsMsgCopy::DoCopy(nsIFile *aDiskFile, nsIMsgFolder *dstFolder, + nsIMsgDBHdr *aMsgToReplace, bool aIsDraft, + nsIMsgWindow *msgWindow, + nsIMsgSend *aMsgSendObj) +{ + nsresult rv = NS_OK; + + // Check sanity + if ((!aDiskFile) || (!dstFolder)) + return NS_ERROR_INVALID_ARG; + + //Call copyservice with dstFolder, disk file, and txnManager + if(NS_SUCCEEDED(rv)) + { + RefPtr<CopyListener> copyListener = new CopyListener(); + if (!copyListener) + return NS_ERROR_OUT_OF_MEMORY; + + copyListener->SetMsgComposeAndSendObject(aMsgSendObj); + nsCOMPtr<nsIThread> thread; + + if (aIsDraft) + { + nsCOMPtr<nsIMsgImapMailFolder> imapFolder = + do_QueryInterface(dstFolder); + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + bool shutdownInProgress = false; + rv = accountManager->GetShutdownInProgress(&shutdownInProgress); + + if (NS_SUCCEEDED(rv) && shutdownInProgress && imapFolder) + { + // set the following only when we were in the middle of shutdown + // process + copyListener->mCopyInProgress = true; + thread = do_GetCurrentThread(); + } + } + nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = copyService->CopyFileMessage(aDiskFile, dstFolder, aMsgToReplace, + aIsDraft, + aIsDraft ? 0 : nsMsgMessageFlags::Read, + EmptyCString(), copyListener, msgWindow); + // copyListener->mCopyInProgress can only be set when we are in the + // middle of the shutdown process + while (copyListener->mCopyInProgress) + { + PR_CEnterMonitor(copyListener); + PR_CWait(copyListener, PR_MicrosecondsToInterval(1000UL)); + PR_CExitMonitor(copyListener); + if (thread) + NS_ProcessPendingEvents(thread); + } + } + + return rv; +} + +// nsIUrlListener methods +NS_IMETHODIMP +nsMsgCopy::OnStartRunningUrl(nsIURI * aUrl) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgCopy::OnStopRunningUrl(nsIURI * aUrl, nsresult aExitCode) +{ + nsresult rv = aExitCode; + if (NS_SUCCEEDED(aExitCode)) + { + rv = DoCopy(mFile, mDstFolder, mMsgToReplace, mIsDraft, nullptr, mMsgSendObj); + } + return rv; +} + +nsresult +nsMsgCopy::GetUnsentMessagesFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **folder, bool *waitForUrl) +{ + nsresult ret = LocateMessageFolder(userIdentity, nsIMsgSend::nsMsgQueueForLater, mSavePref, folder); + if (*folder) + (*folder)->SetFlag(nsMsgFolderFlags::Queue); + CreateIfMissing(folder, waitForUrl); + return ret; +} + +nsresult +nsMsgCopy::GetDraftsFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **folder, bool *waitForUrl) +{ + nsresult ret = LocateMessageFolder(userIdentity, nsIMsgSend::nsMsgSaveAsDraft, mSavePref, folder); + if (*folder) + (*folder)->SetFlag(nsMsgFolderFlags::Drafts); + CreateIfMissing(folder, waitForUrl); + return ret; +} + +nsresult +nsMsgCopy::GetTemplatesFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **folder, bool *waitForUrl) +{ + nsresult ret = LocateMessageFolder(userIdentity, nsIMsgSend::nsMsgSaveAsTemplate, mSavePref, folder); + if (*folder) + (*folder)->SetFlag(nsMsgFolderFlags::Templates); + CreateIfMissing(folder, waitForUrl); + return ret; +} + +nsresult +nsMsgCopy::GetSentFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **folder, bool *waitForUrl) +{ + nsresult ret = LocateMessageFolder(userIdentity, nsIMsgSend::nsMsgDeliverNow, mSavePref, folder); + if (*folder) + { + // If mSavePref is the same as the identity's fcc folder, set the sent flag. + nsCString identityFccUri; + userIdentity->GetFccFolder(identityFccUri); + if (identityFccUri.Equals(mSavePref)) + (*folder)->SetFlag(nsMsgFolderFlags::SentMail); + } + CreateIfMissing(folder, waitForUrl); + return ret; +} + +nsresult +nsMsgCopy::CreateIfMissing(nsIMsgFolder **folder, bool *waitForUrl) +{ + nsresult rv = NS_OK; + if (folder && *folder) + { + nsCOMPtr<nsIMsgFolder> parent; + (*folder)->GetParent(getter_AddRefs(parent)); + if (!parent) + { + nsCOMPtr<nsIFile> folderPath; + // for local folders, path is to the berkeley mailbox. + // for imap folders, path needs to have .msf appended to the name + (*folder)->GetFilePath(getter_AddRefs(folderPath)); + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = (*folder)->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgProtocolInfo> protocolInfo; + rv = server->GetProtocolInfo(getter_AddRefs(protocolInfo)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isAsyncFolder; + rv = protocolInfo->GetFoldersCreatedAsync(&isAsyncFolder); + NS_ENSURE_SUCCESS(rv, rv); + + // if we can't get the path from the folder, then try to create the storage. + // for imap, it doesn't matter if the .msf file exists - it still might not + // exist on the server, so we should try to create it + bool exists = false; + if (!isAsyncFolder && folderPath) + folderPath->Exists(&exists); + if (!exists) + { + (*folder)->CreateStorageIfMissing(this); + if (isAsyncFolder) + *waitForUrl = true; + + rv = NS_OK; + } + } + } + return rv; +} +//////////////////////////////////////////////////////////////////////////////////// +// Utility Functions for MsgFolders +//////////////////////////////////////////////////////////////////////////////////// +nsresult +LocateMessageFolder(nsIMsgIdentity *userIdentity, + nsMsgDeliverMode aFolderType, + const char *aFolderURI, + nsIMsgFolder **msgFolder) +{ + nsresult rv = NS_OK; + + if (!msgFolder) return NS_ERROR_NULL_POINTER; + *msgFolder = nullptr; + + if (!aFolderURI || !*aFolderURI) + return NS_ERROR_INVALID_ARG; + + // as long as it doesn't start with anyfolder:// + if (PL_strncasecmp(ANY_SERVER, aFolderURI, strlen(aFolderURI)) != 0) + { + nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv)); + if (NS_FAILED(rv)) return rv; + + // get the corresponding RDF resource + // RDF will create the folder resource if it doesn't already exist + nsCOMPtr<nsIRDFResource> resource; + rv = rdf->GetResource(nsDependentCString(aFolderURI), getter_AddRefs(resource)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr <nsIMsgFolder> folderResource; + folderResource = do_QueryInterface(resource, &rv); + if (NS_SUCCEEDED(rv) && folderResource) + { + // don't check validity of folder - caller will handle creating it + nsCOMPtr<nsIMsgIncomingServer> server; + //make sure that folder hierarchy is built so that legitimate parent-child relationship is established + rv = folderResource->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv,rv); + return server->GetMsgFolderFromURI(folderResource, nsDependentCString(aFolderURI), msgFolder); + } + else + { + return NS_ERROR_FAILURE; + } + } + else + { + uint32_t cnt = 0; + uint32_t i; + + if (!userIdentity) + return NS_ERROR_INVALID_ARG; + + // get the account manager + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + // If any folder will do, go look for one. + nsCOMPtr<nsIArray> retval; + accountManager->GetServersForIdentity(userIdentity, getter_AddRefs(retval)); + if (!retval) return NS_ERROR_FAILURE; + + // Ok, we have to look through the servers and try to find the server that + // has a valid folder of the type that interests us... + rv = retval->GetLength(&cnt); + if (NS_FAILED(rv)) return rv; + + for (i=0; i<cnt; i++) { + // Now that we have the server...we need to get the named message folder + nsCOMPtr<nsIMsgIncomingServer> inServer; + + inServer = do_QueryElementAt(retval, i, &rv); + if(NS_FAILED(rv) || (!inServer)) + continue; + + // + // If aFolderURI is passed in, then the user has chosen a specific + // mail folder to save the message, but if it is null, just find the + // first one and make that work. The folder is specified as a URI, like + // the following: + // + // mailbox://nobody@Local Folders/Sent + // imap://rhp@nsmail-2/Drafts + // newsgroup://news.mozilla.org/netscape.test + // + nsCString serverURI; + rv = inServer->GetServerURI(serverURI); + if (NS_FAILED(rv) || serverURI.IsEmpty()) + continue; + + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = inServer->GetRootFolder(getter_AddRefs(rootFolder)); + + if(NS_FAILED(rv) || (!rootFolder)) + continue; + + // use the defaults by getting the folder by flags + if (aFolderType == nsIMsgSend::nsMsgQueueForLater || + aFolderType == nsIMsgSend::nsMsgDeliverBackground) + { + // QueueForLater (Outbox) + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Queue, msgFolder); + } + else if (aFolderType == nsIMsgSend::nsMsgSaveAsDraft) // SaveAsDraft (Drafts) + { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Drafts, msgFolder); + } + else if (aFolderType == nsIMsgSend::nsMsgSaveAsTemplate) // SaveAsTemplate (Templates) + { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Templates, msgFolder); + } + else // SaveInSentFolder (Sent) - nsMsgDeliverNow or nsMsgSendUnsent + { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::SentMail, msgFolder); + } + + if (*msgFolder) + { + return NS_OK; + } + } + } + return NS_ERROR_FAILURE; +} + +// +// Figure out if a folder is local or not and return a boolean to +// say so. +// +nsresult +MessageFolderIsLocal(nsIMsgIdentity *userIdentity, + nsMsgDeliverMode aFolderType, + const char *aFolderURI, + bool *aResult) +{ + nsresult rv; + + if (!aFolderURI) return NS_ERROR_NULL_POINTER; + + nsCOMPtr <nsIURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = url->SetSpec(nsDependentCString(aFolderURI)); + if (NS_FAILED(rv)) return rv; + + /* mailbox:/ means its local (on disk) */ + rv = url->SchemeIs("mailbox", aResult); + if (NS_FAILED(rv)) return rv; + return NS_OK; +} + diff --git a/mailnews/compose/src/nsMsgCopy.h b/mailnews/compose/src/nsMsgCopy.h new file mode 100644 index 000000000..7b559d754 --- /dev/null +++ b/mailnews/compose/src/nsMsgCopy.h @@ -0,0 +1,120 @@ +/* -*- 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 _nsMsgCopy_H_ +#define _nsMsgCopy_H_ + +#include "mozilla/Attributes.h" +#include "nscore.h" +#include "nsIFile.h" +#include "nsMsgSend.h" +#include "nsIMsgFolder.h" +#include "nsITransactionManager.h" +#include "nsIMsgCopyServiceListener.h" +#include "nsIMsgCopyService.h" + +// {0874C3B5-317D-11d3-8EFB-00A024A7D144} +#define NS_IMSGCOPY_IID \ +{ 0x874c3b5, 0x317d, 0x11d3, \ +{ 0x8e, 0xfb, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 } }; + +// Forward declarations... +class nsMsgCopy; + +//////////////////////////////////////////////////////////////////////////////////// +// This is the listener class for the copy operation. We have to create this class +// to listen for message copy completion and eventually notify the caller +//////////////////////////////////////////////////////////////////////////////////// +class CopyListener : public nsIMsgCopyServiceListener +{ +public: + CopyListener(void); + + // nsISupports interface + NS_DECL_THREADSAFE_ISUPPORTS + + NS_IMETHOD OnStartCopy() override; + + NS_IMETHOD OnProgress(uint32_t aProgress, uint32_t aProgressMax) override; + + NS_IMETHOD SetMessageKey(nsMsgKey aMessageKey) override; + + NS_IMETHOD GetMessageId(nsACString& aMessageId) override; + + NS_IMETHOD OnStopCopy(nsresult aStatus) override; + + NS_IMETHOD SetMsgComposeAndSendObject(nsIMsgSend *obj); + + bool mCopyInProgress; + +private: + virtual ~CopyListener(); + nsCOMPtr<nsIMsgSend> mComposeAndSend; +}; + +// +// This is a class that deals with processing remote attachments. It implements +// an nsIStreamListener interface to deal with incoming data +// +class nsMsgCopy : public nsIUrlListener +{ +public: + nsMsgCopy(); + + // nsISupports interface + NS_DECL_ISUPPORTS + NS_DECL_NSIURLLISTENER + + + ////////////////////////////////////////////////////////////////////// + // Object methods... + ////////////////////////////////////////////////////////////////////// + // + nsresult StartCopyOperation(nsIMsgIdentity *aUserIdentity, + nsIFile *aFile, + nsMsgDeliverMode aMode, + nsIMsgSend *aMsgSendObj, + const char *aSavePref, + nsIMsgDBHdr *aMsgToReplace); + + nsresult DoCopy(nsIFile *aDiskFile, nsIMsgFolder *dstFolder, + nsIMsgDBHdr *aMsgToReplace, bool aIsDraft, + nsIMsgWindow *msgWindow, + nsIMsgSend *aMsgSendObj); + + nsresult GetUnsentMessagesFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **msgFolder, bool *waitForUrl); + nsresult GetDraftsFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **msgFolder, bool *waitForUrl); + nsresult GetTemplatesFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **msgFolder, bool *waitForUrl); + nsresult GetSentFolder(nsIMsgIdentity *userIdentity, nsIMsgFolder **msgFolder, bool *waitForUrl); + nsresult CreateIfMissing(nsIMsgFolder **folder, bool *waitForUrl); + + + // + // Vars for implementation... + // + nsIFile *mFile; // the file we are sending... + nsMsgDeliverMode mMode; + nsCOMPtr<nsIMsgFolder> mDstFolder; + nsCOMPtr<nsIMsgDBHdr> mMsgToReplace; + bool mIsDraft; + nsCOMPtr<nsIMsgSend> mMsgSendObj; + char *mSavePref; + +private: + virtual ~nsMsgCopy(); +}; + +// Useful function for the back end... +nsresult LocateMessageFolder(nsIMsgIdentity *userIdentity, + nsMsgDeliverMode aFolderType, + const char *aSaveURI, + nsIMsgFolder **msgFolder); + +nsresult MessageFolderIsLocal(nsIMsgIdentity *userIdentity, + nsMsgDeliverMode aFolderType, + const char *aSaveURI, + bool *aResult); + +#endif /* _nsMsgCopy_H_ */ diff --git a/mailnews/compose/src/nsMsgPrompts.cpp b/mailnews/compose/src/nsMsgPrompts.cpp new file mode 100644 index 000000000..473469f16 --- /dev/null +++ b/mailnews/compose/src/nsMsgPrompts.cpp @@ -0,0 +1,115 @@ +/* -*- 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 "nsMsgPrompts.h" + +#include "nsMsgCopy.h" +#include "nsIPrompt.h" +#include "nsIWindowWatcher.h" +#include "nsMsgCompCID.h" +#include "nsComposeStrings.h" +#include "nsIStringBundle.h" +#include "nsServiceManagerUtils.h" +#include "nsMsgUtils.h" +#include "mozilla/Services.h" + +nsresult +nsMsgGetMessageByName(const char16_t* aName, nsString& aResult) +{ + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + return bundle->GetStringFromName(aName, getter_Copies(aResult)); +} + +static nsresult +nsMsgBuildMessageByName(const char16_t *aName, nsIFile *aFile, nsString& aResult) +{ + NS_ENSURE_ARG_POINTER(aFile); + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString path; + aFile->GetPath(path); + + const char16_t *params[1] = {path.get()}; + return bundle->FormatStringFromName(aName, params, 1, getter_Copies(aResult)); +} + +nsresult +nsMsgBuildMessageWithFile(nsIFile *aFile, nsString& aResult) +{ + return nsMsgBuildMessageByName(u"unableToOpenFile", aFile, aResult); +} + +nsresult +nsMsgBuildMessageWithTmpFile(nsIFile *aFile, nsString& aResult) +{ + return nsMsgBuildMessageByName(u"unableToOpenTmpFile", aFile, aResult); +} + +nsresult +nsMsgDisplayMessageByName(nsIPrompt *aPrompt, const char16_t* aName, const char16_t *windowTitle) +{ + nsString msg; + nsMsgGetMessageByName(aName, msg); + return nsMsgDisplayMessageByString(aPrompt, msg.get(), windowTitle); +} + +nsresult +nsMsgDisplayMessageByString(nsIPrompt * aPrompt, const char16_t * msg, const char16_t * windowTitle) +{ + NS_ENSURE_ARG_POINTER(msg); + + nsresult rv = NS_OK; + nsCOMPtr<nsIPrompt> prompt = aPrompt; + + if (!prompt) + { + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + if (wwatch) + wwatch->GetNewPrompter(0, getter_AddRefs(prompt)); + } + + if (prompt) + rv = prompt->Alert(windowTitle, msg); + + return rv; +} + +nsresult +nsMsgAskBooleanQuestionByString(nsIPrompt * aPrompt, const char16_t * msg, bool *answer, const char16_t * windowTitle) +{ + NS_ENSURE_TRUE(msg && *msg, NS_ERROR_INVALID_ARG); + + nsresult rv = NS_OK; + nsCOMPtr<nsIPrompt> dialog = aPrompt; + + if (!dialog) + { + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + if (wwatch) + wwatch->GetNewPrompter(0, getter_AddRefs(dialog)); + } + + if (dialog) { + rv = dialog->Confirm(windowTitle, msg, answer); + } + + return rv; +} diff --git a/mailnews/compose/src/nsMsgPrompts.h b/mailnews/compose/src/nsMsgPrompts.h new file mode 100644 index 000000000..9aed5252e --- /dev/null +++ b/mailnews/compose/src/nsMsgPrompts.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _nsMsgPrompts_H_ +#define _nsMsgPrompts_H_ + +#include "nscore.h" +#include "nsError.h" +#include "nsStringGlue.h" + +class nsIPrompt; + +nsresult nsMsgGetMessageByName(const char16_t* aName, nsString& aResult); +nsresult nsMsgBuildMessageWithFile(nsIFile * aFile, nsString& aResult); +nsresult nsMsgBuildMessageWithTmpFile(nsIFile * aFile, nsString& aResult); +nsresult nsMsgDisplayMessageByName(nsIPrompt *aPrompt, const char16_t *aName, const char16_t *windowTitle = nullptr); +nsresult nsMsgDisplayMessageByString(nsIPrompt * aPrompt, const char16_t * msg, const char16_t * windowTitle = nullptr); +nsresult nsMsgAskBooleanQuestionByString(nsIPrompt * aPrompt, const char16_t * msg, bool *answer, const char16_t * windowTitle = nullptr); + +#endif /* _nsMsgPrompts_H_ */ diff --git a/mailnews/compose/src/nsMsgQuote.cpp b/mailnews/compose/src/nsMsgQuote.cpp new file mode 100644 index 000000000..ce8e1b5d0 --- /dev/null +++ b/mailnews/compose/src/nsMsgQuote.cpp @@ -0,0 +1,233 @@ +/* -*- 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 "nsIURL.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIServiceManager.h" +#include "nsIStreamListener.h" +#include "nsIStreamConverter.h" +#include "nsIStreamConverterService.h" +#include "nsIMimeStreamConverter.h" +#include "nsMimeTypes.h" +#include "nsICharsetConverterManager.h" +#include "prprf.h" +#include "nsMsgQuote.h" +#include "nsMsgCompUtils.h" +#include "nsIMsgMessageService.h" +#include "nsMsgUtils.h" +#include "nsNetUtil.h" +#include "nsMsgMimeCID.h" +#include "nsMsgCompCID.h" +#include "nsMsgCompose.h" +#include "nsMsgMailNewsUrl.h" +#include "mozilla/Services.h" +#include "nsIScriptSecurityManager.h" + +NS_IMPL_ISUPPORTS(nsMsgQuoteListener, nsIMsgQuoteListener, + nsIMimeStreamConverterListener) + +nsMsgQuoteListener::nsMsgQuoteListener() +{ +} + +nsMsgQuoteListener::~nsMsgQuoteListener() +{ +} + +NS_IMETHODIMP nsMsgQuoteListener::SetMsgQuote(nsIMsgQuote * msgQuote) +{ + mMsgQuote = do_GetWeakReference(msgQuote); + return NS_OK; +} + +NS_IMETHODIMP nsMsgQuoteListener::GetMsgQuote(nsIMsgQuote ** aMsgQuote) +{ + nsresult rv = NS_OK; + if (aMsgQuote) + { + nsCOMPtr<nsIMsgQuote> msgQuote = do_QueryReferent(mMsgQuote); + *aMsgQuote = msgQuote; + NS_IF_ADDREF(*aMsgQuote); + } + else + rv = NS_ERROR_NULL_POINTER; + + return rv; +} + +nsresult nsMsgQuoteListener::OnHeadersReady(nsIMimeHeaders * headers) +{ + nsCOMPtr<nsIMsgQuotingOutputStreamListener> quotingOutputStreamListener; + nsCOMPtr<nsIMsgQuote> msgQuote = do_QueryReferent(mMsgQuote); + + if (msgQuote) + msgQuote->GetStreamListener(getter_AddRefs(quotingOutputStreamListener)); + + if (quotingOutputStreamListener) + quotingOutputStreamListener->SetMimeHeaders(headers); + return NS_OK; +} + +// +// Implementation... +// +nsMsgQuote::nsMsgQuote() +{ + mQuoteHeaders = false; + mQuoteListener = nullptr; +} + +nsMsgQuote::~nsMsgQuote() +{ +} + +NS_IMPL_ISUPPORTS(nsMsgQuote, nsIMsgQuote, nsISupportsWeakReference) + +NS_IMETHODIMP nsMsgQuote::GetStreamListener(nsIMsgQuotingOutputStreamListener ** aStreamListener) +{ + nsresult rv = NS_OK; + if (aStreamListener) + { + *aStreamListener = mStreamListener; + NS_IF_ADDREF(*aStreamListener); + } + else + rv = NS_ERROR_NULL_POINTER; + + return rv; +} + +nsresult +nsMsgQuote::QuoteMessage(const char *msgURI, bool quoteHeaders, + nsIMsgQuotingOutputStreamListener * aQuoteMsgStreamListener, + const char * aMsgCharSet, bool headersOnly, + nsIMsgDBHdr *aMsgHdr) +{ + nsresult rv; + if (!msgURI) + return NS_ERROR_INVALID_ARG; + + mQuoteHeaders = quoteHeaders; + mStreamListener = aQuoteMsgStreamListener; + + nsAutoCString msgUri(msgURI); + bool fileUrl = !strncmp(msgURI, "file:", 5); + bool forwardedMessage = PL_strstr(msgURI, "&realtype=message/rfc822") != nullptr; + nsCOMPtr<nsIURI> aURL; + if (fileUrl) + { + msgUri.Replace(0, 5, NS_LITERAL_CSTRING("mailbox:")); + msgUri.AppendLiteral("?number=0"); + rv = NS_NewURI(getter_AddRefs(aURL), msgUri); + nsCOMPtr<nsIMsgMessageUrl> mailUrl(do_QueryInterface(aURL)); + if (mailUrl) + mailUrl->SetMessageHeader(aMsgHdr); + } + else if (forwardedMessage) + rv = NS_NewURI(getter_AddRefs(aURL), msgURI); + else + { + nsCOMPtr <nsIMsgMessageService> msgService; + rv = GetMessageServiceFromURI(nsDependentCString(msgURI), getter_AddRefs(msgService)); + if (NS_FAILED(rv)) return rv; + rv = msgService->GetUrlForUri(msgURI, getter_AddRefs(aURL), nullptr); + } + if (NS_FAILED(rv)) return rv; + + nsCOMPtr <nsIURL> mailNewsUrl = do_QueryInterface(aURL, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsAutoCString queryPart; + rv = mailNewsUrl->GetQuery(queryPart); + if (!queryPart.IsEmpty()) + queryPart.Append('&'); + + if (headersOnly) /* We don't need to quote the message body but we still need to extract the headers */ + queryPart.Append("header=only"); + else if (quoteHeaders) + queryPart.Append("header=quote"); + else + queryPart.Append("header=quotebody"); + rv = mailNewsUrl->SetQuery(queryPart); + NS_ENSURE_SUCCESS(rv,rv); + + // if we were given a non empty charset, then use it + if (aMsgCharSet && *aMsgCharSet) + { + nsCOMPtr<nsIMsgI18NUrl> i18nUrl (do_QueryInterface(aURL)); + if (i18nUrl) + i18nUrl->SetCharsetOverRide(aMsgCharSet); + } + + mQuoteListener = do_CreateInstance(NS_MSGQUOTELISTENER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + mQuoteListener->SetMsgQuote(this); + + // funky magic go get the isupports for this class which inherits from multiple interfaces. + nsISupports * supports; + QueryInterface(NS_GET_IID(nsISupports), (void **) &supports); + nsCOMPtr<nsISupports> quoteSupport = supports; + NS_IF_RELEASE(supports); + + // now we want to create a necko channel for this url and we want to open it + nsCOMPtr<nsIScriptSecurityManager> secMan( + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIPrincipal> systemPrincipal; + rv = secMan->GetSystemPrincipal(getter_AddRefs(systemPrincipal)); + NS_ENSURE_SUCCESS(rv,rv); + + mQuoteChannel = nullptr; + nsCOMPtr<nsIIOService> netService = mozilla::services::GetIOService(); + NS_ENSURE_TRUE(netService, NS_ERROR_UNEXPECTED); + rv = netService->NewChannelFromURI2(aURL, + nullptr, + systemPrincipal, + nullptr, + nsILoadInfo::SEC_NORMAL, + nsIContentPolicy::TYPE_OTHER, + getter_AddRefs(mQuoteChannel)); + + if (NS_FAILED(rv)) return rv; + nsCOMPtr<nsISupports> ctxt = do_QueryInterface(aURL); + + nsCOMPtr<nsIStreamConverterService> streamConverterService = + do_GetService("@mozilla.org/streamConverters;1", &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIStreamListener> convertedListener; + rv = streamConverterService->AsyncConvertData("message/rfc822", + "application/vnd.mozilla.xul+xml", + mStreamListener, + quoteSupport, + getter_AddRefs(convertedListener)); + if (NS_FAILED(rv)) return rv; + + // now try to open the channel passing in our display consumer as the listener + rv = mQuoteChannel->AsyncOpen(convertedListener, ctxt); + return rv; +} + +NS_IMETHODIMP +nsMsgQuote::GetQuoteListener(nsIMimeStreamConverterListener** aQuoteListener) +{ + if (!aQuoteListener || !mQuoteListener) + return NS_ERROR_NULL_POINTER; + *aQuoteListener = mQuoteListener; + NS_ADDREF(*aQuoteListener); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgQuote::GetQuoteChannel(nsIChannel** aQuoteChannel) +{ + if (!aQuoteChannel || !mQuoteChannel) + return NS_ERROR_NULL_POINTER; + *aQuoteChannel = mQuoteChannel; + NS_ADDREF(*aQuoteChannel); + return NS_OK; +} diff --git a/mailnews/compose/src/nsMsgQuote.h b/mailnews/compose/src/nsMsgQuote.h new file mode 100644 index 000000000..2151828ef --- /dev/null +++ b/mailnews/compose/src/nsMsgQuote.h @@ -0,0 +1,52 @@ +/* -*- 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/. */ +#ifndef __nsMsgQuote_h__ +#define __nsMsgQuote_h__ + +#include "nsIMsgQuote.h" +#include "nsIMsgMessageService.h" +#include "nsIStreamListener.h" +#include "nsIMimeStreamConverter.h" +#include "nsIChannel.h" +#include "nsCOMPtr.h" +#include "nsWeakReference.h" + +class nsMsgQuote; + +class nsMsgQuoteListener: public nsIMsgQuoteListener +{ +public: + nsMsgQuoteListener(); + + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIMimeStreamConverterListener support + NS_DECL_NSIMIMESTREAMCONVERTERLISTENER + NS_DECL_NSIMSGQUOTELISTENER + +private: + virtual ~nsMsgQuoteListener(); + nsWeakPtr mMsgQuote; +}; + +class nsMsgQuote: public nsIMsgQuote, public nsSupportsWeakReference { +public: + nsMsgQuote(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMSGQUOTE + +private: + virtual ~nsMsgQuote(); + // + // Implementation data... + // + nsCOMPtr<nsIMsgQuotingOutputStreamListener> mStreamListener; + bool mQuoteHeaders; + nsCOMPtr<nsIMsgQuoteListener> mQuoteListener; + nsCOMPtr<nsIChannel> mQuoteChannel; +}; + +#endif /* __nsMsgQuote_h__ */ diff --git a/mailnews/compose/src/nsMsgSend.cpp b/mailnews/compose/src/nsMsgSend.cpp new file mode 100644 index 000000000..c0f74e11c --- /dev/null +++ b/mailnews/compose/src/nsMsgSend.cpp @@ -0,0 +1,5218 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "nsMsgSend.h" +#include "prmem.h" +#include "nsMsgLocalFolderHdrs.h" +#include "nsMsgSendPart.h" +#include "nsMsgBaseCID.h" +#include "nsMsgNewsCID.h" +#include "nsISmtpService.h" // for actually sending the message... +#include "nsINntpService.h" // for actually posting the message... +#include "nsIMsgMailSession.h" +#include "nsIMsgIdentity.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsMsgCompUtils.h" +#include "nsMsgI18N.h" +#include "nsICharsetConverterManager.h" +#include "nsIMsgSendListener.h" +#include "nsIMsgCopyServiceListener.h" +#include "nsIFile.h" +#include "nsIURL.h" +#include "nsNetUtil.h" +#include "nsIFileURL.h" +#include "nsMsgCopy.h" +#include "nsUnicharUtils.h" +#include "nsMsgPrompts.h" +#include "nsIDOMHTMLBodyElement.h" +#include "nsIDOMHTMLImageElement.h" +#include "nsIDOMHTMLLinkElement.h" +#include "nsIDOMHTMLAnchorElement.h" +#include "nsCExternalHandlerService.h" +#include "nsIMIMEService.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsMsgCompCID.h" +#include "nsIAbAddressCollector.h" +#include "nsAbBaseCID.h" +#include "nsCOMPtr.h" +#include "mozITXTToHTMLConv.h" +#include "nsIMsgStatusFeedback.h" +#include "nsIMsgWindow.h" +#include "nsTextFormatter.h" +#include "nsIPrompt.h" +#include "nsMailHeaders.h" +#include "nsIDocShell.h" +#include "nsMimeTypes.h" +#include "nsISmtpUrl.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIEditorMailSupport.h" +#include "nsIDocumentEncoder.h" // for editor output flags +#include "nsILoadGroup.h" +#include "nsMsgSendReport.h" +#include "nsNetCID.h" +#include "nsError.h" +#include "nsMsgUtils.h" +#include "nsIMsgMdnGenerator.h" +#include "nsISmtpServer.h" +#include "nsIRDFService.h" +#include "nsRDFCID.h" +#include "nsIMsgAccountManager.h" +#include "nsNativeCharsetUtils.h" +#include "nsIAbCard.h" +#include "nsIMsgAttachment.h" +#include "nsIMsgProgress.h" +#include "nsIMsgMessageService.h" +#include "nsIMsgHdr.h" +#include "nsIMsgFolder.h" +#include "nsComposeStrings.h" +#include "nsStringGlue.h" +#include "nsMsgUtils.h" +#include "nsIArray.h" +#include "nsArrayUtils.h" +#include "mozilla/Services.h" +#include "mozilla/Attributes.h" +#include "mozilla/mailnews/MimeEncoder.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "nsIMutableArray.h" +#include "nsIMsgFilterService.h" +#include "nsIMsgProtocolInfo.h" +#include "mozIDOMWindow.h" +#include "mozilla/Preferences.h" + +using namespace mozilla; +using namespace mozilla::mailnews; + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + +#define PREF_MAIL_SEND_STRUCT "mail.send_struct" +#define PREF_MAIL_STRICTLY_MIME "mail.strictly_mime" +#define PREF_MAIL_MESSAGE_WARNING_SIZE "mailnews.message_warning_size" +#define PREF_MAIL_COLLECT_EMAIL_ADDRESS_OUTGOING "mail.collect_email_address_outgoing" + +#define ATTR_MOZ_DO_NOT_SEND "moz-do-not-send" + +enum { kDefaultMode = (PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE) }; + +static bool mime_use_quoted_printable_p = false; + +// +// Ugh, we need to do this currently to access this boolean. +// +bool +UseQuotedPrintable(void) +{ + return mime_use_quoted_printable_p; +} + +/* This function will parse a list of email addresses and groups and just + * return a list of email addresses (recipient) + * + * The input could be: + * [recipient | group] *[,recipient | group] + * + * The group syntax is: + * group-name:[recipient *[,recipient]]; + * + * the output will be: + * recipient *[, recipient] + * + * As the result will always be equal or smaller than the input string, + * the extraction will be made in place. Don't need to create a new buffer. + */ +static nsresult StripOutGroupNames(char * addresses) +{ + char aChar; + char * readPtr = addresses; // current read position + char * writePtr = addresses; // current write position + char * previousSeparator = addresses; // remember last time we wrote a recipient separator + char * endPtr = addresses + PL_strlen(addresses); + + bool quoted = false; // indicate if we are between double quote + bool group = false; // indicate if we found a group prefix + bool atFound = false; // indicate if we found an @ in the current recipient. group name should not have an @ + + while (readPtr < endPtr) + { + aChar = *readPtr; + readPtr ++; + switch(aChar) + { + case '\\': + if (*readPtr == '"') //ignore escaped quote + readPtr ++; + continue; + + case '"': + quoted = !quoted; + break; + + case '@': + if (!quoted) + atFound = true; + break; + + case ':': + if (!quoted && !atFound) + { + // ok, we found a group name + // let's backup the write cursor to remove the group name + writePtr = previousSeparator + 1; + group = true; + continue; + } + break; + + case ';': + if (quoted || !group) + break; + else + group = false; + //end of the group, act like a recipient separator now... + /* NO BREAK */ + MOZ_FALLTHROUGH; + case ',': + if (!quoted) + { + atFound = false; + //let check if we already have a comma separator in the output string + if (writePtr > addresses && *(writePtr - 1) == ',') + writePtr --; + *writePtr = ','; + previousSeparator = writePtr; + writePtr ++; + continue; + } + break; + } + *writePtr = aChar; + writePtr ++; + } + + if (writePtr > addresses && *(writePtr - 1) == ',') + writePtr --; + *writePtr = '\0'; + + return NS_OK; +} + + +// This private class just provides us an external URL listener, with callback functionality. + +class MsgDeliveryListener : public nsIUrlListener +{ +public: + MsgDeliveryListener(nsIMsgSend *aMsgSend, bool inIsNewsDelivery); + + NS_DECL_ISUPPORTS + NS_DECL_NSIURLLISTENER + +private: + virtual ~MsgDeliveryListener(); + nsCOMPtr<nsIMsgSend> mMsgSend; + bool mIsNewsDelivery; +}; + +NS_IMPL_ISUPPORTS(MsgDeliveryListener, nsIUrlListener) + +MsgDeliveryListener::MsgDeliveryListener(nsIMsgSend *aMsgSend, bool inIsNewsDelivery) +{ + mMsgSend = aMsgSend; + mIsNewsDelivery = inIsNewsDelivery; +} + +MsgDeliveryListener::~MsgDeliveryListener() +{ +} + +NS_IMETHODIMP MsgDeliveryListener::OnStartRunningUrl(nsIURI *url) +{ + if (mMsgSend) + mMsgSend->NotifyListenerOnStartSending(nullptr, 0); + + return NS_OK; +} + +NS_IMETHODIMP MsgDeliveryListener::OnStopRunningUrl(nsIURI *url, nsresult aExitCode) +{ + if (url) + { + nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(url); + if (mailUrl) + mailUrl->UnRegisterListener(this); + } + + // Let mMsgSend sort out the OnStopSending notification - it knows more about + // the messages than we do. + if (mMsgSend) + mMsgSend->SendDeliveryCallback(url, mIsNewsDelivery, aExitCode); + + return NS_OK; +} + + +/* the following macro actually implement addref, release and query interface for our component. */ +NS_IMPL_ISUPPORTS(nsMsgComposeAndSend, nsIMsgSend, nsIMsgOperationListener, + nsISupportsWeakReference) + +nsMsgComposeAndSend::nsMsgComposeAndSend() : + m_messageKey(nsMsgKey_None) +{ + mGUINotificationEnabled = true; + mAbortInProcess = false; + mMultipartRelatedAttachmentCount = -1; + mSendMailAlso = false; + + m_dont_deliver_p = false; + m_deliver_mode = nsMsgDeliverNow; + + m_pre_snarfed_attachments_p = false; + m_digest_p = false; + m_be_synchronous_p = false; + m_attachment1_type = 0; + m_attachment1_encoding = 0; + m_attachment1_body = 0; + m_attachment1_body_length = 0; + m_attachment_count = 0; + m_attachment_pending_count = 0; + m_status = NS_OK; + m_plaintext = nullptr; + m_related_part = nullptr; + m_related_body_part = nullptr; + mOriginalHTMLBody = nullptr; + + mNeedToPerformSecondFCC = false; + mPerformingSecondFCC = false; + + mPreloadedAttachmentCount = 0; + mRemoteAttachmentCount = 0; + mCompFieldLocalAttachments = 0; + mCompFieldRemoteAttachments = 0; + mMessageWarningSize = 0; + + mSendReport = new nsMsgSendReport(); +} + +nsMsgComposeAndSend::~nsMsgComposeAndSend() +{ + PR_Free(m_attachment1_type); + PR_Free(m_attachment1_encoding); + PR_Free(m_attachment1_body); + PR_Free(mOriginalHTMLBody); + + if (m_plaintext) + { + if (m_plaintext->mTmpFile) + m_plaintext->mTmpFile->Remove(false); + + m_plaintext = nullptr; + } + + if (mHTMLFile) + mHTMLFile->Remove(false); + + if (mCopyFile) + mCopyFile->Remove(false); + + if (mCopyFile2) + mCopyFile2->Remove(false); + + if (mTempFile && !mReturnFile) + mTempFile->Remove(false); + + m_attachments.Clear(); +} + +NS_IMETHODIMP nsMsgComposeAndSend::GetDefaultPrompt(nsIPrompt ** aPrompt) +{ + NS_ENSURE_ARG(aPrompt); + *aPrompt = nullptr; + + nsresult rv = NS_OK; + + if (mParentWindow) + { + rv = mParentWindow->GetPrompter(aPrompt); + if (NS_SUCCEEDED(rv) && *aPrompt) + return NS_OK; + } + + /* If we cannot find a prompter, try the mail3Pane window */ + nsCOMPtr<nsIMsgWindow> msgWindow; + nsCOMPtr <nsIMsgMailSession> mailSession (do_GetService(NS_MSGMAILSESSION_CONTRACTID)); + if (mailSession) + { + mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + rv = msgWindow->GetPromptDialog(aPrompt); + } + + return rv; +} + +nsresult nsMsgComposeAndSend::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) +{ +// TODO: stop using mail3pane window! + nsCOMPtr<nsIMsgWindow> msgWindow; + nsCOMPtr<nsIMsgMailSession> mailSession(do_GetService(NS_MSGMAILSESSION_CONTRACTID)); + mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) { + nsCOMPtr<nsIDocShell> docShell; + msgWindow->GetRootDocShell(getter_AddRefs(docShell)); + nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(docShell)); + nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks; + msgWindow->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks)); + if (notificationCallbacks) { + nsCOMPtr<nsIInterfaceRequestor> aggregrateIR; + MsgNewInterfaceRequestorAggregation(notificationCallbacks, ir, getter_AddRefs(aggregrateIR)); + ir = aggregrateIR; + } + if (ir) { + NS_ADDREF(*aCallbacks = ir); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + + +static char *mime_mailto_stream_read_buffer = 0; +static char *mime_mailto_stream_write_buffer = 0; + + +char * mime_get_stream_write_buffer(void) +{ + if (!mime_mailto_stream_write_buffer) + mime_mailto_stream_write_buffer = (char *) PR_Malloc(MIME_BUFFER_SIZE); + return mime_mailto_stream_write_buffer; +} + +static bool isEmpty(const char* aString) +{ + return (!aString) || (!*aString); +} + +void nsMsgComposeAndSend::GenerateMessageId() +{ + if (isEmpty(mCompFields->GetMessageId())) + { + if (isEmpty(mCompFields->GetTo()) && + isEmpty(mCompFields->GetCc()) && + isEmpty(mCompFields->GetBcc()) && + !isEmpty(mCompFields->GetNewsgroups())) + { + bool generateNewsMessageId = false; + mUserIdentity->GetBoolAttribute("generate_news_message_id", &generateNewsMessageId); + if (!generateNewsMessageId) + return; + } + + char* msgID = msg_generate_message_id(mUserIdentity); + mCompFields->SetMessageId(msgID); + PR_Free(msgID); + } +} + +// Don't I18N this line...this is per the spec! +#define MIME_MULTIPART_BLURB "This is a multi-part message in MIME format." + +/* All of the desired attachments have been written to individual temp files, + and we know what's in them. Now we need to make a final temp file of the + actual mail message, containing all of the other files after having been + encoded as appropriate. + */ +NS_IMETHODIMP +nsMsgComposeAndSend::GatherMimeAttachments() +{ + bool shouldDeleteDeliveryState = true; + nsresult status; + uint32_t i; + PRFileDesc *in_file = 0; + char *buffer = 0; + nsString msg; + bool body_is_us_ascii = true; + bool isUsingQP = false; + + nsMsgSendPart* toppart = nullptr; // The very top most container of the message + // that we are going to send. + + nsMsgSendPart* mainbody = nullptr; // The leaf node that contains the text of the + // message we're going to contain. + + nsMsgSendPart* maincontainer = nullptr; // The direct child of toppart that will + // contain the mainbody. If mainbody is + // the same as toppart, then this is + // also the same. But if mainbody is + // to end up somewhere inside of a + // multipart/alternative or a + // multipart/related, then this is that + // multipart object. + + nsMsgSendPart* plainpart = nullptr; // If we converted HTML into plaintext, + // the message or child containing the plaintext + // goes here. (Need to use this to determine + // what headers to append/set to the main + // message body.) + + uint32_t multipartRelatedCount = GetMultipartRelatedCount(); // The number of related part we will have to generate + + nsCOMPtr<nsIPrompt> promptObject; // only used if we have to show an alert here.... + GetDefaultPrompt(getter_AddRefs(promptObject)); + + char *hdrs = 0; + bool maincontainerISrelatedpart = false; + const char * toppart_type = nullptr; + + status = m_status; + if (NS_FAILED(status)) + goto FAIL; + + if (!m_attachment1_type) { + m_attachment1_type = PL_strdup(TEXT_PLAIN); + if (!m_attachment1_type) + goto FAILMEM; + } + + nsresult rv; + + // If we have a text/html main part, and we need a plaintext attachment, then + // we'll do so now. This is an asynchronous thing, so we'll kick it off and + // count on getting back here when it finishes. + + if (m_plaintext == nullptr && + (mCompFields->GetForcePlainText() || + mCompFields->GetUseMultipartAlternative()) && + m_attachment1_body && PL_strcmp(m_attachment1_type, TEXT_HTML) == 0) + { + // + // If we get here, we have an HTML body, but we really need to send + // a text/plain message, so we will write the HTML out to a disk file, + // fire off another URL request for this local disk file and that will + // take care of the conversion... + // + rv = nsMsgCreateTempFile("nsemail.html", getter_AddRefs(mHTMLFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIOutputStream> tempfile; + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(tempfile), mHTMLFile, -1, 00600); + if (NS_FAILED(rv)) + { + if (mSendReport) + { + nsAutoString error_msg; + nsMsgBuildMessageWithTmpFile(mHTMLFile, error_msg); + mSendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false); + } + status = NS_MSG_UNABLE_TO_OPEN_TMP_FILE; + goto FAIL; + } + + if (mOriginalHTMLBody) + { + uint32_t origLen = strlen(mOriginalHTMLBody); + uint32_t n; + nsresult rv = tempfile->Write(mOriginalHTMLBody, origLen, &n); + if (NS_FAILED(rv) || n != origLen) + { + status = NS_MSG_ERROR_WRITING_FILE; + goto FAIL; + } + } + + if (NS_FAILED(tempfile->Flush())) + { + status = NS_MSG_ERROR_WRITING_FILE; + goto FAIL; + } + + tempfile->Close(); + + m_plaintext = new nsMsgAttachmentHandler; + if (!m_plaintext) + goto FAILMEM; + m_plaintext->SetMimeDeliveryState(this); + m_plaintext->m_bogus_attachment = true; + + nsAutoCString tempURL; + rv = NS_GetURLSpecFromFile(mHTMLFile, tempURL); + if (NS_FAILED(rv) || NS_FAILED(nsMsgNewURL(getter_AddRefs(m_plaintext->mURL), tempURL.get()))) + { + m_plaintext = nullptr; + goto FAILMEM; + } + + m_plaintext->m_type = TEXT_HTML; + m_plaintext->m_charset = mCompFields->GetCharacterSet(); + m_plaintext->m_desiredType = TEXT_PLAIN; + m_attachment_pending_count ++; + status = m_plaintext->SnarfAttachment(mCompFields); + if (NS_FAILED(status)) + goto FAIL; + if (m_attachment_pending_count > 0) + return NS_OK; + } + + /* Kludge to avoid having to allocate memory on the toy computers... */ + buffer = mime_get_stream_write_buffer(); + if (! buffer) + goto FAILMEM; + + NS_ASSERTION (m_attachment_pending_count == 0, "m_attachment_pending_count != 0"); + + mComposeBundle->GetStringFromName(u"assemblingMessage", + getter_Copies(msg)); + SetStatusMessage( msg ); + + /* First, open the message file. + */ + rv = nsMsgCreateTempFile("nsemail.eml", getter_AddRefs(mTempFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mOutputFile), mTempFile, -1, 00600); + if (NS_FAILED(rv)) + { + status = NS_MSG_UNABLE_TO_OPEN_TMP_FILE; + if (mSendReport) + { + nsAutoString error_msg; + nsMsgBuildMessageWithTmpFile(mTempFile, error_msg); + mSendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false); + } + goto FAIL; + } + + // generate a message id, if necessary + GenerateMessageId( ); + + mainbody = new nsMsgSendPart(this, mCompFields->GetCharacterSet()); + if (!mainbody) + goto FAILMEM; + + mainbody->SetMainPart(true); + mainbody->SetType(m_attachment1_type ? m_attachment1_type : TEXT_PLAIN); + + NS_ASSERTION(mainbody->GetBuffer() == nullptr, "not-null buffer"); + status = mainbody->SetBuffer(m_attachment1_body ? m_attachment1_body : ""); + if (NS_FAILED(status)) + goto FAIL; + + // Determine the encoding of the main message body before we free it. + PR_FREEIF(m_attachment1_encoding); + if (m_attachment1_body) + mCompFields->GetBodyIsAsciiOnly(&body_is_us_ascii); + + if (!mCompFields->GetForceMsgEncoding() && (body_is_us_ascii || + nsMsgI18Nstateful_charset(mCompFields->GetCharacterSet()))) { + m_attachment1_encoding = PL_strdup (ENCODING_7BIT); + } else if (mime_use_quoted_printable_p) { + m_attachment1_encoding = PL_strdup (ENCODING_QUOTED_PRINTABLE); + isUsingQP = true; + } else { + m_attachment1_encoding = PL_strdup (ENCODING_8BIT); + } + + // Make sure the lines don't get to long. + if (m_attachment1_body) { + uint32_t max_column = 0; + uint32_t cur_column = 0; + for (char* c = m_attachment1_body; *c; c++) { + if (*c == '\n') { + if (cur_column > max_column) + max_column = cur_column; + cur_column = 0; + } else if (*c != '\r') { + cur_column++; + } + } + // Check one last time for the last line. + if (cur_column > max_column) + max_column = cur_column; + if (max_column > LINELENGTH_ENCODING_THRESHOLD && !isUsingQP) { + // To encode "long lines" use a CTE that will transmit shorter lines. + // Switch to base64 if we are not already using "quoted printable". + PR_FREEIF(m_attachment1_encoding); + m_attachment1_encoding = PL_strdup (ENCODING_BASE64); + } + } + PR_FREEIF (m_attachment1_body); + + maincontainer = mainbody; + + // If we were given a pre-saved collection of HTML and contained images, + // then we want mainbody to point to the HTML lump therein. + if (m_related_part) + { + // If m_related_part is of type text/html, set both maincontainer + // and mainbody to point to it. If m_related_part is multipart/related, + // however, set mainbody to be the first child within m_related_part. + delete mainbody; + + // No matter what, maincontainer points to the outermost related part. + maincontainer = m_related_part; + maincontainerISrelatedpart = true; + + mainbody = m_related_part->GetChild(0); + mainbody->SetMainPart(true); + } + if (m_plaintext) + { + // + // OK. We have a plaintext version of the main body that we want to + // send instead of or with the text/html. Shove it in. + // + plainpart = new nsMsgSendPart(this, mCompFields->GetCharacterSet()); + if (!plainpart) + goto FAILMEM; + status = plainpart->SetType(TEXT_PLAIN); + if (NS_FAILED(status)) + goto FAIL; + status = plainpart->SetFile(m_plaintext->mTmpFile); + if (NS_FAILED(status)) + goto FAIL; + + m_plaintext->mMainBody = true; + + // Determine Content-Transfer-Encoding for the attachments. + m_plaintext->PickEncoding(mCompFields->GetCharacterSet(), this); + const char *charset = mCompFields->GetCharacterSet(); + hdrs = mime_generate_attachment_headers(m_plaintext->m_type.get(), + nullptr, + m_plaintext->m_encoding.get(), + m_plaintext->m_description.get(), + m_plaintext->m_xMacType.get(), + m_plaintext->m_xMacCreator.get(), + nullptr, 0, + m_digest_p, + m_plaintext, + charset, + charset, + body_is_us_ascii, + nullptr, + true); + if (!hdrs) + goto FAILMEM; + status = plainpart->SetOtherHeaders(hdrs); + PR_Free(hdrs); + hdrs = nullptr; + if (NS_FAILED(status)) + goto FAIL; + + if (mCompFields->GetUseMultipartAlternative()) + { + nsMsgSendPart* htmlpart = maincontainer; + maincontainer = new nsMsgSendPart(this); + if (!maincontainer) + goto FAILMEM; + + // Setup the maincontainer stuff... + status = maincontainer->SetType(MULTIPART_ALTERNATIVE); + if (NS_FAILED(status)) + goto FAIL; + + status = maincontainer->AddChild(plainpart); + if (NS_FAILED(status)) + goto FAIL; + + status = maincontainer->AddChild(htmlpart); + if (NS_FAILED(status)) + goto FAIL; + + // Create the encoder for the plaintext part here, + // because we aren't the main part (attachment1). + // (This, along with the rest of the routine, should really + // be restructured so that no special treatment is given to + // the main body text that came in. Best to put attachment1_text + // etc. into a nsMsgSendPart, then reshuffle the parts. Sigh.) + if (m_plaintext->m_encoding.LowerCaseEqualsLiteral(ENCODING_QUOTED_PRINTABLE)) + { + plainpart->SetEncoder(MimeEncoder::GetQPEncoder( + mime_encoder_output_fn, this)); + } + else if (m_plaintext->m_encoding.LowerCaseEqualsLiteral(ENCODING_BASE64)) + { + plainpart->SetEncoder(MimeEncoder::GetBase64Encoder( + mime_encoder_output_fn, this)); + } + } + else + { + delete maincontainer; + if (maincontainerISrelatedpart) + m_related_part = nullptr; // in that case, m_related_part == maincontainer which we have just deleted! + maincontainer = plainpart; + mainbody = maincontainer; + PR_FREEIF(m_attachment1_type); + m_attachment1_type = PL_strdup(TEXT_PLAIN); + if (!m_attachment1_type) + goto FAILMEM; + + // Override attachment1_encoding here. We do this blindly since we are + // sending plaintext only at this point. + PR_FREEIF(m_attachment1_encoding); + m_attachment1_encoding = ToNewCString(m_plaintext->m_encoding); + } + } + + // check if we need to encapsulate the message in a multipart/mixed or multipart/digest + if (m_attachment_count > multipartRelatedCount) + { + toppart = new nsMsgSendPart(this); + if (!toppart) + goto FAILMEM; + + status = toppart->SetType(m_digest_p ? MULTIPART_DIGEST : MULTIPART_MIXED); + if (NS_FAILED(status)) + goto FAIL; + + status = toppart->AddChild(maincontainer); + if (NS_FAILED(status)) + goto FAIL; + } + else + toppart = maincontainer; + + // Is the top part a multipart container? + // can't use m_attachment_count because it's not reliable for that + // instead use type of main part. See bug #174396 + toppart_type = toppart->GetType(); // GetType return directly the member variable, don't free it! + if (!m_crypto_closure && toppart_type && !PL_strncasecmp(toppart_type, "multipart/", 10)) + { + status = toppart->SetBuffer(MIME_MULTIPART_BLURB); + if (NS_FAILED(status)) + goto FAIL; + } + + { + nsCOMPtr<msgIWritableStructuredHeaders> outputHeaders = + do_CreateInstance(NS_ISTRUCTUREDHEADERS_CONTRACTID); + status = mime_generate_headers(mCompFields, m_deliver_mode, outputHeaders); + if (NS_FAILED(status)) + goto FAIL; + + // Convert the blocks of headers into a single string for emission. + nsAutoCString headers; + outputHeaders->BuildMimeText(headers); + + // If we converted HTML into plaintext, the plaintext part (plainpart) + // already has its content-type and content-transfer-encoding ("other") + // headers set. + // + // In the specific case where such a plaintext part is the top-level message + // part (iff an HTML message is being sent as text only and no other + // attachments exist) we want to preserve the original plainpart headers, + // since they contain accurate transfer encoding and Mac type/creator + // information. + // + // So, in the above case we append the main message headers, otherwise we + // overwrite whatever headers may have existed. + if (plainpart && plainpart == toppart) + status = toppart->AppendOtherHeaders(headers.get()); + else + status = toppart->SetOtherHeaders(headers.get()); + } + + if (NS_FAILED(status)) + goto FAIL; + + // Set up the first part (user-typed.) For now, do it even if the first + // part is empty; we need to add things to skip it if this part is empty. + + // Set up encoder for the first part (message body.) + // + NS_ASSERTION(!m_attachment1_encoder, "not-null m_attachment1_encoder"); + if (!PL_strcasecmp(m_attachment1_encoding, ENCODING_BASE64)) + { + m_attachment1_encoder = MimeEncoder::GetBase64Encoder( + mime_encoder_output_fn, this); + } + else if (!PL_strcasecmp(m_attachment1_encoding, ENCODING_QUOTED_PRINTABLE)) + { + m_attachment1_encoder = MimeEncoder::GetQPEncoder(mime_encoder_output_fn, + this); + } + + // If we converted HTML into plaintext, the plaintext part + // already has its type/encoding headers set. So, in the specific + // case where such a plaintext part is the main message body + // (iff an HTML message is being sent as text only) + // we want to avoid generating type/encoding/digest headers; + // in all other cases, generate such headers here. + // + // We really want to set up headers as a dictionary of some sort + // so that we need not worry about duplicate header lines. + // + if ((!plainpart) || (plainpart != mainbody)) + { + const char *charset = mCompFields->GetCharacterSet(); + hdrs = mime_generate_attachment_headers (m_attachment1_type, + nullptr, + m_attachment1_encoding, + 0, 0, 0, 0, 0, + m_digest_p, + nullptr, /* no "ma"! */ + charset, + charset, + mCompFields->GetBodyIsAsciiOnly(), + nullptr, + true); + if (!hdrs) + goto FAILMEM; + status = mainbody->AppendOtherHeaders(hdrs); + if (NS_FAILED(status)) + goto FAIL; + } + + PR_FREEIF(hdrs); + + mainbody->SetEncoder(m_attachment1_encoder.forget()); + + // + // Now we need to process attachments and slot them in the + // correct hierarchy. + // + if (m_attachment_count > 0) + { + // Kludge to avoid having to allocate memory on the toy computers... + if (! mime_mailto_stream_read_buffer) + mime_mailto_stream_read_buffer = (char *) PR_Malloc (MIME_BUFFER_SIZE); + buffer = mime_mailto_stream_read_buffer; + if (! buffer) + goto FAILMEM; + + // Gather all of the attachments for this message that are NOT + // part of an enclosed MHTML message! + for (i = 0; i < m_attachment_count; i++) + { + nsMsgAttachmentHandler *ma = m_attachments[i]; + if (!ma->mMHTMLPart) + PreProcessPart(ma, toppart); + } + + // + // If we have a m_related_part as a container for children, then we have to + // tack on these children for the part + // + if (m_related_part) + { + for (i = 0; i < m_attachment_count; i++) + { + // + // look for earlier part with the same content id. If we find it, + // need to remember the mapping between our node index and the + // part num of the earlier part. + int32_t nodeIndex = m_attachments[i]->mNodeIndex; + if (nodeIndex != -1) + { + for (uint32_t j = 0; j < i; j++) + { + if (m_attachments[j]->mNodeIndex != -1 && + m_attachments[j]->m_contentId.Equals(m_attachments[i]->m_contentId)) + m_partNumbers[nodeIndex] = m_partNumbers[m_attachments[j]->mNodeIndex]; + } + } + // rhp: This is here because we could get here after saying OK + // to a lot of prompts about not being able to fetch this part! + // + if (m_attachments[i]->mPartUserOmissionOverride) + continue; + + // Now, we need to add this part to the m_related_part member so the + // message will be generated correctly. + if (m_attachments[i]->mMHTMLPart) + PreProcessPart(m_attachments[i], m_related_part); + } + } + + } + + // Tell the user we are creating the message... + mComposeBundle->GetStringFromName(u"creatingMailMessage", + getter_Copies(msg)); + SetStatusMessage( msg ); + + // OK, now actually write the structure we've carefully built up. + status = toppart->Write(); + if (NS_FAILED(status)) + goto FAIL; + + /* Close down encryption stream */ + if (m_crypto_closure) + { + status = m_crypto_closure->FinishCryptoEncapsulation(false, mSendReport); + m_crypto_closure = nullptr; + if (NS_FAILED(status)) goto FAIL; + } + + if (mOutputFile) + { + if (NS_FAILED(mOutputFile->Flush())) + { + status = NS_MSG_ERROR_WRITING_FILE; + goto FAIL; + } + + mOutputFile->Close(); + mOutputFile = nullptr; + + // mTempFile is stale because we wrote to it. Get another copy to refresh. + nsCOMPtr<nsIFile> tempFileCopy; + mTempFile->Clone(getter_AddRefs(tempFileCopy)); + mTempFile = tempFileCopy; + tempFileCopy = nullptr; + /* If we don't do this check...ZERO length files can be sent */ + int64_t fileSize; + rv = mTempFile->GetFileSize(&fileSize); + if (NS_FAILED(rv) || fileSize == 0) + { + status = NS_MSG_ERROR_WRITING_FILE; + goto FAIL; + } + } + + mComposeBundle->GetStringFromName(u"assemblingMessageDone", + getter_Copies(msg)); + SetStatusMessage(msg); + + if (m_dont_deliver_p && mListener) + { + // + // Need to ditch the file spec here so that we don't delete the + // file, since in this case, the caller wants the file + // + mReturnFile = mTempFile; + mTempFile = nullptr; + if (!mReturnFile) + NotifyListenerOnStopSending(nullptr, NS_ERROR_OUT_OF_MEMORY, nullptr, nullptr); + else + { + NotifyListenerOnStopSending(nullptr, NS_OK, nullptr, mReturnFile); + } + } + else + { + status = DeliverMessage(); + if (NS_SUCCEEDED(status)) + shouldDeleteDeliveryState = false; + } + goto FAIL; + +FAILMEM: + status = NS_ERROR_OUT_OF_MEMORY; + +FAIL: + if (toppart) + delete toppart; + toppart = nullptr; + mainbody = nullptr; + maincontainer = nullptr; + + if (in_file) + { + PR_Close (in_file); + in_file = nullptr; + } + + if (shouldDeleteDeliveryState) + { + if (NS_FAILED(status)) + { + m_status = status; + nsresult ignoreMe; + Fail(status, nullptr, &ignoreMe); + } + } + + return status; +} + +int32_t +nsMsgComposeAndSend::PreProcessPart(nsMsgAttachmentHandler *ma, + nsMsgSendPart *toppart) // The very top most container of the message +{ + nsresult status; + char *hdrs = 0; + nsMsgSendPart *part = nullptr; + + // If this was one of those dead parts from a quoted web page, + // then just return safely. + // + if (ma->m_bogus_attachment) + return 0; + + // If at this point we *still* don't have a content-type, then + // we're never going to get one. + if (ma->m_type.IsEmpty()) + ma->m_type = UNKNOWN_CONTENT_TYPE; + + ma->PickEncoding(mCompFields->GetCharacterSet(), this); + ma->PickCharset(); + + part = new nsMsgSendPart(this); + if (!part) + return 0; + status = toppart->AddChild(part); + // Remember the part number if it has a node index. + if (ma->mNodeIndex != -1) + m_partNumbers[ma->mNodeIndex] = part->m_partNum; + + if (NS_FAILED(status)) + return 0; + status = part->SetType(ma->m_type.get()); + if (NS_FAILED(status)) + return 0; + + if (ma->mSendViaCloud) + ma->m_encoding = ENCODING_7BIT; + + nsCString turl; + if (!ma->mURL) + { + if (!ma->m_uri.IsEmpty()) + turl = ma->m_uri; + } + else { + status = ma->mURL->GetSpec(turl); + if (NS_FAILED(status)) + return 0; + } + + nsCString type(ma->m_type); + nsCString realName(ma->m_realName); + + // for cloud attachments, make the part an html part with no name, + // so we don't show it as an attachment. + if (ma->mSendViaCloud) + { + type.Assign("application/octet-stream"); + realName.Truncate(); + } + hdrs = mime_generate_attachment_headers (type.get(), + ma->m_typeParam.get(), + ma->m_encoding.get(), + ma->m_description.get(), + ma->m_xMacType.get(), + ma->m_xMacCreator.get(), + realName.get(), + turl.get(), + m_digest_p, + ma, + ma->m_charset.get(), // rhp - this needs + // to be the charset + // we determine from + // the file or none + // at all! + mCompFields->GetCharacterSet(), + false, // bodyIsAsciiOnly to false + // for attachments + ma->m_contentId.get(), + false); + if (!hdrs) + return 0; + + status = part->SetOtherHeaders(hdrs); + PR_FREEIF(hdrs); + if (ma->mSendViaCloud) + { + nsCString urlSpec; + status = ma->mURL->GetSpec(urlSpec); + if (NS_FAILED(status)) + return 0; + + // Need to add some headers so that libmime can restore the cloud info + // when loading a draft message. + nsCString draftInfo(HEADER_X_MOZILLA_CLOUD_PART": cloudFile; url="); + draftInfo.Append(ma->mCloudUrl.get()); + // don't leak user file paths or account keys to recipients. + if (m_deliver_mode == nsMsgSaveAsDraft) + { + draftInfo.Append("; provider="); + draftInfo.Append(ma->mCloudProviderKey.get()); + draftInfo.Append("; file="); + draftInfo.Append(urlSpec.get()); + } + draftInfo.Append("; name="); + draftInfo.Append(ma->m_realName.get()); + draftInfo.Append(CRLF); + part->AppendOtherHeaders(draftInfo.get()); + part->SetType("application/octet-stream"); + part->SetBuffer(""); + } + if (NS_FAILED(status)) + return 0; + status = part->SetFile(ma->mTmpFile); + if (NS_FAILED(status)) + return 0; + if (ma->m_encoder) + { + part->SetEncoder(ma->m_encoder.forget()); + } + + ma->m_current_column = 0; + + if (ma->m_type.LowerCaseEqualsLiteral(MESSAGE_RFC822) || + ma->m_type.LowerCaseEqualsLiteral(MESSAGE_NEWS)) { + part->SetStripSensitiveHeaders(true); + } + + return 1; +} + +# define FROB(X) \ + if (X && *X) \ + { \ + if (*recipients) PL_strcat(recipients, ","); \ + PL_strcat(recipients, X); \ + } + +nsresult nsMsgComposeAndSend::BeginCryptoEncapsulation () +{ + // Try to create a secure compose object. If we can create it, then query to see + // if we need to use it for this send transaction. + + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgComposeSecure> secureCompose; + secureCompose = do_CreateInstance(NS_MSGCOMPOSESECURE_CONTRACTID, &rv); + // it's not an error scenario of there is secure compose + if (NS_FAILED(rv)) + return NS_OK; + + if (secureCompose) + { + bool requiresEncryptionWork = false; + secureCompose->RequiresCryptoEncapsulation(mUserIdentity, mCompFields, &requiresEncryptionWork); + if (requiresEncryptionWork) + { + m_crypto_closure = secureCompose; + // bah i'd like to move the following blurb into the implementation of BeginCryptoEncapsulation; however + // the apis for nsIMsgComposeField just aren't rich enough. It requires the implementor to jump through way + // too many string conversions.... + char * recipients = (char *) + PR_MALLOC((mCompFields->GetTo() ? strlen(mCompFields->GetTo()) : 0) + + (mCompFields->GetCc() ? strlen(mCompFields->GetCc()) : 0) + + (mCompFields->GetBcc() ? strlen(mCompFields->GetBcc()) : 0) + + (mCompFields->GetNewsgroups() ? strlen(mCompFields->GetNewsgroups()) : 0) + 20); + if (!recipients) return NS_ERROR_OUT_OF_MEMORY; + + *recipients = 0; + + FROB(mCompFields->GetTo()) + FROB(mCompFields->GetCc()) + FROB(mCompFields->GetBcc()) + FROB(mCompFields->GetNewsgroups()) + + // end section of code I'd like to move to the implementor..... + rv = m_crypto_closure->BeginCryptoEncapsulation(mOutputFile, + recipients, + mCompFields, + mUserIdentity, + mSendReport, + (m_deliver_mode == nsMsgSaveAsDraft)); + + PR_FREEIF(recipients); + } + + } + + return rv; +} + +nsresult +mime_write_message_body(nsIMsgSend *state, const char *buf, uint32_t size) +{ + NS_ENSURE_ARG_POINTER(state); + + nsCOMPtr<nsIOutputStream> output; + nsCOMPtr<nsIMsgComposeSecure> crypto_closure; + + state->GetOutputStream(getter_AddRefs(output)); + if (!output) + return NS_MSG_ERROR_WRITING_FILE; + + state->GetCryptoclosure(getter_AddRefs(crypto_closure)); + if (crypto_closure) + { + // Copy to new null-terminated string so JS glue doesn't crash when + // MimeCryptoWriteBlock() is implemented in JS. + nsCString bufWithNull; + bufWithNull.Assign(buf, size); + return crypto_closure->MimeCryptoWriteBlock(bufWithNull.get(), size); + } + + uint32_t n; + nsresult rv = output->Write(buf, size, &n); + if (NS_FAILED(rv) || n != size) + { + return NS_MSG_ERROR_WRITING_FILE; + } + + return NS_OK; +} + +nsresult +mime_encoder_output_fn(const char *buf, int32_t size, void *closure) +{ + nsMsgComposeAndSend *state = (nsMsgComposeAndSend *) closure; + return mime_write_message_body (state, (char *) buf, (uint32_t)size); +} + +nsresult +nsMsgComposeAndSend::GetEmbeddedObjectInfo(nsIDOMNode *node, nsMsgAttachmentData *attachment, bool *acceptObject) +{ + NS_ENSURE_ARG_POINTER(node); + NS_ENSURE_ARG_POINTER(attachment); + NS_ENSURE_ARG_POINTER(acceptObject); + + // GetEmbeddedObjectInfo will determine if we need to attach the source of the + // embedded object with the message. The decision is made automatically unless + // the attribute moz-do-not-send has been set to true or false. + nsresult rv = NS_OK; + + // Reset this structure to null! + *acceptObject = false; + + // We're only interested in body, image, link and anchors which are all + // elements. + nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(node); + if (!domElement) + return NS_OK; + + bool isImage = false; + nsAutoString mozDoNotSendAttr; + domElement->GetAttribute(NS_LITERAL_STRING(ATTR_MOZ_DO_NOT_SEND), mozDoNotSendAttr); + + // Only empty or moz-do-not-send="false" may be accepted later. + if (!(mozDoNotSendAttr.IsEmpty() || mozDoNotSendAttr.LowerCaseEqualsLiteral("false"))) + return NS_OK; + + // Now, we know the types of objects this node can be, so we will do + // our query interface here and see what we come up with + nsCOMPtr<nsIDOMHTMLBodyElement> body = (do_QueryInterface(node)); + // XXX convert to use nsIImageLoadingContent? + nsCOMPtr<nsIDOMHTMLImageElement> image = (do_QueryInterface(node)); + nsCOMPtr<nsIDOMHTMLLinkElement> link = (do_QueryInterface(node)); + nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = (do_QueryInterface(node)); + + // First, try to see if the body as a background image + if (body) + { + nsAutoString tUrl; + if (NS_SUCCEEDED(body->GetBackground(tUrl))) + { + nsAutoCString turlC; + CopyUTF16toUTF8(tUrl, turlC); + if (NS_FAILED(nsMsgNewURL(getter_AddRefs(attachment->m_url), turlC.get()))) + return NS_OK; + } + isImage = true; + } + else if (image) // Is this an image? + { + nsString tUrl; + nsString tName; + nsString tDesc; + + // Create the URI + if (NS_FAILED(image->GetSrc(tUrl))) + return NS_ERROR_FAILURE; + if (tUrl.IsEmpty()) + return NS_OK; + + nsAutoCString turlC; + CopyUTF16toUTF8(tUrl, turlC); + if (NS_FAILED(nsMsgNewURL(getter_AddRefs(attachment->m_url), turlC.get()))) + { + // Well, the first time failed...which means we probably didn't get + // the full path name... + // + nsIDOMDocument *ownerDocument = nullptr; + node->GetOwnerDocument(&ownerDocument); + if (ownerDocument) + { + nsIDocument *doc = nullptr; + if (NS_FAILED(ownerDocument->QueryInterface(NS_GET_IID(nsIDocument),(void**)&doc)) || !doc) + return NS_ERROR_OUT_OF_MEMORY; + + nsAutoCString spec; + nsIURI *uri = doc->GetDocumentURI(); + + if (!uri) + return NS_ERROR_OUT_OF_MEMORY; + + rv = uri->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + // Ok, now get the path to the root doc and tack on the name we + // got from the GetSrc() call.... + NS_ConvertUTF8toUTF16 workURL(spec); + + int32_t loc = workURL.RFindChar('/'); + if (loc >= 0) + workURL.SetLength(loc+1); + workURL.Append(tUrl); + NS_ConvertUTF16toUTF8 workurlC(workURL); + if (NS_FAILED(nsMsgNewURL(getter_AddRefs(attachment->m_url), workurlC.get()))) + return NS_OK; // Continue and send it without this image. + } + } + isImage = true; + + rv = image->GetName(tName); + NS_ENSURE_SUCCESS(rv, rv); + + LossyCopyUTF16toASCII(tName, attachment->m_realName); + rv = image->GetLongDesc(tDesc); + NS_ENSURE_SUCCESS(rv, rv); + attachment->m_description = NS_LossyConvertUTF16toASCII(tDesc); // XXX i18n + } + else if (link) // Is this a link? + { + nsString tUrl; + + // Create the URI + rv = link->GetHref(tUrl); + NS_ENSURE_SUCCESS(rv, rv); + if (tUrl.IsEmpty()) + return NS_OK; + nsAutoCString turlC; + CopyUTF16toUTF8(tUrl, turlC); + if (NS_FAILED(nsMsgNewURL(getter_AddRefs(attachment->m_url), turlC.get()))) + return NS_OK; + } + else if (anchor) + { + nsString tUrl; + nsString tName; + + // Create the URI + rv = anchor->GetHref(tUrl); + NS_ENSURE_SUCCESS(rv, rv); + if (tUrl.IsEmpty()) + return NS_OK; + nsAutoCString turlC; + CopyUTF16toUTF8(tUrl, turlC); + // This can fail since the URL might not be recognised, for example: + // <a href="skype:some-name?call" title="Skype">Some Name</a> + if (NS_FAILED(nsMsgNewURL(getter_AddRefs(attachment->m_url), turlC.get()))) + return NS_OK; + rv = anchor->GetName(tName); + NS_ENSURE_SUCCESS(rv, rv); + LossyCopyUTF16toASCII(tName, attachment->m_realName); + } + else + { + // If we get here, we got something we didn't expect! + // Just try to continue and send it without this thing. + return NS_OK; + } + + // Before going further, check what scheme we're dealing with. Files need to + // be converted to data URLs during composition. "Attaching" means + // sending as a cid: part instead of original URL. + bool isHttp = + (NS_SUCCEEDED(attachment->m_url->SchemeIs("http", &isHttp)) && isHttp) || + (NS_SUCCEEDED(attachment->m_url->SchemeIs("https", &isHttp)) && isHttp); + // Attach (= create cid: part) http resources if moz-do-not-send is set to + // "false". Special processing for images: We attach if the preference says so. + // Note that moz-do-not-send="true" is already processed above so the preference + // doesn't override this. + if (isHttp) + { + *acceptObject = + (isImage && Preferences::GetBool("mail.compose.attach_http_images", false)) || + mozDoNotSendAttr.LowerCaseEqualsLiteral("false"); + return NS_OK; + } + + bool isData = + (NS_SUCCEEDED(attachment->m_url->SchemeIs("data", &isData)) && isData); + bool isNews = + (NS_SUCCEEDED(attachment->m_url->SchemeIs("news", &isNews)) && isNews) || + (NS_SUCCEEDED(attachment->m_url->SchemeIs("snews", &isNews)) && isNews) || + (NS_SUCCEEDED(attachment->m_url->SchemeIs("nntp", &isNews)) && isNews); + // Attach (= create cid: part) data resources if moz-do-not-send is not + // specified or set to "false". + if (isData || isNews) + { + *acceptObject = mozDoNotSendAttr.IsEmpty() || + mozDoNotSendAttr.LowerCaseEqualsLiteral("false"); + return NS_OK; + } + + return NS_OK; +} + + +uint32_t +nsMsgComposeAndSend::GetMultipartRelatedCount(bool forceToBeCalculated /*=false*/) +{ + nsresult rv = NS_OK; + uint32_t count; + + if (mMultipartRelatedAttachmentCount != -1 && !forceToBeCalculated) + return (uint32_t)mMultipartRelatedAttachmentCount; + + //First time here, let's calculate the correct number of related part we need to generate + mMultipartRelatedAttachmentCount = 0; + if (mEditor) + { + nsCOMPtr<nsIEditorMailSupport> mailEditor (do_QueryInterface(mEditor)); + if (!mailEditor) + return 0; + + rv = mailEditor->GetEmbeddedObjects(getter_AddRefs(mEmbeddedObjectList)); + if (NS_FAILED(rv)) + return 0; + } + if (!mEmbeddedObjectList) + return 0; + + if (NS_SUCCEEDED(mEmbeddedObjectList->GetLength(&count))) + { + if (count > 0) + { + // preallocate space for part numbers + m_partNumbers.SetLength(count); + // Let parse the list to count the number of valid objects. BTW, we can remove the others from the list + RefPtr<nsMsgAttachmentData> attachment(new nsMsgAttachmentData); + + int32_t i; + nsCOMPtr<nsIDOMNode> node; + + for (i = count - 1, count = 0; i >= 0; i --) + { + // Reset this structure to null! + + // now we need to get the element in the array and do the magic + // to process this element. + // + node = do_QueryElementAt(mEmbeddedObjectList, i, &rv); + bool acceptObject = false; + if (node) + { + rv = GetEmbeddedObjectInfo(node, attachment, &acceptObject); + } + else // outlook import case + { + nsCOMPtr<nsIMsgEmbeddedImageData> imageData = + do_QueryElementAt(mEmbeddedObjectList, i, &rv); + if (!imageData) + continue; + acceptObject = true; + } + if (NS_SUCCEEDED(rv) && acceptObject) + count ++; + } + } + mMultipartRelatedAttachmentCount = (int32_t)count; + return count; + } + else + return 0; +} + +nsresult +nsMsgComposeAndSend::GetBodyFromEditor() +{ + // + // Now we have to fix up and get the HTML from the editor. After we + // get the HTML data, we need to store it in the m_attachment_1_body + // member variable after doing the necessary charset conversion. + // + + // + // Query the editor, get the body of HTML! + // + uint32_t flags = nsIDocumentEncoder::OutputFormatted | + nsIDocumentEncoder::OutputNoFormattingInPre | + nsIDocumentEncoder::OutputDisallowLineBreaking; + nsAutoString bodyStr; + char16_t* bodyText = nullptr; + nsresult rv; + char16_t *origHTMLBody = nullptr; + + // Ok, get the body...the DOM should have been whacked with + // Content ID's already + if (mEditor) + mEditor->OutputToString(NS_LITERAL_STRING(TEXT_HTML), flags, bodyStr); + else + bodyStr = NS_ConvertASCIItoUTF16(m_attachment1_body); + + // If we really didn't get a body, just return NS_OK + if (bodyStr.IsEmpty()) + return NS_OK; + bodyText = ToNewUnicode(bodyStr); + if (!bodyText) + return NS_ERROR_OUT_OF_MEMORY; + + // If we are forcing this to be plain text, we should not be + // doing this conversion. + bool doConversion = true; + + if ( (mCompFields) && mCompFields->GetForcePlainText() ) + doConversion = false; + + if (doConversion) + { + nsCOMPtr<mozITXTToHTMLConv> conv = do_CreateInstance(MOZ_TXTTOHTMLCONV_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) + { + uint32_t whattodo = mozITXTToHTMLConv::kURLs; + bool enable_structs = false; + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (pPrefBranch) + { + rv = pPrefBranch->GetBoolPref(PREF_MAIL_SEND_STRUCT, &enable_structs); + if (enable_structs) + whattodo = whattodo | mozITXTToHTMLConv::kStructPhrase; + } + + char16_t* wresult; + rv = conv->ScanHTML(bodyText, whattodo, &wresult); + if (NS_SUCCEEDED(rv)) + { + // Save the original body for possible attachment as plain text + // We should have what the user typed in stored in mOriginalHTMLBody + origHTMLBody = bodyText; + bodyText = wresult; + } + } + } + + nsCString attachment1_body; + + // Convert body to mail charset + nsCString outCString; + const char *aCharset = mCompFields->GetCharacterSet(); + + if (aCharset && *aCharset) + { + rv = nsMsgI18NConvertFromUnicode(aCharset, nsDependentString(bodyText), outCString, false, true); + bool isAsciiOnly = NS_IsAscii(outCString.get()) && + !nsMsgI18Nstateful_charset(mCompFields->GetCharacterSet()); + if (mCompFields->GetForceMsgEncoding()) + isAsciiOnly = false; + mCompFields->SetBodyIsAsciiOnly(isAsciiOnly); + + // If the body contains characters outside the current mail charset, + // convert to UTF-8. + if (NS_ERROR_UENC_NOMAPPING == rv) + { + bool needToCheckCharset; + mCompFields->GetNeedToCheckCharset(&needToCheckCharset); + if (needToCheckCharset) + { + // Just use UTF-8 and be done with it + // unless disable_fallback_to_utf8 is set for this charset. + bool disableFallback = false; + nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (prefBranch) + { + nsCString prefName("mailnews.disable_fallback_to_utf8."); + prefName.Append(aCharset); + prefBranch->GetBoolPref(prefName.get(), &disableFallback); + } + if (!disableFallback) + { + CopyUTF16toUTF8(nsDependentString(bodyText), outCString); + mCompFields->SetCharacterSet("UTF-8"); + } + } + } + + if (NS_SUCCEEDED(rv)) + attachment1_body = outCString; + + // If we have an origHTMLBody that is not null, this means that it is + // different than the bodyText because of formatting conversions. Because of + // this we need to do the charset conversion on this part separately + if (origHTMLBody) + { + nsCString newBody; + rv = nsMsgI18NConvertFromUnicode(aCharset, + nsDependentString(origHTMLBody), newBody, false, true); + if (NS_SUCCEEDED(rv)) + { + mOriginalHTMLBody = ToNewCString(newBody); + } + } + else { + mOriginalHTMLBody = ToNewCString(attachment1_body); + } + + NS_Free(bodyText); //Don't need it anymore + } + else + return NS_ERROR_FAILURE; + + rv = SnarfAndCopyBody(attachment1_body, TEXT_HTML); + + return rv; +} + +// +// This is the routine that does the magic of generating the body and the +// attachments for the multipart/related email message. +// +typedef struct +{ + nsIDOMNode *node; + char *url; +} domSaveStruct; + +nsresult +nsMsgComposeAndSend::ProcessMultipartRelated(int32_t *aMailboxCount, int32_t *aNewsCount) +{ + uint32_t multipartCount = GetMultipartRelatedCount(); + nsresult rv = NS_OK; + uint32_t i; + int32_t j = -1; + uint32_t k; + int32_t duplicateOf; + domSaveStruct *domSaveArray = nullptr; + + if (!mEmbeddedObjectList) + return NS_ERROR_MIME_MPART_ATTACHMENT_ERROR; + + RefPtr<nsMsgAttachmentData> attachment(new nsMsgAttachmentData); + int32_t locCount = -1; + + if (multipartCount > 0) + { + domSaveArray = (domSaveStruct *)PR_MALLOC(sizeof(domSaveStruct) * multipartCount); + if (!domSaveArray) + return NS_ERROR_MIME_MPART_ATTACHMENT_ERROR; + memset(domSaveArray, 0, sizeof(domSaveStruct) * multipartCount); + } + + nsCOMPtr<nsIDOMNode> node; + for (i = mPreloadedAttachmentCount; i < (mPreloadedAttachmentCount + multipartCount);) + { + // Ok, now we need to get the element in the array and do the magic + // to process this element. + // + + locCount++; + mEmbeddedObjectList->QueryElementAt(locCount, NS_GET_IID(nsIDOMNode), getter_AddRefs(node)); + if (node) + { + bool acceptObject = false; + rv = GetEmbeddedObjectInfo(node, attachment, &acceptObject); + NS_ENSURE_SUCCESS(rv, NS_ERROR_MIME_MPART_ATTACHMENT_ERROR); + if (!acceptObject) + continue; + nsString nodeValue; + node->GetNodeValue(nodeValue); + LossyCopyUTF16toASCII(nodeValue, m_attachments[i]->m_contentId); + } + else + { + nsCOMPtr<nsIMsgEmbeddedImageData> imageData = do_QueryElementAt(mEmbeddedObjectList, locCount, &rv); + if (!imageData) + return NS_ERROR_MIME_MPART_ATTACHMENT_ERROR; + imageData->GetUri(getter_AddRefs(attachment->m_url)); + if (!attachment->m_url) + return NS_ERROR_MIME_MPART_ATTACHMENT_ERROR; + imageData->GetCid(m_attachments[i]->m_contentId); + imageData->GetName(attachment->m_realName); + } + + + // MUST set this to get placed in the correct part of the message + m_attachments[i]->mMHTMLPart = true; + + m_attachments[i]->mDeleteFile = true; + m_attachments[i]->m_done = false; + m_attachments[i]->SetMimeDeliveryState(this); + m_attachments[i]->mNodeIndex = locCount; + + j++; + domSaveArray[j].node = node; + + // check if we have alreay attached this object, don't need to attach it twice + duplicateOf = -1; + for (k = mPreloadedAttachmentCount; k < i; k++) + { + bool isEqual = false; + NS_ASSERTION(attachment->m_url, "null attachment url!"); + if (attachment->m_url) + (void)attachment->m_url->Equals(m_attachments[k]->mURL, &isEqual); + if (isEqual) + { + duplicateOf = k; + break; + } + } + + if (duplicateOf == -1) + { + // + // Now we have to get all of the interesting information from + // the nsIDOMNode we have in hand... + m_attachments[i]->mURL = attachment->m_url; + + m_attachments[i]->m_overrideType = attachment->m_realType; + m_attachments[i]->m_overrideEncoding = attachment->m_realEncoding; + m_attachments[i]->m_desiredType = attachment->m_desiredType; + m_attachments[i]->m_description = attachment->m_description; + m_attachments[i]->m_realName = attachment->m_realName; + m_attachments[i]->m_xMacType = attachment->m_xMacType; + m_attachments[i]->m_xMacCreator = attachment->m_xMacCreator; + + m_attachments[i]->m_charset = mCompFields->GetCharacterSet(); + m_attachments[i]->m_encoding = ENCODING_7BIT; + + if (m_attachments[i]->mURL) + msg_pick_real_name(m_attachments[i], nullptr, mCompFields->GetCharacterSet()); + + if (m_attachments[i]->m_contentId.IsEmpty()) + { + // + // Next, generate a content id for use with this part + // + nsCString email; + mUserIdentity->GetEmail(email); + m_attachments[i]->m_contentId = mime_gen_content_id(locCount+1, email.get()); + } + + // + // Start counting the attachments which are going to come from mail folders + // and from NNTP servers. + // + if (m_attachments[i]->mURL) + { + nsIURI *uri = m_attachments[i]->mURL; + bool match = false; + if ((NS_SUCCEEDED(uri->SchemeIs("mailbox", &match)) && match) || + (NS_SUCCEEDED(uri->SchemeIs("imap", &match)) && match)) + (*aMailboxCount)++; + else if ((NS_SUCCEEDED(uri->SchemeIs("news", &match)) && match) || + (NS_SUCCEEDED(uri->SchemeIs("snews", &match)) && match)) + (*aNewsCount)++; + else + { + // Additional account types need a mechanism to report that they are + // message protocols. If there is an nsIMsgProtocolInfo component + // registered for this scheme, we'll consider it a mailbox + // attachment. + nsAutoCString contractID; + contractID.Assign( + NS_LITERAL_CSTRING("@mozilla.org/messenger/protocol/info;1")); + nsAutoCString scheme; + uri->GetScheme(scheme); + contractID.Append(scheme); + nsCOMPtr<nsIMsgProtocolInfo> msgProtocolInfo = + do_CreateInstance(contractID.get()); + if (msgProtocolInfo) + (*aMailboxCount)++; + } + + } + } + else + { + m_attachments[i]->m_contentId = m_attachments[duplicateOf]->m_contentId; + m_attachments[i]->SetMimeDeliveryState(nullptr); + } + + // + // Ok, while we are here, we should whack the DOM with the generated + // Content-ID for this object. This will be necessary for generating + // the HTML we need. + // + nsString domURL; + if (!m_attachments[duplicateOf == -1 ? i : duplicateOf]->m_contentId.IsEmpty()) + { + nsString newSpec(NS_LITERAL_STRING("cid:")); + newSpec.AppendASCII(m_attachments[duplicateOf == -1 ? i : duplicateOf]->m_contentId.get()); + + // Now, we know the types of objects this node can be, so we will do + // our query interface here and see what we come up with + nsCOMPtr<nsIDOMHTMLBodyElement> body = (do_QueryInterface(domSaveArray[j].node)); + nsCOMPtr<nsIDOMHTMLImageElement> image = (do_QueryInterface(domSaveArray[j].node)); + nsCOMPtr<nsIDOMHTMLLinkElement> link = (do_QueryInterface(domSaveArray[j].node)); + nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = (do_QueryInterface(domSaveArray[j].node)); + + if (anchor) + { + anchor->GetHref(domURL); + anchor->SetHref(newSpec); + } + else if (link) + { + link->GetHref(domURL); + link->SetHref(newSpec); + } + else if (image) + { + image->GetSrc(domURL); + image->SetSrc(newSpec); + } + else if (body) + { + body->GetBackground(domURL); + body->SetBackground(newSpec); + } + + if (!domURL.IsEmpty()) + domSaveArray[j].url = ToNewCString(NS_LossyConvertUTF16toASCII(domURL)); + } + i++; + } + + rv = GetBodyFromEditor(); + + // + // Ok, now we need to un-whack the DOM or we have a screwed up document on + // Send failure. + // + for (i = 0; i < multipartCount; i++) + { + if ( (!domSaveArray[i].node) || (!domSaveArray[i].url) ) + continue; + + // Now, we know the types of objects this node can be, so we will do + // our query interface here and see what we come up with + nsCOMPtr<nsIDOMHTMLBodyElement> body = (do_QueryInterface(domSaveArray[i].node)); + nsCOMPtr<nsIDOMHTMLImageElement> image = (do_QueryInterface(domSaveArray[i].node)); + nsCOMPtr<nsIDOMHTMLLinkElement> link = (do_QueryInterface(domSaveArray[i].node)); + nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = (do_QueryInterface(domSaveArray[i].node)); + + // STRING USE WARNING: hoisting the following conversion might save code-space, since it happens along every path + + if (anchor) + anchor->SetHref(NS_ConvertASCIItoUTF16(domSaveArray[i].url)); + else if (link) + link->SetHref(NS_ConvertASCIItoUTF16(domSaveArray[i].url)); + else if (image) + image->SetSrc(NS_ConvertASCIItoUTF16(domSaveArray[i].url)); + else if (body) + body->SetBackground(NS_ConvertASCIItoUTF16(domSaveArray[i].url)); + + free(domSaveArray[i].url); + } + + PR_FREEIF(domSaveArray); + + // + // Now, we have to create that first child node for the multipart + // message that holds the body as well as the attachment handler + // for this body part. + // + // If we ONLY have multipart objects, then we don't need the container + // for the multipart section... + // + m_related_part = new nsMsgSendPart(this); + if (!m_related_part) + return NS_ERROR_OUT_OF_MEMORY; + + m_related_part->SetMimeDeliveryState(this); + m_related_part->SetType(MULTIPART_RELATED); + // We are now going to use the m_related_part as a way to store the + // MHTML message for this email. + // + m_related_body_part = new nsMsgSendPart(this); + if (!m_related_body_part) + return NS_ERROR_OUT_OF_MEMORY; + + // Set the body contents... + m_related_body_part->SetBuffer(m_attachment1_body); + m_related_body_part->SetType(m_attachment1_type); + + m_related_part->AddChild(m_related_body_part); + + return rv; +} + +nsresult +nsMsgComposeAndSend::CountCompFieldAttachments() +{ + //Reset the counters + mCompFieldLocalAttachments = 0; + mCompFieldRemoteAttachments = 0; + + //Get the attachments array + nsCOMPtr<nsISimpleEnumerator> attachments; + mCompFields->GetAttachments(getter_AddRefs(attachments)); + if (!attachments) + return NS_OK; + + nsresult rv; + + // Parse the attachments array + bool moreAttachments; + nsCString url; + nsCOMPtr<nsISupports> element; + while (NS_SUCCEEDED(attachments->HasMoreElements(&moreAttachments)) && moreAttachments) { + rv = attachments->GetNext(getter_AddRefs(element)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAttachment> attachment = do_QueryInterface(element, &rv); + if (NS_SUCCEEDED(rv) && attachment) + { + attachment->GetUrl(url); + if (!url.IsEmpty()) + { + // Check to see if this is a file URL, if so, don't retrieve + // like a remote URL... + if (nsMsgIsLocalFile(url.get())) + mCompFieldLocalAttachments++; + else // This is a remote URL... + mCompFieldRemoteAttachments++; + } + } + } + + return NS_OK; +} + +// +// Since we are at the head of the list, we start from ZERO. +// +nsresult +nsMsgComposeAndSend::AddCompFieldLocalAttachments() +{ + // If none, just return... + if (mCompFieldLocalAttachments <= 0) + return NS_OK; + + //Get the attachments array + nsCOMPtr<nsISimpleEnumerator> attachments; + mCompFields->GetAttachments(getter_AddRefs(attachments)); + if (!attachments) + return NS_OK; + + uint32_t newLoc = 0; + nsresult rv; + nsCString url; + + //Parse the attachments array + bool moreAttachments; + nsCOMPtr<nsISupports> element; + while (NS_SUCCEEDED(attachments->HasMoreElements(&moreAttachments)) && moreAttachments) { + rv = attachments->GetNext(getter_AddRefs(element)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAttachment> attachment = do_QueryInterface(element, &rv); + if (NS_SUCCEEDED(rv) && attachment) + { + bool sendViaCloud = false; + attachment->GetSendViaCloud(&sendViaCloud); + m_attachments[newLoc]->mSendViaCloud = sendViaCloud; + attachment->GetUrl(url); + if (!url.IsEmpty()) + { + bool sendViaCloud; + attachment->GetSendViaCloud(&sendViaCloud); + if (sendViaCloud) + { + nsCString cloudProviderKey; + // We'd like to output a part for the attachment, just an html part + // with information about how to download the attachment. + // m_attachments[newLoc]->m_done = true; + attachment->GetHtmlAnnotation(m_attachments[newLoc]->mHtmlAnnotation); + m_attachments[newLoc]->m_type.AssignLiteral("text/html"); + attachment->GetCloudProviderKey(m_attachments[newLoc]->mCloudProviderKey); + attachment->GetContentLocation(m_attachments[newLoc]->mCloudUrl); + } + // Just look for local file:// attachments and do the right thing. + if (nsMsgIsLocalFile(url.get())) + { + // + // Now we have to setup the m_attachments entry for the file:// + // URL that is passed in... + // + m_attachments[newLoc]->mDeleteFile = false; + + nsMsgNewURL(getter_AddRefs(m_attachments[newLoc]->mURL), url.get()); + + if (m_attachments[newLoc]->mTmpFile) + { + if (m_attachments[newLoc]->mDeleteFile) + m_attachments[newLoc]->mTmpFile->Remove(false); + m_attachments[newLoc]->mTmpFile =nullptr; + } + nsresult rv; + nsCOMPtr<nsIIOService> ioService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED); + nsCOMPtr <nsIURI> uri; + rv = ioService->NewURI(url, nullptr, nullptr, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIFileURL> fileURL = do_QueryInterface(uri); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr <nsIFile> fileURLFile; + fileURL->GetFile(getter_AddRefs(fileURLFile)); + m_attachments[newLoc]->mTmpFile = do_QueryInterface(fileURLFile); + m_attachments[newLoc]->mDeleteFile = false; + if (m_attachments[newLoc]->mURL) + { + nsAutoString proposedName; + attachment->GetName(proposedName); + msg_pick_real_name(m_attachments[newLoc], proposedName.get(), mCompFields->GetCharacterSet()); + } + + // Now, most importantly, we need to figure out what the content type is for + // this attachment...If we can't, then just make it application/octet-stream + + #ifdef MAC_OSX + //Mac always need to snarf the file to figure out how to send it, maybe we need to use apple double... + // unless caller has already set the content type, in which case, trust them. + bool mustSnarfAttachment = true; + #else + bool mustSnarfAttachment = false; + #endif + if (sendViaCloud) + mustSnarfAttachment = false; + + attachment->GetContentType(getter_Copies(m_attachments[newLoc]->m_type)); + if (m_attachments[newLoc]->m_type.IsEmpty()) + { + nsresult rv = NS_OK; + nsCOMPtr<nsIMIMEService> mimeFinder (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && mimeFinder) + { + nsCOMPtr<nsIURL> fileUrl(do_CreateInstance(NS_STANDARDURL_CONTRACTID)); + if (fileUrl) + { + nsAutoCString fileExt; + //First try using the real file name + rv = fileUrl->SetFileName(m_attachments[newLoc]->m_realName); + if (NS_SUCCEEDED(rv)) + { + rv = fileUrl->GetFileExtension(fileExt); + if (NS_SUCCEEDED(rv) && !fileExt.IsEmpty()) { + nsAutoCString type; + mimeFinder->GetTypeFromExtension(fileExt, type); + #ifndef XP_MACOSX + if (!type.Equals("multipart/appledouble")) // can't do apple double on non-macs + #endif + m_attachments[newLoc]->m_type = type; + } + } + + //Then try using the url if we still haven't figured out the content type + if (m_attachments[newLoc]->m_type.IsEmpty()) + { + rv = fileUrl->SetSpec(url); + if (NS_SUCCEEDED(rv)) + { + rv = fileUrl->GetFileExtension(fileExt); + if (NS_SUCCEEDED(rv) && !fileExt.IsEmpty()) { + nsAutoCString type; + mimeFinder->GetTypeFromExtension(fileExt, type); + #ifndef XP_MACOSX + if (!type.Equals("multipart/appledouble")) // can't do apple double on non-macs + #endif + m_attachments[newLoc]->m_type = type; + // rtf and vcs files may look like text to sniffers, + // but they're not human readable. + if (type.IsEmpty() && !fileExt.IsEmpty() && + (MsgLowerCaseEqualsLiteral(fileExt, "rtf") || + MsgLowerCaseEqualsLiteral(fileExt, "vcs"))) + m_attachments[newLoc]->m_type = APPLICATION_OCTET_STREAM; + } + } + } + } + } + } + else + { + attachment->GetContentTypeParam(getter_Copies(m_attachments[newLoc]->m_typeParam)); + mustSnarfAttachment = false; + } + + //We need to snarf the file to figure out how to send it only if we don't have a content type... + if (mustSnarfAttachment || m_attachments[newLoc]->m_type.IsEmpty()) + { + m_attachments[newLoc]->m_done = false; + m_attachments[newLoc]->SetMimeDeliveryState(this); + } + else + { + m_attachments[newLoc]->m_done = true; + m_attachments[newLoc]->SetMimeDeliveryState(nullptr); + } + // For local files, if they are HTML docs and we don't have a charset, we should + // sniff the file and see if we can figure it out. + if (!m_attachments[newLoc]->m_type.IsEmpty()) + { + if (m_attachments[newLoc]->m_type.LowerCaseEqualsLiteral(TEXT_HTML)) + { + char *tmpCharset = (char *)nsMsgI18NParseMetaCharset(m_attachments[newLoc]->mTmpFile); + if (tmpCharset[0] != '\0') + m_attachments[newLoc]->m_charset = tmpCharset; + } + } + + attachment->GetMacType(getter_Copies(m_attachments[newLoc]->m_xMacType)); + attachment->GetMacCreator(getter_Copies(m_attachments[newLoc]->m_xMacCreator)); + + ++newLoc; + } + } + } + } + return NS_OK; +} + +nsresult +nsMsgComposeAndSend::AddCompFieldRemoteAttachments(uint32_t aStartLocation, + int32_t *aMailboxCount, + int32_t *aNewsCount) +{ + // If none, just return... + if (mCompFieldRemoteAttachments <= 0) + return NS_OK; + + //Get the attachments array + nsCOMPtr<nsISimpleEnumerator> attachments; + mCompFields->GetAttachments(getter_AddRefs(attachments)); + if (!attachments) + return NS_OK; + + uint32_t newLoc = aStartLocation; + + nsresult rv; + bool moreAttachments; + nsCString url; + nsCOMPtr<nsISupports> element; + while (NS_SUCCEEDED(attachments->HasMoreElements(&moreAttachments)) && moreAttachments) { + rv = attachments->GetNext(getter_AddRefs(element)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAttachment> attachment = do_QueryInterface(element, &rv); + if (NS_SUCCEEDED(rv) && attachment) + { + attachment->GetUrl(url); + if (!url.IsEmpty()) + { + // Just look for files that are NOT local file attachments and do + // the right thing. + if (! nsMsgIsLocalFile(url.get())) + { + // Check for message attachment, see nsMsgMailNewsUrl::GetIsMessageUri. + nsCOMPtr<nsIURI> nsiuri = do_CreateInstance(NS_STANDARDURL_CONTRACTID); + NS_ENSURE_STATE(nsiuri); + nsiuri->SetSpec(url); + nsAutoCString scheme; + nsiuri->GetScheme(scheme); + bool isAMessageAttachment = + StringEndsWith(scheme, NS_LITERAL_CSTRING("-message")); + + m_attachments[newLoc]->mDeleteFile = true; + m_attachments[newLoc]->m_done = false; + m_attachments[newLoc]->SetMimeDeliveryState(this); + + if (!isAMessageAttachment) + nsMsgNewURL(getter_AddRefs(m_attachments[newLoc]->mURL), url.get()); + + m_attachments[newLoc]->m_encoding = ENCODING_7BIT; + + attachment->GetMacType(getter_Copies(m_attachments[newLoc]->m_xMacType)); + attachment->GetMacCreator(getter_Copies(m_attachments[newLoc]->m_xMacCreator)); + + /* Count up attachments which are going to come from mail folders + and from NNTP servers. */ + bool do_add_attachment = false; + if (isAMessageAttachment) + { + do_add_attachment = true; + if (!PL_strncasecmp(url.get(), "news-message://", 15)) + (*aNewsCount)++; + else + (*aMailboxCount)++; + + m_attachments[newLoc]->m_uri = url; + m_attachments[newLoc]->mURL = nullptr; + } + else + do_add_attachment = (nullptr != m_attachments[newLoc]->mURL); + m_attachments[newLoc]->mSendViaCloud = false; + if (do_add_attachment) + { + nsAutoString proposedName; + attachment->GetName(proposedName); + msg_pick_real_name(m_attachments[newLoc], proposedName.get(), mCompFields->GetCharacterSet()); + ++newLoc; + } + } + } + } + } + return NS_OK; +} + +nsresult +nsMsgComposeAndSend::HackAttachments(nsIArray *attachments, + nsIArray *preloadedAttachments) +{ + // + // First, count the total number of attachments we are going to process + // for this operation! This is a little more complicated than you might + // think because we have a few ways to specify attachments. Via the nsMsgAttachmentData + // as well as the composition fields. + // + CountCompFieldAttachments(); + + // Count the preloaded attachments! + mPreloadedAttachmentCount = 0; + + // For now, manually add the local attachments in the comp field! + mPreloadedAttachmentCount += mCompFieldLocalAttachments; + uint32_t numAttachments = 0, numPreloadedAttachments = 0; + if (attachments) + attachments->GetLength(&numAttachments); + if (preloadedAttachments) + preloadedAttachments->GetLength(&numPreloadedAttachments); + mPreloadedAttachmentCount += numPreloadedAttachments; + + // Count the attachments we have to go retrieve! Keep in mind, that these + // will be APPENDED to the current list of URL's that we have gathered if + // this is a multpart/related send operation + mRemoteAttachmentCount = GetMultipartRelatedCount(); + + // For now, manually add the remote attachments in the comp field! + mRemoteAttachmentCount += mCompFieldRemoteAttachments; + + mRemoteAttachmentCount += numAttachments; + + m_attachment_count = mPreloadedAttachmentCount + mRemoteAttachmentCount; + + uint32_t i; // counter for location in attachment array... + // Now create the array of attachment handlers... + for (i = 0; i < m_attachment_count; i++) { + RefPtr<nsMsgAttachmentHandler> handler = new nsMsgAttachmentHandler; + m_attachments.AppendElement(handler); + } + + // + // First, we need to attach the files that are defined in the comp fields... + if (NS_FAILED(AddCompFieldLocalAttachments())) + return NS_ERROR_INVALID_ARG; + + // Now handle the preloaded attachments... + if (numPreloadedAttachments > 0) + { + // These are attachments which have already been downloaded to tmp files. + // We merely need to point the internal attachment data at those tmp + // files. + m_pre_snarfed_attachments_p = true; + + for (i = mCompFieldLocalAttachments; i < mPreloadedAttachmentCount; i++) + { + nsCOMPtr<nsIMsgAttachedFile> attachedFile = do_QueryElementAt(preloadedAttachments, i); + if (!attachedFile) + continue; + + /* These attachments are already "snarfed". */ + m_attachments[i]->mDeleteFile = false; + m_attachments[i]->SetMimeDeliveryState(nullptr); + m_attachments[i]->m_done = true; + + attachedFile->GetOrigUrl(getter_AddRefs(m_attachments[i]->mURL)); + + attachedFile->GetType(m_attachments[i]->m_type); + + // Set it to the compose fields for a default... + m_attachments[i]->m_charset = mCompFields->GetCharacterSet(); + + // If we still don't have a content type, we should really try sniff one out! + if (m_attachments[i]->m_type.IsEmpty()) + m_attachments[i]->PickEncoding(mCompFields->GetCharacterSet(), this); + + // For local files, if they are HTML docs and we don't have a charset, we should + // sniff the file and see if we can figure it out. + if (!m_attachments[i]->m_type.IsEmpty()) + { + nsCOMPtr<nsIFile> tmpFile; + attachedFile->GetTmpFile(getter_AddRefs(tmpFile)); + if (m_attachments[i]->m_type.LowerCaseEqualsLiteral(TEXT_HTML) && tmpFile) + { + char *tmpCharset = (char *)nsMsgI18NParseMetaCharset(tmpFile); + if (tmpCharset[0] != '\0') + m_attachments[i]->m_charset = tmpCharset; + } + } + + attachedFile->GetDescription(m_attachments[i]->m_description); + attachedFile->GetRealName(m_attachments[i]->m_realName); + attachedFile->GetXMacType(m_attachments[i]->m_xMacType); + attachedFile->GetXMacCreator(m_attachments[i]->m_xMacCreator); + attachedFile->GetEncoding(m_attachments[i]->m_encoding); + + if (m_attachments[i]->mTmpFile) + { + if (m_attachments[i]->mDeleteFile) + m_attachments[i]->mTmpFile->Remove(false); + m_attachments[i]->mTmpFile = nullptr; + } + attachedFile->GetTmpFile(getter_AddRefs(m_attachments[i]->mTmpFile)); + + attachedFile->GetSize(&m_attachments[i]->m_size); + attachedFile->GetUnprintableCount(&m_attachments[i]->m_unprintable_count); + attachedFile->GetHighbitCount(&m_attachments[i]->m_highbit_count); + attachedFile->GetCtlCount(&m_attachments[i]->m_ctl_count); + attachedFile->GetNullCount(&m_attachments[i]->m_null_count); + attachedFile->GetMaxLineLength(&m_attachments[i]->m_max_column); + + /* If the attachment has an encoding, and it's not one of + the "null" encodings, then keep it. */ + if (!m_attachments[i]->m_encoding.IsEmpty() && + !m_attachments[i]->m_encoding.LowerCaseEqualsLiteral(ENCODING_7BIT) && + !m_attachments[i]->m_encoding.LowerCaseEqualsLiteral(ENCODING_8BIT) && + !m_attachments[i]->m_encoding.LowerCaseEqualsLiteral(ENCODING_BINARY)) + m_attachments[i]->m_already_encoded_p = true; + + if (m_attachments[i]->mURL) + msg_pick_real_name(m_attachments[i], nullptr, mCompFields->GetCharacterSet()); + } + } + + // First, handle the multipart related attachments if any... + // + int32_t mailbox_count = 0, news_count = 0; + int32_t multipartRelatedCount = GetMultipartRelatedCount(); + + if (multipartRelatedCount > 0) + { + nsresult rv = ProcessMultipartRelated(&mailbox_count, &news_count); + if (NS_FAILED(rv)) + { + // The destructor will take care of the m_attachment array + return rv; + } + } + + // + // Now add the comp field remote attachments... + // + if (NS_FAILED( AddCompFieldRemoteAttachments( (mPreloadedAttachmentCount + multipartRelatedCount), + &mailbox_count, &news_count) )) + return NS_ERROR_INVALID_ARG; + + // + // Now deal remote attachments and attach multipart/related attachments (url's and such..) + // first! + // + if (attachments) + { + int32_t locCount = -1; + + for (i = (mPreloadedAttachmentCount + GetMultipartRelatedCount() + mCompFieldRemoteAttachments); i < m_attachment_count; i++) + { + locCount++; + nsCOMPtr<nsIMsgAttachmentData> attachment(do_QueryElementAt(attachments, i)); + if (!attachment) + continue; + m_attachments[i]->mDeleteFile = true; + m_attachments[i]->m_done = false; + m_attachments[i]->SetMimeDeliveryState(this); + + attachment->GetUrl(getter_AddRefs(m_attachments[i]->mURL)); + + attachment->GetRealType(m_attachments[i]->m_overrideType); + m_attachments[i]->m_charset = mCompFields->GetCharacterSet(); + attachment->GetRealEncoding(m_attachments[i]->m_overrideEncoding); + attachment->GetDesiredType(m_attachments[i]->m_desiredType); + attachment->GetDescription(m_attachments[i]->m_description); + attachment->GetRealName(m_attachments[i]->m_realName); + attachment->GetXMacType(m_attachments[i]->m_xMacType); + attachment->GetXMacCreator(m_attachments[i]->m_xMacCreator); + m_attachments[i]->m_encoding = ENCODING_7BIT; + + // real name is set in the case of vcard so don't change it. XXX STILL NEEDED? + // m_attachments[i]->m_real_name = 0; + + /* Count up attachments which are going to come from mail folders + and from NNTP servers. */ + if (m_attachments[i]->mURL) + { + nsIURI *uri = m_attachments[i]->mURL; + bool match = false; + if ((NS_SUCCEEDED(uri->SchemeIs("mailbox", &match)) && match) || + (NS_SUCCEEDED(uri->SchemeIs("imap", &match)) && match)) + mailbox_count++; + else if ((NS_SUCCEEDED(uri->SchemeIs("news", &match)) && match) || + (NS_SUCCEEDED(uri->SchemeIs("snews", &match)) && match)) + news_count++; + else + { + // Additional account types need a mechanism to report that they are + // message protocols. If there is an nsIMsgProtocolInfo component + // registered for this scheme, we'll consider it a mailbox + // attachment. + nsAutoCString contractID; + contractID.Assign( + NS_LITERAL_CSTRING("@mozilla.org/messenger/protocol/info;1")); + nsAutoCString scheme; + uri->GetScheme(scheme); + contractID.Append(scheme); + nsCOMPtr<nsIMsgProtocolInfo> msgProtocolInfo = + do_CreateInstance(contractID.get()); + if (msgProtocolInfo) + mailbox_count++; + } + if (uri) + msg_pick_real_name(m_attachments[i], nullptr, mCompFields->GetCharacterSet()); + } + } + } + + bool needToCallGatherMimeAttachments = true; + + if (m_attachment_count > 0) + { + // If there is more than one mailbox URL, or more than one NNTP url, + // do the load in serial rather than parallel, for efficiency. + if (mailbox_count > 1 || news_count > 1) + m_be_synchronous_p = true; + + m_attachment_pending_count = m_attachment_count; + + // Start the URL attachments loading (eventually, an exit routine will + // call the done_callback). + + for (i = 0; i < m_attachment_count; i++) + { + if (m_attachments[i]->m_done || m_attachments[i]->mSendViaCloud) + { + m_attachment_pending_count--; + continue; + } + + // + // IF we get here and the URL is NULL, just dec the pending count and move on!!! + // + if ( (!m_attachments[i]->mURL) && (!m_attachments[i]->m_uri.Length()) ) + { + m_attachments[i]->m_bogus_attachment = true; + m_attachments[i]->m_done = true; + m_attachments[i]->SetMimeDeliveryState(nullptr); + m_attachment_pending_count--; + continue; + } + + // + // This only returns a failure code if NET_GetURL was not called + // (and thus no exit routine was or will be called.) + // + + // Display some feedback to user... + nsString msg; + nsAutoString attachmentFileName; + NS_ConvertUTF8toUTF16 params(m_attachments[i]->m_realName); + const char16_t *formatParams[1]; + if (!params.IsEmpty()) { + formatParams[0] = params.get(); + } else if (m_attachments[i]->mURL) { + nsCString asciiSpec; + m_attachments[i]->mURL->GetAsciiSpec(asciiSpec); + attachmentFileName.AssignASCII(asciiSpec.get()); + formatParams[0] = attachmentFileName.get(); + } + mComposeBundle->FormatStringFromName(u"gatheringAttachment", + formatParams, 1, getter_Copies(msg)); + + if (!msg.IsEmpty()) + { + SetStatusMessage(msg); + } + + /* As SnarfAttachment will call GatherMimeAttachments when it will be done (this is an async process), + we need to avoid to call it ourself. + */ + needToCallGatherMimeAttachments = false; + + nsresult status = m_attachments[i]->SnarfAttachment(mCompFields); + if (NS_FAILED(status)) + { + nsString errorMsg; + nsresult rv = ConvertToUnicode(nsMsgI18NFileSystemCharset(), m_attachments[i]->m_realName, attachmentFileName); + if (attachmentFileName.IsEmpty() && m_attachments[i]->mURL) { + nsCString asciiSpec; + m_attachments[i]->mURL->GetAsciiSpec(asciiSpec); + attachmentFileName.AssignASCII(asciiSpec.get()); + rv = NS_OK; + } + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIStringBundle> bundle; + const char16_t *params[] = { attachmentFileName.get() }; + mComposeBundle->FormatStringFromName(u"errorAttachingFile", + params, 1, + getter_Copies(errorMsg)); + mSendReport->SetMessage(nsIMsgSendReport::process_Current, errorMsg.get(), false); + mSendReport->SetError(nsIMsgSendReport::process_Current, + NS_MSG_ERROR_ATTACHING_FILE, + false); + } + return NS_MSG_ERROR_ATTACHING_FILE; + } + if (m_be_synchronous_p) + break; + } + } + + // If no attachments - finish now (this will call the done_callback). + if (needToCallGatherMimeAttachments) + return GatherMimeAttachments(); + + return NS_OK; +} + +nsresult +nsMsgComposeAndSend::InitCompositionFields(nsMsgCompFields *fields, + const nsACString &aOriginalMsgURI, + MSG_ComposeType aType) +{ + nsresult rv = NS_OK; + const char *pStr = nullptr; + + mCompFields = new nsMsgCompFields(); + if (!mCompFields) + return NS_ERROR_OUT_OF_MEMORY; + + const char *cset = fields->GetCharacterSet(); + // Make sure charset is sane... + if (!cset || !*cset) + { + mCompFields->SetCharacterSet("UTF-8"); + } + else + { + mCompFields->SetCharacterSet(fields->GetCharacterSet()); + } + + // Now, we will look for a URI defined as the default FCC pref. If this is set, + // then SetFcc will use this value. The FCC field is a URI for the server that + // will hold the "Sent" folder...the + // + // First, look at what was passed in via the "fields" structure...if that was + // set then use it, otherwise, fall back to what is set in the prefs... + // + // But even before that, pay attention to the new OVERRIDE pref that will cancel + // any and all copy operations! + // + bool doFcc = true; + rv = mUserIdentity->GetDoFcc(&doFcc); + if (!doFcc) + { + // If the identity pref "fcc" is set to false, then we will not do + // any FCC operation! + mCompFields->SetFcc(""); + } + else + { + bool useDefaultFCC = true; + const char *fieldsFCC = fields->GetFcc(); + if (fieldsFCC && *fieldsFCC) + { + if (PL_strcasecmp(fieldsFCC, "nocopy://") == 0) + { + useDefaultFCC = false; + mCompFields->SetFcc(""); + } + else + { + nsCOMPtr<nsIMsgFolder> folder; + GetExistingFolder(nsDependentCString(fieldsFCC), getter_AddRefs(folder)); + if (folder) + { + useDefaultFCC = false; + mCompFields->SetFcc(mime_fix_header(fieldsFCC)); + } + } + } + + // We use default FCC setting if it's not set or was set to an invalid folder. + if (useDefaultFCC) + { + // Only check whether the user wants the message in the original message + // folder if the msgcomptype is some kind of a reply. + if (!aOriginalMsgURI.IsEmpty() && ( + aType == nsIMsgCompType::Reply || + aType == nsIMsgCompType::ReplyAll || + aType == nsIMsgCompType::ReplyToGroup || + aType == nsIMsgCompType::ReplyToSender || + aType == nsIMsgCompType::ReplyToSenderAndGroup || + aType == nsIMsgCompType::ReplyWithTemplate ) + ) + { + nsCOMPtr <nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(PromiseFlatCString(aOriginalMsgURI).get(), + getter_AddRefs(msgHdr)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr <nsIMsgFolder> folder; + msgHdr->GetFolder(getter_AddRefs(folder)); + if (NS_SUCCEEDED(rv)) + { + bool canFileMessages; + rv = folder->GetCanFileMessages(&canFileMessages); + if (NS_SUCCEEDED(rv) && canFileMessages) + { + nsCOMPtr <nsIMsgIncomingServer> incomingServer; + rv = folder->GetServer(getter_AddRefs(incomingServer)); + if (NS_SUCCEEDED(rv)) + { + nsCString incomingServerType; + rv = incomingServer->GetCharValue("type", incomingServerType); + // Exclude RSS accounts, as they falsely report + // 'canFileMessages' = true + if (NS_SUCCEEDED(rv) && !incomingServerType.Equals("rss")) + { + bool fccReplyFollowsParent; + rv = mUserIdentity->GetFccReplyFollowsParent( + &fccReplyFollowsParent); + if (NS_SUCCEEDED(rv) && fccReplyFollowsParent) + { + nsCString folderURI; + rv = folder->GetURI(folderURI); + if (NS_SUCCEEDED(rv)) + { + mCompFields->SetFcc(folderURI.get()); + useDefaultFCC = false; + } + } + } + } + } + } + } + } + } + + if (useDefaultFCC) + { + nsCString uri; + GetFolderURIFromUserPrefs(nsMsgDeliverNow, mUserIdentity, uri); + mCompFields->SetFcc(MsgLowerCaseEqualsLiteral(uri, "nocopy://") ? "" : uri.get()); + } + } + } + + // + // Deal with an additional FCC operation for this email. + // + const char *fieldsFCC2 = fields->GetFcc2(); + if ( (fieldsFCC2) && (*fieldsFCC2) ) + { + if (PL_strcasecmp(fieldsFCC2, "nocopy://") == 0) + { + mCompFields->SetFcc2(""); + mNeedToPerformSecondFCC = false; + } + else + { + mCompFields->SetFcc2(fieldsFCC2); + mNeedToPerformSecondFCC = true; + } + } + + // Copy the main bodies of headers over. + rv = mCompFields->AddAllHeaders(fields); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> srcAttachments; + fields->GetAttachments(getter_AddRefs(srcAttachments)); + if (srcAttachments) + { + bool moreAttachments; + nsCOMPtr<nsISupports> element; + while (NS_SUCCEEDED(srcAttachments->HasMoreElements(&moreAttachments)) && moreAttachments) { + rv = srcAttachments->GetNext(getter_AddRefs(element)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgAttachment> attachment = do_QueryInterface(element, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mCompFields->AddAttachment(attachment); + } + } + + AddDefaultCustomHeaders(); + + AddMailFollowupToHeader(); + AddMailReplyToHeader(); + + if (aType == nsIMsgCompType::ForwardInline || + aType == nsIMsgCompType::ForwardAsAttachment) + AddXForwardedMessageIdHeader(); + + pStr = fields->GetPriority(); + if (pStr) + mCompFields->SetPriority((char *) pStr); + + mCompFields->SetAttachVCard(fields->GetAttachVCard()); + mCompFields->SetForcePlainText(fields->GetForcePlainText()); + mCompFields->SetUseMultipartAlternative(fields->GetUseMultipartAlternative()); + int32_t receiptType = nsIMsgMdnGenerator::eDntType; + fields->GetReceiptHeaderType(&receiptType); + + mCompFields->SetReturnReceipt(fields->GetReturnReceipt()); + mCompFields->SetAttachmentReminder(fields->GetAttachmentReminder()); + mCompFields->SetDeliveryFormat(fields->GetDeliveryFormat()); + mCompFields->SetContentLanguage(fields->GetContentLanguage()); + mCompFields->SetReceiptHeaderType(receiptType); + + mCompFields->SetDSN(fields->GetDSN()); + + mCompFields->SetBodyIsAsciiOnly(fields->GetBodyIsAsciiOnly()); + mCompFields->SetForceMsgEncoding(fields->GetForceMsgEncoding()); + + nsCOMPtr<nsISupports> secInfo; + fields->GetSecurityInfo(getter_AddRefs(secInfo)); + + mCompFields->SetSecurityInfo(secInfo); + + bool needToCheckCharset; + fields->GetNeedToCheckCharset(&needToCheckCharset); + mCompFields->SetNeedToCheckCharset(needToCheckCharset); + + if ( m_deliver_mode != nsMsgSaveAsDraft && m_deliver_mode != nsMsgSaveAsTemplate ) + { + // Check the fields for legitimacy... + return mime_sanity_check_fields ( + mCompFields->GetFrom(), mCompFields->GetReplyTo(), + mCompFields->GetTo(), mCompFields->GetCc(), + mCompFields->GetBcc(), mCompFields->GetFcc(), + mCompFields->GetNewsgroups(), mCompFields->GetFollowupTo(), + mCompFields->GetSubject(), mCompFields->GetReferences(), + mCompFields->GetOrganization(), ""); + } + return NS_OK; +} + +// Add default headers to outgoing messages see Bug #61520 +// mail.identity.<id#>.headers pref is a comma separated value of pref names +// containging headers to add headers are stored in +// mail.identity.<id#>.header.<header name> grab all the headers, mime encode +// them and add them to the other custom headers. +nsresult +nsMsgComposeAndSend::AddDefaultCustomHeaders() { + nsCString headersList; + // get names of prefs containing headers to add + nsresult rv = mUserIdentity->GetCharAttribute("headers", headersList); + if (NS_SUCCEEDED(rv) && !headersList.IsEmpty()) { + int32_t start = 0; + int32_t end = 0; + int32_t len = 0; + while (end != -1) { + end = headersList.FindChar(',', start); + if (end == -1) { + len = headersList.Length() - start; + } else { + len = end - start; + } + // grab the name of the current header pref + nsAutoCString headerName("header."); + headerName.Append(Substring(headersList, start, len)); + start = end + 1; + + nsCString headerVal; + rv = mUserIdentity->GetCharAttribute(headerName.get(), headerVal); + if (NS_SUCCEEDED(rv)) { + int32_t colonIdx = headerVal.FindChar(':'); + if (colonIdx > 0) { // check that the header is *most likely* valid. + nsCString name(Substring(headerVal, 0, colonIdx)); + mCompFields->SetRawHeader(name.get(), + Substring(headerVal, colonIdx + 1), nullptr); + } + } + } + } + return rv; +} + +// Add Mail-Followup-To header +// See bug #204339 and http://cr.yp.to/proto/replyto.html for details +nsresult +nsMsgComposeAndSend::AddMailFollowupToHeader() { + nsresult rv; + + // If there's already a Mail-Followup-To header, don't need to do anything. + nsAutoCString mftHeader; + mCompFields->GetRawHeader(HEADER_MAIL_FOLLOWUP_TO, mftHeader); + if (!mftHeader.IsEmpty()) + { + return NS_OK; + } + + // Get list of subscribed mailing lists + nsAutoCString mailing_lists; + rv = mUserIdentity->GetCharAttribute("subscribed_mailing_lists", mailing_lists); + // Stop here if this list is missing or empty + if (NS_FAILED(rv) || mailing_lists.IsEmpty()) + return NS_OK; + + // Get a list of all recipients excluding bcc + nsDependentCString to(mCompFields->GetTo()); + nsDependentCString cc(mCompFields->GetCc()); + nsAutoCString recipients; + + if (to.IsEmpty() && cc.IsEmpty()) + // We have bcc recipients only, so we don't add the Mail-Followup-To header + return NS_OK; + + if (!to.IsEmpty() && cc.IsEmpty()) + recipients = to; + else if (to.IsEmpty() && !cc.IsEmpty()) + recipients = cc; + else + { + recipients.Assign(to); + recipients.AppendLiteral(", "); + recipients.Append(cc); + } + + // Remove duplicate addresses in recipients + nsAutoCString recipients_no_dups; + RemoveDuplicateAddresses(recipients, EmptyCString(), recipients_no_dups); + + // Remove subscribed mailing lists from recipients... + nsAutoCString recipients_without_mailing_lists; + RemoveDuplicateAddresses(recipients_no_dups, mailing_lists, + recipients_without_mailing_lists); + + // ... If the result is equal to the input, we don't write to a subscribed + // mailing list and therefore we don't add Mail-Followup-To + if (recipients_no_dups == recipients_without_mailing_lists) + return NS_OK; + + // Set Mail-Followup-To + return mCompFields->SetRawHeader(HEADER_MAIL_FOLLOWUP_TO, recipients, + mCompFields->GetCharacterSet()); +} + +// Add Mail-Reply-To header +// See bug #204339 and http://cr.yp.to/proto/replyto.html for details +nsresult +nsMsgComposeAndSend::AddMailReplyToHeader() { + nsresult rv; + + // If there's already a Mail-Reply-To header, don't need to do anything. + nsAutoCString mrtHeader; + mCompFields->GetRawHeader(HEADER_MAIL_REPLY_TO, mrtHeader); + if (!mrtHeader.IsEmpty()) + return NS_OK; + + // Get list of reply-to mangling mailing lists + nsAutoCString mailing_lists; + rv = mUserIdentity->GetCharAttribute("replyto_mangling_mailing_lists", mailing_lists); + // Stop here if this list is missing or empty + if (NS_FAILED(rv) || mailing_lists.IsEmpty()) + return NS_OK; + + // MRT will be set if the recipients of the message contains at least one + // of the addresses in mailing_lists or if mailing_lists has '*' as first + // character. The latter case gives the user an easy way to always set + // the MRT header. Notice that this behaviour wouldn't make sense for MFT + // in AddMailFollowupToHeader() above. + + if (mailing_lists[0] != '*') { + // Get a list of all recipients excluding bcc + nsDependentCString to(mCompFields->GetTo()); + nsDependentCString cc(mCompFields->GetCc()); + nsAutoCString recipients; + + if (to.IsEmpty() && cc.IsEmpty()) + // We have bcc recipients only, so we don't add the Mail-Reply-To header + return NS_OK; + + if (!to.IsEmpty() && cc.IsEmpty()) + recipients = to; + else if (to.IsEmpty() && !cc.IsEmpty()) + recipients = cc; + else + { + recipients.Assign(to); + recipients.AppendLiteral(", "); + recipients.Append(cc); + } + + // Remove duplicate addresses in recipients + nsAutoCString recipients_no_dups; + RemoveDuplicateAddresses(recipients, EmptyCString(), recipients_no_dups); + + // Remove reply-to mangling mailing lists from recipients... + nsAutoCString recipients_without_mailing_lists; + RemoveDuplicateAddresses(recipients_no_dups, mailing_lists, + recipients_without_mailing_lists); + + // ... If the result is equal to the input, none of the recipients + // occure in the MRT addresses and therefore we stop here. + if (recipients_no_dups == recipients_without_mailing_lists) + return NS_OK; + } + + // Set Mail-Reply-To + nsAutoCString replyTo, mailReplyTo; + replyTo = mCompFields->GetReplyTo(); + if (replyTo.IsEmpty()) + mailReplyTo = mCompFields->GetFrom(); + else + mailReplyTo = replyTo; + + mCompFields->SetRawHeader(HEADER_MAIL_REPLY_TO, mailReplyTo, + mCompFields->GetCharacterSet()); + return NS_OK; +} + +nsresult +nsMsgComposeAndSend::AddXForwardedMessageIdHeader() { + return mCompFields->SetRawHeader("X-Forwarded-Message-Id", + nsDependentCString(mCompFields->GetReferences()), nullptr); +} + +nsresult +nsMsgComposeAndSend::SnarfAndCopyBody(const nsACString &attachment1_body, + const char *attachment1_type) +{ + // + // If we are here, then just process the body from what was + // passed in the attachment1_body field. + // + // strip out whitespaces from the end of body ONLY. + nsAutoCString body(attachment1_body); + body.Trim(" ", false, true); + + if (body.Length() > 0) + { + m_attachment1_body = ToNewCString(body); + if (!m_attachment1_body) { + return NS_ERROR_OUT_OF_MEMORY; + } + m_attachment1_body_length = body.Length(); + } + + PR_FREEIF(m_attachment1_type); + m_attachment1_type = PL_strdup (attachment1_type); + PR_FREEIF(m_attachment1_encoding); + m_attachment1_encoding = PL_strdup (ENCODING_8BIT); + return NS_OK; +} + +nsresult +nsMsgComposeAndSend::Init( + nsIMsgIdentity *aUserIdentity, + const char *aAccountKey, + nsMsgCompFields *fields, + nsIFile *sendFile, + bool digest_p, + bool dont_deliver_p, + nsMsgDeliverMode mode, + nsIMsgDBHdr *msgToReplace, + const char *attachment1_type, + const nsACString &attachment1_body, + nsIArray *attachments, + nsIArray *preloaded_attachments, + const char *password, + const nsACString &aOriginalMsgURI, + MSG_ComposeType aType) +{ + nsresult rv = NS_OK; + + //Let make sure we retreive the correct number of related parts. It may have changed since last time + GetMultipartRelatedCount(true); + + nsString msg; + if (!mComposeBundle) + { + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(mComposeBundle)); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Tell the user we are assembling the message... + mComposeBundle->GetStringFromName(u"assemblingMailInformation", getter_Copies(msg)); + SetStatusMessage(msg); + if (mSendReport) + mSendReport->SetCurrentProcess(nsIMsgSendReport::process_BuildMessage); + + // + // The Init() method should initialize a send operation for full + // blown create and send operations as well as just the "send a file" + // operations. + // + m_dont_deliver_p = dont_deliver_p; + m_deliver_mode = mode; + mMsgToReplace = msgToReplace; + + mUserIdentity = aUserIdentity; + mAccountKey = aAccountKey; + NS_ASSERTION(mUserIdentity, "Got null identity!\n"); + if (!mUserIdentity) return NS_ERROR_UNEXPECTED; + + // + // First sanity check the composition fields parameter and + // see if we should continue + // + if (!fields) + return NS_ERROR_OUT_OF_MEMORY; + + m_digest_p = digest_p; + + // + // Needed for mime encoding! + // + bool strictly_mime = true; + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (pPrefBranch) + { + rv = pPrefBranch->GetBoolPref(PREF_MAIL_STRICTLY_MIME, &strictly_mime); + rv = pPrefBranch->GetIntPref(PREF_MAIL_MESSAGE_WARNING_SIZE, (int32_t *) &mMessageWarningSize); + } + + nsCOMPtr<nsIMsgComposeSecure> secureCompose + = do_CreateInstance(NS_MSGCOMPOSESECURE_CONTRACTID, &rv); + // It's not an error scenario if there is no secure compose. + // The S/MIME extension may be unavailable. + if (NS_SUCCEEDED(rv) && secureCompose) + { + bool requiresEncryptionWork = false; + rv = secureCompose->RequiresCryptoEncapsulation(aUserIdentity, fields, + &requiresEncryptionWork); + NS_ENSURE_SUCCESS(rv, rv); + if (requiresEncryptionWork) + { + strictly_mime = true; + // RFC2633 3.1.3 doesn't require multipart/signed entities to have + // transfer encoding applied for ascii, but do it anyway to make sure + // the content (e.g. line endings) isn't mangled along the way. + fields->SetForceMsgEncoding(true); + } + } + + nsMsgMIMESetConformToStandard(strictly_mime); + mime_use_quoted_printable_p = strictly_mime; + + rv = InitCompositionFields(fields, aOriginalMsgURI, aType); + if (NS_FAILED(rv)) + return rv; + + // + // At this point, if we are only creating this object to do + // send operations on externally created RFC822 disk files, + // make sure we have setup the appropriate nsIFile and + // move on with life. + // + // + // First check to see if we are doing a send operation on an external file + // or creating the file itself. + // + if (sendFile) + { + mTempFile = sendFile; + return NS_OK; + } + + // Ok, now watch me pull a rabbit out of my hat....what we need + // to do here is figure out what the body will be. If this is a + // MHTML request, then we need to do some processing of the document + // and figure out what we need to package along with this message + // to send. See ProcessMultipartRelated() for further details. + // + + // + // If we don't have an editor, then we won't be doing multipart related processing + // for the body, so make a copy of the one passed in. + // + if (!mEditor) + { + SnarfAndCopyBody(attachment1_body, attachment1_type); + mOriginalHTMLBody = ToNewCString(attachment1_body); + } + else if (GetMultipartRelatedCount() == 0) // Only do this if there are not embedded objects + { + rv = GetBodyFromEditor(); + if (NS_FAILED(rv)) + return rv; + } + + mSmtpPassword = password; + + return HackAttachments(attachments, preloaded_attachments); +} + +NS_IMETHODIMP nsMsgComposeAndSend::SendDeliveryCallback(nsIURI *aUrl, bool inIsNewsDelivery, nsresult aExitCode) +{ + if (inIsNewsDelivery) + { + if (NS_FAILED(aExitCode)) + if (aExitCode != NS_ERROR_ABORT && !NS_IS_MSG_ERROR(aExitCode)) + aExitCode = NS_ERROR_POST_FAILED; + + DeliverAsNewsExit(aUrl, aExitCode); + } + else + { + if (NS_FAILED(aExitCode)) + { +#ifdef __GNUC__ +// Temporary workaroung until bug 783526 is fixed. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" +#endif + switch (aExitCode) + { + case NS_ERROR_UNKNOWN_HOST: + case NS_ERROR_UNKNOWN_PROXY_HOST: + aExitCode = NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER; + break; + case NS_ERROR_CONNECTION_REFUSED: + case NS_ERROR_PROXY_CONNECTION_REFUSED: + aExitCode = NS_ERROR_SMTP_SEND_FAILED_REFUSED; + break; + case NS_ERROR_NET_INTERRUPT: + aExitCode = NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED; + break; + case NS_ERROR_NET_TIMEOUT: + case NS_ERROR_NET_RESET: + aExitCode = NS_ERROR_SMTP_SEND_FAILED_TIMEOUT; + break; + case NS_ERROR_SMTP_PASSWORD_UNDEFINED: + // nothing to do, just keep the code + break; + default: + if (aExitCode != NS_ERROR_ABORT && !NS_IS_MSG_ERROR(aExitCode)) + aExitCode = NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON; + break; + } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + DeliverAsMailExit(aUrl, aExitCode); + } + + return aExitCode; +} + +nsresult +nsMsgComposeAndSend::DeliverMessage() +{ + if (mSendProgress) + { + bool canceled = false; + mSendProgress->GetProcessCanceledByUser(&canceled); + if (canceled) + return NS_ERROR_ABORT; + } + + bool mail_p = ((mCompFields->GetTo() && *mCompFields->GetTo()) || + (mCompFields->GetCc() && *mCompFields->GetCc()) || + (mCompFields->GetBcc() && *mCompFields->GetBcc())); + bool news_p = mCompFields->GetNewsgroups() && *(mCompFields->GetNewsgroups()); + NS_ASSERTION(!( m_deliver_mode != nsMsgSaveAsDraft && m_deliver_mode != nsMsgSaveAsTemplate) || (mail_p || news_p), "message without destination"); + if (m_deliver_mode == nsMsgQueueForLater || + m_deliver_mode == nsMsgDeliverBackground || + m_deliver_mode == nsMsgSaveAsDraft || + m_deliver_mode == nsMsgSaveAsTemplate) + return SendToMagicFolder(m_deliver_mode); + + // + // Ok, we are about to send the file that we have built up...but what + // if this is a mongo email...we should have a way to warn the user that + // they are about to do something they may not want to do. + // + int64_t fileSize; + nsresult rv = mTempFile->GetFileSize(&fileSize); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + if ((mMessageWarningSize > 0) && (fileSize > mMessageWarningSize) && (mGUINotificationEnabled)) + { + bool abortTheSend = false; + nsString msg; + nsAutoString formattedFileSize; + FormatFileSize(fileSize, true, formattedFileSize); + const char16_t* params[] = { formattedFileSize.get() }; + mComposeBundle->FormatStringFromName(u"largeMessageSendWarning", + params, 1, getter_Copies(msg)); + + if (!msg.IsEmpty()) + { + nsCOMPtr<nsIPrompt> prompt; + GetDefaultPrompt(getter_AddRefs(prompt)); + nsMsgAskBooleanQuestionByString(prompt, msg.get(), &abortTheSend); + if (!abortTheSend) + { + nsresult ignoreMe; + Fail(NS_ERROR_BUT_DONT_SHOW_ALERT, msg.get(), &ignoreMe); + return NS_ERROR_FAILURE; + } + } + } + + if (news_p) + { + if (mail_p) + mSendMailAlso = true; + + return DeliverFileAsNews(); /* will call DeliverFileAsMail if it needs to */ + } + else if (mail_p) + return DeliverFileAsMail(); + else + return NS_ERROR_UNEXPECTED; + return NS_OK; +} + + +nsresult +nsMsgComposeAndSend::DeliverFileAsMail() +{ + char *buf, *buf2; + buf = (char *) PR_Malloc ((mCompFields->GetTo() ? PL_strlen (mCompFields->GetTo()) + 10 : 0) + + (mCompFields->GetCc() ? PL_strlen (mCompFields->GetCc()) + 10 : 0) + + (mCompFields->GetBcc() ? PL_strlen (mCompFields->GetBcc()) + 10 : 0) + + 10); + + if (mSendReport) + mSendReport->SetCurrentProcess(nsIMsgSendReport::process_SMTP); + + nsCOMPtr<nsIPrompt> promptObject; + GetDefaultPrompt(getter_AddRefs(promptObject)); + + if (!buf) + { + nsresult ignoreMe; + Fail(NS_ERROR_OUT_OF_MEMORY, nullptr, &ignoreMe); + NotifyListenerOnStopSending(nullptr, NS_ERROR_OUT_OF_MEMORY, nullptr, nullptr); + return NS_ERROR_OUT_OF_MEMORY; + } + + bool collectOutgoingAddresses = true; + nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (pPrefBranch) + pPrefBranch->GetBoolPref(PREF_MAIL_COLLECT_EMAIL_ADDRESS_OUTGOING, &collectOutgoingAddresses); + + nsCOMPtr<nsIAbAddressCollector> addressCollector = + do_GetService(NS_ABADDRESSCOLLECTOR_CONTRACTID); + + bool collectAddresses = (collectOutgoingAddresses && addressCollector); + uint32_t sendFormat = nsIAbPreferMailFormat::unknown; + + // this code is not ready yet + // see bug #44494 for more details + // so for now, just pass in nsIAbPreferMailFormat::unknown + // which will have no effect on the "prefers" attribute in the ab +#if 0 + bool forcePlainText = mCompFields->GetForcePlainText(); + bool useMultipartAlternative = mCompFields->GetUseMultipartAlternative(); + // see GenericSendMessage() in MsgComposeCommands.js for the reverse logic + // if we choose to send both (html and plain) remember html. + if (forcePlainText && !useMultipartAlternative) + { + // for now, don't remember the "plaintext" decision. + // we could get in here because while sending html mail + // the body was "convertible", but that doesn't mean + // we intended to force plain text here. + // so for now, use "unknown" which will have no effect on the + // "prefers" attribute in the ab. + // see bug #245520 for more details + // sendFormat = nsIAbPreferMailFormat::plaintext; + sendFormat = nsIAbPreferMailFormat::unknown; + } + else if (!forcePlainText) + sendFormat = nsIAbPreferMailFormat::html; + else + NS_ERROR("unknown send format, should not happen"); +#endif + + PL_strcpy (buf, ""); + buf2 = buf + PL_strlen (buf); + if (mCompFields->GetTo() && *mCompFields->GetTo()) + { + PL_strcat (buf2, mCompFields->GetTo()); + if (addressCollector) + addressCollector->CollectAddress(nsCString(mCompFields->GetTo()), + collectAddresses /* create card if one doesn't exist */, sendFormat); + } + if (mCompFields->GetCc() && *mCompFields->GetCc()) { + if (*buf2) PL_strcat (buf2, ","); + PL_strcat (buf2, mCompFields->GetCc()); + if (addressCollector) + addressCollector->CollectAddress(nsCString(mCompFields->GetCc()), + collectAddresses /* create card if one doesn't exist */, sendFormat); + } + if (mCompFields->GetBcc() && *mCompFields->GetBcc()) { + if (*buf2) PL_strcat (buf2, ","); + PL_strcat (buf2, mCompFields->GetBcc()); + if (addressCollector) + addressCollector->CollectAddress(nsCString(mCompFields->GetBcc()), + collectAddresses /* create card if one doesn't exist */, sendFormat); + } + + // We need undo groups to keep only the addresses + nsresult rv = StripOutGroupNames(buf); + NS_ENSURE_SUCCESS(rv, rv); + + // Ok, now MIME II encode this to prevent 8bit problems... + char *convbuf = nsMsgI18NEncodeMimePartIIStr(buf, true, + mCompFields->GetCharacterSet(), 0, nsMsgMIMEGetConformToStandard()); + if (convbuf) + { + // MIME-PartII conversion + PR_FREEIF(buf); + buf = convbuf; + } + + nsCString escaped_buf; + MsgEscapeString(nsDependentCString(buf), nsINetUtil::ESCAPE_URL_PATH, escaped_buf); + + if (!escaped_buf.IsEmpty()) + { + NS_Free(buf); + buf = ToNewCString(escaped_buf); + } + + nsCOMPtr<nsISmtpService> smtpService(do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv) && smtpService) + { + MsgDeliveryListener *deliveryListener = new MsgDeliveryListener(this, false); + if (!deliveryListener) + return NS_ERROR_OUT_OF_MEMORY; + + // we used to get the prompt from the compose window and we'd pass that in + // to the smtp protocol as the prompt to use. But when you send a message, + // we dismiss the compose window.....so you are parenting off of a window that + // isn't there. To have it work correctly I think we want the alert dialogs to be modal + // to the top most mail window...after all, that's where we are going to be sending status + // update information too.... + + nsCOMPtr<nsIInterfaceRequestor> callbacks; + GetNotificationCallbacks(getter_AddRefs(callbacks)); + + // Tell the user we are sending the message! + nsString msg; + mComposeBundle->GetStringFromName(u"sendingMessage", getter_Copies(msg)); + SetStatusMessage(msg); + nsCOMPtr<nsIMsgStatusFeedback> msgStatus (do_QueryInterface(mSendProgress)); + // if the sendProgress isn't set, let's use the member variable. + if (!msgStatus) + msgStatus = do_QueryInterface(mStatusFeedback); + + nsCOMPtr<nsIURI> runningUrl; + rv = smtpService->SendMailMessage(mTempFile, buf, mUserIdentity, + mSmtpPassword.get(), deliveryListener, msgStatus, + callbacks, mCompFields->GetDSN(), + getter_AddRefs(runningUrl), + getter_AddRefs(mRunningRequest)); + // set envid on the returned URL + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsISmtpUrl> smtpUrl(do_QueryInterface(runningUrl, &rv)); + if (NS_SUCCEEDED(rv)) + smtpUrl->SetDsnEnvid(nsDependentCString(mCompFields->GetMessageId())); + } + } + + PR_FREEIF(buf); // free the buf because we are done with it.... + return rv; +} + +nsresult +nsMsgComposeAndSend::DeliverFileAsNews() +{ + nsresult rv = NS_OK; + if (!(mCompFields->GetNewsgroups())) + return rv; + + if (mSendReport) + mSendReport->SetCurrentProcess(nsIMsgSendReport::process_NNTP); + + nsCOMPtr<nsIPrompt> promptObject; + GetDefaultPrompt(getter_AddRefs(promptObject)); + + nsCOMPtr<nsINntpService> nntpService(do_GetService(NS_NNTPSERVICE_CONTRACTID, &rv)); + + if (NS_SUCCEEDED(rv) && nntpService) + { + MsgDeliveryListener *deliveryListener = new MsgDeliveryListener(this, true); + if (!deliveryListener) + return NS_ERROR_OUT_OF_MEMORY; + + // Tell the user we are posting the message! + nsString msg; + mComposeBundle->GetStringFromName(u"postingMessage", + getter_Copies(msg)); + SetStatusMessage(msg); + + nsCOMPtr <nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // JFD TODO: we should use GetDefaultPrompt instead + nsCOMPtr<nsIMsgWindow> msgWindow; + rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + // see bug #163139 + // we might not have a msg window if only the compose window is open. + if(NS_FAILED(rv)) + msgWindow = nullptr; + + rv = nntpService->PostMessage(mTempFile, mCompFields->GetNewsgroups(), mAccountKey.get(), + deliveryListener, msgWindow, nullptr); + if (NS_FAILED(rv)) return rv; + } + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::Fail(nsresult aFailureCode, const char16_t *aErrorMsg, + nsresult *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = aFailureCode; + + if (NS_FAILED(aFailureCode)) + { + nsCOMPtr<nsIPrompt> prompt; + GetDefaultPrompt(getter_AddRefs(prompt)); + + if (mSendReport) + { + int32_t process; + if (NS_SUCCEEDED(mSendReport->GetCurrentProcess(&process)) && process == nsIMsgSendReport::process_Current) + { + // currentProcess isn't set yet, so we need another value. + mSendReport->SetCurrentProcess(nsIMsgSendReport::process_BuildMessage); + } + mSendReport->SetError(nsIMsgSendReport::process_Current, aFailureCode, false); + mSendReport->SetMessage(nsIMsgSendReport::process_Current, aErrorMsg, false); + mSendReport->DisplayReport(prompt, true, true, aResult); + } + else + { + if (aFailureCode != NS_ERROR_BUT_DONT_SHOW_ALERT) + nsMsgDisplayMessageByName(prompt, u"sendFailed"); + } + } + + if (NS_SUCCEEDED(m_status)) + m_status = NS_ERROR_BUT_DONT_SHOW_ALERT; + + //Stop any pending process... + Abort(); + + return NS_OK; +} + +nsresult +nsMsgComposeAndSend::FormatStringWithSMTPHostNameByName(const char16_t* aMsgName, char16_t **aString) +{ + NS_ENSURE_ARG(aString); + + nsresult rv; + nsCOMPtr<nsISmtpService> smtpService(do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv,rv); + + // Get the smtp hostname and format the string. + nsCString smtpHostName; + nsCOMPtr<nsISmtpServer> smtpServer; + rv = smtpService->GetServerByIdentity(mUserIdentity, getter_AddRefs(smtpServer)); + if (NS_SUCCEEDED(rv)) + smtpServer->GetHostname(smtpHostName); + + nsAutoString hostStr; + CopyASCIItoUTF16(smtpHostName, hostStr); + const char16_t *params[] = { hostStr.get() }; + if (NS_SUCCEEDED(rv)) + mComposeBundle->FormatStringFromName(aMsgName, params, 1, aString); + return rv; +} + +void +nsMsgComposeAndSend::DoDeliveryExitProcessing(nsIURI * aUri, nsresult aExitCode, bool aCheckForMail) +{ + // If we fail on the news delivery, no sense in going on so just notify + // the user and exit. + if (NS_FAILED(aExitCode)) + { + const char16_t* exitString = errorStringNameForErrorCode(aExitCode); + nsString eMsg; + if (aExitCode == NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_SERVER || + aExitCode == NS_ERROR_SMTP_SEND_FAILED_UNKNOWN_REASON || + aExitCode == NS_ERROR_SMTP_SEND_FAILED_REFUSED || + aExitCode == NS_ERROR_SMTP_SEND_FAILED_INTERRUPTED || + aExitCode == NS_ERROR_SMTP_SEND_FAILED_TIMEOUT || + aExitCode == NS_ERROR_SMTP_PASSWORD_UNDEFINED || + aExitCode == NS_ERROR_SMTP_AUTH_FAILURE || + aExitCode == NS_ERROR_SMTP_AUTH_GSSAPI || + aExitCode == NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED || + aExitCode == NS_ERROR_SMTP_AUTH_NOT_SUPPORTED || + aExitCode == NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL || + aExitCode == NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL || + aExitCode == NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT || + aExitCode == NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS) { + FormatStringWithSMTPHostNameByName(exitString, getter_Copies(eMsg)); + } else { + mComposeBundle->GetStringFromName(exitString, getter_Copies(eMsg)); + } + + Fail(aExitCode, eMsg.get(), &aExitCode); + NotifyListenerOnStopSending(nullptr, aExitCode, nullptr, nullptr); + return; + } + + if (aCheckForMail) + { + if ((mCompFields->GetTo() && *mCompFields->GetTo()) || + (mCompFields->GetCc() && *mCompFields->GetCc()) || + (mCompFields->GetBcc() && *mCompFields->GetBcc())) + { + // If we're sending this news message to mail as well, start it now. + // Completion and further errors will be handled there. + DeliverFileAsMail(); + return; + } + } + + // + // Tell the listeners that we are done with the sending operation... + // + NotifyListenerOnStopSending(mCompFields->GetMessageId(), + aExitCode, + nullptr, + nullptr); + + // If we hit here, we are done with delivery! + // + // Just call the DoFCC() method and if that fails, then we should just + // cleanup and get out. If DoFCC "succeeds", then all that means is the + // async copy operation has been started and we will be notified later + // when it is done. DON'T cleanup until the copy is complete and don't + // notify the listeners with OnStop() until we are done. + // + // For now, we don't need to do anything here, but the code will stay this + // way until later... + // + + DoFcc(); +} + +NS_IMETHODIMP +nsMsgComposeAndSend::DeliverAsMailExit(nsIURI *aUrl, nsresult aExitCode) +{ + DoDeliveryExitProcessing(aUrl, aExitCode, false); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::DeliverAsNewsExit(nsIURI *aUrl, nsresult aExitCode) +{ + DoDeliveryExitProcessing(aUrl, aExitCode, mSendMailAlso); + return NS_OK; +} + +bool nsMsgComposeAndSend::CanSaveMessagesToFolder(const char *folderURL) +{ + nsresult rv; + nsCOMPtr<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1", &rv)); + if (NS_FAILED(rv)) + return false; + + nsCOMPtr<nsIRDFResource> resource; + rv = rdf->GetResource(nsDependentCString(folderURL), getter_AddRefs(resource)); + if (NS_FAILED(rv)) + return false; + + nsCOMPtr <nsIMsgFolder> thisFolder; + thisFolder = do_QueryInterface(resource, &rv); + if (NS_FAILED(rv) || !thisFolder) + return false; + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = thisFolder->GetServer(getter_AddRefs(server)); + if (NS_FAILED(rv) || !server) + return false; + + // See if we are allowed to save/file msgs to this folder. + bool canSave; + rv = server->GetCanFileMessagesOnServer(&canSave); + return canSave; +} + +// +// Now, start the appropriate copy operation. +// +nsresult +nsMsgComposeAndSend::DoFcc() +{ + // + // Just cleanup and return success if we're not allowed to save msgs to FCC folder. + // + const char* fcc = mCompFields->GetFcc(); + if (!fcc || !*fcc || !CanSaveMessagesToFolder(fcc)) + { + + // It is the caller's responsibility to say we've stopped sending, so just + // let the listeners know we're not doing a copy. + NotifyListenerOnStopCopy(NS_OK); // For closure of compose window... + return NS_OK; + } + + if (mSendReport) + mSendReport->SetCurrentProcess(nsIMsgSendReport::process_Copy); + + // + // If we are here, then we need to save off the FCC file to save and + // start the copy operation. MimeDoFCC() will take care of all of this + // for us. + // + nsresult rv = MimeDoFCC(mTempFile, + nsMsgDeliverNow, + mCompFields->GetBcc(), + mCompFields->GetFcc(), + mCompFields->GetNewspostUrl()); + if (NS_FAILED(rv)) + { + // + // If we hit here, the copy operation FAILED and we should at least tell the + // user that it did fail but the send operation has already succeeded. + // + NotifyListenerOnStopCopy(rv); + } + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::NotifyListenerOnStartSending(const char *aMsgID, uint32_t aMsgSize) +{ + if (mListener) + mListener->OnStartSending(aMsgID, aMsgSize); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::NotifyListenerOnProgress(const char *aMsgID, uint32_t aProgress, uint32_t aProgressMax) +{ + if (mListener) + mListener->OnProgress(aMsgID, aProgress, aProgressMax); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::NotifyListenerOnStatus(const char *aMsgID, const char16_t *aMsg) +{ + if (mListener) + mListener->OnStatus(aMsgID, aMsg); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::NotifyListenerOnStopSending(const char *aMsgID, nsresult aStatus, const char16_t *aMsg, + nsIFile *returnFile) +{ + if (mListener != nullptr) + mListener->OnStopSending(aMsgID, aStatus, aMsg, returnFile); + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::NotifyListenerOnStartCopy() +{ + nsCOMPtr<nsIMsgCopyServiceListener> copyListener; + + if (mListener) + { + copyListener = do_QueryInterface(mListener); + if (copyListener) + copyListener->OnStartCopy(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::NotifyListenerOnProgressCopy(uint32_t aProgress, + uint32_t aProgressMax) +{ + nsCOMPtr<nsIMsgCopyServiceListener> copyListener; + + if (mListener) + { + copyListener = do_QueryInterface(mListener); + if (copyListener) + copyListener->OnProgress(aProgress, aProgressMax); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::SetMessageKey(nsMsgKey aMessageKey) +{ + m_messageKey = aMessageKey; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::GetMessageKey(nsMsgKey *aMessageKey) +{ + *aMessageKey = m_messageKey; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::GetFolderUri(nsACString &aFolderUri) +{ + aFolderUri = m_folderName; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::GetPartForDomIndex(int32_t aDomIndex, nsACString &aPartNum) +{ + aPartNum = m_partNumbers.SafeElementAt(aDomIndex, EmptyCString()); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::GetMessageId(nsACString& aMessageId) +{ + nsresult rv = NS_OK; + if (mCompFields) + aMessageId = mCompFields->GetMessageId(); + else + rv = NS_ERROR_NULL_POINTER; + return rv; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::NotifyListenerOnStopCopy(nsresult aStatus) +{ + // This is one per copy so make sure we clean this up first. + mCopyObj = nullptr; + + // Set a status message... + nsString msg; + if (NS_SUCCEEDED(aStatus)) + mComposeBundle->GetStringFromName(u"copyMessageComplete", getter_Copies(msg)); + else + mComposeBundle->GetStringFromName(u"copyMessageFailed", getter_Copies(msg)); + + SetStatusMessage(msg); + nsCOMPtr<nsIPrompt> prompt; + GetDefaultPrompt(getter_AddRefs(prompt)); + + if (NS_FAILED(aStatus)) + { + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString msg; + const char16_t *formatStrings[] = { mSavedToFolderName.get() }; + + rv = bundle->FormatStringFromName(u"errorSavingMsg", + formatStrings, 1, + getter_Copies(msg)); + if (NS_SUCCEEDED(rv)) + { + bool retry = false; + nsMsgAskBooleanQuestionByString(prompt, msg.get(), &retry, nullptr); + if (retry) + { + mSendProgress = nullptr; // this was cancelled, so we need to clear it. + return SendToMagicFolder(m_deliver_mode); + } + } + + // We failed, and the user decided not to retry. So we're just going to + // fail out. However, give Fail a success code so that it doesn't prompt + // the user a second time as they already know about the failure. + Fail(NS_OK, nullptr, &aStatus); + } + + if (NS_SUCCEEDED(aStatus) && + !mPerformingSecondFCC && m_messageKey != nsMsgKey_None && + (m_deliver_mode == nsMsgDeliverNow || m_deliver_mode == nsMsgSendUnsent)) + { + nsresult rv = FilterSentMessage(); + if (NS_FAILED(rv)) + OnStopOperation(rv); + return rv; + } + + return MaybePerformSecondFCC(aStatus); +} + +nsresult +nsMsgComposeAndSend::FilterSentMessage() +{ + if (mSendReport) + mSendReport->SetCurrentProcess(nsIMsgSendReport::process_Filter); + + nsCOMPtr<nsIMsgFolder> folder; + nsresult rv = GetExistingFolder(m_folderName, getter_AddRefs(folder)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = folder->GetMessageHeader(m_messageKey, getter_AddRefs(msgHdr)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIMutableArray> msgArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIMsgFilterService> filterSvc = do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + rv = msgArray->AppendElement(msgHdr, false); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIMsgWindow> msgWindow; + if (mSendProgress) + mSendProgress->GetMsgWindow(getter_AddRefs(msgWindow)); + + return filterSvc->ApplyFilters(nsMsgFilterType::PostOutgoing, msgArray, folder, msgWindow, this); +} + +NS_IMETHODIMP +nsMsgComposeAndSend::OnStopOperation(nsresult aStatus) +{ + // Set a status message... + nsString msg; + if (NS_SUCCEEDED(aStatus)) + mComposeBundle->GetStringFromName(u"filterMessageComplete", getter_Copies(msg)); + else + mComposeBundle->GetStringFromName(u"filterMessageFailed", getter_Copies(msg)); + + SetStatusMessage(msg); + + if (NS_FAILED(aStatus)) + { + nsresult rv = mComposeBundle->GetStringFromName(u"errorFilteringMsg", getter_Copies(msg)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIPrompt> prompt; + GetDefaultPrompt(getter_AddRefs(prompt)); + nsMsgDisplayMessageByString(prompt, msg.get(), nullptr); + } + + // We failed, however, give Fail a success code so that it doesn't prompt + // the user a second time as they already know about the failure. + Fail(NS_OK, nullptr, &aStatus); + } + + return MaybePerformSecondFCC(aStatus); +} + +nsresult +nsMsgComposeAndSend::MaybePerformSecondFCC(nsresult aStatus) +{ + // Ok, now to support a second copy operation, we need to figure + // out which copy request just finished. If the user has requested + // a second copy operation, then we need to fire that off, but if they + // just wanted a single copy operation, we can tell everyone we are done + // and move on with life. Only do the second copy if the first one worked. + // + if ( NS_SUCCEEDED(aStatus) && (mNeedToPerformSecondFCC) ) + { + if (mSendReport) + mSendReport->SetCurrentProcess(nsIMsgSendReport::process_FCC); + + mNeedToPerformSecondFCC = false; + mPerformingSecondFCC = true; + + const char *fcc2 = mCompFields->GetFcc2(); + if (fcc2 && *fcc2) + { + nsresult rv = MimeDoFCC(mTempFile, + nsMsgDeliverNow, + mCompFields->GetBcc(), + fcc2, + mCompFields->GetNewspostUrl()); + if (NS_FAILED(rv)) + Fail(rv, nullptr, &aStatus); + else + return NS_OK; + } + } + + // If we are here, its real cleanup time! + if (mListener) + { + nsCOMPtr<nsIMsgCopyServiceListener> copyListener = + do_QueryInterface(mListener); + if (copyListener) + copyListener->OnStopCopy(aStatus); + } + + return aStatus; +} + +/* This is the main driving function of this module. It generates a + document of type message/rfc822, which contains the stuff provided. + The first few arguments are the standard header fields that the + generated document should have. + + `other_random_headers' is a string of additional headers that should + be inserted beyond the standard ones. If provided, it is just tacked + on to the end of the header block, so it should have newlines at the + end of each line, shouldn't have blank lines, multi-line headers + should be properly continued, etc. + + `digest_p' says that most of the documents we are attaching are + themselves messages, and so we should generate a multipart/digest + container instead of multipart/mixed. (It's a minor difference.) + + The full text of the first attachment is provided via `attachment1_type' + and `attachment1_body'. These may all be 0 if all attachments are + provided externally. + + Subsequent attachments are provided as URLs to load, described in the + nsMsgAttachmentData structures. + + If `dont_deliver_p' is false, then we actually deliver the message to the + SMTP and/or NNTP server, and the message_delivery_done_callback will be + invoked with the status. + + If `dont_deliver_p' is true, then we just generate the message, we don't + actually deliver it, and the message_delivery_done_callback will be called + with the name of the generated file. The callback is responsible for both + freeing the file name string, and deleting the file when it is done with + it. If an error occurred, then `status' will be negative and + `error_message' may be an error message to display. If status is non- + negative, then `error_message' contains the file name (this is kind of + a kludge...) + */ +NS_IMETHODIMP +nsMsgComposeAndSend::CreateAndSendMessage( + nsIEditor *aEditor, + nsIMsgIdentity *aUserIdentity, + const char *aAccountKey, + nsIMsgCompFields *fields, + bool digest_p, + bool dont_deliver_p, + nsMsgDeliverMode mode, + nsIMsgDBHdr *msgToReplace, + const char *attachment1_type, + const nsACString &attachment1_body, + nsIArray *attachments, + nsIArray *preloaded_attachments, + mozIDOMWindowProxy *parentWindow, + nsIMsgProgress *progress, + nsIMsgSendListener *aListener, + const char *password, + const nsACString &aOriginalMsgURI, + MSG_ComposeType aType + ) +{ + nsresult rv; + /* First thing to do is to reset the send errors report */ + mSendReport->Reset(); + mSendReport->SetDeliveryMode(mode); + + mParentWindow = do_QueryInterface(parentWindow); + mSendProgress = progress; + mListener = aListener; + + // Set the editor for MHTML operations if necessary + if (aEditor) + mEditor = aEditor; + + rv = Init(aUserIdentity, aAccountKey, (nsMsgCompFields *)fields, nullptr, + digest_p, dont_deliver_p, mode, msgToReplace, + attachment1_type, attachment1_body, + attachments, preloaded_attachments, + password, aOriginalMsgURI, aType); + + if (NS_FAILED(rv) && mSendReport) + mSendReport->SetError(nsIMsgSendReport::process_Current, rv, false); + + return rv; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::CreateRFC822Message( + nsIMsgIdentity *aUserIdentity, + nsIMsgCompFields *aFields, + const char *aMsgType, + const nsACString &aMsgBody, + bool aIsDraft, + nsIArray *aAttachments, + nsIArray *aEmbeddedObjects, + nsIMsgSendListener *aListener + ) +{ + nsresult rv; + nsMsgDeliverMode mode = aIsDraft ? nsIMsgSend::nsMsgSaveAsDraft : + nsIMsgSend::nsMsgDeliverNow; + + /* First thing to do is to reset the send errors report */ + mSendReport->Reset(); + mSendReport->SetDeliveryMode(mode); + + mParentWindow = nullptr; + mSendProgress = nullptr; + mListener = aListener; + mEmbeddedObjectList = aEmbeddedObjects; + + rv = Init(aUserIdentity, nullptr, (nsMsgCompFields *)aFields, nullptr, + false, true, mode, nullptr, + aMsgType, + aMsgBody, + nullptr, aAttachments, + nullptr, EmptyCString(), nsIMsgCompType::New); + + if (NS_FAILED(rv) && mSendReport) + mSendReport->SetError(nsIMsgSendReport::process_Current, rv, false); + + return rv; +} + +nsresult +nsMsgComposeAndSend::SendMessageFile( + nsIMsgIdentity *aUserIndentity, + const char *aAccountKey, + nsIMsgCompFields *fields, + nsIFile *sendIFile, + bool deleteSendFileOnCompletion, + bool digest_p, + nsMsgDeliverMode mode, + nsIMsgDBHdr *msgToReplace, + nsIMsgSendListener *aListener, + nsIMsgStatusFeedback *aStatusFeedback, + const char *password + ) +{ + NS_ENSURE_ARG_POINTER(fields); + NS_ENSURE_ARG_POINTER(sendIFile); + + nsresult rv; + + /* First thing to do is to reset the send errors report */ + mSendReport->Reset(); + mSendReport->SetDeliveryMode(mode); + + mStatusFeedback = aStatusFeedback; + // + // First check to see if the external file we are sending is a valid file. + // + bool exists; + if (NS_FAILED(sendIFile->Exists(&exists))) + return NS_ERROR_INVALID_ARG; + + if (!exists) + return NS_ERROR_INVALID_ARG; + + // Setup the listener... + mListener = aListener; + + // Should we delete the temp file when done? + if (!deleteSendFileOnCompletion) + mReturnFile = sendIFile; + + rv = Init(aUserIndentity, aAccountKey, (nsMsgCompFields *)fields, sendIFile, + digest_p, false, mode, msgToReplace, + nullptr, EmptyCString(), + nullptr, nullptr, + password, EmptyCString(), nsIMsgCompType::New); + + if (NS_SUCCEEDED(rv)) + rv = DeliverMessage(); + + if (NS_FAILED(rv) && mSendReport) + mSendReport->SetError(nsIMsgSendReport::process_Current, rv, false); + + return rv; +} + +// +// Send the message to the magic folder, and runs the completion/failure +// callback. +// +nsresult +nsMsgComposeAndSend::SendToMagicFolder(nsMsgDeliverMode mode) +{ + nsresult rv = MimeDoFCC(mTempFile, + mode, + mCompFields->GetBcc(), + mCompFields->GetFcc(), + mCompFields->GetNewspostUrl()); + // + // The caller of MimeDoFCC needs to deal with failure. + // + if (NS_FAILED(rv)) + rv = NotifyListenerOnStopCopy(rv); + + return rv; +} + +char* +nsMsgGetEnvelopeLine(void) +{ + static char result[75] = ""; + PRExplodedTime now; + char buffer[128] = ""; + + // Generate envelope line in format of: From - Sat Apr 18 20:01:49 1998 + // + // Use PR_FormatTimeUSEnglish() to format the date in US English format, + // then figure out what our local GMT offset is, and append it (since + // PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as + // per RFC 1123 (superceding RFC 822.) + // + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now); + PR_FormatTimeUSEnglish(buffer, sizeof(buffer), + "%a %b %d %H:%M:%S %Y", + &now); + + // This value must be in ctime() format, with English abbreviations. + // PL_strftime("... %c ...") is no good, because it is localized. + // + PL_strcpy(result, "From - "); + PL_strcpy(result + 7, buffer); + PL_strcpy(result + 7 + 24, CRLF); + return result; +} + +#define ibuffer_size FILE_IO_BUFFER_SIZE +nsresult +nsMsgComposeAndSend::MimeDoFCC(nsIFile *input_file, + nsMsgDeliverMode mode, + const char *bcc_header, + const char *fcc_header, + const char *news_url) +{ + nsresult status = NS_OK; + char *ibuffer = nullptr; + uint32_t n; + bool folderIsLocal = true; + nsCString turi; + char16_t *printfString = nullptr; + nsString msg; + nsCOMPtr<nsIMsgFolder> folder; + + // Before continuing, just check the user has not cancel the operation + if (mSendProgress) + { + bool canceled = false; + mSendProgress->GetProcessCanceledByUser(&canceled); + if (canceled) + return NS_ERROR_ABORT; + else + mSendProgress->OnProgressChange(nullptr, nullptr, 0, 0, 0, -1); + } + + // + // Ok, this is here to keep track of this for 2 copy operations... + // + if (mCopyFile) + { + mCopyFile2 = mCopyFile; + mCopyFile = nullptr; + } + + // + // Create the file that will be used for the copy service! + // + nsresult rv = nsMsgCreateTempFile("nscopy.tmp", getter_AddRefs(mCopyFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIOutputStream> tempOutfile; + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(tempOutfile), mCopyFile, -1, 00600); + if (NS_FAILED(rv)) + { + if (mSendReport) + { + nsAutoString error_msg; + nsMsgBuildMessageWithTmpFile(mCopyFile, error_msg); + mSendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false); + } + status = NS_MSG_UNABLE_TO_OPEN_TMP_FILE; + + mCopyFile = nullptr; + return status; + } + + // + // Get our files ready... + // + nsCOMPtr<nsIInputStream> inputFile; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputFile), input_file); + if (NS_FAILED(rv)) + { + if (mSendReport) + { + nsAutoString error_msg; + nsMsgBuildMessageWithFile(input_file, error_msg); + mSendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false); + } + status = NS_MSG_UNABLE_TO_OPEN_FILE; + goto FAIL; + } + + // now the buffers... + ibuffer = (char *) PR_Malloc(ibuffer_size); + if (!ibuffer) + { + status = NS_ERROR_OUT_OF_MEMORY; + goto FAIL; + } + + // + // First, we we need to put a Berkeley "From - " delimiter at the head of + // the file for parsing... + // + + if (fcc_header && *fcc_header) + GetExistingFolder(nsDependentCString(fcc_header), getter_AddRefs(folder)); + + if ((mode == nsMsgDeliverNow || mode == nsMsgSendUnsent) && folder) + turi = fcc_header; + else + GetFolderURIFromUserPrefs(mode, mUserIdentity, turi); + status = MessageFolderIsLocal(mUserIdentity, mode, turi.get(), &folderIsLocal); + if (NS_FAILED(status)) + goto FAIL; + + // Tell the user we are copying the message... + mComposeBundle->GetStringFromName(u"copyMessageStart", + getter_Copies(msg)); + if (!msg.IsEmpty()) + { + nsCOMPtr<nsIRDFService> rdfService = do_GetService(kRDFServiceCID); + if (rdfService) + { + nsCOMPtr<nsIRDFResource> res; + rdfService->GetResource(turi, getter_AddRefs(res)); + nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(res); + if (folder) + folder->GetName(mSavedToFolderName); + } + if (!mSavedToFolderName.IsEmpty()) + printfString = nsTextFormatter::smprintf(msg.get(), mSavedToFolderName.get()); + else + printfString = nsTextFormatter::smprintf(msg.get(), "?"); + if (printfString) + { + SetStatusMessage(nsDependentString(printfString)); + PR_Free(printfString); + } + } + + if (folderIsLocal) + { + char *envelopeLine = nsMsgGetEnvelopeLine(); + uint32_t len = PL_strlen(envelopeLine); + + rv = tempOutfile->Write(envelopeLine, len, &n); + if (NS_FAILED(rv) || n != len) + { + status = NS_ERROR_FAILURE; + goto FAIL; + } + } + + // + // Write out an X-Mozilla-Status header. + // + // This is required for the queue file, so that we can overwrite it once + // the messages have been delivered, and so that the nsMsgMessageFlags::Queued bit + // is set. + // + // For FCC files, we don't necessarily need one, but we might as well put + // one in so that it's marked as read already. + // + // + // Need to add these lines for POP3 ONLY! IMAP servers will handle + // this status information for summary file regeneration for us. + if ((mode == nsMsgQueueForLater || mode == nsMsgSaveAsDraft || + mode == nsMsgSaveAsTemplate || mode == nsMsgDeliverNow || + mode == nsMsgSendUnsent || mode == nsMsgDeliverBackground) && + folderIsLocal) + { + char *buf = 0; + uint16_t flags = 0; + + // for save as draft and send later, we want to leave the message as unread. + // See Bug #198087 + // Messages sent with mode nsMsgDeliverBackground must not have the Queued + // flag sent so that they get picked up by the background send function. + if (mode == nsMsgQueueForLater) + flags |= nsMsgMessageFlags::Queued; + else if (mode != nsMsgSaveAsDraft && mode != nsMsgDeliverBackground) + flags |= nsMsgMessageFlags::Read; + buf = PR_smprintf(X_MOZILLA_STATUS_FORMAT CRLF, flags); + if (buf) + { + uint32_t len = PL_strlen(buf); + rv = tempOutfile->Write(buf, len, &n); + PR_Free(buf); + if (NS_FAILED(rv) || n != len) + { + status = NS_ERROR_FAILURE; + goto FAIL; + } + } + + uint32_t flags2 = 0; + if (mode == nsMsgSaveAsTemplate) + flags2 |= nsMsgMessageFlags::Template; + if (mode == nsMsgDeliverNow || mode == nsMsgSendUnsent) + { + flags2 &= ~nsMsgMessageFlags::MDNReportNeeded; + flags2 |= nsMsgMessageFlags::MDNReportSent; + } + buf = PR_smprintf(X_MOZILLA_STATUS2_FORMAT CRLF, flags2); + if (buf) + { + uint32_t len = PL_strlen(buf); + rv = tempOutfile->Write(buf, len, &n); + PR_Free(buf); + if (NS_FAILED(rv) || n != len) + { + status = NS_ERROR_FAILURE; + goto FAIL; + } + } + tempOutfile->Write(X_MOZILLA_KEYWORDS, sizeof(X_MOZILLA_KEYWORDS) - 1, &n); + } + + // Write out the FCC and BCC headers. + // When writing to the Queue file, we *must* write the FCC and BCC + // headers, or else that information would be lost. Because, when actually + // delivering the message (with "deliver now") we do FCC/BCC right away; + // but when queueing for later delivery, we do FCC/BCC at delivery-time. + // + // The question remains of whether FCC and BCC should be written into normal + // BCC folders (like the Sent Mail folder.) + // + // For FCC, there seems no point to do that; it's not information that one + // would want to refer back to. + // + // For BCC, the question isn't as clear. On the one hand, if I send someone + // a BCC'ed copy of the message, and save a copy of it for myself (with FCC) + // I might want to be able to look at that message later and see the list of + // people to whom I had BCC'ed it. + // + // On the other hand, the contents of the BCC header is sensitive + // information, and should perhaps not be stored at all. + // + // Thus the consultation of the #define SAVE_BCC_IN_FCC_FILE. + // + // (Note that, if there is a BCC header present in a message in some random + // folder, and that message is forwarded to someone, then the attachment + // code will strip out the BCC header before forwarding it.) + // + if ((mode == nsMsgQueueForLater || mode == nsMsgDeliverBackground || + mode == nsMsgSaveAsDraft || mode == nsMsgSaveAsTemplate) && + fcc_header && *fcc_header) + { + int32_t L = PL_strlen(fcc_header) + 20; + char *buf = (char *) PR_Malloc (L); + if (!buf) + { + status = NS_ERROR_OUT_OF_MEMORY; + goto FAIL; + } + + PR_snprintf(buf, L-1, "FCC: %s" CRLF, fcc_header); + + uint32_t len = PL_strlen(buf); + rv = tempOutfile->Write(buf, len, &n); + if (NS_FAILED(rv) || n != len) + { + status = NS_ERROR_FAILURE; + goto FAIL; + } + } + + // + // Ok, now I want to get the identity key and write it out if this is for a + // nsMsgQueueForLater operation! + // + if ((nsMsgQueueForLater == mode || nsMsgSaveAsDraft == mode || + nsMsgDeliverBackground == mode || nsMsgSaveAsTemplate == mode) && + mUserIdentity) + { + char *buf = nullptr; + nsCString key; + + if (NS_SUCCEEDED(mUserIdentity->GetKey(key)) && !key.IsEmpty()) + { + buf = PR_smprintf(HEADER_X_MOZILLA_IDENTITY_KEY ": %s" CRLF, key.get()); + if (buf) + { + uint32_t len = strlen(buf); + rv = tempOutfile->Write(buf, len, &n); + PR_Free(buf); + if (NS_FAILED(rv) || n != len) + { + status = NS_ERROR_FAILURE; + goto FAIL; + } + } + } + + if (!mAccountKey.IsEmpty()) + { + buf = PR_smprintf(HEADER_X_MOZILLA_ACCOUNT_KEY ": %s" CRLF, mAccountKey.get()); + if (buf) + { + uint32_t len = strlen(buf); + rv = tempOutfile->Write(buf, len, &n); + PR_Free(buf); + if (NS_FAILED(rv) || n != len) + { + status = NS_ERROR_FAILURE; + goto FAIL; + } + } + } + } + + if (bcc_header && *bcc_header +#ifndef SAVE_BCC_IN_FCC_FILE + && (mode == MSG_QueueForLater || mode == MSG_SaveAsDraft || + mode == MSG_SaveAsTemplate) +#endif + ) + { + char *convBcc; + convBcc = nsMsgI18NEncodeMimePartIIStr(bcc_header, true, + mCompFields->GetCharacterSet(), sizeof("BCC: "), + nsMsgMIMEGetConformToStandard()); + + int32_t L = strlen(convBcc ? convBcc : bcc_header) + 20; + char *buf = (char *) PR_Malloc (L); + if (!buf) + { + status = NS_ERROR_OUT_OF_MEMORY; + goto FAIL; + } + + PR_snprintf(buf, L-1, "BCC: %s" CRLF, convBcc ? convBcc : bcc_header); + uint32_t len = strlen(buf); + rv = tempOutfile->Write(buf, len, &n); + PR_Free(buf); + PR_Free(convBcc); + if (NS_FAILED(rv) || n != len) + { + status = NS_ERROR_FAILURE; + goto FAIL; + } + } + + // + // Write out the X-Mozilla-News-Host header. + // This is done only when writing to the queue file, not the FCC file. + // We need this to complement the "Newsgroups" header for the case of + // queueing a message for a non-default news host. + // + // Convert a URL like "snews://host:123/" to the form "host:123/secure" + // or "news://user@host:222" to simply "host:222". + // + if ((mode == nsMsgQueueForLater || mode == nsMsgSaveAsDraft || + mode == nsMsgSaveAsTemplate || mode == nsMsgDeliverBackground) && + news_url && *news_url) + { + bool secure_p = (news_url[0] == 's' || news_url[0] == 'S'); + char *orig_hap = nsMsgParseURLHost (news_url); + char *host_and_port = orig_hap; + if (host_and_port) + { + // There may be authinfo at the front of the host - it could be of + // the form "user:password@host:port", so take off everything before + // the first at-sign. We don't want to store authinfo in the queue + // folder, I guess, but would want it to be re-prompted-for at + // delivery-time. + // + char *at = PL_strchr (host_and_port, '@'); + if (at) + host_and_port = at + 1; + } + + if ((host_and_port && *host_and_port) || !secure_p) + { + char *line = PR_smprintf(X_MOZILLA_NEWSHOST ": %s%s" CRLF, + host_and_port ? host_and_port : "", + secure_p ? "/secure" : ""); + PR_FREEIF(orig_hap); + if (!line) + { + status = NS_ERROR_OUT_OF_MEMORY; + goto FAIL; + } + + uint32_t len = PL_strlen(line); + rv = tempOutfile->Write(line, len, &n); + PR_Free(line); + if (NS_FAILED(rv) || n != len) + { + status = NS_ERROR_FAILURE; + goto FAIL; + } + } + + PR_Free(orig_hap); + } + + // + // Read from the message file, and write to the FCC or Queue file. + // There are two tricky parts: the first is that the message file + // uses CRLF, and the FCC file should use LINEBREAK. The second + // is that the message file may have lines beginning with "From " + // but the FCC file must have those lines mangled. + // + // It's unfortunate that we end up writing the FCC file a line + // at a time, but it's the easiest way... + // + uint64_t available; + rv = inputFile->Available(&available); + NS_ENSURE_SUCCESS(rv, rv); + while (available > 0) + { + // check *ibuffer in case that ibuffer isn't big enough + uint32_t readCount; + rv = inputFile->Read(ibuffer, ibuffer_size, &readCount); + if (NS_FAILED(rv) || readCount == 0 || *ibuffer == 0) + { + status = NS_ERROR_FAILURE; + goto FAIL; + } + + rv = tempOutfile->Write(ibuffer, readCount, &n); + if (NS_FAILED(rv) || n != readCount) // write failed + { + status = NS_MSG_ERROR_WRITING_FILE; + goto FAIL; + } + + rv = inputFile->Available(&available); + NS_ENSURE_SUCCESS(rv, rv); + } + +FAIL: + PR_Free(ibuffer); + + if (NS_FAILED(tempOutfile->Flush())) + status = NS_MSG_ERROR_WRITING_FILE; + + tempOutfile->Close(); + + if (inputFile) + inputFile->Close(); + + + // here we should clone mCopyFile, since it has changed on disk. + nsCOMPtr <nsIFile> clonedFile; + mCopyFile->Clone(getter_AddRefs(clonedFile)); + mCopyFile = clonedFile; + + // When we get here, we have to see if we have been successful so far. + // If we have, then we should start up the async copy service operation. + // If we weren't successful, then we should just return the error and + // bail out. + if (NS_SUCCEEDED(status)) + { + // If we are here, time to start the async copy service operation! + status = StartMessageCopyOperation(mCopyFile, mode, turi); + } + return status; +} + +// +// This is pretty much a wrapper to the functionality that lives in the +// nsMsgCopy class +// +nsresult +nsMsgComposeAndSend::StartMessageCopyOperation(nsIFile *aFile, + nsMsgDeliverMode mode, + const nsCString& dest_uri) +{ + mCopyObj = new nsMsgCopy(); + if (!mCopyObj) + return NS_ERROR_OUT_OF_MEMORY; + + // + // Actually, we need to pick up the proper folder from the prefs and not + // default to the default "Flagged" folder choices + // + nsresult rv; + if (!dest_uri.IsEmpty()) + m_folderName = dest_uri; + else + GetFolderURIFromUserPrefs(mode, mUserIdentity, m_folderName); + + if (mListener) + mListener->OnGetDraftFolderURI(m_folderName.get()); + + rv = mCopyObj->StartCopyOperation(mUserIdentity, aFile, mode, + this, m_folderName.get(), mMsgToReplace); + return rv; +} + +//I'm getting this each time without holding onto the feedback so that 3 pane windows can be closed +//without any chance of crashing due to holding onto a deleted feedback. +nsresult +nsMsgComposeAndSend::SetStatusMessage(const nsString &aMsgString) +{ + if (mSendProgress) + mSendProgress->OnStatusChange(nullptr, nullptr, NS_OK, aMsgString.get()); + return NS_OK; +} + +// For GUI notification... +nsresult +nsMsgComposeAndSend::SetGUINotificationState(bool aEnableFlag) +{ + mGUINotificationEnabled = aEnableFlag; + return NS_OK; +} + +/* readonly attribute nsIMsgSendReport sendReport; */ +NS_IMETHODIMP +nsMsgComposeAndSend::GetSendReport(nsIMsgSendReport * *aSendReport) +{ + NS_ENSURE_ARG_POINTER(aSendReport); + NS_IF_ADDREF(*aSendReport = mSendReport); + return NS_OK; +} + +nsresult nsMsgComposeAndSend::Abort() +{ + uint32_t i; + nsresult rv; + + if (mAbortInProcess) + return NS_OK; + + mAbortInProcess = true; + + if (m_plaintext) + rv = m_plaintext->Abort(); + + for (i = 0; i < m_attachment_count; i ++) + { + nsMsgAttachmentHandler *ma = m_attachments[i]; + if (ma) + rv = ma->Abort(); + } + + /* stop the current running url */ + if (mRunningRequest) + { + mRunningRequest->Cancel(NS_ERROR_ABORT); + mRunningRequest = nullptr; + } + + if (mCopyObj) + { + nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + copyService->NotifyCompletion(mCopyFile, mCopyObj->mDstFolder, NS_ERROR_ABORT); + } + mAbortInProcess = false; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeAndSend::GetProcessAttachmentsSynchronously(bool *_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = m_be_synchronous_p; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeAndSend::GetAttachmentHandlers(nsTArray<RefPtr<nsMsgAttachmentHandler>> **_retval) +{ + NS_ENSURE_ARG(_retval); + *_retval = &m_attachments; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeAndSend::GetAttachmentCount(uint32_t *aAttachmentCount) +{ + NS_ENSURE_ARG(aAttachmentCount); + *aAttachmentCount = m_attachment_count; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeAndSend::GetPendingAttachmentCount(uint32_t *aPendingAttachmentCount) +{ + NS_ENSURE_ARG(aPendingAttachmentCount); + *aPendingAttachmentCount = m_attachment_pending_count; + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeAndSend::SetPendingAttachmentCount(uint32_t aPendingAttachmentCount) +{ + m_attachment_pending_count = aPendingAttachmentCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeAndSend::GetDeliveryMode(nsMsgDeliverMode *aDeliveryMode) +{ + NS_ENSURE_ARG(aDeliveryMode); + *aDeliveryMode = m_deliver_mode; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeAndSend::GetProgress(nsIMsgProgress **_retval) +{ + NS_ENSURE_ARG(_retval); + NS_IF_ADDREF(*_retval = mSendProgress); + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeAndSend::GetOutputStream(nsIOutputStream **_retval) +{ + NS_ENSURE_ARG(_retval); + NS_IF_ADDREF(*_retval = mOutputFile); + return NS_OK; +} + + +/* [noscript] attribute nsIURI runningURL; */ +NS_IMETHODIMP nsMsgComposeAndSend::GetRunningRequest(nsIRequest **request) +{ + NS_ENSURE_ARG(request); + NS_IF_ADDREF(*request = mRunningRequest); + return NS_OK; +} +NS_IMETHODIMP nsMsgComposeAndSend::SetRunningRequest(nsIRequest *request) +{ + mRunningRequest = request; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeAndSend::GetStatus(nsresult *aStatus) +{ + NS_ENSURE_ARG(aStatus); + *aStatus = m_status; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeAndSend::SetStatus(nsresult aStatus) +{ + m_status = aStatus; + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeAndSend::GetCryptoclosure(nsIMsgComposeSecure ** aCryptoclosure) +{ + NS_ENSURE_ARG(aCryptoclosure); + NS_IF_ADDREF(*aCryptoclosure = m_crypto_closure); + return NS_OK; +} + +NS_IMETHODIMP nsMsgComposeAndSend::SetCryptoclosure(nsIMsgComposeSecure * aCryptoclosure) +{ + m_crypto_closure = aCryptoclosure; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::GetSendCompFields(nsIMsgCompFields** aCompFields) +{ + NS_ENSURE_ARG_POINTER(aCompFields); + nsCOMPtr<nsIMsgCompFields> qiCompFields(mCompFields); + NS_ENSURE_STATE(qiCompFields); + qiCompFields.forget(aCompFields); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::GetSendBody(nsAString& aBody) +{ + nsCString charSet; + if (mCompFields) + mCompFields->GetCharacterSet(getter_Copies(charSet)); + if (!m_attachment1_body) { + aBody.Truncate(); + return NS_OK; + } + return ConvertToUnicode(charSet.get(), m_attachment1_body, aBody); +} + +NS_IMETHODIMP +nsMsgComposeAndSend::GetSendBodyType(nsACString& aBodyType) +{ + if (m_attachment1_type && *m_attachment1_type) + aBodyType.Assign(nsDependentCString(m_attachment1_type)); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::GetIdentity(nsIMsgIdentity **aIdentity) +{ + NS_ENSURE_ARG_POINTER(aIdentity); + *aIdentity = mUserIdentity; + NS_IF_ADDREF(*aIdentity); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::GetAttachment(uint32_t aIndex, + nsIMsgAttachmentHandler **aAttachment) +{ + NS_ENSURE_ARG_POINTER(aAttachment); + if (aIndex >= m_attachment_count) + return NS_ERROR_ILLEGAL_VALUE; + *aAttachment = m_attachments[aIndex]; + NS_IF_ADDREF(*aAttachment); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::SetSavedToFolderName(const nsAString& aName) +{ + mSavedToFolderName.Assign(aName); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::GetSavedToFolderName(nsAString& aName) +{ + aName.Assign(mSavedToFolderName); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgComposeAndSend::SetDontDeliver(bool aDontDeliver) +{ + m_dont_deliver_p = aDontDeliver; + return NS_OK; +} +NS_IMETHODIMP +nsMsgComposeAndSend::GetDontDeliver(bool *aDontDeliver) +{ + NS_ENSURE_ARG_POINTER(aDontDeliver); + *aDontDeliver = m_dont_deliver_p; + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsMsgAttachmentData, nsIMsgAttachmentData) + +nsMsgAttachmentData::nsMsgAttachmentData() : m_size(0), m_sizeExternalStr("-1"), + m_isExternalAttachment(false), m_isExternalLinkAttachment(false), + m_isDownloaded(false), m_hasFilename(false), m_displayableInline(false) +{ +} + +nsMsgAttachmentData::~nsMsgAttachmentData() +{ +} + +NS_IMETHODIMP nsMsgAttachmentData::GetUrl(nsIURI **aUrl) +{ + NS_ENSURE_ARG_POINTER(aUrl); + NS_IF_ADDREF(*aUrl = m_url); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetUrl(nsIURI *aUrl) +{ + m_url = aUrl; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetDesiredType(nsACString &aDesiredType) +{ + aDesiredType = m_desiredType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetDesiredType(const nsACString &aDesiredType) +{ + m_desiredType = aDesiredType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetRealType(nsACString &aRealType) +{ + aRealType = m_realType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetRealType(const nsACString &aRealType) +{ + m_realType = aRealType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetRealEncoding(nsACString &aRealEncoding) +{ + aRealEncoding = m_realEncoding; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetRealEncoding(const nsACString &aRealEncoding) +{ + m_realEncoding = aRealEncoding; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetRealName(nsACString &aRealName) +{ + aRealName = m_realName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetRealName(const nsACString &aRealName) +{ + m_realName = aRealName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetDescription(nsACString &aDescription) +{ + aDescription = m_description; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetDescription(const nsACString &aDescription) +{ + m_description = aDescription; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetXMacType(nsACString & aXMacType) +{ + aXMacType = m_xMacType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetXMacType(const nsACString & aXMacType) +{ + m_xMacType = aXMacType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::GetXMacCreator(nsACString & aXMacCreator) +{ + aXMacCreator = m_xMacCreator; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachmentData::SetXMacCreator(const nsACString & aXMacCreator) +{ + m_xMacCreator = aXMacCreator; + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsMsgAttachedFile, nsIMsgAttachedFile) + +nsMsgAttachedFile::nsMsgAttachedFile() : m_size(0), m_unprintableCount(0), + m_highbitCount(0), m_ctlCount(0), m_nullCount(0), m_maxLineLength(0) +{ +} + +nsMsgAttachedFile::~nsMsgAttachedFile() +{ +} + +NS_IMETHODIMP nsMsgAttachedFile::GetOrigUrl(nsIURI **aOrigUrl) +{ + NS_ENSURE_ARG_POINTER(aOrigUrl); + NS_IF_ADDREF(*aOrigUrl = m_origUrl); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetOrigUrl(nsIURI *aOrigUrl) +{ + m_origUrl = aOrigUrl; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetTmpFile(nsIFile **aTmpFile) +{ + NS_ENSURE_ARG_POINTER(aTmpFile); + NS_IF_ADDREF(*aTmpFile = m_tmpFile); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetTmpFile(nsIFile *aTmpFile) +{ + m_tmpFile = aTmpFile; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetType(nsACString &aType) +{ + aType = m_type; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetType(const nsACString &aType) +{ + m_type = aType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetEncoding(nsACString &aEncoding) +{ + aEncoding = m_encoding; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetEncoding(const nsACString &aEncoding) +{ + m_encoding = aEncoding; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetDescription(nsACString &aDescription) +{ + aDescription = m_description; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetDescription(const nsACString &aDescription) +{ + m_description = aDescription; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetCloudPartInfo(nsACString &aCloudPartInfo) +{ + aCloudPartInfo = m_cloudPartInfo; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetCloudPartInfo(const nsACString &aCloudPartInfo) +{ + m_cloudPartInfo = aCloudPartInfo; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetXMacType(nsACString & aXMacType) +{ + aXMacType = m_xMacType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetXMacType(const nsACString & aXMacType) +{ + m_xMacType = aXMacType; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetXMacCreator(nsACString & aXMacCreator) +{ + aXMacCreator = m_xMacCreator; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetXMacCreator(const nsACString & aXMacCreator) +{ + m_xMacCreator = aXMacCreator; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetRealName(nsACString & aRealName) +{ + aRealName = m_realName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetRealName(const nsACString & aRealName) +{ + m_realName = aRealName; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetSize(uint32_t *aSize) +{ + NS_ENSURE_ARG_POINTER(aSize); + *aSize = m_size; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetSize(uint32_t aSize) +{ + m_size = aSize; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetUnprintableCount(uint32_t *aUnprintableCount) +{ + NS_ENSURE_ARG_POINTER(aUnprintableCount); + *aUnprintableCount = m_unprintableCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetUnprintableCount(uint32_t aUnprintableCount) +{ + m_unprintableCount = aUnprintableCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetHighbitCount(uint32_t *aHighbitCount) +{ + NS_ENSURE_ARG_POINTER(aHighbitCount); + *aHighbitCount = m_highbitCount; + return NS_OK; +} +NS_IMETHODIMP nsMsgAttachedFile::SetHighbitCount(uint32_t aHighbitCount) +{ + m_highbitCount = aHighbitCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetCtlCount(uint32_t *aCtlCount) +{ + NS_ENSURE_ARG_POINTER(aCtlCount); + *aCtlCount = m_ctlCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetCtlCount(uint32_t aCtlCount) +{ + m_ctlCount = aCtlCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetNullCount(uint32_t *aNullCount) +{ + NS_ENSURE_ARG_POINTER(aNullCount); + *aNullCount = m_nullCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetNullCount(uint32_t aNullCount) +{ + m_nullCount = aNullCount; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::GetMaxLineLength(uint32_t *aMaxLineLength) +{ + NS_ENSURE_ARG_POINTER(aMaxLineLength); + *aMaxLineLength = m_maxLineLength; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAttachedFile::SetMaxLineLength(uint32_t aMaxLineLength) +{ + m_maxLineLength = aMaxLineLength; + return NS_OK; +} diff --git a/mailnews/compose/src/nsMsgSend.h b/mailnews/compose/src/nsMsgSend.h new file mode 100644 index 000000000..558c8e53e --- /dev/null +++ b/mailnews/compose/src/nsMsgSend.h @@ -0,0 +1,405 @@ +/* -*- 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 __MSGSEND_H__ +#define __MSGSEND_H__ + +/* Asynchronous mailing of messages with attached URLs. + + - If there are any attachments, start their URLs going, and write each + of them to a temp file. + + - While writing to their files, examine the data going by and decide + what kind of encoding, if any, they need. Also remember their content + types. + + - Once that URLs has been saved to a temp file (or, if there were no + attachments) generate a final temp file, of the actual message: + + - Generate a string of the headers. + - Open the final temp file. + - Write the headers. + - Examine the first part, and decide whether to encode it. + - Write the first part to the file, possibly encoded. + - Write the second and subsequent parts to the file, possibly encoded. + (Open the first temp file and copy it to the final temp file, and so + on, through an encoding filter.) + + - Delete the attachment temp file(s) as we finish with them. + - Close the final temp file. + - Open the news: url. + - Send the final temp file to NNTP. + If there's an error, run the callback with "failure" status. + - If mail succeeded, open the mailto: url. + - Send the final temp file to SMTP. + If there's an error, run the callback with "failure" status. + - Otherwise, run the callback with "success" status. + - Free everything, delete the final temp file. + + The theory behind the encoding logic: + ===================================== + + If the document is of type text/html, and the user has asked to attach it + as source or postscript, it will be run through the appropriate converter + (which will result in a document of type text/plain.) + + An attachment will be encoded if: + + - it is of a non-text type (in which case we will use base64); or + - The "use QP" option has been selected and high-bit characters exist; or + - any NULLs exist in the document; or + - any line is longer than 990 bytes (see LINELENGTH_ENCODING_THRESHOLD below) + and it is not of type message/rfc822. + + - If we are encoding, and more than 10% of the document consists of + non-ASCII characters, then we always use base64 instead of QP. + + We eschew quoted-printable in favor of base64 for documents which are likely + to always be binary (images, sound) because, on the off chance that a GIF + file (for example) might contain primarily bytes in the ASCII range, using + the quoted-printable representation might cause corruption due to the + translation of CR or LF to CRLF. So, when we don't know that the document + has "lines", we don't use quoted-printable. + */ + +/* For maximal compatibility, it helps to emit both + Content-Type: <type>; name="<original-file-name>" + as well as + Content-Disposition: inline; filename="<original-file-name>" + + The lossage here is, RFC1341 defined the "name" parameter to Content-Type, + but then RFC1521 deprecated it in anticipation of RFC1806, which defines + Content-Type and the "filename" parameter. But, RFC1521 is "Standards Track" + while RFC1806 is still "Experimental." So, it's probably best to just + implement both. + */ +#define EMIT_NAME_IN_CONTENT_TYPE + +/* Whether the contents of the BCC header should be preserved in the FCC'ed + copy of a message. See comments below, in mime_do_fcc_1(). + */ +#define SAVE_BCC_IN_FCC_FILE + +/* When attaching an HTML document, one must indicate the original URL of + that document, if the receiver is to have any chance of being able to + retreive and display the inline images, or to click on any links in the + HTML. + + The way we have done this in the past is by inserting a <BASE> tag as the + first line of all HTML documents we attach. (This is kind of bad in that + we're actually modifying the document, and it really isn't our place to + do that.) + + The sanctioned *new* way of doing this is to insert a Content-Base header + field on the attachment. This is (will be) a part of the forthcoming MHTML + spec. + + If GENERATE_CONTENT_BASE, we generate a Content-Base header. + + We used to have a MANGLE_HTML_ATTACHMENTS_WITH_BASE_TAG symbol that we + defined, which added a BASE tag to the bodies. We stopped doing this in + 4.0. */ +#define GENERATE_CONTENT_BASE + + +// +// Necessary includes +// +#include "nsCOMPtr.h" +#include "nsIMsgSend.h" +#include "nsIStringBundle.h" +#include "msgCore.h" +#include "prprf.h" +#include "nsIOutputStream.h" +#include "nsMsgMessageFlags.h" +#include "nsIURL.h" +#include "nsMsgAttachmentHandler.h" +#include "nsMsgCompFields.h" +#include "nsIMsgSendListener.h" +#include "nsIDOMNode.h" +#include "nsIEditor.h" +#include "nsIUrlListener.h" +#include "nsIMsgStatusFeedback.h" +#include "nsIMsgIdentity.h" +#include "nsIMsgHdr.h" +#include "nsIMsgIdentity.h" +#include "nsWeakReference.h" +#include "nsPIDOMWindow.h" +#include "nsIDOMWindow.h" +#include "nsIMsgComposeSecure.h" +#include "nsAutoPtr.h" +#include "nsMsgAttachmentData.h" +#include "nsIMsgFilterService.h" +#include "nsIMsgOperationListener.h" + +// +// Some necessary defines... +// +#define MIME_BUFFER_SIZE 4096 // must be greater than 1000 + // SMTP (RFC821) limit +// Maximum number of bytes we allow in a line before we force +// encoding to base64 if not already QR-encoded or of type message/rfc822. +#define LINELENGTH_ENCODING_THRESHOLD 990 + +// +// Utilities for string handling +// +#define PUSH_STRING(S) \ + do { PL_strcpy (buffer_tail, S); buffer_tail += PL_strlen (S); } while(0) +#define PUSH_STRINGN(S,N) \ + do { memcpy(buffer_tail, (S), (N)); buffer_tail += (N); } while(0) +#define PUSH_NEWLINE() \ + do { *buffer_tail++ = '\r'; *buffer_tail++ = '\n'; *buffer_tail = '\0'; } while(0) + +// +// Forward declarations... +// +class nsMsgSendPart; +class nsMsgCopy; +class nsIPrompt; +class nsIInterfaceRequestor; + +namespace mozilla { +namespace mailnews { +class MimeEncoder; +} +} + +class nsMsgComposeAndSend : public nsIMsgSend, + public nsIMsgOperationListener, + public nsSupportsWeakReference +{ + typedef mozilla::mailnews::MimeEncoder MimeEncoder; +public: + // + // Define QueryInterface, AddRef and Release for this class + // + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMSGSEND + NS_DECL_NSIMSGOPERATIONLISTENER + + nsMsgComposeAndSend(); + + + // Delivery and completion callback routines... + NS_IMETHOD DeliverMessage(); + NS_IMETHOD DeliverFileAsMail(); + NS_IMETHOD DeliverFileAsNews(); + void DoDeliveryExitProcessing(nsIURI * aUrl, nsresult aExitCode, bool aCheckForMail); + nsresult FormatStringWithSMTPHostNameByName(const char16_t* aMsgName, char16_t **aString); + + nsresult DoFcc(); + nsresult StartMessageCopyOperation(nsIFile *aFileSpec, + nsMsgDeliverMode mode, + const nsCString& dest_uri); + + + nsresult SendToMagicFolder(nsMsgDeliverMode flag); + + // Check to see if it's ok to save msgs to the configured folder. + bool CanSaveMessagesToFolder(const char *folderURL); + + // + // FCC operations... + // + nsresult MimeDoFCC (nsIFile *input_file, + nsMsgDeliverMode mode, + const char *bcc_header, + const char *fcc_header, + const char *news_url); + + // Init() will allow for either message creation without delivery or full + // message creation and send operations + // + nsresult Init( + nsIMsgIdentity *aUserIdentity, + const char *aAccountKey, + nsMsgCompFields *fields, + nsIFile *sendFile, + bool digest_p, + bool dont_deliver_p, + nsMsgDeliverMode mode, + nsIMsgDBHdr *msgToReplace, + const char *attachment1_type, + const nsACString &attachment1_body, + nsIArray *attachments, + nsIArray *preloaded_attachments, + const char *password, + const nsACString &aOriginalMsgURI, + MSG_ComposeType aType); + + // + // Setup the composition fields + // + nsresult InitCompositionFields(nsMsgCompFields *fields, + const nsACString &aOriginalMsgURI, + MSG_ComposeType aType); + + NS_IMETHOD GetBodyFromEditor(); + + + // + // Attachment processing... + // + nsresult HackAttachments(nsIArray *attachments, + nsIArray *preloaded_attachments); + nsresult CountCompFieldAttachments(); + nsresult AddCompFieldLocalAttachments(); + nsresult AddCompFieldRemoteAttachments(uint32_t aStartLocation, int32_t *aMailboxCount, int32_t *aNewsCount); + + // Deal with multipart related data + nsresult ProcessMultipartRelated(int32_t *aMailboxCount, int32_t *aNewsCount); + nsresult GetEmbeddedObjectInfo(nsIDOMNode *node, nsMsgAttachmentData *attachment, bool *acceptObject); + uint32_t GetMultipartRelatedCount(bool forceToBeCalculated = false); + nsCOMPtr<nsIArray> mEmbeddedObjectList; // it's initialized when calling GetMultipartRelatedCount + + // Body processing + nsresult SnarfAndCopyBody(const nsACString &attachment1_body, + const char *attachment1_type); + + int32_t PreProcessPart(nsMsgAttachmentHandler *ma, + nsMsgSendPart *toppart); // The very top most container of the message + // For part processing + + nsresult SetStatusMessage(const nsString &aMsgString); // Status message method + + // + // All vars necessary for this implementation + // + nsMsgKey m_messageKey; // jt -- Draft/Template support; newly created key + nsCOMPtr<nsIMsgIdentity> mUserIdentity; + nsCString mAccountKey; + RefPtr<nsMsgCompFields> mCompFields; // All needed composition fields (header, etc...) + nsCOMPtr<nsIFile> mTempFile; // our temporary file + + nsCOMPtr<nsIOutputStream> mOutputFile; // the actual output file stream + uint32_t mMessageWarningSize; // Warn if a message is over this size! + + bool m_dont_deliver_p; // If set, we just return the nsIFile of the file + // created, instead of actually delivering message. + nsMsgDeliverMode m_deliver_mode; // nsMsgDeliverNow, nsMsgQueueForLater, nsMsgSaveAsDraft, + // nsMsgSaveAsTemplate and nsMsgSendUnsent + nsCOMPtr<nsIMsgDBHdr> mMsgToReplace; // If the mode is nsMsgSaveAsDraft, this is the message it will + // replace + nsString mSavedToFolderName; // Name of folder we're saving to, used when + // displaying error on save. + // These are needed for callbacks to the FE... + nsCOMPtr<nsPIDOMWindowOuter> mParentWindow; + nsCOMPtr<nsIMsgProgress> mSendProgress; + nsCOMPtr<nsIMsgSendListener> mListener; + nsCOMPtr<nsIMsgStatusFeedback> mStatusFeedback; + nsCOMPtr<nsIRequest> mRunningRequest; + bool mSendMailAlso; + nsCOMPtr<nsIFile> mReturnFile; // a holder for file spec's to be returned to caller + + // File where we stored our HTML so that we could make the plaintext form. + nsCOMPtr<nsIFile> mHTMLFile; + + // Variable for storing the draft name; + nsCString m_folderName; + + // mapping between editor dom node indexes and saved mime part numbers. + nsTArray<nsCString> m_partNumbers; + // + // These variables are needed for message Copy operations! + // + nsCOMPtr<nsIFile> mCopyFile; + nsCOMPtr<nsIFile> mCopyFile2; + RefPtr<nsMsgCopy> mCopyObj; + bool mNeedToPerformSecondFCC; + bool mPerformingSecondFCC; + + // For MHTML message creation + nsCOMPtr<nsIEditor> mEditor; + + // + // The first attachment, if any (typed in by the user.) + // + char *m_attachment1_type; + char *m_attachment1_encoding; + nsAutoPtr<MimeEncoder> m_attachment1_encoder; + char *m_attachment1_body; + uint32_t m_attachment1_body_length; + char *mOriginalHTMLBody; + + // The plaintext form of the first attachment, if needed. + RefPtr<nsMsgAttachmentHandler> m_plaintext; + + // The multipart/related save object for HTML text. + nsMsgSendPart *m_related_part; + nsMsgSendPart *m_related_body_part; + + // + // Subsequent attachments, if any. + // + uint32_t m_attachment_count; + uint32_t m_attachment_pending_count; + nsTArray< RefPtr<nsMsgAttachmentHandler> > m_attachments; + nsresult m_status; // in case some attachments fail but not all + + uint32_t mPreloadedAttachmentCount; + uint32_t mRemoteAttachmentCount; + int32_t mMultipartRelatedAttachmentCount; // the number of mpart related attachments, -1 means it has not been yet initialized + + uint32_t mCompFieldLocalAttachments; // the number of file:// attachments in the comp fields + uint32_t mCompFieldRemoteAttachments; // the number of remote attachments in the comp fields + + // + // attachment states and other info... + // + bool m_pre_snarfed_attachments_p; // If true, then the attachments were + // loaded by in the background and therefore + // we shouldn't delete the tmp files (but should + // leave that to the caller.) + + bool m_digest_p; // Whether to be multipart/digest instead of + // multipart/mixed. + + bool m_be_synchronous_p; // If true, we will load one URL after another, + // rather than starting all URLs going at once + // and letting them load in parallel. This is + // more efficient if (for example) all URLs are + // known to be coming from the same news server + // or mailbox: loading them in parallel would + // cause multiple connections to the news + // server to be opened, or would cause much seek()ing. + + bool mGUINotificationEnabled; // Should we throw up the GUI alerts on errors? + bool mAbortInProcess; // Used by Abort to avoid reentrance. + + nsCOMPtr<nsIMsgComposeSecure> m_crypto_closure; + +protected: + nsCOMPtr<nsIStringBundle> mComposeBundle; + nsresult GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks); + + virtual ~nsMsgComposeAndSend(); + nsresult FilterSentMessage(); + nsresult MaybePerformSecondFCC(nsresult aStatus); + + // generates a message id for our message, if necessary + void GenerateMessageId( ); + + // add default custom headers to the message + nsresult AddDefaultCustomHeaders(); + + // add Mail-Followup-To and Mail-Reply-To header + nsresult AddMailFollowupToHeader(); + nsresult AddMailReplyToHeader(); + nsresult AddXForwardedMessageIdHeader(); + + nsCOMPtr<nsIMsgSendReport> mSendReport; + nsCString mSmtpPassword; // store the smtp Password use during a send +}; + +// +// These C routines should only be used by the nsMsgSendPart class. +// +extern nsresult mime_write_message_body(nsIMsgSend *state, const char *buf, uint32_t size); +extern char *mime_get_stream_write_buffer(void); +extern nsresult mime_encoder_output_fn (const char *buf, int32_t size, void *closure); +extern bool UseQuotedPrintable(void); + +#endif /* __MSGSEND_H__ */ diff --git a/mailnews/compose/src/nsMsgSendLater.cpp b/mailnews/compose/src/nsMsgSendLater.cpp new file mode 100644 index 000000000..97206f12b --- /dev/null +++ b/mailnews/compose/src/nsMsgSendLater.cpp @@ -0,0 +1,1552 @@ +/* -*- 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 "nsMsgSendLater.h" +#include "nsMsgCopy.h" +#include "nsIMsgSend.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIMsgMessageService.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgBaseCID.h" +#include "nsMsgCompCID.h" +#include "nsMsgCompUtils.h" +#include "nsMsgUtils.h" +#include "nsMailHeaders.h" +#include "nsMsgPrompts.h" +#include "nsISmtpUrl.h" +#include "nsIChannel.h" +#include "nsNetUtil.h" +#include "prlog.h" +#include "prmem.h" +#include "nsIMimeConverter.h" +#include "nsMsgMimeCID.h" +#include "nsComposeStrings.h" +#include "nsIMutableArray.h" +#include "nsArrayEnumerator.h" +#include "nsIObserverService.h" +#include "nsIMsgLocalMailFolder.h" +#include "nsIMsgDatabase.h" +#include "mozilla/Services.h" +#include "nsArrayUtils.h" + +// Consts for checking and sending mail in milliseconds + +// 1 second from mail into the unsent messages folder to initially trying to +// send it. +const uint32_t kInitialMessageSendTime = 1000; + +NS_IMPL_ISUPPORTS(nsMsgSendLater, + nsIMsgSendLater, + nsIFolderListener, + nsIRequestObserver, + nsIStreamListener, + nsIObserver, + nsIUrlListener, + nsIMsgShutdownTask) + +nsMsgSendLater::nsMsgSendLater() +{ + mSendingMessages = false; + mTimerSet = false; + mTotalSentSuccessfully = 0; + mTotalSendCount = 0; + mLeftoverBuffer = nullptr; + + m_to = nullptr; + m_bcc = nullptr; + m_fcc = nullptr; + m_newsgroups = nullptr; + m_newshost = nullptr; + m_headers = nullptr; + m_flags = 0; + m_headersFP = 0; + m_inhead = true; + m_headersPosition = 0; + + m_bytesRead = 0; + m_position = 0; + m_flagsPosition = 0; + m_headersSize = 0; + + mIdentityKey = nullptr; + mAccountKey = nullptr; +} + +nsMsgSendLater::~nsMsgSendLater() +{ + PR_Free(m_to); + PR_Free(m_fcc); + PR_Free(m_bcc); + PR_Free(m_newsgroups); + PR_Free(m_newshost); + PR_Free(m_headers); + PR_Free(mLeftoverBuffer); + PR_Free(mIdentityKey); + PR_Free(mAccountKey); +} + +nsresult +nsMsgSendLater::Init() +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + bool sendInBackground; + rv = prefs->GetBoolPref("mailnews.sendInBackground", &sendInBackground); + // If we're not sending in the background, don't do anything else + if (NS_FAILED(rv) || !sendInBackground) + return NS_OK; + + // We need to know when we're shutting down. + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED); + + rv = observerService->AddObserver(this, "xpcom-shutdown", false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = observerService->AddObserver(this, "quit-application", false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = observerService->AddObserver(this, "msg-shutdown", false); + NS_ENSURE_SUCCESS(rv, rv); + + // Subscribe to the unsent messages folder + // XXX This code should be set up for multiple unsent folders, however we + // don't support that at the moment, so for now just assume one folder. + rv = GetUnsentMessagesFolder(nullptr, getter_AddRefs(mMessageFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mMessageFolder->AddFolderListener(this); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX may want to send messages X seconds after startup if there are any. + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::Observe(nsISupports *aSubject, const char* aTopic, + const char16_t *aData) +{ + if (aSubject == mTimer && !strcmp(aTopic, "timer-callback")) + { + if (mTimer) + mTimer->Cancel(); + else + NS_ERROR("mTimer was null in nsMsgSendLater::Observe"); + + mTimerSet = false; + // If we've already started a send since the timer fired, don't start + // another + if (!mSendingMessages) + InternalSendMessages(false, nullptr); + } + else if (!strcmp(aTopic, "quit-application")) + { + // If the timer is set, cancel it - we're quitting, the shutdown service + // interfaces will sort out sending etc. + if (mTimer) + mTimer->Cancel(); + + mTimerSet = false; + } + else if (!strcmp(aTopic, "xpcom-shutdown")) + { + // We're shutting down. Unsubscribe from the unsentFolder notifications + // they aren't any use to us now, we don't want to start sending more + // messages. + nsresult rv; + if (mMessageFolder) + { + rv = mMessageFolder->RemoveFolderListener(this); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Now remove ourselves from the observer service as well. + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED); + + rv = observerService->RemoveObserver(this, "xpcom-shutdown"); + NS_ENSURE_SUCCESS(rv, rv); + + rv = observerService->RemoveObserver(this, "quit-application"); + NS_ENSURE_SUCCESS(rv, rv); + + rv = observerService->RemoveObserver(this, "msg-shutdown"); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::SetStatusFeedback(nsIMsgStatusFeedback *aFeedback) +{ + mFeedback = aFeedback; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::GetStatusFeedback(nsIMsgStatusFeedback **aFeedback) +{ + NS_ENSURE_ARG_POINTER(aFeedback); + NS_IF_ADDREF(*aFeedback = mFeedback); + return NS_OK; +} + +// Stream is done...drive on! +NS_IMETHODIMP +nsMsgSendLater::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) +{ + nsresult rv; + + // First, this shouldn't happen, but if + // it does, flush the buffer and move on. + if (mLeftoverBuffer) + { + DeliverQueuedLine(mLeftoverBuffer, PL_strlen(mLeftoverBuffer)); + } + + if (mOutFile) + mOutFile->Close(); + + // See if we succeeded on reading the message from the message store? + // + if (NS_SUCCEEDED(status)) + { + // Message is done...send it! + rv = CompleteMailFileSend(); + +#ifdef NS_DEBUG + printf("nsMsgSendLater: Success on getting message...\n"); +#endif + + // If the send operation failed..try the next one... + if (NS_FAILED(rv)) + { + rv = StartNextMailFileSend(rv); + if (NS_FAILED(rv)) + EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully); + } + } + else + { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + if(!channel) return NS_ERROR_FAILURE; + + // extract the prompt object to use for the alert from the url.... + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIPrompt> promptObject; + if (channel) + { + channel->GetURI(getter_AddRefs(uri)); + nsCOMPtr<nsISmtpUrl> smtpUrl (do_QueryInterface(uri)); + if (smtpUrl) + smtpUrl->GetPrompt(getter_AddRefs(promptObject)); + } + nsMsgDisplayMessageByName(promptObject, u"errorQueuedDeliveryFailed"); + + // Getting the data failed, but we will still keep trying to send the rest... + rv = StartNextMailFileSend(status); + if (NS_FAILED(rv)) + EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully); + } + + return rv; +} + +char * +FindEOL(char *inBuf, char *buf_end) +{ + char *buf = inBuf; + char *findLoc = nullptr; + + while (buf <= buf_end) + if (*buf == 0) + return buf; + else if ( (*buf == '\n') || (*buf == '\r') ) + { + findLoc = buf; + break; + } + else + ++buf; + + if (!findLoc) + return nullptr; + else if ((findLoc + 1) > buf_end) + return buf; + + if ( (*findLoc == '\n' && *(findLoc+1) == '\r') || + (*findLoc == '\r' && *(findLoc+1) == '\n')) + findLoc++; // possibly a pair. + return findLoc; +} + +nsresult +nsMsgSendLater::RebufferLeftovers(char *startBuf, uint32_t aLen) +{ + PR_FREEIF(mLeftoverBuffer); + mLeftoverBuffer = (char *)PR_Malloc(aLen + 1); + if (!mLeftoverBuffer) + return NS_ERROR_OUT_OF_MEMORY; + + memcpy(mLeftoverBuffer, startBuf, aLen); + mLeftoverBuffer[aLen] = '\0'; + return NS_OK; +} + +nsresult +nsMsgSendLater::BuildNewBuffer(const char* aBuf, uint32_t aCount, uint32_t *totalBufSize) +{ + // Only build a buffer when there are leftovers... + NS_ENSURE_TRUE(mLeftoverBuffer, NS_ERROR_FAILURE); + + int32_t leftoverSize = PL_strlen(mLeftoverBuffer); + char* newBuffer = (char *) PR_Realloc(mLeftoverBuffer, aCount + leftoverSize); + NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY); + mLeftoverBuffer = newBuffer; + + memcpy(mLeftoverBuffer + leftoverSize, aBuf, aCount); + *totalBufSize = aCount + leftoverSize; + return NS_OK; +} + +// Got data? +NS_IMETHODIMP +nsMsgSendLater::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, nsIInputStream *inStr, uint64_t sourceOffset, uint32_t count) +{ + NS_ENSURE_ARG_POINTER(inStr); + + // This is a little bit tricky since we have to chop random + // buffers into lines and deliver the lines...plus keeping the + // leftovers for next time...some fun, eh? + // + nsresult rv = NS_OK; + char *startBuf; + char *endBuf; + char *lineEnd; + char *newbuf = nullptr; + uint32_t size; + + uint32_t aCount = count; + char *aBuf = (char *)PR_Malloc(aCount + 1); + + inStr->Read(aBuf, count, &aCount); + + // First, create a new work buffer that will + if (NS_FAILED(BuildNewBuffer(aBuf, aCount, &size))) // no leftovers... + { + startBuf = (char *)aBuf; + endBuf = (char *)(aBuf + aCount - 1); + } + else // yum, leftovers...new buffer created...sitting in mLeftoverBuffer + { + newbuf = mLeftoverBuffer; + startBuf = newbuf; + endBuf = startBuf + size - 1; + mLeftoverBuffer = nullptr; // null out this + } + + while (startBuf <= endBuf) + { + lineEnd = FindEOL(startBuf, endBuf); + if (!lineEnd) + { + rv = RebufferLeftovers(startBuf, (endBuf - startBuf) + 1); + break; + } + + rv = DeliverQueuedLine(startBuf, (lineEnd - startBuf) + 1); + if (NS_FAILED(rv)) + break; + + startBuf = lineEnd+1; + } + + PR_Free(newbuf); + PR_Free(aBuf); + return rv; +} + +NS_IMETHODIMP +nsMsgSendLater::OnStartRunningUrl(nsIURI *url) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnStopRunningUrl(nsIURI *url, nsresult aExitCode) +{ + if (NS_SUCCEEDED(aExitCode)) + InternalSendMessages(mUserInitiated, mIdentity); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////////// +// This is the listener class for the send operation. We have to create this class +// to listen for message send completion and eventually notify the caller +//////////////////////////////////////////////////////////////////////////////////// +NS_IMPL_ISUPPORTS(SendOperationListener, nsIMsgSendListener, + nsIMsgCopyServiceListener) + +SendOperationListener::SendOperationListener(nsMsgSendLater *aSendLater) +: mSendLater(aSendLater) +{ +} + +SendOperationListener::~SendOperationListener(void) +{ +} + +NS_IMETHODIMP +SendOperationListener::OnGetDraftFolderURI(const char *aFolderURI) +{ + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::OnStartSending(const char *aMsgID, uint32_t aMsgSize) +{ +#ifdef NS_DEBUG + printf("SendOperationListener::OnStartSending()\n"); +#endif + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::OnProgress(const char *aMsgID, uint32_t aProgress, uint32_t aProgressMax) +{ +#ifdef NS_DEBUG + printf("SendOperationListener::OnProgress()\n"); +#endif + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::OnStatus(const char *aMsgID, const char16_t *aMsg) +{ +#ifdef NS_DEBUG + printf("SendOperationListener::OnStatus()\n"); +#endif + + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::OnSendNotPerformed(const char *aMsgID, nsresult aStatus) +{ + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::OnStopSending(const char *aMsgID, nsresult aStatus, const char16_t *aMsg, + nsIFile *returnFile) +{ + if (mSendLater && !mSendLater->OnSendStepFinished(aStatus)) + NS_RELEASE(mSendLater); + + return NS_OK; +} + +// nsIMsgCopyServiceListener + +NS_IMETHODIMP +SendOperationListener::OnStartCopy(void) +{ + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax) +{ + return NS_OK; +} + +NS_IMETHODIMP +SendOperationListener::SetMessageKey(nsMsgKey aKey) +{ + NS_NOTREACHED("SendOperationListener::SetMessageKey()"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +SendOperationListener::GetMessageId(nsACString& messageId) +{ + NS_NOTREACHED("SendOperationListener::GetMessageId()\n"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +SendOperationListener::OnStopCopy(nsresult aStatus) +{ + if (mSendLater) + { + mSendLater->OnCopyStepFinished(aStatus); + NS_RELEASE(mSendLater); + } + + return NS_OK; +} + +nsresult +nsMsgSendLater::CompleteMailFileSend() +{ + // get the identity from the key + // if no key, or we fail to find the identity + // use the default identity on the default account + nsCOMPtr<nsIMsgIdentity> identity; + nsresult rv = GetIdentityFromKey(mIdentityKey, getter_AddRefs(identity)); + NS_ENSURE_SUCCESS(rv,rv); + + // If for some reason the tmp file didn't get created, we've failed here + bool created; + mTempFile->Exists(&created); + if (!created) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIMsgCompFields> compFields = do_CreateInstance(NS_MSGCOMPFIELDS_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIMsgSend> pMsgSend = do_CreateInstance(NS_MSGSEND_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + // Since we have already parsed all of the headers, we are simply going to + // set the composition fields and move on. + nsCString author; + mMessage->GetAuthor(getter_Copies(author)); + + nsMsgCompFields * fields = (nsMsgCompFields *)compFields.get(); + + fields->SetFrom(author.get()); + + if (m_to) + { + fields->SetTo(m_to); + } + + if (m_bcc) + { + fields->SetBcc(m_bcc); + } + + if (m_fcc) + { + fields->SetFcc(m_fcc); + } + + if (m_newsgroups) + fields->SetNewsgroups(m_newsgroups); + +#if 0 + // needs cleanup. Is this needed? + if (m_newshost) + fields->SetNewspostUrl(m_newshost); +#endif + + // Create the listener for the send operation... + SendOperationListener *sendListener = new SendOperationListener(this); + if (!sendListener) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(sendListener); + + NS_ADDREF(this); //TODO: We should remove this!!! + rv = pMsgSend->SendMessageFile(identity, + mAccountKey, + compFields, // nsIMsgCompFields *fields, + mTempFile, // nsIFile *sendFile, + true, // bool deleteSendFileOnCompletion, + false, // bool digest_p, + nsIMsgSend::nsMsgSendUnsent, // nsMsgDeliverMode mode, + nullptr, // nsIMsgDBHdr *msgToReplace, + sendListener, + mFeedback, + nullptr); + NS_RELEASE(sendListener); + return rv; +} + +nsresult +nsMsgSendLater::StartNextMailFileSend(nsresult prevStatus) +{ + bool hasMoreElements = false; + if ((!mEnumerator) || + NS_FAILED(mEnumerator->HasMoreElements(&hasMoreElements)) || + !hasMoreElements) + { + // Notify that this message has finished being sent. + NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100, 100); + + // EndSendMessages resets everything for us + EndSendMessages(prevStatus, nullptr, mTotalSendCount, mTotalSentSuccessfully); + + // XXX Should we be releasing references so that we don't hold onto items + // unnecessarily. + return NS_OK; + } + + // If we've already sent a message, and are sending more, send out a progress + // update with 100% for both send and copy as we must have finished by now. + if (mTotalSendCount) + NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100, 100); + + nsCOMPtr<nsISupports> currentItem; + nsresult rv = mEnumerator->GetNext(getter_AddRefs(currentItem)); + NS_ENSURE_SUCCESS(rv, rv); + + mMessage = do_QueryInterface(currentItem); + if (!mMessage) + return NS_ERROR_NOT_AVAILABLE; + + if (!mMessageFolder) + return NS_ERROR_UNEXPECTED; + + nsCString messageURI; + mMessageFolder->GetUriForMsg(mMessage, messageURI); + + rv = nsMsgCreateTempFile("nsqmail.tmp", getter_AddRefs(mTempFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgMessageService> messageService; + rv = GetMessageServiceFromURI(messageURI, getter_AddRefs(messageService)); + if (NS_FAILED(rv) && !messageService) + return NS_ERROR_FACTORY_NOT_LOADED; + + ++mTotalSendCount; + + nsCString identityKey; + rv = mMessage->GetStringProperty(HEADER_X_MOZILLA_IDENTITY_KEY, + getter_Copies(identityKey)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgIdentity> identity; + rv = GetIdentityFromKey(identityKey.get(), getter_AddRefs(identity)); + NS_ENSURE_SUCCESS(rv, rv); + + // Notify that we're just about to start sending this message + NotifyListenersOnMessageStartSending(mTotalSendCount, mMessagesToSend.Count(), + identity); + + // Setup what we need to parse the data stream correctly + m_inhead = true; + m_headersFP = 0; + m_headersPosition = 0; + m_bytesRead = 0; + m_position = 0; + m_flagsPosition = 0; + m_headersSize = 0; + PR_FREEIF(mLeftoverBuffer); + + // Now, get our stream listener interface and plug it into the DisplayMessage + // operation + AddRef(); + + nsCOMPtr<nsIURI> dummyNull; + rv = messageService->DisplayMessage(messageURI.get(), + static_cast<nsIStreamListener*>(this), + nullptr, nullptr, nullptr, + getter_AddRefs(dummyNull)); + + Release(); + + return rv; +} + +NS_IMETHODIMP +nsMsgSendLater::GetUnsentMessagesFolder(nsIMsgIdentity *aIdentity, nsIMsgFolder **folder) +{ + nsCString uri; + GetFolderURIFromUserPrefs(nsIMsgSend::nsMsgQueueForLater, aIdentity, uri); + return LocateMessageFolder(aIdentity, nsIMsgSend::nsMsgQueueForLater, + uri.get(), folder); +} + +NS_IMETHODIMP +nsMsgSendLater::HasUnsentMessages(nsIMsgIdentity *aIdentity, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + nsresult rv; + + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIArray> accounts; + accountManager->GetAccounts(getter_AddRefs(accounts)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t cnt = 0; + rv = accounts->GetLength(&cnt); + if (cnt == 0) { + *aResult = false; + return NS_OK; // no account set up -> no unsent messages + } + + // XXX This code should be set up for multiple unsent folders, however we + // don't support that at the moment, so for now just assume one folder. + if (!mMessageFolder) + { + rv = GetUnsentMessagesFolder(nullptr, + getter_AddRefs(mMessageFolder)); + NS_ENSURE_SUCCESS(rv, rv); + } + rv = ReparseDBIfNeeded(nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t totalMessages; + rv = mMessageFolder->GetTotalMessages(false, &totalMessages); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = totalMessages > 0; + return NS_OK; +} + +// +// To really finalize this capability, we need to have the ability to get +// the message from the mail store in a stream for processing. The flow +// would be something like this: +// +// foreach (message in Outbox folder) +// get stream of Nth message +// if (not done with headers) +// Tack on to current buffer of headers +// when done with headers +// BuildHeaders() +// Write Headers to Temp File +// after done with headers +// write rest of message body to temp file +// +// when done with the message +// do send operation +// +// when send is complete +// Copy from Outbox to FCC folder +// Delete from Outbox folder +// +// +NS_IMETHODIMP +nsMsgSendLater::SendUnsentMessages(nsIMsgIdentity *aIdentity) +{ + return InternalSendMessages(true, aIdentity); +} + +// Returns NS_OK if the db is OK, an error otherwise, e.g., we had to reparse. +nsresult nsMsgSendLater::ReparseDBIfNeeded(nsIUrlListener *aListener) +{ + // This will kick off a reparse, if needed. So the next time we check if + // there are unsent messages, the db will be up to date. + nsCOMPtr<nsIMsgDatabase> unsentDB; + nsresult rv; + nsCOMPtr<nsIMsgLocalMailFolder> locFolder(do_QueryInterface(mMessageFolder, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return locFolder->GetDatabaseWithReparse(aListener, nullptr, + getter_AddRefs(unsentDB)); +} + +nsresult +nsMsgSendLater::InternalSendMessages(bool aUserInitiated, + nsIMsgIdentity *aIdentity) +{ + if (WeAreOffline()) + return NS_MSG_ERROR_OFFLINE; + + // Protect against being called whilst we're already sending. + if (mSendingMessages) + { + NS_ERROR("nsMsgSendLater is already sending messages\n"); + return NS_ERROR_FAILURE; + } + + nsresult rv; + + // XXX This code should be set up for multiple unsent folders, however we + // don't support that at the moment, so for now just assume one folder. + if (!mMessageFolder) + { + rv = GetUnsentMessagesFolder(nullptr, + getter_AddRefs(mMessageFolder)); + NS_ENSURE_SUCCESS(rv, rv); + } + nsCOMPtr<nsIMsgDatabase> unsentDB; + // Remember these in case we need to reparse the db. + mUserInitiated = aUserInitiated; + mIdentity = aIdentity; + rv = ReparseDBIfNeeded(this); + NS_ENSURE_SUCCESS(rv, rv); + mIdentity = nullptr; // don't hold onto the identity since we're a service. + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = mMessageFolder->GetMessages(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + // copy all the elements in the enumerator into our isupports array.... + + nsCOMPtr<nsISupports> currentItem; + nsCOMPtr<nsIMsgDBHdr> messageHeader; + bool hasMoreElements = false; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) && hasMoreElements) + { + rv = enumerator->GetNext(getter_AddRefs(currentItem)); + if (NS_SUCCEEDED(rv)) + { + messageHeader = do_QueryInterface(currentItem, &rv); + if (NS_SUCCEEDED(rv)) + { + if (aUserInitiated) + // If the user initiated the send, add all messages + mMessagesToSend.AppendObject(messageHeader); + else + { + // Else just send those that are NOT marked as Queued. + uint32_t flags; + rv = messageHeader->GetFlags(&flags); + if (NS_SUCCEEDED(rv) && !(flags & nsMsgMessageFlags::Queued)) + mMessagesToSend.AppendObject(messageHeader); + } + } + } + } + + // Now get an enumerator for our array. + rv = NS_NewArrayEnumerator(getter_AddRefs(mEnumerator), mMessagesToSend); + NS_ENSURE_SUCCESS(rv, rv); + + // We're now sending messages so its time to signal that and reset our counts. + mSendingMessages = true; + mTotalSentSuccessfully = 0; + mTotalSendCount = 0; + + // Notify the listeners that we are starting a send. + NotifyListenersOnStartSending(mMessagesToSend.Count()); + + return StartNextMailFileSend(NS_OK); +} + +nsresult nsMsgSendLater::SetOrigMsgDisposition() +{ + if (!mMessage) + return NS_ERROR_NULL_POINTER; + + // We're finished sending a queued message. We need to look at mMessage + // and see if we need to set replied/forwarded + // flags for the original message that this message might be a reply to + // or forward of. + nsCString originalMsgURIs; + nsCString queuedDisposition; + mMessage->GetStringProperty(ORIG_URI_PROPERTY, getter_Copies(originalMsgURIs)); + mMessage->GetStringProperty(QUEUED_DISPOSITION_PROPERTY, getter_Copies(queuedDisposition)); + if (!queuedDisposition.IsEmpty()) + { + nsTArray<nsCString> uriArray; + ParseString(originalMsgURIs, ',', uriArray); + for (uint32_t i = 0; i < uriArray.Length(); i++) + { + nsCOMPtr <nsIMsgDBHdr> msgHdr; + nsresult rv = GetMsgDBHdrFromURI(uriArray[i].get(), getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv,rv); + if (msgHdr) + { + // get the folder for the message resource + nsCOMPtr<nsIMsgFolder> msgFolder; + msgHdr->GetFolder(getter_AddRefs(msgFolder)); + if (msgFolder) + { + nsMsgDispositionState dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Replied; + if (queuedDisposition.Equals("forwarded")) + dispositionSetting = nsIMsgFolder::nsMsgDispositionState_Forwarded; + + msgFolder->AddMessageDispositionState(msgHdr, dispositionSetting); + } + } + } + } + return NS_OK; +} + +nsresult +nsMsgSendLater::DeleteCurrentMessage() +{ + if (!mMessage) + { + NS_ERROR("nsMsgSendLater: Attempt to delete an already deleted message"); + return NS_OK; + } + + // Get the composition fields interface + nsCOMPtr<nsIMutableArray> msgArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); + if (!msgArray) + return NS_ERROR_FACTORY_NOT_LOADED; + + if (!mMessageFolder) + return NS_ERROR_UNEXPECTED; + + msgArray->InsertElementAt(mMessage, 0, false); + + nsresult res = mMessageFolder->DeleteMessages(msgArray, nullptr, true, false, nullptr, false /*allowUndo*/); + if (NS_FAILED(res)) + return NS_ERROR_FAILURE; + + // Null out the message so we don't try and delete it again. + mMessage = nullptr; + + return NS_OK; +} + +// +// This function parses the headers, and also deletes from the header block +// any headers which should not be delivered in mail, regardless of whether +// they were present in the queue file. Such headers include: BCC, FCC, +// Sender, X-Mozilla-Status, X-Mozilla-News-Host, and Content-Length. +// (Content-Length is for the disk file only, and must not be allowed to +// escape onto the network, since it depends on the local linebreak +// representation. Arguably, we could allow Lines to escape, but it's not +// required by NNTP.) +// +nsresult +nsMsgSendLater::BuildHeaders() +{ + char *buf = m_headers; + char *buf_end = buf + m_headersFP; + + PR_FREEIF(m_to); + PR_FREEIF(m_bcc); + PR_FREEIF(m_newsgroups); + PR_FREEIF(m_newshost); + PR_FREEIF(m_fcc); + PR_FREEIF(mIdentityKey); + PR_FREEIF(mAccountKey); + m_flags = 0; + + while (buf < buf_end) + { + bool prune_p = false; + bool do_flags_p = false; + char *colon = PL_strchr(buf, ':'); + char *end; + char *value = 0; + char **header = 0; + char *header_start = buf; + + if (! colon) + break; + + end = colon; + while (end > buf && (*end == ' ' || *end == '\t')) + end--; + + switch (buf [0]) + { + case 'B': case 'b': + if (!PL_strncasecmp ("BCC", buf, end - buf)) + { + header = &m_bcc; + prune_p = true; + } + break; + case 'C': case 'c': + if (!PL_strncasecmp ("CC", buf, end - buf)) + header = &m_to; + else if (!PL_strncasecmp (HEADER_CONTENT_LENGTH, buf, end - buf)) + prune_p = true; + break; + case 'F': case 'f': + if (!PL_strncasecmp ("FCC", buf, end - buf)) + { + header = &m_fcc; + prune_p = true; + } + break; + case 'L': case 'l': + if (!PL_strncasecmp ("Lines", buf, end - buf)) + prune_p = true; + break; + case 'N': case 'n': + if (!PL_strncasecmp ("Newsgroups", buf, end - buf)) + header = &m_newsgroups; + break; + case 'S': case 's': + if (!PL_strncasecmp ("Sender", buf, end - buf)) + prune_p = true; + break; + case 'T': case 't': + if (!PL_strncasecmp ("To", buf, end - buf)) + header = &m_to; + break; + case 'X': case 'x': + { + if (buf + strlen(HEADER_X_MOZILLA_STATUS2) == end && + !PL_strncasecmp(HEADER_X_MOZILLA_STATUS2, buf, end - buf)) + prune_p = true; + else if (buf + strlen(HEADER_X_MOZILLA_STATUS) == end && + !PL_strncasecmp(HEADER_X_MOZILLA_STATUS, buf, end - buf)) + prune_p = do_flags_p = true; + else if (!PL_strncasecmp(HEADER_X_MOZILLA_DRAFT_INFO, buf, end - buf)) + prune_p = true; + else if (!PL_strncasecmp(HEADER_X_MOZILLA_KEYWORDS, buf, end - buf)) + prune_p = true; + else if (!PL_strncasecmp(HEADER_X_MOZILLA_NEWSHOST, buf, end - buf)) + { + prune_p = true; + header = &m_newshost; + } + else if (!PL_strncasecmp(HEADER_X_MOZILLA_IDENTITY_KEY, buf, end - buf)) + { + prune_p = true; + header = &mIdentityKey; + } + else if (!PL_strncasecmp(HEADER_X_MOZILLA_ACCOUNT_KEY, buf, end - buf)) + { + prune_p = true; + header = &mAccountKey; + } + break; + } + } + + buf = colon + 1; + while (*buf == ' ' || *buf == '\t') + buf++; + + value = buf; + +SEARCH_NEWLINE: + while (*buf != 0 && *buf != '\r' && *buf != '\n') + buf++; + + if (buf+1 >= buf_end) + ; + // If "\r\n " or "\r\n\t" is next, that doesn't terminate the header. + else if (buf+2 < buf_end && + (buf[0] == '\r' && buf[1] == '\n') && + (buf[2] == ' ' || buf[2] == '\t')) + { + buf += 3; + goto SEARCH_NEWLINE; + } + // If "\r " or "\r\t" or "\n " or "\n\t" is next, that doesn't terminate + // the header either. + else if ((buf[0] == '\r' || buf[0] == '\n') && + (buf[1] == ' ' || buf[1] == '\t')) + { + buf += 2; + goto SEARCH_NEWLINE; + } + + if (header) + { + int L = buf - value; + if (*header) + { + char *newh = (char*) PR_Realloc ((*header), + PL_strlen(*header) + L + 10); + if (!newh) return NS_ERROR_OUT_OF_MEMORY; + *header = newh; + newh = (*header) + PL_strlen (*header); + *newh++ = ','; + *newh++ = ' '; + memcpy(newh, value, L); + newh [L] = 0; + } + else + { + *header = (char *) PR_Malloc(L+1); + if (!*header) return NS_ERROR_OUT_OF_MEMORY; + memcpy((*header), value, L); + (*header)[L] = 0; + } + } + else if (do_flags_p) + { + char *s = value; + PR_ASSERT(*s != ' ' && *s != '\t'); + NS_ASSERTION(MsgIsHex(s, 4), "Expected 4 hex digits for flags."); + m_flags = MsgUnhex(s, 4); + } + + if (*buf == '\r' || *buf == '\n') + { + if (*buf == '\r' && buf[1] == '\n') + buf++; + buf++; + } + + if (prune_p) + { + char *to = header_start; + char *from = buf; + while (from < buf_end) + *to++ = *from++; + buf = header_start; + buf_end = to; + m_headersFP = buf_end - m_headers; + } + } + + m_headers[m_headersFP++] = '\r'; + m_headers[m_headersFP++] = '\n'; + + // Now we have parsed out all of the headers we need and we + // can proceed. + return NS_OK; +} + +nsresult +DoGrowBuffer(int32_t desired_size, int32_t element_size, int32_t quantum, + char **buffer, int32_t *size) +{ + if (*size <= desired_size) + { + char *new_buf; + int32_t increment = desired_size - *size; + if (increment < quantum) // always grow by a minimum of N bytes + increment = quantum; + + new_buf = (*buffer + ? (char *) PR_Realloc (*buffer, (*size + increment) + * (element_size / sizeof(char))) + : (char *) PR_Malloc ((*size + increment) + * (element_size / sizeof(char)))); + if (! new_buf) + return NS_ERROR_OUT_OF_MEMORY; + *buffer = new_buf; + *size += increment; + } + return NS_OK; +} + +#define do_grow_headers(desired_size) \ + (((desired_size) >= m_headersSize) ? \ + DoGrowBuffer ((desired_size), sizeof(char), 1024, \ + &m_headers, &m_headersSize) \ + : NS_OK) + +nsresult +nsMsgSendLater::DeliverQueuedLine(char *line, int32_t length) +{ + int32_t flength = length; + + m_bytesRead += length; + +// convert existing newline to CRLF +// Don't need this because the calling routine is taking care of it. +// if (length > 0 && (line[length-1] == '\r' || +// (line[length-1] == '\n' && (length < 2 || line[length-2] != '\r')))) +// { +// line[length-1] = '\r'; +// line[length++] = '\n'; +// } +// + // + // We are going to check if we are looking at a "From - " line. If so, + // then just eat it and return NS_OK + // + if (!PL_strncasecmp(line, "From - ", 7)) + return NS_OK; + + if (m_inhead) + { + if (m_headersPosition == 0) + { + // This line is the first line in a header block. + // Remember its position. + m_headersPosition = m_position; + + // Also, since we're now processing the headers, clear out the + // slots which we will parse data into, so that the values that + // were used the last time around do not persist. + + // We must do that here, and not in the previous clause of this + // `else' (the "I've just seen a `From ' line clause") because + // that clause happens before delivery of the previous message is + // complete, whereas this clause happens after the previous msg + // has been delivered. If we did this up there, then only the + // last message in the folder would ever be able to be both + // mailed and posted (or fcc'ed.) + PR_FREEIF(m_to); + PR_FREEIF(m_bcc); + PR_FREEIF(m_newsgroups); + PR_FREEIF(m_newshost); + PR_FREEIF(m_fcc); + PR_FREEIF(mIdentityKey); + } + + if (line[0] == '\r' || line[0] == '\n' || line[0] == 0) + { + // End of headers. Now parse them; open the temp file; + // and write the appropriate subset of the headers out. + m_inhead = false; + + nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mOutFile), mTempFile, -1, 00600); + if (NS_FAILED(rv)) + return NS_MSG_ERROR_WRITING_FILE; + + nsresult status = BuildHeaders(); + if (NS_FAILED(status)) + return status; + + uint32_t n; + rv = mOutFile->Write(m_headers, m_headersFP, &n); + if (NS_FAILED(rv) || n != (uint32_t)m_headersFP) + return NS_MSG_ERROR_WRITING_FILE; + } + else + { + // Otherwise, this line belongs to a header. So append it to the + // header data. + + if (!PL_strncasecmp (line, HEADER_X_MOZILLA_STATUS, PL_strlen(HEADER_X_MOZILLA_STATUS))) + // Notice the position of the flags. + m_flagsPosition = m_position; + else if (m_headersFP == 0) + m_flagsPosition = 0; + + nsresult status = do_grow_headers (length + m_headersFP + 10); + if (NS_FAILED(status)) + return status; + + memcpy(m_headers + m_headersFP, line, length); + m_headersFP += length; + } + } + else + { + // This is a body line. Write it to the file. + PR_ASSERT(mOutFile); + if (mOutFile) + { + uint32_t wrote; + nsresult rv = mOutFile->Write(line, length, &wrote); + if (NS_FAILED(rv) || wrote < (uint32_t) length) + return NS_MSG_ERROR_WRITING_FILE; + } + } + + m_position += flength; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::AddListener(nsIMsgSendLaterListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + mListenerArray.AppendElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::RemoveListener(nsIMsgSendLaterListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + return mListenerArray.RemoveElement(aListener) ? NS_OK : NS_ERROR_INVALID_ARG; +} + +NS_IMETHODIMP +nsMsgSendLater::GetSendingMessages(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mSendingMessages; + return NS_OK; +} + +#define NOTIFY_LISTENERS(propertyfunc_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray<nsCOMPtr<nsIMsgSendLaterListener> >::ForwardIterator iter(mListenerArray); \ + nsCOMPtr<nsIMsgSendLaterListener> listener; \ + while (iter.HasMore()) { \ + listener = iter.GetNext(); \ + listener->propertyfunc_ params_; \ + } \ + PR_END_MACRO + +void +nsMsgSendLater::NotifyListenersOnStartSending(uint32_t aTotalMessageCount) +{ + NOTIFY_LISTENERS(OnStartSending, (aTotalMessageCount)); +} + +void +nsMsgSendLater::NotifyListenersOnMessageStartSending(uint32_t aCurrentMessage, + uint32_t aTotalMessage, + nsIMsgIdentity *aIdentity) +{ + NOTIFY_LISTENERS(OnMessageStartSending, (aCurrentMessage, aTotalMessage, + mMessage, aIdentity)); +} + +void +nsMsgSendLater::NotifyListenersOnProgress(uint32_t aCurrentMessage, + uint32_t aTotalMessage, + uint32_t aSendPercent, + uint32_t aCopyPercent) +{ + NOTIFY_LISTENERS(OnMessageSendProgress, (aCurrentMessage, aTotalMessage, + aSendPercent, aCopyPercent)); +} + +void +nsMsgSendLater::NotifyListenersOnMessageSendError(uint32_t aCurrentMessage, + nsresult aStatus, + const char16_t *aMsg) +{ + NOTIFY_LISTENERS(OnMessageSendError, (aCurrentMessage, mMessage, + aStatus, aMsg)); +} + +/** + * This function is called to end sending of messages, it resets the send later + * system and notifies the relevant parties that we have finished. + */ +void +nsMsgSendLater::EndSendMessages(nsresult aStatus, const char16_t *aMsg, + uint32_t aTotalTried, uint32_t aSuccessful) +{ + // Catch-all, we may have had an issue sending, so we may not be calling + // StartNextMailFileSend to fully finish the sending. Therefore set + // mSendingMessages to false here so that we don't think we're still trying + // to send messages + mSendingMessages = false; + + // Clear out our array of messages. + mMessagesToSend.Clear(); + + // We don't need to keep hold of the database now we've finished sending. + (void)mMessageFolder->SetMsgDatabase(nullptr); + + // or the enumerator, temp file or output stream + mEnumerator = nullptr; + mTempFile = nullptr; + mOutFile = nullptr; + + NOTIFY_LISTENERS(OnStopSending, (aStatus, aMsg, aTotalTried, aSuccessful)); + + // If we've got a shutdown listener, notify it that we've finished. + if (mShutdownListener) + { + mShutdownListener->OnStopRunningUrl(nullptr, NS_OK); + mShutdownListener = nullptr; + } +} + +/** + * Called when the send part of sending a message is finished. This will set up + * for the next step or "end" depending on the status. + * + * @param aStatus The success or fail result of the send step. + * @return True if the copy process will continue, false otherwise. + */ +bool +nsMsgSendLater::OnSendStepFinished(nsresult aStatus) +{ + if (NS_SUCCEEDED(aStatus)) + { + SetOrigMsgDisposition(); + DeleteCurrentMessage(); + + // Send finished, so that is now 100%, copy to proceed... + NotifyListenersOnProgress(mTotalSendCount, mMessagesToSend.Count(), 100, 0); + + ++mTotalSentSuccessfully; + return true; + } + else + { + // XXX we don't currently get a message string from the send service. + NotifyListenersOnMessageSendError(mTotalSendCount, aStatus, nullptr); + nsresult rv = StartNextMailFileSend(aStatus); + // if this is the last message we're sending, we should report + // the status failure. + if (NS_FAILED(rv)) + EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully); + } + return false; +} + +/** + * Called when the copy part of sending a message is finished. This will send + * the next message or handle failure as appropriate. + * + * @param aStatus The success or fail result of the copy step. + */ +void +nsMsgSendLater::OnCopyStepFinished(nsresult aStatus) +{ + // Regardless of the success of the copy we will still keep trying + // to send the rest... + nsresult rv = StartNextMailFileSend(aStatus); + if (NS_FAILED(rv)) + EndSendMessages(rv, nullptr, mTotalSendCount, mTotalSentSuccessfully); +} + +// XXX todo +// maybe this should just live in the account manager? +nsresult +nsMsgSendLater::GetIdentityFromKey(const char *aKey, nsIMsgIdentity **aIdentity) +{ + NS_ENSURE_ARG_POINTER(aIdentity); + + nsresult rv; + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + if (aKey) + { + nsCOMPtr<nsIArray> identities; + if (NS_SUCCEEDED(accountManager->GetAllIdentities(getter_AddRefs(identities)))) + { + nsCOMPtr<nsIMsgIdentity> lookupIdentity; + uint32_t count = 0; + + identities->GetLength(&count); + for (uint32_t i = 0; i < count; i++) + { + lookupIdentity = do_QueryElementAt(identities, i, &rv); + if (NS_FAILED(rv)) + continue; + + nsCString key; + lookupIdentity->GetKey(key); + if (key.Equals(aKey)) + { + NS_IF_ADDREF(*aIdentity = lookupIdentity); + return NS_OK; + } + } + } + } + + // if no aKey, or we failed to find the identity from the key + // use the identity from the default account. + nsCOMPtr<nsIMsgAccount> defaultAccount; + rv = accountManager->GetDefaultAccount(getter_AddRefs(defaultAccount)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = defaultAccount->GetDefaultIdentity(aIdentity); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +NS_IMETHODIMP +nsMsgSendLater::OnItemAdded(nsIMsgFolder *aParentItem, nsISupports *aItem) +{ + // No need to trigger if timer is already set + if (mTimerSet) + return NS_OK; + + // XXX only trigger for non-queued headers + + // Items from this function return NS_OK because the callee won't care about + // the result anyway. + nsresult rv; + if (!mTimer) + { + mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + NS_ENSURE_SUCCESS(rv, NS_OK); + } + + rv = mTimer->Init(static_cast<nsIObserver*>(this), kInitialMessageSendTime, + nsITimer::TYPE_ONE_SHOT); + NS_ENSURE_SUCCESS(rv, NS_OK); + + mTimerSet = true; + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnItemRemoved(nsIMsgFolder *aParentItem, nsISupports *aItem) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnItemPropertyChanged(nsIMsgFolder *aItem, nsIAtom *aProperty, + const char* aOldValue, + const char* aNewValue) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnItemIntPropertyChanged(nsIMsgFolder *aItem, nsIAtom *aProperty, + int64_t aOldValue, int64_t aNewValue) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnItemBoolPropertyChanged(nsIMsgFolder *aItem, nsIAtom *aProperty, + bool aOldValue, bool aNewValue) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnItemUnicharPropertyChanged(nsIMsgFolder *aItem, + nsIAtom *aProperty, + const char16_t* aOldValue, + const char16_t* aNewValue) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnItemPropertyFlagChanged(nsIMsgDBHdr *aItem, nsIAtom *aProperty, + uint32_t aOldValue, uint32_t aNewValue) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::OnItemEvent(nsIMsgFolder* aItem, nsIAtom *aEvent) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::GetNeedsToRunTask(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mSendingMessages; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgSendLater::DoShutdownTask(nsIUrlListener *aListener, nsIMsgWindow *aWindow, + bool *aResult) +{ + if (mTimer) + mTimer->Cancel(); + // If we're already sending messages, nothing to do, but save the shutdown + // listener until we've finished. + if (mSendingMessages) + { + mShutdownListener = aListener; + return NS_OK; + } + // Else we have pending messages, we need to throw up a dialog to find out + // if to send them or not. + + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgSendLater::GetCurrentTaskName(nsAString &aResult) +{ + // XXX Bug 440794 will localize this, left as non-localized whilst we decide + // on the actual strings and try out the UI. + aResult = NS_LITERAL_STRING("Sending Messages"); + return NS_OK; +} diff --git a/mailnews/compose/src/nsMsgSendLater.h b/mailnews/compose/src/nsMsgSendLater.h new file mode 100644 index 000000000..734396acc --- /dev/null +++ b/mailnews/compose/src/nsMsgSendLater.h @@ -0,0 +1,144 @@ +/* -*- 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 _nsMsgSendLater_H_ +#define _nsMsgSendLater_H_ + +#include "nsCOMArray.h" +#include "nsIMsgFolder.h" +#include "nsIMsgSendListener.h" +#include "nsIMsgSendLaterListener.h" +#include "nsIMsgSendLater.h" +#include "nsIMsgStatusFeedback.h" +#include "nsTObserverArray.h" +#include "nsIObserver.h" +#include "nsITimer.h" +#include "nsIMsgShutdown.h" + +//////////////////////////////////////////////////////////////////////////////////// +// This is the listener class for the send operation. We have to create this class +// to listen for message send completion and eventually notify the caller +//////////////////////////////////////////////////////////////////////////////////// +class nsMsgSendLater; + +class SendOperationListener : public nsIMsgSendListener, + public nsIMsgCopyServiceListener +{ +public: + SendOperationListener(nsMsgSendLater *aSendLater); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGSENDLISTENER + NS_DECL_NSIMSGCOPYSERVICELISTENER + +private: + virtual ~SendOperationListener(); + nsMsgSendLater *mSendLater; +}; + +class nsMsgSendLater: public nsIMsgSendLater, + public nsIFolderListener, + public nsIObserver, + public nsIUrlListener, + public nsIMsgShutdownTask + +{ +public: + nsMsgSendLater(); + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGSENDLATER + NS_DECL_NSIFOLDERLISTENER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSIOBSERVER + NS_DECL_NSIURLLISTENER + NS_DECL_NSIMSGSHUTDOWNTASK + + // Methods needed for implementing interface... + nsresult StartNextMailFileSend(nsresult prevStatus); + nsresult CompleteMailFileSend(); + + nsresult DeleteCurrentMessage(); + nsresult SetOrigMsgDisposition(); + // Necessary for creating a valid list of recipients + nsresult BuildHeaders(); + nsresult DeliverQueuedLine(char *line, int32_t length); + nsresult RebufferLeftovers(char *startBuf, uint32_t aLen); + nsresult BuildNewBuffer(const char* aBuf, uint32_t aCount, uint32_t *totalBufSize); + + // methods for listener array processing... + void NotifyListenersOnStartSending(uint32_t aTotalMessageCount); + void NotifyListenersOnMessageStartSending(uint32_t aCurrentMessage, + uint32_t aTotalMessage, + nsIMsgIdentity *aIdentity); + void NotifyListenersOnProgress(uint32_t aCurrentMessage, + uint32_t aTotalMessage, + uint32_t aSendPercent, + uint32_t aCopyPercent); + void NotifyListenersOnMessageSendError(uint32_t aCurrentMessage, + nsresult aStatus, + const char16_t *aMsg); + void EndSendMessages(nsresult aStatus, const char16_t *aMsg, + uint32_t aTotalTried, uint32_t aSuccessful); + + bool OnSendStepFinished(nsresult aStatus); + void OnCopyStepFinished(nsresult aStatus); + + // counters and things for enumeration + uint32_t mTotalSentSuccessfully; + uint32_t mTotalSendCount; + nsCOMArray<nsIMsgDBHdr> mMessagesToSend; + nsCOMPtr<nsISimpleEnumerator> mEnumerator; + nsCOMPtr<nsIMsgFolder> mMessageFolder; + nsCOMPtr<nsIMsgStatusFeedback> mFeedback; + + // Private Information +private: + virtual ~nsMsgSendLater(); + nsresult GetIdentityFromKey(const char *aKey, nsIMsgIdentity **aIdentity); + nsresult ReparseDBIfNeeded(nsIUrlListener *aListener); + nsresult InternalSendMessages(bool aUserInitiated, + nsIMsgIdentity *aIdentity); + + nsTObserverArray<nsCOMPtr<nsIMsgSendLaterListener> > mListenerArray; + nsCOMPtr<nsIMsgDBHdr> mMessage; + nsCOMPtr<nsITimer> mTimer; + bool mTimerSet; + nsCOMPtr<nsIUrlListener> mShutdownListener; + + // + // File output stuff... + // + nsCOMPtr<nsIFile> mTempFile; + nsCOMPtr<nsIOutputStream> mOutFile; + + // For building headers and stream parsing... + char *m_to; + char *m_bcc; + char *m_fcc; + char *m_newsgroups; + char *m_newshost; + char *m_headers; + int32_t m_flags; + int32_t m_headersFP; + bool m_inhead; + int32_t m_headersPosition; + int32_t m_bytesRead; + int32_t m_position; + int32_t m_flagsPosition; + int32_t m_headersSize; + char *mLeftoverBuffer; + char *mIdentityKey; + char *mAccountKey; + + bool mSendingMessages; + bool mUserInitiated; + nsCOMPtr<nsIMsgIdentity> mIdentity; +}; + + +#endif /* _nsMsgSendLater_H_ */ diff --git a/mailnews/compose/src/nsMsgSendPart.cpp b/mailnews/compose/src/nsMsgSendPart.cpp new file mode 100644 index 000000000..a33dabf33 --- /dev/null +++ b/mailnews/compose/src/nsMsgSendPart.cpp @@ -0,0 +1,779 @@ +/* -*- 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 "nsMsgLocalFolderHdrs.h" +#include "nsMsgSend.h" +#include "nsMsgSendPart.h" +#include "nsIMimeConverter.h" +#include "nsCOMPtr.h" +#include "nsIComponentManager.h" +#include "nsMsgI18N.h" +#include "nsMsgCompUtils.h" +#include "nsMsgMimeCID.h" +#include "nsMimeTypes.h" +#include "prmem.h" +#include "nsMsgPrompts.h" +#include "nsNativeCharsetUtils.h" +#include "nsNetUtil.h" +#include "nsISeekableStream.h" +#include "nsReadLine.h" +#include "nsILineInputStream.h" +#include "nsComposeStrings.h" +#include "mozilla/mailnews/MimeEncoder.h" + +static char *mime_mailto_stream_read_buffer = 0; + +int32_t nsMsgSendPart::M_counter = 0; + +nsMsgSendPart::nsMsgSendPart(nsIMsgSend* state, const char *part_charset) +{ + PL_strncpy(m_charset_name, (part_charset ? part_charset : "UTF-8"), sizeof(m_charset_name)-1); + m_charset_name[sizeof(m_charset_name)-1] = '\0'; + m_children = nullptr; + m_numchildren = 0; + // if we're not added as a child, the default part number will be "1". + m_partNum = "1"; + SetMimeDeliveryState(state); + + m_parent = nullptr; + m_buffer = nullptr; + m_type = nullptr; + m_other = nullptr; + m_strip_sensitive_headers = false; + + m_firstBlock = false; + m_needIntlConversion = false; + + m_mainpart = false; + m_just_hit_CR = false; +} + + +nsMsgSendPart::~nsMsgSendPart() +{ + for (int i=0 ; i < m_numchildren; i++) + delete m_children[i]; + + delete [] m_children; + PR_FREEIF(m_buffer); + PR_FREEIF(m_other); + PR_FREEIF(m_type); +} + +nsresult nsMsgSendPart::CopyString(char** dest, const char* src) +{ + NS_ASSERTION(src, "src null"); + + PR_FREEIF(*dest); + if (!src) + *dest = PL_strdup(""); + else + *dest = PL_strdup(src); + + return *dest? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + + +nsresult nsMsgSendPart::SetFile(nsIFile *file) +{ + m_file = file; + return NS_OK; +} + + +nsresult nsMsgSendPart::SetBuffer(const char* buffer) +{ + PR_FREEIF(m_buffer); + return CopyString(&m_buffer, buffer); +} + + +nsresult nsMsgSendPart::SetType(const char* type) +{ + PR_FREEIF(m_type); + m_type = PL_strdup(type); + return m_type ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + + +nsresult nsMsgSendPart::SetOtherHeaders(const char* other) +{ + return CopyString(&m_other, other); +} + +nsresult nsMsgSendPart::SetMimeDeliveryState(nsIMsgSend *state) +{ + m_state = state; + if (GetNumChildren() > 0) + { + for (int i = 0; i < GetNumChildren(); i++) + { + nsMsgSendPart *part = GetChild(i); + if (part) + part->SetMimeDeliveryState(state); + } + } + return NS_OK; +} + +nsresult nsMsgSendPart::AppendOtherHeaders(const char* more) +{ + if (!m_other) + return SetOtherHeaders(more); + + if (!more || !*more) + return NS_OK; + + char* tmp = (char *) PR_Malloc(sizeof(char) * (PL_strlen(m_other) + PL_strlen(more) + 2)); + if (!tmp) + return NS_ERROR_OUT_OF_MEMORY; + + PL_strcpy(tmp, m_other); + PL_strcat(tmp, more); + PR_FREEIF(m_other); + m_other = tmp; + + return NS_OK; +} + + +nsresult nsMsgSendPart::SetMainPart(bool value) +{ + m_mainpart = value; + return NS_OK; +} + +nsresult nsMsgSendPart::AddChild(nsMsgSendPart* child) +{ + m_numchildren++; + nsMsgSendPart** tmp = new nsMsgSendPart* [m_numchildren]; + if (tmp == nullptr) return NS_ERROR_OUT_OF_MEMORY; + for (int i=0 ; i<m_numchildren-1 ; i++) { + tmp[i] = m_children[i]; + } + delete [] m_children; + m_children = tmp; + m_children[m_numchildren - 1] = child; + child->m_parent = this; + nsCString partNum(m_partNum); + partNum.Append("."); + partNum.AppendInt(m_numchildren); + child->m_partNum = partNum; + return NS_OK; +} + +nsMsgSendPart * nsMsgSendPart::DetachChild(int32_t whichOne) +{ + nsMsgSendPart *returnValue = nullptr; + + NS_ASSERTION(whichOne >= 0 && whichOne < m_numchildren, "parameter out of range"); + if (whichOne >= 0 && whichOne < m_numchildren) + { + returnValue = m_children[whichOne]; + + if (m_numchildren > 1) + { + nsMsgSendPart** tmp = new nsMsgSendPart* [m_numchildren-1]; + if (tmp != nullptr) + { + // move all the other kids over + for (int i=0 ; i<m_numchildren-1 ; i++) + { + if (i >= whichOne) + tmp[i] = m_children[i+1]; + else + tmp[i] = m_children[i]; + } + delete [] m_children; + m_children = tmp; + m_numchildren--; + } + } + else + { + delete [] m_children; + m_children = nullptr; + m_numchildren = 0; + } + } + + if (returnValue) + returnValue->m_parent = nullptr; + + return returnValue; +} + +nsMsgSendPart* nsMsgSendPart::GetChild(int32_t which) +{ + NS_ASSERTION(which >= 0 && which < m_numchildren, "parameter out of range"); + if (which >= 0 && which < m_numchildren) { + return m_children[which]; + } + return nullptr; +} + + + +nsresult nsMsgSendPart::PushBody(const char* buffer, int32_t length) +{ + nsresult status = NS_OK; + const char* encoded_data = buffer; + + if (m_encoder) + { + status = m_encoder->Write(encoded_data, length); + } + else + { + // Merely translate all linebreaks to CRLF. + const char *in = encoded_data; + const char *end = in + length; + char *buffer, *out; + + + buffer = mime_get_stream_write_buffer(); + // XXX -1 is not a valid nsresult + NS_ENSURE_TRUE(buffer, static_cast<nsresult>(-1)); + + NS_ASSERTION(encoded_data != buffer, "encoded_data == buffer"); + out = buffer; + + for (; in < end; in++) { + if (m_just_hit_CR) { + m_just_hit_CR = false; + if (*in == '\n') { + // The last thing we wrote was a CRLF from hitting a CR. + // So, we don't want to do anything from a following LF; + // we want to ignore it. + continue; + } + } + if (*in == '\r' || *in == '\n') { + /* Write out the newline. */ + *out++ = '\r'; + *out++ = '\n'; + + status = mime_write_message_body(m_state, buffer, + out - buffer); + if (NS_FAILED(status)) return status; + out = buffer; + + if (*in == '\r') { + m_just_hit_CR = true; + } + + out = buffer; + } else { + + /* Fix for bug #95985. We can't assume that all lines are shorter + than 4096 chars (MIME_BUFFER_SIZE), so we need to test + for this here. sfraser. + */ + if (out - buffer >= MIME_BUFFER_SIZE) + { + status = mime_write_message_body(m_state, buffer, out - buffer); + if (NS_FAILED(status)) return status; + + out = buffer; + } + + *out++ = *in; + } + } + + /* Flush the last line. */ + if (out > buffer) { + status = mime_write_message_body(m_state, buffer, out - buffer); + if (NS_FAILED(status)) return status; + out = buffer; + } + } + + if (encoded_data && encoded_data != buffer) { + PR_Free((char *) encoded_data); + } + + return status; +} + + +/* Partition the headers into those which apply to the message as a whole; +those which apply to the message's contents; and the Content-Type header +itself. (This relies on the fact that all body-related headers begin with +"Content-".) + + (How many header parsers are in this program now?) + */ +static nsresult +divide_content_headers(const char *headers, + char **message_headers, + char **content_headers, + char **content_type_header) +{ + const char *tail; + char *message_tail, *content_tail, *type_tail; + int L = 0; + if (headers) + L = PL_strlen(headers); + + if (L == 0) + return NS_OK; + + *message_headers = (char *)PR_Malloc(L+1); + if (!*message_headers) + return NS_ERROR_OUT_OF_MEMORY; + + *content_headers = (char *)PR_Malloc(L+1); + if (!*content_headers) { + PR_Free(*message_headers); + return NS_ERROR_OUT_OF_MEMORY; + } + + *content_type_header = (char *)PR_Malloc(L+1); + if (!*content_type_header) { + PR_Free(*message_headers); + PR_Free(*content_headers); + return NS_ERROR_OUT_OF_MEMORY; + } + + message_tail = *message_headers; + content_tail = *content_headers; + type_tail = *content_type_header; + tail = headers; + + while (*tail) + { + const char *head = tail; + char **out; + while(true) { + /* Loop until we reach a newline that is not followed by whitespace. + */ + if (tail[0] == 0 || + ((tail[0] == '\r' || tail[0] == '\n') && + !(tail[1] == ' ' || tail[1] == '\t' || tail[1] == '\n'))) + { + /* Swallow the whole newline. */ + if (tail[0] == '\r' && tail[1] == '\n') + tail++; + if (*tail) + tail++; + break; + } + tail++; + } + + /* Decide which block this header goes into. + */ + if (!PL_strncasecmp(head, "Content-Type:", 13)) + out = &type_tail; + else + if (!PL_strncasecmp(head, "Content-", 8)) + out = &content_tail; + else + out = &message_tail; + + memcpy(*out, head, (tail-head)); + *out += (tail-head); + } + + *message_tail = 0; + *content_tail = 0; + *type_tail = 0; + + if (!**message_headers) { + PR_Free(*message_headers); + *message_headers = 0; + } + + if (!**content_headers) { + PR_Free(*content_headers); + *content_headers = 0; + } + + if (!**content_type_header) { + PR_Free(*content_type_header); + *content_type_header = 0; + } + +#ifdef DEBUG + // ### mwelch Because of the extreme difficulty we've had with + // duplicate part headers, I'm going to put in an + // ASSERT here which makes sure that no duplicate + // Content-Type or Content-Transfer-Encoding headers + // leave here undetected. + const char* tmp; + if (*content_type_header) { + tmp = PL_strstr(*content_type_header, "Content-Type"); + if (tmp) { + tmp++; // get past the first occurrence + NS_ASSERTION(!PL_strstr(tmp, "Content-Type"), "Content-part already present"); + } + } + + if (*content_headers) { + tmp = PL_strstr(*content_headers, "Content-Transfer-Encoding"); + if (tmp) { + tmp++; // get past the first occurrence + NS_ASSERTION(!PL_strstr(tmp, "Content-Transfer-Encoding"), "Content-Transfert already present"); + } + } +#endif // DEBUG + + return NS_OK; +} + +#define SKIP_EMPTY_PART 1966 + +nsresult +nsMsgSendPart::Write() +{ + nsresult status = NS_OK; + char *separator = nullptr; + bool needToWriteCRLFAfterEncodedBody = false; + +#define PUSHLEN(str, length) \ + do { \ + status = mime_write_message_body(m_state, str, length); \ + if (NS_FAILED(status)) goto FAIL; \ + } while (0) \ + +#define PUSH(str) PUSHLEN(str, PL_strlen(str)) + + // rhp: Suppress the output of parts that are empty! + if ( (m_parent) && + (m_numchildren == 0) && + ( (!m_buffer) || (!*m_buffer) ) && + (!m_file) && + (!m_mainpart) ) + // XXX SKIP_EMPTY_PART (= 1966) is not a valid nsresult + return static_cast<nsresult>(SKIP_EMPTY_PART); + + if (m_mainpart && m_type && PL_strcmp(m_type, TEXT_HTML) == 0) + { + if (m_file) + { + // The "insert HTML links" code requires a memory buffer, + // so read the file into memory. + NS_ASSERTION(m_buffer == nullptr, "not-null buffer"); + int32_t length = 0; + int64_t fileSize; + if (NS_SUCCEEDED(m_file->GetFileSize(&fileSize))) + length = fileSize; + + m_buffer = (char *) PR_Malloc(sizeof(char) * (length + 1)); + if (m_buffer) + { + nsCOMPtr<nsIInputStream> inputFile; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputFile), m_file); + if (NS_SUCCEEDED(rv)) + { + uint32_t bytesRead; + rv = inputFile->Read(m_buffer, length, &bytesRead); + inputFile->Close(); + m_buffer[length] = '\0'; + } + else + PR_Free(m_buffer); + } + } + } + + if (m_parent && m_parent->m_type && + !PL_strcasecmp(m_parent->m_type, MULTIPART_DIGEST) && + m_type && + (!PL_strcasecmp(m_type, MESSAGE_RFC822) || + !PL_strcasecmp(m_type, MESSAGE_NEWS))) + { + // If we're in a multipart/digest, and this document is of type + // message/rfc822, then it's appropriate to emit no headers. + // + } + else + { + char *message_headers = 0; + char *content_headers = 0; + char *content_type_header = 0; + status = divide_content_headers(m_other, + &message_headers, + &content_headers, + &content_type_header); + if (NS_FAILED(status)) + goto FAIL; + + /* First, write out all of the headers that refer to the message + itself (From, Subject, MIME-Version, etc.) + */ + if (message_headers) + { + PUSH(message_headers); + PR_Free(message_headers); + message_headers = 0; + } + + /* Now allow the crypto library to (potentially) insert some text + (it may want to wrap the body in an envelope.) */ + if (!m_parent) { + status = m_state->BeginCryptoEncapsulation(); + if (NS_FAILED(status)) goto FAIL; + } + + /* Now make sure there's a Content-Type header. + */ + if (!content_type_header) + { + NS_ASSERTION(m_type && *m_type, "null ptr"); + bool needsCharset = mime_type_needs_charset(m_type ? m_type : TEXT_PLAIN); + if (needsCharset) + { + content_type_header = PR_smprintf("Content-Type: %s; charset=%s" CRLF, + (m_type ? m_type : TEXT_PLAIN), m_charset_name); + } + else + content_type_header = PR_smprintf("Content-Type: %s" CRLF, + (m_type ? m_type : TEXT_PLAIN)); + if (!content_type_header) + { + if (content_headers) + PR_Free(content_headers); + status = NS_ERROR_OUT_OF_MEMORY; + goto FAIL; + } + } + + /* If this is a compound object, tack a boundary string onto the + Content-Type header. this + */ + if (m_numchildren > 0) + { + int L; + char *ct2; + NS_ASSERTION(m_type, "null ptr"); + + if (!separator) + { + separator = mime_make_separator(""); + if (!separator) + { + status = NS_ERROR_OUT_OF_MEMORY; + goto FAIL; + } + } + + L = PL_strlen(content_type_header); + + if (content_type_header[L-1] == '\n') + content_type_header[--L] = 0; + if (content_type_header[L-1] == '\r') + content_type_header[--L] = 0; + + ct2 = PR_smprintf("%s;\r\n boundary=\"%s\"" CRLF, content_type_header, separator); + PR_Free(content_type_header); + if (!ct2) + { + if (content_headers) + PR_Free(content_headers); + status = NS_ERROR_OUT_OF_MEMORY; + goto FAIL; + } + + content_type_header = ct2; + } + + // Now write out the Content-Type header... + NS_ASSERTION(content_type_header && *content_type_header, "null ptr"); + PUSH(content_type_header); + PR_Free(content_type_header); + content_type_header = 0; + + /* ...followed by all of the other headers that refer to the body of + the message (Content-Transfer-Encoding, Content-Dispositon, etc.) + */ + if (content_headers) + { + PUSH(content_headers); + PR_Free(content_headers); + content_headers = 0; + } + } + + PUSH(CRLF); // A blank line, to mark the end of headers. + + m_firstBlock = true; + /* only convert if we need to tag charset */ + m_needIntlConversion = mime_type_needs_charset(m_type); + + if (m_buffer) + { + status = PushBody(m_buffer, PL_strlen(m_buffer)); + if (NS_FAILED(status)) + goto FAIL; + } + else if (m_file) + { + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), m_file); + if (NS_FAILED(rv)) + { + // mysteriously disappearing? + nsCOMPtr<nsIMsgSendReport> sendReport; + m_state->GetSendReport(getter_AddRefs(sendReport)); + if (sendReport) + { + nsAutoString error_msg; + nsMsgBuildMessageWithTmpFile(m_file, error_msg); + sendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false); + } + status = NS_MSG_UNABLE_TO_OPEN_TMP_FILE; + goto FAIL; + } + + nsCString curLine; + bool more = true; + + /* Kludge to avoid having to allocate memory on the toy computers... */ + if (!mime_mailto_stream_read_buffer) + { + mime_mailto_stream_read_buffer = (char *) PR_Malloc(MIME_BUFFER_SIZE); + if (!mime_mailto_stream_read_buffer) + { + status = NS_ERROR_OUT_OF_MEMORY; + goto FAIL; + } + } + + char *buffer = mime_mailto_stream_read_buffer; + if (m_strip_sensitive_headers) + { + // We are attaching a message, so we should be careful to + // strip out certain sensitive internal header fields. + bool skipping = false; + nsAutoPtr<nsLineBuffer<char> > lineBuffer(new nsLineBuffer<char>); + NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY); + + while (more) + { + // NS_ReadLine doesn't return line termination chars. + rv = NS_ReadLine(inputStream.get(), lineBuffer.get(), curLine, &more); + + curLine.Append(CRLF); + + char *line = (char *) curLine.get(); + if (skipping) { + if (*line == ' ' || *line == '\t') + continue; + else + skipping = false; + } + + if (!PL_strncasecmp(line, "From -", 6) || + !PL_strncasecmp(line, "BCC:", 4) || + !PL_strncasecmp(line, "FCC:", 4) || + !PL_strncasecmp(line, CONTENT_LENGTH ":", CONTENT_LENGTH_LEN+1) || + !PL_strncasecmp(line, "Lines:", 6) || + !PL_strncasecmp(line, "Status:", 7) || + !PL_strncasecmp(line, X_MOZILLA_STATUS ":", X_MOZILLA_STATUS_LEN+1) || + !PL_strncasecmp(line, X_MOZILLA_STATUS2 ":", X_MOZILLA_STATUS2_LEN+1) || + !PL_strncasecmp(line, X_MOZILLA_DRAFT_INFO ":", X_MOZILLA_DRAFT_INFO_LEN+1) || + !PL_strncasecmp(line, X_MOZILLA_NEWSHOST ":", X_MOZILLA_NEWSHOST_LEN+1) || + !PL_strncasecmp(line, X_UIDL ":", X_UIDL_LEN+1) || + !PL_strncasecmp(line, "X-VM-", 5)) /* hi Kyle */ + { + skipping = true; + continue; + } + + PUSH(line); + + if (curLine.Length() == 2) { + nsCOMPtr <nsISeekableStream> seekableStream = do_QueryInterface(inputStream); + // seek back the amount of data left in the line buffer... + seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, lineBuffer->start - lineBuffer->end); + break; // Now can do normal reads for the body. + } + } + lineBuffer = nullptr; + } + + while (NS_SUCCEEDED(status)) + { + uint32_t bytesRead; + nsresult rv = inputStream->Read(buffer, MIME_BUFFER_SIZE, &bytesRead); + if (NS_FAILED(rv)) + { + nsCOMPtr<nsIMsgSendReport> sendReport; + m_state->GetSendReport(getter_AddRefs(sendReport)); + if (sendReport) + { + nsAutoString error_msg; + nsMsgBuildMessageWithFile(m_file, error_msg); + sendReport->SetMessage(nsIMsgSendReport::process_Current, error_msg.get(), false); + status = NS_MSG_UNABLE_TO_OPEN_FILE; + goto FAIL; + } + } + status = PushBody(buffer, bytesRead); + if (NS_FAILED(status)) + goto FAIL; + if (bytesRead < MIME_BUFFER_SIZE) + break; + } + } + + if (m_encoder) + { + nsresult rv = m_encoder->Flush(); + m_encoder = nullptr; + needToWriteCRLFAfterEncodedBody = !m_parent; + if (NS_FAILED(rv)) + { + // XXX -1 is not a valid nsresult + status = static_cast<nsresult>(-1); + goto FAIL; + } + } + + // + // Ok, from here we loop and drive the the output of all children + // for this message. + // + if (m_numchildren > 0) + { + bool writeSeparator = true; + + for (int i = 0 ; i < m_numchildren ; i ++) + { + if (writeSeparator) + { + PUSH(CRLF); + PUSH("--"); + + PUSH(separator); + PUSH(CRLF); + } + + status = m_children[i]->Write(); + if (NS_FAILED(status)) + goto FAIL; + + // XXX SKIP_EMPTY_PART (= 1966) is not a valid nsresult + if (status == static_cast<nsresult>(SKIP_EMPTY_PART)) + writeSeparator = false; + else + writeSeparator = true; + } + + PUSH(CRLF); + PUSH("--"); + PUSH(separator); + PUSH("--"); + PUSH(CRLF); + } + else if (needToWriteCRLFAfterEncodedBody) + PUSH(CRLF); + +FAIL: + PR_FREEIF(separator); + return status; +} + diff --git a/mailnews/compose/src/nsMsgSendPart.h b/mailnews/compose/src/nsMsgSendPart.h new file mode 100644 index 000000000..edb5422ea --- /dev/null +++ b/mailnews/compose/src/nsMsgSendPart.h @@ -0,0 +1,101 @@ +/* -*- 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 _MsgSendPart_H_ +#define _MsgSendPart_H_ + +#include "msgCore.h" +#include "prprf.h" /* should be defined into msgCore.h? */ +#include "nsMsgSend.h" + +namespace mozilla { +namespace mailnews { +class MimeEncoder; +} +} + +typedef int (*MSG_SendPartWriteFunc)(const char* line, int32_t size, + bool isheader, void* closure); + +class nsMsgSendPart { + typedef mozilla::mailnews::MimeEncoder MimeEncoder; +public: + nsMsgSendPart(nsIMsgSend* state, const char *part_charset = NULL); + virtual ~nsMsgSendPart(); // Note that the destructor also destroys + // any children that were added. + + virtual nsresult Write(); + + virtual nsresult SetFile(nsIFile *filename); + const nsIFile *GetFile() {return m_file;} + + virtual nsresult SetBuffer(const char* buffer); + const char *GetBuffer() {return m_buffer;} + + virtual nsresult SetType(const char* type); + const char *GetType() {return m_type;} + + const char *GetCharsetName() {return m_charset_name;} + + virtual nsresult SetOtherHeaders(const char* other); + const char *SetOtherHeaders() {return m_other;} + virtual nsresult AppendOtherHeaders(const char* moreother); + + virtual nsresult SetMimeDeliveryState(nsIMsgSend* state); + + // Note that the nsMsgSendPart class will take over ownership of the + // MimeEncoderData* object, deleting it when it chooses. (This is + // necessary because deleting these objects is the only current way to + // flush out the data in them.) + void SetEncoder(MimeEncoder* encoder) {m_encoder = encoder;} + MimeEncoder *GetEncoder() {return m_encoder;} + + void SetStripSensitiveHeaders(bool value) + { + m_strip_sensitive_headers = value; + } + bool GetStripSensitiveHeaders() {return m_strip_sensitive_headers;} + + virtual nsresult AddChild(nsMsgSendPart* child); + + int32_t GetNumChildren() {return m_numchildren;} + nsMsgSendPart *GetChild(int32_t which); + nsMsgSendPart *DetachChild(int32_t which); + + virtual nsresult SetMainPart(bool value); + bool IsMainPart() + { + return m_mainpart; + } + nsCString m_partNum; +protected: + nsresult CopyString(char** dest, const char* src); + nsresult PushBody(const char* buffer, int32_t length); + + nsCOMPtr<nsIMsgSend> m_state; + nsMsgSendPart *m_parent; + nsCOMPtr <nsIFile> m_file; + char *m_buffer; + char *m_type; + char *m_other; + char m_charset_name[64+1]; // charset name associated with this part + bool m_strip_sensitive_headers; + nsAutoPtr<MimeEncoder> m_encoder; + + nsMsgSendPart **m_children; + int32_t m_numchildren; + + // Data used while actually writing. + bool m_firstBlock; + bool m_needIntlConversion; + + bool m_mainpart; + + bool m_just_hit_CR; + + static int32_t M_counter; +}; + +#endif /* _MsgSendPart_H_ */ diff --git a/mailnews/compose/src/nsMsgSendReport.cpp b/mailnews/compose/src/nsMsgSendReport.cpp new file mode 100644 index 000000000..1bc26ca8a --- /dev/null +++ b/mailnews/compose/src/nsMsgSendReport.cpp @@ -0,0 +1,437 @@ +/* -*- 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 "nsMsgSendReport.h" + +#include "msgCore.h" +#include "nsIMsgCompose.h" +#include "nsMsgCompCID.h" +#include "nsMsgPrompts.h" +#include "nsError.h" +#include "nsComposeStrings.h" +#include "nsIStringBundle.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/Services.h" + +NS_IMPL_ISUPPORTS(nsMsgProcessReport, nsIMsgProcessReport) + +nsMsgProcessReport::nsMsgProcessReport() +{ + Reset(); +} + +nsMsgProcessReport::~nsMsgProcessReport() +{ +} + +/* attribute boolean proceeded; */ +NS_IMETHODIMP nsMsgProcessReport::GetProceeded(bool *aProceeded) +{ + NS_ENSURE_ARG_POINTER(aProceeded); + *aProceeded = mProceeded; + return NS_OK; +} +NS_IMETHODIMP nsMsgProcessReport::SetProceeded(bool aProceeded) +{ + mProceeded = aProceeded; + return NS_OK; +} + +/* attribute nsresult error; */ +NS_IMETHODIMP nsMsgProcessReport::GetError(nsresult *aError) +{ + NS_ENSURE_ARG_POINTER(aError); + *aError = mError; + return NS_OK; +} +NS_IMETHODIMP nsMsgProcessReport::SetError(nsresult aError) +{ + mError = aError; + return NS_OK; +} + +/* attribute wstring message; */ +NS_IMETHODIMP nsMsgProcessReport::GetMessage(char16_t * *aMessage) +{ + NS_ENSURE_ARG_POINTER(aMessage); + *aMessage = ToNewUnicode(mMessage); + return NS_OK; +} +NS_IMETHODIMP nsMsgProcessReport::SetMessage(const char16_t * aMessage) +{ + mMessage = aMessage; + return NS_OK; +} + +/* void Reset (); */ +NS_IMETHODIMP nsMsgProcessReport::Reset() +{ + mProceeded = false; + mError = NS_OK; + mMessage.Truncate(); + + return NS_OK; +} + + +NS_IMPL_ISUPPORTS(nsMsgSendReport, nsIMsgSendReport) + +nsMsgSendReport::nsMsgSendReport() +{ + uint32_t i; + for (i = 0; i <= SEND_LAST_PROCESS; i ++) + mProcessReport[i] = new nsMsgProcessReport(); + + Reset(); +} + +nsMsgSendReport::~nsMsgSendReport() +{ + uint32_t i; + for (i = 0; i <= SEND_LAST_PROCESS; i ++) + mProcessReport[i] = nullptr; +} + +/* attribute long currentProcess; */ +NS_IMETHODIMP nsMsgSendReport::GetCurrentProcess(int32_t *aCurrentProcess) +{ + NS_ENSURE_ARG_POINTER(aCurrentProcess); + *aCurrentProcess = mCurrentProcess; + return NS_OK; +} +NS_IMETHODIMP nsMsgSendReport::SetCurrentProcess(int32_t aCurrentProcess) +{ + if (aCurrentProcess < 0 || aCurrentProcess > SEND_LAST_PROCESS) + return NS_ERROR_ILLEGAL_VALUE; + + mCurrentProcess = aCurrentProcess; + if (mProcessReport[mCurrentProcess]) + mProcessReport[mCurrentProcess]->SetProceeded(true); + + return NS_OK; +} + +/* attribute long deliveryMode; */ +NS_IMETHODIMP nsMsgSendReport::GetDeliveryMode(int32_t *aDeliveryMode) +{ + NS_ENSURE_ARG_POINTER(aDeliveryMode); + *aDeliveryMode = mDeliveryMode; + return NS_OK; +} +NS_IMETHODIMP nsMsgSendReport::SetDeliveryMode(int32_t aDeliveryMode) +{ + mDeliveryMode = aDeliveryMode; + return NS_OK; +} + +/* void Reset (); */ +NS_IMETHODIMP nsMsgSendReport::Reset() +{ + uint32_t i; + for (i = 0; i <= SEND_LAST_PROCESS; i ++) + if (mProcessReport[i]) + mProcessReport[i]->Reset(); + + mCurrentProcess = 0; + mDeliveryMode = 0; + mAlreadyDisplayReport = false; + + return NS_OK; +} + +/* void setProceeded (in long process, in boolean proceeded); */ +NS_IMETHODIMP nsMsgSendReport::SetProceeded(int32_t process, bool proceeded) +{ + if (process < process_Current || process > SEND_LAST_PROCESS) + return NS_ERROR_ILLEGAL_VALUE; + + if (process == process_Current) + process = mCurrentProcess; + + if (!mProcessReport[process]) + return NS_ERROR_NOT_INITIALIZED; + + return mProcessReport[process]->SetProceeded(proceeded); +} + +/* void setError (in long process, in nsresult error, in boolean overwriteError); */ +NS_IMETHODIMP nsMsgSendReport::SetError(int32_t process, nsresult newError, bool overwriteError) +{ + if (process < process_Current || process > SEND_LAST_PROCESS) + return NS_ERROR_ILLEGAL_VALUE; + + if (process == process_Current) + { + if (mCurrentProcess == process_Current) + // We don't know what we're currently trying to do + return NS_ERROR_ILLEGAL_VALUE; + + process = mCurrentProcess; + } + + if (!mProcessReport[process]) + return NS_ERROR_NOT_INITIALIZED; + + nsresult currError = NS_OK; + mProcessReport[process]->GetError(&currError); + if (overwriteError || NS_SUCCEEDED(currError)) + return mProcessReport[process]->SetError(newError); + else + return NS_OK; +} + +/* void setMessage (in long process, in wstring message, in boolean overwriteMessage); */ +NS_IMETHODIMP nsMsgSendReport::SetMessage(int32_t process, const char16_t *message, bool overwriteMessage) +{ + if (process < process_Current || process > SEND_LAST_PROCESS) + return NS_ERROR_ILLEGAL_VALUE; + + if (process == process_Current) + { + if (mCurrentProcess == process_Current) + // We don't know what we're currently trying to do + return NS_ERROR_ILLEGAL_VALUE; + + process = mCurrentProcess; + } + + if (!mProcessReport[process]) + return NS_ERROR_NOT_INITIALIZED; + + nsString currMessage; + mProcessReport[process]->GetMessage(getter_Copies(currMessage)); + if (overwriteMessage || currMessage.IsEmpty()) + return mProcessReport[process]->SetMessage(message); + else + return NS_OK; +} + +/* nsIMsgProcessReport getProcessReport (in long process); */ +NS_IMETHODIMP nsMsgSendReport::GetProcessReport(int32_t process, nsIMsgProcessReport **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + if (process < process_Current || process > SEND_LAST_PROCESS) + return NS_ERROR_ILLEGAL_VALUE; + + if (process == process_Current) + { + if (mCurrentProcess == process_Current) + // We don't know what we're currently trying to do + return NS_ERROR_ILLEGAL_VALUE; + + process = mCurrentProcess; + } + + NS_IF_ADDREF(*_retval = mProcessReport[process]); + return NS_OK; +} + +/* nsresult displayReport (in nsIPrompt prompt, in boolean showErrorOnly, in boolean dontShowReportTwice); */ +NS_IMETHODIMP nsMsgSendReport::DisplayReport(nsIPrompt *prompt, bool showErrorOnly, bool dontShowReportTwice, nsresult *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + NS_ENSURE_TRUE(mCurrentProcess >= 0 && mCurrentProcess <= SEND_LAST_PROCESS, + NS_ERROR_NOT_INITIALIZED); + + nsresult currError = NS_OK; + mProcessReport[mCurrentProcess]->GetError(&currError); + *_retval = currError; + + if (dontShowReportTwice && mAlreadyDisplayReport) + return NS_OK; + + if (showErrorOnly && NS_SUCCEEDED(currError)) + return NS_OK; + + nsString currMessage; + mProcessReport[mCurrentProcess]->GetMessage(getter_Copies(currMessage)); + + nsresult rv; // don't step on currError. + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(bundle)); + if (NS_FAILED(rv)) + { + //TODO need to display a generic hardcoded message + mAlreadyDisplayReport = true; + return NS_OK; + } + + nsString dialogTitle; + nsString dialogMessage; + + if (NS_SUCCEEDED(currError)) + { + //TODO display a success error message + return NS_OK; + } + + //Do we have an explanation of the error? if no, try to build one... + if (currMessage.IsEmpty()) + { +#ifdef __GNUC__ +// Temporary workaroung until bug 783526 is fixed. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" +#endif + switch (currError) + { + case NS_BINDING_ABORTED: + case NS_ERROR_SEND_FAILED: + case NS_ERROR_SEND_FAILED_BUT_NNTP_OK: + case NS_MSG_FAILED_COPY_OPERATION: + case NS_MSG_UNABLE_TO_SEND_LATER: + case NS_MSG_UNABLE_TO_SAVE_DRAFT: + case NS_MSG_UNABLE_TO_SAVE_TEMPLATE: + //Ignore, don't need to repeat ourself. + break; + default: + const char16_t* errorString = errorStringNameForErrorCode(currError); + nsMsgGetMessageByName(errorString, currMessage); + break; + } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + + if (mDeliveryMode == nsIMsgCompDeliverMode::Now || mDeliveryMode == nsIMsgCompDeliverMode::SendUnsent) + { + // SMTP is taking care of it's own error message and will return NS_ERROR_BUT_DONT_SHOW_ALERT as error code. + // In that case, we must not show an alert ourself. + if (currError == NS_ERROR_BUT_DONT_SHOW_ALERT) + { + mAlreadyDisplayReport = true; + return NS_OK; + } + + bundle->GetStringFromName(u"sendMessageErrorTitle", + getter_Copies(dialogTitle)); + + const char16_t* preStrName = u"sendFailed"; + bool askToGoBackToCompose = false; + switch (mCurrentProcess) + { + case process_BuildMessage : + preStrName = u"sendFailed"; + askToGoBackToCompose = false; + break; + case process_NNTP : + preStrName = u"sendFailed"; + askToGoBackToCompose = false; + break; + case process_SMTP : + bool nntpProceeded; + mProcessReport[process_NNTP]->GetProceeded(&nntpProceeded); + if (nntpProceeded) + preStrName = u"sendFailedButNntpOk"; + else + preStrName = u"sendFailed"; + askToGoBackToCompose = false; + break; + case process_Copy: + preStrName = u"failedCopyOperation"; + askToGoBackToCompose = (mDeliveryMode == nsIMsgCompDeliverMode::Now); + break; + case process_FCC: + preStrName = u"failedCopyOperation"; + askToGoBackToCompose = (mDeliveryMode == nsIMsgCompDeliverMode::Now); + break; + } + bundle->GetStringFromName(preStrName, getter_Copies(dialogMessage)); + + //Do we already have an error message? + if (!askToGoBackToCompose && currMessage.IsEmpty()) + { + //we don't have an error description but we can put a generic explanation + bundle->GetStringFromName(u"genericFailureExplanation", + getter_Copies(currMessage)); + } + + if (!currMessage.IsEmpty()) + { + //Don't need to repeat ourself! + if (!currMessage.Equals(dialogMessage)) + { + if (!dialogMessage.IsEmpty()) + dialogMessage.Append(char16_t('\n')); + dialogMessage.Append(currMessage); + } + } + + if (askToGoBackToCompose) + { + bool oopsGiveMeBackTheComposeWindow = true; + nsString text1; + bundle->GetStringFromName(u"returnToComposeWindowQuestion", + getter_Copies(text1)); + if (!dialogMessage.IsEmpty()) + dialogMessage.AppendLiteral("\n"); + dialogMessage.Append(text1); + nsMsgAskBooleanQuestionByString(prompt, dialogMessage.get(), &oopsGiveMeBackTheComposeWindow, dialogTitle.get()); + if (!oopsGiveMeBackTheComposeWindow) + *_retval = NS_OK; + } + else + nsMsgDisplayMessageByString(prompt, dialogMessage.get(), dialogTitle.get()); + } + else + { + const char16_t* title; + const char16_t* messageName; + + switch (mDeliveryMode) + { + case nsIMsgCompDeliverMode::Later: + title = u"sendLaterErrorTitle"; + messageName = u"unableToSendLater"; + break; + + case nsIMsgCompDeliverMode::AutoSaveAsDraft: + case nsIMsgCompDeliverMode::SaveAsDraft: + title = u"saveDraftErrorTitle"; + messageName = u"unableToSaveDraft"; + break; + + case nsIMsgCompDeliverMode::SaveAsTemplate: + title = u"saveTemplateErrorTitle"; + messageName = u"unableToSaveTemplate"; + break; + + default: + /* This should never happen! */ + title = u"sendMessageErrorTitle"; + messageName = u"sendFailed"; + break; + } + + bundle->GetStringFromName(title, getter_Copies(dialogTitle)); + bundle->GetStringFromName(messageName, + getter_Copies(dialogMessage)); + + //Do we have an error message... + if (currMessage.IsEmpty()) + { + //we don't have an error description but we can put a generic explanation + bundle->GetStringFromName(u"genericFailureExplanation", + getter_Copies(currMessage)); + } + + if (!currMessage.IsEmpty()) + { + if (!dialogMessage.IsEmpty()) + dialogMessage.Append(char16_t('\n')); + dialogMessage.Append(currMessage); + } + nsMsgDisplayMessageByString(prompt, dialogMessage.get(), dialogTitle.get()); + } + + mAlreadyDisplayReport = true; + return NS_OK; +} + diff --git a/mailnews/compose/src/nsMsgSendReport.h b/mailnews/compose/src/nsMsgSendReport.h new file mode 100644 index 000000000..3e344f628 --- /dev/null +++ b/mailnews/compose/src/nsMsgSendReport.h @@ -0,0 +1,46 @@ +/* -*- 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 __nsMsgSendReport_h__ +#define __nsMsgSendReport_h__ + +#include "nsIMsgSendReport.h" +#include "nsStringGlue.h" +#include "nsCOMPtr.h" + +class nsMsgProcessReport : public nsIMsgProcessReport +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGPROCESSREPORT + + nsMsgProcessReport(); + +private: + virtual ~nsMsgProcessReport(); + bool mProceeded; + nsresult mError; + nsString mMessage; +}; + + +class nsMsgSendReport : public nsIMsgSendReport +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGSENDREPORT + + nsMsgSendReport(); + +private: + virtual ~nsMsgSendReport(); + #define SEND_LAST_PROCESS process_FCC + nsCOMPtr<nsIMsgProcessReport> mProcessReport[SEND_LAST_PROCESS + 1]; + int32_t mDeliveryMode; + int32_t mCurrentProcess; + bool mAlreadyDisplayReport; +}; + +#endif diff --git a/mailnews/compose/src/nsSMTPProtocolHandler.js b/mailnews/compose/src/nsSMTPProtocolHandler.js new file mode 100644 index 000000000..9b9e3ead0 --- /dev/null +++ b/mailnews/compose/src/nsSMTPProtocolHandler.js @@ -0,0 +1,62 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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/XPCOMUtils.jsm"); + +var kNetworkProtocolCIDPrefix = "@mozilla.org/network/protocol;1?name="; +var nsIProtocolHandler = Components.interfaces.nsIProtocolHandler; + +function makeProtocolHandler(aProtocol, aDefaultPort, aClassID) { + return { + classID: Components.ID(aClassID), + QueryInterface: XPCOMUtils.generateQI([nsIProtocolHandler]), + + scheme: aProtocol, + defaultPort: aDefaultPort, + protocolFlags: nsIProtocolHandler.URI_NORELATIVE | + nsIProtocolHandler.URI_DANGEROUS_TO_LOAD | + nsIProtocolHandler.URI_NON_PERSISTABLE | + nsIProtocolHandler.ALLOWS_PROXY | + nsIProtocolHandler.URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT, + + newURI: function (aSpec, aOriginCharset, aBaseURI) { + var url = Components.classes["@mozilla.org/messengercompose/smtpurl;1"] + .createInstance(Components.interfaces.nsIURI); + + url.spec = aSpec; + + return url; + }, + + newChannel: function (aURI) { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + newChannel2: function(aURI, aLoadInfo) + { + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; + }, + + allowPort: function (port, scheme) { + return port == aDefaultPort; + } + }; +} + +function nsSMTPProtocolHandler() {} + +nsSMTPProtocolHandler.prototype = + makeProtocolHandler("smtp", + Components.interfaces.nsISmtpUrl.DEFAULT_SMTP_PORT, + "b14c2b67-8680-4c11-8d63-9403c7d4f757"); + +function nsSMTPSProtocolHandler() {} + +nsSMTPSProtocolHandler.prototype = + makeProtocolHandler("smtps", + Components.interfaces.nsISmtpUrl.DEFAULT_SMTPS_PORT, + "057d0997-9e3a-411e-b4ee-2602f53fe05f"); + +var components = [nsSMTPProtocolHandler, nsSMTPSProtocolHandler]; +var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/mailnews/compose/src/nsSMTPProtocolHandler.manifest b/mailnews/compose/src/nsSMTPProtocolHandler.manifest new file mode 100644 index 000000000..4d371163f --- /dev/null +++ b/mailnews/compose/src/nsSMTPProtocolHandler.manifest @@ -0,0 +1,4 @@ +component {b14c2b67-8680-4c11-8d63-9403c7d4f757} nsSMTPProtocolHandler.js +contract @mozilla.org/network/protocol;1?name=smtp {b14c2b67-8680-4c11-8d63-9403c7d4f757} +component {057d0997-9e3a-411e-b4ee-2602f53fe05f} nsSMTPProtocolHandler.js +contract @mozilla.org/network/protocol;1?name=smtps {057d0997-9e3a-411e-b4ee-2602f53fe05f} diff --git a/mailnews/compose/src/nsSmtpProtocol.cpp b/mailnews/compose/src/nsSmtpProtocol.cpp new file mode 100644 index 000000000..54d1e6f64 --- /dev/null +++ b/mailnews/compose/src/nsSmtpProtocol.cpp @@ -0,0 +1,2275 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" +#include "nsSmtpProtocol.h" +#include "nscore.h" +#include "nsIStreamListener.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsISocketTransport.h" +#include "nsIMsgMailNewsUrl.h" +#include "nsMsgBaseCID.h" +#include "nsMsgCompCID.h" +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsStringGlue.h" +#include "nsTextFormatter.h" +#include "nsIMsgIdentity.h" +#include "nsISmtpServer.h" +#include "prtime.h" +#include "mozilla/Logging.h" +#include "prerror.h" +#include "prprf.h" +#include "prmem.h" +#include "plbase64.h" +#include "prnetdb.h" +#include "prsystem.h" +#include "nsMsgUtils.h" +#include "nsIPipe.h" +#include "nsNetUtil.h" +#include "nsIPrefService.h" +#include "nsISSLSocketControl.h" +#include "nsComposeStrings.h" +#include "nsIStringBundle.h" +#include "nsMsgCompUtils.h" +#include "nsIMsgWindow.h" +#include "MailNewsTypes2.h" // for nsMsgSocketType and nsMsgAuthMethod +#include "nsIIDNService.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "mozilla/Services.h" +#include "mozilla/Attributes.h" +#include "nsINetAddr.h" +#include "nsIProxyInfo.h" + +#ifndef XP_UNIX +#include <stdarg.h> +#endif /* !XP_UNIX */ + +#undef PostMessage // avoid to collision with WinUser.h + +static PRLogModuleInfo *SMTPLogModule = nullptr; + +using namespace mozilla::mailnews; + +/* the output_buffer_size must be larger than the largest possible line + * 2000 seems good for news + * + * jwz: I increased this to 4k since it must be big enough to hold the + * entire button-bar HTML, and with the new "mailto" format, that can + * contain arbitrarily long header fields like "references". + * + * fortezza: proxy auth is huge, buffer increased to 8k (sigh). + */ +#define OUTPUT_BUFFER_SIZE (4096*2) + +//////////////////////////////////////////////////////////////////////////////////////////// +// TEMPORARY HARD CODED FUNCTIONS +/////////////////////////////////////////////////////////////////////////////////////////// + +/* based on in NET_ExplainErrorDetails in mkmessag.c */ +nsresult nsExplainErrorDetails(nsISmtpUrl * aSmtpUrl, nsresult aCode, ...) +{ + NS_ENSURE_ARG(aSmtpUrl); + + va_list args; + + nsCOMPtr<nsIPrompt> dialog; + aSmtpUrl->GetPrompt(getter_AddRefs(dialog)); + NS_ENSURE_TRUE(dialog, NS_ERROR_FAILURE); + + char16_t * msg; + nsString eMsg; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + nsresult rv = bundleService->CreateBundle( + "chrome://messenger/locale/messengercompose/composeMsgs.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + va_start (args, aCode); + + const char16_t* exitString; +#ifdef __GNUC__ +// Temporary workaroung until bug 783526 is fixed. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch" +#endif + switch (aCode) + { + case NS_ERROR_ILLEGAL_LOCALPART: + bundle->GetStringFromName(u"errorIllegalLocalPart", + getter_Copies(eMsg)); + msg = nsTextFormatter::vsmprintf(eMsg.get(), args); + break; + case NS_ERROR_SMTP_SERVER_ERROR: + case NS_ERROR_TCP_READ_ERROR: + case NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED: + case NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_1: + case NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2: + case NS_ERROR_SENDING_FROM_COMMAND: + case NS_ERROR_SENDING_RCPT_COMMAND: + case NS_ERROR_SENDING_DATA_COMMAND: + case NS_ERROR_SENDING_MESSAGE: + case NS_ERROR_SMTP_GREETING: + exitString = errorStringNameForErrorCode(aCode); + bundle->GetStringFromName(exitString, getter_Copies(eMsg)); + msg = nsTextFormatter::vsmprintf(eMsg.get(), args); + break; + default: + NS_WARNING("falling to default error code"); + bundle->GetStringFromName(u"communicationsError", getter_Copies(eMsg)); + msg = nsTextFormatter::smprintf(eMsg.get(), aCode); + break; + } +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + + if (msg) + { + rv = dialog->Alert(nullptr, msg); + nsTextFormatter::smprintf_free(msg); + } + + va_end (args); + + return rv; +} + +/* RFC 1891 -- extended smtp value encoding scheme + + 5. Additional parameters for RCPT and MAIL commands + + The extended RCPT and MAIL commands are issued by a client when it wishes to request a DSN from the + server, under certain conditions, for a particular recipient. The extended RCPT and MAIL commands are + identical to the RCPT and MAIL commands defined in [1], except that one or more of the following parameters + appear after the sender or recipient address, respectively. The general syntax for extended SMTP commands is + defined in [4]. + + NOTE: Although RFC 822 ABNF is used to describe the syntax of these parameters, they are not, in the + language of that document, "structured field bodies". Therefore, while parentheses MAY appear within an + emstp-value, they are not recognized as comment delimiters. + + The syntax for "esmtp-value" in [4] does not allow SP, "=", control characters, or characters outside the + traditional ASCII range of 1- 127 decimal to be transmitted in an esmtp-value. Because the ENVID and + ORCPT parameters may need to convey values outside this range, the esmtp-values for these parameters are + encoded as "xtext". "xtext" is formally defined as follows: + + xtext = *( xchar / hexchar ) + + xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive, except for "+" and "=". + + ; "hexchar"s are intended to encode octets that cannot appear + ; as ASCII characters within an esmtp-value. + + hexchar = ASCII "+" immediately followed by two upper case hexadecimal digits + + When encoding an octet sequence as xtext: + + + Any ASCII CHAR between "!" and "~" inclusive, except for "+" and "=", + MAY be encoded as itself. (A CHAR in this range MAY instead be encoded as a "hexchar", at the + implementor's discretion.) + + + ASCII CHARs that fall outside the range above must be encoded as + "hexchar". + + */ +/* caller must free the return buffer */ +static char * +esmtp_value_encode(const char *addr) +{ + char *buffer = (char *) PR_Malloc(512); /* esmtp ORCPT allow up to 500 chars encoded addresses */ + char *bp = buffer, *bpEnd = buffer+500; + int len, i; + + if (!buffer) return NULL; + + *bp=0; + if (! addr || *addr == 0) /* this will never happen */ + return buffer; + + for (i=0, len=PL_strlen(addr); i < len && bp < bpEnd; i++) + { + if (*addr >= 0x21 && + *addr <= 0x7E && + *addr != '+' && + *addr != '=') + { + *bp++ = *addr++; + } + else + { + PR_snprintf(bp, bpEnd-bp, "+%.2X", ((int)*addr++)); + bp += PL_strlen(bp); + } + } + *bp=0; + return buffer; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// END OF TEMPORARY HARD CODED FUNCTIONS +/////////////////////////////////////////////////////////////////////////////////////////// + +#ifdef MOZ_MAILNEWS_OAUTH2 +NS_IMPL_ISUPPORTS_INHERITED(nsSmtpProtocol, nsMsgAsyncWriteProtocol, + msgIOAuth2ModuleListener) +#else +NS_IMPL_ADDREF_INHERITED(nsSmtpProtocol, nsMsgAsyncWriteProtocol) +NS_IMPL_RELEASE_INHERITED(nsSmtpProtocol, nsMsgAsyncWriteProtocol) + +NS_INTERFACE_MAP_BEGIN(nsSmtpProtocol) +NS_INTERFACE_MAP_END_INHERITING(nsMsgAsyncWriteProtocol) +// TODO: See if we can use NS_IMPL_ISUPPORTS_INHERITED: https://hg.mozilla.org/comm-central/diff/eae1195fde6d/mailnews/compose/src/nsSmtpProtocol.cpp +#endif +nsSmtpProtocol::nsSmtpProtocol(nsIURI * aURL) + : nsMsgAsyncWriteProtocol(aURL) +{ +} + +nsSmtpProtocol::~nsSmtpProtocol() +{ + // free our local state + PR_Free(m_dataBuf); + delete m_lineStreamBuffer; +} + +void nsSmtpProtocol::Initialize(nsIURI * aURL) +{ + NS_PRECONDITION(aURL, "invalid URL passed into Smtp Protocol"); + nsresult rv = NS_OK; + + m_flags = 0; + m_prefAuthMethods = 0; + m_failedAuthMethods = 0; + m_currentAuthMethod = 0; + m_usernamePrompted = false; + m_prefSocketType = nsMsgSocketType::trySTARTTLS; + m_tlsInitiated = false; + + m_urlErrorState = NS_ERROR_FAILURE; + + if (!SMTPLogModule) + SMTPLogModule = PR_NewLogModule("SMTP"); + + if (aURL) + m_runningURL = do_QueryInterface(aURL); + + // extract out message feedback if there is any. + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aURL); + if (mailnewsUrl) + mailnewsUrl->GetStatusFeedback(getter_AddRefs(m_statusFeedback)); + + m_dataBuf = (char *) PR_Malloc(sizeof(char) * OUTPUT_BUFFER_SIZE); + m_dataBufSize = OUTPUT_BUFFER_SIZE; + + m_nextState = SMTP_START_CONNECT; + m_nextStateAfterResponse = SMTP_START_CONNECT; + m_responseCode = 0; + m_previousResponseCode = 0; + m_continuationResponse = -1; + m_tlsEnabled = false; + m_addressesLeft = 0; + + m_sendDone = false; + + m_sizelimit = 0; + m_totalMessageSize = 0; + nsCOMPtr<nsIFile> file; + m_runningURL->GetPostMessageFile(getter_AddRefs(file)); + if (file) + file->GetFileSize(&m_totalMessageSize); + + m_originalContentLength = 0; + m_totalAmountRead = 0; + + m_lineStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true); + // ** may want to consider caching the server capability to save lots of + // round trip communication between the client and server + int32_t authMethod = 0; + nsCOMPtr<nsISmtpServer> smtpServer; + m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer)); + if (smtpServer) { + smtpServer->GetAuthMethod(&authMethod); + smtpServer->GetSocketType(&m_prefSocketType); + smtpServer->GetHelloArgument(getter_Copies(m_helloArgument)); + +#ifdef MOZ_MAILNEWS_OAUTH2 + // Query for OAuth2 support. If the SMTP server preferences don't allow + // for OAuth2, then don't carry around the OAuth2 module any longer + // since we won't need it. + mOAuth2Support = do_CreateInstance(MSGIOAUTH2MODULE_CONTRACTID); + if (mOAuth2Support) + { + bool supportsOAuth = false; + mOAuth2Support->InitFromSmtp(smtpServer, &supportsOAuth); + if (!supportsOAuth) + mOAuth2Support = nullptr; + } +#endif + } + InitPrefAuthMethods(authMethod); + + nsAutoCString hostName; + int32_t port = 0; + + aURL->GetPort(&port); + aURL->GetAsciiHost(hostName); + + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("SMTP Connecting to: %s", hostName.get())); + + // When we are making a secure connection, we need to make sure that we + // pass an interface requestor down to the socket transport so that PSM can + // retrieve a nsIPrompt instance if needed. + nsCOMPtr<nsIInterfaceRequestor> callbacks; + nsCOMPtr<nsISmtpUrl> smtpUrl(do_QueryInterface(aURL)); + if (smtpUrl) + smtpUrl->GetNotificationCallbacks(getter_AddRefs(callbacks)); + + nsCOMPtr<nsIProxyInfo> proxyInfo; + rv = MsgExamineForProxy(this, getter_AddRefs(proxyInfo)); + if (NS_FAILED(rv)) proxyInfo = nullptr; + + if (m_prefSocketType == nsMsgSocketType::SSL) + rv = OpenNetworkSocketWithInfo(hostName.get(), port, "ssl", proxyInfo, + callbacks); + else if (m_prefSocketType != nsMsgSocketType::plain) + { + rv = OpenNetworkSocketWithInfo(hostName.get(), port, "starttls", + proxyInfo, callbacks); + if (NS_FAILED(rv) && m_prefSocketType == nsMsgSocketType::trySTARTTLS) + { + m_prefSocketType = nsMsgSocketType::plain; + rv = OpenNetworkSocketWithInfo(hostName.get(), port, nullptr, + proxyInfo, callbacks); + } + } + else + rv = OpenNetworkSocketWithInfo(hostName.get(), port, nullptr, proxyInfo, + callbacks); +} + +void nsSmtpProtocol::AppendHelloArgument(nsACString& aResult) +{ + nsresult rv; + + // is a custom EHLO/HELO argument configured for the transport to be used? + if (!m_helloArgument.IsEmpty()) + { + aResult += m_helloArgument; + } + else + { + // is a FQDN known for this system? + char hostName[256]; + PR_GetSystemInfo(PR_SI_HOSTNAME_UNTRUNCATED, hostName, sizeof hostName); + if ((hostName[0] != '\0') && (strchr(hostName, '.') != NULL)) + { + nsDependentCString cleanedHostName(hostName); + // avoid problems with hostnames containing newlines/whitespace + cleanedHostName.StripWhitespace(); + aResult += cleanedHostName; + } + else + { + nsCOMPtr<nsINetAddr> iaddr; // IP address for this connection + // our transport is always a nsISocketTransport + nsCOMPtr<nsISocketTransport> socketTransport = do_QueryInterface(m_transport); + // should return the interface ip of the SMTP connection + // minimum case - see bug 68877 and RFC 2821, chapter 4.1.1.1 + rv = socketTransport->GetScriptableSelfAddr(getter_AddRefs(iaddr)); + + if (NS_SUCCEEDED(rv)) + { + // turn it into a string + nsCString ipAddressString; + rv = iaddr->GetAddress(ipAddressString); + if (NS_SUCCEEDED(rv)) + { +#ifdef DEBUG + bool v4mapped = false; + iaddr->GetIsV4Mapped(&v4mapped); + NS_ASSERTION(!v4mapped, + "unexpected IPv4-mapped IPv6 address"); +#endif + + uint16_t family = nsINetAddr::FAMILY_INET; + iaddr->GetFamily(&family); + + if (family == nsINetAddr::FAMILY_INET6) // IPv6 style address? + aResult.AppendLiteral("[IPv6:"); + else + aResult.AppendLiteral("["); + + aResult.Append(ipAddressString); + aResult.Append(']'); + } + } + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////////// +// we suppport the nsIStreamListener interface +//////////////////////////////////////////////////////////////////////////////////////////// + +// stop binding is a "notification" informing us that the stream +// associated with aURL is going away. +NS_IMETHODIMP nsSmtpProtocol::OnStopRequest(nsIRequest *request, nsISupports *ctxt, + nsresult aStatus) +{ + bool connDroppedDuringAuth = NS_SUCCEEDED(aStatus) && !m_sendDone && + (m_nextStateAfterResponse == SMTP_AUTH_LOGIN_STEP0_RESPONSE || + m_nextStateAfterResponse == SMTP_AUTH_LOGIN_RESPONSE); + // ignore errors handling the QUIT command so fcc can continue. + if (m_sendDone && NS_FAILED(aStatus)) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, + ("SMTP connection error quitting %lx, ignoring ", aStatus)); + aStatus = NS_OK; + } + if (NS_SUCCEEDED(aStatus) && !m_sendDone) { + // if we are getting OnStopRequest() with NS_OK, + // but we haven't finished clean, that's spells trouble. + // it means that the server has dropped us before we could send the whole mail + // for example, see bug #200647 + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, + ("SMTP connection dropped after %ld total bytes read", m_totalAmountRead)); + if (!connDroppedDuringAuth) + nsMsgAsyncWriteProtocol::OnStopRequest(nullptr, ctxt, NS_ERROR_NET_INTERRUPT); + } + else + nsMsgAsyncWriteProtocol::OnStopRequest(nullptr, ctxt, aStatus); + + // okay, we've been told that the send is done and the connection is going away. So + // we need to release all of our state + nsresult rv = nsMsgAsyncWriteProtocol::CloseSocket(); + // If the server dropped the connection when we were expecting + // a login response, reprompt for password, and if the user asks, + // retry the url. + if (connDroppedDuringAuth) + { + nsCOMPtr<nsIURI> runningURI = do_QueryInterface(m_runningURL); + nsresult rv = AuthLoginResponse(nullptr, 0); + if (NS_FAILED(rv)) + return rv; + return LoadUrl(runningURI, ctxt); + } + + return rv; +} + +///////////////////////////////////////////////////////////////////////////////////////////// +// End of nsIStreamListenerSupport +////////////////////////////////////////////////////////////////////////////////////////////// + +void nsSmtpProtocol::UpdateStatus(const char16_t* aStatusName) +{ + if (m_statusFeedback) + { + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (!bundleService) return; + nsCOMPtr<nsIStringBundle> bundle; + nsresult rv = bundleService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(bundle)); + if (NS_FAILED(rv)) return; + nsString msg; + bundle->GetStringFromName(aStatusName, getter_Copies(msg)); + UpdateStatusWithString(msg.get()); + } +} + +void nsSmtpProtocol::UpdateStatusWithString(const char16_t * aStatusString) +{ + if (m_statusFeedback && aStatusString) + m_statusFeedback->ShowStatusString(nsDependentString(aStatusString)); +} + +///////////////////////////////////////////////////////////////////////////////////////////// +// Begin protocol state machine functions... +////////////////////////////////////////////////////////////////////////////////////////////// + +/* + * gets the response code from the SMTP server and the + * response line + */ +nsresult nsSmtpProtocol::SmtpResponse(nsIInputStream * inputStream, uint32_t length) +{ + char * line = nullptr; + char cont_char; + uint32_t ln = 0; + bool pauseForMoreData = false; + + if (!m_lineStreamBuffer) + // this will force an error and at least we won't crash + return NS_ERROR_NULL_POINTER; + + line = m_lineStreamBuffer->ReadNextLine(inputStream, ln, pauseForMoreData); + + if (pauseForMoreData || !line) + { + SetFlag(SMTP_PAUSE_FOR_READ); /* pause */ + PR_Free(line); + return NS_OK; + } + + m_totalAmountRead += ln; + + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("SMTP Response: %s", line)); + cont_char = ' '; /* default */ + // sscanf() doesn't update m_responseCode if line doesn't start + // with a number. That can be dangerous. So be sure to set + // m_responseCode to 0 if no items read. + if (PR_sscanf(line, "%d%c", &m_responseCode, &cont_char) <= 0) + m_responseCode = 0; + + if (m_continuationResponse == -1) + { + if (cont_char == '-') /* begin continuation */ + m_continuationResponse = m_responseCode; + + // display the whole message if no valid response code or + // message shorter than 4 chars + m_responseText = (m_responseCode >= 100 && PL_strlen(line) > 3) ? line + 4 : line; + } + else + { /* have to continue */ + if (m_continuationResponse == m_responseCode && cont_char == ' ') + m_continuationResponse = -1; /* ended */ + + if (m_responseText.IsEmpty() || m_responseText.Last() != '\n') + m_responseText += "\n"; + + m_responseText += (PL_strlen(line) > 3) ? line + 4 : line; + } + + if (m_responseCode == 220 && m_responseText.Length() && !m_tlsInitiated && + !m_sendDone) + m_nextStateAfterResponse = SMTP_EXTN_LOGIN_RESPONSE; + + if (m_continuationResponse == -1) /* all done with this response? */ + { + m_nextState = m_nextStateAfterResponse; + ClearFlag(SMTP_PAUSE_FOR_READ); /* don't pause */ + } + + PR_Free(line); + return NS_OK; +} + +nsresult nsSmtpProtocol::ExtensionLoginResponse(nsIInputStream * inputStream, uint32_t length) +{ + nsresult status = NS_OK; + + if (m_responseCode != 220) + { +#ifdef DEBUG + nsresult rv = +#endif + nsExplainErrorDetails(m_runningURL, NS_ERROR_SMTP_GREETING, + m_responseText.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return NS_ERROR_SMTP_AUTH_FAILURE; + } + + nsAutoCString buffer("EHLO "); + AppendHelloArgument(buffer); + buffer += CRLF; + + status = SendData(buffer.get()); + + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_SEND_EHLO_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + + return(status); +} + +nsresult nsSmtpProtocol::SendHeloResponse(nsIInputStream * inputStream, uint32_t length) +{ + nsresult status = NS_OK; + nsAutoCString buffer; + nsresult rv; + + if (m_responseCode != 250) + { +#ifdef DEBUG + rv = +#endif + nsExplainErrorDetails(m_runningURL, NS_ERROR_SMTP_SERVER_ERROR, + m_responseText.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return NS_ERROR_SMTP_AUTH_FAILURE; + } + + // check if we're just verifying the ability to logon + nsCOMPtr<nsISmtpUrl> smtpUrl = do_QueryInterface(m_runningURL, &rv); + NS_ENSURE_SUCCESS(rv, rv); + bool verifyingLogon = false; + smtpUrl->GetVerifyLogon(&verifyingLogon); + if (verifyingLogon) + return SendQuit(); + + // extract the email address from the identity + nsCString emailAddress; + nsCOMPtr <nsIMsgIdentity> senderIdentity; + rv = m_runningURL->GetSenderIdentity(getter_AddRefs(senderIdentity)); + if (NS_FAILED(rv) || !senderIdentity) + { + m_urlErrorState = NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS; + return(NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS); + } + senderIdentity->GetEmail(emailAddress); + + if (emailAddress.IsEmpty()) + { + m_urlErrorState = NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS; + return(NS_ERROR_COULD_NOT_GET_USERS_MAIL_ADDRESS); + } + + nsCString fullAddress; + // Quote the email address before passing it to the SMTP server. + MakeMimeAddress(EmptyCString(), emailAddress, fullAddress); + + buffer = "MAIL FROM:<"; + buffer += fullAddress; + buffer += ">"; + + nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + if (TestFlag(SMTP_EHLO_DSN_ENABLED)) + { + bool requestDSN = false; + rv = m_runningURL->GetRequestDSN(&requestDSN); + + if (requestDSN) + { + bool requestRetFull = false; + rv = prefBranch->GetBoolPref("mail.dsn.ret_full_on", &requestRetFull); + + buffer += requestRetFull ? " RET=FULL" : " RET=HDRS"; + + nsCString dsnEnvid; + + // get the envid from the smtpUrl + rv = m_runningURL->GetDsnEnvid(dsnEnvid); + + if (dsnEnvid.IsEmpty()) + dsnEnvid.Adopt(msg_generate_message_id(senderIdentity)); + + buffer += " ENVID="; + buffer += dsnEnvid; + } + } + + if (TestFlag(SMTP_EHLO_8BIT_ENABLED)) + { + bool strictlyMime = false; + rv = prefBranch->GetBoolPref("mail.strictly_mime", &strictlyMime); + + if (!strictlyMime) + buffer.Append(" BODY=8BITMIME"); + } + + if (TestFlag(SMTP_EHLO_SIZE_ENABLED)) + { + buffer.Append(" SIZE="); + buffer.AppendInt(m_totalMessageSize); + } + buffer += CRLF; + + status = SendData(buffer.get()); + + m_nextState = SMTP_RESPONSE; + + + m_nextStateAfterResponse = SMTP_SEND_MAIL_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + + return(status); +} + +nsresult nsSmtpProtocol::SendEhloResponse(nsIInputStream * inputStream, uint32_t length) +{ + nsresult status = NS_OK; + + if (m_responseCode != 250) + { + /* EHLO must not be implemented by the server, so fall back to the HELO case + * if command is unrecognized or unimplemented. + */ + if (m_responseCode == 500 || m_responseCode == 502) + { + /* If STARTTLS is requested by the user, EHLO is required to advertise it. + * But only if TLS handshake is not already accomplished. + */ + if (m_prefSocketType == nsMsgSocketType::alwaysSTARTTLS && + !m_tlsEnabled) + { + m_nextState = SMTP_ERROR_DONE; + m_urlErrorState = NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS; + return(NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS); + } + + nsAutoCString buffer("HELO "); + AppendHelloArgument(buffer); + buffer += CRLF; + + status = SendData(buffer.get()); + + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_SEND_HELO_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + return (status); + } + /* e.g. getting 421 "Server says unauthorized, bye" or + * 501 "Syntax error in EHLOs parameters or arguments" + */ + else + { +#ifdef DEBUG + nsresult rv = +#endif + nsExplainErrorDetails(m_runningURL, NS_ERROR_SMTP_SERVER_ERROR, + m_responseText.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return NS_ERROR_SMTP_AUTH_FAILURE; + } + } + + int32_t responseLength = m_responseText.Length(); + int32_t startPos = 0; + int32_t endPos; + do + { + endPos = m_responseText.FindChar('\n', startPos + 1); + nsAutoCString responseLine; + responseLine.Assign(Substring(m_responseText, startPos, + (endPos >= 0 ? endPos : responseLength) - startPos)); + + MsgCompressWhitespace(responseLine); + if (responseLine.LowerCaseEqualsLiteral("starttls")) + { + SetFlag(SMTP_EHLO_STARTTLS_ENABLED); + } + else if (responseLine.LowerCaseEqualsLiteral("dsn")) + { + SetFlag(SMTP_EHLO_DSN_ENABLED); + } + else if (StringBeginsWith(responseLine, NS_LITERAL_CSTRING("AUTH"), nsCaseInsensitiveCStringComparator())) + { + SetFlag(SMTP_AUTH); + + if (responseLine.Find(NS_LITERAL_CSTRING("GSSAPI"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_GSSAPI_ENABLED); + + if (responseLine.Find(NS_LITERAL_CSTRING("CRAM-MD5"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_CRAM_MD5_ENABLED); + + if (responseLine.Find(NS_LITERAL_CSTRING("NTLM"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_NTLM_ENABLED); + + if (responseLine.Find(NS_LITERAL_CSTRING("MSN"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_MSN_ENABLED); + + if (responseLine.Find(NS_LITERAL_CSTRING("PLAIN"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_PLAIN_ENABLED); + + if (responseLine.Find(NS_LITERAL_CSTRING("LOGIN"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_LOGIN_ENABLED); + + if (responseLine.Find(NS_LITERAL_CSTRING("EXTERNAL"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_EXTERNAL_ENABLED); + +#ifdef MOZ_MAILNEWS_OAUTH2 + if (responseLine.Find(NS_LITERAL_CSTRING("XOAUTH2"), + CaseInsensitiveCompare) >= 0) + SetFlag(SMTP_AUTH_OAUTH2_ENABLED); +#endif + } + else if (StringBeginsWith(responseLine, NS_LITERAL_CSTRING("SIZE"), nsCaseInsensitiveCStringComparator())) + { + SetFlag(SMTP_EHLO_SIZE_ENABLED); + + m_sizelimit = atol((responseLine.get()) + 4); + } + else if (StringBeginsWith(responseLine, NS_LITERAL_CSTRING("8BITMIME"), nsCaseInsensitiveCStringComparator())) + { + SetFlag(SMTP_EHLO_8BIT_ENABLED); + } + + startPos = endPos + 1; + } while (endPos >= 0); + + if (TestFlag(SMTP_EHLO_SIZE_ENABLED) && + m_sizelimit > 0 && (int32_t)m_totalMessageSize > m_sizelimit) + { +#ifdef DEBUG + nsresult rv = +#endif + nsExplainErrorDetails(m_runningURL, + NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_1, m_sizelimit); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return(NS_ERROR_SENDING_FROM_COMMAND); + } + + m_nextState = SMTP_AUTH_PROCESS_STATE; + return status; +} + + +nsresult nsSmtpProtocol::SendTLSResponse() +{ + // only tear down our existing connection and open a new one if we received a 220 response + // from the smtp server after we issued the STARTTLS + nsresult rv = NS_OK; + if (m_responseCode == 220) + { + nsCOMPtr<nsISupports> secInfo; + nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport, &rv); + if (NS_FAILED(rv)) return rv; + + rv = strans->GetSecurityInfo(getter_AddRefs(secInfo)); + + if (NS_SUCCEEDED(rv) && secInfo) { + nsCOMPtr<nsISSLSocketControl> sslControl = do_QueryInterface(secInfo, &rv); + + if (NS_SUCCEEDED(rv) && sslControl) + rv = sslControl->StartTLS(); + } + + if (NS_SUCCEEDED(rv)) + { + m_nextState = SMTP_EXTN_LOGIN_RESPONSE; + m_nextStateAfterResponse = SMTP_EXTN_LOGIN_RESPONSE; + m_tlsEnabled = true; + m_flags = 0; // resetting the flags + return rv; + } + } + + ClearFlag(SMTP_EHLO_STARTTLS_ENABLED); + m_tlsInitiated = false; + m_nextState = SMTP_AUTH_PROCESS_STATE; + + return rv; +} + +void nsSmtpProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue) +{ + // for m_prefAuthMethods, using the same flags as server capablities. + switch (authMethodPrefValue) + { + case nsMsgAuthMethod::none: + m_prefAuthMethods = SMTP_AUTH_NONE_ENABLED; + break; + //case nsMsgAuthMethod::old -- no such thing for SMTP + case nsMsgAuthMethod::passwordCleartext: + m_prefAuthMethods = SMTP_AUTH_LOGIN_ENABLED | + SMTP_AUTH_PLAIN_ENABLED; + break; + case nsMsgAuthMethod::passwordEncrypted: + m_prefAuthMethods = SMTP_AUTH_CRAM_MD5_ENABLED; + break; + case nsMsgAuthMethod::NTLM: + m_prefAuthMethods = SMTP_AUTH_NTLM_ENABLED | + SMTP_AUTH_MSN_ENABLED; + break; + case nsMsgAuthMethod::GSSAPI: + m_prefAuthMethods = SMTP_AUTH_GSSAPI_ENABLED; + break; +#ifdef MOZ_MAILNEWS_OAUTH2 + case nsMsgAuthMethod::OAuth2: + m_prefAuthMethods = SMTP_AUTH_OAUTH2_ENABLED; + break; +#endif + case nsMsgAuthMethod::secure: + m_prefAuthMethods = SMTP_AUTH_CRAM_MD5_ENABLED | + SMTP_AUTH_GSSAPI_ENABLED | + SMTP_AUTH_NTLM_ENABLED | SMTP_AUTH_MSN_ENABLED | + SMTP_AUTH_EXTERNAL_ENABLED; // TODO: Expose EXTERNAL? How? + break; + default: + NS_ASSERTION(false, "SMTP: authMethod pref invalid"); + // TODO log to error console + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, + ("SMTP: bad pref authMethod = %d\n", authMethodPrefValue)); + // fall to any + MOZ_FALLTHROUGH; + case nsMsgAuthMethod::anything: + m_prefAuthMethods = + SMTP_AUTH_LOGIN_ENABLED | SMTP_AUTH_PLAIN_ENABLED | + SMTP_AUTH_CRAM_MD5_ENABLED | SMTP_AUTH_GSSAPI_ENABLED | + SMTP_AUTH_NTLM_ENABLED | SMTP_AUTH_MSN_ENABLED | +#ifdef MOZ_MAILNEWS_OAUTH2 + SMTP_AUTH_OAUTH2_ENABLED | +#endif + SMTP_AUTH_EXTERNAL_ENABLED; + break; + } + +#ifdef MOZ_MAILNEWS_OAUTH2 + // Only enable OAuth2 support if we can do the lookup. + if ((m_prefAuthMethods & SMTP_AUTH_OAUTH2_ENABLED) && !mOAuth2Support) + m_prefAuthMethods &= ~SMTP_AUTH_OAUTH2_ENABLED; +#endif + + NS_ASSERTION(m_prefAuthMethods != 0, "SMTP:InitPrefAuthMethods() failed"); +} + +/** + * Changes m_currentAuthMethod to pick the next-best one + * which is allowed by server and prefs and not marked failed. + * The order of preference and trying of auth methods is encoded here. + */ +nsresult nsSmtpProtocol::ChooseAuthMethod() +{ + int32_t serverCaps = m_flags; // from nsMsgProtocol::TestFlag() + int32_t availCaps = serverCaps & m_prefAuthMethods & ~m_failedAuthMethods; + + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, + ("SMTP auth: server caps 0x%X, pref 0x%X, failed 0x%X, avail caps 0x%X", + serverCaps, m_prefAuthMethods, m_failedAuthMethods, availCaps)); + MOZ_LOG(SMTPLogModule, mozilla::LogLevel:: Debug, + ("(GSSAPI = 0x%X, CRAM = 0x%X, NTLM = 0x%X, " + "MSN = 0x%X, PLAIN = 0x%X, LOGIN = 0x%X, EXTERNAL = 0x%X)", + SMTP_AUTH_GSSAPI_ENABLED, SMTP_AUTH_CRAM_MD5_ENABLED, + SMTP_AUTH_NTLM_ENABLED, SMTP_AUTH_MSN_ENABLED, SMTP_AUTH_PLAIN_ENABLED, + SMTP_AUTH_LOGIN_ENABLED, SMTP_AUTH_EXTERNAL_ENABLED)); + + if (SMTP_AUTH_GSSAPI_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_GSSAPI_ENABLED; + else if (SMTP_AUTH_CRAM_MD5_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_CRAM_MD5_ENABLED; + else if (SMTP_AUTH_NTLM_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_NTLM_ENABLED; + else if (SMTP_AUTH_MSN_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_MSN_ENABLED; +#ifdef MOZ_MAILNEWS_OAUTH2 + else if (SMTP_AUTH_OAUTH2_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_OAUTH2_ENABLED; +#endif + else if (SMTP_AUTH_PLAIN_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_PLAIN_ENABLED; + else if (SMTP_AUTH_LOGIN_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_LOGIN_ENABLED; + else if (SMTP_AUTH_EXTERNAL_ENABLED & availCaps) + m_currentAuthMethod = SMTP_AUTH_EXTERNAL_ENABLED; + else + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("no auth method remaining")); + m_currentAuthMethod = 0; + return NS_ERROR_SMTP_AUTH_FAILURE; + } + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("trying auth method 0x%X", m_currentAuthMethod)); + return NS_OK; +} + +void nsSmtpProtocol::MarkAuthMethodAsFailed(int32_t failedAuthMethod) +{ + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, + ("marking auth method 0x%X failed", failedAuthMethod)); + m_failedAuthMethods |= failedAuthMethod; +} + +/** + * Start over, trying all auth methods again + */ +void nsSmtpProtocol::ResetAuthMethods() +{ + m_currentAuthMethod = 0; + m_failedAuthMethods = 0; +} + +nsresult nsSmtpProtocol::ProcessAuth() +{ + nsresult status = NS_OK; + nsAutoCString buffer; + + if (!m_tlsEnabled) + { + if (TestFlag(SMTP_EHLO_STARTTLS_ENABLED)) + { + // Do not try to combine SMTPS with STARTTLS. + // If nsMsgSocketType::SSL is set, + // we are alrady using a secure connection. + // Do not attempt to do STARTTLS, + // even if server offers it. + if (m_prefSocketType == nsMsgSocketType::trySTARTTLS || + m_prefSocketType == nsMsgSocketType::alwaysSTARTTLS) + { + buffer = "STARTTLS"; + buffer += CRLF; + + status = SendData(buffer.get()); + + m_tlsInitiated = true; + + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_TLS_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + return status; + } + } + else if (m_prefSocketType == nsMsgSocketType::alwaysSTARTTLS) + { + m_nextState = SMTP_ERROR_DONE; + m_urlErrorState = NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS; + return NS_ERROR_STARTTLS_FAILED_EHLO_STARTTLS; + } + } + // (wrong indention until here) + + (void) ChooseAuthMethod(); // advance m_currentAuthMethod + + // We don't need to auth, per pref, or the server doesn't advertise AUTH, + // so skip auth and try to send message. + if (m_prefAuthMethods == SMTP_AUTH_NONE_ENABLED || !TestFlag(SMTP_AUTH)) + { + m_nextState = SMTP_SEND_HELO_RESPONSE; + // fake to 250 because SendHeloResponse() tests for this + m_responseCode = 250; + } + else if (m_currentAuthMethod == SMTP_AUTH_EXTERNAL_ENABLED) + { + buffer = "AUTH EXTERNAL ="; + buffer += CRLF; + SendData(buffer.get()); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_AUTH_EXTERNAL_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + return NS_OK; + } + else if (m_currentAuthMethod == SMTP_AUTH_GSSAPI_ENABLED) + { + m_nextState = SMTP_SEND_AUTH_GSSAPI_FIRST; + } + else if (m_currentAuthMethod == SMTP_AUTH_CRAM_MD5_ENABLED || + m_currentAuthMethod == SMTP_AUTH_PLAIN_ENABLED || + m_currentAuthMethod == SMTP_AUTH_NTLM_ENABLED) + { + m_nextState = SMTP_SEND_AUTH_LOGIN_STEP1; + } + else if (m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED || + m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED) + { + m_nextState = SMTP_SEND_AUTH_LOGIN_STEP0; + } +#ifdef MOZ_MAILNEWS_OAUTH2 + else if (m_currentAuthMethod == SMTP_AUTH_OAUTH2_ENABLED) + { + m_nextState = SMTP_AUTH_OAUTH2_STEP; + } +#endif + else // All auth methods failed + { + // show an appropriate error msg + if (m_failedAuthMethods == 0) + { + // we didn't even try anything, so we had a non-working config: + // pref doesn't match server + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, + ("no working auth mech - pref doesn't match server capas")); + + // pref has encrypted pw & server claims to support plaintext pw + if (m_prefAuthMethods == SMTP_AUTH_CRAM_MD5_ENABLED && + m_flags & (SMTP_AUTH_LOGIN_ENABLED | SMTP_AUTH_PLAIN_ENABLED)) + { + // have SSL + if (m_prefSocketType == nsMsgSocketType::SSL || + m_prefSocketType == nsMsgSocketType::alwaysSTARTTLS) + // tell user to change to plaintext pw + m_urlErrorState = NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_SSL; + else + // tell user to change to plaintext pw, with big warning + m_urlErrorState = NS_ERROR_SMTP_AUTH_CHANGE_ENCRYPT_TO_PLAIN_NO_SSL; + } + // pref has plaintext pw & server claims to support encrypted pw + else if (m_prefAuthMethods == (SMTP_AUTH_LOGIN_ENABLED | + SMTP_AUTH_PLAIN_ENABLED) && + m_flags & SMTP_AUTH_CRAM_MD5_ENABLED) + // tell user to change to encrypted pw + m_urlErrorState = NS_ERROR_SMTP_AUTH_CHANGE_PLAIN_TO_ENCRYPT; + else + { + // just "change auth method" + m_urlErrorState = NS_ERROR_SMTP_AUTH_MECH_NOT_SUPPORTED; + } + } + else if (m_failedAuthMethods == SMTP_AUTH_GSSAPI_ENABLED) + { + // We have only GSSAPI, and it failed, so nothing left to do. + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("GSSAPI only and it failed")); + m_urlErrorState = NS_ERROR_SMTP_AUTH_GSSAPI; + } + else + { + // we tried to login, but it all failed + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("All auth attempts failed")); + m_urlErrorState = NS_ERROR_SMTP_AUTH_FAILURE; + } + m_nextState = SMTP_ERROR_DONE; + return NS_ERROR_SMTP_AUTH_FAILURE; + } + + return NS_OK; +} + + + +nsresult nsSmtpProtocol::AuthLoginResponse(nsIInputStream * stream, uint32_t length) +{ + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP Login response, code %d", m_responseCode)); + nsresult status = NS_OK; + + switch (m_responseCode/100) + { + case 2: + m_nextState = SMTP_SEND_HELO_RESPONSE; + // fake to 250 because SendHeloResponse() tests for this + m_responseCode = 250; + break; + case 3: + m_nextState = SMTP_SEND_AUTH_LOGIN_STEP2; + break; + case 5: + default: + nsCOMPtr<nsISmtpServer> smtpServer; + m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer)); + if (smtpServer) + { + // If one authentication failed, mark it failed, so that we're going to + // fall back on a less secure login method. + MarkAuthMethodAsFailed(m_currentAuthMethod); + + bool allFailed = NS_FAILED(ChooseAuthMethod()); + if (allFailed && m_failedAuthMethods > 0 && + m_failedAuthMethods != SMTP_AUTH_GSSAPI_ENABLED && + m_failedAuthMethods != SMTP_AUTH_EXTERNAL_ENABLED) + { + // We've tried all avail. methods, and they all failed, and we have no mechanism left. + // Ask user to try with a new password. + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Warning, + ("SMTP: ask user what to do (after login failed): new password, retry or cancel")); + + nsCOMPtr<nsISmtpServer> smtpServer; + nsresult rv = m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString hostname; + rv = smtpServer->GetHostname(hostname); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t buttonPressed = 1; + if (NS_SUCCEEDED(MsgPromptLoginFailed(nullptr, hostname, + &buttonPressed))) + { + if (buttonPressed == 1) // Cancel button + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Warning, ("cancel button pressed")); + // abort and get out of here + status = NS_ERROR_ABORT; + break; + } + else if (buttonPressed == 2) // 'New password' button + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Warning, ("new password button pressed")); + // Change password was pressed. For now, forget the stored + // password and we'll prompt for a new one next time around. + smtpServer->ForgetPassword(); + if (m_usernamePrompted) + smtpServer->SetUsername(EmptyCString()); + + // Let's restore the original auth flags from SendEhloResponse + // so we can try them again with new password and username + ResetAuthMethods(); + // except for GSSAPI and EXTERNAL, which don't care about passwords. + MarkAuthMethodAsFailed(SMTP_AUTH_GSSAPI_ENABLED); + MarkAuthMethodAsFailed(SMTP_AUTH_EXTERNAL_ENABLED); + } + else if (buttonPressed == 0) // Retry button + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Warning, ("retry button pressed")); + // try all again, including GSSAPI + ResetAuthMethods(); + } + } + } + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, + ("SMTP: login failed: failed %X, current %X", m_failedAuthMethods, m_currentAuthMethod)); + + m_nextState = SMTP_AUTH_PROCESS_STATE; // try auth (ProcessAuth()) again, with other method + } + else + status = NS_ERROR_SMTP_PASSWORD_UNDEFINED; + break; + } + + return (status); +} + +nsresult nsSmtpProtocol::AuthGSSAPIFirst() +{ + NS_ASSERTION(m_currentAuthMethod == SMTP_AUTH_GSSAPI_ENABLED, "called in invalid state"); + nsAutoCString command("AUTH GSSAPI "); + nsAutoCString resp; + nsAutoCString service("smtp@"); + nsCString hostName; + nsCString userName; + nsresult rv; + nsCOMPtr<nsISmtpServer> smtpServer; + rv = m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer)); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + rv = smtpServer->GetUsername(userName); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + + rv = smtpServer->GetHostname(hostName); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + service.Append(hostName); + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP: GSSAPI step 1 for user %s at server %s, service %s", + userName.get(), hostName.get(), service.get())); + + rv = DoGSSAPIStep1(service.get(), userName.get(), resp); + if (NS_FAILED(rv)) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("SMTP: GSSAPI step 1 failed early")); + MarkAuthMethodAsFailed(SMTP_AUTH_GSSAPI_ENABLED); + m_nextState = SMTP_AUTH_PROCESS_STATE; + return NS_OK; + } + else + command.Append(resp); + command.Append(CRLF); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_SEND_AUTH_GSSAPI_STEP; + SetFlag(SMTP_PAUSE_FOR_READ); + return SendData(command.get()); +} + +// GSSAPI may consist of multiple round trips + +nsresult nsSmtpProtocol::AuthGSSAPIStep() +{ + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP: GSSAPI auth step 2")); + NS_ASSERTION(m_currentAuthMethod == SMTP_AUTH_GSSAPI_ENABLED, "called in invalid state"); + nsresult rv; + nsAutoCString cmd; + + // Check to see what the server said + if (m_responseCode / 100 != 3) { + m_nextState = SMTP_AUTH_LOGIN_RESPONSE; + return NS_OK; + } + + rv = DoGSSAPIStep2(m_responseText, cmd); + if (NS_FAILED(rv)) + cmd = "*"; + cmd += CRLF; + + m_nextStateAfterResponse = (rv == NS_SUCCESS_AUTH_FINISHED)?SMTP_AUTH_LOGIN_RESPONSE:SMTP_SEND_AUTH_GSSAPI_STEP; + m_nextState = SMTP_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + + return SendData(cmd.get()); +} + + +// LOGIN and MSN consist of three steps (MSN not through the mechanism +// but by non-RFC2821 compliant implementation in MS servers) not two as +// PLAIN or CRAM-MD5, so we've to start here and continue with AuthStep1 +// if the server responds with with a 3xx code to "AUTH LOGIN" or "AUTH MSN" +nsresult nsSmtpProtocol::AuthLoginStep0() +{ + NS_ASSERTION(m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED || + m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED, + "called in invalid state"); + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP: MSN or LOGIN auth, step 0")); + nsAutoCString command(m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED + ? "AUTH MSN" CRLF : "AUTH LOGIN" CRLF); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_AUTH_LOGIN_STEP0_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + + return SendData(command.get()); +} + +void nsSmtpProtocol::AuthLoginStep0Response() +{ + NS_ASSERTION(m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED || + m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED, + "called in invalid state"); + // need the test to be here instead in AuthLoginResponse() to + // continue with step 1 instead of 2 in case of a code 3xx + m_nextState = (m_responseCode/100 == 3) ? + SMTP_SEND_AUTH_LOGIN_STEP1 : SMTP_AUTH_LOGIN_RESPONSE; +} + +nsresult nsSmtpProtocol::AuthLoginStep1() +{ + char buffer[512]; // TODO nsAutoCString + nsresult rv; + nsresult status = NS_OK; + nsCString username; + char *base64Str = nullptr; + nsAutoCString password; + nsCOMPtr<nsISmtpServer> smtpServer; + rv = m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer)); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + rv = smtpServer->GetUsername(username); + if (username.IsEmpty()) + { + rv = GetUsernamePassword(username, password); + m_usernamePrompted = true; + if (username.IsEmpty() || password.IsEmpty()) + return NS_ERROR_SMTP_PASSWORD_UNDEFINED; + } + + nsCString hostname; + smtpServer->GetHostname(hostname); + + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP AuthLoginStep1() for %s@%s", + username.get(), hostname.get())); + + GetPassword(password); + if (password.IsEmpty()) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("SMTP: password undefined")); + m_urlErrorState = NS_ERROR_SMTP_PASSWORD_UNDEFINED; + return NS_ERROR_SMTP_PASSWORD_UNDEFINED; + } + + if (m_currentAuthMethod == SMTP_AUTH_CRAM_MD5_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Error, ("CRAM auth, step 1")); + PR_snprintf(buffer, sizeof(buffer), "AUTH CRAM-MD5" CRLF); + } + else if (m_currentAuthMethod == SMTP_AUTH_NTLM_ENABLED || + m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("NTLM/MSN auth, step 1")); + nsAutoCString response; + rv = DoNtlmStep1(username.get(), password.get(), response); + PR_snprintf(buffer, sizeof(buffer), TestFlag(SMTP_AUTH_NTLM_ENABLED) ? + "AUTH NTLM %.256s" CRLF : + "%.256s" CRLF, response.get()); + } + else if (m_currentAuthMethod == SMTP_AUTH_PLAIN_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("PLAIN auth")); + char plain_string[512]; + int len = 1; /* first <NUL> char */ + + memset(plain_string, 0, 512); + PR_snprintf(&plain_string[1], 510, "%s", username.get()); + len += username.Length(); + len++; /* second <NUL> char */ + PR_snprintf(&plain_string[len], 511-len, "%s", password.get()); + len += password.Length(); + + base64Str = PL_Base64Encode(plain_string, len, nullptr); + PR_snprintf(buffer, sizeof(buffer), "AUTH PLAIN %.256s" CRLF, base64Str); + } + else if (m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("LOGIN auth")); + base64Str = PL_Base64Encode(username.get(), + username.Length(), nullptr); + PR_snprintf(buffer, sizeof(buffer), "%.256s" CRLF, base64Str); + } + else + return (NS_ERROR_COMMUNICATIONS_ERROR); + + status = SendData(buffer, true); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_AUTH_LOGIN_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + NS_Free(base64Str); + + return (status); +} + +nsresult nsSmtpProtocol::AuthLoginStep2() +{ + /* use cached smtp password first + * if not then use cached pop password + * if pop password undefined + * sync with smtp password + */ + nsresult status = NS_OK; + nsresult rv; + nsAutoCString password; + + GetPassword(password); + if (password.IsEmpty()) + { + m_urlErrorState = NS_ERROR_SMTP_PASSWORD_UNDEFINED; + return NS_ERROR_SMTP_PASSWORD_UNDEFINED; + } + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("SMTP AuthLoginStep2")); + + if (!password.IsEmpty()) + { + char buffer[512]; + if (m_currentAuthMethod == SMTP_AUTH_CRAM_MD5_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("CRAM auth, step 2")); + unsigned char digest[DIGEST_LENGTH]; + char * decodedChallenge = PL_Base64Decode(m_responseText.get(), + m_responseText.Length(), nullptr); + + if (decodedChallenge) + rv = MSGCramMD5(decodedChallenge, strlen(decodedChallenge), password.get(), password.Length(), digest); + else + rv = NS_ERROR_FAILURE; + + PR_Free(decodedChallenge); + if (NS_SUCCEEDED(rv)) + { + nsAutoCString encodedDigest; + char hexVal[8]; + + for (uint32_t j=0; j<16; j++) + { + PR_snprintf (hexVal,8, "%.2x", 0x0ff & (unsigned short)digest[j]); + encodedDigest.Append(hexVal); + } + + nsCOMPtr<nsISmtpServer> smtpServer; + rv = m_runningURL->GetSmtpServer(getter_AddRefs(smtpServer)); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + nsCString userName; + rv = smtpServer->GetUsername(userName); + + PR_snprintf(buffer, sizeof(buffer), "%s %s", userName.get(), encodedDigest.get()); + char *base64Str = PL_Base64Encode(buffer, strlen(buffer), nullptr); + PR_snprintf(buffer, sizeof(buffer), "%s" CRLF, base64Str); + NS_Free(base64Str); + } + if (NS_FAILED(rv)) + PR_snprintf(buffer, sizeof(buffer), "*" CRLF); + } + else if (m_currentAuthMethod == SMTP_AUTH_NTLM_ENABLED || + m_currentAuthMethod == SMTP_AUTH_MSN_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("NTLM/MSN auth, step 2")); + nsAutoCString response; + rv = DoNtlmStep2(m_responseText, response); + PR_snprintf(buffer, sizeof(buffer), "%.509s" CRLF, response.get()); + } + else if (m_currentAuthMethod == SMTP_AUTH_PLAIN_ENABLED || + m_currentAuthMethod == SMTP_AUTH_LOGIN_ENABLED) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("PLAIN/LOGIN auth, step 2")); + char *base64Str = PL_Base64Encode(password.get(), password.Length(), nullptr); + PR_snprintf(buffer, sizeof(buffer), "%.256s" CRLF, base64Str); + NS_Free(base64Str); + } + else + return NS_ERROR_COMMUNICATIONS_ERROR; + + status = SendData(buffer, true); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_AUTH_LOGIN_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + return (status); + } + + // XXX -1 is not a valid nsresult + return static_cast<nsresult>(-1); +} + +#ifdef MOZ_MAILNEWS_OAUTH2 +nsresult nsSmtpProtocol::AuthOAuth2Step1() +{ + MOZ_ASSERT(mOAuth2Support, "Can't do anything without OAuth2 support"); + + nsresult rv = mOAuth2Support->Connect(true, this); + NS_ENSURE_SUCCESS(rv, rv); + + m_nextState = SMTP_SUSPENDED; + return NS_OK; +} + +nsresult nsSmtpProtocol::OnSuccess(const nsACString &aAccessToken) +{ + MOZ_ASSERT(mOAuth2Support, "Can't do anything without OAuth2 support"); + + nsCString base64Str; + mOAuth2Support->BuildXOAuth2String(base64Str); + + // Send the AUTH XOAUTH2 command, and then siphon us back to the regular + // authentication login stream. + nsAutoCString buffer; + buffer.AppendLiteral("AUTH XOAUTH2 "); + buffer += base64Str; + buffer += CRLF; + nsresult rv = SendData(buffer.get(), true); + if (NS_FAILED(rv)) + { + m_nextState = SMTP_ERROR_DONE; + } + else + { + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_AUTH_LOGIN_RESPONSE; + } + + SetFlag(SMTP_PAUSE_FOR_READ); + + ProcessProtocolState(nullptr, nullptr, 0, 0); + return NS_OK; +} + +nsresult nsSmtpProtocol::OnFailure(nsresult aError) +{ + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Debug, ("OAuth2 login error %08x", + (uint32_t)aError)); + m_urlErrorState = aError; + m_nextState = SMTP_ERROR_DONE; + return ProcessProtocolState(nullptr, nullptr, 0, 0); +} +#endif + +nsresult nsSmtpProtocol::SendMailResponse() +{ + nsresult status = NS_OK; + nsAutoCString buffer; + nsresult rv; + + if (m_responseCode/10 != 25) + { + nsresult errorcode; + if (TestFlag(SMTP_EHLO_SIZE_ENABLED)) + errorcode = (m_responseCode == 452) ? NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED : + (m_responseCode == 552) ? NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2 : + NS_ERROR_SENDING_FROM_COMMAND; + else + errorcode = NS_ERROR_SENDING_FROM_COMMAND; + + rv = nsExplainErrorDetails(m_runningURL, errorcode, m_responseText.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return(NS_ERROR_SENDING_FROM_COMMAND); + } + + /* Send the RCPT TO: command */ + bool requestDSN = false; + rv = m_runningURL->GetRequestDSN(&requestDSN); + + nsCOMPtr <nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv,rv); + + bool requestOnSuccess = false; + rv = prefBranch->GetBoolPref("mail.dsn.request_on_success_on", &requestOnSuccess); + + bool requestOnFailure = false; + rv = prefBranch->GetBoolPref("mail.dsn.request_on_failure_on", &requestOnFailure); + + bool requestOnDelay = false; + rv = prefBranch->GetBoolPref("mail.dsn.request_on_delay_on", &requestOnDelay); + + bool requestOnNever = false; + rv = prefBranch->GetBoolPref("mail.dsn.request_never_on", &requestOnNever); + + nsCString &address = m_addresses[m_addressesLeft - 1]; + if (TestFlag(SMTP_EHLO_DSN_ENABLED) && requestDSN && (requestOnSuccess || requestOnFailure || requestOnDelay || requestOnNever)) + { + char *encodedAddress = esmtp_value_encode(address.get()); + nsAutoCString dsnBuffer; + + if (encodedAddress) + { + buffer = "RCPT TO:<"; + buffer += address; + buffer += "> NOTIFY="; + + if (requestOnNever) + dsnBuffer += "NEVER"; + else + { + if (requestOnSuccess) + dsnBuffer += "SUCCESS"; + + if (requestOnFailure) + dsnBuffer += dsnBuffer.IsEmpty() ? "FAILURE" : ",FAILURE"; + + if (requestOnDelay) + dsnBuffer += dsnBuffer.IsEmpty() ? "DELAY" : ",DELAY"; + } + + buffer += dsnBuffer; + buffer += " ORCPT=rfc822;"; + buffer += encodedAddress; + buffer += CRLF; + PR_FREEIF(encodedAddress); + } + else + { + m_urlErrorState = NS_ERROR_OUT_OF_MEMORY; + return (NS_ERROR_OUT_OF_MEMORY); + } + } + else + { + buffer = "RCPT TO:<"; + buffer += address; + buffer += ">"; + buffer += CRLF; + } + status = SendData(buffer.get()); + + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_SEND_RCPT_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + + return(status); +} + +nsresult nsSmtpProtocol::SendRecipientResponse() +{ + nsresult status = NS_OK; + nsAutoCString buffer; + nsresult rv; + + if (m_responseCode / 10 != 25) + { + nsresult errorcode; + if (TestFlag(SMTP_EHLO_SIZE_ENABLED)) + errorcode = (m_responseCode == 452) ? NS_ERROR_SMTP_TEMP_SIZE_EXCEEDED : + (m_responseCode == 552) ? NS_ERROR_SMTP_PERM_SIZE_EXCEEDED_2 : + NS_ERROR_SENDING_RCPT_COMMAND; + else + errorcode = NS_ERROR_SENDING_RCPT_COMMAND; + + if (errorcode == NS_ERROR_SENDING_RCPT_COMMAND) { + rv = nsExplainErrorDetails( + m_runningURL, errorcode, NS_ConvertUTF8toUTF16(m_responseText).get(), + NS_ConvertUTF8toUTF16(m_addresses[m_addressesLeft - 1]).get()); + } else { + rv = nsExplainErrorDetails(m_runningURL, errorcode, + m_responseText.get(), + m_addresses[m_addressesLeft - 1].get()); + } + + if (!NS_SUCCEEDED(rv)) + NS_ASSERTION(false, "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return(NS_ERROR_SENDING_RCPT_COMMAND); + } + + if (--m_addressesLeft > 0) + { + // more senders to RCPT to + // fake to 250 because SendMailResponse() can't handle 251 + m_responseCode = 250; + m_nextState = SMTP_SEND_MAIL_RESPONSE; + return NS_OK; + } + + /* else send the DATA command */ + buffer = "DATA"; + buffer += CRLF; + status = SendData(buffer.get()); + + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_SEND_DATA_RESPONSE; + SetFlag(SMTP_PAUSE_FOR_READ); + + return(status); +} + + +nsresult nsSmtpProtocol::SendData(const char *dataBuffer, bool aSuppressLogging) +{ + // XXX -1 is not a valid nsresult + if (!dataBuffer) return static_cast<nsresult>(-1); + + if (!aSuppressLogging) { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("SMTP Send: %s", dataBuffer)); + } else { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("Logging suppressed for this command (it probably contained authentication information)")); + } + return nsMsgAsyncWriteProtocol::SendData(dataBuffer); +} + + +nsresult nsSmtpProtocol::SendDataResponse() +{ + nsresult status = NS_OK; + + if (m_responseCode != 354) + { + mozilla::DebugOnly<nsresult> rv = nsExplainErrorDetails(m_runningURL, + NS_ERROR_SENDING_DATA_COMMAND, + m_responseText.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return(NS_ERROR_SENDING_DATA_COMMAND); + } + + m_nextState = SMTP_SEND_POST_DATA; + ClearFlag(SMTP_PAUSE_FOR_READ); /* send data directly */ + + UpdateStatus(u"smtpDeliveringMail"); + + { +// m_runningURL->GetBodySize(&m_totalMessageSize); + } + return(status); +} + +void nsSmtpProtocol::SendMessageInFile() +{ + nsCOMPtr<nsIFile> file; + nsCOMPtr<nsIURI> url = do_QueryInterface(m_runningURL); + m_runningURL->GetPostMessageFile(getter_AddRefs(file)); + if (url && file) + // need to fully qualify to avoid getting overwritten by a #define + // in some windows header file + nsMsgAsyncWriteProtocol::PostMessage(url, file); + + SetFlag(SMTP_PAUSE_FOR_READ); + + // for now, we are always done at this point..we aren't making multiple calls + // to post data... + + UpdateStatus(u"smtpDeliveringMail"); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_SEND_MESSAGE_RESPONSE; +} + +void nsSmtpProtocol::SendPostData() +{ + // mscott: as a first pass, I'm writing everything at once and am not + // doing it in chunks... + + /* returns 0 on done and negative on error + * positive if it needs to continue. + */ + + // check to see if url is a file..if it is...call our file handler... + bool postMessageInFile = true; + m_runningURL->GetPostMessage(&postMessageInFile); + if (postMessageInFile) + { + SendMessageInFile(); + } + + /* Update the thermo and the status bar. This is done by hand, rather + than using the FE_GraphProgress* functions, because there seems to be + no way to make FE_GraphProgress shut up and not display anything more + when all the data has arrived. At the end, we want to show the + "message sent; waiting for reply" status; FE_GraphProgress gets in + the way of that. See bug #23414. */ +} + + + +nsresult nsSmtpProtocol::SendMessageResponse() +{ + if((m_responseCode/10 != 25)) + { + mozilla::DebugOnly<nsresult> rv = nsExplainErrorDetails(m_runningURL, + NS_ERROR_SENDING_MESSAGE, + m_responseText.get()); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain SMTP error"); + + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return(NS_ERROR_SENDING_MESSAGE); + } + + UpdateStatus(u"smtpMailSent"); + + /* else */ + return SendQuit(); +} + +nsresult nsSmtpProtocol::SendQuit(SmtpState aNextStateAfterResponse) +{ + m_sendDone = true; + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = aNextStateAfterResponse; + + return SendData("QUIT" CRLF); // send a quit command to close the connection with the server. +} + +nsresult nsSmtpProtocol::LoadUrl(nsIURI * aURL, nsISupports * aConsumer ) +{ + if (!aURL) + return NS_OK; + + Initialize(aURL); + + m_continuationResponse = -1; /* init */ + m_runningURL = do_QueryInterface(aURL); + if (!m_runningURL) + return NS_ERROR_FAILURE; + + // we had a bug where we failed to bring up an alert if the host + // name was empty....so throw up an alert saying we don't have + // a host name and inform the caller that we are not going to + // run the url... + nsAutoCString hostName; + aURL->GetHost(hostName); + if (hostName.IsEmpty()) + { + nsCOMPtr <nsIMsgMailNewsUrl> aMsgUrl = do_QueryInterface(aURL); + if (aMsgUrl) + { + aMsgUrl->SetUrlState(true, NS_OK); + // set the url as a url currently being run... + aMsgUrl->SetUrlState(false /* we aren't running the url */, + NS_ERROR_SMTP_AUTH_FAILURE); + } + return NS_ERROR_BUT_DONT_SHOW_ALERT; + } + + bool postMessage = false; + m_runningURL->GetPostMessage(&postMessage); + + if (postMessage) + { + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_EXTN_LOGIN_RESPONSE; + + // compile a minimal list of valid target addresses by + // - looking only at mailboxes + // - dropping addresses with invalid localparts (until we implement RFC 6532) + // - using ACE for IDN domainparts + // - stripping duplicates + nsCString addresses; + m_runningURL->GetRecipients(getter_Copies(addresses)); + + ExtractEmails(EncodedHeader(addresses), UTF16ArrayAdapter<>(m_addresses)); + + nsCOMPtr<nsIIDNService> converter = do_GetService(NS_IDNSERVICE_CONTRACTID); + addresses.Truncate(); + uint32_t count = m_addresses.Length(); + for (uint32_t i = 0; i < count; i++) + { + const char *start = m_addresses[i].get(); + // Location of the @ character + const char *lastAt = nullptr; + const char *ch = start; + for (; *ch; ch++) + { + if (*ch == '@') + lastAt = ch; + // Check for first illegal character (outside 0x09,0x20-0x7e) + else if ((*ch < ' ' || *ch > '~') && (*ch != '\t')) + { + break; + } + } + // validate the just parsed address + if (*ch || m_addresses[i].IsEmpty()) + { + // Fortunately, we will always have an @ in each mailbox address. + // We try to fix illegal character in the domain part by converting + // that to ACE. Illegal characters in the local part are not fixable + // (which charset would it be anyway?), hence we error out in that + // case as well. + nsresult rv = NS_ERROR_FAILURE; // anything but NS_OK + if (lastAt) + { + // Illegal char in the domain part, hence use ACE + nsAutoCString domain; + domain.Assign(lastAt + 1); + rv = converter->ConvertUTF8toACE(domain, domain); + if (NS_SUCCEEDED(rv)) + { + m_addresses[i].SetLength(lastAt - start + 1); + m_addresses[i] += domain; + } + } + if (NS_FAILED(rv)) + { + // Throw an error, including the broken address + m_nextState = SMTP_ERROR_DONE; + ClearFlag(SMTP_PAUSE_FOR_READ); + // Unfortunately, nsExplainErrorDetails will show the error above + // the mailnews main window, because we don't necessarily get + // passed down a compose window - we might be sending in the + // background! + rv = nsExplainErrorDetails(m_runningURL, + NS_ERROR_ILLEGAL_LOCALPART, start); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to explain illegal localpart"); + m_urlErrorState = NS_ERROR_BUT_DONT_SHOW_ALERT; + return NS_ERROR_BUT_DONT_SHOW_ALERT; + } + } + } + + // final cleanup + m_addressesLeft = m_addresses.Length(); + + // hmm no addresses to send message to... + if (m_addressesLeft == 0) + { + m_nextState = SMTP_ERROR_DONE; + ClearFlag(SMTP_PAUSE_FOR_READ); + m_urlErrorState = NS_MSG_NO_RECIPIENTS; + return NS_MSG_NO_RECIPIENTS; + } + } // if post message + + return nsMsgProtocol::LoadUrl(aURL, aConsumer); +} + +/* + * returns negative if the transfer is finished or error'd out + * + * returns zero or more if the transfer needs to be continued. + */ +nsresult nsSmtpProtocol::ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream, + uint64_t sourceOffset, uint32_t length) + { + nsresult status = NS_OK; + ClearFlag(SMTP_PAUSE_FOR_READ); /* already paused; reset */ + + while(!TestFlag(SMTP_PAUSE_FOR_READ)) + { + MOZ_LOG(SMTPLogModule, mozilla::LogLevel::Info, ("SMTP entering state: %d", + m_nextState)); + switch(m_nextState) + { + case SMTP_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SmtpResponse(inputStream, length); + break; + + case SMTP_START_CONNECT: + SetFlag(SMTP_PAUSE_FOR_READ); + m_nextState = SMTP_RESPONSE; + m_nextStateAfterResponse = SMTP_EXTN_LOGIN_RESPONSE; + break; + case SMTP_FINISH_CONNECT: + SetFlag(SMTP_PAUSE_FOR_READ); + break; + case SMTP_TLS_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendTLSResponse(); + break; + case SMTP_EXTN_LOGIN_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = ExtensionLoginResponse(inputStream, length); + break; + + case SMTP_SEND_HELO_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendHeloResponse(inputStream, length); + break; + case SMTP_SEND_EHLO_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendEhloResponse(inputStream, length); + break; + case SMTP_AUTH_PROCESS_STATE: + status = ProcessAuth(); + break; + + case SMTP_SEND_AUTH_GSSAPI_FIRST: + status = AuthGSSAPIFirst(); + break; + + case SMTP_SEND_AUTH_GSSAPI_STEP: + status = AuthGSSAPIStep(); + break; + + case SMTP_SEND_AUTH_LOGIN_STEP0: + status = AuthLoginStep0(); + break; + + case SMTP_AUTH_LOGIN_STEP0_RESPONSE: + AuthLoginStep0Response(); + status = NS_OK; + break; + + case SMTP_AUTH_EXTERNAL_RESPONSE: + case SMTP_AUTH_LOGIN_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = AuthLoginResponse(inputStream, length); + break; + + case SMTP_SEND_AUTH_LOGIN_STEP1: + status = AuthLoginStep1(); + break; + + case SMTP_SEND_AUTH_LOGIN_STEP2: + status = AuthLoginStep2(); + break; + +#ifdef MOZ_MAILNEWS_OAUTH2 + case SMTP_AUTH_OAUTH2_STEP: + status = AuthOAuth2Step1(); + break; +#endif + + case SMTP_SEND_MAIL_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendMailResponse(); + break; + + case SMTP_SEND_RCPT_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendRecipientResponse(); + break; + + case SMTP_SEND_DATA_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendDataResponse(); + break; + + case SMTP_SEND_POST_DATA: + SendPostData(); + status = NS_OK; + break; + + case SMTP_SEND_MESSAGE_RESPONSE: + if (inputStream == nullptr) + SetFlag(SMTP_PAUSE_FOR_READ); + else + status = SendMessageResponse(); + break; + case SMTP_DONE: + { + nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(m_runningURL); + mailNewsUrl->SetUrlState(false, NS_OK); + } + + m_nextState = SMTP_FREE; + break; + + case SMTP_ERROR_DONE: + { + nsCOMPtr <nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(m_runningURL); + // propagate the right error code + mailNewsUrl->SetUrlState(false, m_urlErrorState); + } + + m_nextState = SMTP_FREE; + break; + + case SMTP_FREE: + // smtp is a one time use connection so kill it if we get here... + nsMsgAsyncWriteProtocol::CloseSocket(); + return NS_OK; /* final end */ + +#ifdef MOZ_MAILNEWS_OAUTH2 + // This state means we're going into an async loop and waiting for + // something (say auth) to happen. ProcessProtocolState will be + // retriggered when necessary. + case SMTP_SUSPENDED: + return NS_OK; +#endif + + default: /* should never happen !!! */ + m_nextState = SMTP_ERROR_DONE; + break; + } + + /* check for errors during load and call error + * state if found + */ + if (NS_FAILED(status) && m_nextState != SMTP_FREE) { + // send a quit command to close the connection with the server. + if (NS_FAILED(SendQuit(SMTP_ERROR_DONE))) + { + m_nextState = SMTP_ERROR_DONE; + // Don't exit - loop around again and do the free case + ClearFlag(SMTP_PAUSE_FOR_READ); + } + } + } /* while(!SMTP_PAUSE_FOR_READ) */ + + return NS_OK; +} + +nsresult +nsSmtpProtocol::GetPassword(nsCString &aPassword) +{ + nsresult rv; + nsCOMPtr<nsISmtpUrl> smtpUrl = do_QueryInterface(m_runningURL, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsISmtpServer> smtpServer; + rv = smtpUrl->GetSmtpServer(getter_AddRefs(smtpServer)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = smtpServer->GetPassword(aPassword); + NS_ENSURE_SUCCESS(rv,rv); + + if (!aPassword.IsEmpty()) + return rv; + // empty password + + nsCOMPtr <nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefs->GetBranch(nullptr, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCString username; + rv = smtpServer->GetUsername(username); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ConvertASCIItoUTF16 usernameUTF16(username); + + nsCString hostname; + rv = smtpServer->GetHostname(hostname); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString hostnameUTF16; + CopyASCIItoUTF16(hostname, hostnameUTF16); + + const char16_t *formatStrings[] = + { + hostnameUTF16.get(), + usernameUTF16.get() + }; + + rv = PromptForPassword(smtpServer, smtpUrl, formatStrings, aPassword); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +/** + * formatStrings is an array for the prompts, item 0 is the hostname, item 1 + * is the username. + */ +nsresult +nsSmtpProtocol::PromptForPassword(nsISmtpServer *aSmtpServer, nsISmtpUrl *aSmtpUrl, const char16_t **formatStrings, nsACString &aPassword) +{ + nsCOMPtr<nsIStringBundleService> stringService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(stringService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> composeStringBundle; + nsresult rv = stringService->CreateBundle("chrome://messenger/locale/messengercompose/composeMsgs.properties", getter_AddRefs(composeStringBundle)); + NS_ENSURE_SUCCESS(rv,rv); + + nsString passwordPromptString; + if(formatStrings[1]) + rv = composeStringBundle->FormatStringFromName( + u"smtpEnterPasswordPromptWithUsername", + formatStrings, 2, getter_Copies(passwordPromptString)); + else + rv = composeStringBundle->FormatStringFromName( + u"smtpEnterPasswordPrompt", + formatStrings, 1, getter_Copies(passwordPromptString)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAuthPrompt> netPrompt; + rv = aSmtpUrl->GetAuthPrompt(getter_AddRefs(netPrompt)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString passwordTitle; + rv = composeStringBundle->GetStringFromName( + u"smtpEnterPasswordPromptTitle", + getter_Copies(passwordTitle)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = aSmtpServer->GetPasswordWithUI(passwordPromptString.get(), passwordTitle.get(), + netPrompt, aPassword); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +nsresult +nsSmtpProtocol::GetUsernamePassword(nsACString &aUsername, + nsACString &aPassword) +{ + nsresult rv; + nsCOMPtr<nsISmtpUrl> smtpUrl = do_QueryInterface(m_runningURL, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsISmtpServer> smtpServer; + rv = smtpUrl->GetSmtpServer(getter_AddRefs(smtpServer)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = smtpServer->GetPassword(aPassword); + NS_ENSURE_SUCCESS(rv,rv); + + if (!aPassword.IsEmpty()) + { + rv = smtpServer->GetUsername(aUsername); + NS_ENSURE_SUCCESS(rv,rv); + + if (!aUsername.IsEmpty()) + return rv; + } + // empty password + + aPassword.Truncate(); + + nsCString hostname; + rv = smtpServer->GetHostname(hostname); + NS_ENSURE_SUCCESS(rv, rv); + + const char16_t *formatStrings[] = + { + NS_ConvertASCIItoUTF16(hostname).get(), + nullptr + }; + + rv = PromptForPassword(smtpServer, smtpUrl, formatStrings, aPassword); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + diff --git a/mailnews/compose/src/nsSmtpProtocol.h b/mailnews/compose/src/nsSmtpProtocol.h new file mode 100644 index 000000000..c5dba76e3 --- /dev/null +++ b/mailnews/compose/src/nsSmtpProtocol.h @@ -0,0 +1,247 @@ +/* -*- 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/. */ + +#ifndef nsSmtpProtocol_h___ +#define nsSmtpProtocol_h___ + +#include "mozilla/Attributes.h" +#ifdef MOZ_MAILNEWS_OAUTH2 +#include "msgIOAuth2Module.h" +#endif +#include "nsMsgProtocol.h" +#include "nsIStreamListener.h" +#include "nsISmtpUrl.h" +#include "nsIMsgStatusFeedback.h" +#include "nsMsgLineBuffer.h" +#include "nsIAuthModule.h" +#include "MailNewsTypes2.h" // for nsMsgSocketType + +#include "nsCOMPtr.h" +#include "nsTArray.h" + +#ifdef MOZ_MAILNEWS_OAUTH2 +class nsIVariant; +class nsIWritableVariant; +#endif + + /* states of the machine + */ +typedef enum _SmtpState { +SMTP_RESPONSE = 0, // 0 +SMTP_START_CONNECT, // 1 +SMTP_FINISH_CONNECT, // 2 +SMTP_SEND_HELO_RESPONSE, // 3 +SMTP_SEND_EHLO_RESPONSE, // 4 +SMTP_SEND_MAIL_RESPONSE, // 5 +SMTP_SEND_RCPT_RESPONSE, // 6 +SMTP_SEND_DATA_RESPONSE, // 7 +SMTP_SEND_POST_DATA, // 8 +SMTP_SEND_MESSAGE_RESPONSE, // 9 +SMTP_DONE, // 10 +SMTP_ERROR_DONE, // 11 +SMTP_FREE, // 12 +SMTP_AUTH_LOGIN_STEP0_RESPONSE, // 13 +SMTP_EXTN_LOGIN_RESPONSE, // 14 +SMTP_SEND_AUTH_LOGIN_STEP0, // 15 +SMTP_SEND_AUTH_LOGIN_STEP1, // 16 +SMTP_SEND_AUTH_LOGIN_STEP2, // 17 +SMTP_AUTH_LOGIN_RESPONSE, // 18 +SMTP_TLS_RESPONSE, // 19 +SMTP_AUTH_EXTERNAL_RESPONSE, // 20 +SMTP_AUTH_PROCESS_STATE, // 21 +SMTP_AUTH_CRAM_MD5_CHALLENGE_RESPONSE, // 22 +SMTP_SEND_AUTH_GSSAPI_FIRST, // 23 +SMTP_SEND_AUTH_GSSAPI_STEP, // 24 +#ifdef MOZ_MAILNEWS_OAUTH2 +SMTP_SUSPENDED, // 25 +SMTP_AUTH_OAUTH2_STEP, // 26 +SMTP_AUTH_OAUTH2_RESPONSE, // 27 +#endif +} SmtpState; + +// State Flags (Note, I use the word state in terms of storing +// state information about the connection (authentication, have we sent +// commands, etc. I do not intend it to refer to protocol state) +#define SMTP_PAUSE_FOR_READ 0x00000001 /* should we pause for the next read */ +#define SMTP_ESMTP_SERVER 0x00000002 +#define SMTP_EHLO_DSN_ENABLED 0x00000004 +#define SMTP_EHLO_STARTTLS_ENABLED 0x00000008 +#define SMTP_EHLO_SIZE_ENABLED 0x00000010 +#define SMTP_EHLO_8BIT_ENABLED 0x00000020 + +// insecure mechanisms follow +#define SMTP_AUTH_LOGIN_ENABLED 0x00000100 +#define SMTP_AUTH_PLAIN_ENABLED 0x00000200 +#define SMTP_AUTH_EXTERNAL_ENABLED 0x00000400 +// secure mechanisms follow +#define SMTP_AUTH_GSSAPI_ENABLED 0x00000800 +#define SMTP_AUTH_DIGEST_MD5_ENABLED 0x00001000 +#define SMTP_AUTH_CRAM_MD5_ENABLED 0x00002000 +#define SMTP_AUTH_NTLM_ENABLED 0x00004000 +#define SMTP_AUTH_MSN_ENABLED 0x00008000 +#ifdef MOZ_MAILNEWS_OAUTH2 +#define SMTP_AUTH_OAUTH2_ENABLED 0x00010000 +// sum of all above auth mechanisms +#define SMTP_AUTH_ANY 0x0001FF00 +// indicates that AUTH has been advertised +#define SMTP_AUTH 0x00020000 +// No login necessary (pref) +#define SMTP_AUTH_NONE_ENABLED 0x00040000 +#else +#define SMTP_AUTH_ANY 0x0000FF00 +#define SMTP_AUTH 0x00010000 +#define SMTP_AUTH_NONE_ENABLED 0x00020000 +#endif + +#ifdef MOZ_MAILNEWS_OAUTH2 +class nsSmtpProtocol : public nsMsgAsyncWriteProtocol, + public msgIOAuth2ModuleListener +#else +class nsSmtpProtocol : public nsMsgAsyncWriteProtocol +#endif +{ +public: + NS_DECL_ISUPPORTS_INHERITED +#ifdef MOZ_MAILNEWS_OAUTH2 + NS_DECL_MSGIOAUTH2MODULELISTENER +#endif + + // Creating a protocol instance requires the URL which needs to be run. + nsSmtpProtocol(nsIURI * aURL); + + virtual nsresult LoadUrl(nsIURI * aURL, nsISupports * aConsumer = nullptr) override; + virtual nsresult SendData(const char * dataBuffer, bool aSuppressLogging = false) override; + + //////////////////////////////////////////////////////////////////////////////////////// + // we suppport the nsIStreamListener interface + //////////////////////////////////////////////////////////////////////////////////////// + + // stop binding is a "notification" informing us that the stream associated with aURL is going away. + NS_IMETHOD OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) override; + +private: + virtual ~nsSmtpProtocol(); + // if we are asked to load a url while we are blocked waiting for redirection information, + // then we'll store the url consumer in mPendingConsumer until we can actually load + // the url. + nsCOMPtr<nsISupports> mPendingConsumer; + + // the nsISmtpURL that is currently running + nsCOMPtr<nsISmtpUrl> m_runningURL; + + // the error state we want to set on the url + nsresult m_urlErrorState; + nsCOMPtr<nsIMsgStatusFeedback> m_statusFeedback; + + // Generic state information -- What state are we in? What state do we want to go to + // after the next response? What was the last response code? etc. + SmtpState m_nextState; + SmtpState m_nextStateAfterResponse; + int32_t m_responseCode; /* code returned from Smtp server */ + int32_t m_previousResponseCode; + int32_t m_continuationResponse; + nsCString m_responseText; /* text returned from Smtp server */ + nsMsgLineStreamBuffer *m_lineStreamBuffer; // used to efficiently extract lines from the incoming data stream + + nsTArray<nsCString> m_addresses; + uint32_t m_addressesLeft; + nsCString m_mailAddr; + nsCString m_helloArgument; + int32_t m_sizelimit; + + // *** the following should move to the smtp server when we support + // multiple smtp servers + bool m_usernamePrompted; + int32_t m_prefSocketType; + bool m_tlsEnabled; + + bool m_tlsInitiated; + + bool m_sendDone; + + int32_t m_totalAmountRead; + int64_t m_totalMessageSize; + + char *m_dataBuf; + uint32_t m_dataBufSize; + + int32_t m_originalContentLength; /* the content length at the time of calling graph progress */ + + // initialization function given a new url and transport layer + void Initialize(nsIURI * aURL); + virtual nsresult ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream, + uint64_t sourceOffset, uint32_t length) override; + + //////////////////////////////////////////////////////////////////////////////////////// + // Communication methods --> Reading and writing protocol + //////////////////////////////////////////////////////////////////////////////////////// + + void UpdateStatus(const char16_t* aStatusName); + void UpdateStatusWithString(const char16_t * aStatusString); + + //////////////////////////////////////////////////////////////////////////////////////// + // Protocol Methods --> This protocol is state driven so each protocol method is + // designed to re-act to the current "state". I've attempted to + // group them together based on functionality. + //////////////////////////////////////////////////////////////////////////////////////// + + nsresult SmtpResponse(nsIInputStream * inputStream, uint32_t length); + nsresult ExtensionLoginResponse(nsIInputStream * inputStream, uint32_t length); + nsresult SendHeloResponse(nsIInputStream * inputStream, uint32_t length); + nsresult SendEhloResponse(nsIInputStream * inputStream, uint32_t length); + nsresult SendQuit(SmtpState aNextStateAfterResponse = SMTP_DONE); + + nsresult AuthGSSAPIFirst(); + nsresult AuthGSSAPIStep(); + nsresult AuthLoginStep0(); + void AuthLoginStep0Response(); + nsresult AuthLoginStep1(); + nsresult AuthLoginStep2(); + nsresult AuthLoginResponse(nsIInputStream * stream, uint32_t length); +#ifdef MOZ_MAILNEWS_OAUTH2 + nsresult AuthOAuth2Step1(); +#endif + + nsresult SendTLSResponse(); + nsresult SendMailResponse(); + nsresult SendRecipientResponse(); + nsresult SendDataResponse(); + void SendPostData(); + nsresult SendMessageResponse(); + nsresult ProcessAuth(); + + + //////////////////////////////////////////////////////////////////////////////////////// + // End of Protocol Methods + //////////////////////////////////////////////////////////////////////////////////////// + + void SendMessageInFile(); + + void AppendHelloArgument(nsACString& aResult); + nsresult GetPassword(nsCString &aPassword); + nsresult GetUsernamePassword(nsACString &aUsername, nsACString &aPassword); + nsresult PromptForPassword(nsISmtpServer *aSmtpServer, nsISmtpUrl *aSmtpUrl, + const char16_t **formatStrings, + nsACString &aPassword); + + void InitPrefAuthMethods(int32_t authMethodPrefValue); + nsresult ChooseAuthMethod(); + void MarkAuthMethodAsFailed(int32_t failedAuthMethod); + void ResetAuthMethods(); + + virtual const char* GetType() override {return "smtp";} + + int32_t m_prefAuthMethods; // set of capability flags for auth methods + int32_t m_failedAuthMethods; // ditto + int32_t m_currentAuthMethod; // exactly one capability flag, or 0 + +#ifdef MOZ_MAILNEWS_OAUTH2 + // The support module for OAuth2 logon, only present if OAuth2 is enabled + // and working. + nsCOMPtr<msgIOAuth2Module> mOAuth2Support; +#endif +}; + +#endif // nsSmtpProtocol_h___ diff --git a/mailnews/compose/src/nsSmtpServer.cpp b/mailnews/compose/src/nsSmtpServer.cpp new file mode 100644 index 000000000..4dc3e1f64 --- /dev/null +++ b/mailnews/compose/src/nsSmtpServer.cpp @@ -0,0 +1,629 @@ +/* -*- 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 "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsSmtpServer.h" +#include "nsNetUtil.h" +#include "nsIAuthPrompt.h" +#include "nsMsgUtils.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgBaseCID.h" +#include "nsISmtpService.h" +#include "nsMsgCompCID.h" +#include "nsILoginInfo.h" +#include "nsILoginManager.h" +#include "nsIArray.h" +#include "nsArrayUtils.h" + +NS_IMPL_ADDREF(nsSmtpServer) +NS_IMPL_RELEASE(nsSmtpServer) +NS_INTERFACE_MAP_BEGIN(nsSmtpServer) + NS_INTERFACE_MAP_ENTRY(nsISmtpServer) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISmtpServer) +NS_INTERFACE_MAP_END + +nsSmtpServer::nsSmtpServer(): + mKey("") +{ + m_logonFailed = false; + getPrefs(); +} + +nsSmtpServer::~nsSmtpServer() +{ +} + +NS_IMETHODIMP +nsSmtpServer::GetKey(char * *aKey) +{ + if (!aKey) return NS_ERROR_NULL_POINTER; + if (mKey.IsEmpty()) + *aKey = nullptr; + else + *aKey = ToNewCString(mKey); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::SetKey(const char * aKey) +{ + NS_ASSERTION(aKey, "Bad key pointer"); + mKey = aKey; + return getPrefs(); +} + +nsresult nsSmtpServer::getPrefs() +{ + nsresult rv; + nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + nsAutoCString branchName; + branchName.AssignLiteral("mail.smtpserver."); + branchName += mKey; + branchName.Append('.'); + rv = prefs->GetBranch(branchName.get(), getter_AddRefs(mPrefBranch)); + if (NS_FAILED(rv)) + return rv; + + if(!mDefPrefBranch) { + branchName.AssignLiteral("mail.smtpserver.default."); + rv = prefs->GetBranch(branchName.get(), getter_AddRefs(mDefPrefBranch)); + if (NS_FAILED(rv)) + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::GetHostname(nsACString &aHostname) +{ + nsCString result; + nsresult rv = mPrefBranch->GetCharPref("hostname", getter_Copies(result)); + if (NS_FAILED(rv)) + aHostname.Truncate(); + else + aHostname = result; + + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::SetHostname(const nsACString &aHostname) +{ + if (!aHostname.IsEmpty()) + return mPrefBranch->SetCharPref("hostname", PromiseFlatCString(aHostname).get()); + + // If the pref value is already empty, ClearUserPref will return + // NS_ERROR_UNEXPECTED, so don't check the rv here. + mPrefBranch->ClearUserPref("hostname"); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::GetDescription(nsACString &aDescription) +{ + nsCString temp; + mPrefBranch->GetCharPref("description", getter_Copies(temp)); + aDescription.Assign(temp); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::SetDescription(const nsACString &aDescription) +{ + if (!aDescription.IsEmpty()) + return mPrefBranch->SetCharPref("description", PromiseFlatCString(aDescription).get()); + else + mPrefBranch->ClearUserPref("description"); + return NS_OK; +} + +// if GetPort returns 0, it means default port +NS_IMETHODIMP +nsSmtpServer::GetPort(int32_t *aPort) +{ + NS_ENSURE_ARG_POINTER(aPort); + if (NS_FAILED(mPrefBranch->GetIntPref("port", aPort))) + *aPort = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::SetPort(int32_t aPort) +{ + if (aPort) + return mPrefBranch->SetIntPref("port", aPort); + + mPrefBranch->ClearUserPref("port"); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::GetDisplayname(char * *aDisplayname) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(aDisplayname); + + nsCString hostname; + rv = mPrefBranch->GetCharPref("hostname", getter_Copies(hostname)); + if (NS_FAILED(rv)) { + *aDisplayname=nullptr; + return NS_OK; + } + int32_t port; + rv = mPrefBranch->GetIntPref("port", &port); + if (NS_FAILED(rv)) + port = 0; + + if (port) { + hostname.Append(':'); + hostname.AppendInt(port); + } + + *aDisplayname = ToNewCString(hostname); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::GetSocketType(int32_t *socketType) +{ + NS_ENSURE_ARG_POINTER(socketType); + getIntPrefWithDefault("try_ssl", socketType, 0); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::SetSocketType(int32_t socketType) +{ + return mPrefBranch->SetIntPref("try_ssl", socketType); +} + +NS_IMETHODIMP +nsSmtpServer::GetHelloArgument(char * *aHelloArgument) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(aHelloArgument); + rv = mPrefBranch->GetCharPref("hello_argument", aHelloArgument); + if (NS_FAILED(rv)) + { + rv = mDefPrefBranch->GetCharPref("hello_argument", aHelloArgument); + if (NS_FAILED(rv)) + *aHelloArgument = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::GetAuthMethod(int32_t *authMethod) +{ + NS_ENSURE_ARG_POINTER(authMethod); + getIntPrefWithDefault("authMethod", authMethod, 3); + return NS_OK; +} + +void +nsSmtpServer::getIntPrefWithDefault(const char *prefName, + int32_t *val, + int32_t defVal) +{ + nsresult rv = mPrefBranch->GetIntPref(prefName, val); + if (NS_SUCCEEDED(rv)) + return; + + rv = mDefPrefBranch->GetIntPref(prefName, val); + if (NS_FAILED(rv)) + // last resort + *val = defVal; +} + +NS_IMETHODIMP +nsSmtpServer::SetAuthMethod(int32_t authMethod) +{ + return mPrefBranch->SetIntPref("authMethod", authMethod); +} + +NS_IMETHODIMP +nsSmtpServer::GetUsername(nsACString &aUsername) +{ + nsCString result; + nsresult rv = mPrefBranch->GetCharPref("username", getter_Copies(result)); + if (NS_FAILED(rv)) + aUsername.Truncate(); + else + aUsername = result; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::SetUsername(const nsACString &aUsername) +{ + if (!aUsername.IsEmpty()) + return mPrefBranch->SetCharPref("username", PromiseFlatCString(aUsername).get()); + + // If the pref value is already empty, ClearUserPref will return + // NS_ERROR_UNEXPECTED, so don't check the rv here. + mPrefBranch->ClearUserPref("username"); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::GetPassword(nsACString& aPassword) +{ + if (m_password.IsEmpty() && !m_logonFailed) + { + // try to avoid prompting the user for another password. If the user has set + // the appropriate pref, we'll use the password from an incoming server, if + // the user has already logged onto that server. + + // if this is set, we'll only use this, and not the other prefs + // user_pref("mail.smtpserver.smtp1.incomingAccount", "server1"); + + // if this is set, we'll accept an exact match of user name and server + // user_pref("mail.smtp.useMatchingHostNameServer", true); + + // if this is set, and we don't find an exact match of user and host name, + // we'll accept a match of username and domain, where domain + // is everything after the first '.' + // user_pref("mail.smtp.useMatchingDomainServer", true); + + nsCString accountKey; + bool useMatchingHostNameServer = false; + bool useMatchingDomainServer = false; + mPrefBranch->GetCharPref("incomingAccount", getter_Copies(accountKey)); + + nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID); + nsCOMPtr<nsIMsgIncomingServer> incomingServerToUse; + if (accountManager) + { + if (!accountKey.IsEmpty()) + accountManager->GetIncomingServer(accountKey, getter_AddRefs(incomingServerToUse)); + else + { + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv,rv); + prefBranch->GetBoolPref("mail.smtp.useMatchingHostNameServer", &useMatchingHostNameServer); + prefBranch->GetBoolPref("mail.smtp.useMatchingDomainServer", &useMatchingDomainServer); + if (useMatchingHostNameServer || useMatchingDomainServer) + { + nsCString userName; + nsCString hostName; + GetHostname(hostName); + GetUsername(userName); + if (useMatchingHostNameServer) + // pass in empty type and port=0, to match imap and pop3. + accountManager->FindRealServer(userName, hostName, EmptyCString(), 0, getter_AddRefs(incomingServerToUse)); + int32_t dotPos = -1; + if (!incomingServerToUse && useMatchingDomainServer + && (dotPos = hostName.FindChar('.')) != kNotFound) + { + hostName.Cut(0, dotPos); + nsCOMPtr<nsIArray> allServers; + accountManager->GetAllServers(getter_AddRefs(allServers)); + if (allServers) + { + uint32_t count = 0; + allServers->GetLength(&count); + uint32_t i; + for (i = 0; i < count; i++) + { + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryElementAt(allServers, i); + if (server) + { + nsCString serverUserName; + nsCString serverHostName; + server->GetRealUsername(serverUserName); + server->GetRealHostName(serverHostName); + if (serverUserName.Equals(userName)) + { + int32_t serverDotPos = serverHostName.FindChar('.'); + if (serverDotPos != kNotFound) + { + serverHostName.Cut(0, serverDotPos); + if (serverHostName.Equals(hostName)) + { + incomingServerToUse = server; + break; + } + } + } + } + } + } + } + } + } + } + if (incomingServerToUse) + return incomingServerToUse->GetPassword(aPassword); + } + aPassword = m_password; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::VerifyLogon(nsIUrlListener *aUrlListener, nsIMsgWindow *aMsgWindow, + nsIURI **aURL) +{ + nsresult rv; + nsCOMPtr<nsISmtpService> smtpService(do_GetService(NS_SMTPSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return smtpService->VerifyLogon(this, aUrlListener, aMsgWindow, aURL); +} + + +NS_IMETHODIMP +nsSmtpServer::SetPassword(const nsACString& aPassword) +{ + m_password = aPassword; + return NS_OK; +} + +nsresult +nsSmtpServer::GetPasswordWithoutUI() +{ + nsresult rv; + nsCOMPtr<nsILoginManager> loginMgr(do_GetService(NS_LOGINMANAGER_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ConvertASCIItoUTF16 serverUri(GetServerURIInternal(false)); + + uint32_t numLogins = 0; + nsILoginInfo** logins = nullptr; + rv = loginMgr->FindLogins(&numLogins, serverUri, EmptyString(), + serverUri, &logins); + // Login manager can produce valid fails, e.g. NS_ERROR_ABORT when a user + // cancels the master password dialog. Therefore handle that here, but don't + // warn about it. + if (NS_FAILED(rv)) + return rv; + + // Don't abort here, if we didn't find any or failed, then we'll just have + // to prompt. + if (numLogins > 0) + { + nsCString serverCUsername; + rv = GetUsername(serverCUsername); + NS_ConvertASCIItoUTF16 serverUsername(serverCUsername); + + nsString username; + for (uint32_t i = 0; i < numLogins; ++i) + { + rv = logins[i]->GetUsername(username); + NS_ENSURE_SUCCESS(rv, rv); + + if (username.Equals(serverUsername)) + { + nsString password; + rv = logins[i]->GetPassword(password); + NS_ENSURE_SUCCESS(rv, rv); + + LossyCopyUTF16toASCII(password, m_password); + break; + } + } + } + NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(numLogins, logins); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::GetPasswordWithUI(const char16_t *aPromptMessage, + const char16_t *aPromptTitle, + nsIAuthPrompt* aDialog, + nsACString &aPassword) +{ + if (!m_password.IsEmpty()) + return GetPassword(aPassword); + + // We need to get a password, but see if we can get it from the password + // manager without requiring a prompt. + nsresult rv = GetPasswordWithoutUI(); + if (rv == NS_ERROR_ABORT) + return NS_MSG_PASSWORD_PROMPT_CANCELLED; + + // Now re-check if we've got a password or not, if we have, then we + // don't need to prompt the user. + if (!m_password.IsEmpty()) + { + aPassword = m_password; + return NS_OK; + } + + NS_ENSURE_ARG_POINTER(aDialog); + + // PromptPassword needs the username as well. + nsCString serverUri(GetServerURIInternal(true)); + + bool okayValue = true; + nsString uniPassword; + + rv = aDialog->PromptPassword(aPromptTitle, aPromptMessage, + NS_ConvertASCIItoUTF16(serverUri).get(), + nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY, + getter_Copies(uniPassword), &okayValue); + NS_ENSURE_SUCCESS(rv, rv); + + // If the user pressed cancel, just return an empty string. + if (!okayValue) + { + aPassword.Truncate(); + return NS_MSG_PASSWORD_PROMPT_CANCELLED; + } + + NS_LossyConvertUTF16toASCII password(uniPassword); + + rv = SetPassword(password); + NS_ENSURE_SUCCESS(rv, rv); + + aPassword = password; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::GetUsernamePasswordWithUI(const char16_t * aPromptMessage, const + char16_t *aPromptTitle, + nsIAuthPrompt* aDialog, + nsACString &aUsername, + nsACString &aPassword) +{ + nsresult rv; + if (!m_password.IsEmpty()) + { + rv = GetUsername(aUsername); + NS_ENSURE_SUCCESS(rv, rv); + + return GetPassword(aPassword); + } + + NS_ENSURE_ARG_POINTER(aDialog); + + nsCString serverUri; + rv = GetServerURI(serverUri); + NS_ENSURE_SUCCESS(rv, rv); + + nsString uniUsername; + nsString uniPassword; + bool okayValue = true; + + rv = aDialog->PromptUsernameAndPassword(aPromptTitle, aPromptMessage, + NS_ConvertASCIItoUTF16(serverUri).get(), + nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY, + getter_Copies(uniUsername), + getter_Copies(uniPassword), + &okayValue); + NS_ENSURE_SUCCESS(rv, rv); + + // If the user pressed cancel, just return emtpy strings. + if (!okayValue) + { + aUsername.Truncate(); + aPassword.Truncate(); + return rv; + } + + // We got a username and password back...so remember them. + NS_LossyConvertUTF16toASCII username(uniUsername); + + rv = SetUsername(username); + NS_ENSURE_SUCCESS(rv, rv); + + NS_LossyConvertUTF16toASCII password(uniPassword); + + rv = SetPassword(password); + NS_ENSURE_SUCCESS(rv, rv); + + aUsername = username; + aPassword = password; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpServer::ForgetPassword() +{ + nsresult rv; + nsCOMPtr<nsILoginManager> loginMgr = + do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the current server URI without the username + nsAutoCString serverUri(NS_LITERAL_CSTRING("smtp://")); + + nsCString hostname; + rv = GetHostname(hostname); + + if (NS_SUCCEEDED(rv) && !hostname.IsEmpty()) { + nsCString escapedHostname; + MsgEscapeString(hostname, nsINetUtil::ESCAPE_URL_PATH, escapedHostname); + // not all servers have a hostname + serverUri.Append(escapedHostname); + } + + uint32_t count; + nsILoginInfo** logins; + + NS_ConvertUTF8toUTF16 currServer(serverUri); + + nsCString serverCUsername; + rv = GetUsername(serverCUsername); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ConvertUTF8toUTF16 serverUsername(serverCUsername); + + rv = loginMgr->FindLogins(&count, currServer, EmptyString(), + currServer, &logins); + NS_ENSURE_SUCCESS(rv, rv); + + // There should only be one-login stored for this url, however just in case + // there isn't. + nsString username; + for (uint32_t i = 0; i < count; ++i) + { + if (NS_SUCCEEDED(logins[i]->GetUsername(username)) && + username.Equals(serverUsername)) + { + // If this fails, just continue, we'll still want to remove the password + // from our local cache. + loginMgr->RemoveLogin(logins[i]); + } + } + NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins); + + rv = SetPassword(EmptyCString()); + m_logonFailed = true; + return rv; +} + +NS_IMETHODIMP +nsSmtpServer::GetServerURI(nsACString &aResult) +{ + aResult = GetServerURIInternal(true); + return NS_OK; +} + +nsCString +nsSmtpServer::GetServerURIInternal(const bool aIncludeUsername) +{ + nsCString uri(NS_LITERAL_CSTRING("smtp://")); + nsresult rv; + + if (aIncludeUsername) + { + nsCString username; + rv = GetUsername(username); + + if (NS_SUCCEEDED(rv) && !username.IsEmpty()) { + nsCString escapedUsername; + MsgEscapeString(username, nsINetUtil::ESCAPE_XALPHAS, escapedUsername); + // not all servers have a username + uri.Append(escapedUsername); + uri.AppendLiteral("@"); + } + } + + nsCString hostname; + rv = GetHostname(hostname); + + if (NS_SUCCEEDED(rv) && !hostname.IsEmpty()) { + nsCString escapedHostname; + MsgEscapeString(hostname, nsINetUtil::ESCAPE_URL_PATH, escapedHostname); + // not all servers have a hostname + uri.Append(escapedHostname); + } + + return uri; +} + +NS_IMETHODIMP +nsSmtpServer::ClearAllValues() +{ + return mPrefBranch->DeleteBranch(""); +} diff --git a/mailnews/compose/src/nsSmtpServer.h b/mailnews/compose/src/nsSmtpServer.h new file mode 100644 index 000000000..cbd7dba67 --- /dev/null +++ b/mailnews/compose/src/nsSmtpServer.h @@ -0,0 +1,40 @@ +/* -*- 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/. */ + +#ifndef __nsSmtpServer_h_ +#define __nsSmtpServer_h_ + + +#include "nsStringGlue.h" +#include "nsISmtpServer.h" +#include "nsIPrefBranch.h" +#include "nsWeakReference.h" + +class nsSmtpServer : public nsISmtpServer, + public nsSupportsWeakReference +{ +public: + nsSmtpServer(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISMTPSERVER + +private: + virtual ~nsSmtpServer(); + nsCString mKey; + nsCOMPtr<nsIPrefBranch> mPrefBranch; + nsCOMPtr<nsIPrefBranch> mDefPrefBranch; + + nsresult getPrefs(); + void getIntPrefWithDefault(const char *prefName, int32_t *val, + int32_t defval); + nsresult GetPasswordWithoutUI(); + nsCString GetServerURIInternal(const bool aIncludeUsername); + + nsCString m_password; + bool m_logonFailed; +}; + +#endif diff --git a/mailnews/compose/src/nsSmtpService.cpp b/mailnews/compose/src/nsSmtpService.cpp new file mode 100644 index 000000000..9f9295934 --- /dev/null +++ b/mailnews/compose/src/nsSmtpService.cpp @@ -0,0 +1,772 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" // precompiled header... +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIIOService.h" +#include "nsIPipe.h" +#include "nsNetCID.h" +#include "nsMsgUtils.h" +#include "nsNetUtil.h" +#include "nsSmtpService.h" +#include "nsMsgBaseCID.h" +#include "nsMsgCompCID.h" +#include "nsArrayEnumerator.h" +#include "nsSmtpUrl.h" +#include "nsSmtpProtocol.h" +#include "nsCOMPtr.h" +#include "nsIMsgIdentity.h" +#include "nsIPrompt.h" +#include "nsIWindowWatcher.h" +#include "nsIUTF8ConverterService.h" +#include "nsUConvCID.h" +#include "nsAutoPtr.h" +#include "nsComposeStrings.h" +#include "nsIAsyncInputStream.h" +#include "nsIPrincipal.h" + +#define SERVER_DELIMITER ',' +#define APPEND_SERVERS_VERSION_PREF_NAME "append_preconfig_smtpservers.version" +#define MAIL_ROOT_PREF "mail." +#define PREF_MAIL_SMTPSERVERS "mail.smtpservers" +#define PREF_MAIL_SMTPSERVERS_APPEND_SERVERS "mail.smtpservers.appendsmtpservers" +#define PREF_MAIL_SMTP_DEFAULTSERVER "mail.smtp.defaultserver" + +typedef struct _findServerByKeyEntry { + const char *key; + nsISmtpServer *server; +} findServerByKeyEntry; + +typedef struct _findServerByHostnameEntry { + nsCString hostname; + nsCString username; + nsISmtpServer *server; +} findServerByHostnameEntry; + +static NS_DEFINE_CID(kCSmtpUrlCID, NS_SMTPURL_CID); +static NS_DEFINE_CID(kCMailtoUrlCID, NS_MAILTOURL_CID); + +// foward declarations... +nsresult +NS_MsgBuildSmtpUrl(nsIFile * aFilePath, + nsISmtpServer *aServer, + const char* aRecipients, + nsIMsgIdentity * aSenderIdentity, + nsIUrlListener * aUrlListener, + nsIMsgStatusFeedback *aStatusFeedback, + nsIInterfaceRequestor* aNotificationCallbacks, + nsIURI ** aUrl, + bool aRequestDSN); + +nsresult NS_MsgLoadSmtpUrl(nsIURI * aUrl, nsISupports * aConsumer, nsIRequest ** aRequest); + +nsSmtpService::nsSmtpService() : + mSmtpServersLoaded(false) +{ +} + +nsSmtpService::~nsSmtpService() +{ + // save the SMTP servers to disk + +} + +NS_IMPL_ISUPPORTS(nsSmtpService, nsISmtpService, nsIProtocolHandler) + + +NS_IMETHODIMP nsSmtpService::SendMailMessage(nsIFile * aFilePath, + const char * aRecipients, + nsIMsgIdentity * aSenderIdentity, + const char * aPassword, + nsIUrlListener * aUrlListener, + nsIMsgStatusFeedback *aStatusFeedback, + nsIInterfaceRequestor* aNotificationCallbacks, + bool aRequestDSN, + nsIURI ** aURL, + nsIRequest ** aRequest) +{ + nsIURI * urlToRun = nullptr; + nsresult rv = NS_OK; + + nsCOMPtr<nsISmtpServer> smtpServer; + rv = GetServerByIdentity(aSenderIdentity, getter_AddRefs(smtpServer)); + + if (NS_SUCCEEDED(rv) && smtpServer) + { + if (aPassword && *aPassword) + smtpServer->SetPassword(nsDependentCString(aPassword)); + + // this ref counts urlToRun + rv = NS_MsgBuildSmtpUrl(aFilePath, smtpServer, aRecipients, aSenderIdentity, + aUrlListener, aStatusFeedback, + aNotificationCallbacks, &urlToRun, aRequestDSN); + if (NS_SUCCEEDED(rv) && urlToRun) + rv = NS_MsgLoadSmtpUrl(urlToRun, nullptr, aRequest); + + if (aURL) // does the caller want a handle on the url? + *aURL = urlToRun; // transfer our ref count to the caller.... + else + NS_IF_RELEASE(urlToRun); + } + + return rv; +} + + +// The following are two convience functions I'm using to help expedite building and running a mail to url... + +// short cut function for creating a mailto url... +nsresult NS_MsgBuildSmtpUrl(nsIFile * aFilePath, + nsISmtpServer *aSmtpServer, + const char * aRecipients, + nsIMsgIdentity * aSenderIdentity, + nsIUrlListener * aUrlListener, + nsIMsgStatusFeedback *aStatusFeedback, + nsIInterfaceRequestor* aNotificationCallbacks, + nsIURI ** aUrl, + bool aRequestDSN) +{ + // mscott: this function is a convience hack until netlib actually dispatches + // smtp urls. in addition until we have a session to get a password, host and + // other stuff from, we need to use default values.... + // ..for testing purposes.... + + nsCString smtpHostName; + nsCString smtpUserName; + int32_t smtpPort; + int32_t socketType; + + aSmtpServer->GetHostname(smtpHostName); + aSmtpServer->GetUsername(smtpUserName); + aSmtpServer->GetPort(&smtpPort); + aSmtpServer->GetSocketType(&socketType); + + if (!smtpPort) + smtpPort = (socketType == nsMsgSocketType::SSL) ? + nsISmtpUrl::DEFAULT_SMTPS_PORT : nsISmtpUrl::DEFAULT_SMTP_PORT; + + nsresult rv; + nsCOMPtr<nsISmtpUrl> smtpUrl(do_CreateInstance(kCSmtpUrlCID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString urlSpec("smtp://"); + + if (!smtpUserName.IsEmpty()) + { + nsCString escapedUsername; + MsgEscapeString(smtpUserName, nsINetUtil::ESCAPE_XALPHAS, + escapedUsername); + urlSpec.Append(escapedUsername); + urlSpec.Append('@'); + } + + urlSpec.Append(smtpHostName); + if (smtpHostName.FindChar(':') == -1) + { + urlSpec.Append(':'); + urlSpec.AppendInt(smtpPort); + } + + nsCOMPtr<nsIMsgMailNewsUrl> url(do_QueryInterface(smtpUrl, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = url->SetSpec(urlSpec); + NS_ENSURE_SUCCESS(rv, rv); + + smtpUrl->SetRecipients(aRecipients); + smtpUrl->SetRequestDSN(aRequestDSN); + smtpUrl->SetPostMessageFile(aFilePath); + smtpUrl->SetSenderIdentity(aSenderIdentity); + if (aNotificationCallbacks) + smtpUrl->SetNotificationCallbacks(aNotificationCallbacks); + smtpUrl->SetSmtpServer(aSmtpServer); + + nsCOMPtr<nsIPrompt> smtpPrompt(do_GetInterface(aNotificationCallbacks)); + nsCOMPtr<nsIAuthPrompt> smtpAuthPrompt(do_GetInterface(aNotificationCallbacks)); + if (!smtpPrompt || !smtpAuthPrompt) + { + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!smtpPrompt) + wwatch->GetNewPrompter(0, getter_AddRefs(smtpPrompt)); + if (!smtpAuthPrompt) + wwatch->GetNewAuthPrompter(0, getter_AddRefs(smtpAuthPrompt)); + } + + smtpUrl->SetPrompt(smtpPrompt); + smtpUrl->SetAuthPrompt(smtpAuthPrompt); + + if (aUrlListener) + url->RegisterListener(aUrlListener); + if (aStatusFeedback) + url->SetStatusFeedback(aStatusFeedback); + + return CallQueryInterface(smtpUrl, aUrl); +} + +nsresult NS_MsgLoadSmtpUrl(nsIURI * aUrl, nsISupports * aConsumer, nsIRequest ** aRequest) +{ + NS_ENSURE_ARG_POINTER(aUrl); + + // For now, assume the url is an smtp url and load it. + nsresult rv; + nsCOMPtr<nsISmtpUrl> smtpUrl(do_QueryInterface(aUrl, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // Create a smtp protocol instance to run the url in. + RefPtr<nsSmtpProtocol> smtpProtocol = new nsSmtpProtocol(aUrl); + if (!smtpProtocol) + return NS_ERROR_OUT_OF_MEMORY; + + // Protocol will get destroyed when url is completed. + rv = smtpProtocol->LoadUrl(aUrl, aConsumer); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(smtpProtocol.get(), aRequest); +} + +NS_IMETHODIMP nsSmtpService::VerifyLogon(nsISmtpServer *aServer, + nsIUrlListener *aUrlListener, + nsIMsgWindow *aMsgWindow, + nsIURI **aURL) +{ + NS_ENSURE_ARG_POINTER(aServer); + nsCString popHost; + nsCString popUser; + nsCOMPtr <nsIURI> urlToRun; + + nsresult rv = NS_MsgBuildSmtpUrl(nullptr, aServer, + nullptr, nullptr, aUrlListener, nullptr, + nullptr , getter_AddRefs(urlToRun), false); + if (NS_SUCCEEDED(rv) && urlToRun) + { + nsCOMPtr<nsIMsgMailNewsUrl> url(do_QueryInterface(urlToRun, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + url->SetMsgWindow(aMsgWindow); + rv = NS_MsgLoadSmtpUrl(urlToRun, nullptr, nullptr /* aRequest */); + if (aURL) + urlToRun.forget(aURL); + } + return rv; +} + +NS_IMETHODIMP nsSmtpService::GetScheme(nsACString &aScheme) +{ + aScheme = "mailto"; + return NS_OK; +} + +NS_IMETHODIMP nsSmtpService::GetDefaultPort(int32_t *aDefaultPort) +{ + nsresult rv = NS_OK; + if (aDefaultPort) + *aDefaultPort = nsISmtpUrl::DEFAULT_SMTP_PORT; + else + rv = NS_ERROR_NULL_POINTER; + return rv; +} + +NS_IMETHODIMP +nsSmtpService::AllowPort(int32_t port, const char *scheme, bool *_retval) +{ + // allow smtp to run on any port + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP nsSmtpService::GetProtocolFlags(uint32_t *result) +{ + *result = URI_NORELATIVE | ALLOWS_PROXY | URI_LOADABLE_BY_ANYONE | + URI_NON_PERSISTABLE | URI_DOES_NOT_RETURN_DATA | + URI_FORBIDS_COOKIE_ACCESS; + return NS_OK; +} + +// the smtp service is also the protocol handler for mailto urls.... + +NS_IMETHODIMP nsSmtpService::NewURI(const nsACString &aSpec, + const char *aOriginCharset, + nsIURI *aBaseURI, + nsIURI **_retval) +{ + // get a new smtp url + nsresult rv; + nsCOMPtr<nsIURI> mailtoUrl = do_CreateInstance(kCMailtoUrlCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString utf8Spec; + if (aOriginCharset) + { + nsCOMPtr<nsIUTF8ConverterService> + utf8Converter(do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + rv = utf8Converter->ConvertURISpecToUTF8(aSpec, aOriginCharset, utf8Spec); + } + + // utf8Spec is filled up only when aOriginCharset is specified and + // the conversion is successful. Otherwise, fall back to aSpec. + if (aOriginCharset && NS_SUCCEEDED(rv)) + rv = mailtoUrl->SetSpec(utf8Spec); + else + rv = mailtoUrl->SetSpec(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + + mailtoUrl.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP nsSmtpService::NewChannel(nsIURI *aURI, nsIChannel **_retval) +{ + return NewChannel2(aURI, nullptr, _retval); +} + +NS_IMETHODIMP nsSmtpService::NewChannel2(nsIURI *aURI, + nsILoadInfo* aLoadInfo, + nsIChannel **_retval) +{ + NS_ENSURE_ARG_POINTER(aURI); + // create an empty pipe for use with the input stream channel. + nsCOMPtr<nsIAsyncInputStream> pipeIn; + nsCOMPtr<nsIAsyncOutputStream> pipeOut; + nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1"); + nsresult rv = pipe->Init(false, false, 0, 0); + NS_ENSURE_SUCCESS(rv, rv); + + // These always succeed because the pipe is initialized above. + MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(getter_AddRefs(pipeIn))); + MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(getter_AddRefs(pipeOut))); + + pipeOut->Close(); + + if (aLoadInfo) { + return NS_NewInputStreamChannelInternal(_retval, + aURI, + pipeIn, + NS_LITERAL_CSTRING("application/x-mailto"), + EmptyCString(), + aLoadInfo); + } + + nsCOMPtr<nsIPrincipal> nullPrincipal = + do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipal failed."); + if (NS_FAILED(rv)) + return rv; + + return NS_NewInputStreamChannel(_retval, aURI, pipeIn, + nullPrincipal, nsILoadInfo::SEC_NORMAL, + nsIContentPolicy::TYPE_OTHER, + NS_LITERAL_CSTRING("application/x-mailto")); +} + +NS_IMETHODIMP +nsSmtpService::GetServers(nsISimpleEnumerator **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + // now read in the servers from prefs if necessary + uint32_t serverCount = mSmtpServers.Count(); + + if (serverCount <= 0) + loadSmtpServers(); + + return NS_NewArrayEnumerator(aResult, mSmtpServers); +} + +nsresult +nsSmtpService::loadSmtpServers() +{ + if (mSmtpServersLoaded) + return NS_OK; + + nsresult rv; + nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + nsCOMPtr<nsIPrefBranch> prefRootBranch; + prefService->GetBranch(nullptr, getter_AddRefs(prefRootBranch)); + if (NS_FAILED(rv)) + return rv; + + nsCString serverList; + rv = prefRootBranch->GetCharPref(PREF_MAIL_SMTPSERVERS, getter_Copies(serverList)); + serverList.StripWhitespace(); + + nsTArray<nsCString> servers; + ParseString(serverList, SERVER_DELIMITER, servers); + + /** + * Check to see if we need to add pre-configured smtp servers. + * Following prefs are important to note in understanding the procedure here. + * + * 1. pref("mailnews.append_preconfig_smtpservers.version", version number); + * This pref registers the current version in the user prefs file. A default value + * is stored in mailnews.js file. If a given vendor needs to add more preconfigured + * smtp servers, the default version number can be increased. Comparing version + * number from user's prefs file and the default one from mailnews.js, we + * can add new smtp servers and any other version level changes that need to be done. + * + * 2. pref("mail.smtpservers.appendsmtpservers", <comma separated servers list>); + * This pref contains the list of pre-configured smp servers that ISP/Vendor wants to + * to add to the existing servers list. + */ + nsCOMPtr<nsIPrefBranch> defaultsPrefBranch; + rv = prefService->GetDefaultBranch(MAIL_ROOT_PREF, getter_AddRefs(defaultsPrefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefService->GetBranch(MAIL_ROOT_PREF, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv,rv); + + int32_t appendSmtpServersCurrentVersion = 0; + int32_t appendSmtpServersDefaultVersion = 0; + rv = prefBranch->GetIntPref(APPEND_SERVERS_VERSION_PREF_NAME, &appendSmtpServersCurrentVersion); + NS_ENSURE_SUCCESS(rv,rv); + + rv = defaultsPrefBranch->GetIntPref(APPEND_SERVERS_VERSION_PREF_NAME, &appendSmtpServersDefaultVersion); + NS_ENSURE_SUCCESS(rv,rv); + + // Update the smtp server list if needed + if (appendSmtpServersCurrentVersion <= appendSmtpServersDefaultVersion) { + // If there are pre-configured servers, add them to the existing server list + nsCString appendServerList; + rv = prefRootBranch->GetCharPref(PREF_MAIL_SMTPSERVERS_APPEND_SERVERS, getter_Copies(appendServerList)); + appendServerList.StripWhitespace(); + ParseString(appendServerList, SERVER_DELIMITER, servers); + + // Increase the version number so that updates will happen as and when needed + prefBranch->SetIntPref(APPEND_SERVERS_VERSION_PREF_NAME, appendSmtpServersCurrentVersion + 1); + } + + // use GetServerByKey to check if the key (pref) is already in + // in the list. If not it calls createKeyedServer directly. + + for (uint32_t i = 0; i < servers.Length(); i++) { + nsCOMPtr<nsISmtpServer> server; + GetServerByKey(servers[i].get(), getter_AddRefs(server)); + } + + saveKeyList(); + + mSmtpServersLoaded = true; + return NS_OK; +} + +// save the list of keys +nsresult +nsSmtpService::saveKeyList() +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return rv; + + return prefBranch->SetCharPref(PREF_MAIL_SMTPSERVERS, mServerKeyList.get()); +} + +nsresult +nsSmtpService::createKeyedServer(const char *key, nsISmtpServer** aResult) +{ + if (!key) return NS_ERROR_NULL_POINTER; + + nsresult rv; + nsCOMPtr<nsISmtpServer> server = do_CreateInstance(NS_SMTPSERVER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + server->SetKey(key); + mSmtpServers.AppendObject(server); + + if (mServerKeyList.IsEmpty()) + mServerKeyList = key; + else { + mServerKeyList.Append(','); + mServerKeyList += key; + } + + if (aResult) + server.swap(*aResult); + + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpService::GetSessionDefaultServer(nsISmtpServer **aServer) +{ + NS_ENSURE_ARG_POINTER(aServer); + + if (!mSessionDefaultServer) + return GetDefaultServer(aServer); + + NS_ADDREF(*aServer = mSessionDefaultServer); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpService::SetSessionDefaultServer(nsISmtpServer *aServer) +{ + mSessionDefaultServer = aServer; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpService::GetDefaultServer(nsISmtpServer **aServer) +{ + NS_ENSURE_ARG_POINTER(aServer); + + loadSmtpServers(); + + *aServer = nullptr; + // always returns NS_OK, just leaving *aServer at nullptr + if (!mDefaultSmtpServer) { + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return rv; + + // try to get it from the prefs + nsCString defaultServerKey; + rv = prefBranch->GetCharPref(PREF_MAIL_SMTP_DEFAULTSERVER, getter_Copies(defaultServerKey)); + if (NS_SUCCEEDED(rv) && + !defaultServerKey.IsEmpty()) { + + nsCOMPtr<nsISmtpServer> server; + rv = GetServerByKey(defaultServerKey.get(), + getter_AddRefs(mDefaultSmtpServer)); + } else { + // no pref set, so just return the first one, and set the pref + + // Ensure the list of servers is loaded + loadSmtpServers(); + + // nothing in the array, we had better create a new server + // (which will add it to the array & prefs anyway) + if (mSmtpServers.Count() == 0) + // if there are no smtp servers then don't create one for the default. + return NS_OK; + + mDefaultSmtpServer = mSmtpServers[0]; + NS_ENSURE_TRUE(mDefaultSmtpServer, NS_ERROR_NULL_POINTER); + + // now we have a default server, set the prefs correctly + nsCString serverKey; + mDefaultSmtpServer->GetKey(getter_Copies(serverKey)); + if (NS_SUCCEEDED(rv)) + prefBranch->SetCharPref(PREF_MAIL_SMTP_DEFAULTSERVER, serverKey.get()); + } + } + + // at this point: + // * mDefaultSmtpServer has a valid server + // * the key has been set in the prefs + + NS_IF_ADDREF(*aServer = mDefaultSmtpServer); + + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpService::SetDefaultServer(nsISmtpServer *aServer) +{ + NS_ENSURE_ARG_POINTER(aServer); + + mDefaultSmtpServer = aServer; + + nsCString serverKey; + nsresult rv = aServer->GetKey(getter_Copies(serverKey)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv,rv); + prefBranch->SetCharPref(PREF_MAIL_SMTP_DEFAULTSERVER, serverKey.get()); + return NS_OK; +} + +bool +nsSmtpService::findServerByKey(nsISmtpServer *aServer, void *aData) +{ + findServerByKeyEntry *entry = (findServerByKeyEntry*) aData; + + nsCString key; + nsresult rv = aServer->GetKey(getter_Copies(key)); + if (NS_FAILED(rv)) + return true; + + if (key.Equals(entry->key)) + { + entry->server = aServer; + return false; + } + + return true; +} + +NS_IMETHODIMP +nsSmtpService::CreateServer(nsISmtpServer **aResult) +{ + if (!aResult) return NS_ERROR_NULL_POINTER; + + loadSmtpServers(); + nsresult rv; + int32_t i = 0; + bool unique = false; + + findServerByKeyEntry entry; + nsAutoCString key; + + do { + key = "smtp"; + key.AppendInt(++i); + entry.key = key.get(); + entry.server = nullptr; + + mSmtpServers.EnumerateForwards(findServerByKey, (void *)&entry); + if (!entry.server) unique=true; + + } while (!unique); + + rv = createKeyedServer(key.get(), aResult); + NS_ENSURE_SUCCESS(rv, rv); + return saveKeyList(); +} + + +nsresult +nsSmtpService::GetServerByKey(const char* aKey, nsISmtpServer **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + if (!aKey || !*aKey) + { + NS_ASSERTION(false, "bad key"); + return NS_ERROR_FAILURE; + } + findServerByKeyEntry entry; + entry.key = aKey; + entry.server = nullptr; + mSmtpServers.EnumerateForwards(findServerByKey, (void *)&entry); + + if (entry.server) { + NS_ADDREF(*aResult = entry.server); + return NS_OK; + } + + // not found in array, I guess we load it + return createKeyedServer(aKey, aResult); +} + +NS_IMETHODIMP +nsSmtpService::DeleteServer(nsISmtpServer *aServer) +{ + if (!aServer) return NS_OK; + + int32_t idx = mSmtpServers.IndexOf(aServer); + if (idx == -1) + return NS_OK; + + nsCString serverKey; + aServer->GetKey(getter_Copies(serverKey)); + + mSmtpServers.RemoveObjectAt(idx); + + if (mDefaultSmtpServer.get() == aServer) + mDefaultSmtpServer = nullptr; + if (mSessionDefaultServer.get() == aServer) + mSessionDefaultServer = nullptr; + + nsAutoCString newServerList; + nsCString tmpStr = mServerKeyList; + char *newStr = tmpStr.BeginWriting(); + char *token = NS_strtok(",", &newStr); + while (token) { + // only re-add the string if it's not the key + if (strcmp(token, serverKey.get()) != 0) { + if (newServerList.IsEmpty()) + newServerList = token; + else { + newServerList += ','; + newServerList += token; + } + } + token = NS_strtok(",", &newStr); + } + + // make sure the server clears out it's values.... + aServer->ClearAllValues(); + + mServerKeyList = newServerList; + saveKeyList(); + return NS_OK; +} + +bool +nsSmtpService::findServerByHostname(nsISmtpServer *aServer, void *aData) +{ + findServerByHostnameEntry *entry = (findServerByHostnameEntry*)aData; + + nsCString hostname; + nsresult rv = aServer->GetHostname(hostname); + if (NS_FAILED(rv)) + return true; + + nsCString username; + rv = aServer->GetUsername(username); + if (NS_FAILED(rv)) + return true; + + bool checkHostname = !entry->hostname.IsEmpty(); + bool checkUsername = !entry->username.IsEmpty(); + + if ((!checkHostname || + (entry->hostname.Equals(hostname, nsCaseInsensitiveCStringComparator()))) && + (!checkUsername || + entry->username.Equals(username, nsCaseInsensitiveCStringComparator()))) + { + entry->server = aServer; + return false; // stop when found + } + return true; +} + +NS_IMETHODIMP +nsSmtpService::FindServer(const char *aUsername, + const char *aHostname, nsISmtpServer ** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + findServerByHostnameEntry entry; + entry.server = nullptr; + entry.hostname = aHostname; + entry.username = aUsername; + + mSmtpServers.EnumerateForwards(findServerByHostname, (void *)&entry); + + // entry.server may be null, but that's ok. + // just return null if no server is found + NS_IF_ADDREF(*aResult = entry.server); + + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpService::GetServerByIdentity(nsIMsgIdentity *aSenderIdentity, + nsISmtpServer **aSmtpServer) +{ + NS_ENSURE_ARG_POINTER(aSmtpServer); + nsresult rv = NS_ERROR_FAILURE; + + // First try the identity's preferred server + if (aSenderIdentity) + { + nsCString smtpServerKey; + rv = aSenderIdentity->GetSmtpServerKey(smtpServerKey); + if (NS_SUCCEEDED(rv) && !(smtpServerKey.IsEmpty())) + rv = GetServerByKey(smtpServerKey.get(), aSmtpServer); + } + + // Fallback to the default + if (NS_FAILED(rv) || !(*aSmtpServer)) + rv = GetDefaultServer(aSmtpServer); + return rv; +} diff --git a/mailnews/compose/src/nsSmtpService.h b/mailnews/compose/src/nsSmtpService.h new file mode 100644 index 000000000..8aca6e7ef --- /dev/null +++ b/mailnews/compose/src/nsSmtpService.h @@ -0,0 +1,63 @@ +/* -*- 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/. */ + +#ifndef nsSmtpService_h___ +#define nsSmtpService_h___ + +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsISmtpService.h" +#include "nsISmtpServer.h" +#include "nsIProtocolHandler.h" + +//////////////////////////////////////////////////////////////////////////////////////// +// The Smtp Service is an interfaced designed to make building and running mail to urls +// easier. I'm not sure if this service will go away when the new networking model comes +// on line (as part of the N2 project). So I reserve the right to change my mind and take +// this service away =). +//////////////////////////////////////////////////////////////////////////////////////// + +class nsSmtpService : public nsISmtpService, public nsIProtocolHandler +{ +public: + + nsSmtpService(); + + NS_DECL_ISUPPORTS + + //////////////////////////////////////////////////////////////////////// + // we suppport the nsISmtpService interface + //////////////////////////////////////////////////////////////////////// + NS_DECL_NSISMTPSERVICE + + ////////////////////////////////////////////////////////////////////////// + // we suppport the nsIProtocolHandler interface + ////////////////////////////////////////////////////////////////////////// + NS_DECL_NSIPROTOCOLHANDLER + +protected: + nsresult loadSmtpServers(); + + +private: + virtual ~nsSmtpService(); + static bool findServerByKey(nsISmtpServer *aServer, void *aData); + static bool findServerByHostname(nsISmtpServer *aServer, void *aData); + + nsresult createKeyedServer(const char* key, + nsISmtpServer **aResult = nullptr); + nsresult saveKeyList(); + + nsCOMArray<nsISmtpServer> mSmtpServers; + nsCOMPtr<nsISmtpServer> mDefaultSmtpServer; + nsCOMPtr<nsISmtpServer> mSessionDefaultServer; + + nsCString mServerKeyList; + + bool mSmtpServersLoaded; +}; + +#endif /* nsSmtpService_h___ */ diff --git a/mailnews/compose/src/nsSmtpUrl.cpp b/mailnews/compose/src/nsSmtpUrl.cpp new file mode 100644 index 000000000..29678397c --- /dev/null +++ b/mailnews/compose/src/nsSmtpUrl.cpp @@ -0,0 +1,802 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "msgCore.h" + +#include "nsIURI.h" +#include "nsNetCID.h" +#include "nsSmtpUrl.h" +#include "nsStringGlue.h" +#include "nsMsgUtils.h" +#include "nsIMimeConverter.h" +#include "nsMsgMimeCID.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsCRT.h" +#include "nsAutoPtr.h" + +///////////////////////////////////////////////////////////////////////////////////// +// mailto url definition +///////////////////////////////////////////////////////////////////////////////////// +nsMailtoUrl::nsMailtoUrl() +{ + mFormat = nsIMsgCompFormat::Default; + m_baseURL = do_CreateInstance(NS_SIMPLEURI_CONTRACTID); +} + +nsMailtoUrl::~nsMailtoUrl() +{ +} + +NS_IMPL_ISUPPORTS(nsMailtoUrl, nsIMailtoUrl, nsIURI) + +static void UnescapeAndConvert(nsIMimeConverter *mimeConverter, + const nsACString &escaped, nsACString &out) +{ + NS_ASSERTION(mimeConverter, "Set mimeConverter before calling!"); + // If the string is empty, do absolutely nothing. + if (escaped.IsEmpty()) + return; + + MsgUnescapeString(escaped, 0, out); + nsAutoCString decodedString; + nsresult rv = mimeConverter->DecodeMimeHeaderToUTF8(out, "UTF_8", false, + true, decodedString); + if (NS_SUCCEEDED(rv) && !decodedString.IsEmpty()) + out = decodedString; +} + +nsresult nsMailtoUrl::ParseMailtoUrl(char * searchPart) +{ + char *rest = searchPart; + nsCString escapedInReplyToPart; + nsCString escapedToPart; + nsCString escapedCcPart; + nsCString escapedSubjectPart; + nsCString escapedNewsgroupPart; + nsCString escapedNewsHostPart; + nsCString escapedReferencePart; + nsCString escapedBodyPart; + nsCString escapedBccPart; + nsCString escapedFollowUpToPart; + nsCString escapedFromPart; + nsCString escapedHtmlPart; + nsCString escapedOrganizationPart; + nsCString escapedReplyToPart; + nsCString escapedPriorityPart; + + // okay, first, free up all of our old search part state..... + CleanupMailtoState(); + // m_toPart has the escaped address from before the query string, copy it + // over so we can add on any additional to= addresses and unescape them all. + escapedToPart = m_toPart; + + if (rest && *rest == '?') + { + /* start past the '?' */ + rest++; + } + + if (rest) + { + char *token = NS_strtok("&", &rest); + while (token && *token) + { + char *value = 0; + char *eq = PL_strchr(token, '='); + if (eq) + { + value = eq+1; + *eq = 0; + } + + nsCString decodedName; + MsgUnescapeString(nsDependentCString(token), 0, decodedName); + + if (decodedName.Length() == 0) + break; + + switch (NS_ToUpper(decodedName.First())) + { + /* DO NOT support attachment= in mailto urls. This poses a security fire hole!!! + case 'A': + if (!PL_strcasecmp (token, "attachment")) + m_attachmentPart = value; + break; + */ + case 'B': + if (decodedName.LowerCaseEqualsLiteral("bcc")) + { + if (!escapedBccPart.IsEmpty()) + { + escapedBccPart += ", "; + escapedBccPart += value; + } + else + escapedBccPart = value; + } + else if (decodedName.LowerCaseEqualsLiteral("body")) + { + if (!escapedBodyPart.IsEmpty()) + { + escapedBodyPart +="\n"; + escapedBodyPart += value; + } + else + escapedBodyPart = value; + } + break; + case 'C': + if (decodedName.LowerCaseEqualsLiteral("cc")) + { + if (!escapedCcPart.IsEmpty()) + { + escapedCcPart += ", "; + escapedCcPart += value; + } + else + escapedCcPart = value; + } + break; + case 'F': + if (decodedName.LowerCaseEqualsLiteral("followup-to")) + escapedFollowUpToPart = value; + else if (decodedName.LowerCaseEqualsLiteral("from")) + escapedFromPart = value; + break; + case 'H': + if (decodedName.LowerCaseEqualsLiteral("html-part") || + decodedName.LowerCaseEqualsLiteral("html-body")) + { + // escapedHtmlPart holds the body for both html-part and html-body. + escapedHtmlPart = value; + mFormat = nsIMsgCompFormat::HTML; + } + break; + case 'I': + if (decodedName.LowerCaseEqualsLiteral("in-reply-to")) + escapedInReplyToPart = value; + break; + + case 'N': + if (decodedName.LowerCaseEqualsLiteral("newsgroups")) + escapedNewsgroupPart = value; + else if (decodedName.LowerCaseEqualsLiteral("newshost")) + escapedNewsHostPart = value; + break; + case 'O': + if (decodedName.LowerCaseEqualsLiteral("organization")) + escapedOrganizationPart = value; + break; + case 'R': + if (decodedName.LowerCaseEqualsLiteral("references")) + escapedReferencePart = value; + else if (decodedName.LowerCaseEqualsLiteral("reply-to")) + escapedReplyToPart = value; + break; + case 'S': + if(decodedName.LowerCaseEqualsLiteral("subject")) + escapedSubjectPart = value; + break; + case 'P': + if (decodedName.LowerCaseEqualsLiteral("priority")) + escapedPriorityPart = PL_strdup(value); + break; + case 'T': + if (decodedName.LowerCaseEqualsLiteral("to")) + { + if (!escapedToPart.IsEmpty()) + { + escapedToPart += ", "; + escapedToPart += value; + } + else + escapedToPart = value; + } + break; + default: + break; + } // end of switch statement... + + if (eq) + *eq = '='; /* put it back */ + token = NS_strtok("&", &rest); + } // while we still have part of the url to parse... + } // if rest && *rest + + // Get a global converter + nsCOMPtr<nsIMimeConverter> mimeConverter = + do_GetService(NS_MIME_CONVERTER_CONTRACTID); + + // Now unescape everything, and mime-decode the things that can be encoded. + UnescapeAndConvert(mimeConverter, escapedToPart, m_toPart); + UnescapeAndConvert(mimeConverter, escapedCcPart, m_ccPart); + UnescapeAndConvert(mimeConverter, escapedBccPart, m_bccPart); + UnescapeAndConvert(mimeConverter, escapedSubjectPart, m_subjectPart); + UnescapeAndConvert(mimeConverter, escapedNewsgroupPart, m_newsgroupPart); + UnescapeAndConvert(mimeConverter, escapedReferencePart, m_referencePart); + if (!escapedBodyPart.IsEmpty()) + MsgUnescapeString(escapedBodyPart, 0, m_bodyPart); + if (!escapedHtmlPart.IsEmpty()) + MsgUnescapeString(escapedHtmlPart, 0, m_htmlPart); + UnescapeAndConvert(mimeConverter, escapedNewsHostPart, m_newsHostPart); + UnescapeAndConvert(mimeConverter, escapedFollowUpToPart, m_followUpToPart); + UnescapeAndConvert(mimeConverter, escapedFromPart, m_fromPart); + UnescapeAndConvert(mimeConverter, escapedOrganizationPart, m_organizationPart); + UnescapeAndConvert(mimeConverter, escapedReplyToPart, m_replyToPart); + UnescapeAndConvert(mimeConverter, escapedPriorityPart, m_priorityPart); + + nsCString inReplyToPart; // Not a member like the others... + UnescapeAndConvert(mimeConverter, escapedInReplyToPart, inReplyToPart); + + if (!inReplyToPart.IsEmpty()) + { + // Ensure that References and In-Reply-To are consistent... The last + // reference will be used as In-Reply-To header. + if (m_referencePart.IsEmpty()) + { + // If References is not set, set it to be the In-Reply-To. + m_referencePart = inReplyToPart; + } + else + { + // References is set. Add the In-Reply-To as last header unless it's + // set as last reference already. + int32_t lastRefStart = m_referencePart.RFindChar('<'); + nsAutoCString lastReference; + if (lastRefStart != -1) + lastReference = StringTail(m_referencePart, lastRefStart); + else + lastReference = m_referencePart; + + if (lastReference != inReplyToPart) + { + m_referencePart += " "; + m_referencePart += inReplyToPart; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsMailtoUrl::SetSpec(const nsACString &aSpec) +{ + nsresult rv = m_baseURL->SetSpec(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + return ParseUrl(); +} + +nsresult nsMailtoUrl::CleanupMailtoState() +{ + m_ccPart = ""; + m_subjectPart = ""; + m_newsgroupPart = ""; + m_newsHostPart = ""; + m_referencePart = ""; + m_bodyPart = ""; + m_bccPart = ""; + m_followUpToPart = ""; + m_fromPart = ""; + m_htmlPart = ""; + m_organizationPart = ""; + m_replyToPart = ""; + m_priorityPart = ""; + return NS_OK; +} + +nsresult nsMailtoUrl::ParseUrl() +{ + // we can get the path from the simple url..... + nsCString escapedPath; + m_baseURL->GetPath(escapedPath); + + int32_t startOfSearchPart = escapedPath.FindChar('?'); + if (startOfSearchPart >= 0) + { + // now parse out the search field... + nsAutoCString searchPart(Substring(escapedPath, startOfSearchPart)); + + if (!searchPart.IsEmpty()) + { + // now we need to strip off the search part from the + // to part.... + escapedPath.SetLength(startOfSearchPart); + MsgUnescapeString(escapedPath, 0, m_toPart); + ParseMailtoUrl(searchPart.BeginWriting()); + } + } + else if (!escapedPath.IsEmpty()) + { + MsgUnescapeString(escapedPath, 0, m_toPart); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetMessageContents(nsACString &aToPart, nsACString &aCcPart, + nsACString &aBccPart, nsACString &aSubjectPart, + nsACString &aBodyPart, nsACString &aHtmlPart, + nsACString &aReferencePart, + nsACString &aNewsgroupPart, + MSG_ComposeFormat *aFormat) +{ + NS_ENSURE_ARG_POINTER(aFormat); + + aToPart = m_toPart; + aCcPart = m_ccPart; + aBccPart = m_bccPart; + aSubjectPart = m_subjectPart; + aBodyPart = m_bodyPart; + aHtmlPart = m_htmlPart; + aReferencePart = m_referencePart; + aNewsgroupPart = m_newsgroupPart; + *aFormat = mFormat; + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetFromPart(nsACString &aResult) +{ + aResult = m_fromPart; + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetFollowUpToPart(nsACString &aResult) +{ + aResult = m_followUpToPart; + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetOrganizationPart(nsACString &aResult) +{ + aResult = m_organizationPart; + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetReplyToPart(nsACString &aResult) +{ + aResult = m_replyToPart; + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetPriorityPart(nsACString &aResult) +{ + aResult = m_priorityPart; + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::GetNewsHostPart(nsACString &aResult) +{ + aResult = m_newsHostPart; + return NS_OK; +} + +////////////////////////////////////////////////////////////////////////////// +// Begin nsIURI support +////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP nsMailtoUrl::GetSpec(nsACString &aSpec) +{ + return m_baseURL->GetSpec(aSpec); +} + +NS_IMETHODIMP nsMailtoUrl::GetPrePath(nsACString &aPrePath) +{ + return m_baseURL->GetPrePath(aPrePath); +} + +NS_IMETHODIMP nsMailtoUrl::GetScheme(nsACString &aScheme) +{ + return m_baseURL->GetScheme(aScheme); +} + +NS_IMETHODIMP nsMailtoUrl::SetScheme(const nsACString &aScheme) +{ + m_baseURL->SetScheme(aScheme); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetUserPass(nsACString &aUserPass) +{ + return m_baseURL->GetUserPass(aUserPass); +} + +NS_IMETHODIMP nsMailtoUrl::SetUserPass(const nsACString &aUserPass) +{ + m_baseURL->SetUserPass(aUserPass); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetUsername(nsACString &aUsername) +{ + return m_baseURL->GetUsername(aUsername); +} + +NS_IMETHODIMP nsMailtoUrl::SetUsername(const nsACString &aUsername) +{ + m_baseURL->SetUsername(aUsername); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetPassword(nsACString &aPassword) +{ + return m_baseURL->GetPassword(aPassword); +} + +NS_IMETHODIMP nsMailtoUrl::SetPassword(const nsACString &aPassword) +{ + m_baseURL->SetPassword(aPassword); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetHostPort(nsACString &aHostPort) +{ + return m_baseURL->GetHost(aHostPort); +} + +NS_IMETHODIMP nsMailtoUrl::SetHostPort(const nsACString &aHostPort) +{ + m_baseURL->SetHost(aHostPort); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::SetHostAndPort(const nsACString &aHostPort) +{ + m_baseURL->SetHostAndPort(aHostPort); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetHost(nsACString &aHost) +{ + return m_baseURL->GetHost(aHost); +} + +NS_IMETHODIMP nsMailtoUrl::SetHost(const nsACString &aHost) +{ + m_baseURL->SetHost(aHost); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetPort(int32_t *aPort) +{ + return m_baseURL->GetPort(aPort); +} + +NS_IMETHODIMP nsMailtoUrl::SetPort(int32_t aPort) +{ + m_baseURL->SetPort(aPort); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetPath(nsACString &aPath) +{ + return m_baseURL->GetPath(aPath); +} + +NS_IMETHODIMP nsMailtoUrl::SetPath(const nsACString &aPath) +{ + m_baseURL->SetPath(aPath); + return ParseUrl(); +} + +NS_IMETHODIMP nsMailtoUrl::GetAsciiHost(nsACString &aHostA) +{ + return m_baseURL->GetAsciiHost(aHostA); +} + +NS_IMETHODIMP nsMailtoUrl::GetAsciiHostPort(nsACString &aHostPortA) +{ + return m_baseURL->GetAsciiHostPort(aHostPortA); +} + +NS_IMETHODIMP nsMailtoUrl::GetAsciiSpec(nsACString &aSpecA) +{ + return m_baseURL->GetAsciiSpec(aSpecA); +} + +NS_IMETHODIMP nsMailtoUrl::GetOriginCharset(nsACString &aOriginCharset) +{ + return m_baseURL->GetOriginCharset(aOriginCharset); +} + +NS_IMETHODIMP nsMailtoUrl::SchemeIs(const char *aScheme, bool *_retval) +{ + return m_baseURL->SchemeIs(aScheme, _retval); +} + +NS_IMETHODIMP nsMailtoUrl::Equals(nsIURI *other, bool *_retval) +{ + // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its + // Equals method. The other nsMailtoUrl will then pass its inner URL to + // to the Equals method of our inner URL. Other URIs will return false. + if (other) + return other->Equals(m_baseURL, _retval); + + return m_baseURL->Equals(other, _retval); +} + +nsresult +nsMailtoUrl::CloneInternal(RefHandlingEnum aRefHandlingMode, + const nsACString& newRef, nsIURI** _retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + RefPtr<nsMailtoUrl> clone = new nsMailtoUrl(); + + NS_ENSURE_TRUE(clone, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv; + if (aRefHandlingMode == eHonorRef) { + rv = m_baseURL->Clone(getter_AddRefs(clone->m_baseURL)); + } else if (aRefHandlingMode == eReplaceRef) { + rv = m_baseURL->CloneWithNewRef(newRef, getter_AddRefs(clone->m_baseURL)); + } else { + rv = m_baseURL->CloneIgnoringRef(getter_AddRefs(clone->m_baseURL)); + } + NS_ENSURE_SUCCESS(rv, rv); + clone->ParseUrl(); + clone.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +nsMailtoUrl::Clone(nsIURI **_retval) +{ + return CloneInternal(eHonorRef, EmptyCString(), _retval); +} + +NS_IMETHODIMP +nsMailtoUrl::CloneIgnoringRef(nsIURI** _retval) +{ + return CloneInternal(eIgnoreRef, EmptyCString(), _retval); +} + +NS_IMETHODIMP +nsMailtoUrl::CloneWithNewRef(const nsACString& newRef, nsIURI** _retval) +{ + return CloneInternal(eReplaceRef, newRef, _retval); +} + +NS_IMETHODIMP nsMailtoUrl::Resolve(const nsACString &relativePath, nsACString &result) +{ + return m_baseURL->Resolve(relativePath, result); +} + +NS_IMETHODIMP nsMailtoUrl::SetRef(const nsACString &aRef) +{ + return m_baseURL->SetRef(aRef); +} + +NS_IMETHODIMP +nsMailtoUrl::GetRef(nsACString &result) +{ + return m_baseURL->GetRef(result); +} + +NS_IMETHODIMP nsMailtoUrl::EqualsExceptRef(nsIURI *other, bool *result) +{ + // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its + // Equals method. The other nsMailtoUrl will then pass its inner URL to + // to the Equals method of our inner URL. Other URIs will return false. + if (other) + return other->EqualsExceptRef(m_baseURL, result); + + return m_baseURL->EqualsExceptRef(other, result); +} + +NS_IMETHODIMP +nsMailtoUrl::GetSpecIgnoringRef(nsACString &result) +{ + return m_baseURL->GetSpecIgnoringRef(result); +} + +NS_IMETHODIMP +nsMailtoUrl::GetHasRef(bool *result) +{ + return m_baseURL->GetHasRef(result); +} + +NS_IMETHODIMP +nsMailtoUrl::GetFilePath(nsACString &aFilePath) +{ + return m_baseURL->GetFilePath(aFilePath); +} + +NS_IMETHODIMP +nsMailtoUrl::SetFilePath(const nsACString &aFilePath) +{ + return m_baseURL->SetFilePath(aFilePath); +} + +NS_IMETHODIMP +nsMailtoUrl::GetQuery(nsACString &aQuery) +{ + return m_baseURL->GetQuery(aQuery); +} + +NS_IMETHODIMP +nsMailtoUrl::SetQuery(const nsACString &aQuery) +{ + return m_baseURL->SetQuery(aQuery); +} + +///////////////////////////////////////////////////////////////////////////////////// +// smtp url definition +///////////////////////////////////////////////////////////////////////////////////// + +nsSmtpUrl::nsSmtpUrl() : nsMsgMailNewsUrl() +{ + // nsISmtpUrl specific state... + + m_isPostMessage = true; + m_requestDSN = false; + m_verifyLogon = false; +} + +nsSmtpUrl::~nsSmtpUrl() +{ +} + +NS_IMPL_ISUPPORTS_INHERITED(nsSmtpUrl, nsMsgMailNewsUrl, nsISmtpUrl) + +//////////////////////////////////////////////////////////////////////////////////// +// Begin nsISmtpUrl specific support + +//////////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsSmtpUrl::SetRecipients(const char * aRecipientsList) +{ + NS_ENSURE_ARG(aRecipientsList); + MsgUnescapeString(nsDependentCString(aRecipientsList), 0, m_toPart); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetRecipients(char ** aRecipientsList) +{ + NS_ENSURE_ARG_POINTER(aRecipientsList); + if (aRecipientsList) + *aRecipientsList = ToNewCString(m_toPart); + return NS_OK; +} + +NS_IMPL_GETSET(nsSmtpUrl, PostMessage, bool, m_isPostMessage) + +NS_IMPL_GETSET(nsSmtpUrl, VerifyLogon, bool, m_verifyLogon) + +// the message can be stored in a file....allow accessors for getting and setting +// the file name to post... +NS_IMETHODIMP nsSmtpUrl::SetPostMessageFile(nsIFile * aFile) +{ + NS_ENSURE_ARG_POINTER(aFile); + m_fileName = aFile; + return NS_OK; +} + +NS_IMETHODIMP nsSmtpUrl::GetPostMessageFile(nsIFile ** aFile) +{ + NS_ENSURE_ARG_POINTER(aFile); + if (m_fileName) + { + // Clone the file so nsLocalFile stat caching doesn't make the caller get + // the wrong file size. + m_fileName->Clone(aFile); + return *aFile ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + return NS_ERROR_NULL_POINTER; +} + +NS_IMPL_GETSET(nsSmtpUrl, RequestDSN, bool, m_requestDSN) + +NS_IMETHODIMP +nsSmtpUrl::SetDsnEnvid(const nsACString &aDsnEnvid) +{ + m_dsnEnvid = aDsnEnvid; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetDsnEnvid(nsACString &aDsnEnvid) +{ + aDsnEnvid = m_dsnEnvid; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetSenderIdentity(nsIMsgIdentity **aSenderIdentity) +{ + NS_ENSURE_ARG_POINTER(aSenderIdentity); + *aSenderIdentity = m_senderIdentity; + NS_ADDREF(*aSenderIdentity); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::SetSenderIdentity(nsIMsgIdentity *aSenderIdentity) +{ + NS_ENSURE_ARG_POINTER(aSenderIdentity); + m_senderIdentity = aSenderIdentity; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::SetPrompt(nsIPrompt *aNetPrompt) +{ + NS_ENSURE_ARG_POINTER(aNetPrompt); + m_netPrompt = aNetPrompt; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetPrompt(nsIPrompt **aNetPrompt) +{ + NS_ENSURE_ARG_POINTER(aNetPrompt); + NS_ENSURE_TRUE(m_netPrompt, NS_ERROR_NULL_POINTER); + *aNetPrompt = m_netPrompt; + NS_ADDREF(*aNetPrompt); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::SetAuthPrompt(nsIAuthPrompt *aNetAuthPrompt) +{ + NS_ENSURE_ARG_POINTER(aNetAuthPrompt); + m_netAuthPrompt = aNetAuthPrompt; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetAuthPrompt(nsIAuthPrompt **aNetAuthPrompt) +{ + NS_ENSURE_ARG_POINTER(aNetAuthPrompt); + NS_ENSURE_TRUE(m_netAuthPrompt, NS_ERROR_NULL_POINTER); + *aNetAuthPrompt = m_netAuthPrompt; + NS_ADDREF(*aNetAuthPrompt); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) +{ + NS_ENSURE_ARG_POINTER(aCallbacks); + m_callbacks = aCallbacks; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) +{ + NS_ENSURE_ARG_POINTER(aCallbacks); + NS_ENSURE_TRUE(m_callbacks, NS_ERROR_NULL_POINTER); + *aCallbacks = m_callbacks; + NS_ADDREF(*aCallbacks); + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::SetSmtpServer(nsISmtpServer * aSmtpServer) +{ + NS_ENSURE_ARG_POINTER(aSmtpServer); + m_smtpServer = aSmtpServer; + return NS_OK; +} + +NS_IMETHODIMP +nsSmtpUrl::GetSmtpServer(nsISmtpServer ** aSmtpServer) +{ + NS_ENSURE_ARG_POINTER(aSmtpServer); + NS_ENSURE_TRUE(m_smtpServer, NS_ERROR_NULL_POINTER); + *aSmtpServer = m_smtpServer; + NS_ADDREF(*aSmtpServer); + return NS_OK; +} diff --git a/mailnews/compose/src/nsSmtpUrl.h b/mailnews/compose/src/nsSmtpUrl.h new file mode 100644 index 000000000..d603fa365 --- /dev/null +++ b/mailnews/compose/src/nsSmtpUrl.h @@ -0,0 +1,100 @@ +/* -*- 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 nsSmtpUrl_h__ +#define nsSmtpUrl_h__ + +#include "nsISmtpUrl.h" +#include "nsIURI.h" +#include "nsMsgMailNewsUrl.h" +#include "nsIMsgIdentity.h" +#include "nsCOMPtr.h" +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsISmtpServer.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" + +class nsMailtoUrl : public nsIMailtoUrl, public nsIURI +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURI + NS_DECL_NSIMAILTOURL + + nsMailtoUrl(); + +protected: + enum RefHandlingEnum { + eIgnoreRef, + eHonorRef, + eReplaceRef + }; + virtual ~nsMailtoUrl(); + nsresult ParseUrl(); + nsresult CleanupMailtoState(); + nsresult ParseMailtoUrl(char * searchPart); + nsresult CloneInternal(RefHandlingEnum aRefHandlingMode, + const nsACString& newRef, nsIURI** _retval); + + nsCOMPtr<nsIURI> m_baseURL; + + // data retrieved from parsing the url: (Note the url could be a post from file or it could be in the url) + nsCString m_toPart; + nsCString m_ccPart; + nsCString m_subjectPart; + nsCString m_newsgroupPart; + nsCString m_newsHostPart; + nsCString m_referencePart; + nsCString m_bodyPart; + nsCString m_bccPart; + nsCString m_followUpToPart; + nsCString m_fromPart; + nsCString m_htmlPart; + nsCString m_organizationPart; + nsCString m_replyToPart; + nsCString m_priorityPart; + + MSG_ComposeFormat mFormat; +}; + +class nsSmtpUrl : public nsISmtpUrl, public nsMsgMailNewsUrl +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + // From nsISmtpUrl + NS_DECL_NSISMTPURL + + // nsSmtpUrl + nsSmtpUrl(); + +protected: + virtual ~nsSmtpUrl(); + + // data retrieved from parsing the url: (Note the url could be a post from + // file or it could be in the url) + nsCString m_toPart; + + bool m_isPostMessage; + bool m_requestDSN; + nsCString m_dsnEnvid; + bool m_verifyLogon; + + // Smtp specific event sinks + nsCOMPtr<nsIFile> m_fileName; + nsCOMPtr<nsIMsgIdentity> m_senderIdentity; + nsCOMPtr<nsIPrompt> m_netPrompt; + nsCOMPtr<nsIAuthPrompt> m_netAuthPrompt; + nsCOMPtr<nsIInterfaceRequestor> m_callbacks; + nsCOMPtr<nsISmtpServer> m_smtpServer; + + // it is possible to encode the message to parse in the form of a url. + // This function is used to decompose the search and path part into the bare + // message components (to, fcc, bcc, etc.) + nsresult ParseMessageToPost(char * searchPart); +}; + +#endif // nsSmtpUrl_h__ diff --git a/mailnews/compose/src/nsURLFetcher.cpp b/mailnews/compose/src/nsURLFetcher.cpp new file mode 100644 index 000000000..b564ab9a4 --- /dev/null +++ b/mailnews/compose/src/nsURLFetcher.cpp @@ -0,0 +1,526 @@ +/* -*- 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 "nsURLFetcher.h" + +#include "msgCore.h" // for pre-compiled headers +#include "nsCOMPtr.h" +#include "nsNullPrincipal.h" +#include <stdio.h> +#include "nscore.h" +#include "nsIFactory.h" +#include "nsISupports.h" +#include "prmem.h" +#include "plstr.h" +#include "nsIComponentManager.h" +#include "nsStringGlue.h" +#include "nsIIOService.h" +#include "nsIChannel.h" +#include "nsNetUtil.h" +#include "nsMimeTypes.h" +#include "nsIHttpChannel.h" +#include "nsIWebProgress.h" +#include "nsMsgAttachmentHandler.h" +#include "nsMsgSend.h" +#include "nsISeekableStream.h" +#include "nsIStreamConverterService.h" +#include "nsIMsgProgress.h" +#include "nsMsgUtils.h" + +NS_IMPL_ISUPPORTS(nsURLFetcher, + nsIURLFetcher, + nsIStreamListener, + nsIRequestObserver, + nsIURIContentListener, + nsIInterfaceRequestor, + nsIWebProgressListener, + nsISupportsWeakReference) + + +/* + * Inherited methods for nsMimeConverter + */ +nsURLFetcher::nsURLFetcher() +{ + // Init member variables... + mTotalWritten = 0; + mBuffer = nullptr; + mBufferSize = 0; + mStillRunning = true; + mCallback = nullptr; + mOnStopRequestProcessed = false; + mIsFile=false; + nsURLFetcherStreamConsumer *consumer = new nsURLFetcherStreamConsumer(this); + mConverter = do_QueryInterface(consumer); +} + +nsURLFetcher::~nsURLFetcher() +{ + mStillRunning = false; + + PR_FREEIF(mBuffer); + // Remove the DocShell as a listener of the old WebProgress... + if (mLoadCookie) + { + nsCOMPtr<nsIWebProgress> webProgress(do_QueryInterface(mLoadCookie)); + + if (webProgress) + webProgress->RemoveProgressListener(this); + } +} + +NS_IMETHODIMP nsURLFetcher::GetInterface(const nsIID & aIID, void * *aInstancePtr) +{ + NS_ENSURE_ARG_POINTER(aInstancePtr); + return QueryInterface(aIID, aInstancePtr); +} + +// nsIURIContentListener support +NS_IMETHODIMP +nsURLFetcher::OnStartURIOpen(nsIURI* aURI, bool* aAbortOpen) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::IsPreferred(const char * aContentType, + char ** aDesiredContentType, + bool * aCanHandleContent) + +{ + return CanHandleContent(aContentType, true, aDesiredContentType, + aCanHandleContent); +} + +NS_IMETHODIMP +nsURLFetcher::CanHandleContent(const char * aContentType, + bool aIsContentPreferred, + char ** aDesiredContentType, + bool * aCanHandleContent) + +{ + if (!mIsFile && PL_strcasecmp(aContentType, MESSAGE_RFC822) == 0) + *aDesiredContentType = strdup("text/html"); + + // since we explicilty loaded the url, we always want to handle it! + *aCanHandleContent = true; + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::DoContent(const nsACString& aContentType, + bool aIsContentPreferred, + nsIRequest *request, + nsIStreamListener ** aContentHandler, + bool * aAbortProcess) +{ + nsresult rv = NS_OK; + + if (aAbortProcess) + *aAbortProcess = false; + QueryInterface(NS_GET_IID(nsIStreamListener), (void **) aContentHandler); + + /* + Check the content-type to see if we need to insert a converter + */ + if (PL_strcasecmp(PromiseFlatCString(aContentType).get(), UNKNOWN_CONTENT_TYPE) == 0 || + PL_strcasecmp(PromiseFlatCString(aContentType).get(), MULTIPART_MIXED_REPLACE) == 0 || + PL_strcasecmp(PromiseFlatCString(aContentType).get(), MULTIPART_MIXED) == 0 || + PL_strcasecmp(PromiseFlatCString(aContentType).get(), MULTIPART_BYTERANGES) == 0) + { + rv = InsertConverter(PromiseFlatCString(aContentType).get()); + if (NS_SUCCEEDED(rv)) + mConverterContentType = PromiseFlatCString(aContentType).get(); + } + + return rv; +} + +NS_IMETHODIMP +nsURLFetcher::GetParentContentListener(nsIURIContentListener** aParent) +{ + *aParent = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::SetParentContentListener(nsIURIContentListener* aParent) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::GetLoadCookie(nsISupports ** aLoadCookie) +{ + *aLoadCookie = mLoadCookie; + NS_IF_ADDREF(*aLoadCookie); + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::SetLoadCookie(nsISupports * aLoadCookie) +{ + // Remove the DocShell as a listener of the old WebProgress... + if (mLoadCookie) + { + nsCOMPtr<nsIWebProgress> webProgress(do_QueryInterface(mLoadCookie)); + + if (webProgress) + webProgress->RemoveProgressListener(this); + } + + mLoadCookie = aLoadCookie; + + // Add the DocShell as a listener to the new WebProgress... + if (mLoadCookie) + { + nsCOMPtr<nsIWebProgress> webProgress(do_QueryInterface(mLoadCookie)); + + if (webProgress) + webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_ALL); + } + return NS_OK; + +} + +nsresult +nsURLFetcher::StillRunning(bool *running) +{ + *running = mStillRunning; + return NS_OK; +} + + +// Methods for nsIStreamListener... +nsresult +nsURLFetcher::OnDataAvailable(nsIRequest *request, nsISupports * ctxt, nsIInputStream *aIStream, + uint64_t sourceOffset, uint32_t aLength) +{ + /* let our converter or consumer process the data */ + if (!mConverter) + return NS_ERROR_FAILURE; + + return mConverter->OnDataAvailable(request, ctxt, aIStream, sourceOffset, aLength); +} + + +// Methods for nsIStreamObserver +nsresult +nsURLFetcher::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + /* check if the user has canceld the operation */ + if (mTagData) + { + nsCOMPtr<nsIMsgSend> sendPtr; + mTagData->GetMimeDeliveryState(getter_AddRefs(sendPtr)); + if (sendPtr) + { + nsCOMPtr<nsIMsgProgress> progress; + sendPtr->GetProgress(getter_AddRefs(progress)); + if (progress) + { + bool cancel = false; + progress->GetProcessCanceledByUser(&cancel); + if (cancel) + return request->Cancel(NS_ERROR_ABORT); + } + } + mTagData->mRequest = request; + } + + /* call our converter or consumer */ + if (mConverter) + return mConverter->OnStartRequest(request, ctxt); + + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::OnStopRequest(nsIRequest *request, nsISupports * ctxt, nsresult aStatus) +{ + // it's possible we could get in here from the channel calling us with an OnStopRequest and from our + // onStatusChange method (in the case of an error). So we should protect against this to make sure we + // don't process the on stop request twice... + + if (mOnStopRequestProcessed) + return NS_OK; + + mOnStopRequestProcessed = true; + + /* first, call our converter or consumer */ + if (mConverter) + (void) mConverter->OnStopRequest(request, ctxt, aStatus); + + if (mTagData) + mTagData->mRequest = nullptr; + + // + // Now complete the stream! + // + mStillRunning = false; + + // time to close the output stream... + if (mOutStream) + { + mOutStream->Close(); + mOutStream = nullptr; + + /* In case of multipart/x-mixed-replace, we need to truncate the file to the current part size */ + if (MsgLowerCaseEqualsLiteral(mConverterContentType, MULTIPART_MIXED_REPLACE)) + { + mLocalFile->SetFileSize(mTotalWritten); + } + } + + // Now if there is a callback, we need to call it... + if (mCallback) + mCallback (aStatus, mContentType, mCharset, mTotalWritten, nullptr, mTagData); + + // Time to return... + return NS_OK; +} + +nsresult +nsURLFetcher::Initialize(nsIFile *localFile, + nsIOutputStream *outputStream, + nsAttachSaveCompletionCallback cb, + nsMsgAttachmentHandler *tagData) +{ + if (!outputStream || !localFile) + return NS_ERROR_INVALID_ARG; + + mOutStream = outputStream; + mLocalFile = localFile; + mCallback = cb; //JFD: Please, no more callback, use a listener... + mTagData = tagData; + return NS_OK; +} + +nsresult +nsURLFetcher::FireURLRequest(nsIURI *aURL, nsIFile *localFile, nsIOutputStream *outputStream, + nsAttachSaveCompletionCallback cb, nsMsgAttachmentHandler *tagData) +{ + nsresult rv; + + rv = Initialize(localFile, outputStream, cb, tagData); + NS_ENSURE_SUCCESS(rv, rv); + + //check to see if aURL is a local file or not + aURL->SchemeIs("file", &mIsFile); + + // we're about to fire a new url request so make sure the on stop request flag is cleared... + mOnStopRequestProcessed = false; + + // let's try uri dispatching... + nsCOMPtr<nsIURILoader> pURILoader (do_GetService(NS_URI_LOADER_CONTRACTID)); + NS_ENSURE_TRUE(pURILoader, NS_ERROR_FAILURE); + + nsCOMPtr<nsIPrincipal> nullPrincipal = + do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), + aURL, + nullPrincipal, + nsILoadInfo::SEC_NORMAL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // aLoadGroup + this); // aCallbacks + NS_ENSURE_SUCCESS(rv, rv); + + return pURILoader->OpenURI(channel, false, this); +} + +nsresult +nsURLFetcher::InsertConverter(const char * aContentType) +{ + nsresult rv; + + nsCOMPtr<nsIStreamConverterService> convServ(do_GetService("@mozilla.org/streamConverters;1", &rv)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIStreamListener> toListener(mConverter); + nsCOMPtr<nsIStreamListener> fromListener; + + rv = convServ->AsyncConvertData(aContentType, + "*/*", + toListener, + nullptr, + getter_AddRefs(fromListener)); + if (NS_SUCCEEDED(rv)) + mConverter = fromListener; + } + + return rv; +} + +// web progress listener implementation + +NS_IMETHODIMP +nsURLFetcher::OnProgressChange(nsIWebProgress *aProgress, nsIRequest *aRequest, + int32_t aCurSelfProgress, int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::OnStateChange(nsIWebProgress *aProgress, nsIRequest *aRequest, + uint32_t aStateFlags, nsresult aStatus) +{ + // all we care about is the case where an error occurred (as in we were unable to locate the + // the url.... + + if (NS_FAILED(aStatus)) + OnStopRequest(aRequest, nullptr, aStatus); + + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI *aURI, + uint32_t aFlags) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::OnSecurityChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + uint32_t state) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + + +/** + * Stream consumer used for handling special content type like multipart/x-mixed-replace + */ + +NS_IMPL_ISUPPORTS(nsURLFetcherStreamConsumer, nsIStreamListener, nsIRequestObserver) + +nsURLFetcherStreamConsumer::nsURLFetcherStreamConsumer(nsURLFetcher* urlFetcher) : + mURLFetcher(urlFetcher) +{ +} + +nsURLFetcherStreamConsumer::~nsURLFetcherStreamConsumer() +{ +} + +/** nsIRequestObserver methods **/ + +/* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ +NS_IMETHODIMP nsURLFetcherStreamConsumer::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt) +{ + if (!mURLFetcher || !mURLFetcher->mOutStream) + return NS_ERROR_FAILURE; + + /* In case of multipart/x-mixed-replace, we need to erase the output file content */ + if (MsgLowerCaseEqualsLiteral(mURLFetcher->mConverterContentType, MULTIPART_MIXED_REPLACE)) + { + nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(mURLFetcher->mOutStream); + if (seekStream) + seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 0); + mURLFetcher->mTotalWritten = 0; + } + + return NS_OK; +} + +/* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */ +NS_IMETHODIMP nsURLFetcherStreamConsumer::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status) +{ + if (!mURLFetcher) + return NS_ERROR_FAILURE; + + // Check the content type! + nsAutoCString contentType; + nsAutoCString charset; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + if(!channel) return NS_ERROR_FAILURE; + + if (NS_SUCCEEDED(channel->GetContentType(contentType)) && + !contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) + { + nsAutoCString uriSpec; + nsCOMPtr <nsIURI> channelURI; + channel->GetURI(getter_AddRefs(channelURI)); + nsresult rv = channelURI->GetSpec(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + if (uriSpec.Find("&realtype=message/rfc822") >= 0) + mURLFetcher->mContentType = MESSAGE_RFC822; + else + mURLFetcher->mContentType = contentType; + } + + if (NS_SUCCEEDED(channel->GetContentCharset(charset)) && !charset.IsEmpty()) + { + mURLFetcher->mCharset = charset; + } + + return NS_OK; +} + +/** nsIStreamListener methods **/ + +/* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */ +NS_IMETHODIMP nsURLFetcherStreamConsumer::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, nsIInputStream *inStr, uint64_t sourceOffset, uint32_t count) +{ + uint32_t readLen = count; + uint32_t wroteIt; + + if (!mURLFetcher) + return NS_ERROR_FAILURE; + + if (!mURLFetcher->mOutStream) + return NS_ERROR_INVALID_ARG; + + if (mURLFetcher->mBufferSize < count) + { + PR_FREEIF(mURLFetcher->mBuffer); + + if (count > 0x1000) + mURLFetcher->mBufferSize = count; + else + mURLFetcher->mBufferSize = 0x1000; + + mURLFetcher->mBuffer = (char *)PR_Malloc(mURLFetcher->mBufferSize); + if (!mURLFetcher->mBuffer) + return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */ + } + + // read the data from the input stram... + nsresult rv = inStr->Read(mURLFetcher->mBuffer, count, &readLen); + if (NS_FAILED(rv)) + return rv; + + // write to the output file... + mURLFetcher->mOutStream->Write(mURLFetcher->mBuffer, readLen, &wroteIt); + + if (wroteIt != readLen) + return NS_ERROR_FAILURE; + else + { + mURLFetcher->mTotalWritten += wroteIt; + return NS_OK; + } +} diff --git a/mailnews/compose/src/nsURLFetcher.h b/mailnews/compose/src/nsURLFetcher.h new file mode 100644 index 000000000..d252b014b --- /dev/null +++ b/mailnews/compose/src/nsURLFetcher.h @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#ifndef nsURLFetcher_h_ +#define nsURLFetcher_h_ + +#include "nsIURLFetcher.h" + +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsIInputStream.h" +#include "nsIStreamListener.h" + +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsCURILoader.h" +#include "nsIURIContentListener.h" +#include "nsIWebProgressListener.h" +#include "nsWeakReference.h" +#include "nsStringGlue.h" + +class nsMsgAttachmentHandler; + +class nsURLFetcher : public nsIURLFetcher, + public nsIStreamListener, + public nsIURIContentListener, + public nsIInterfaceRequestor, + public nsIWebProgressListener, + public nsSupportsWeakReference +{ +public: + nsURLFetcher(); + + /* this macro defines QueryInterface, AddRef and Release for this class */ + NS_DECL_ISUPPORTS + + // Methods for nsIURLFetcher + NS_DECL_NSIURLFETCHER + + // Methods for nsIStreamListener + NS_DECL_NSISTREAMLISTENER + + // Methods for nsIRequestObserver + NS_DECL_NSIREQUESTOBSERVER + + // Methods for nsIURICOntentListener + NS_DECL_NSIURICONTENTLISTENER + + // Methods for nsIInterfaceRequestor + NS_DECL_NSIINTERFACEREQUESTOR + + // Methods for nsIWebProgressListener + NS_DECL_NSIWEBPROGRESSLISTENER + +protected: + nsresult InsertConverter(const char * aContentType); + +private: + virtual ~nsURLFetcher(); + nsCOMPtr<nsIOutputStream> mOutStream; // the output file stream + nsCOMPtr<nsIFile> mLocalFile; // the output file itself + nsCOMPtr<nsIStreamListener> mConverter; // the stream converter, if needed + nsCString mConverterContentType; // The content type of the converter + bool mStillRunning; // Are we still running? + int32_t mTotalWritten; // Size counter variable + char *mBuffer; // Buffer used for reading the data + uint32_t mBufferSize; // Buffer size; + nsCString mContentType; // The content type retrieved from the server + nsCString mCharset; // The charset retrieved from the server + RefPtr<nsMsgAttachmentHandler> mTagData; // Tag data for callback... + nsAttachSaveCompletionCallback mCallback; // Callback to call once the file is saved... + nsCOMPtr<nsISupports> mLoadCookie; // load cookie used by the uri loader when we fetch the url + bool mOnStopRequestProcessed; // used to prevent calling OnStopRequest multiple times + bool mIsFile; // This is used to check whether the URI is a local file. + + friend class nsURLFetcherStreamConsumer; +}; + + +/** + * Stream consumer used for handling special content type like multipart/x-mixed-replace + */ + +class nsURLFetcherStreamConsumer : public nsIStreamListener +{ +public: + nsURLFetcherStreamConsumer(nsURLFetcher* urlFetcher); + + /* additional members */ + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + +private: + virtual ~nsURLFetcherStreamConsumer(); + nsURLFetcher* mURLFetcher; +}; + + +#endif /* nsURLFetcher_h_ */ |