/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. */ /* compose.c --- generation and delivery of MIME objects. */ #include "rosetta.h" #include "msg.h" #include "ntypes.h" #include "structs.h" #include "xlate.h" /* Text and PostScript converters */ #include "merrors.h" #include "gui.h" /* for XP_AppCodeName */ #include "mime.h" #include "xp_time.h" /* For XP_LocalZoneOffset() */ #include "libi18n.h" #include "xpgetstr.h" #include "prtime.h" #include "prtypes.h" #include "msgcom.h" #include "msgsend.h" #include "msgsendp.h" #include "maildb.h" #include "mailhdr.h" #include "msgprefs.h" #include "msgmast.h" #include "msgcpane.h" #ifndef NO_SECURITY #include HG02902 #endif #include "grpinfo.h" #include "msgcflds.h" #include "prefapi.h" #include "abdefn.h" #include "prsembst.h" #include "secrng.h" /* for RNG_GenerateGlobalRandomBytes() */ #include "addrbook.h" #include "imaphost.h" #include "imapoff.h" #ifdef XP_MAC #include "errors.h" #endif #include "intl_csi.h" #include "msgimap.h" #include "msgurlq.h" #ifdef XP_MAC #pragma warn_unusedarg off #endif // XP_MAC extern "C" { extern int MK_MSG_ASSEMBLING_MSG; extern int MK_MSG_ASSEMB_DONE_MSG; extern int MK_MSG_LOAD_ATTACHMNT; extern int MK_MSG_LOAD_ATTACHMNTS; extern int MK_MSG_DELIV_MAIL; extern int MK_MSG_DELIV_MAIL_DONE; extern int MK_MSG_DELIV_NEWS; extern int MK_MSG_DELIV_NEWS_DONE; extern int MK_MSG_QUEUEING; extern int MK_MSG_WRITING_TO_FCC; extern int MK_MSG_QUEUED; extern int MK_MIME_ERROR_WRITING_FILE; extern int MK_MIME_MULTIPART_BLURB; extern int MK_MIME_NO_RECIPIENTS; extern int MK_MIME_NO_SENDER; extern int MK_MSG_COULDNT_OPEN_FCC_FILE; extern int MK_OUT_OF_MEMORY; extern int MK_UNABLE_TO_OPEN_TMP_FILE; extern int MK_MSG_CANT_CREATE_FOLDER; extern int MK_MSG_SAVE_DRAFT; extern int MK_ADDR_BOOK_CARD; extern int MK_MSG_MAC_PROMPT_UUENCODE; extern int MK_MSG_SAVING_AS_DRAFT; extern int MK_MSG_SAVING_AS_TEMPLATE; extern int MK_MSG_UNABLE_TO_SAVE_DRAFT; extern int MK_MSG_UNABLE_TO_SAVE_TEMPLATE; extern int MK_UNABLE_TO_OPEN_FILE; extern int MK_IMAP_UNABLE_TO_SAVE_MESSAGE; extern int MK_IMAP_NO_ONLINE_FOLDER; } #ifdef XP_MAC #include "m_cvstrm.h" #endif #define TEN_K 10240 #define MK_ATTACHMENT_LOAD_FAILED -666 /* 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 900 bytes. - 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. */ /* It's better to send a message as news before sending it as mail, because the NNTP server is more likely to reject the article (for any number of reasons) than the SMTP server is. */ #undef MAIL_BEFORE_NEWS /* Generating a message ID here is a good because it means that if a message is sent to both mail and news, it will have the same ID in both places. */ #define GENERATE_MESSAGE_ID /* For maximal compatibility, it helps to emit both Content-Type: ; name="" as well as Content-Disposition: inline; filename="" 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 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 static XP_Bool mime_use_quoted_printable_p = TRUE; static XP_Bool mime_headers_use_quoted_printable_p = FALSE; #ifdef XP_MAC XP_BEGIN_PROTOS extern OSErr my_FSSpecFromPathname(char* src_filename, FSSpec* fspec); extern char * mime_make_separator(const char *prefix); HG89984 XP_END_PROTOS static char* NET_GetLocalFileFromURL(char *url) { char * finalPath; XP_ASSERT(strncasecomp(url, "file://", 7) == 0); finalPath = (char*)XP_ALLOC(strlen(url)); if (finalPath == NULL) return NULL; strcpy(finalPath, url+6+1); return finalPath; } static char* NET_GetURLFromLocalFile(char *filename) { /* file:///0 */ char * finalPath = (char*)XP_ALLOC(strlen(filename) + 8 + 1); if (finalPath == NULL) return NULL; finalPath[0] = 0; strcat(finalPath, "file://"); strcat(finalPath, filename); return finalPath; } #endif /* XP_MAC */ void MIME_ConformToStandard (XP_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 PREF_GetBoolPref("mail.strictly_mime_headers", &mime_headers_use_quoted_printable_p); mime_use_quoted_printable_p = conform_p; } MSG_SendMimeDeliveryState::MSG_SendMimeDeliveryState() { m_pane = NULL; /* Pane to use when loading the URLs */ m_fe_data = NULL; /* passed in and passed to callback */ m_fields = NULL; /* Where to send the message once it's done */ m_dont_deliver_p = FALSE; m_deliver_mode = MSG_DeliverNow; m_attachments_only_p = FALSE; m_pre_snarfed_attachments_p = FALSE; m_digest_p = FALSE; m_be_synchronous_p = FALSE; HG54689 m_attachment1_type = 0; m_attachment1_encoding = 0; m_attachment1_encoder_data = NULL; m_attachment1_body = 0; m_attachment1_body_length = 0; m_attachment_count = 0; m_attachment_pending_count = 0; m_attachments = NULL; m_status = 0; m_message_delivery_done_callback = NULL; m_attachments_done_callback = NULL; m_msg_file_name = NULL; m_msg_file = 0; m_plaintext = NULL; m_html_filename = NULL; //#ifdef MSG_SEND_MULTIPART_RELATED m_related_part = NULL; //#endif m_imapFolderInfo = NULL; m_imapOutgoingParser = NULL; m_imapLocalMailDB = NULL; } MSG_SendMimeDeliveryState::~MSG_SendMimeDeliveryState() { //#ifdef MSG_SEND_MULTIPART_RELATED #if 0 if (m_related_part) { // m_related_part (if it exists) gets deleted when // m_related_saver gets torched. delete m_related_part; m_related_part = NULL; } #endif //#endif FREEIF(m_msg_file_name); } static char *mime_mailto_stream_read_buffer = 0; static char *mime_mailto_stream_write_buffer = 0; static void mime_attachment_url_exit (URL_Struct *url, int status, MWContext *context); static void mime_text_attachment_url_exit (PrintSetup *p); static int 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); static char *mime_generate_headers (MSG_CompositionFields *fields, int csid, MSG_Deliver_Mode deliver_mode); static char *mime_generate_attachment_headers (const char *type, const char *encoding, const char *description, const char *x_mac_type, const char *x_mac_creator, const char *real_name, const char *base_url, XP_Bool digest_p, MSG_DeliverMimeAttachment *ma, int16 mail_csid); static char *RFC2231ParmFolding(const char *parmName, const char *charset, const char *language, const char *parmValue); #if 0 static XP_Bool mime_type_conversion_possible (const char *from_type, const char *to_type); #endif #ifdef XP_UNIX extern "C" void XFE_InitializePrintSetup (PrintSetup *p); #endif /* XP_UNIX */ extern "C" char * NET_ExplainErrorDetails (int code, ...); MSG_DeliverMimeAttachment::MSG_DeliverMimeAttachment() { m_url_string = NULL; m_url = NULL; m_done = FALSE; m_type = NULL; m_override_type = NULL; m_override_encoding = NULL; m_desired_type = NULL; m_description = NULL; m_x_mac_type = NULL; m_x_mac_creator = NULL; m_encoding = NULL; m_real_name = NULL; m_mime_delivery_state = NULL; m_encoding = NULL; m_already_encoded_p = FALSE; m_file_name = NULL; m_file = 0; #ifdef XP_MAC m_ap_filename = NULL; #endif HG54897 m_size = 0; m_unprintable_count = 0; m_highbit_count = 0; m_ctl_count = 0; m_null_count = 0; m_current_column = 0; m_max_column = 0; m_lines = 0; m_encoder_data = NULL; XP_MEMSET(&m_print_setup, 0, sizeof(m_print_setup)); m_graph_progress_started = FALSE; } MSG_DeliverMimeAttachment::~MSG_DeliverMimeAttachment() { } extern "C" char * mime_make_separator(const char *prefix) { unsigned char rand_buf[13]; RNG_GenerateGlobalRandomBytes((void *) 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]); } XP_Bool MSG_DeliverMimeAttachment::UseUUEncode_p(void) { XP_Bool returnVal = (m_mime_delivery_state) && (m_mime_delivery_state->m_pane) && ((MSG_CompositionPane*)(m_mime_delivery_state->m_pane))-> GetCompBoolHeader(MSG_UUENCODE_BINARY_BOOL_HEADER_MASK); return returnVal; } static void msg_escape_file_name (URL_Struct *m_url) { XP_ASSERT (m_url->address && !XP_STRNCASECMP(m_url->address, "file:", 5)); if (!m_url->address || XP_STRNCASECMP(m_url->address, "file:", 5)) return; char * new_address = NET_Escape(XP_STRCHR(m_url->address, ':')+1, URL_PATH); XP_ASSERT(new_address); if (!new_address) return; XP_FREEIF(m_url->address); m_url->address = PR_smprintf("file:%s", new_address); XP_FREEIF(new_address); } // Returns a newly-allocated string containing the MIME type to be used for // outgoing attachments of the given document type. The way this is determined // will be platform-specific, based on the real filename of the file (i.e. not the temp filename) // and some Mac creator info. // If there is no default specified in the prefs, then this returns NULL. static char *msg_GetMissionControlledOutgoingMIMEType(const char *filename, const char *x_mac_type, const char *x_mac_creator) { if (!filename) return NULL; #ifdef XP_WIN char *whereDot = XP_STRRCHR(filename, '.'); if (whereDot) { char *extension = whereDot + 1; if (extension) { char *mcOutgoingMimeType = NULL; char *prefString = PR_smprintf("mime.table.extension.%s.outgoing_default_type",extension); if (prefString) { PREF_CopyCharPref(prefString, &mcOutgoingMimeType); XP_FREE(prefString); } return mcOutgoingMimeType; } else return NULL; // no file extension } else return NULL; // no file extension #endif return NULL; } static char * RFC2231ParmFolding(const char *parmName, const char *charset, const char *language, const char *parmValue) { #define MAX_FOLDING_LEN 75 // this is to gurantee the folded line will // never be greater than 78 = 75 + CRLFLWSP char *foldedParm = NULL; char *dupParm = NULL; int32 parmNameLen = 0; int32 parmValueLen = 0; int32 charsetLen = 0; int32 languageLen = 0; XP_Bool needEscape = FALSE; XP_ASSERT(parmName && *parmName && parmValue && *parmValue); if (!parmName || !*parmName || !parmValue || !*parmValue) return NULL; if ((charset && *charset) || (language && *language)) needEscape = TRUE; if (needEscape) dupParm = NET_Escape(parmValue, URL_PATH); else dupParm = XP_STRDUP(parmValue); if (!dupParm) return NULL; parmValueLen = XP_STRLEN(dupParm); parmNameLen = XP_STRLEN(parmName); if (needEscape) parmNameLen += 5; // *=__'__'___ or *[0]*=__'__'__ or *[1]*=___ else parmNameLen += 5; // *[0]="___"; charsetLen = charset ? XP_STRLEN(charset) : 0; languageLen = language ? XP_STRLEN(language) : 0; if ((parmValueLen + parmNameLen + charsetLen + languageLen) < MAX_FOLDING_LEN) { StrAllocCopy(foldedParm, parmName); if (needEscape) { StrAllocCat(foldedParm, "*="); if (charsetLen) StrAllocCat(foldedParm, charset); StrAllocCat(foldedParm, "'"); if (languageLen) StrAllocCat(foldedParm, language); StrAllocCat(foldedParm, "'"); } else StrAllocCat(foldedParm, "=\""); StrAllocCat(foldedParm, dupParm); if (!needEscape) StrAllocCat(foldedParm, "\""); goto done; } else { int curLineLen = 0; int counter = 0; char digits[32]; char *start = dupParm; char *end = NULL; char tmp = 0; while (parmValueLen > 0) { curLineLen = 0; if (counter == 0) { StrAllocCopy (foldedParm, parmName); } else { if (needEscape) StrAllocCat(foldedParm, "\r\n "); else StrAllocCat(foldedParm, ";\r\n "); StrAllocCat(foldedParm, parmName); } XP_SPRINTF(digits, "*%d", counter); StrAllocCat(foldedParm, digits); curLineLen += XP_STRLEN(digits); if (needEscape) { StrAllocCat(foldedParm, "*="); if (counter == 0) { if (charsetLen) StrAllocCat(foldedParm, charset); StrAllocCat(foldedParm, "'"); if (languageLen) StrAllocCat(foldedParm, language); StrAllocCat (foldedParm, "'"); curLineLen += charsetLen; curLineLen += languageLen; } } else { StrAllocCat(foldedParm, "=\""); } counter++; curLineLen += parmNameLen; if (parmValueLen <= MAX_FOLDING_LEN - curLineLen) end = start + parmValueLen; else end = start + (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 = NULL; } else if (end-1 > start && *(end-1) == '%') { end -= 1; tmp = '%'; *end = NULL; } else if (end-2 > start && *(end-2) == '%') { end -= 2; tmp = '%'; *end = NULL; } else { tmp = *end; *end = NULL; } } else { tmp = *end; *end = NULL; } StrAllocCat(foldedParm, start); if (!needEscape) StrAllocCat(foldedParm, "\""); parmValueLen -= (end-start); if (tmp) *end = tmp; start = end; } } done: XP_FREEIF(dupParm); return foldedParm; } int32 MSG_DeliverMimeAttachment::SnarfAttachment () { int32 status = 0; XP_ASSERT (! m_done); m_file_name = WH_TempName (xpFileToPost, "nsmail"); if (! m_file_name) return (MK_OUT_OF_MEMORY); m_file = XP_FileOpen (m_file_name, xpFileToPost, XP_FILE_WRITE_BIN); if (! m_file) return MK_UNABLE_TO_OPEN_TMP_FILE; /* #### how do we pass file name? */ m_url->fe_data = this; /* #### m_type is still unknown at this point. We need to find a way to make the textfe not blow up on documents that are not text/html! */ #ifdef XP_MAC if (NET_IsLocalFileURL(m_url->address) && // do we need to add IMAP: to this list? NET_IsLocalFileURL returns FALSE always for IMAP - DMB (strncasecomp(m_url->address, "mailbox:", 8) != 0)) { /* convert the apple file to AppleDouble first, and then patch the address in the url. */ char* src_filename = NET_GetLocalFileFromURL (m_url->address); // ### mwelch Only use appledouble if we aren't uuencoding. if(isMacFile(src_filename) && (! UseUUEncode_p())) { char *separator, tmp[128]; NET_StreamClass *ad_encode_stream; separator = mime_make_separator("ad"); if (!separator) return MK_OUT_OF_MEMORY; m_ap_filename = WH_TempName (xpFileToPost, "nsmail"); ad_encode_stream = (NET_StreamClass *) /* need a prototype */ fe_MakeAppleDoubleEncodeStream (FO_CACHE_AND_MAIL_TO, NULL, m_url, m_mime_delivery_state->GetContext(), src_filename, m_ap_filename, separator); if (ad_encode_stream == NULL) { FREEIF(separator); return MK_OUT_OF_MEMORY; } do { status = (*ad_encode_stream->put_block) ((NET_StreamClass *)ad_encode_stream->data_object, NULL, 1024); } while (status == noErr); if (status >= 0) ad_encode_stream->complete ((NET_StreamClass *)ad_encode_stream->data_object); else ad_encode_stream->abort ((NET_StreamClass *)ad_encode_stream->data_object, status); XP_FREE(ad_encode_stream); if (status < 0) { FREEIF(separator); return status; } XP_FREE(m_url->address); { char * temp = WH_FileName(m_ap_filename, xpFileToPost ); m_url->address = XP_PlatformFileToURL(temp); // jrm 97/02/08 if (temp) XP_FREE(temp); } /* and also patch the types. */ if (m_type) XP_FREE (m_type); XP_SPRINTF(tmp, MULTIPART_APPLEDOUBLE ";\r\n boundary=\"%s\"", separator); FREEIF(separator); m_type = XP_STRDUP(tmp); } else { if (isMacFile(src_filename)) { // The only time we want to send just the data fork of a two-fork // Mac file is if uuencoding has been requested. XP_ASSERT(UseUUEncode_p()); if (!((MSG_CompositionPane *) m_mime_delivery_state->m_pane)->m_confirmed_uuencode_p) { XP_Bool confirmed = FE_Confirm(m_mime_delivery_state->m_pane->GetContext(), XP_GetString(MK_MSG_MAC_PROMPT_UUENCODE)); // only want to do this once ((MSG_CompositionPane *) m_mime_delivery_state->m_pane)->m_confirmed_uuencode_p = TRUE; if (! confirmed) // cancelled return MK_INTERRUPTED; } } /* make sure the file type and create are set. */ char filetype[32]; FSSpec fsSpec; FInfo info; Bool useDefault; char *macType, *macEncoding; my_FSSpecFromPathname(src_filename, &fsSpec); if (FSpGetFInfo (&fsSpec, &info) == noErr) { XP_SPRINTF(filetype, "%X", info.fdType); m_x_mac_type = XP_STRDUP(filetype); XP_SPRINTF(filetype, "%X", info.fdCreator); m_x_mac_creator = XP_STRDUP(filetype); if (m_type == NULL || !strcasecomp (m_type, TEXT_PLAIN)) { # define TEXT_TYPE 0x54455854 /* the characters 'T' 'E' 'X' 'T' */ # define text_TYPE 0x74657874 /* the characters 't' 'e' 'x' 't' */ if (info.fdType != TEXT_TYPE && info.fdType != text_TYPE) { FE_FileType(m_url->address, &useDefault, &macType, &macEncoding); FREEIF(m_type); m_type = macType; } } } /* don't bother to set the types if we failed in getting the file info. */ } FREEIF(src_filename); src_filename = 0; } #else /* if we are attaching a local file make sure the file name are escaped * properly */ if (NET_IsLocalFileURL(m_url->address) && XP_STRNCASECMP (m_url->address, "file:", 5) == 0) { msg_escape_file_name(m_url); } #endif /* XP_MAC */ if (m_desired_type && !strcasecomp (m_desired_type, TEXT_PLAIN) /* #### && mime_type_conversion_possible (m_type, m_desired_type) */ ) { /* Conversion to plain text desired. */ m_print_setup.url = m_url; m_print_setup.carg = this; m_print_setup.completion = mime_text_attachment_url_exit; m_print_setup.filename = NULL; m_print_setup.out = m_file; m_print_setup.eol = CRLF; int32 width = 72; PREF_GetIntPref("mailnews.wraplength", &width); if (width == 0) width = 72; else if (width < 10) width = 10; else if (width > 30000) width = 30000; if (m_mime_delivery_state->m_pane->GetPaneType() == MSG_COMPOSITIONPANE) { int lineWidth = ((MSG_CompositionPane *) m_mime_delivery_state->m_pane) ->GetLineWidth(); if (lineWidth > width) width = lineWidth; } m_print_setup.width = width; m_url->savedData.FormList = 0; #ifdef _USRDLL if (! NDLLXL_TranslateText (m_mime_delivery_state->GetContext(), m_url, &m_print_setup)) return MK_ATTACHMENT_LOAD_FAILED; #else if (! XL_TranslateText (m_mime_delivery_state->GetContext(), m_url, &m_print_setup)) return MK_ATTACHMENT_LOAD_FAILED; #endif if (m_type) XP_FREE (m_type); m_type = m_desired_type; m_desired_type = 0; if (m_encoding) XP_FREE (m_encoding); m_encoding = 0; } #ifdef XP_UNIX else if (m_desired_type && !strcasecomp (m_desired_type, APPLICATION_POSTSCRIPT) /* #### && mime_type_conversion_possible (m_type, m_desired_type) */ ) { SHIST_SavedData saved_data; /* Make sure layout saves the current state of form elements. */ LO_SaveFormData(m_mime_delivery_state->GetContext()); /* Hold on to the saved data. */ XP_MEMCPY(&saved_data, &m_url->savedData, sizeof(SHIST_SavedData)); /* Conversion to postscript desired. */ XFE_InitializePrintSetup (&m_print_setup); m_print_setup.url = m_url; m_print_setup.carg = this; m_print_setup.completion = mime_text_attachment_url_exit; m_print_setup.filename = NULL; m_print_setup.out = m_file; m_print_setup.eol = CRLF; XP_MEMSET (&m_url->savedData, 0, sizeof (SHIST_SavedData)); XL_TranslatePostscript (m_mime_delivery_state->GetContext(), m_url, &saved_data, &m_print_setup); if (m_type) XP_FREE (m_type); m_type = m_desired_type; m_desired_type = 0; if (m_encoding) XP_FREE (m_encoding); m_encoding = 0; } #endif /* XP_UNIX */ else { int get_url_status; /* In this case, ignore the status, as that will be handled by the exit routine. */ /* jwz && tj -> we're assuming that it is safe to return the result of this call as our status result. A negative result means that the exit routine was run, either because the operation completed quickly or failed. */ m_url->allow_content_change = FALSE; // don't use modified content get_url_status = NET_GetURL (m_url, FO_CACHE_AND_MAIL_TO, m_mime_delivery_state->GetContext(), mime_attachment_url_exit); if (get_url_status < 0) return MK_ATTACHMENT_LOAD_FAILED; else return 0; } return status; } static void mime_attachment_url_exit (URL_Struct *url, int status, MWContext *context) { MSG_DeliverMimeAttachment *ma = (MSG_DeliverMimeAttachment *) url->fe_data; XP_ASSERT(ma != NULL); if (ma != NULL) ma->UrlExit(url, status, context); } void MSG_DeliverMimeAttachment::UrlExit(URL_Struct *url, int status, MWContext *context) { char *error_msg = url->error_msg; url->error_msg = 0; url->fe_data = 0; XP_ASSERT(m_mime_delivery_state != NULL); XP_ASSERT(m_mime_delivery_state->GetContext() != NULL); XP_ASSERT(m_url != NULL); if (m_graph_progress_started) { m_graph_progress_started = FALSE; FE_GraphProgressDestroy (m_mime_delivery_state->GetContext(), m_url, m_url->content_length, m_size); } if (status < 0) /* If any of the attachment URLs fail, kill them all. */ NET_InterruptWindow (context); /* Close the file, but don't delete it (or the file name.) */ XP_FileClose (m_file); m_file = 0; NET_FreeURLStruct (m_url); /* I'm pretty sure m_url == url */ m_url = 0; url = 0; if (status < 0) { if (m_mime_delivery_state->m_status >= 0) m_mime_delivery_state->m_status = status; XP_FileRemove(m_file_name, xpFileToPost); XP_FREEIF(m_file_name); } m_done = TRUE; XP_ASSERT (m_mime_delivery_state->m_attachment_pending_count > 0); m_mime_delivery_state->m_attachment_pending_count--; if (status >= 0 && m_mime_delivery_state->m_be_synchronous_p) { /* Find the next attachment which has not yet been loaded, if any, and start it going. */ int32 i; MSG_DeliverMimeAttachment *next = 0; for (i = 0; i < m_mime_delivery_state->m_attachment_count; i++) if (!m_mime_delivery_state->m_attachments[i].m_done) { next = &m_mime_delivery_state->m_attachments[i]; break; } if (next) { int status = next->SnarfAttachment (); if (status < 0) { m_mime_delivery_state->Fail(status, 0); return; } } } if (m_mime_delivery_state->m_attachment_pending_count == 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 (status < 0) { m_mime_delivery_state->Fail(status, error_msg); error_msg = 0; } else { m_mime_delivery_state->GatherMimeAttachments (); } } else { /* If this is not the last attachment, but it got an error, then report that error and continue (we won't actually abort the delivery until all the other pending URLs have caught up with the NET_InterruptWindow() we did up above.) */ if (status < 0 && error_msg) FE_Alert (context, error_msg); } FREEIF (error_msg); } void MSG_DeliverMimeAttachment::AnalyzeDataChunk(const char *chunk, int32 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 != CR && *s != LF) { m_unprintable_count++; m_ctl_count++; if (*s == 0) m_null_count++; } if (*s == CR || *s == LF) { if (s+1 < end && s[0] == CR && s[1] == LF) s++; if (m_max_column < m_current_column) m_max_column = m_current_column; m_current_column = 0; m_lines++; } else { m_current_column++; } } } void MSG_DeliverMimeAttachment::AnalyzeSnarfedFile(void) { char chunk[256]; XP_File fileHdl = NULL; int32 numRead = 0; if (m_file_name && *m_file_name) { fileHdl = XP_FileOpen(m_file_name, xpFileToPost, XP_FILE_READ_BIN); if (fileHdl) { do { numRead = XP_FileRead(chunk, 256, fileHdl); if (numRead > 0) AnalyzeDataChunk(chunk, numRead); } while (numRead > 0); XP_FileClose(fileHdl); } } } static void mime_text_attachment_url_exit (PrintSetup *p) { MSG_DeliverMimeAttachment *ma = (MSG_DeliverMimeAttachment *) p->carg; XP_ASSERT (p->url == ma->m_url); ma->m_url->fe_data = ma; /* grr */ mime_attachment_url_exit (p->url, p->status, ma->m_mime_delivery_state->GetContext()); } PRIVATE unsigned int mime_attachment_stream_write_ready (NET_StreamClass *stream) { return MAX_WRITE_READY; } PRIVATE int mime_attachment_stream_write (NET_StreamClass *stream, const char *block, int32 length) { MSG_DeliverMimeAttachment *ma = (MSG_DeliverMimeAttachment *) stream->data_object; /* const unsigned char *s; const unsigned char *end; */ if (ma->m_mime_delivery_state->m_status < 0) return ma->m_mime_delivery_state->m_status; ma->m_size += length; if (!ma->m_graph_progress_started) { ma->m_graph_progress_started = TRUE; FE_GraphProgressInit (ma->m_mime_delivery_state->GetContext(), ma->m_url, ma->m_url->content_length); } FE_GraphProgress (ma->m_mime_delivery_state->GetContext(), ma->m_url, ma->m_size, length, ma->m_url->content_length); /* Copy out the content type and encoding if we haven't already. */ if (!ma->m_type && ma->m_url->content_type) { ma->m_type = XP_STRDUP (ma->m_url->content_type); /* If the URL has an encoding, and it's not one of the "null" encodings, then keep it. */ if (ma->m_url->content_encoding && strcasecomp (ma->m_url->content_encoding, ENCODING_7BIT) && strcasecomp (ma->m_url->content_encoding, ENCODING_8BIT) && strcasecomp (ma->m_url->content_encoding, ENCODING_BINARY)) { if (ma->m_encoding) XP_FREE (ma->m_encoding); ma->m_encoding = XP_STRDUP (ma->m_url->content_encoding); ma->m_already_encoded_p = TRUE; } /* Make sure there's a string in the type field. Note that UNKNOWN_CONTENT_TYPE and APPLICATION_OCTET_STREAM are different; "octet-stream" means that this document was *specified* as an anonymous binary type; "unknown" means that we will guess whether it is text or binary based on its contents. */ if (!ma->m_type || !*ma->m_type) StrAllocCopy (ma->m_type, UNKNOWN_CONTENT_TYPE); #if defined(XP_WIN) || defined(XP_OS2) /* WinFE tends to spew out bogus internal "zz-" types for things it doesn't know, so map those to the "real" unknown type. */ if (ma->m_type && !strncasecomp (ma->m_type, "zz-", 3)) StrAllocCopy (ma->m_type, UNKNOWN_CONTENT_TYPE); #endif /* XP_WIN */ /* There are some of "magnus" types in the default mime.types file that some platforms ship in /usr/local/lib/netscape/. These types are meaningless to the end user, and the server never returns them, but they're getting attached to local .cgi files anyway. Remove them. */ if (ma->m_type && !strncasecomp (ma->m_type, "magnus-internal/", 16)) StrAllocCopy (ma->m_type, UNKNOWN_CONTENT_TYPE); /* kludge. Unfortunately, the URL_Struct shares the `encoding' slot amongst the Content-Encoding and Content-Transfer-Encoding headers. Content-Transfer-Encoding is required to be one of the standard MIME encodings (x- types are explicitly discourgaged.) But Content-Encoding can be anything (it's HTTP, not MIME.) So, to prevent binary compressed data from getting dumped into the mail stream, we special case some things here. If the encoding is "x-compress" or "x-gzip", then that must have come from a Content-Encoding header, So change the type to application/x-compress and allow it to be encoded in base64. But what if it's something we don't know? In that case, we just dump it into the mail. For Content-Transfer-Encodings, like for example, x-uuencode, that's appropriate. But for Content-Encodings, like for example, x-some-brand-new-binary-compression-algorithm, that's wrong. */ if (ma->m_encoding && (!strcasecomp (ma->m_encoding, ENCODING_COMPRESS) || !strcasecomp (ma->m_encoding, ENCODING_COMPRESS2))) { StrAllocCopy (ma->m_type, APPLICATION_COMPRESS); StrAllocCopy (ma->m_encoding, ENCODING_BINARY); ma->m_already_encoded_p = FALSE; } else if (ma->m_encoding && (!strcasecomp (ma->m_encoding, ENCODING_GZIP) || !strcasecomp (ma->m_encoding, ENCODING_GZIP2))) { StrAllocCopy (ma->m_type, APPLICATION_GZIP); StrAllocCopy (ma->m_encoding, ENCODING_BINARY); ma->m_already_encoded_p = FALSE; } /* If the caller has passed in an overriding type for this URL, then ignore what the netlib guessed it to be. This is so that we can hand it a file:/some/tmp/file and tell it that it's of type message/rfc822 without having to depend on that tmp file having some particular extension. */ if (ma->m_override_type) { StrAllocCopy (ma->m_type, ma->m_override_type); if (ma->m_override_encoding) StrAllocCopy (ma->m_encoding, ma->m_override_encoding); } char *mcType = msg_GetMissionControlledOutgoingMIMEType(ma->m_real_name, ma->m_x_mac_type, ma->m_x_mac_creator); // returns an allocated string if (mcType) { FREEIF(ma->m_type); ma->m_type = mcType; } } /* Cumulatively examine the data that is passing through this stream, building up a histogram that will be used when deciding which encoding (if any) should be used. */ ma->AnalyzeDataChunk(block, length); /* calling this instead of the previous 20ish lines */ /* Write it to the file. */ while (length > 0) { int32 l; l = XP_FileWrite (block, length, ma->m_file); if (l < length) { ma->m_mime_delivery_state->m_status = MK_MIME_ERROR_WRITING_FILE; return ma->m_mime_delivery_state->m_status; } block += l; length -= l; } return 1; } PRIVATE void mime_attachment_stream_complete (NET_StreamClass *stream) { /* Nothing to do here - the URL exit method does our cleanup. */ } PRIVATE void mime_attachment_stream_abort (NET_StreamClass *stream, int status) { MSG_DeliverMimeAttachment *ma = (MSG_DeliverMimeAttachment *) stream->data_object; if (ma->m_mime_delivery_state->m_status >= 0) ma->m_mime_delivery_state->m_status = status; /* Nothing else to do here - the URL exit method does our cleanup. */ } #ifdef XP_OS2 //DSR040297 - This looks pretty bad, but some compilers are very type //strict when it comes to function pointers & the like... So this must be extern & extern "C" to match. XP_BEGIN_PROTOS extern NET_StreamClass * mime_make_attachment_stream (int /*format_out*/, void * /*closure*/, URL_Struct *url, MWContext *context); XP_END_PROTOS extern NET_StreamClass * #else static NET_StreamClass * #endif mime_make_attachment_stream (int /*format_out*/, void * /*closure*/, URL_Struct *url, MWContext *context) { NET_StreamClass *stream; TRACEMSG(("Setting up attachment stream. Have URL: %s\n", url->address)); stream = XP_NEW (NET_StreamClass); if (stream == NULL) return (NULL); XP_MEMSET (stream, 0, sizeof (NET_StreamClass)); stream->name = "attachment stream"; stream->complete = mime_attachment_stream_complete; stream->abort = mime_attachment_stream_abort; stream->put_block = mime_attachment_stream_write; stream->is_write_ready = mime_attachment_stream_write_ready; stream->data_object = url->fe_data; stream->window_id = context; TRACEMSG(("Returning stream from mime_make_attachment_stream")); return stream; } HG55451 void MSG_RegisterConverters (void) { NET_RegisterContentTypeConverter ("*", FO_MAIL_TO, NULL, mime_make_attachment_stream); NET_RegisterContentTypeConverter ("*", FO_CACHE_AND_MAIL_TO, NULL, mime_make_attachment_stream); /* FO_MAIL_MESSAGE_TO is treated the same as FO_MAIL_TO -- this format_out just means that libmime has already gotten its hands on this document (which happens to be of type message/rfc822 or message/news) and has altered it in some way (for example, has decrypted it.) */ NET_RegisterContentTypeConverter ("*", FO_MAIL_MESSAGE_TO, NULL, mime_make_attachment_stream); NET_RegisterContentTypeConverter ("*", FO_CACHE_AND_MAIL_MESSAGE_TO, NULL, mime_make_attachment_stream); /* Attachment of mail and news messages happens slightly differently: Rather than FO_MAIL_TO going in to mime_make_attachment_stream, it goes into MIME_MessageConverter, which will then open a later stream with FO_MAIL_MESSAGE_TO -- which is how it eventually gets into mime_make_attachment_stream, after having gone through libmime. */ NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_MAIL_TO, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_MAIL_TO, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_CACHE_AND_MAIL_TO, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_CACHE_AND_MAIL_TO, NULL, MIME_MessageConverter); /* for saving news messages */ NET_RegisterContentTypeConverter ("*", FO_APPEND_TO_FOLDER, NULL, msg_MakeAppendToFolderStream); NET_RegisterContentTypeConverter ("*", FO_CACHE_AND_APPEND_TO_FOLDER, NULL, msg_MakeAppendToFolderStream); /* Decoders from mimehtml.c for message/rfc822 */ NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_PRESENT, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_PRINT, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_EMBED, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_QUOTE_MESSAGE, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_QUOTE_HTML_MESSAGE, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_SAVE_AS, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_SAVE_AS_TEXT, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_INTERNAL_IMAGE, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_FONT, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_CMDLINE_ATTACHMENTS, NULL, MIME_ToDraftConverter); NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_OPEN_DRAFT, NULL, MIME_ToDraftConverter); #ifdef XP_UNIX NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_SAVE_AS_POSTSCRIPT, NULL, MIME_MessageConverter); #endif /* XP_UNIX */ /* Decoders from mimehtml.c for message/news (same as message/rfc822) */ NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_PRESENT, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_PRINT, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_EMBED, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_QUOTE_MESSAGE, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_QUOTE_HTML_MESSAGE, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_SAVE_AS, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_SAVE_AS_TEXT, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_INTERNAL_IMAGE, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_OPEN_DRAFT, NULL, MIME_ToDraftConverter); #ifdef XP_UNIX NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_SAVE_AS_POSTSCRIPT, NULL, MIME_MessageConverter); #endif /* XP_UNIX */ /* Decoders from mimehtml.c for text/richtext and text/enriched */ NET_RegisterContentTypeConverter (TEXT_RICHTEXT, FO_PRESENT, NULL, MIME_RichtextConverter); NET_RegisterContentTypeConverter (TEXT_RICHTEXT, FO_PRINT, NULL, MIME_RichtextConverter); NET_RegisterContentTypeConverter (TEXT_ENRICHED, FO_PRESENT, NULL, MIME_EnrichedTextConverter); NET_RegisterContentTypeConverter (TEXT_ENRICHED, FO_PRINT, NULL, MIME_EnrichedTextConverter); /* Decoders from mimevcrd.c for text/x-vcard and text/enriched */ NET_RegisterContentTypeConverter (TEXT_VCARD, FO_PRESENT, NULL, MIME_VCardConverter); NET_RegisterContentTypeConverter (TEXT_VCARD, FO_PRINT, NULL, MIME_VCardConverter); /* Decoders from mimejul.c for text/calendar */ #ifdef MOZ_CALENDAR NET_RegisterContentTypeConverter (TEXT_CALENDAR, FO_PRESENT, NULL, MIME_JulianConverter); NET_RegisterContentTypeConverter (TEXT_CALENDAR, FO_PRINT, NULL, MIME_JulianConverter); #endif } static XP_Bool mime_7bit_data_p (const char *string, uint32 size) { const unsigned char *s = (const unsigned char *) string; const unsigned char *end = s + size; if (s) for (; s < end; s++) if (*s > 0x7F) return FALSE; return TRUE; } static int 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 (XP_IS_SPACE (*from)) from++; if (reply_to) while (XP_IS_SPACE (*reply_to)) reply_to++; if (to) while (XP_IS_SPACE (*to)) to++; if (cc) while (XP_IS_SPACE (*cc)) cc++; if (bcc) while (XP_IS_SPACE (*bcc)) bcc++; if (fcc) while (XP_IS_SPACE (*fcc)) fcc++; if (newsgroups) while (XP_IS_SPACE (*newsgroups)) newsgroups++; if (followup_to) while (XP_IS_SPACE (*followup_to)) followup_to++; /* #### check other_random_headers for newline conventions */ if (!from || !*from) return MK_MIME_NO_SENDER; else if ((!to || !*to) && (!cc || !*cc) && (!bcc || !*bcc) && (!newsgroups || !*newsgroups)) return MK_MIME_NO_RECIPIENTS; else return 0; } /* 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. */ static char * mime_fix_header_1 (const char *string, XP_Bool addr_p, XP_Bool news_p) { char *new_string; const char *in; char *out; int32 i, old_size, new_size; if (!string || !*string) return 0; if (addr_p) { char *n = MSG_ReformatRFC822Addresses (string); if (n) return n; } old_size = XP_STRLEN (string); new_size = old_size; for (i = 0; i < old_size; i++) if (string[i] == CR || string[i] == LF) new_size += 2; new_string = (char *) XP_ALLOC (new_size + 1); if (! new_string) return 0; in = string; out = new_string; /* strip leading whitespace. */ while (XP_IS_SPACE (*in)) in++; /* replace CR, LF, or CRLF with CRLF-TAB. */ while (*in) { if (*in == CR || *in == LF) { if (*in == CR && in[1] == LF) in++; in++; *out++ = CR; *out++ = LF; *out++ = '\t'; } else if (news_p && *in == ',') { *out++ = *in++; /* skip over all whitespace after a comma. */ while (XP_IS_SPACE (*in)) in++; } else { *out++ = *in++; } } *out = 0; /* strip trailing whitespace. */ while (out > in && XP_IS_SPACE (out[-1])) *out-- = 0; /* If we ended up throwing it all away, use 0 instead of "". */ if (!*new_string) { XP_FREE (new_string); new_string = 0; } return new_string; } static char * mime_fix_header (const char *string) { return mime_fix_header_1 (string, FALSE, FALSE); } static char * mime_fix_addr_header (const char *string) { return mime_fix_header_1 (string, TRUE, FALSE); } static char * mime_fix_news_header (const char *string) { return mime_fix_header_1 (string, FALSE, TRUE); } #if 0 static XP_Bool mime_type_conversion_possible (const char *from_type, const char *to_type) { if (! to_type) return TRUE; if (! from_type) return FALSE; if (!strcasecomp (from_type, to_type)) /* Don't run text/plain through the text->html converter. */ return FALSE; if ((!strcasecomp (from_type, INTERNAL_PARSER) || !strcasecomp (from_type, TEXT_HTML) || !strcasecomp (from_type, TEXT_MDL)) && !strcasecomp (to_type, TEXT_PLAIN)) /* Don't run UNKNOWN_CONTENT_TYPE through the text->html converter (treat it as text/plain already.) */ return TRUE; #ifdef XP_UNIX if ((!strcasecomp (from_type, INTERNAL_PARSER) || !strcasecomp (from_type, TEXT_PLAIN) || !strcasecomp (from_type, TEXT_HTML) || !strcasecomp (from_type, TEXT_MDL) || !strcasecomp (from_type, IMAGE_GIF) || !strcasecomp (from_type, IMAGE_JPG) || !strcasecomp (from_type, IMAGE_PJPG) || !strcasecomp (from_type, IMAGE_XBM) || !strcasecomp (from_type, IMAGE_XBM2) || !strcasecomp (from_type, IMAGE_XBM3) || /* always treat unknown content types as text/plain */ !strcasecomp (from_type, UNKNOWN_CONTENT_TYPE) ) && !strcasecomp (to_type, APPLICATION_POSTSCRIPT)) return TRUE; #endif /* XP_UNIX */ return FALSE; } #endif static XP_Bool mime_type_requires_b64_p (const char *type) { if (!type || !strcasecomp (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 (!strncasecomp (type, "image/", 6) || !strncasecomp (type, "audio/", 6) || !strncasecomp (type, "video/", 6) || !strncasecomp (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/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/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 (!strcasecomp (type, *s)) return FALSE; /* All others must be assumed to be binary formats, and need Base64. */ return TRUE; } else return FALSE; } #ifdef XP_OS2 XP_BEGIN_PROTOS extern int mime_encoder_output_fn (const char *buf, int32 size, void *closure); XP_END_PROTOS #else static int mime_encoder_output_fn (const char *buf, int32 size, void *closure); #endif /* Given a content-type and some info about the contents of the document, decide what encoding it should have. */ int MSG_DeliverMimeAttachment::PickEncoding (int16 mail_csid) { // use the boolean so we only have to test for uuencode vs base64 once XP_Bool needsB64 = FALSE; if (m_already_encoded_p) goto DONE; if (mime_type_requires_b64_p (m_type)) { /* If the content-type is "image/" or something else known to be binary, always use base64 (so that we don't get confused by newline conversions.) */ needsB64 = TRUE; } else { /* Otherwise, we need to pick an encoding based on the contents of the document. */ XP_Bool encode_p; if (m_max_column > 900) encode_p = TRUE; else if (mime_use_quoted_printable_p && 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; #if 0 else if (m_ctl_count) /* Should we encode if random other control characters are present? Probably... */ encode_p = TRUE; #endif else encode_p = FALSE; /* MIME requires a special case that these types never be encoded. */ if (!strncasecomp (m_type, "message", 7) || !strncasecomp (m_type, "multipart", 9)) { encode_p = FALSE; if (m_desired_type && !strcasecomp (m_desired_type, TEXT_PLAIN)) { XP_FREE (m_desired_type); m_desired_type = 0; } } /* If the Mail csid is CS_JIS we force it to use Base64 for attachments (bug#104255). Use 7 bit for other STATFUL ( e.g. CS_2022_KR) csid. */ if ((mail_csid == CS_JIS) && (strcasecomp(m_type, TEXT_HTML) == 0)) needsB64 = TRUE; else if((mail_csid & STATEFUL) && ((strcasecomp(m_type, TEXT_HTML) == 0) || (strcasecomp(m_type, TEXT_MDL) == 0) || (strcasecomp(m_type, TEXT_PLAIN) == 0) || (strcasecomp(m_type, TEXT_RICHTEXT) == 0) || (strcasecomp(m_type, TEXT_ENRICHED) == 0) || (strcasecomp(m_type, TEXT_VCARD) == 0) || (strcasecomp(m_type, TEXT_CSS) == 0) || (strcasecomp(m_type, TEXT_JSSS) == 0) || (strcasecomp(m_type, MESSAGE_RFC822) == 0) || (strcasecomp(m_type, MESSAGE_NEWS) == 0))) { needsB64 = TRUE; } else if (encode_p && m_size > 500 && 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) StrAllocCopy (m_encoding, ENCODING_QUOTED_PRINTABLE); else if (m_highbit_count > 0) StrAllocCopy (m_encoding, ENCODING_8BIT); else StrAllocCopy (m_encoding, ENCODING_7BIT); } if (needsB64) { /* ### mwelch We might have to uuencode instead of base64 the binary data. */ if (UseUUEncode_p()) StrAllocCopy (m_encoding, ENCODING_UUENCODE); else StrAllocCopy (m_encoding, ENCODING_BASE64); } /* Now that we've picked an encoding, initialize the filter. */ XP_ASSERT(!m_encoder_data); if (!strcasecomp(m_encoding, ENCODING_BASE64)) { m_encoder_data = MimeB64EncoderInit(mime_encoder_output_fn, m_mime_delivery_state); if (!m_encoder_data) return MK_OUT_OF_MEMORY; } else if (!strcasecomp(m_encoding, ENCODING_UUENCODE)) { char *tailName = NULL; if (m_url_string) { tailName = XP_STRRCHR(m_url_string, '/'); if (tailName) tailName = XP_STRDUP(tailName+1); } if (m_url && !tailName) { tailName = XP_STRRCHR(m_url->address, '/'); if (tailName) tailName = XP_STRDUP(tailName+1); } m_encoder_data = MimeUUEncoderInit(tailName ? tailName : "", mime_encoder_output_fn, m_mime_delivery_state); XP_FREEIF(tailName); if (!m_encoder_data) return MK_OUT_OF_MEMORY; } else if (!strcasecomp(m_encoding, ENCODING_QUOTED_PRINTABLE)) { m_encoder_data = MimeQPEncoderInit(mime_encoder_output_fn, m_mime_delivery_state); if (!m_encoder_data) return MK_OUT_OF_MEMORY; } else { m_encoder_data = 0; } /* 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 || !*m_type || !strcasecomp(m_type, UNKNOWN_CONTENT_TYPE)) { if (m_already_encoded_p) StrAllocCopy (m_type, APPLICATION_OCTET_STREAM); else if (m_encoding && (!strcasecomp(m_encoding, ENCODING_BASE64) || !strcasecomp(m_encoding, ENCODING_UUENCODE))) StrAllocCopy (m_type, APPLICATION_OCTET_STREAM); else StrAllocCopy (m_type, TEXT_PLAIN); } return 0; } /* Some types should have a "charset=" parameter, and some shouldn't. This is what decides. */ XP_Bool mime_type_needs_charset (const char *type) { /* Only text types should have charset. */ if (!type || !*type) return FALSE; else if (!strncasecomp (type, "text", 4)) return TRUE; else return FALSE; } char* mime_get_stream_write_buffer(void) { if (!mime_mailto_stream_write_buffer) { mime_mailto_stream_write_buffer = (char *) XP_ALLOC(MIME_BUFFER_SIZE); } return mime_mailto_stream_write_buffer; } int MSG_SendMimeDeliveryState::InitImapOfflineDB(uint32 flag) { int status = 0; char *folderName = msg_MagicFolderName(m_pane->GetMaster()->GetPrefs(), flag, &status); if (status < 0) return status; else if (!folderName) return MK_OUT_OF_MEMORY; else if (NET_URL_Type(folderName) == IMAP_TYPE_URL) { MsgERR err = eSUCCESS; char *dummyEnvelope = msg_GetDummyEnvelope(); XP_ASSERT(!m_imapOutgoingParser && !m_imapLocalMailDB); MailDB::Open(m_msg_file_name, TRUE, &m_imapLocalMailDB); if (err != eSUCCESS) { if (m_imapLocalMailDB) m_imapLocalMailDB->Close(); m_imapLocalMailDB = NULL; status = MK_OUT_OF_MEMORY; goto done; } m_imapOutgoingParser = new ParseOutgoingMessage; if (!m_imapOutgoingParser) { m_imapLocalMailDB->Close(); m_imapLocalMailDB = NULL; status = MK_OUT_OF_MEMORY; goto done; } m_imapOutgoingParser->Clear(); m_imapOutgoingParser->SetOutFile(m_msg_file); m_imapOutgoingParser->SetMailDB(m_imapLocalMailDB); m_imapOutgoingParser->SetWriteToOutFile(FALSE); m_imapOutgoingParser->Init(0); m_imapOutgoingParser->StartNewEnvelope(dummyEnvelope, XP_STRLEN(dummyEnvelope)); } done: XP_FREEIF(folderName); return status; } #define PUSH_STRING(S) \ do { XP_STRCPY (buffer_tail, S); buffer_tail += XP_STRLEN (S); } while(0) #define PUSH_NEWLINE() \ do { *buffer_tail++ = CR; *buffer_tail++ = LF; *buffer_tail = '\0'; } while(0) /* 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. */ int MSG_SendMimeDeliveryState::GatherMimeAttachments () { int32 status, i; char *headers = 0; char *separator = 0; XP_File in_file = 0; XP_Bool multipart_p = FALSE; XP_Bool plaintext_is_mainbody_p = FALSE; // only using text converted from HTML? char *buffer = 0; char *buffer_tail = 0; char* error_msg = 0; int16 win_csid = INTL_DefaultWinCharSetID(GetContext()); XP_Bool tonews = (m_fields && m_fields->GetNewsgroups() && *m_fields->GetNewsgroups()) ; // to news is true if we have a m_field and we have a Newsgroup and it is not empty INTL_MessageSendToNews(tonews); // hack to make Korean Mail/News work correctly // Look at libi18n/doc_ccc.c for details // temp solution for bug 30725 int16 mail_csid = tonews ? INTL_DefaultNewsCharSetID(win_csid) : INTL_DefaultMailCharSetID(win_csid); MSG_SendPart* toppart = NULL; // The very top most container of the message // that we are going to send. MSG_SendPart* mainbody = NULL;// The leaf node that contains the text of the // message we're going to contain. MSG_SendPart* maincontainer = NULL;// 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. MSG_SendPart* plainpart = NULL; // 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.) char *hdrs = 0; XP_Bool maincontainerISrelatedpart = FALSE; INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(GetContext()); status = m_status; if (status < 0) goto FAIL; if (m_attachments_only_p) { /* If we get here, we shouldn't have the "generating a message" cb. */ XP_ASSERT(!m_dont_deliver_p && !m_message_delivery_done_callback); if (m_attachments_done_callback) { struct MSG_AttachedFile *attachments; XP_ASSERT(m_attachment_count > 0); if (m_attachment_count <= 0) { m_attachments_done_callback (GetContext(), m_fe_data, 0, 0, 0); m_attachments_done_callback = 0; Clear(); goto FAIL; } attachments = (struct MSG_AttachedFile *) XP_ALLOC((m_attachment_count + 1) * sizeof(*attachments)); if (!attachments) goto FAILMEM; XP_MEMSET(attachments, 0, ((m_attachment_count + 1) * sizeof(*attachments))); for (i = 0; i < m_attachment_count; i++) { MSG_DeliverMimeAttachment *ma = &m_attachments[i]; #undef SNARF #define SNARF(x,y) do { if((y) && *(y) && !(x)) { ((x) = (y)); ((y) = 0); }} \ while(0) /* Rather than copying the strings and dealing with allocation failures, we'll just "move" them into the other struct (this should be ok since this file uses FREEIF when discarding the mime_attachment objects.) */ SNARF(attachments[i].orig_url, ma->m_url_string); SNARF(attachments[i].file_name, ma->m_file_name); SNARF(attachments[i].type, ma->m_type); SNARF(attachments[i].encoding, ma->m_encoding); SNARF(attachments[i].description, ma->m_description); SNARF(attachments[i].x_mac_type, ma->m_x_mac_type); SNARF(attachments[i].x_mac_creator, ma->m_x_mac_creator); #undef SNARF HG96484 attachments[i].size = ma->m_size; attachments[i].unprintable_count = ma->m_unprintable_count; attachments[i].highbit_count = ma->m_highbit_count; attachments[i].ctl_count = ma->m_ctl_count; attachments[i].null_count = ma->m_null_count; attachments[i].max_line_length = ma->m_max_column; /* Doesn't really matter, but let's not lie about encoding in case it does someday. */ if (attachments[i].highbit_count > 0 && attachments[i].encoding && !strcasecomp(attachments[i].encoding, ENCODING_7BIT)) StrAllocCopy (attachments[i].encoding, ENCODING_8BIT); } /* The callback is expected to free the attachments list and all the strings in it. It's also expected to delete the files! */ m_attachments_done_callback (GetContext(), m_fe_data, 0, 0, attachments); m_attachments_done_callback = 0; Clear(); } goto FAIL; } /* If we get here, we're generating a message, so there shouldn't be an attachments callback. */ XP_ASSERT(!m_attachments_done_callback); if (!m_attachment1_type) { m_attachment1_type = XP_STRDUP(TEXT_PLAIN); if (!m_attachment1_type) goto FAILMEM; } // 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 == NULL && (m_fields->GetForcePlainText() || m_fields->GetUseMultipartAlternative()) && m_attachment1_body && XP_STRCMP(m_attachment1_type, TEXT_HTML) == 0) { m_html_filename = WH_TempName (xpFileToPost, "nsmail"); if (!m_html_filename) goto FAILMEM; in_file = XP_FileOpen(m_html_filename, xpFileToPost, XP_FILE_WRITE_BIN); if (!in_file) { status = MK_UNABLE_TO_OPEN_TMP_FILE; goto FAIL; } status = XP_FileWrite(m_attachment1_body, m_attachment1_body_length, in_file); if (status < int(m_attachment1_body_length)) { if (status >= 0) { status = MK_MIME_ERROR_WRITING_FILE; } goto FAIL; } status = XP_FileClose(in_file); in_file = NULL; if (status < 0) goto FAIL; m_plaintext = new MSG_DeliverMimeAttachment; if (!m_plaintext) goto FAILMEM; m_plaintext->m_mime_delivery_state = this; char* temp = WH_FileName(m_html_filename, xpFileToPost); m_plaintext->m_url_string = XP_PlatformFileToURL(temp); if (temp) XP_FREE(temp); if (!m_plaintext->m_url_string) goto FAILMEM; m_plaintext->m_url = NET_CreateURLStruct (m_plaintext->m_url_string, NET_DONT_RELOAD); if (!m_plaintext->m_url) goto FAILMEM; StrAllocCopy(m_plaintext->m_url->content_type, TEXT_HTML); StrAllocCopy(m_plaintext->m_type, TEXT_HTML); StrAllocCopy(m_plaintext->m_desired_type, TEXT_PLAIN); m_attachment_pending_count++; status = m_plaintext->SnarfAttachment(); if (status < 0) goto FAIL; if (m_attachment_pending_count > 0) return 0; } /* hack to avoid having to allocate memory... */ buffer = mime_get_stream_write_buffer(); if (! buffer) goto FAILMEM; buffer_tail = buffer; XP_ASSERT (m_attachment_pending_count == 0); FE_Progress(GetContext(), XP_GetString(MK_MSG_ASSEMBLING_MSG)); /* First, open the message file. */ m_msg_file_name = WH_TempName (xpFileToPost, "nsmail"); if (! m_msg_file_name) goto FAILMEM; m_msg_file = XP_FileOpen (m_msg_file_name, xpFileToPost, XP_FILE_WRITE_BIN); if (! m_msg_file) { status = MK_UNABLE_TO_OPEN_TMP_FILE; error_msg = PR_smprintf(XP_GetString(status), m_msg_file_name); if (!error_msg) status = MK_OUT_OF_MEMORY; goto FAIL; } if (NET_IsOffline() && (m_deliver_mode == MSG_SaveAsDraft || m_deliver_mode == MSG_SaveAsTemplate)) { status = InitImapOfflineDB( m_deliver_mode == MSG_SaveAsDraft ? MSG_FOLDER_FLAG_DRAFTS : MSG_FOLDER_FLAG_TEMPLATES ); if (status < 0) { StrAllocCopy (error_msg, XP_GetString(status)); goto FAIL; } } #ifdef GENERATE_MESSAGE_ID if (m_fields->GetMessageId() == NULL) { m_fields->SetMessageId(msg_generate_message_id ()); } #endif /* GENERATE_MESSAGE_ID */ mainbody = new MSG_SendPart(this, mail_csid); if (!mainbody) goto FAILMEM; mainbody->SetMainPart(TRUE); mainbody->SetType(m_attachment1_type ? m_attachment1_type : TEXT_PLAIN); XP_ASSERT(mainbody->GetBuffer() == NULL); status = mainbody->SetBuffer(m_attachment1_body ? m_attachment1_body : " "); if (status < 0) goto FAIL; /* ### mwelch Determine the encoding of the main message body before we free it. The proper way to do this should be to test whatever text is in mainbody just before writing it out, but that will require a fix that is less safe and takes more memory. */ if ((mail_csid & STATEFUL) || /* CS_JIS or CS_2022_KR */ mime_7bit_data_p (m_attachment1_body, m_attachment1_body_length)) StrAllocCopy (m_attachment1_encoding, ENCODING_7BIT); else if (mime_use_quoted_printable_p) StrAllocCopy (m_attachment1_encoding, ENCODING_QUOTED_PRINTABLE); else StrAllocCopy (m_attachment1_encoding, ENCODING_8BIT); 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. // To plug a memory leak, delete the original maincontainer/mainbody. // // NOTE: We DO NOT want to do this to the HTML or multipart/related // MSG_SendParts because they are deleted at the end of message // delivery by the TapeFileSystem object // (MSG_MimeRelatedSaver / mhtmlstm.cpp). delete mainbody; // No matter what, maincontainer points to the outermost related part. maincontainer = m_related_part; maincontainerISrelatedpart = TRUE; const char *relPartType = m_related_part->GetType(); if (relPartType && !strcmp(relPartType, MULTIPART_RELATED)) // outer shell is m/related, // mainbody wants to be the HTML lump within mainbody = m_related_part->GetChild(0); else // outer shell is text/html, // so mainbody wants to be the same lump mainbody = maincontainer; 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. Put it in. plainpart = new MSG_SendPart(this, mail_csid); if (!plainpart) goto FAILMEM; status = plainpart->SetType(TEXT_PLAIN); if (status < 0) goto FAIL; status = plainpart->SetFile(m_plaintext->m_file_name, xpFileToPost); if (status < 0) goto FAIL; m_plaintext->AnalyzeSnarfedFile(); // look for 8 bit text, long lines, etc. m_plaintext->PickEncoding(mail_csid); hdrs = mime_generate_attachment_headers(m_plaintext->m_type, m_plaintext->m_encoding, m_plaintext->m_description, m_plaintext->m_x_mac_type, m_plaintext->m_x_mac_creator, m_plaintext->m_real_name, 0, m_digest_p, m_plaintext, mail_csid); if (!hdrs) goto FAILMEM; status = plainpart->SetOtherHeaders(hdrs); XP_FREE(hdrs); hdrs = NULL; if (status < 0) goto FAIL; if (m_fields->GetUseMultipartAlternative()) { MSG_SendPart* htmlpart = maincontainer; maincontainer = new MSG_SendPart(this); if (!maincontainer) goto FAILMEM; status = maincontainer->SetType(MULTIPART_ALTERNATIVE); if (status < 0) goto FAIL; status = maincontainer->AddChild(plainpart); if (status < 0) goto FAIL; status = maincontainer->AddChild(htmlpart); if (status < 0) 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 MSG_SendPart, then reshuffle the parts. Sigh.) if (!XP_STRCASECMP(m_plaintext->m_encoding, ENCODING_QUOTED_PRINTABLE)) { MimeEncoderData *plaintext_enc = MimeQPEncoderInit(mime_encoder_output_fn, this); if (!plaintext_enc) { status = MK_OUT_OF_MEMORY; goto FAIL; } plainpart->SetEncoderData(plaintext_enc); } } else { delete maincontainer; //### mwelch - does this cause a crash?! if (maincontainerISrelatedpart) m_related_part = NULL; maincontainer = plainpart; mainbody = maincontainer; FREEIF(m_attachment1_type); m_attachment1_type = XP_STRDUP(TEXT_PLAIN); if (!m_attachment1_type) goto FAILMEM; /* Override attachment1_encoding here. */ FREEIF(m_attachment1_encoding); StrAllocCopy(m_attachment1_encoding, m_plaintext->m_encoding); plaintext_is_mainbody_p = TRUE; // converted plaintext is mainbody } } // ###tw This used to be this more complicated thing, but for now, if it we // have any attachments, we generate multipart. //multipart_p = (m_attachment_count > 1 || // (m_attachment_count == 1 && // m_attachment1_body_length > 0)); multipart_p = (m_attachment_count > 0); if (multipart_p) { toppart = new MSG_SendPart(this); if (!toppart) goto FAILMEM; status = toppart->SetType(m_digest_p ? MULTIPART_DIGEST : MULTIPART_MIXED); if (status < 0) goto FAIL; status = toppart->AddChild(maincontainer); if (status < 0) goto FAIL; HG36459 { status = toppart->SetBuffer(XP_GetString (MK_MIME_MULTIPART_BLURB)); if (status < 0) goto FAIL; } } else { toppart = maincontainer; } /* Write out the message headers. */ headers = mime_generate_headers (m_fields, INTL_GetCSIWinCSID(c), m_deliver_mode); if (!headers) goto FAILMEM; // ### mwelch // 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. // /* reordering of headers will happen in MSG_SendPart::Write */ if ((plainpart) && (plainpart == toppart)) status = toppart->AppendOtherHeaders(headers); else status = toppart->SetOtherHeaders(headers); XP_FREE(headers); headers = NULL; if (status < 0) 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. * ###tw */ /* Set up encoder for the first part (message body.) */ XP_ASSERT(!m_attachment1_encoder_data); if (!strcasecomp(m_attachment1_encoding, ENCODING_BASE64)) { m_attachment1_encoder_data = MimeB64EncoderInit(mime_encoder_output_fn, this); if (!m_attachment1_encoder_data) goto FAILMEM; } else if (!strcasecomp(m_attachment1_encoding, ENCODING_QUOTED_PRINTABLE)) { m_attachment1_encoder_data = MimeQPEncoderInit(mime_encoder_output_fn, this); if (!m_attachment1_encoder_data) goto FAILMEM; } // ### mwelch // 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)) { hdrs = mime_generate_attachment_headers (m_attachment1_type, m_attachment1_encoding, 0, 0, 0, 0, 0, m_digest_p, NULL, /* no "ma"! */ mail_csid); if (!hdrs) goto FAILMEM; status = mainbody->AppendOtherHeaders(hdrs); if (status < 0) goto FAIL; } FREEIF(hdrs); status = mainbody->SetEncoderData(m_attachment1_encoder_data); m_attachment1_encoder_data = NULL; if (status < 0) goto FAIL; /* Set up the subsequent parts. */ if (m_attachment_count > 0) { char *buffer; /* Hack to avoid having to allocate memory... */ if (! mime_mailto_stream_read_buffer) mime_mailto_stream_read_buffer = (char *) XP_ALLOC (MIME_BUFFER_SIZE); buffer = mime_mailto_stream_read_buffer; if (! buffer) goto FAILMEM; buffer_tail = buffer; for (i = 0; i < m_attachment_count; i++) { MSG_DeliverMimeAttachment *ma = &m_attachments[i]; char *hdrs = 0; MSG_SendPart* part = NULL; // If at this point we *still* don't have an content-type, then // we're never going to get one. if (ma->m_type == NULL) { ma->m_type = XP_STRDUP(UNKNOWN_CONTENT_TYPE); if (ma->m_type == NULL) goto FAILMEM; } ma->PickEncoding (mail_csid); part = new MSG_SendPart(this); if (!part) goto FAILMEM; status = toppart->AddChild(part); if (status < 0) goto FAIL; status = part->SetType(ma->m_type); if (status < 0) goto FAIL; hdrs = mime_generate_attachment_headers (ma->m_type, ma->m_encoding, ma->m_description, ma->m_x_mac_type, ma->m_x_mac_creator, ma->m_real_name, ma->m_url_string, m_digest_p, ma, mail_csid); if (!hdrs) goto FAILMEM; status = part->SetOtherHeaders(hdrs); FREEIF(hdrs); if (status < 0) goto FAIL; status = part->SetFile(ma->m_file_name, xpFileToPost); if (status < 0) goto FAIL; if (ma->m_encoder_data) { status = part->SetEncoderData(ma->m_encoder_data); if (status < 0) goto FAIL; ma->m_encoder_data = NULL; } ma->m_current_column = 0; if (ma->m_type && (!strcasecomp (ma->m_type, MESSAGE_RFC822) || !strcasecomp (ma->m_type, MESSAGE_NEWS))) { status = part->SetStripSensitiveHeaders(TRUE); if (status < 0) goto FAIL; } } } // OK, now actually write the structure we've carefully built up. status = toppart->Write(); if (status < 0) goto FAIL; HG75442 if (m_msg_file) XP_FileClose (m_msg_file); m_msg_file = 0; if (m_imapOutgoingParser) { m_imapOutgoingParser->FinishHeader(); XP_ASSERT(m_imapOutgoingParser->m_newMsgHdr); m_imapOutgoingParser->m_newMsgHdr->OrFlags(kIsRead); m_imapOutgoingParser->m_newMsgHdr->SetMessageSize (m_imapOutgoingParser->m_bytes_written); m_imapOutgoingParser->m_newMsgHdr->SetMessageKey(0); m_imapLocalMailDB->AddHdrToDB(m_imapOutgoingParser->m_newMsgHdr, NULL, TRUE); } FE_Progress(GetContext(), XP_GetString(MK_MSG_ASSEMB_DONE_MSG)); if (m_dont_deliver_p && m_message_delivery_done_callback) { m_message_delivery_done_callback (GetContext(), m_fe_data, 0, XP_STRDUP (m_msg_file_name)); /* Need to ditch the file name here so that we don't delete the file, since in this case, the FE needs the file itself. */ FREEIF (m_msg_file_name); m_msg_file_name = 0; m_message_delivery_done_callback = 0; Clear(); } else { DeliverMessage(); } // Get rid of all the encoder data and temporary files. for (i = 0; i < m_attachment_count; i++) { MSG_DeliverMimeAttachment *ma = &m_attachments[i]; if (ma->m_encoder_data) { status = MimeEncoderDestroy(ma->m_encoder_data, FALSE); ma->m_encoder_data = 0; if (status < 0) goto FAIL; } if (!m_pre_snarfed_attachments_p) { if (ma->m_file) { XP_FileClose(ma->m_file); ma->m_file = 0; } XP_FileRemove(ma->m_file_name, xpFileToPost); } XP_FREE (ma->m_file_name); ma->m_file_name = 0; } FAIL: if (toppart) delete toppart; toppart = NULL; mainbody = NULL; maincontainer = NULL; /* Close off encoder for the first part (message body.) */ if (m_attachment1_encoder_data) { status = MimeEncoderDestroy(m_attachment1_encoder_data, FALSE); m_attachment1_encoder_data = 0; if (status < 0) goto FAIL; } if (headers) XP_FREE (headers); if (separator) XP_FREE (separator); if (in_file) { XP_FileClose (in_file); in_file = NULL; } HG59731 if (status < 0) { m_status = status; Fail (status, error_msg); } /* If status is >= 0, then the the next event coming up is posting to a "mailto:" or "news:" URL; the message_delivery_done_callback will be called from the exit routine of that URL. */ if (m_plaintext) { if (m_plaintext->m_file) XP_FileClose(m_plaintext->m_file); XP_FileRemove(m_plaintext->m_file_name, xpFileToPost); XP_FREE(m_plaintext->m_file_name); m_plaintext->m_file_name = NULL; delete m_plaintext; m_plaintext = NULL; } if (m_html_filename) { XP_FileRemove(m_html_filename, xpFileToPost); XP_FREE(m_html_filename); m_html_filename = NULL; } return status; FAILMEM: status = MK_OUT_OF_MEMORY; goto FAIL; } #if defined(XP_MAC) && defined(DEBUG) // Compiler runs out of registers for the debug build. #pragma global_optimizer on #pragma optimization_level 4 #endif // XP_MAC && DEBUG static void FROB (const char *src, char *dest) { if (src && *src && dest) { if (*dest) XP_STRCAT(dest, ","); XP_STRCAT(dest, src); } } HG53784 #if defined(XP_MAC) && defined(DEBUG) #pragma global_optimizer reset #endif // XP_MAC && DEBUG int mime_write_message_body (MSG_SendMimeDeliveryState *state, char *buf, int32 size) { HG56898 { if (int32(XP_FileWrite (buf, size, state->m_msg_file)) < size) { return MK_MIME_ERROR_WRITING_FILE; } else { if (state->m_imapOutgoingParser) { state->m_imapOutgoingParser->ParseFolderLine(buf, size); state->m_imapOutgoingParser->m_bytes_written += size; } return 0; } } } #ifdef XP_OS2 extern int #else static int #endif mime_encoder_output_fn (const char *buf, int32 size, void *closure) { MSG_SendMimeDeliveryState *state = (MSG_SendMimeDeliveryState *) closure; return mime_write_message_body (state, (char *) buf, size); } char * msg_generate_message_id (void) { time_t now = XP_TIME(); uint32 salt = 0; const char *host = 0; const char *from = FE_UsersMailAddress (); RNG_GenerateGlobalRandomBytes((void *) &salt, sizeof(salt)); if (from) { host = XP_STRCHR (from, '@'); if (host) { const char *s; for (s = ++host; *s; s++) if (!XP_IS_ALPHA(*s) && !XP_IS_DIGIT(*s) && *s != '-' && *s != '_' && *s != '.') { host = 0; break; } } } if (! 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; return PR_smprintf("<%lX.%lX@%s>", (unsigned long) now, (unsigned long) salt, host); } static char * mime_generate_headers (MSG_CompositionFields *fields, int csid, MSG_Deliver_Mode deliver_mode) { int size = 0; char *buffer = 0, *buffer_tail = 0; XP_Bool isDraft = deliver_mode == MSG_SaveAsDraft || deliver_mode == MSG_SaveAsTemplate || deliver_mode == MSG_QueueForLater; XP_ASSERT (fields); if (!fields) return NULL; /* Multiply by 3 here to make enough room for MimePartII conversion */ if (fields->GetFrom()) size += 3 * XP_STRLEN (fields->GetFrom()); if (fields->GetReplyTo()) size += 3 * XP_STRLEN (fields->GetReplyTo()); if (fields->GetTo()) size += 3 * XP_STRLEN (fields->GetTo()); if (fields->GetCc()) size += 3 * XP_STRLEN (fields->GetCc()); if (fields->GetNewsgroups()) size += 3 * XP_STRLEN (fields->GetNewsgroups()); if (fields->GetFollowupTo()) size += 3 * XP_STRLEN (fields->GetFollowupTo()); if (fields->GetSubject()) size += 3 * XP_STRLEN (fields->GetSubject()); if (fields->GetReferences()) size += 3 * XP_STRLEN (fields->GetReferences()); if (fields->GetOrganization()) size += 3 * XP_STRLEN (fields->GetOrganization()); if (fields->GetOtherRandomHeaders()) size += 3 * XP_STRLEN (fields->GetOtherRandomHeaders()); if (fields->GetPriority()) size += 3 * XP_STRLEN (fields->GetPriority()); #ifdef GENERATE_MESSAGE_ID if (fields->GetMessageId()) size += XP_STRLEN (fields->GetMessageId()); #endif /* GENERATE_MESSAGE_ID */ /* Add a bunch of space for the static parts of the headers. */ /* size += 2048; */ size += 2560; buffer = (char *) XP_ALLOC (size); if (!buffer) return 0; /* MK_OUT_OF_MEMORY */ buffer_tail = buffer; #ifdef GENERATE_MESSAGE_ID if (fields->GetMessageId() && *fields->GetMessageId()) { char *convbuf = NULL; PUSH_STRING ("Message-ID: "); PUSH_STRING (fields->GetMessageId()); PUSH_NEWLINE (); /* 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 */ if (fields->GetReturnReceipt() && (fields->GetReturnReceiptType() == 2 || fields->GetReturnReceiptType() == 3) && (deliver_mode != MSG_SaveAsDraft && deliver_mode != MSG_SaveAsTemplate)) { int32 receipt_header_type = 0; PREF_GetIntPref("mail.receipt.request_header_type", &receipt_header_type); // 0 = MDN Disposition-Notification-To: ; 1 = Return-Receipt-To: ; 2 = // both MDN DNT & RRT headers if (receipt_header_type == 1) { RRT_HEADER: PUSH_STRING ("Return-Receipt-To: "); convbuf = IntlEncodeMimePartIIStr((char *)fields->GetFrom(), csid, mime_headers_use_quoted_printable_p); if (convbuf) /* MIME-PartII conversion */ { PUSH_STRING (convbuf); XP_FREEIF(convbuf); } else PUSH_STRING (fields->GetFrom()); PUSH_NEWLINE (); } else { PUSH_STRING ("Disposition-Notification-To: "); convbuf = IntlEncodeMimePartIIStr((char *)fields->GetFrom(), csid, mime_headers_use_quoted_printable_p); if (convbuf) /* MIME-PartII conversion */ { PUSH_STRING (convbuf); XP_FREEIF(convbuf); } else PUSH_STRING (fields->GetFrom()); PUSH_NEWLINE (); if (receipt_header_type == 2) goto RRT_HEADER; } } #ifdef SUPPORT_X_TEMPLATE_NAME if (deliver_mode == MSG_SaveAsTemplate) { PUSH_STRING ("X-Template: "); if (fields->GetTemplateName()) { convbuf = IntlEncodeMimePartIIStr((char *) fields->GetTemplateName(), csid, mime_headers_use_quoted_printable_p); if (convbuf) { PUSH_STRING (convbuf); XP_FREEIF(convbuf); } else { PUSH_STRING(fields->GetTemplateName()); } } PUSH_NEWLINE (); } #endif /* SUPPORT_X_TEMPLATE_NAME */ } #endif /* GENERATE_MESSAGE_ID */ { #if 0 /* Use strftime() to format the date, then figure out what our local GMT offset it, and append that (since strftime() can't do that.) Generate four digit years as per RFC 1123 (superceding RFC 822.) */ time_t now = time ((time_t *) 0); int gmtoffset = XP_LocalZoneOffset(); strftime (buffer_tail, 100, "Date: %a, %d %b %Y %H:%M:%S ", localtime (&now)); #else int gmtoffset = XP_LocalZoneOffset(); #ifndef NSPR20 PRTime now; PR_ExplodeTime(&now, PR_Now()); #else PRExplodedTime now; PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now); #endif /* NSPR20 */ /* 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_FormatTimeUSEnglish(buffer_tail, 100, "Date: %a, %d %b %Y %H:%M:%S ", &now); #endif buffer_tail += XP_STRLEN (buffer_tail); PR_snprintf(buffer_tail, buffer + size - buffer_tail, "%c%02d%02d" CRLF, (gmtoffset >= 0 ? '+' : '-'), ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60), ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60)); buffer_tail += XP_STRLEN (buffer_tail); } if (fields->GetFrom() && *fields->GetFrom()) { char *convbuf; PUSH_STRING ("From: "); convbuf = IntlEncodeMimePartIIStr((char *)fields->GetFrom(), csid, mime_headers_use_quoted_printable_p); if (convbuf) /* MIME-PartII conversion */ { PUSH_STRING (convbuf); XP_FREE(convbuf); } else PUSH_STRING (fields->GetFrom()); PUSH_NEWLINE (); } if (fields->GetReplyTo() && *fields->GetReplyTo()) { char *convbuf; PUSH_STRING ("Reply-To: "); convbuf = IntlEncodeMimePartIIStr((char *)fields->GetReplyTo(), csid, mime_headers_use_quoted_printable_p); if (convbuf) /* MIME-PartII conversion */ { PUSH_STRING (convbuf); XP_FREE(convbuf); } else PUSH_STRING (fields->GetReplyTo()); PUSH_NEWLINE (); } if (fields->GetOrganization() && *fields->GetOrganization()) { char *convbuf; PUSH_STRING ("Organization: "); convbuf = IntlEncodeMimePartIIStr((char *)fields->GetOrganization(), csid, mime_headers_use_quoted_printable_p); if (convbuf) /* MIME-PartII conversion */ { PUSH_STRING (convbuf); XP_FREE(convbuf); } else PUSH_STRING (fields->GetOrganization()); PUSH_NEWLINE (); } // X-Sender tag if (fields->GetOwner()) { XP_Bool bUseXSender = FALSE; PREF_GetBoolPref("mail.use_x_sender", &bUseXSender); if (bUseXSender) { char *convbuf; char tmpBuffer[256]; int bufSize = 256; *tmpBuffer = 0; PUSH_STRING ("X-Sender: "); PUSH_STRING("\""); PREF_GetCharPref("mail.identity.username", tmpBuffer, &bufSize); convbuf = IntlEncodeMimePartIIStr((char *)tmpBuffer, csid, mime_headers_use_quoted_printable_p); if (convbuf) /* MIME-PartII conversion */ { PUSH_STRING (convbuf); XP_FREE(convbuf); } else PUSH_STRING (tmpBuffer); PUSH_STRING("\" <"); PREF_GetCharPref("mail.pop_name", tmpBuffer, &bufSize); convbuf = IntlEncodeMimePartIIStr((char *)tmpBuffer, csid, mime_headers_use_quoted_printable_p); if (convbuf) /* MIME-PartII conversion */ { PUSH_STRING (convbuf); XP_FREE(convbuf); } else PUSH_STRING (tmpBuffer); PUSH_STRING ("@"); PREF_GetCharPref("network.hosts.pop_server", tmpBuffer, &bufSize); convbuf = IntlEncodeMimePartIIStr((char *)tmpBuffer, csid, mime_headers_use_quoted_printable_p); if (convbuf) /* MIME-PartII conversion */ { PUSH_STRING (convbuf); XP_FREE(convbuf); } else PUSH_STRING (tmpBuffer); PUSH_STRING(">"); convbuf = IntlEncodeMimePartIIStr((char *)tmpBuffer, csid, mime_headers_use_quoted_printable_p); if (!fields->GetOwner()->GetMaster()->IsUserAuthenticated()) PUSH_STRING (" (Unverified)"); PUSH_NEWLINE (); } } // X-Mozilla-Draft-Info if (isDraft) { char *htmlAction = 0; char *lineWidth = 0; // force plain text hard line break info PUSH_STRING(X_MOZILLA_DRAFT_INFO); PUSH_STRING(": internal/draft; "); if (fields->GetAttachVCard()) { PUSH_STRING("vcard=1"); } else { PUSH_STRING("vcard=0"); } PUSH_STRING("; "); if (fields->GetReturnReceipt()) { char *type = PR_smprintf("%d", (int) fields->GetReturnReceiptType()); if (type) { PUSH_STRING("receipt="); PUSH_STRING(type); XP_FREEIF(type); } } else { PUSH_STRING("receipt=0"); } PUSH_STRING("; "); if (fields->GetBoolHeader(MSG_UUENCODE_BINARY_BOOL_HEADER_MASK)) { PUSH_STRING("uuencode=1"); } else { PUSH_STRING("uuencode=0"); } htmlAction = PR_smprintf("html=%d", ((MSG_CompositionPane*)fields->GetOwner())->GetHTMLAction()); if (htmlAction) { PUSH_STRING("; "); PUSH_STRING(htmlAction); FREEIF(htmlAction); } lineWidth = PR_smprintf("; linewidth=%d", ((MSG_CompositionPane*)fields->GetOwner())->GetLineWidth()); if (lineWidth) { PUSH_STRING(lineWidth); FREEIF(lineWidth); } PUSH_NEWLINE (); } PUSH_STRING ("X-Mailer: "); PUSH_STRING (XP_AppCodeName); PUSH_STRING (" "); PUSH_STRING (XP_AppVersion); PUSH_NEWLINE (); /* for Netscape Server, Accept-Language data sent in Mail header */ char *acceptlang = INTL_GetAcceptLanguage(); if( (acceptlang != NULL) && ( *acceptlang != '\0') ){ PUSH_STRING( "X-Accept-Language: " ); PUSH_STRING( acceptlang ); PUSH_NEWLINE(); } PUSH_STRING ("MIME-Version: 1.0" CRLF); if (fields->GetNewsgroups() && *fields->GetNewsgroups()) { /* turn whitespace into a comma list */ char *ptr, *ptr2; char *n2; char *convbuf; convbuf = IntlEncodeMimePartIIStr((char *)fields->GetNewsgroups(), csid, mime_headers_use_quoted_printable_p); if (convbuf) n2 = XP_StripLine (convbuf); else { ptr = XP_STRDUP(fields->GetNewsgroups()); if (!ptr) { FREEIF(buffer); return 0; /* MK_OUT_OF_MEMORY */ } n2 = XP_StripLine(ptr); XP_ASSERT(n2 == ptr); /* Otherwise, the XP_FREE below is gonna choke badly. */ } for(ptr=n2; *ptr != '\0'; ptr++) { /* find first non white space */ while(!XP_IS_SPACE(*ptr) && *ptr != ',' && *ptr != '\0') ptr++; if(*ptr == '\0') break; if(*ptr != ',') *ptr = ','; /* find next non white space */ ptr2 = ptr+1; while(XP_IS_SPACE(*ptr2)) ptr2++; if(ptr2 != ptr+1) XP_STRCPY(ptr+1, ptr2); } PUSH_STRING ("Newsgroups: "); PUSH_STRING (n2); XP_FREE (n2); PUSH_NEWLINE (); } /* #### shamelessly duplicated from above */ if (fields->GetFollowupTo() && *fields->GetFollowupTo()) { /* turn whitespace into a comma list */ char *ptr, *ptr2; char *n2; char *convbuf; convbuf = IntlEncodeMimePartIIStr((char *)fields->GetFollowupTo(), csid, mime_headers_use_quoted_printable_p); if (convbuf) n2 = XP_StripLine (convbuf); else { ptr = XP_STRDUP(fields->GetFollowupTo()); if (!ptr) { FREEIF(buffer); return 0; /* MK_OUT_OF_MEMORY */ } n2 = XP_StripLine (ptr); XP_ASSERT(n2 == ptr); /* Otherwise, the XP_FREE below is gonna choke badly. */ } for(ptr=n2; *ptr != '\0'; ptr++) { /* find first non white space */ while(!XP_IS_SPACE(*ptr) && *ptr != ',' && *ptr != '\0') ptr++; if(*ptr == '\0') break; if(*ptr != ',') *ptr = ','; /* find next non white space */ ptr2 = ptr+1; while(XP_IS_SPACE(*ptr2)) ptr2++; if(ptr2 != ptr+1) XP_STRCPY(ptr+1, ptr2); } PUSH_STRING ("Followup-To: "); PUSH_STRING (n2); XP_FREE (n2); PUSH_NEWLINE (); } if (fields->GetTo() && *fields->GetTo()) { char *convbuf; PUSH_STRING ("To: "); convbuf = IntlEncodeMimePartIIStr((char *)fields->GetTo(), csid, mime_headers_use_quoted_printable_p); if (convbuf) /* MIME-PartII conversion */ { PUSH_STRING (convbuf); XP_FREE(convbuf); } else PUSH_STRING (fields->GetTo()); PUSH_NEWLINE (); } if (fields->GetCc() && *fields->GetCc()) { char *convbuf; PUSH_STRING ("CC: "); convbuf = IntlEncodeMimePartIIStr((char *)fields->GetCc(), csid, mime_headers_use_quoted_printable_p); if (convbuf) /* MIME-PartII conversion */ { PUSH_STRING (convbuf); XP_FREE(convbuf); } else PUSH_STRING (fields->GetCc()); PUSH_NEWLINE (); } if (fields->GetSubject() && *fields->GetSubject()) { char *convbuf; PUSH_STRING ("Subject: "); convbuf = IntlEncodeMimePartIIStr((char *)fields->GetSubject(), csid, mime_headers_use_quoted_printable_p); if (convbuf) /* MIME-PartII conversion */ { PUSH_STRING (convbuf); XP_FREE(convbuf); } else PUSH_STRING (fields->GetSubject()); PUSH_NEWLINE (); } if (fields->GetPriority() && *(fields->GetPriority())) { char *priority = (char *) fields->GetPriority(); if (!strcasestr(priority, "normal")) { PUSH_STRING ("X-Priority: "); /* Important: do not change the order of the * following if statements */ if (strcasestr (priority, "highest")) PUSH_STRING("1 ("); else if (strcasestr(priority, "high")) PUSH_STRING("2 ("); else if (strcasestr(priority, "lowest")) PUSH_STRING("5 ("); else if (strcasestr(priority, "low")) PUSH_STRING("4 ("); PUSH_STRING (priority); PUSH_STRING(")"); PUSH_NEWLINE (); } } if (fields->GetReferences() && *fields->GetReferences()) { PUSH_STRING ("References: "); PUSH_STRING (fields->GetReferences()); PUSH_NEWLINE (); } if (fields->GetOtherRandomHeaders() && *fields->GetOtherRandomHeaders()) { /* Assume they already have the right newlines and continuations and so on. */ PUSH_STRING (fields->GetOtherRandomHeaders()); } if (buffer_tail > buffer + size - 1) abort (); /* realloc it smaller... */ buffer = (char*)XP_REALLOC (buffer, buffer_tail - buffer + 1); return buffer; } /* Generate headers for a form post to a mailto: URL. This lets the URL specify additional headers, but is careful to ignore headers which would be dangerous. It may modify the URL (because of CC) so a new URL to actually post to is returned. */ int MIME_GenerateMailtoFormPostHeaders (const char *old_post_url, const char * /*referer*/, char **new_post_url_return, char **headers_return) { char *from = 0, *to = 0, *cc = 0, *body = 0, *search = 0; char *extra_headers = 0; char *s; XP_Bool subject_p = FALSE; XP_Bool sign_p = FALSE; HG29292 char *rest; int status = 0; MSG_CompositionFields *fields = NULL; static const char *forbidden_headers[] = { "Apparently-To", "BCC", "Content-Encoding", CONTENT_LENGTH, "Content-Transfer-Encoding", "Content-Type", "Date", "Distribution", "FCC", "Followup-To", "From", "Lines", "MIME-Version", "Message-ID", "Newsgroups", "Organization", "Reply-To", "Sender", X_MOZILLA_STATUS, X_MOZILLA_STATUS2, X_MOZILLA_NEWSHOST, X_UIDL, "XRef", 0 }; from = MIME_MakeFromField (); if (!from) { status = MK_OUT_OF_MEMORY; goto FAIL; } to = NET_ParseURL (old_post_url, GET_PATH_PART); if (!to) { status = MK_OUT_OF_MEMORY; goto FAIL; } if (!*to) { status = MK_MIME_NO_RECIPIENTS; /* rb -1; */ goto FAIL; } search = NET_ParseURL (old_post_url, GET_SEARCH_PART); rest = search; if (rest && *rest == '?') { /* start past the '?' */ rest++; rest = XP_STRTOK (rest, "&"); while (rest && *rest) { char *token = rest; char *value = 0; char *eq; rest = XP_STRTOK (0, "&"); eq = XP_STRCHR (token, '='); if (eq) { value = eq+1; *eq = 0; } if (!strcasecomp (token, "subject")) subject_p = TRUE; if (value) /* Don't allow newlines or control characters in the value. */ for (s = value; *s; s++) if (*s < ' ' && *s != '\t') *s = ' '; if (!strcasecomp (token, "to")) { if (to && *to) { StrAllocCat (to, ", "); StrAllocCat (to, value); } else { StrAllocCopy (to, value); } } else if (!strcasecomp (token, "cc")) { if (cc && *cc) { StrAllocCat (cc, ", "); StrAllocCat (cc, value); } else { StrAllocCopy (cc, value); } } else if (!strcasecomp (token, "body")) { if (body && *body) { StrAllocCat (body, "\n"); StrAllocCat (body, value); } else { StrAllocCopy (body, value); } } HG28926 else if (!strcasecomp (token, "sign") || !strcasecomp (token, "signed")) { sign_p = (!strcasecomp(value, "true") || !strcasecomp(value, "yes")); } else { const char **fh = forbidden_headers; XP_Bool ok = TRUE; while (*fh) if (!strcasecomp (token, *fh++)) { ok = FALSE; break; } if (ok) { XP_Bool upper_p = FALSE; char *s; for (s = token; *s; s++) { if (*s >= 'A' && *s <= 'Z') upper_p = TRUE; else if (*s <= ' ' || *s >= '~' || *s == ':') goto NOT_OK; /* bad character in header! */ } if (!upper_p && *token >= 'a' && *token <= 'z') *token -= ('a' - 'A'); StrAllocCat (extra_headers, token); StrAllocCat (extra_headers, ": "); if (value) StrAllocCat (extra_headers, value); StrAllocCat (extra_headers, CRLF); NOT_OK: ; } } } } if (!subject_p) { /* If the URL didn't provide a subject, we will. */ StrAllocCat (extra_headers, "Subject: Form posted from "); XP_ASSERT (XP_AppCodeName); StrAllocCat (extra_headers, XP_AppCodeName); StrAllocCat (extra_headers, CRLF); } /* Note: the `sign', and `body' parameters are currently ignored in mailto form submissions. */ *new_post_url_return = 0; fields = MSG_CreateCompositionFields(from, 0, to, cc, 0, 0, 0, 0, FE_UsersOrganization(), 0, 0, extra_headers, 0, 0, 0 HG15448); if (!fields) { status = MK_OUT_OF_MEMORY; goto FAIL; } fields->SetDefaultBody(body); *headers_return = mime_generate_headers (fields, 0, MSG_DeliverNow); if (*headers_return == 0) { status = MK_OUT_OF_MEMORY; goto FAIL; } StrAllocCat ((*new_post_url_return), "mailto:"); if (to) StrAllocCat ((*new_post_url_return), to); if (to && cc) StrAllocCat ((*new_post_url_return), ","); if (cc) StrAllocCat ((*new_post_url_return), cc); FAIL: FREEIF (from); FREEIF (to); FREEIF (cc); FREEIF (body); FREEIF (search); FREEIF (extra_headers); if (fields) MSG_DestroyCompositionFields(fields); return status; } static char * mime_generate_attachment_headers (const char *type, const char *encoding, const char *description, const char *x_mac_type, const char *x_mac_creator, const char *real_name, const char *base_url, XP_Bool /*digest_p*/, MSG_DeliverMimeAttachment * /*ma*/, int16 mail_csid) { char *buffer = (char *) XP_ALLOC (2048); char *buffer_tail = buffer; char charset[30]; if (! buffer) return 0; /* MK_OUT_OF_MEMORY */ XP_ASSERT (encoding); charset[0] = 0; PUSH_STRING ("Content-Type: "); PUSH_STRING (type); if (mime_type_needs_charset (type)) { /* push 7bit encoding out based on current default codeset */ INTL_CharSetIDToName (mail_csid, charset); /* If the characters are all 7bit, then it's better (and true) to claim the charset to be US-ASCII rather than Latin1. Should we do this all the time, for all charsets? I'm not sure. But we should definitely do it for Latin1. */ if (encoding && !strcasecomp (encoding, "7bit") && !strcasecomp (charset, "iso-8859-1")) XP_STRCPY (charset, "us-ascii"); // If csid is JIS and and type is HTML // then no charset to be specified (apply base64 instead) // in order to avoid mismatch META_TAG (bug#104255). if ((mail_csid != CS_JIS) || (strcasecomp(type, TEXT_HTML) != 0) || (strcasecomp(encoding, ENCODING_BASE64) != 0)) { PUSH_STRING ("; charset="); PUSH_STRING (charset); } } if (x_mac_type && *x_mac_type) { PUSH_STRING ("; x-mac-type=\""); PUSH_STRING (x_mac_type); PUSH_STRING ("\""); } if (x_mac_creator && *x_mac_creator) { PUSH_STRING ("; x-mac-creator=\""); PUSH_STRING (x_mac_creator); PUSH_STRING ("\""); } int32 parmFolding = 0; PREF_GetIntPref("mail.strictly_mime.parm_folding", &parmFolding); #ifdef EMIT_NAME_IN_CONTENT_TYPE if (real_name && *real_name) { if (parmFolding == 0 || parmFolding == 1) { PUSH_STRING (";\r\n name=\""); PUSH_STRING (real_name); PUSH_STRING ("\""); } else // if (parmFolding == 2) { char *rfc2231Parm = RFC2231ParmFolding("name", charset, INTL_GetAcceptLanguage(), real_name); if (rfc2231Parm) { PUSH_STRING(";\r\n "); PUSH_STRING(rfc2231Parm); XP_FREE(rfc2231Parm); } } } #endif /* EMIT_NAME_IN_CONTENT_TYPE */ PUSH_NEWLINE (); PUSH_STRING ("Content-Transfer-Encoding: "); PUSH_STRING (encoding); PUSH_NEWLINE (); if (description && *description) { char *s = mime_fix_header (description); if (s) { PUSH_STRING ("Content-Description: "); PUSH_STRING (s); PUSH_NEWLINE (); XP_FREE(s); } } if (real_name && *real_name) { char *period = XP_STRRCHR(real_name, '.'); int32 pref_content_disposition = 0; PREF_GetIntPref("mail.content_disposition_type", &pref_content_disposition); PUSH_STRING ("Content-Disposition: "); if (pref_content_disposition == 1) PUSH_STRING ("attachment"); else if (pref_content_disposition == 2 && (!strcasecomp(type, TEXT_PLAIN) || (period && !strcasecomp(period, ".txt")))) PUSH_STRING("attachment"); /* If this document is an anonymous binary file or a vcard, then always show it as an attachment, never inline. */ else if (!strcasecomp(type, APPLICATION_OCTET_STREAM) || !strcasecomp(type, vCardMimeFormat)) PUSH_STRING ("attachment"); else PUSH_STRING ("inline"); if (parmFolding == 0 || parmFolding == 1) { PUSH_STRING (";\r\n filename=\""); PUSH_STRING (real_name); PUSH_STRING ("\"" CRLF); } else // if (parmFolding == 2) { char *rfc2231Parm = RFC2231ParmFolding("name", charset, INTL_GetAcceptLanguage(), real_name); if (rfc2231Parm) { PUSH_STRING(";\r\n "); PUSH_STRING(rfc2231Parm); PUSH_NEWLINE (); XP_FREE(rfc2231Parm); } } } else if (type && (!strcasecomp (type, MESSAGE_RFC822) || !strcasecomp (type, MESSAGE_NEWS))) { PUSH_STRING ("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 && (!strcasecomp (type, TEXT_HTML) || !strcasecomp (type, TEXT_MDL)) && base_url && *base_url) { int32 col = 0; const char *s = base_url; const char *colon = XP_STRCHR (s, ':'); XP_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 (!strncasecomp (s, "news:", 5) || !strncasecomp (s, "snews:", 6) || !strncasecomp (s, "IMAP:", 5) || !strncasecomp (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 */ PREF_GetBoolPref("mail.use_content_location_on_send", &useContentLocation); if (useContentLocation) PUSH_STRING ("Content-Location: \""); else PUSH_STRING ("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 != '#') { const char *ot = buffer_tail; /* URLs must be wrapped at 40 characters or less. */ if (col >= 38) { PUSH_STRING(CRLF "\t"); col = 0; } if (*s == ' ') PUSH_STRING("%20"); else if (*s == '\t') PUSH_STRING("%09"); else if (*s == '\n') PUSH_STRING("%0A"); else if (*s == '\r') PUSH_STRING("%0D"); else { *buffer_tail++ = *s; *buffer_tail = '\0'; } s++; col += (buffer_tail - ot); } PUSH_STRING ("\"" CRLF); /* rhp: this is to try to get around this fun problem with Content-Location */ if (!useContentLocation) { PUSH_STRING ("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... */ buffer = (char*) XP_REALLOC (buffer, buffer_tail - buffer + 1); return buffer; } void MSG_SendMimeDeliveryState::Fail (int failure_code, char *error_msg) { if (m_message_delivery_done_callback) { if (failure_code < 0 && !error_msg) error_msg = NET_ExplainErrorDetails(failure_code, 0, 0, 0, 0); m_message_delivery_done_callback (GetContext(), m_fe_data, failure_code, error_msg); FREEIF(error_msg); /* #### Is there a memory leak here? Shouldn't this free be outside the if? */ } else if (m_attachments_done_callback) { if (failure_code < 0 && !error_msg) error_msg = NET_ExplainErrorDetails(failure_code, 0, 0, 0, 0); /* mime_free_message_state will take care of cleaning up the attachment files and attachment structures */ m_attachments_done_callback (GetContext(), m_fe_data, failure_code, error_msg, 0); FREEIF(error_msg); /* #### Is there a memory leak here? Shouldn't this free be outside the if? */ } m_message_delivery_done_callback = 0; m_attachments_done_callback = 0; Clear(); } /* Given a string, convert it to 'qtext' (quoted text) for RFC822 header purposes. */ static char * msg_make_filename_qtext(const char *srcText, XP_Bool stripCRLFs) { /* newString can be at most twice the original string (every char quoted). */ char *newString = (char *) XP_ALLOC(XP_STRLEN(srcText)*2 + 1); if (!newString) return NULL; const char *s = srcText; const char *end = srcText + XP_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 == CR && (*(s+1) != LF || (*(s+1) == LF && (s+2) < end && !XP_IS_SPACE(*(s+2))))))) *d++ = '\\'; if (*s == CR) { if (stripCRLFs && *(s+1) == LF && (s+2) < end && XP_IS_SPACE(*(s+2))) s += 2; // skip CRLFLWSP } else { *d++ = *s; } s++; } *d = 0; return newString; } /* Rip apart the URL and extract a reasonable value for the `real_name' slot. */ static void msg_pick_real_name (MSG_DeliverMimeAttachment *attachment, int16 csid) { const char *s, *s2; char *s3; char *url; if (attachment->m_real_name) return; url = attachment->m_url_string; /* Perhaps the MIME parser knows a better name than the URL itself? This can happen when one attaches a MIME part from one message directly into another message. ### mwelch Note that this function simply duplicates and returns an existing MIME header, so we don't need to process it. */ attachment->m_real_name = MimeGuessURLContentName(attachment->m_mime_delivery_state->GetContext(), url); if (attachment->m_real_name) return; /* Otherwise, extract a name from the URL. */ s = url; s2 = XP_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 (!strncasecomp (url, "news:", 5) || !strncasecomp (url, "snews:", 6) || !strncasecomp (url, "IMAP:", 5) || !strncasecomp (url, "mailbox:", 8)) return; /* Take the part of the file name after the last / or \ */ s2 = XP_STRRCHR (s, '/'); if (s2) s = s2+1; s2 = XP_STRRCHR (s, '\\'); if (csid & MULTIBYTE) { // We don't want to truncate the file name in case of the double // byte file name while ( s2 != NULL && s2 > s && INTL_IsLeadByte(csid, *(s2-1))) { s3 = (char *) s2; *s3 = 0; s2 = XP_STRRCHR(s, '\\'); *s3 = '\\'; } } if (s2) s = s2+1; /* Copy it into the attachment struct. */ StrAllocCopy (attachment->m_real_name, s); /* Now trim off any named anchors or search data. */ s3 = XP_STRCHR (attachment->m_real_name, '?'); if (s3) *s3 = 0; s3 = XP_STRCHR (attachment->m_real_name, '#'); if (s3) *s3 = 0; /* Now lose the %XX */ NET_UnEscape (attachment->m_real_name); int32 parmFolding = 0; PREF_GetIntPref("mail.strictly_mime.parm_folding", &parmFolding); if (parmFolding == 0 || parmFolding == 1) { /* Try to MIME-2 encode the filename... */ char *mime2Name = IntlEncodeMimePartIIStr(attachment->m_real_name, csid, mime_headers_use_quoted_printable_p); if (mime2Name && (mime2Name != attachment->m_real_name)) { XP_FREE(attachment->m_real_name); attachment->m_real_name = mime2Name; } /* ... and then put backslashes before special characters (RFC 822 tells us to). */ char *qtextName = NULL; qtextName = msg_make_filename_qtext(attachment->m_real_name, (parmFolding == 0 ? TRUE : FALSE)); if (qtextName) { XP_FREE(attachment->m_real_name); attachment->m_real_name = qtextName; } } /* 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) { char *result = attachment->m_real_name; int32 L = XP_STRLEN(result); const char **exts = 0; /* #### hack 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 (!strcasecomp(attachment->m_encoding, ENCODING_UUENCODE) || !strcasecomp(attachment->m_encoding, ENCODING_UUENCODE2) || !strcasecomp(attachment->m_encoding, ENCODING_UUENCODE3) || !strcasecomp(attachment->m_encoding, ENCODING_UUENCODE4)) { static const char *uue_exts[] = { "uu", "uue", 0 }; exts = uue_exts; } while (exts && *exts) { const char *ext = *exts; int32 L2 = XP_STRLEN(ext); if (L > L2 + 1 && /* long enough */ result[L - L2 - 1] == '.' && /* '.' in right place*/ !strcasecomp(ext, result + (L - L2))) /* ext matches */ { result[L - L2 - 1] = 0; /* truncate at '.' and stop. */ break; } exts++; } } } int MSG_SendMimeDeliveryState::HackAttachments( const struct MSG_AttachmentData *attachments, const struct MSG_AttachedFile *preloaded_attachments) { INTL_CharSetInfo c = LO_GetDocumentCharacterSetInfo(GetContext()); if (preloaded_attachments) XP_ASSERT(!attachments); if (attachments) XP_ASSERT(!preloaded_attachments); if (preloaded_attachments && preloaded_attachments[0].orig_url) { /* These are attachments which have already been downloaded to tmp files. We merely need to point the internal attachment data at those tmp files. */ int32 i; m_pre_snarfed_attachments_p = TRUE; m_attachment_count = 0; while (preloaded_attachments[m_attachment_count].orig_url) m_attachment_count++; m_attachments = (MSG_DeliverMimeAttachment *) new MSG_DeliverMimeAttachment[m_attachment_count]; if (! m_attachments) return MK_OUT_OF_MEMORY; for (i = 0; i < m_attachment_count; i++) { m_attachments[i].m_mime_delivery_state = this; /* These attachments are already "snarfed". */ m_attachments[i].m_done = TRUE; XP_ASSERT (preloaded_attachments[i].orig_url); StrAllocCopy (m_attachments[i].m_url_string, preloaded_attachments[i].orig_url); StrAllocCopy (m_attachments[i].m_type, preloaded_attachments[i].type); StrAllocCopy (m_attachments[i].m_description, preloaded_attachments[i].description); StrAllocCopy (m_attachments[i].m_real_name, preloaded_attachments[i].real_name); StrAllocCopy (m_attachments[i].m_x_mac_type, preloaded_attachments[i].x_mac_type); StrAllocCopy (m_attachments[i].m_x_mac_creator, preloaded_attachments[i].x_mac_creator); StrAllocCopy (m_attachments[i].m_encoding, preloaded_attachments[i].encoding); StrAllocCopy (m_attachments[i].m_file_name, preloaded_attachments[i].file_name); m_attachments[i].m_size = preloaded_attachments[i].size; m_attachments[i].m_unprintable_count = preloaded_attachments[i].unprintable_count; m_attachments[i].m_highbit_count = preloaded_attachments[i].highbit_count; m_attachments[i].m_ctl_count = preloaded_attachments[i].ctl_count; m_attachments[i].m_null_count = preloaded_attachments[i].null_count; m_attachments[i].m_max_column = preloaded_attachments[i].max_line_length; /* If the attachment has an encoding, and it's not one of the "null" encodings, then keep it. */ if (m_attachments[i].m_encoding && strcasecomp (m_attachments[i].m_encoding, ENCODING_7BIT) && strcasecomp (m_attachments[i].m_encoding, ENCODING_8BIT) && strcasecomp (m_attachments[i].m_encoding, ENCODING_BINARY)) m_attachments[i].m_already_encoded_p = TRUE; msg_pick_real_name(&m_attachments[i], INTL_GetCSIWinCSID(c)); } } else if (attachments && attachments[0].url) { /* These are attachments which have already been downloaded to tmp files. We merely need to point the internal attachment data at those tmp files. We will delete the tmp files as we attach them. */ int32 i; int mailbox_count = 0, news_count = 0; m_attachment_count = 0; while (attachments[m_attachment_count].url) m_attachment_count++; m_attachments = (MSG_DeliverMimeAttachment *) new MSG_DeliverMimeAttachment[m_attachment_count]; if (! m_attachments) return MK_OUT_OF_MEMORY; for (i = 0; i < m_attachment_count; i++) { m_attachments[i].m_mime_delivery_state = this; XP_ASSERT (attachments[i].url); StrAllocCopy (m_attachments[i].m_url_string, attachments[i].url); StrAllocCopy (m_attachments[i].m_override_type, attachments[i].real_type); StrAllocCopy (m_attachments[i].m_override_encoding, attachments[i].real_encoding); StrAllocCopy (m_attachments[i].m_desired_type, attachments[i].desired_type); StrAllocCopy (m_attachments[i].m_description, attachments[i].description); StrAllocCopy (m_attachments[i].m_real_name, attachments[i].real_name); StrAllocCopy (m_attachments[i].m_x_mac_type, attachments[i].x_mac_type); StrAllocCopy (m_attachments[i].m_x_mac_creator, attachments[i].x_mac_creator); StrAllocCopy (m_attachments[i].m_encoding, "7bit"); m_attachments[i].m_url = NET_CreateURLStruct (m_attachments[i].m_url_string, NET_DONT_RELOAD); // real name is set in the case of vcard so don't change it. // m_attachments[i].m_real_name = 0; /* Count up attachments which are going to come from mail folders and from NNTP servers. */ if (strncasecomp(m_attachments[i].m_url_string, "mailbox:",8) || strncasecomp(m_attachments[i].m_url_string, "IMAP:",5)) mailbox_count++; else if (strncasecomp(m_attachments[i].m_url_string, "news:",5) || strncasecomp(m_attachments[i].m_url_string, "snews:",6)) news_count++; msg_pick_real_name(&m_attachments[i], INTL_GetCSIWinCSID(c)); } /* 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). */ if (m_attachment_count == 1) FE_Progress(GetContext(), XP_GetString(MK_MSG_LOAD_ATTACHMNT)); else FE_Progress(GetContext(), XP_GetString(MK_MSG_LOAD_ATTACHMNTS)); for (i = 0; i < m_attachment_count; i++) { /* This only returns a failure code if NET_GetURL was not called (and thus no exit routine was or will be called.) */ int status = m_attachments [i].SnarfAttachment (); if (status < 0) return status; if (m_be_synchronous_p) break; } } if (m_attachment_pending_count <= 0) /* No attachments - finish now (this will call the done_callback). */ GatherMimeAttachments (); return 0; } void MSG_SendMimeDeliveryState::StartMessageDelivery( MSG_Pane *pane, void *fe_data, MSG_CompositionFields *fields, XP_Bool digest_p, XP_Bool dont_deliver_p, MSG_Deliver_Mode mode, const char *attachment1_type, const char *attachment1_body, uint32 attachment1_body_length, const struct MSG_AttachmentData *attachments, const struct MSG_AttachedFile *preloaded_attachments, //#ifdef MSG_SEND_MULTIPART_RELATED MSG_SendPart *relatedPart, //#endif void (*message_delivery_done_callback) (MWContext *context, void *fe_data, int status, const char *error_message)) { int failure = 0; MSG_SendMimeDeliveryState *state; if (!attachment1_body || !*attachment1_body) attachment1_type = attachment1_body = 0; state = new MSG_SendMimeDeliveryState; if (! state) { failure = MK_OUT_OF_MEMORY; goto FAIL; } failure = state->Init(pane, fe_data, fields, digest_p, dont_deliver_p, mode, attachment1_type, attachment1_body, attachment1_body_length, attachments, preloaded_attachments, //#ifdef MSG_SEND_MULTIPART_RELATED relatedPart, //#endif message_delivery_done_callback); if (failure >= 0) return; FAIL: char *err_msg = NET_ExplainErrorDetails (failure); message_delivery_done_callback (pane->GetContext(), fe_data, failure, err_msg); if (err_msg) XP_FREE (err_msg); delete state; } int MSG_SendMimeDeliveryState::SetMimeHeader(MSG_HEADER_SET header, const char *value) { char *dupHeader = NULL; int ret = MK_OUT_OF_MEMORY; if (header & (MSG_FROM_HEADER_MASK | MSG_TO_HEADER_MASK | MSG_REPLY_TO_HEADER_MASK | MSG_CC_HEADER_MASK | MSG_BCC_HEADER_MASK)) dupHeader = mime_fix_addr_header(value); else if (header & (MSG_NEWSGROUPS_HEADER_MASK| MSG_FOLLOWUP_TO_HEADER_MASK)) dupHeader = mime_fix_news_header(value); else if (header & (MSG_FCC_HEADER_MASK | MSG_ORGANIZATION_HEADER_MASK | MSG_SUBJECT_HEADER_MASK | MSG_REFERENCES_HEADER_MASK | MSG_X_TEMPLATE_HEADER_MASK)) dupHeader = mime_fix_header(value); else XP_ASSERT(FALSE); // unhandled header mask if (dupHeader) { ret = m_fields->SetHeader(header, dupHeader); XP_FREE(dupHeader); } return ret; } int MSG_SendMimeDeliveryState::Init( MSG_Pane *pane, void *fe_data, MSG_CompositionFields *fields, XP_Bool digest_p, XP_Bool dont_deliver_p, MSG_Deliver_Mode mode, const char *attachment1_type, const char *attachment1_body, uint32 attachment1_body_length, const struct MSG_AttachmentData *attachments, const struct MSG_AttachedFile *preloaded_attachments, //#ifdef MSG_SEND_MULTIPART_RELATED MSG_SendPart *relatedPart, //#endif void (*message_delivery_done_callback) (MWContext *context, void *fe_data, int status, const char *error_message)) { int failure = 0; m_pane = pane; m_fe_data = fe_data; m_message_delivery_done_callback = message_delivery_done_callback; //#ifdef MSG_SEND_MULTIPART_RELATED m_related_part = relatedPart; if (m_related_part) m_related_part->SetMimeDeliveryState(this); //#endif XP_ASSERT (fields); if (!fields) return MK_OUT_OF_MEMORY; /* rb -1; */ if (m_fields) { delete m_fields; } m_fields = new MSG_CompositionFields; if (!m_fields) return MK_OUT_OF_MEMORY; m_fields->SetOwner(pane); #ifdef GENERATE_MESSAGE_ID if (fields->GetMessageId()) { m_fields->SetMessageId(XP_STRDUP(fields->GetMessageId())); /* Don't bother checking for out of memory; if it fails, then we'll just let the server generate the message-id, and suffer with the possibility of duplicate messages.*/ } #endif /* GENERATE_MESSAGE_ID */ /* Strip whitespace from beginning and end of body. */ if (attachment1_body) { while (attachment1_body_length > 0 && XP_IS_SPACE (*attachment1_body)) { attachment1_body++; attachment1_body_length--; } while (attachment1_body_length > 0 && XP_IS_SPACE (attachment1_body [attachment1_body_length - 1])) { attachment1_body_length--; } if (attachment1_body_length <= 0) attachment1_body = 0; if (attachment1_body) { char *newb = (char *) XP_ALLOC (attachment1_body_length + 1); if (! newb) { return MK_OUT_OF_MEMORY; } XP_MEMCPY (newb, attachment1_body, attachment1_body_length); newb [attachment1_body_length] = 0; m_attachment1_body = newb; m_attachment1_body_length = attachment1_body_length; } } if (!fields->GetNewspostUrl() || !*fields->GetNewspostUrl()) fields->SetNewspostUrl("news:"); m_fields->SetNewspostUrl(fields->GetNewspostUrl()); m_fields->SetDefaultBody(fields->GetDefaultBody()); StrAllocCopy (m_attachment1_type, attachment1_type); StrAllocCopy (m_attachment1_encoding, "8bit"); /* strip whitespace from and duplicate header fields. */ SetMimeHeader(MSG_FROM_HEADER_MASK, fields->GetFrom()); SetMimeHeader(MSG_REPLY_TO_HEADER_MASK, fields->GetReplyTo()); SetMimeHeader(MSG_TO_HEADER_MASK, (fields->GetTo())); SetMimeHeader(MSG_CC_HEADER_MASK, (fields->GetCc())); SetMimeHeader(MSG_FCC_HEADER_MASK, (fields->GetFcc())); SetMimeHeader(MSG_BCC_HEADER_MASK, (fields->GetBcc())); SetMimeHeader(MSG_NEWSGROUPS_HEADER_MASK, (fields->GetNewsgroups())); SetMimeHeader(MSG_FOLLOWUP_TO_HEADER_MASK, (fields->GetFollowupTo())); SetMimeHeader(MSG_ORGANIZATION_HEADER_MASK, (fields->GetOrganization())); SetMimeHeader(MSG_SUBJECT_HEADER_MASK, (fields->GetSubject())); SetMimeHeader(MSG_REFERENCES_HEADER_MASK, (fields->GetReferences())); SetMimeHeader(MSG_X_TEMPLATE_HEADER_MASK, (fields->GetTemplateName())); if (fields->GetOtherRandomHeaders()) m_fields->SetOtherRandomHeaders(fields->GetOtherRandomHeaders()); if (fields->GetPriority()) m_fields->SetPriority(fields->GetPriority()); int i, j = (int) MSG_LAST_BOOL_HEADER_MASK; for (i = 0; i < j; i++) m_fields->SetBoolHeader((MSG_BOOL_HEADER_SET) i, fields->GetBoolHeader((MSG_BOOL_HEADER_SET) i)); #if 0 m_fields->SetReturnReceipt(fields->GetReturnReceipt()); HG29822 m_fields->SetSigned(fields->GetSigned()); m_fields->SetAttachVCard(fields->GetAttachVCard()); #endif m_fields->SetForcePlainText(fields->GetForcePlainText()); m_fields->SetUseMultipartAlternative(fields->GetUseMultipartAlternative()); if (pane && m_fields->GetReturnReceipt()) { if (m_fields->GetReturnReceiptType() == 1 || m_fields->GetReturnReceiptType() == 3) pane->SetRequestForReturnReceipt(TRUE); else pane->SetRequestForReturnReceipt(FALSE); } /* Check the fields for legitimacy, and run the callback if they're not ok. */ if ( mode != MSG_SaveAsDraft && mode != MSG_SaveAsTemplate ) failure = mime_sanity_check_fields (m_fields->GetFrom(), m_fields->GetReplyTo(), m_fields->GetTo(), m_fields->GetCc(), m_fields->GetBcc(), m_fields->GetFcc(), m_fields->GetNewsgroups(), m_fields->GetFollowupTo(), m_fields->GetSubject(), m_fields->GetReferences(), m_fields->GetOrganization(), m_fields->GetOtherRandomHeaders()); if (failure) return failure; m_digest_p = digest_p; m_dont_deliver_p = dont_deliver_p; m_deliver_mode = mode; // m_msg_file_name = WH_TempName (xpFileToPost, "nsmail"); failure = HackAttachments(attachments, preloaded_attachments); return failure; } /* 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', `attachment1_body' and `attachment1_body_length'. These may all be 0 if all attachments are provided externally. Subsequent attachments are provided as URLs to load, described in the MSG_AttachmentData 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...) */ extern "C" void MSG_StartMessageDelivery (MSG_Pane *pane, void *fe_data, MSG_CompositionFields *fields, XP_Bool digest_p, XP_Bool dont_deliver_p, const char *attachment1_type, const char *attachment1_body, uint32 attachment1_body_length, const struct MSG_AttachmentData *attachments, void *relatedPart, void (*message_delivery_done_callback) (MWContext *context, void *fe_data, int status, const char *error_message)) { MSG_SendMimeDeliveryState::StartMessageDelivery(pane, fe_data, fields, digest_p, dont_deliver_p, MSG_DeliverNow, /* ####??? */ attachment1_type, attachment1_body, attachment1_body_length, attachments, 0, (MSG_SendPart *) relatedPart, #ifdef XP_OS2 //DSR040297 - Casting away extern "C" //DSR040297 - Note: this simple little cast switches the function pointers from extern "C" //to non-extern "C" pointer (aka a C++ function). Don't try to change the static method, I've //tried it and it only gets uglier. This (for what its worth) is at least only a few casts in //spots where it makes sense. (void (*) (MWContext *, void *, int, const char *)) #endif message_delivery_done_callback); } extern "C" void msg_StartMessageDeliveryWithAttachments (MSG_Pane *pane, void *fe_data, MSG_CompositionFields *fields, XP_Bool digest_p, XP_Bool dont_deliver_p, MSG_Deliver_Mode mode, const char *attachment1_type, const char *attachment1_body, uint32 attachment1_body_length, const struct MSG_AttachedFile *attachments, //#ifdef MSG_SEND_MULTIPART_RELATED void *relatedPart, //#endif void (*message_delivery_done_callback) (MWContext *context, void *fe_data, int status, const char *error_message)) { MSG_SendMimeDeliveryState::StartMessageDelivery(pane, fe_data, fields, digest_p, dont_deliver_p, mode, attachment1_type, attachment1_body, attachment1_body_length, 0, attachments, (MSG_SendPart *) relatedPart, #ifdef XP_OS2 //DSR040297 - see comment above about 'Casting away extern "C"' (void (*) (MWContext *, void *, int, const char *)) #endif message_delivery_done_callback); } extern "C" int msg_DownloadAttachments (MSG_Pane *pane, void *fe_data, const struct MSG_AttachmentData *attachments, void (*attachments_done_callback) (MWContext *context, void *fe_data, int status, const char *error_message, struct MSG_AttachedFile *attachments)) { MSG_SendMimeDeliveryState *state = 0; int failure = 0; XP_ASSERT(attachments && attachments[0].url); /* if (!attachments || !attachments[0].url) { failure = -1; goto FAIL; } */ /* The only possible error above is out of memory and it is handled in MSG_CompositionPane::DownloadAttachments() */ state = new MSG_SendMimeDeliveryState; if (! state) { failure = MK_OUT_OF_MEMORY; goto FAIL; } state->m_pane = pane; state->m_fe_data = fe_data; state->m_attachments_only_p = TRUE; state->m_attachments_done_callback = #ifdef XP_OS2 //DSR040297 - see comment above about 'Casting away extern "C"' (void(*)(MWContext*,void*,int,const char*,MSG_AttachedFile*)) #endif attachments_done_callback; failure = state->HackAttachments(attachments, 0); if (failure >= 0) return 0; FAIL: XP_ASSERT (failure); /* in this case, our NET_GetURL exit routine has already freed the state */ if (failure != MK_ATTACHMENT_LOAD_FAILED) { char *err_msg = NET_ExplainErrorDetails (failure); attachments_done_callback (state->GetContext(), fe_data, failure, err_msg, 0); if (state) delete state; if (err_msg) XP_FREE (err_msg); #ifdef XP_MAC // ### mwelch The MacFE wants this error thrown as an exception. // This is because of the way that error recovery occurs // inside the compose session object. if (failure == MK_INTERRUPTED) failure = userCanceledErr; #endif } return failure; } void MSG_SendMimeDeliveryState::Clear() { if (m_fields) { delete m_fields; m_fields = NULL; } if (m_attachment1_type) XP_FREE (m_attachment1_type); if (m_attachment1_encoding) XP_FREE (m_attachment1_encoding); if (m_attachment1_body) XP_FREE (m_attachment1_body); if (m_attachment1_encoder_data) { MimeEncoderDestroy(m_attachment1_encoder_data, TRUE); m_attachment1_encoder_data = 0; } /* if (m_headers) XP_FREE (m_headers); */ if (m_msg_file) { XP_FileClose (m_msg_file); m_msg_file = 0; XP_ASSERT (m_msg_file_name); } if (m_imapOutgoingParser) { delete m_imapOutgoingParser; m_imapOutgoingParser = NULL; } if(m_imapLocalMailDB) { m_imapLocalMailDB->Close(); m_imapLocalMailDB = NULL; XP_FileRemove (m_msg_file_name, xpMailFolderSummary); } if (m_msg_file_name) { XP_FileRemove (m_msg_file_name, xpFileToPost); XP_FREE (m_msg_file_name); m_msg_file_name = 0; } if (m_attachments) { int i; for (i = 0; i < m_attachment_count; i++) { if (m_attachments [i].m_encoder_data) { MimeEncoderDestroy(m_attachments [i].m_encoder_data, TRUE); m_attachments [i].m_encoder_data = 0; } FREEIF (m_attachments [i].m_url_string); if (m_attachments [i].m_url) NET_FreeURLStruct (m_attachments [i].m_url); FREEIF (m_attachments [i].m_type); FREEIF (m_attachments [i].m_override_type); FREEIF (m_attachments [i].m_override_encoding); FREEIF (m_attachments [i].m_desired_type); FREEIF (m_attachments [i].m_description); FREEIF (m_attachments [i].m_x_mac_type); FREEIF (m_attachments [i].m_x_mac_creator); FREEIF (m_attachments [i].m_real_name); FREEIF (m_attachments [i].m_encoding); if (m_attachments [i].m_file) XP_FileClose (m_attachments [i].m_file); if (m_attachments [i].m_file_name) { if (!m_pre_snarfed_attachments_p) XP_FileRemove (m_attachments [i].m_file_name, xpFileToPost); XP_FREE (m_attachments [i].m_file_name); } #ifdef XP_MAC /* remove the appledoubled intermediate file after we done all. */ if (m_attachments [i].m_ap_filename) { XP_FileRemove (m_attachments [i].m_ap_filename, xpFileToPost); XP_FREE (m_attachments [i].m_ap_filename); } #endif /* XP_MAC */ } delete[] m_attachments; m_attachment_count = m_attachment_pending_count = 0; m_attachments = 0; } } void MSG_SendMimeDeliveryState::DeliverMessage () { XP_Bool mail_p = ((m_fields->GetTo() && *m_fields->GetTo()) || (m_fields->GetCc() && *m_fields->GetCc()) || (m_fields->GetBcc() && *m_fields->GetBcc())); XP_Bool news_p = (m_fields->GetNewsgroups() && *(m_fields->GetNewsgroups()) ? TRUE : FALSE); if ( m_deliver_mode != MSG_SaveAsDraft && m_deliver_mode != MSG_SaveAsTemplate ) XP_ASSERT(mail_p || news_p); #if 0 /* Figure out how many bytes we're actually going to be writing, total. */ m_delivery_bytes = 0; m_delivery_total_bytes = 0; if (m_fcc && *m_fcc) m_delivery_total_bytes += m_msg_size; if (m_queue_for_later_p) m_delivery_total_bytes += m_msg_size; else { if (mail_p) m_delivery_total_bytes += m_msg_size; if (news_p) m_delivery_total_bytes += m_msg_size; } #endif /* 0 */ if (m_deliver_mode == MSG_QueueForLater) { QueueForLater(); return; } else if (m_deliver_mode == MSG_SaveAsDraft) { SaveAsDraft(); return; } else if (m_deliver_mode == MSG_SaveAsTemplate) { SaveAsTemplate(); return; } /* if (m_fields->GetFcc()) if (!DoFcc()) return; */ #ifdef XP_UNIX { int status = msg_DeliverMessageExternally(GetContext(), m_msg_file_name); if (status != 0) { if (status < 0) Fail (status, 0); else { /* The message has now been delivered successfully. */ MWContext *context = GetContext(); if (m_message_delivery_done_callback) m_message_delivery_done_callback (context, m_fe_data, 0, NULL); m_message_delivery_done_callback = 0; Clear(); /* When attaching, even though the context has active_url_count == 0, XFE_AllConnectionsComplete() **is** called. However, when not attaching, and not delivering right away, we don't actually open any URLs, so we need to destroy the window ourself. Ugh!! */ if (m_attachment_count == 0) MSG_MailCompositionAllConnectionsComplete(MSG_FindPane(context, MSG_ANYPANE)); } return; } } #endif /* XP_UNIX */ #ifdef MAIL_BEFORE_NEWS if (mail_p) DeliverFileAsMail (); /* May call ...as_news() next. */ else if (news_p) DeliverFileAsNews (); #else /* !MAIL_BEFORE_NEWS */ if (news_p) DeliverFileAsNews (); /* May call ...as_mail() next. */ else if (mail_p) DeliverFileAsMail (); #endif /* !MAIL_BEFORE_NEWS */ else abort (); } #if 0 void MSG_SendMimeDeliveryState::DeliveryThermo (int32 increment) { int32 percent; m_delivery_bytes += increment; XP_ASSERT(m_delivery_total_bytes > 0); if (m_delivery_total_bytes <= 0) return; percent = 100 * (((double) m_delivery_bytes) / ((double) m_delivery_total_bytes)); FE_SetProgressBarPercent (GetContext(), percent); #if 0 FE_GraphProgress (GetContext(), 0, m_delivery_bytes, 0, m_delivery_total_bytes); #endif /* 0 */ } #endif /* 0 */ static void mime_deliver_as_mail_exit (URL_Struct *, int status, MWContext *); static void mime_deliver_as_news_exit (URL_Struct *url, int status, MWContext *); void MSG_SendMimeDeliveryState::DeliverFileAsMail () { char *buf, *buf2; URL_Struct *url; FE_Progress (GetContext(), XP_GetString(MK_MSG_DELIV_MAIL)); buf = (char *) XP_ALLOC ((m_fields->GetTo() ? XP_STRLEN (m_fields->GetTo()) + 10 : 0) + (m_fields->GetCc() ? XP_STRLEN (m_fields->GetCc()) + 10 : 0) + (m_fields->GetBcc() ? XP_STRLEN (m_fields->GetBcc()) + 10 : 0) + 10); if (! buf) { Fail (MK_OUT_OF_MEMORY, 0); return; } XP_STRCPY (buf, "mailto:"); buf2 = buf + XP_STRLEN (buf); if (m_fields->GetTo()) { XP_STRCAT (buf2, m_fields->GetTo()); } if (m_fields->GetCc()) { if (*buf2) XP_STRCAT (buf2, ","); XP_STRCAT (buf2, m_fields->GetCc()); } if (m_fields->GetBcc()) { if (*buf2) XP_STRCAT (buf2, ","); XP_STRCAT (buf2, m_fields->GetBcc()); } url = NET_CreateURLStruct (buf, NET_DONT_RELOAD); XP_FREE (buf); if (! url) { Fail (MK_OUT_OF_MEMORY, 0); return; } /* put the filename of the message into the post data field and set a flag in the URL struct to specify that it is a file */ url->post_data = XP_STRDUP(m_msg_file_name); url->post_data_size = XP_STRLEN(url->post_data); url->post_data_is_file = TRUE; url->method = URL_POST_METHOD; url->fe_data = this; url->internal_url = TRUE; url->msg_pane = m_pane; /* We can ignore the return value of NET_GetURL() because we have handled the error in mime_deliver_as_mail_exit(). */ MSG_UrlQueue::AddUrlToPane(url, mime_deliver_as_mail_exit, m_pane, TRUE); } void MSG_SendMimeDeliveryState::DeliverFileAsNews () { URL_Struct *url = NET_CreateURLStruct (m_fields->GetNewspostUrl(), NET_DONT_RELOAD); if (! url) { Fail (MK_OUT_OF_MEMORY, 0); return; } FE_Progress (GetContext(), XP_GetString(MK_MSG_DELIV_NEWS)); /* put the filename of the message into the post data field and set a flag in the URL struct to specify that it is a file. */ url->post_data = XP_STRDUP(m_msg_file_name); url->post_data_size = XP_STRLEN(url->post_data); url->post_data_is_file = TRUE; url->method = URL_POST_METHOD; url->fe_data = this; url->internal_url = TRUE; url->msg_pane = m_pane; /* We can ignore the return value of NET_GetURL() because we have handled the error in mime_deliver_as_news_exit(). */ MSG_UrlQueue::AddUrlToPane (url, mime_deliver_as_news_exit, m_pane, TRUE); } static void mime_deliver_as_mail_exit (URL_Struct *url, int status, MWContext * /*context*/) { MSG_SendMimeDeliveryState *state = (MSG_SendMimeDeliveryState *) url->fe_data; state->DeliverAsMailExit(url, status); } void MSG_SendMimeDeliveryState::DeliverAsMailExit(URL_Struct *url, int status) { char *error_msg = 0; url->fe_data = 0; if (status < 0 && url->error_msg) { error_msg = url->error_msg; url->error_msg = 0; } // NET_FreeURLStruct (url); if (status < 0) { Fail (status, error_msg); } #ifdef MAIL_BEFORE_NEWS else if (m_newsgroups) { /* If we're sending this mail message to news as well, start it now. Completion and further errors will be handled there. */ DeliverFileAsNews (); } #endif /* MAIL_BEFORE_NEWS */ else { /* The message has now been sent successfully! */ if (m_fields->GetFcc()) if (!DoFcc()) return; FE_Progress (GetContext(), XP_GetString(MK_MSG_DELIV_MAIL_DONE)); if (m_message_delivery_done_callback) m_message_delivery_done_callback (GetContext(), m_fe_data, 0, NULL); m_message_delivery_done_callback = 0; Clear(); delete this; } } static void mime_deliver_as_news_exit (URL_Struct *url, int status, MWContext * /*context*/) { MSG_SendMimeDeliveryState *state = (MSG_SendMimeDeliveryState *) url->fe_data; state->DeliverAsNewsExit(url, status); } void MSG_SendMimeDeliveryState::DeliverAsNewsExit(URL_Struct *url, int status) { char *error_msg = 0; url->fe_data = 0; if (status < 0 && url->error_msg) { error_msg = url->error_msg; url->error_msg = 0; } // NET_FreeURLStruct (url); if (status < 0) { Fail (status, error_msg); } #ifndef MAIL_BEFORE_NEWS else if ((m_fields->GetTo() && *m_fields->GetTo()) || (m_fields->GetCc() && *m_fields->GetCc()) || (m_fields->GetBcc() && *m_fields->GetBcc())) { /* If we're sending this news message to mail as well, start it now. Completion and further errors will be handled there. */ DeliverFileAsMail (); } #endif /* !MAIL_BEFORE_NEWS */ else { /* The message has now been sent successfully! */ if (m_fields->GetFcc()) if (!DoFcc()) return; FE_Progress (GetContext(), XP_GetString(MK_MSG_DELIV_NEWS_DONE)); if (m_message_delivery_done_callback) m_message_delivery_done_callback (GetContext(), m_fe_data, 0, NULL); m_message_delivery_done_callback = 0; Clear(); delete this; } } #ifdef XP_OS2 XP_BEGIN_PROTOS extern int32 msg_do_fcc_handle_line(char* line, uint32 length, void* closure); XP_END_PROTOS #else static int32 msg_do_fcc_handle_line(char* line, uint32 length, void* closure); #endif static int mime_do_fcc_1 (MSG_Pane *pane, const char *input_file_name, XP_FileType input_file_type, const char *output_name, XP_FileType output_file_type, MSG_Deliver_Mode mode, const char *bcc_header, const char *fcc_header, const char *news_url) { int status = 0; XP_File in = 0; XP_File out = 0; XP_Bool file_existed_p; XP_StatStruct st; char *ibuffer = 0; int ibuffer_size = TEN_K; char *obuffer = 0; int32 obuffer_size = 0, obuffer_fp = 0; int32 n; XP_Bool summaryWasValid = FALSE; XP_Bool summaryIsValid = FALSE; XP_Bool mark_as_read = TRUE; ParseOutgoingMessage *outgoingParser = NULL; MailDB *mail_db = NULL; MsgERR err = eSUCCESS; char *envelope; char *output_file_name = NET_ParseURL(output_name, GET_PATH_PART); if (!output_file_name || !*output_file_name) // must be real file path StrAllocCopy(output_file_name, output_name); if (mode == MSG_QueueForLater) FE_Progress (pane->GetContext(), XP_GetString(MK_MSG_QUEUEING)); else if ( mode == MSG_SaveAsDraft ) FE_Progress (pane->GetContext(), XP_GetString(MK_MSG_SAVING_AS_DRAFT)); else if ( mode == MSG_SaveAsTemplate ) FE_Progress (pane->GetContext(), XP_GetString(MK_MSG_SAVING_AS_TEMPLATE)); else FE_Progress (pane->GetContext(), XP_GetString(MK_MSG_WRITING_TO_FCC)); ibuffer = NULL; while (!ibuffer && (ibuffer_size >= 1024)) { ibuffer = (char *) XP_ALLOC (ibuffer_size); if (!ibuffer) ibuffer_size /= 2; } if (!ibuffer) { status = MK_OUT_OF_MEMORY; goto FAIL; } file_existed_p = !XP_Stat (output_file_name, &st, output_file_type); if (file_existed_p) { summaryWasValid = msg_IsSummaryValid(output_file_name, &st); if (!msg_ConfirmMailFile (pane->GetContext(), output_file_name)) { XP_FREEIF(output_file_name); return MK_INTERRUPTED; /* #### hack. It turns out we already were testing for this result code and silently canceling the send if we ever got it (because it meant that the user hit the Stop button). Returning it here has a similar effect -- the user was asked to confirm writing to the FCC folder, and he hit the Cancel button, so we now quietly do nothing. */ } } else { pane->GetMaster()->FindMailFolder(output_file_name, TRUE /*createIfMissing*/); if (-1 == XP_Stat (output_file_name, &st, output_file_type)) FE_Alert (pane->GetContext(), XP_GetString(MK_MSG_CANT_CREATE_FOLDER)); } out = XP_FileOpen (output_file_name, output_file_type, XP_FILE_APPEND_BIN); if (!out) { /* #### include file name in error message! */ status = MK_MSG_COULDNT_OPEN_FCC_FILE; goto FAIL; } in = XP_FileOpen (input_file_name, input_file_type, XP_FILE_READ_BIN); if (!in) { status = MK_UNABLE_TO_OPEN_FILE; /* rb -1; */ /* How did this happen? */ goto FAIL; } // set up database and outbound message parser to keep db up to date. outgoingParser = new ParseOutgoingMessage; err = MailDB::Open(output_file_name, FALSE, &mail_db); if (err != eSUCCESS) mail_db = NULL; if (!outgoingParser) { status = MK_OUT_OF_MEMORY; goto FAIL; } outgoingParser->SetOutFile(out); outgoingParser->SetMailDB(mail_db); outgoingParser->SetWriteToOutFile(FALSE); /* Write a BSD mailbox envelope line to the file. If the file existed, preceed it by a linebreak: this file format wants a *blank* line before all "From " lines except the first. This assumes that the previous message in the file was properly closed, that is, that the last line in the file ended with a linebreak. */ XP_FileSeek(out, 0, SEEK_END); if (file_existed_p && st.st_size > 0) { if (XP_FileWrite (LINEBREAK, LINEBREAK_LEN, out) < LINEBREAK_LEN) { status = MK_MIME_ERROR_WRITING_FILE; goto FAIL; } } outgoingParser->Init(XP_FileTell(out)); envelope = msg_GetDummyEnvelope(); if (msg_do_fcc_handle_line (envelope, XP_STRLEN (envelope), outgoingParser) < 0) { status = MK_MIME_ERROR_WRITING_FILE; 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 MSG_FLAG_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. */ if (mode == MSG_QueueForLater || mode == MSG_SaveAsDraft || mode == MSG_SaveAsTemplate || mark_as_read) { char *buf = 0; uint16 flags = 0; mark_as_read = TRUE; flags |= MSG_FLAG_READ; if (mode == MSG_QueueForLater ) flags |= MSG_FLAG_QUEUED; buf = PR_smprintf(X_MOZILLA_STATUS_FORMAT LINEBREAK, flags); if (buf) { status = msg_do_fcc_handle_line(buf, XP_STRLEN(buf), outgoingParser); XP_FREEIF(buf); if (status < 0) goto FAIL; } uint32 flags2 = 0; if (mode == MSG_SaveAsTemplate) flags2 |= MSG_FLAG_TEMPLATE; buf = PR_smprintf(X_MOZILLA_STATUS2_FORMAT LINEBREAK, flags2); if (buf) { status = msg_do_fcc_handle_line(buf, XP_STRLEN(buf), outgoingParser); XP_FREEIF(buf); if (status < 0) goto FAIL; } } /* 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 == MSG_QueueForLater || mode == MSG_SaveAsDraft || mode == MSG_SaveAsTemplate) && fcc_header && *fcc_header) { int32 L = XP_STRLEN(fcc_header) + 20; char *buf = (char *) XP_ALLOC (L); if (!buf) { status = MK_OUT_OF_MEMORY; goto FAIL; } PR_snprintf(buf, L-1, "FCC: %s" LINEBREAK, fcc_header); status = msg_do_fcc_handle_line(buf, XP_STRLEN(buf), outgoingParser); if (status < 0) goto FAIL; } if (bcc_header && *bcc_header #ifndef SAVE_BCC_IN_FCC_FILE && (mode == MSG_QueueForLater || mode == MSG_SaveAsDraft || mode == MSG_SaveAsTemplate) #endif ) { int32 L = XP_STRLEN(bcc_header) + 20; char *buf = (char *) XP_ALLOC (L); if (!buf) { status = MK_OUT_OF_MEMORY; goto FAIL; } PR_snprintf(buf, L-1, "BCC: %s" LINEBREAK, bcc_header); status = msg_do_fcc_handle_line(buf, XP_STRLEN(buf), outgoingParser); if (status < 0) 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 == MSG_QueueForLater || mode == MSG_SaveAsDraft || mode == MSG_SaveAsTemplate) && news_url && *news_url) { XP_Bool secure_p = (news_url[0] == 's' || news_url[0] == 'S'); char *orig_hap = NET_ParseURL (news_url, GET_HOST_PART); 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 = XP_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" LINEBREAK, host_and_port ? host_and_port : "", secure_p ? "/secure" : ""); FREEIF(orig_hap); orig_hap = 0; if (!line) { status = MK_OUT_OF_MEMORY; goto FAIL; } status = msg_do_fcc_handle_line(line, XP_STRLEN(line), outgoingParser); FREEIF(line); if (status < 0) goto FAIL; } FREEIF(orig_hap); orig_hap = 0; } /* 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... */ while (1) { n = XP_FileRead (ibuffer, ibuffer_size, in); if (n == 0) break; if (n < 0) /* read failed (not eof) */ { status = n; goto FAIL; } n = msg_LineBuffer (ibuffer, n, &obuffer, (uint32 *)&obuffer_size, (uint32*)&obuffer_fp, TRUE, msg_do_fcc_handle_line, outgoingParser); if (n < 0) /* write failed */ { status = n; goto FAIL; } } /* If there's still stuff in the buffer (meaning the last line had no newline) push it out. */ if (obuffer_fp > 0) msg_do_fcc_handle_line (obuffer, obuffer_fp, outgoingParser); /* Terminate with a final newline. */ if (XP_FileWrite (LINEBREAK, LINEBREAK_LEN, out) < LINEBREAK_LEN) { status = MK_MIME_ERROR_WRITING_FILE; } else outgoingParser->AdvanceOutPosition(LINEBREAK_LEN); if (mail_db != NULL && outgoingParser != NULL && outgoingParser->m_newMsgHdr != NULL) { outgoingParser->FinishHeader(); mail_db->AddHdrToDB(outgoingParser->m_newMsgHdr, NULL, TRUE); if (summaryWasValid) summaryIsValid = TRUE; } FAIL: if (ibuffer) XP_FREE (ibuffer); if (obuffer && obuffer != ibuffer) XP_FREE (obuffer); if (in) XP_FileClose (in); if (out) { if (status >= 0) { XP_FileClose (out); if (summaryIsValid) msg_SetSummaryValid(output_file_name, 0, 0); } else if (! file_existed_p) { XP_FileClose (out); XP_FileRemove (output_file_name, output_file_type); } else { XP_FileClose (out); XP_FileTruncate (output_file_name, output_file_type, st.st_size); /* restore original size */ } } if (mail_db != NULL && status >= 0) { if ( mode == MSG_SaveAsDraft ) { MSG_PostDeliveryActionInfo *actionInfo = pane->GetPostDeliveryActionInfo(); if (actionInfo) { if (actionInfo->m_flags & MSG_FLAG_EXPUNGED) { XP_ASSERT(actionInfo->m_msgKeyArray.GetSize()== 1); mail_db->DeleteMessage(actionInfo->m_msgKeyArray.GetAt(0)); actionInfo->m_msgKeyArray.RemoveAt(0); } } else { actionInfo = new MSG_PostDeliveryActionInfo((MSG_FolderInfo*) pane->GetMaster()-> FindMailFolder(output_file_name, TRUE)); if (actionInfo) { actionInfo->m_flags |= MSG_FLAG_EXPUNGED; pane->SetPostDeliveryActionInfo(actionInfo); } } if (outgoingParser->m_newMsgHdr && actionInfo) actionInfo->m_msgKeyArray.Add(outgoingParser->m_newMsgHdr->GetMessageKey()); } mail_db->Close(); } FREEIF(output_file_name); delete outgoingParser; if (status < 0) { /* Fail, and terminate. */ return status; } else { /* Otherwise, continue on to _deliver_as_mail or _deliver_as_news or mime_queue_for_later. */ return 0; } } #ifdef XP_OS2 extern int32 #else static int32 #endif msg_do_fcc_handle_line(char* line, uint32 length, void* closure) { ParseOutgoingMessage *outgoingParser = (ParseOutgoingMessage *) closure; int32 err = 0; XP_File out = outgoingParser->GetOutFile(); // if we have a DB, feed the line to the parser if (outgoingParser->GetMailDB() != NULL) { if (outgoingParser->m_bytes_written == 0) err = outgoingParser->StartNewEnvelope(line, length); else err = outgoingParser->ParseFolderLine(line, length); if (err < 0) return err; } #ifdef MANGLE_INTERNAL_ENVELOPE_LINES /* Note: it is correct to mangle all lines beginning with "From ", not just those that look like parsable message delimiters. Other software expects this. */ // If this really is the envelope, don't escape it. m_bytes_written will // be 0 in that case, because envelope is always first. if (outgoingParser->m_bytes_written > 0 && length >= 5 && line[0] == 'F' && !XP_STRNCMP(line, "From ", 5)) { if (XP_FileWrite (">", 1, out) < 1) return MK_MIME_ERROR_WRITING_FILE; outgoingParser->AdvanceOutPosition(1); } #endif /* MANGLE_INTERNAL_ENVELOPE_LINES */ /* #### if XP_FileWrite is a performance problem, we can put in a call to msg_ReBuffer() here... */ if (XP_FileWrite (line, length, out) < length) return MK_MIME_ERROR_WRITING_FILE; outgoingParser->m_bytes_written += length; return 0; } extern "C" int msg_DoFCC (MSG_Pane *pane, const char *input_file, XP_FileType input_file_type, const char *output_file, XP_FileType output_file_type, const char *bcc_header_value, const char *fcc_header_value) { XP_ASSERT(pane && input_file && *input_file && output_file && *output_file); if (! (pane && input_file && *input_file && output_file && *output_file)) return MK_MIME_ERROR_WRITING_FILE; return mime_do_fcc_1 (pane, input_file, input_file_type, output_file, output_file_type, MSG_DeliverNow, bcc_header_value, fcc_header_value, 0); } /* Returns false if an error happened. */ XP_Bool MSG_SendMimeDeliveryState::DoFcc() { if (!m_fields->GetFcc() || !*m_fields->GetFcc()) return TRUE; else if (NET_URL_Type(m_fields->GetFcc()) == IMAP_TYPE_URL && m_pane->GetMaster()->GetPrefs()->GetMailServerIsIMAP4()) { SendToImapMagicFolder(MSG_FOLDER_FLAG_SENTMAIL); return FALSE; } else { int status = msg_DoFCC (m_pane, m_msg_file_name, xpFileToPost, m_fields->GetFcc(), xpMailFolder, m_fields->GetBcc(), m_fields->GetFcc()); if (status < 0) Fail (status, 0); return (status >= 0); } } char * MSG_SendMimeDeliveryState::GetOnlineFolderName(uint32 flag, const char **pDefaultName) { char *onlineFolderName = NULL; switch (flag) { case MSG_FOLDER_FLAG_DRAFTS: if (pDefaultName) *pDefaultName = DRAFTS_FOLDER_NAME; PREF_CopyCharPref ("mail.default_drafts", &onlineFolderName); break; case MSG_FOLDER_FLAG_TEMPLATES: if (pDefaultName) *pDefaultName = TEMPLATES_FOLDER_NAME; PREF_CopyCharPref("mail.default_templates", &onlineFolderName); break; case MSG_FOLDER_FLAG_SENTMAIL: if (pDefaultName) *pDefaultName = SENT_FOLDER_NAME; onlineFolderName = XP_STRDUP(m_fields->GetFcc()); break; default: XP_ASSERT(0); break; } return onlineFolderName; } void MSG_SendMimeDeliveryState::SaveAsOfflineOp() { XP_ASSERT (m_imapOutgoingParser && m_imapLocalMailDB && m_imapFolderInfo); if (!m_imapOutgoingParser || !m_imapLocalMailDB || !m_imapFolderInfo) { Fail(MK_IMAP_UNABLE_TO_SAVE_MESSAGE, 0); return; } MsgERR err = eSUCCESS; MailDB *mailDB = NULL; MSG_IMAPFolderInfoMail *mailFolderInfo = m_imapFolderInfo->GetIMAPFolderInfoMail(); XP_ASSERT (mailFolderInfo); err = MailDB::Open (mailFolderInfo->GetPathname(), FALSE, &mailDB); if (err == eSUCCESS) { MessageKey fakeId = mailDB->GetUnusedFakeId(); MailMessageHdr *mailMsgHdr = NULL, *newMailMsgHdr = NULL; if (m_deliver_mode == MSG_SaveAsDraft) { // Do we have an actionInfo? MSG_PostDeliveryActionInfo *actionInfo = m_pane->GetPostDeliveryActionInfo(); if (actionInfo) { DBOfflineImapOperation *op = NULL; MessageKey msgKey = actionInfo->m_msgKeyArray.GetAt(0); if ((int32) msgKey >= 0) // real message key { // we start with an existing draft and save it while offline // delete the old message and create a new message header op = mailDB->GetOfflineOpForKey(msgKey, TRUE); if (op) { op->SetImapFlagOperation(op->GetNewMessageFlags() | kImapMsgDeletedFlag); delete op; } actionInfo->m_msgKeyArray.RemoveAt(0); actionInfo->m_msgKeyArray.Add(fakeId); mailDB->DeleteMessage(msgKey); } else // faked UID; reuse it { fakeId = actionInfo->m_msgKeyArray.GetAt(0); mailDB->DeleteOfflineOp(fakeId); mailDB->DeleteMessage(fakeId); } } else { // this is a new draft create a new actionInfo and a message // header actionInfo = new MSG_PostDeliveryActionInfo(m_imapFolderInfo); actionInfo->m_flags |= MSG_FLAG_EXPUNGED; actionInfo->m_msgKeyArray.Add(fakeId); m_pane->SetPostDeliveryActionInfo(actionInfo); } } newMailMsgHdr = new MailMessageHdr; mailMsgHdr = m_imapLocalMailDB->GetMailHdrForKey(0); if (mailMsgHdr) { if (newMailMsgHdr) { XP_File fileId = XP_FileOpen (m_msg_file_name, xpFileToPost, XP_FILE_READ_BIN); int iSize = 10240; char *ibuffer = NULL; while (!ibuffer && (iSize >= 512)) { ibuffer = (char *) XP_ALLOC(iSize); if (!ibuffer) iSize /= 2; } if (fileId && ibuffer) { int32 numRead = 0; newMailMsgHdr->CopyFromMsgHdr(mailMsgHdr, m_imapLocalMailDB->GetDB(), mailDB->GetDB()); newMailMsgHdr->SetMessageKey(fakeId); err = mailDB->AddHdrToDB(newMailMsgHdr, NULL, TRUE); // now write the offline message numRead = XP_FileRead(ibuffer, iSize, fileId); while(numRead > 0) { newMailMsgHdr->AddToOfflineMessage(ibuffer, numRead, mailDB->GetDB()); numRead = XP_FileRead(ibuffer, iSize, fileId); } // now add the offline op to the database DBOfflineImapOperation *op = mailDB->GetOfflineOpForKey(fakeId, TRUE); if (op) { op->SetAppendMsgOperation(mailFolderInfo->GetOnlineName(), m_deliver_mode == MSG_SaveAsDraft ? kAppendDraft : kAppendTemplate); delete op; /* The message has now been queued successfully. */ if (m_message_delivery_done_callback) m_message_delivery_done_callback (GetContext(), m_fe_data, 0, NULL); m_message_delivery_done_callback = 0; // Clear() clears the Fcc path Clear(); } else { mailDB->RemoveHeaderFromDB (newMailMsgHdr); Fail(MK_IMAP_UNABLE_TO_SAVE_MESSAGE, 0); } } else { Fail(MK_IMAP_UNABLE_TO_SAVE_MESSAGE, 0); } if (fileId) XP_FileClose(fileId); XP_FREEIF(ibuffer); delete newMailMsgHdr; } else { Fail(MK_IMAP_UNABLE_TO_SAVE_MESSAGE, 0); } delete mailMsgHdr; } else { Fail(MK_IMAP_UNABLE_TO_SAVE_MESSAGE, 0); } } else { Fail(MK_IMAP_UNABLE_TO_SAVE_MESSAGE, 0); } if (mailDB) mailDB->Close(); } void MSG_SendMimeDeliveryState::ImapAppendAddBccHeadersIfNeeded(URL_Struct *url) { XP_ASSERT(url); const char *bcc_headers = m_fields->GetBcc(); char *post_data = NULL; if (bcc_headers && *bcc_headers) { post_data = WH_TempName(xpFileToPost, "nsmail"); if (post_data) { XP_File dstFile = XP_FileOpen(post_data, xpFileToPost, XP_FILE_WRITE_BIN); if (dstFile) { XP_File srcFile = XP_FileOpen(m_msg_file_name, xpFileToPost, XP_FILE_READ_BIN); if (srcFile) { char *tmpBuffer = NULL; int bSize = TEN_K; while (!tmpBuffer && (bSize >= 512)) { tmpBuffer = (char *)XP_ALLOC(bSize); if (!tmpBuffer) bSize /= 2; } int bytesRead = 0; if (tmpBuffer) { XP_FileWrite("Bcc: ", 5, dstFile); XP_FileWrite(bcc_headers, XP_STRLEN(bcc_headers), dstFile); XP_FileWrite(CRLF, XP_STRLEN(CRLF), dstFile); bytesRead = XP_FileRead(tmpBuffer, bSize, srcFile); while (bytesRead > 0) { XP_FileWrite(tmpBuffer, bytesRead, dstFile); bytesRead = XP_FileRead(tmpBuffer, bSize, srcFile); } XP_FREE(tmpBuffer); } XP_FileClose(srcFile); } XP_FileClose(dstFile); } } } else { post_data = XP_STRDUP(m_msg_file_name); } if (post_data) { url->post_data = post_data; url->post_data_size = XP_STRLEN(post_data); url->post_data_is_file = TRUE; url->method = URL_POST_METHOD; url->fe_data = this; url->internal_url = TRUE; url->msg_pane = m_pane; m_pane->GetContext()->imapURLPane = m_pane; MSG_UrlQueue::AddUrlToPane (url, MSG_SendMimeDeliveryState::PostSendToImapMagicFolder, m_pane, TRUE); } else { NET_FreeURLStruct(url); } } /* Send the message to the magic folder, and runs the completion/failure callback. */ void MSG_SendMimeDeliveryState::SendToImapMagicFolder ( uint32 flag ) { char *onlineFolderName = NULL; const char *defaultName = ""; char *name = NULL; char *host = NULL; char *owner = NULL; URL_Struct* url = NULL; char* buf = NULL; if (!m_imapFolderInfo) { XP_ASSERT (m_pane); XP_ASSERT (m_pane->GetMaster()->GetPrefs()->GetMailServerIsIMAP4()); onlineFolderName = GetOnlineFolderName(flag, &defaultName); if (onlineFolderName && NET_URL_Type(onlineFolderName) == IMAP_TYPE_URL) { host = NET_ParseURL(onlineFolderName, GET_HOST_PART); name = NET_ParseURL(onlineFolderName, GET_PATH_PART); owner = NET_ParseURL(onlineFolderName, GET_USERNAME_PART); if (!name || !*name) { XP_FREEIF (name); // in case of allocated empty string name = PR_smprintf("/%s", defaultName); } if (!owner || !*owner) { MSG_IMAPHost *imapHost = m_pane->GetMaster()->GetIMAPHost(host); if (imapHost && imapHost->GetDefaultNamespacePrefixOfType(kPersonalNamespace)) StrAllocCopy(owner, imapHost->GetUserName()); } } if (name && *name && host && *host) m_imapFolderInfo = m_pane->GetMaster()->FindImapMailFolder(host, name+1, owner, FALSE); } if (m_imapFolderInfo) { if (NET_IsOffline()) { if (flag == MSG_FOLDER_FLAG_DRAFTS || flag == MSG_FOLDER_FLAG_TEMPLATES) SaveAsOfflineOp(); else XP_ASSERT(0); // shouldn't be here } else { buf = CreateImapAppendMessageFromFileUrl( m_imapFolderInfo->GetHostName(), m_imapFolderInfo->GetOnlineName(), m_imapFolderInfo->GetOnlineHierarchySeparator(), m_deliver_mode == MSG_SaveAsDraft); if (buf) { url = NET_CreateURLStruct(buf, NET_NORMAL_RELOAD); if (url) { ImapAppendAddBccHeadersIfNeeded(url); } XP_FREEIF(buf); } } } else if (host && name && *host && *name && !NET_IsOffline()) { if (m_pane->IMAPListMailboxExist()) { m_pane->SetIMAPListMailboxExist(FALSE); buf = CreateImapAppendMessageFromFileUrl(host, name+1, kOnlineHierarchySeparatorUnknown, m_deliver_mode == MSG_SaveAsDraft); if (buf) { url = NET_CreateURLStruct(buf, NET_NORMAL_RELOAD); if (url) { ImapAppendAddBccHeadersIfNeeded(url); } XP_FREEIF(buf); } } else { buf = CreateImapListUrl(host, name+1, kOnlineHierarchySeparatorUnknown); if (buf) { url = NET_CreateURLStruct(buf, NET_NORMAL_RELOAD); if (url) { url->fe_data = this; url->internal_url = TRUE; url->msg_pane = m_pane; GetContext()->imapURLPane = m_pane; m_pane->SetIMAPListInProgress(TRUE); MSG_UrlQueue::AddUrlToPane (url, MSG_SendMimeDeliveryState::PostListImapMailboxFolder, m_pane, TRUE); } XP_FREEIF(buf); } } } else { switch (m_deliver_mode) { case MSG_SaveAsDraft: FE_Alert(GetContext(),XP_GetString(MK_MSG_UNABLE_TO_SAVE_DRAFT)); break; case MSG_SaveAsTemplate: FE_Alert(GetContext(), XP_GetString(MK_MSG_UNABLE_TO_SAVE_TEMPLATE)); break; case MSG_DeliverNow: default: FE_Alert(GetContext(), XP_GetString(MK_MSG_COULDNT_OPEN_FCC_FILE)); break; } Fail(MK_IMAP_UNABLE_TO_SAVE_MESSAGE, 0); /* -1 rb */ } XP_FREEIF(host); XP_FREEIF(name); XP_FREEIF(owner); XP_FREEIF(onlineFolderName); } void MSG_SendMimeDeliveryState::PostCreateImapMagicFolder ( URL_Struct *url, int status, MWContext * context) { MSG_SendMimeDeliveryState *state = (MSG_SendMimeDeliveryState*) url->fe_data; XP_ASSERT(state); if (status < 0) { state->Fail (status, 0); } else { MSG_Master::PostCreateImapFolderUrlExitFunc (url, status, context); char *host = NET_ParseURL(url->address, GET_HOST_PART); char *name = NET_ParseURL(url->address, GET_PATH_PART); char *owner = NET_ParseURL(url->address, GET_USERNAME_PART); state->m_imapFolderInfo = state->m_pane->GetMaster()->FindImapMailFolder(host, name+1, owner, FALSE); XP_ASSERT(state->m_imapFolderInfo); if (state->m_imapFolderInfo) { if (state->m_deliver_mode == MSG_SaveAsDraft) { state->m_imapFolderInfo->SetFlag(MSG_FOLDER_FLAG_DRAFTS); state->SendToImapMagicFolder(MSG_FOLDER_FLAG_DRAFTS); } else if (state->m_deliver_mode == MSG_DeliverNow) { state->m_imapFolderInfo->SetFlag(MSG_FOLDER_FLAG_SENTMAIL); state->SendToImapMagicFolder(MSG_FOLDER_FLAG_SENTMAIL); } else if (state->m_deliver_mode == MSG_SaveAsTemplate) { state->m_imapFolderInfo->SetFlag(MSG_FOLDER_FLAG_TEMPLATES); state->SendToImapMagicFolder(MSG_FOLDER_FLAG_TEMPLATES); } else XP_ASSERT(FALSE); } else { state->Fail(MK_IMAP_NO_ONLINE_FOLDER, 0); /* -1 rb */ } XP_FREEIF(host); XP_FREEIF(name); XP_FREEIF(owner); } NET_FreeURLStruct(url); } void MSG_SendMimeDeliveryState::PostListImapMailboxFolder ( URL_Struct *url, int status, MWContext *context) { MSG_SendMimeDeliveryState *state = (MSG_SendMimeDeliveryState*) url->fe_data; XP_ASSERT(state); const char *defaultName = ""; if (status < 0) { state->Fail (status, 0); } else { uint32 flag; switch (state->m_deliver_mode) { case MSG_SaveAsDraft: defaultName = DRAFTS_FOLDER_NAME; flag = MSG_FOLDER_FLAG_DRAFTS; break; case MSG_SaveAsTemplate: defaultName = TEMPLATES_FOLDER_NAME; flag = MSG_FOLDER_FLAG_TEMPLATES; break; case MSG_DeliverNow: defaultName = SENT_FOLDER_NAME; flag = MSG_FOLDER_FLAG_SENTMAIL; break; default: XP_ASSERT(0); state->Fail(MK_IMAP_UNABLE_TO_SAVE_MESSAGE, 0); /* -1 rb */ return; break; } state->m_pane->SetIMAPListInProgress(FALSE); if (state->m_pane->IMAPListMailboxExist()) { state->SendToImapMagicFolder(flag); } else { char *onlineFolderName = msg_MagicFolderName(state->m_pane->GetMaster()->GetPrefs(), flag, &status); char *buf = NULL; char *host = NULL, *name = NULL /*, *owner = NULL */; MSG_IMAPHost *imapHost = NULL; if (status < 0) { char *error_msg = XP_GetString(status); state->Fail(status, error_msg ? XP_STRDUP(error_msg) : 0); return; } else if (!onlineFolderName) { state->Fail(MK_IMAP_NO_ONLINE_FOLDER, 0); /* -1 rb */ return; } XP_ASSERT(NET_URL_Type(onlineFolderName) == IMAP_TYPE_URL); host = NET_ParseURL(onlineFolderName, GET_HOST_PART); name = NET_ParseURL(onlineFolderName, GET_PATH_PART); // owner = NET_ParseURL(onlineFolderName, GET_USERNAME_PART); if (!name || !*name) { XP_FREEIF(name); name = PR_smprintf("/%s", defaultName); } imapHost = state->m_pane->GetMaster()->GetIMAPHost(host); XP_ASSERT(imapHost); if (imapHost->GetDefaultNamespacePrefixOfType(kPersonalNamespace)) { char *nameWithPrefix = PR_smprintf("%s%s", imapHost->GetDefaultNamespacePrefixOfType(kPersonalNamespace), name+1); if (nameWithPrefix) { XP_FREE(name); name = nameWithPrefix; } } // *** Create Imap magic folder // *** then append message to the folder buf = CreateImapMailboxCreateUrl(host, *name == '/' ? name+1 : name, kOnlineHierarchySeparatorUnknown); if (buf) { URL_Struct* url_struct = NULL; url_struct = NET_CreateURLStruct(buf, NET_NORMAL_RELOAD); if (url_struct) { url_struct->fe_data = state; url_struct->internal_url = TRUE; url_struct->msg_pane = state->m_pane; state->GetContext()->imapURLPane = state->m_pane; MSG_UrlQueue::AddUrlToPane (url_struct, MSG_SendMimeDeliveryState::PostCreateImapMagicFolder, state->m_pane, TRUE); } XP_FREEIF(buf); } XP_FREEIF(onlineFolderName); XP_FREEIF(host); XP_FREEIF(name); // XP_FREEIF(owner); } } NET_FreeURLStruct(url); } void MSG_SendMimeDeliveryState::SetIMAPMessageUID(MessageKey key) { XP_ASSERT(m_pane && m_pane->GetPaneType() == MSG_COMPOSITIONPANE); MSG_CompositionPane *composePane = (MSG_CompositionPane*) m_pane; composePane->SetIMAPMessageUID(key); } void MSG_SendMimeDeliveryState::PostSendToImapMagicFolder ( URL_Struct *url, int status, MWContext *context) { MSG_SendMimeDeliveryState *state = (MSG_SendMimeDeliveryState*) url->fe_data; MSG_CompositionPane *composePane = (MSG_CompositionPane*) state->m_pane; XP_ASSERT(state && composePane); MSG_PostDeliveryActionInfo *actionInfo = composePane->GetPostDeliveryActionInfo(); MailDB *mailDB = NULL; IDArray *idArray = new IDArray; char *onlineFolderName = NULL; if (XP_STRCMP(url->post_data, state->m_msg_file_name)) XP_FileRemove(url->post_data, xpFileToPost); if (status < 0) { state->Fail (status, 0); } else { /* The message has now been queued successfully. */ if (state->m_message_delivery_done_callback) state->m_message_delivery_done_callback (state->GetContext(), state->m_fe_data, 0, NULL); state->m_message_delivery_done_callback = 0; // Clear() clears the Fcc path state->Clear(); } XP_ASSERT (composePane && composePane->GetPaneType() == MSG_COMPOSITIONPANE); if (actionInfo && state->m_deliver_mode == MSG_SaveAsDraft && actionInfo->m_msgKeyArray.GetSize() > 1 && actionInfo->m_flags & MSG_FLAG_EXPUNGED) { MSG_Pane *urlPane = NULL; if (!urlPane) actionInfo->m_folderInfo ? state->m_pane->GetMaster()->FindPaneOfType (actionInfo->m_folderInfo, MSG_THREADPANE) : NULL; composePane->DeleteIMAPOldDraftUID(actionInfo, urlPane); } if (state->m_imapFolderInfo) { char *dbName = WH_FileName(state->m_imapFolderInfo->GetPathname(), xpMailFolderSummary); if (dbName) mailDB = (MailDB *) MessageDB::FindInCache(dbName); XP_FREEIF(dbName); MSG_Pane *urlPane = NULL; if (!urlPane) urlPane = state->m_pane->GetMaster()->FindPaneOfType (state->m_imapFolderInfo, MSG_THREADPANE); if (!urlPane) urlPane = composePane; if (mailDB && urlPane) { char *url_string = CreateImapMailboxLITESelectUrl( state->m_imapFolderInfo->GetIMAPFolderInfoMail()->GetHostName(), state->m_imapFolderInfo->GetIMAPFolderInfoMail()->GetOnlineName(), state->m_imapFolderInfo->GetIMAPFolderInfoMail()->GetOnlineHierarchySeparator()); if (url_string) { URL_Struct *url_struct = NET_CreateURLStruct(url_string, NET_NORMAL_RELOAD); XP_ASSERT(urlPane); if (url_struct) { state->m_imapFolderInfo->SetFolderLoadingContext(urlPane->GetContext()); urlPane->SetLoadingImapFolder(state->m_imapFolderInfo); url_struct->fe_data = (void *) state->m_imapFolderInfo; url_struct->internal_url = TRUE; url_struct->msg_pane = urlPane; urlPane->GetContext()->imapURLPane = urlPane; MSG_UrlQueue::AddUrlToPane (url_struct, MSG_Pane::PostLiteSelectExitFunc, urlPane, TRUE); } XP_FREEIF(url_string); } // MessageDB::FindInCache() does not add refCount so don't call close // mailDB->Close(); } else { idArray->Add(0); // add dummy message key state->m_imapFolderInfo->UpdatePendingCounts(state->m_imapFolderInfo, idArray, FALSE); state->m_imapFolderInfo->SummaryChanged(); // make sure we close down the cached imap connection when we done // with save draft; this is save draft then send case what about the closing // down the compose window case? if (urlPane->GetPaneType() != MSG_THREADPANE && state->m_imapFolderInfo->GetFlags() & MSG_FOLDER_FLAG_DRAFTS && state->m_deliver_mode != MSG_SaveAsDraft) composePane->GetMaster()->ImapFolderClosed(state->m_imapFolderInfo); } } XP_FREEIF(onlineFolderName); if (idArray) delete idArray; NET_FreeURLStruct(url); delete state; } /* Send the message to the magic folder, and runs the completion/failure callback. */ void MSG_SendMimeDeliveryState::SendToMagicFolder ( uint32 flag ) { char *name = 0; int status = 0; name = msg_MagicFolderName(m_pane->GetMaster()->GetPrefs(), flag, &status); if (status < 0) { char *error_msg = XP_GetString(status); Fail (status, error_msg ? XP_STRDUP(error_msg) : 0); return; } else if (!name || *name == 0) { XP_FREEIF(name); status = MK_OUT_OF_MEMORY; goto FAIL; } else if (NET_URL_Type(name) == IMAP_TYPE_URL && m_pane->GetMaster()->GetPrefs()->GetMailServerIsIMAP4()) { SendToImapMagicFolder(flag); return; } status = mime_do_fcc_1 (m_pane, m_msg_file_name, xpFileToPost, name, xpMailFolder, m_deliver_mode, m_fields->GetBcc(), m_fields->GetFcc(), (m_fields->GetNewsgroups() && *m_fields->GetNewsgroups() ? m_fields->GetNewspostUrl() : 0)); XP_FREEIF (name); if (status < 0) goto FAIL; FAIL: if (status < 0) { Fail (status, 0); } else { MWContext *context = GetContext(); FE_Progress(context, XP_GetString(MK_MSG_QUEUED)); /* The message has now been queued successfully. */ if (m_message_delivery_done_callback) m_message_delivery_done_callback (context, m_fe_data, 0, NULL); m_message_delivery_done_callback = 0; Clear(); /* When attaching, even though the context has active_url_count == 0, XFE_AllConnectionsComplete() **is** called. However, when not attaching, and not delivering right away, we don't actually open any URLs, so we need to destroy the window ourself. Ugh!! */ /* Unfortunately, Multipart related message falls into the same category. * If we are sending images within a html message, we'll be chaining URLs * one after another. Which will get FE_AllConnectionsComplete() to be * called from NET_ProcessNet(). To prevent from calling * MSG_MailCompositionAllConnectionsComplete() twice and * not crashing the browser we only call all connections complete * when we are not an mhtml message or an mhtml message which does not * embed any images. This could be done better. */ if (m_attachment_count == 0) if (!m_related_part || !m_related_part->GetNumChildren()) MSG_MailCompositionAllConnectionsComplete(MSG_FindPane(context, MSG_ANYPANE)); } } /* Queues the message for later delivery, and runs the completion/failure callback. */ void MSG_SendMimeDeliveryState::QueueForLater() { SendToMagicFolder (MSG_FOLDER_FLAG_QUEUE); } /* Save the message to the Drafts folder, and runs the completion/failure callback. */ void MSG_SendMimeDeliveryState::SaveAsDraft() { SendToMagicFolder (MSG_FOLDER_FLAG_DRAFTS); } /* Save the message to the Template folder, and runs the completion/failure callback. */ void MSG_SendMimeDeliveryState::SaveAsTemplate() { SendToMagicFolder (MSG_FOLDER_FLAG_TEMPLATES); } #ifdef _USRDLL PUBLIC void NET_RegisterDLLContentConverters() { NET_RegisterContentTypeConverter ("*", FO_MAIL_TO, NULL, mime_make_attachment_stream); NET_RegisterContentTypeConverter ("*", FO_CACHE_AND_MAIL_TO, NULL, mime_make_attachment_stream); /* #### What is this function for? Is this right? I've cloned this stuff from MSG_RegisterConverters() above. --jwz */ NET_RegisterContentTypeConverter ("*", FO_MAIL_MESSAGE_TO, NULL, mime_make_attachment_stream); NET_RegisterContentTypeConverter ("*", FO_CACHE_AND_MAIL_MESSAGE_TO, NULL, mime_make_attachment_stream); NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_MAIL_MESSAGE_TO, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_MAIL_MESSAGE_TO, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_CACHE_AND_MAIL_MESSAGE_TO, NULL, MIME_MessageConverter); NET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_CACHE_AND_MAIL_MESSAGE_TO, NULL, MIME_MessageConverter); } #endif