Milán Major

netscape-revival

1 branch
Code

lib/libmsg/compose.c

/* -*- Mode: C; tab-width: 4 -*-
   compose.c --- generation and delivery of MIME objects.
   Copyright © 1997 Netscape Communications Corporation, all rights reserved.
   Created: Jamie Zawinski <jwz@netscape.com>, 25-May-95.
 */

#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 "mimeenc.h"
#include "mailsum.h"
#include "xp_time.h"	/* For XP_LocalZoneOffset() */
#include "libi18n.h"
#include "xpgetstr.h"
#include "prtime.h"
#include "prtypes.h"
#include "composec.h"
#include "secrng.h"	/* for RNG_GenerateGlobalRandomBytes() */

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;

#ifdef XP_MAC
#include "m_cvstrm.h"
#endif

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;  


#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: <type>; name="<original-file-name>"
   as well as
      Content-Disposition: inline; filename="<original-file-name>"

  The lossage here is, RFC1341 defined the "name" parameter to Content-Type,
  but then RFC1521 deprecated it in anticipation of RFC1806, which defines
  Content-Type and the "filename" parameter.  But, RFC1521 is "Standards Track"
  while RFC1806 is still "Experimental."  So, it's probably best to just
  implement both.
 */
#define EMIT_NAME_IN_CONTENT_TYPE


/* Whether the contents of the BCC header should be preserved in the FCC'ed
   copy of a message.  See comments below, in mime_do_fcc_1().
 */
#ifndef XP_MAC
#define SAVE_BCC_IN_FCC_FILE
#endif

/* When attaching an HTML document, one must indicate the original URL of
   that document, if the receiver is to have any chance of being able to
   retreive and display the inline images, or to click on any links in the
   HTML.

   The way we have done this in the past is by inserting a <BASE> tag as the
   first line of all HTML documents we attach.  (This is kind of bad in that
   we're actually modifying the document, and it really isn't our place to
   do that.)

   The sanctioned *new* way of doing this is to insert a Content-Base header
   field on the attachment.  This is (will be) a part of the forthcoming MHTML
   spec.

   If GENERATE_CONTENT_BASE, we generate a Content-Base header.

   If MANGLE_HTML_ATTACHMENTS_WITH_BASE_TAG is defined, we add a BASE tag to
   the bodies; we should do this for a while if only for backward compatibility
   with the installed base.
 */
#define GENERATE_CONTENT_BASE
#define MANGLE_HTML_ATTACHMENTS_WITH_BASE_TAG


static XP_Bool mime_use_quoted_printable_p = TRUE;

#ifdef XP_MAC

extern OSErr my_FSSpecFromPathname(char* src_filename, FSSpec* fspec);
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:///<path>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

void
MIME_ConformToStandard (XP_Bool conform_p)
{
  mime_use_quoted_printable_p = conform_p;
}


struct mime_delivery_state
{
  MWContext *context;		/* Context to use when loading the URLs */
  void *fe_data;			/* passed in and passed to callback */
  char *to;					/* Where to send the message once it's done */
  char *cc;
  char *bcc;
  char *fcc;
  char *newsgroups;
  char *followup_to;
  char *reply_to;
  char *news_url;			/* the news url prefix for posting */
  char* message_id;

  /* these are used only in generating the initial header block. */
  char *from;
  char *organization;
  char *subject;
  char *references;
  char *other_random_headers;

  XP_Bool dont_deliver_p;	/* If set, we just return the name of the file
							   we created, instead of actually delivering
							   this message. */

  XP_Bool queue_for_later_p;	/* If set, we write the message to the
								   Queue folder, instead of doing delivery.
								   We also write FCC and BCC fields there
								   instead of doing them now. */

  XP_Bool encrypt_p;			/* Whether the user has requested crypto. */
  XP_Bool sign_p;

  XP_Bool attachments_only_p;	/* If set, then we don't construct a complete
								   MIME message; instead, we just retrieve the
								   attachments from the network, store them in
								   tmp files, and return a list of
								   MSG_AttachedFile structs which describe
								   them. */

  XP_Bool pre_snarfed_attachments_p;	/* If true, then the attachments were
										   loaded by msg_DownloadAttachments()
										   and therefore we shouldn't delete
										   the tmp files (but should leave
										   that to the caller.) */

  XP_Bool digest_p;			/* Whether to be multipart/digest instead of
							   multipart/mixed. */

  XP_Bool be_synchronous_p;	/* If true, we will load one URL after another,
							   rather than starting all URLs going at once
							   and letting them load in parallel.  This is
							   more efficient if (for example) all URLs are
							   known to be coming from the same news server
							   or mailbox: loading them in parallel would
							   cause multiple connections to the news server
							   to be opened, or would cause much seek()ing.
							 */

  void *crypto_closure;		/* State used by composec.c */

  /* The first attachment, if any (typed in by the user.)
   */
  char *attachment1_type;
  char *attachment1_encoding;
  MimeEncoderData *attachment1_encoder_data;
  char *attachment1_body;
  uint32 attachment1_body_length;

  /* Subsequent attachments, if any.
   */
  uint32 attachment_count;
  uint32 attachment_pending_count;
  struct mime_attachment *attachments;
  int32 status; /* in case some attachments fail but not all */

  /* The caller's `exit' method. */
  void (*message_delivery_done_callback) (MWContext *context,
										  void * fe_data, int status,
										  const char * error_msg);

  /* The exit method used when downloading attachments only. */
  void (*attachments_done_callback) (MWContext *context,
									 void * fe_data, int status,
									 const char * error_msg,
									 struct MSG_AttachedFile *attachments);

  char *msg_file_name;				/* Our temporary file */
  XP_File msg_file;
/*  char *headers;					/ * The headers of the message */

#if 0
  /* Some state to control the thermometer during message delivery.
   */
  int32 msg_size;					/* Size of the final message. */
  int32 delivery_total_bytes;		/* How many bytes we will be delivering:
									   for example, if we're sending the
									   message to both mail and news, this
									   will be 2x the size of the message.
									 */
  int32 delivery_bytes;				/* How many bytes we have delivered so far.
									 */
#endif /* 0 */
};

struct mime_attachment
{
  char *url_string;
  URL_Struct *url;
  XP_Bool done;

  struct mime_delivery_state *state;

  char *type;						/* The real type, once we know it. */
  char *override_type;				/* The type we should assume it to be
									   or 0, if we should get it from the
									   URL_Struct (from the server) */
  char *override_encoding;			/* Goes along with override_type */

  char *desired_type;				/* The type it should be converted to. */
  char *description;				/* For Content-Description header */
  char *x_mac_type, *x_mac_creator; /* Some kinda dumb-ass mac shit. */
  char *real_name;					/* The name for the headers, if different
									   from the URL. */
  char *encoding;					/* The encoding, once we've decided. */
  XP_Bool already_encoded_p;		/* If we attach a document that is already
									   encoded, we just pass it through. */

  char *file_name;					/* The temp file to which we save it */
  XP_File file;

#ifdef XP_MAC
  char *ap_filename;				/* The temp file holds the appledouble
									   encoding of the file we want to post. */
#endif

  XP_Bool decrypted_p;		/* S/MIME -- when attaching a message that was
							   encrypted, it's necessary to decrypt it first
							   (since nobody but the original recipient can
							   read it -- if you forward it to someone in the
							   raw, it will be useless to them.)  This flag
							   indicates whether decryption occurred, so that
							   libmsg can issue appropriate warnings about
							   doing a cleartext forward of a message that was
							   originally encrypted.
							 */
  uint32 size;						/* Some state used while filtering it */
  uint32 unprintable_count;
  uint32 highbit_count;
  uint32 ctl_count;
  uint32 null_count;
  uint32 current_column;
  uint32 max_column;
  uint32 lines;

  MimeEncoderData *encoder_data;	/* Opaque state for base64/qp encoder. */

  XP_Bool graph_progress_started;

  PrintSetup print_setup;			/* Used by HTML->Text and HTML->PS */
};


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_gather_attachments (struct mime_delivery_state *state);
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 (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* message_id,
									 const char *other_random_headers,
									 int csid);
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,
												int16 mail_csid);
static void mime_free_message_state (struct mime_delivery_state *state);
#if 0
static XP_Bool mime_type_conversion_possible (const char *from_type,
											  const char *to_type);
#endif
static void mime_deliver_message (struct mime_delivery_state *state);
static void mime_fail (struct mime_delivery_state *state, int failure_code,
					   char *error_msg);

static int mime_encoder_output_fn (const char *, int32, void *);
static int mime_write_message_body (struct mime_delivery_state *,
									char *buf, int32 size);

#ifdef XP_UNIX
extern void XFE_InitializePrintSetup (PrintSetup *p);
#endif /* XP_UNIX */

extern char * NET_ExplainErrorDetails (int code, ...);


char *
mime_make_separator(const char *prefix)
{
  unsigned char rand_buf[13]; 
#ifdef HAVE_SECURITY /* added by jwz */
  RNG_GenerateGlobalRandomBytes((void *) rand_buf, 12);
#else
  {
	int i;
	for (i = 0; i < 12; i++)
	  rand_buf[i] = random();
  }
#endif /* !HAVE_SECURITY -- added by jwz */
  return PR_smprintf("------------%s"
					 "%02X%02X%02X%02X"
					 "%02X%02X%02X%02X"
					 "%02X%02X%02X%02X",
					 prefix,
					 rand_buf[0], rand_buf[1], rand_buf[2], rand_buf[3],
					 rand_buf[4], rand_buf[5], rand_buf[6], rand_buf[7],
					 rand_buf[8], rand_buf[9], rand_buf[10], rand_buf[11]);
}


static int32
mime_snarf_attachment (struct mime_attachment *ma)
{
  int32 status = 0;
  XP_ASSERT (! ma->done);

  ma->file_name = WH_TempName (xpFileToPost, "nsmail");
  if (! ma->file_name)
	return (MK_OUT_OF_MEMORY);

  ma->file = XP_FileOpen (ma->file_name, xpFileToPost, XP_FILE_WRITE_BIN);
  if (! ma->file)
	return MK_UNABLE_TO_OPEN_TMP_FILE; /* #### how do we pass file name? */

  ma->url->fe_data = ma;

  /* #### ma->type is still unknown at this point.
	 We *really* 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(ma->url->address) &&
	  (strncasecomp(ma->url->address, "mailbox:", 8) != 0))
  {
	/* convert the apple file to the appledouble first, 
	**	and then patch the address in the url. 
	*/
	char* src_filename = NET_GetLocalFileFromURL (ma->url->address);
	
	if(isMacFile(src_filename))
	{
		char	*separator, tmp[128];
		NET_StreamClass *ad_encode_stream;
		
		separator = mime_make_separator("ad");
		if (!separator)
		  return MK_OUT_OF_MEMORY;
						
		ma->ap_filename  = WH_TempName (xpFileToPost, "nsmail");
		
		ad_encode_stream = (NET_StreamClass *)		/* need a prototype */
			fe_MakeAppleDoubleEncodeStream (FO_CACHE_AND_MAIL_TO,
	                         NULL,
	                         ma->url,
	                         ma->state->context,
	                         src_filename,
	                         ma->ap_filename,
	                         separator);	/* the file to hold the encoding */
		
		if (ad_encode_stream == NULL)
		  {
			FREEIF(separator);
			return MK_OUT_OF_MEMORY;
		  }
			
		do
		{ 
			status = (*ad_encode_stream->put_block)
				(ad_encode_stream->data_object, NULL, 1024);
		
		} while (status == noErr);
		
		if (status >= 0)
			(*ad_encode_stream->complete)(ad_encode_stream->data_object);
		else
			(*ad_encode_stream->abort)(ad_encode_stream->data_object, status);
	
		FREE(ad_encode_stream);
	
		if (status < 0)
		  {
			FREEIF(separator);
			return status;
		  }
			
		FREE(ma->url->address);
		ma->url->address = NET_GetURLFromLocalFile(ma->ap_filename);
		/*
		**	and also patch the types.
		*/ 
		if (ma->type) 
			XP_FREE (ma->type);
			
		XP_SPRINTF(tmp, MULTIPART_APPLEDOUBLE "; boundary=\"%s\"",
				   separator);
		FREEIF(separator);

		ma->type = XP_STRDUP(tmp);
	}
	else
	{
		/* 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);
	  		ma->x_mac_type    = XP_STRDUP(filetype);
	  		
	  		XP_SPRINTF(filetype, "%X", info.fdCreator);
	  		ma->x_mac_creator = XP_STRDUP(filetype);
	  		if (ma->type == NULL || 
	  			!strcasecomp (ma->type, TEXT_PLAIN))
	  		{

#define TEXT_TYPE	0x54455854
#define text_TYPE	0x74657874

	  			if (info.fdType != TEXT_TYPE && info.fdType != text_TYPE)
	  			{
	  				FE_FileType(ma->url->address, &useDefault,
								&macType, &macEncoding);

	  				FREEIF(ma->type);
	  				ma->type = macType;
	  			}
	  		}
	  	}
	  	
	  	/* don't bother to set the types if we failed in getting the file
		   infor. */		
	}
	FREEIF(src_filename);
  }
#endif /* XP_MAC */

  if (ma->desired_type &&
	  !strcasecomp (ma->desired_type, TEXT_PLAIN) /* #### &&
	  mime_type_conversion_possible (ma->type, ma->desired_type) */ )
	{
	  /* Conversion to plain text desired.
	   */
	  ma->print_setup.url = ma->url;
	  ma->print_setup.carg = ma;
	  ma->print_setup.completion = mime_text_attachment_url_exit;
	  ma->print_setup.filename = NULL;
	  ma->print_setup.out = ma->file;
	  ma->print_setup.eol = CRLF;
	  ma->print_setup.width = 76;
	  ma->url->savedData.FormList = 0;

	  /* 5-31-96 jefft -- bug# 21519
	   * We have to check for the valid return from XL_TranslateText.
	   * ma may already been free'd if url is malformed or for some other
	   * reason. 
	   */
#ifdef _USRDLL
	  if ( ! NDLLXL_TranslateText (ma->state->context, ma->url, &ma->print_setup) )
#else
	  if ( ! XL_TranslateText (ma->state->context, ma->url, &ma->print_setup) )
#endif
		return MK_ATTACHMENT_LOAD_FAILED;

	  if (ma->type) XP_FREE (ma->type);
	  ma->type = ma->desired_type;
	  ma->desired_type = 0;
	  if (ma->encoding) XP_FREE (ma->encoding);
	  ma->encoding = 0;
	}
#ifdef XP_UNIX
  else if (ma->desired_type &&
		   !strcasecomp (ma->desired_type, APPLICATION_POSTSCRIPT) /* #### &&
		   mime_type_conversion_possible (ma->type, ma->desired_type) */ )
	{
	  /* Conversion to postscript desired.
	   */
	  XFE_InitializePrintSetup (&ma->print_setup);
	  ma->print_setup.url = ma->url;
	  ma->print_setup.carg = ma;
	  ma->print_setup.completion = mime_text_attachment_url_exit;
	  ma->print_setup.filename = NULL;
	  ma->print_setup.out = ma->file;
	  ma->print_setup.eol = CRLF;
	  ma->url->savedData.FormList = 0;
	  XL_TranslatePostscript (ma->state->context, ma->url,
							  &ma->print_setup);
	  if (ma->type) XP_FREE (ma->type);
	  ma->type = ma->desired_type;
	  ma->desired_type = 0;
	  if (ma->encoding) XP_FREE (ma->encoding);
	  ma->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.
	  
	  */
	   get_url_status = NET_GetURL (ma->url, FO_CACHE_AND_MAIL_TO,
				   ma->state->context, 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)
{
  struct mime_attachment *ma = (struct mime_attachment *) url->fe_data;
  char *error_msg = url->error_msg;
  url->error_msg = 0;
  url->fe_data = 0;

  XP_ASSERT(ma != NULL);
  XP_ASSERT(ma->state != NULL);
  XP_ASSERT(ma->state->context != NULL);
  XP_ASSERT(ma->url != NULL);
  
  if (ma->graph_progress_started)
	{
	  ma->graph_progress_started = FALSE;
	  FE_GraphProgressDestroy (ma->state->context, ma->url,
							   ma->url->content_length, ma->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 (ma->file);
  ma->file = 0;
  NET_FreeURLStruct (ma->url);
  /* I'm pretty sure ma->url == url */
  ma->url = 0;
  url = 0;

  if (status < 0 && ma->state->status >= 0)
	ma->state->status = status;

  ma->done = TRUE;

  XP_ASSERT (ma->state->attachment_pending_count > 0);
  ma->state->attachment_pending_count--;

  if (status >= 0 && ma->state->be_synchronous_p)
	{
	  /* Find the next attachment which has not yet been loaded,
		 if any, and start it going.
		 */
	  int i;
	  struct mime_attachment *next = 0;
	  for (i = 0; i < ma->state->attachment_count; i++)
		if (!ma->state->attachments[i].done)
		  {
			next = &ma->state->attachments[i];
			break;
		  }
	  if (next)
		{
		  int status = mime_snarf_attachment (next);
		  if (status < 0)
			{
			  mime_fail (ma->state, status, 0);
			  return;
			}
		}
	}

  if (ma->state->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)
		{
		  mime_fail (ma->state, status, error_msg);
		  error_msg = 0;
		}
	  else
		{
		  mime_gather_attachments (ma->state);
		}
	}
  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);
}


static void
mime_text_attachment_url_exit (PrintSetup *p)
{
  struct mime_attachment *ma = (struct mime_attachment *) p->carg;
  XP_ASSERT (p->url == ma->url);
  ma->url->fe_data = ma;  /* grr */
  mime_attachment_url_exit (p->url, p->status, ma->state->context);
}


PRIVATE unsigned int
mime_attachment_stream_write_ready (void *closure)
{
  return MAX_WRITE_READY;
}

PRIVATE int
mime_attachment_stream_write (void *closure, const char *block, int32 length)
{
  struct mime_attachment *ma = (struct mime_attachment *) closure;
  const unsigned char *s;
  const unsigned char *end;

  if (ma->state->status < 0)
	return ma->state->status;

  ma->size += length;

  if (!ma->graph_progress_started)
	{
	  ma->graph_progress_started = TRUE;
	  FE_GraphProgressInit (ma->state->context, ma->url,
							ma->url->content_length);
	}

  FE_GraphProgress (ma->state->context, ma->url,
					ma->size, length, ma->url->content_length);


  /* Copy out the content type and encoding if we haven't already.
   */
  if (!ma->type && ma->url->content_type)
	{
	  ma->type = XP_STRDUP (ma->url->content_type);

	  /* If the URL has an encoding, and it's not one of the "null" encodings,
		 then keep it. */
	  if (ma->url->content_encoding &&
		  strcasecomp (ma->url->content_encoding, ENCODING_7BIT) &&
		  strcasecomp (ma->url->content_encoding, ENCODING_8BIT) &&
		  strcasecomp (ma->url->content_encoding, ENCODING_BINARY))
		{
		  if (ma->encoding) XP_FREE (ma->encoding);
		  ma->encoding = XP_STRDUP (ma->url->content_encoding);
		  ma->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->type || !*ma->type)
		StrAllocCopy (ma->type, UNKNOWN_CONTENT_TYPE);


#ifdef XP_WIN
	  /* This bogosity thanks to Garrett, our resident ZZ-Top fan.
		 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->type && !strncasecomp (ma->type, "zz-", 3))
		StrAllocCopy (ma->type, UNKNOWN_CONTENT_TYPE);
#endif /* XP_WIN */

	  /* This bogosity thanks to the server folks, and their co-contipators
		 at SGI.  There are some of these stupid "magnus" types in the default
		 mime.types file that SGI ships 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.
		 Losers.  Nuke 'em.
	   */
	  if (ma->type && !strncasecomp (ma->type, "magnus-internal/", 16))
		StrAllocCopy (ma->type, UNKNOWN_CONTENT_TYPE);


	  /* #### COMPLETE HORRIFIC KLUDGE
		 It totally sucks that 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->encoding &&
		  (!strcasecomp (ma->encoding, ENCODING_COMPRESS) ||
		   !strcasecomp (ma->encoding, ENCODING_COMPRESS2)))
		{
		  StrAllocCopy (ma->type, APPLICATION_COMPRESS);
		  StrAllocCopy (ma->encoding, ENCODING_BINARY);
		  ma->already_encoded_p = FALSE;
		}
	  else if (ma->encoding &&
			   (!strcasecomp (ma->encoding, ENCODING_GZIP) ||
				!strcasecomp (ma->encoding, ENCODING_GZIP2)))
		{
		  StrAllocCopy (ma->type, APPLICATION_GZIP);
		  StrAllocCopy (ma->encoding, ENCODING_BINARY);
		  ma->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->override_type)
		{
		  StrAllocCopy (ma->type, ma->override_type);
		  if (ma->override_encoding)
			StrAllocCopy (ma->encoding, ma->override_encoding);
		}
	}

  /* Cumulatively examine the data that is passing through this stream.
   */
  s = (unsigned char *) block;
  end = s + length;
  for (; s < end; s++)
	{
	  if (*s > 126)
		{
		  ma->highbit_count++;
		  ma->unprintable_count++;
		}
	  else if (*s < ' ' && *s != '\t' && *s != CR && *s != LF)
		{
		  ma->unprintable_count++;
		  ma->ctl_count++;
		  if (*s == 0)
			ma->null_count++;
		}

	  if (*s == CR || *s == LF)
		{
		  if (s+1 < end && s[0] == CR && s[1] == LF)
			s++;
		  if (ma->max_column < ma->current_column)
			ma->max_column = ma->current_column;
		  ma->current_column = 0;
		  ma->lines++;
		}
	  else
		{
		  ma->current_column++;
		}
	}

  /* Write it to the file.
   */
  while (length > 0)
	{
	  int32 l;
	  l = XP_FileWrite (block, length, ma->file);
	  if (l < length)
		{
		  ma->state->status = MK_MIME_ERROR_WRITING_FILE;
		  return ma->state->status;
		}
	  block += l;
	  length -= l;
	}

  return 1;
}

PRIVATE void
mime_attachment_stream_complete (void *closure)
{
  /* Nothing to do here - the URL exit method does our cleanup. */
}

PRIVATE void
mime_attachment_stream_abort (void *closure, int status)
{
  struct mime_attachment *ma = (struct mime_attachment *) closure;

  if (ma->state->status >= 0)
	ma->state->status = status;

  /* Nothing else to do here - the URL exit method does our cleanup. */
}


static NET_StreamClass *
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;
}


/* This is how libmime/mimemoz.c lets us know that the message was decrypted.
   Oh, the humanity. */
void
MSG_MimeNotifyCryptoAttachmentKludge(NET_StreamClass *stream)
{
  struct mime_attachment *ma;
  if (!stream || !stream->data_object)
	{
	  XP_ASSERT(0);
	  return;
	}

  ma = (struct mime_attachment *) stream->data_object;

  /* I'm nervous -- do some checks to make sure this void* really smells
	 like a mime_attachment struct. */
  if (!ma->state ||
	  !ma->state->context ||
	  ma->state->context != stream->window_id)
	{
	  XP_ASSERT(0);
	  return;
	}

  /* Well ok then. */
  ma->decrypted_p = TRUE;
}



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 been frobbed by 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);


  /* This is for newsgrp.c */
  NET_RegisterContentTypeConverter ("*", FO_APPEND_TO_FOLDER, 0,
									msg_MakeAppendToFolderStream);
  NET_RegisterContentTypeConverter ("*", FO_CACHE_AND_APPEND_TO_FOLDER, 0,
									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_QUOTE_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);
#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_QUOTE_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);
#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);
}


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++;

  /* #### sanity check other_random_headers for newline conventions */

  if (!from || !*from)
	return MK_MIME_NO_SENDER;
  else if ((!to || !*to) && (!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;
}


/* Given a content-type and some info about the contents of the document,
   decide what encoding it should have. 
 */
static int
mime_pick_encoding (struct mime_attachment *ma, int16 mail_csid)
{
  if (ma->already_encoded_p)
	goto DONE;

  if (mime_type_requires_b64_p (ma->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.)
	   */
	  StrAllocCopy (ma->encoding, ENCODING_BASE64);
	}
  else
	{
	  /* Otherwise, we need to pick an encoding based on the contents of
		 the document.
	   */

	  XP_Bool encode_p;

	  if (ma->max_column > 900)
		encode_p = TRUE;
	  else if (mime_use_quoted_printable_p && ma->unprintable_count)
		encode_p = TRUE;

	  else if (ma->null_count)	/* If there are nulls, we must always encode,
								   because sendmail will blow up. */
		encode_p = TRUE;
#if 0
	  else if (ma->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.
		 It also doesn't really work to convert them to text, but that's
		 because the whole architecture here is such a piece of junk.
	   */
	  if (!strncasecomp (ma->type, "message", 7) ||
		  !strncasecomp (ma->type, "multipart", 9))
		{
		  encode_p = FALSE;
		  if (ma->desired_type && !strcasecomp (ma->desired_type, TEXT_PLAIN))
			{
			  XP_FREE (ma->desired_type);
			  ma->desired_type = 0;
			}
		}
	  /* if the Mail csid is STATFUL ( CS_JIS or CS_2022_KR)
		we should force it to use 7 bit */
#ifdef XP_WIN
	  if((mail_csid & STATEFUL) &&
         ((strcasecomp(ma->type, TEXT_HTML) == 0) ||     
          (strcasecomp(ma->type, TEXT_MDL) == 0) ||
          (strcasecomp(ma->type, TEXT_PLAIN) == 0) ||
          (strcasecomp(ma->type, TEXT_RICHTEXT) == 0) ||
          (strcasecomp(ma->type, TEXT_ENRICHED) == 0) ||
          (strcasecomp(ma->type, MESSAGE_RFC822) == 0) ||
          (strcasecomp(ma->type, MESSAGE_NEWS) == 0)))
      {
        StrAllocCopy (ma->encoding, ENCODING_7BIT);
      }
#else
	  if(mail_csid & STATEFUL)
		StrAllocCopy (ma->encoding, ENCODING_7BIT);
#endif
	  else if (encode_p &&
			   ma->size > 500 &&
			   ma->unprintable_count > (ma->size / 10))
		/* If the document contains more than 10% unprintable characters,
		   then that seems like a good candidate for base64 instead of
		   quoted-printable.
		 */
		StrAllocCopy (ma->encoding, ENCODING_BASE64);
	  else if (encode_p)
		StrAllocCopy (ma->encoding, ENCODING_QUOTED_PRINTABLE);
	  else if (ma->highbit_count > 0)
		StrAllocCopy (ma->encoding, ENCODING_8BIT);
	  else
		StrAllocCopy (ma->encoding, ENCODING_7BIT);
	}


  /* Now that we've picked an encoding, initialize the filter.
   */
  XP_ASSERT(!ma->encoder_data);
  if (!strcasecomp(ma->encoding, ENCODING_BASE64))
	{
	  ma->encoder_data = MimeB64EncoderInit(mime_encoder_output_fn, ma->state);
	  if (!ma->encoder_data) return MK_OUT_OF_MEMORY;
	}
  else if (!strcasecomp(ma->encoding, ENCODING_QUOTED_PRINTABLE))
	{
	  ma->encoder_data = MimeQPEncoderInit(mime_encoder_output_fn, ma->state);
	  if (!ma->encoder_data) return MK_OUT_OF_MEMORY;
	}
  else
	{
	  ma->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 (!ma->type || !*ma->type || !strcasecomp(ma->type, UNKNOWN_CONTENT_TYPE))
	{
	  if (ma->already_encoded_p)
		StrAllocCopy (ma->type, APPLICATION_OCTET_STREAM);
	  else if (ma->encoding && !strcasecomp(ma->encoding, ENCODING_BASE64))
		StrAllocCopy (ma->type, APPLICATION_OCTET_STREAM);
	  else
		StrAllocCopy (ma->type, TEXT_PLAIN);
	}
  return 0;
}


/* Some types should have a "charset=" parameter, and some shouldn't.
   This is what decides. */
static 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;
}


static char *mime_mailto_stream_read_buffer = 0;
static char *mime_mailto_stream_write_buffer = 0;
#define MIME_BUFFER_SIZE 4096

static int32
mime_encode_block (struct mime_delivery_state *state,
				   MimeEncoderData *encoder_data,
				   const char *block, uint32 block_size)
{
  if (encoder_data)
	{
	  /* Feed it through the encoder. */
	  return MimeEncoderWrite(encoder_data, block, block_size);
	}
  else
	{
	  /* Merely translate all linebreaks to CRLF.
	   */
	  int status = 0;
	  const char *in = block;
	  const char *end = in + block_size;
	  char *buffer, *out;

	  /* Kludge to avoid having to allocate memory on the toy computers... */
	  if (! mime_mailto_stream_write_buffer)
		mime_mailto_stream_write_buffer = (char *) XP_ALLOC (MIME_BUFFER_SIZE);
	  buffer = mime_mailto_stream_write_buffer;
	  if (! buffer) return MK_OUT_OF_MEMORY;

	  XP_ASSERT (block != buffer);
	  out = buffer;

	  for (; in < end; in++)
		{
		  if (*in == CR || *in == LF)
			{
			  /* Write out the newline. */
			  *out++ = CR;
			  *out++ = LF;

			  status = mime_write_message_body(state, buffer, out - buffer);
			  if (status < 0) return status;
			  out = buffer;

			  /* If its CRLF, swallow two chars instead of one. */
			  if (in[0] == CR && in[1] == LF)
				in++;

			  out = buffer;
			}
		  else
			{
			  *out++ = *in;
			}
		}

	  /* Flush the last line. */
	  if (out > buffer)
		{
		  status = mime_write_message_body(state, buffer, out - buffer);
		  if (status < 0) return status;
		  out = buffer;
		}
	}

  return 0;
}


#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.
 */
static int
mime_gather_attachments (struct mime_delivery_state *state)
{
  int32 status, i;
  char *headers = 0;
  char *separator = 0;
  XP_File in_file = 0;
  XP_Bool multipart_p = FALSE;
  char *buffer = 0;
  char *buffer_tail = 0;
  char* error_msg = 0;
  int16 win_csid	 = INTL_DefaultWinCharSetID(state->context);
  int16 mail_csid 	 = INTL_DefaultMailCharSetID(win_csid);

  status = state->status;
  if (status < 0)
	goto FAIL;


  if (state->attachments_only_p)
	{
	  /* If we get here, we shouldn't have the "generating a message" cb. */
	  XP_ASSERT(!state->dont_deliver_p &&
				!state->message_delivery_done_callback);
	  if (state->attachments_done_callback)
		{
		  struct MSG_AttachedFile *attachments;

		  XP_ASSERT(state->attachment_count > 0);
		  if (state->attachment_count <= 0)
			{
			  state->attachments_done_callback (state->context,
												state->fe_data, 0, 0,
												0);
			  state->attachments_done_callback = 0;
			  mime_free_message_state (state);
			  state = 0;
			  goto FAIL;
			}

		  attachments = (struct MSG_AttachedFile *)
			XP_ALLOC((state->attachment_count + 1) * sizeof(*attachments));

		  if (!attachments)
			{
			  status = MK_OUT_OF_MEMORY;
			  goto FAIL;
			}
		  XP_MEMSET(attachments, 0, ((state->attachment_count + 1)
									 * sizeof(*attachments)));
		  for (i = 0; i < state->attachment_count; i++)
			{
			  struct mime_attachment *ma = &state->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->url_string);
			  SNARF(attachments[i].file_name, ma->file_name);
			  SNARF(attachments[i].type, ma->type);
			  SNARF(attachments[i].encoding, ma->encoding);
			  SNARF(attachments[i].description, ma->description);
			  SNARF(attachments[i].x_mac_type, ma->x_mac_type);
			  SNARF(attachments[i].x_mac_creator, ma->x_mac_creator);
#undef SNARF
			  attachments[i].decrypted_p = ma->decrypted_p;
			  attachments[i].size = ma->size;
			  attachments[i].unprintable_count = ma->unprintable_count;
			  attachments[i].highbit_count = ma->highbit_count;
			  attachments[i].ctl_count = ma->ctl_count;
			  attachments[i].null_count = ma->null_count;
			  attachments[i].max_line_length = ma->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!
		   */
		  state->attachments_done_callback (state->context,
											state->fe_data, 0, 0,
											attachments);
		  state->attachments_done_callback = 0;
		  mime_free_message_state (state);
		  state = 0;
		}
	  goto FAIL;
	}


  /* If we get here, we're generating a message, so there shouldn't be an
	 attachments callback. */
  XP_ASSERT(!state->attachments_done_callback);


  /* Kludge to avoid having to allocate memory on the toy computers... */
  if (! mime_mailto_stream_write_buffer)
	mime_mailto_stream_write_buffer = (char *) XP_ALLOC (MIME_BUFFER_SIZE);
  buffer = mime_mailto_stream_write_buffer;
  if (! buffer) return MK_OUT_OF_MEMORY;

  buffer_tail = buffer;

  XP_ASSERT (state->attachment_pending_count == 0);

  FE_Progress(state->context, XP_GetString(MK_MSG_ASSEMBLING_MSG));


  /* First, open the message file.
   */
  state->msg_file_name = WH_TempName (xpFileToPost, "nsmail");
  if (! state->msg_file_name)
	{
	  status = MK_OUT_OF_MEMORY;
	  goto FAIL;
	}
  state->msg_file = XP_FileOpen (state->msg_file_name, xpFileToPost,
								 XP_FILE_WRITE_BIN);
  if (! state->msg_file)
	{
	  status = MK_UNABLE_TO_OPEN_TMP_FILE;
	  error_msg = PR_smprintf(XP_GetString(status), state->msg_file_name);
	  if (!error_msg) status = MK_OUT_OF_MEMORY;
	  goto FAIL;
	}

#ifdef GENERATE_MESSAGE_ID
  if (state->message_id == NULL) {
	state->message_id = msg_generate_message_id ();
  }
#endif /* GENERATE_MESSAGE_ID */


  /* Write out the message headers.
   */
  headers = mime_generate_headers (state->from, state->reply_to, state->to,
								   state->cc, state->bcc, state->fcc,
								   state->newsgroups, state->followup_to,
								   state->subject, state->references,
								   state->organization,
								   state->message_id,
								   state->other_random_headers,
								   state->context->win_csid);
  if (!headers)
	{
	  status = MK_OUT_OF_MEMORY;
	  goto FAIL;
	}
  if (XP_FileWrite(headers, XP_STRLEN (headers),
				   state->msg_file) < XP_STRLEN(headers))
	{
	  status = MK_MIME_ERROR_WRITING_FILE;
	  goto FAIL;
	}


#ifdef HAVE_SECURITY /* added by jwz */
  if (state->encrypt_p || state->sign_p)
	{
	  char *recipients = (char *)
		XP_ALLOC((state->to  ? XP_STRLEN(state->to)  : 0) +
				 (state->cc  ? XP_STRLEN(state->cc)  : 0) +
				 (state->bcc ? XP_STRLEN(state->bcc) : 0) +
				 (state->newsgroups ? XP_STRLEN(state->newsgroups) : 0) +
				 20);
	  if (!recipients)
		{
		  status = MK_OUT_OF_MEMORY;
		  goto FAIL;
		}
	  *recipients = 0;
# define FROB(X) \
	  if (state->X && *state->X) \
		{ \
		  if (*recipients) XP_STRCAT(recipients, ","); \
		  XP_STRCAT(recipients, state->X); \
		}
	  FROB(to)
	  FROB(cc)
	  FROB(bcc)
	  FROB(newsgroups)
# undef FROB
	  status = mime_begin_crypto_encapsulation (state->context,
												state->msg_file,
												&state->crypto_closure,
												state->encrypt_p,
												state->sign_p,
												recipients,
												FALSE);
	  XP_FREE(recipients);
	  if (status < 0) goto FAIL;
	}
#endif /* NO_SSL -- added by jwz */


  multipart_p = (state->attachment_count > 1 ||
				 (state->attachment_count == 1 &&
				  state->attachment1_body_length > 0));

  if (multipart_p)
	{
	  /* It's a multipart.  Write the content-type and first separator. */

	  const char *multipart_blurb = (state->crypto_closure
									 ? 0
									 : XP_GetString (MK_MIME_MULTIPART_BLURB));

	  separator = mime_make_separator("");
	  if (!separator)
		{
		  status = MK_OUT_OF_MEMORY;
		  goto FAIL;
		}

	  PUSH_STRING ("Content-Type: " MULTIPART_MIXED "; boundary=\"");
	  PUSH_STRING (separator);
	  PUSH_STRING ("\"" CRLF);
	  PUSH_NEWLINE ();				/* close header block */

	  if (multipart_blurb)
		{
		  PUSH_STRING (multipart_blurb);
		  PUSH_STRING (CRLF CRLF);
		}

	  PUSH_STRING ("--");
	  PUSH_STRING (separator);
	  PUSH_NEWLINE ();
	  status = mime_write_message_body (state, buffer, buffer_tail - buffer);
	  buffer_tail = buffer;
	  if (status < 0) goto FAIL;
	}

  /* Write out the first part (user-typed.)
   */
  if (state->attachment1_body_length > 0)
	{
	  unsigned char * intlbuff;
	  char *hdrs = 0;
	  
	  if ((mail_csid & STATEFUL) || /* CS_JIS or CS_2022_KR */
			  mime_7bit_data_p (state->attachment1_body,
							state->attachment1_body_length))	
		StrAllocCopy (state->attachment1_encoding, ENCODING_7BIT);
	  else if (mime_use_quoted_printable_p)
		StrAllocCopy (state->attachment1_encoding, ENCODING_QUOTED_PRINTABLE);
	  else
		StrAllocCopy (state->attachment1_encoding, ENCODING_8BIT);


	  /* Set up encoder for the first part (message body.)
	   */
	  XP_ASSERT(!state->attachment1_encoder_data);
	  if (!strcasecomp(state->attachment1_encoding, ENCODING_BASE64))
		{
		  state->attachment1_encoder_data =
			MimeB64EncoderInit(mime_encoder_output_fn, state);
		  if (!state->attachment1_encoder_data)
			{
			  status = MK_OUT_OF_MEMORY;
			  goto FAIL;
			}
		}
	  else if (!strcasecomp(state->attachment1_encoding,
							ENCODING_QUOTED_PRINTABLE))
		{
		  state->attachment1_encoder_data =
			MimeQPEncoderInit(mime_encoder_output_fn, state);
		  if (!state->attachment1_encoder_data)
			{
			  status = MK_OUT_OF_MEMORY;
			  goto FAIL;
			}
		}


	  hdrs = mime_generate_attachment_headers (state->attachment1_type,
											   state->attachment1_encoding,
											   0, 0, 0, 0, 0,
											   state->digest_p,
											   mail_csid);
	  if (!hdrs)
		{
		  status = MK_OUT_OF_MEMORY;
		  goto FAIL;
		}
	  status = mime_write_message_body (state, hdrs, XP_STRLEN(hdrs));
	  if (status < 0) goto FAIL;

	  FREEIF(hdrs);


	  /* for the message body. We need to convert it from the win_csid to the
	       mail csid */
	  intlbuff = INTL_ConvWinToMailCharCode (state->context, 
						(unsigned char *)state->attachment1_body, 
						state->attachment1_body_length);
						
	  if (intlbuff)
		{
		  /* if it is multibyte and return something. We need to free the origional one and
			assign the state->attachment1_body to the new one */
		  if (intlbuff != (unsigned char *)state->attachment1_body)
		  	{
		  	  XP_FREE (state->attachment1_body);
		  	  state->attachment1_body_length = XP_STRLEN((char*)intlbuff);
		  	}
		  state->attachment1_body = (char*)intlbuff;
		}
		
	  status = mime_encode_block (state,
								  state->attachment1_encoder_data,
								  state->attachment1_body,
								  state->attachment1_body_length);
	  if (status < 0)
		goto FAIL;
				
	  XP_FREE (state->attachment1_body);
	  state->attachment1_body = 0;

	  /* Close off encoder for the first part (message body.) */
	  if (state->attachment1_encoder_data)
		{
		  status = MimeEncoderDestroy(state->attachment1_encoder_data, FALSE);
		  state->attachment1_encoder_data = 0;
		  if (status < 0) goto FAIL;
		}


	  /* Write out multipart seperator */
	  if (multipart_p)
		{
		  buffer_tail = buffer;
		  /* one CRLF to terminate last line of first part;
			 a second CRLF to insert a blank line; and then
			 two dashes to start the separator. */
		  PUSH_STRING (CRLF CRLF "--");
		  PUSH_STRING (separator);
		  PUSH_NEWLINE ();
		  status = mime_write_message_body (state, buffer,
											buffer_tail - buffer);
		  buffer_tail = buffer;
		  if (status < 0) goto FAIL;
		}
	}

  /* Write out the subsequent parts.
   */
  if (state->attachment_count > 0)
	{
	  char *buffer;

	  /* Kludge to avoid having to allocate memory on the toy computers... */
	  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) return MK_OUT_OF_MEMORY;
	  buffer_tail = buffer;

	  for (i = 0; i < state->attachment_count; i++)
		{
		  struct mime_attachment *ma = &state->attachments[i];
		  char *hdrs = 0;

		  status = mime_pick_encoding (ma, mail_csid);
		  if (status < 0) goto FAIL;
		  		  
		  hdrs = mime_generate_attachment_headers (ma->type, ma->encoding,
												   ma->description,
												   ma->x_mac_type,
												   ma->x_mac_creator,
												   ma->real_name,
												   ma->url_string,
												   state->digest_p,
												   mail_csid);
		  if (!hdrs)
			{
			  status = MK_OUT_OF_MEMORY;
			  goto FAIL;
			}
		  status = mime_write_message_body (state, hdrs, XP_STRLEN(hdrs));
		  if (status < 0) goto FAIL;

		  FREEIF(hdrs);

		  in_file = XP_FileOpen (ma->file_name, xpFileToPost,
								 XP_FILE_READ_BIN);
		  if (! in_file)
			{
			  status = -1;
			  goto FAIL;
			}

		  ma->current_column = 0;

#ifdef MANGLE_HTML_ATTACHMENTS_WITH_BASE_TAG
		  if (ma->url_string &&
			  ma->type &&
			  !ma->already_encoded_p &&		/* can't add it in this case */
			  (!strcasecomp (ma->type, TEXT_HTML) ||
			   !strcasecomp (ma->type, TEXT_MDL)))
			{
			  char *base = (char *) XP_ALLOC (XP_STRLEN (ma->url_string) + 50);
			  if (!base)
				return MK_OUT_OF_MEMORY;
			  XP_STRCPY (base, "<BASE HREF=\"");
			  XP_STRCAT (base, ma->url_string);
			  XP_STRCAT (base, "\">" CRLF CRLF);
			  status = mime_encode_block (state, ma->encoder_data,
										  base, XP_STRLEN (base));
			  XP_FREE (base);
			}
#endif /* MANGLE_HTML_ATTACHMENTS_WITH_BASE_TAG */

		  if (ma->type &&
			  (!strcasecomp (ma->type, MESSAGE_RFC822) ||
			   !strcasecomp (ma->type, MESSAGE_NEWS)))
			{
			  /* We are attaching a message, so we should be careful to
				 strip out certain sensitive internal header fields.
			   */
			  XP_Bool in_headers = TRUE;
			  XP_Bool skipping = FALSE;
			  XP_ASSERT(MIME_BUFFER_SIZE > 1000); /* SMTP (RFC821) limit */

			  while (1)
				{
				  char *line = XP_FileReadLine(buffer, MIME_BUFFER_SIZE-3,
											   in_file);
				  if (!line) break;  /* EOF */

				  if (skipping)
					{
					  if (*line == ' ' || *line == '\t')
						continue;
					  else
						skipping = FALSE;
					}

				  if (in_headers &&
					  (!strncasecomp(line, "BCC:", 4) ||
					   !strncasecomp(line, "FCC:", 4) ||
					   !strncasecomp(line, CONTENT_LENGTH ":",
									 CONTENT_LENGTH_LEN + 1) ||
					   !strncasecomp(line, "Lines:", 6) ||
					   !strncasecomp(line, "Status:", 7) ||
					   !strncasecomp(line, X_MOZILLA_STATUS ":",
									 X_MOZILLA_STATUS_LEN+1) ||
					   !strncasecomp(line, X_MOZILLA_NEWSHOST ":",
									 X_MOZILLA_NEWSHOST_LEN+1) ||
					   !strncasecomp(line, X_UIDL ":", X_UIDL_LEN+1) ||
					   !strncasecomp(line, "X-VM-", 5))) /* hi Kyle */
					{
					  skipping = TRUE;
					  continue;
					}

				  status = mime_encode_block (state, ma->encoder_data,
											  line, XP_STRLEN(line));
				  if (status < 0) return status;

				  if (in_headers && (*line == CR || *line == LF))
					in_headers = FALSE;
				}
			}
		  else
			{
			  /* Normal attachment.  Do big reads.
			   */
			  XP_Bool firstBlock = TRUE;
			  /* only convert if we need to tag charset */
			  XP_Bool needIntlConversion = mime_type_needs_charset(ma->type);
			  CCCDataObject	*intlDocToMailConverter = NULL;
			  char* encoded_data = NULL;
			  uint32 encoded_data_len = 0;
		  	  while (1)
				{
			  	  status = XP_FileRead (buffer, MIME_BUFFER_SIZE, in_file);
			  	  if (status < 0)
			    	{
				  	  if(intlDocToMailConverter)
					 	  INTL_DestroyCharCodeConverter(intlDocToMailConverter);
				  	  goto FAIL;
					}
			  	  else if (status == 0)
					  break;

			  	  encoded_data = buffer;
			  	  encoded_data_len = status;
			  
			  	  /* if this is the first block, create the conversion object */
			  	  if(firstBlock)
					{
				  	  if(needIntlConversion)
						{
					  	  intlDocToMailConverter = 
						  	  INTL_CreateDocToMailConverter(
							  		  state->context, 
									  (!strcasecomp (ma->type, TEXT_HTML)) , 
									  (unsigned char*) buffer, 
									  status);
						}
				  	  firstBlock = FALSE;		/* No longer the first block */
					}
				
			  	  if(intlDocToMailConverter)
					{
				  	  encoded_data = (char*)INTL_CallCharCodeConverter(
												intlDocToMailConverter, 
											  	(unsigned char*)buffer,
											  	status);
				  		/* the return buffer is different from the */
						/* origional one. The size need to be change */
				  	  if(encoded_data && encoded_data != buffer)
						  encoded_data_len = XP_STRLEN(encoded_data);
					}
				
			  	  status = mime_encode_block (state,
										  	  (ma->already_encoded_p
										   	  /* If the file already has an
											 	   encoding, leave it alone. */
										 	    ? 0
										 	    : ma->encoder_data),
										  	  (encoded_data 
										 	    ? encoded_data 
										 	    : buffer), 
										  	  encoded_data_len);
										  
			  	  if(encoded_data && encoded_data != buffer)
				 	  XP_FREE(encoded_data);
					
			  	  if (status < 0)
					{ 
				  	  if(intlDocToMailConverter)
						  INTL_DestroyCharCodeConverter(intlDocToMailConverter);
				  	  goto FAIL;
					}
				}
		  	  if(intlDocToMailConverter)
					INTL_DestroyCharCodeConverter(intlDocToMailConverter);
			}
			
		  /* Shut down the encoder (and flush out any buffered data.)
		   */
		  if (ma->encoder_data)
			{
			  status = MimeEncoderDestroy(ma->encoder_data, FALSE);
			  ma->encoder_data = 0;
			  if (status < 0) goto FAIL;
			}

		  XP_FileClose (in_file);
		  in_file = 0;

		  /* At this point, we're done with the temp file for this part,
			 and can safely delete it. */
		  if (!state->pre_snarfed_attachments_p)
			XP_FileRemove (ma->file_name, xpFileToPost);
		  XP_FREE (ma->file_name);
		  ma->file_name = 0;

		  /* Write out multipart seperator */
		  if (multipart_p)
			{
			  buffer_tail = buffer;
			  PUSH_STRING (CRLF "--");
			  PUSH_STRING (separator);
			  if (i == state->attachment_count - 1)  /* final part */
				PUSH_STRING ("--");
			  PUSH_NEWLINE ();
			  status = mime_write_message_body (state, buffer,
												buffer_tail - buffer);
			  buffer_tail = buffer;
			  if (status < 0) goto FAIL;
			}
		}
	}


#ifdef HAVE_SECURITY /* added by jwz */
  /* Close down encryption stream */
  if (state && state->crypto_closure)
	{
	  status = mime_finish_crypto_encapsulation (state->crypto_closure, FALSE);
	  state->crypto_closure = 0;
	  if (status < 0) goto FAIL;
	}
#endif /* !HAVE_SECURITY -- added by jwz */

  if (state->msg_file)
	XP_FileClose (state->msg_file);
  state->msg_file = 0;

#if 0
  /* Find the size of the final, fully-assembled message. */
  {
	XP_StatStruct st;
	st.st_size = 0;
	XP_Stat (state->msg_file_name, &st, xpFileToPost);
	state->msg_size = st.st_size;
  }
#endif

  FE_Progress(state->context, XP_GetString(MK_MSG_ASSEMB_DONE_MSG));

  if (state->dont_deliver_p &&
	  state->message_delivery_done_callback)
	{
	  state->message_delivery_done_callback (state->context,
											 state->fe_data, 0,
											 XP_STRDUP (state->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 (state->msg_file_name);
	  state->msg_file_name = 0;
	  state->message_delivery_done_callback = 0;
	  mime_free_message_state (state);
	  state = 0;
	}
  else
	{
	  mime_deliver_message (state);
	}

 FAIL:
  if (headers) XP_FREE (headers);
  if (separator) XP_FREE (separator);
  if (in_file)
	XP_FileClose (in_file);

#ifdef HAVE_SECURITY /* added by jwz */
  /* Close down encryption stream */
  if (state && state->crypto_closure)
	{
	  mime_finish_crypto_encapsulation (state->crypto_closure, TRUE);
	  state->crypto_closure = 0;
	}
#endif /* !HAVE_SECURITY -- added by jwz */

  if (state && status < 0)
	{
	  state->status = status;
	  mime_fail (state, 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. */

  return status;
}


static int
mime_write_message_body (struct mime_delivery_state *state,
						 char *buf, int32 size)
{
#ifdef HAVE_SECURITY /* added by jwz */
  if (state->crypto_closure)
	{
	  return mime_crypto_write_block (state->crypto_closure, buf, size);
	}
  else
#endif /* !HAVE_SECURITY -- added by jwz */
	{
	  if (XP_FileWrite (buf, size, state->msg_file) < size)
		return MK_MIME_ERROR_WRITING_FILE;
	  else
		return 0;
	}
}


static int
mime_encoder_output_fn (const char *buf, int32 size, void *closure)
{
  struct mime_delivery_state *state = (struct mime_delivery_state *) 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 ();

#ifdef HAVE_SECURITY /* added by jwz */
  RNG_GenerateGlobalRandomBytes((void *) &salt, sizeof(salt));
#else
  salt = random();
#endif /* !HAVE_SECURITY -- added by jwz */

  if (from && (host = XP_STRCHR (from, '@')))
	{
	  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 (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* message_id,
					   const char *other_random_headers,
					   int csid)
{
  int size = 0;
  char *buffer = 0, *buffer_tail = 0;

  /* Multiply by 3 here to make enough room for MimePartII conversion */
  if (from)                 size += 3 * XP_STRLEN (from);
  if (reply_to)             size += 3 * XP_STRLEN (reply_to);
  if (to)                   size += 3 * XP_STRLEN (to);
  if (cc)                   size += 3 * XP_STRLEN (cc);
  if (newsgroups)           size += 3 * XP_STRLEN (newsgroups);
  if (followup_to)          size += 3 * XP_STRLEN (followup_to);
  if (subject)              size += 3 * XP_STRLEN (subject);
  if (references)           size += 3 * XP_STRLEN (references);
  if (organization)         size += 3 * XP_STRLEN (organization);
  if (other_random_headers) size += 3 * XP_STRLEN (other_random_headers);
#ifdef GENERATE_MESSAGE_ID
  if (message_id)           size += XP_STRLEN (message_id);
#endif /* GENERATE_MESSAGE_ID */

  /* Add a bunch of slop for the static parts of the headers. */
  size += 2048;

  buffer = (char *) XP_ALLOC (size);
  if (!buffer)
	return 0; /* MK_OUT_OF_MEMORY */

  buffer_tail = buffer;

#ifdef GENERATE_MESSAGE_ID
  if (message_id)
	{
	  PUSH_STRING ("Message-ID: ");
	  PUSH_STRING (message_id);
	  PUSH_NEWLINE ();
	}
#endif /* GENERATE_MESSAGE_ID */

  {
	int gmtoffset = XP_LocalZoneOffset();
    PRTime now;
    PR_ExplodeTime( &now, PR_Now() );
    
  /* 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 );
	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 (from)
	{
	  char *convbuf;
  	  PUSH_STRING ("From: ");
	  convbuf = IntlEncodeMimePartIIStr((char *)from, csid,
										mime_use_quoted_printable_p);
	  if (convbuf)     /* MIME-PartII conversion */
	    {
	  	  PUSH_STRING (convbuf);
	  	  XP_FREE(convbuf);
	    }
	  else
	    PUSH_STRING (from);
      PUSH_NEWLINE ();
	}

  if (reply_to)
	{
	  char *convbuf;
	  PUSH_STRING ("Reply-To: ");
	  convbuf = IntlEncodeMimePartIIStr((char *)reply_to, csid,
										mime_use_quoted_printable_p);
	  if (convbuf)     /* MIME-PartII conversion */
	    {
	  	  PUSH_STRING (convbuf);
	  	  XP_FREE(convbuf);
	    }
	  else
	    PUSH_STRING (reply_to);
	  PUSH_NEWLINE ();
	}

  if (organization)
	{
	  char *convbuf;
	  PUSH_STRING ("Organization: ");
	  convbuf = IntlEncodeMimePartIIStr((char *)organization, csid,
										mime_use_quoted_printable_p);
	  if (convbuf)     /* MIME-PartII conversion */
	    {
	  	  PUSH_STRING (convbuf);
	  	  XP_FREE(convbuf);
	    }
	  else
	    PUSH_STRING (organization);
	  PUSH_NEWLINE ();
	}

  PUSH_STRING ("X-Mailer: ");
  PUSH_STRING (XP_AppCodeName);
  PUSH_STRING (" ");
  PUSH_STRING (XP_AppVersion);
  PUSH_NEWLINE ();

  PUSH_STRING ("MIME-Version: 1.0" CRLF);

  if (newsgroups)
	{
      /* turn whitespace into a comma list
       */
	  char *ptr, *ptr2;
	  char *n2;
	  char *convbuf;
	  
	  convbuf = IntlEncodeMimePartIIStr((char *)newsgroups, csid,
										mime_use_quoted_printable_p);
	  if (convbuf)
	  	n2 = XP_StripLine (convbuf);
	  else {
		ptr = XP_STRDUP(newsgroups);
		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 (followup_to)
	{
      /* turn whitespace into a comma list
       */
	  char *ptr, *ptr2;
	  char *n2;
	  char *convbuf;
	  
	  convbuf = IntlEncodeMimePartIIStr((char *)followup_to, csid,
										mime_use_quoted_printable_p);
	  if (convbuf)
	  	n2 = XP_StripLine (convbuf);
	  else {
		ptr = XP_STRDUP(followup_to);
		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 (to)
	{
	  char *convbuf;
	  PUSH_STRING ("To: ");
	  convbuf = IntlEncodeMimePartIIStr((char *)to, csid,
										mime_use_quoted_printable_p);
	  if (convbuf)     /* MIME-PartII conversion */
	    {
	  	  PUSH_STRING (convbuf);
	  	  XP_FREE(convbuf);
	    }
	  else
	    PUSH_STRING (to);

	  PUSH_NEWLINE ();
	}
  if (cc)
	{
	  char *convbuf;
	  PUSH_STRING ("CC: ");
	  convbuf = IntlEncodeMimePartIIStr((char *)cc, csid,
										mime_use_quoted_printable_p);
	  if (convbuf)   /* MIME-PartII conversion */
	    {
	  	  PUSH_STRING (convbuf);
	  	  XP_FREE(convbuf);
	    }
	  else
	    PUSH_STRING (cc);
	  PUSH_NEWLINE ();
	}
  if (subject)
	{
	  char *convbuf;
	  PUSH_STRING ("Subject: ");
	  convbuf = IntlEncodeMimePartIIStr((char *)subject, csid,
										mime_use_quoted_printable_p);
	  if (convbuf)  /* MIME-PartII conversion */
	    {
	  	  PUSH_STRING (convbuf);
	  	  XP_FREE(convbuf);
	    }
	  else
	    PUSH_STRING (subject);
	  PUSH_NEWLINE ();
	}
  if (references)
	{
	  PUSH_STRING ("References: ");
	  PUSH_STRING (references);
	  PUSH_NEWLINE ();
	}

  if (other_random_headers)
	{
	  /* Assume they already have the right newlines and continuations
		 and so on. */
	  PUSH_STRING (other_random_headers);
	}

  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, encrypt_p = FALSE;
  char *rest;
  int status = 0;
  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_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 = -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);
				}
			}
		  else if (!strcasecomp (token, "encrypt") ||
				   !strcasecomp (token, "encrypted"))
			{
			  encrypt_p = (!strcasecomp(value, "true") ||
						   !strcasecomp(value, "yes"));
			}
		  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 `encrypt', `sign', and `body' parameters are currently
	 ignored in mailto form submissions. */

  *new_post_url_return = 0;
  *headers_return = mime_generate_headers (from, 0, to, cc, 0, 0, 0, 0, 0, 0,
										   FE_UsersOrganization(), 0,
										   extra_headers, 0);
  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);
  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,
								  int16 mail_csid)
{
  char *buffer = (char *) XP_ALLOC (1024);
  char *buffer_tail = buffer;

  if (! buffer)
	return 0; /* MK_OUT_OF_MEMORY */

  XP_ASSERT (encoding);

  if (digest_p && type &&
	  (!strcasecomp (type, MESSAGE_RFC822) ||
	   !strcasecomp (type, MESSAGE_NEWS)))
	{
	  /* If we're in a multipart/digest, and this document is of type
		 message/rfc822, then it's appropriate to emit no headers.
	   */
	  PUSH_NEWLINE ();
	}
  else
	{
	  PUSH_STRING ("Content-Type: ");
	  PUSH_STRING (type);

	  if (mime_type_needs_charset (type))
		{
		  char charset[30];

		  /* 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");

		  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 ("\"");
		}

#ifdef EMIT_NAME_IN_CONTENT_TYPE
	  if (real_name && *real_name)
		{
		  char *s;
		  PUSH_STRING ("; name=\"");
		  s = buffer_tail;
		  PUSH_STRING (real_name);
		  while (*s)
			{
			  /* This isn't quite right, but who cares.  If the file name
				 contains characters which would break the string quoting,
				 map them to SPC. */
			  if (*s == '\\' || *s == '"' || *s == CR || *s == LF)
				*s = ' ';
			  s++;
			}
		  PUSH_STRING ("\"");
		}
#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 *s;
		  PUSH_STRING ("Content-Disposition: ");

		  /* If this document is an anonymous binary file, then always show it
			 as an attachment, never inline. */
		  if (!strcasecomp(type, APPLICATION_OCTET_STREAM))
			PUSH_STRING ("attachment");
		  else
			PUSH_STRING ("inline");

		  PUSH_STRING ("; filename=\"");
		  s = buffer_tail;
		  PUSH_STRING (real_name);
		  while (*s)
			{
			  /* This isn't quite right, but who cares.  If the file name
				 contains characters which would break the string quoting,
				 map them to SPC. */
			  if (*s == '\\' || *s == '"' || *s == CR || *s == LF)
				*s = ' ';
			  s++;
			}
		  PUSH_STRING ("\"" CRLF);
		}
	  else if (type &&
			   (!strcasecomp (type, MESSAGE_RFC822) ||
				!strcasecomp (type, MESSAGE_NEWS)))
		{
		  XP_ASSERT (!digest_p);
		  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, ':');

		  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, "mailbox:", 8))
			goto GIVE_UP_ON_CONTENT_BASE;

		  PUSH_STRING ("Content-Base: \"");
		  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);

		GIVE_UP_ON_CONTENT_BASE:
		  ;
		}
#endif /* GENERATE_CONTENT_BASE */


	  PUSH_NEWLINE ();	/* blank line for end of headers */
	}

  /* realloc it smaller... */
  buffer = (char*) XP_REALLOC (buffer, buffer_tail - buffer + 1);

  return buffer;
}


static void
mime_fail (struct mime_delivery_state *state, int failure_code,
		   char *error_msg)
{
  if ( ! state ) return;

  if (state->message_delivery_done_callback)
	{
	  if (failure_code < 0 && !error_msg)
		error_msg = NET_ExplainErrorDetails(failure_code, 0, 0, 0, 0);
	  state->message_delivery_done_callback (state->context, state->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 (state->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 */
	  state->attachments_done_callback (state->context,
										state->fe_data, failure_code,
										error_msg, 0);
											 
	  FREEIF(error_msg);		/* ### Is there a memory leak here?  Shouldn't
								   this free be outside the if? */   
   }
 
  state->message_delivery_done_callback = 0;
  state->attachments_done_callback = 0;
  
  mime_free_message_state (state);
}


/* Rip apart the URL and extract a reasonable value for the `real_name' slot.
 */
static void
msg_pick_real_name (struct mime_attachment *attachment)
{
  const char *s, *s2;
  char *s3;
  char *url;

  if (attachment->real_name)
	return;

  url = attachment->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. */
  attachment->real_name = MimeGuessURLContentName(attachment->state->context,
												  url);
  if (attachment->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, "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 (s2) s = s2+1;
  /* Copy it into the attachment struct. */
  StrAllocCopy (attachment->real_name, s);
  /* Now trim off any named anchors or search data. */
  s3 = XP_STRCHR (attachment->real_name, '?');
  if (s3) *s3 = 0;
  s3 = XP_STRCHR (attachment->real_name, '#');
  if (s3) *s3 = 0;

  /* Now lose the %XX crap. */
  NET_UnEscape (attachment->real_name);


  /* Now a special case for attaching uuencoded files...

	 If we attach a file "foo.txt.uu", we will send it out with
	 Content-Type: text/plain; Content-Transfer-Encoding: x-uuencode.
	 When saving such a file, a mail reader will generally decode it first
	 (thus removing the uuencoding.)  So, let's make life a little easier by
	 removing the indication of uuencoding from the file name itself.  (This
	 will presumably make the file name in the Content-Disposition header be
	 the same as the file name in the "begin" line of the uuencoded data.)

	 However, since there are mailers out there (including earlier versions of
	 Mozilla) that will use "foo.txt.uu" as the file name, we still need to
	 cope with that; the code which copes with that is in the MIME parser, in
	 libmime/mimei.c.
   */
  if (attachment->already_encoded_p &&
	  attachment->encoding)
	{
	  char *result = attachment->real_name;
	  int32 L = XP_STRLEN(result);
	  const char **exts = 0;

	  /* #### TOTAL FUCKING KLUDGE.
		 I'd like to ask the mime.types file, "what extensions correspond
		 to obj->encoding (which happens to be "x-uuencode") but doing that
		 in a non-sphagetti way would require brain surgery.  So, since
		 currently uuencode is the only content-transfer-encoding which we
		 understand which traditionally has an extension, we just special-
		 case it here!  Icepicks in my forehead!

		 Note that it's special-cased in a similar way in libmime/mimei.c.
	   */
	  if (!strcasecomp(attachment->encoding, ENCODING_UUENCODE) ||
		  !strcasecomp(attachment->encoding, ENCODING_UUENCODE2) ||
		  !strcasecomp(attachment->encoding, ENCODING_UUENCODE3) ||
		  !strcasecomp(attachment->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++;
		}
	}

}


static int
msg_start_message_delivery_hack_attachments (struct mime_delivery_state *state,
					   const struct MSG_AttachmentData *attachments,
					   const struct MSG_AttachedFile *preloaded_attachments)
{
  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;

	  state->pre_snarfed_attachments_p = TRUE;

	  state->attachment_count = 0;
	  while (preloaded_attachments[state->attachment_count].orig_url)
		state->attachment_count++;
	  state->attachments = (struct mime_attachment *)
		XP_ALLOC (sizeof (struct mime_attachment) * state->attachment_count);
	  if (! state->attachments)
		return MK_OUT_OF_MEMORY;
	  XP_MEMSET (state->attachments, 0,
				 sizeof (struct mime_attachment) * state->attachment_count);

	  for (i = 0; i < state->attachment_count; i++)
		{
		  state->attachments[i].state = state;
		  /* These attachments are already "snarfed". */
		  state->attachments[i].done = TRUE;
		  XP_ASSERT (preloaded_attachments[i].orig_url);
		  StrAllocCopy (state->attachments[i].url_string,
						preloaded_attachments[i].orig_url);
		  StrAllocCopy (state->attachments[i].type,
						preloaded_attachments[i].type);
		  StrAllocCopy (state->attachments[i].description,
						preloaded_attachments[i].description);
		  StrAllocCopy (state->attachments[i].x_mac_type,
						preloaded_attachments[i].x_mac_type);
		  StrAllocCopy (state->attachments[i].x_mac_creator,
						preloaded_attachments[i].x_mac_creator);
		  StrAllocCopy (state->attachments[i].encoding,
						preloaded_attachments[i].encoding);
		  StrAllocCopy (state->attachments[i].file_name,
						preloaded_attachments[i].file_name);

		  state->attachments[i].decrypted_p =
			preloaded_attachments[i].decrypted_p;

		  state->attachments[i].size = preloaded_attachments[i].size;
		  state->attachments[i].unprintable_count =
			preloaded_attachments[i].unprintable_count;
		  state->attachments[i].highbit_count =
			preloaded_attachments[i].highbit_count;
		  state->attachments[i].ctl_count = preloaded_attachments[i].ctl_count;
		  state->attachments[i].null_count =
			preloaded_attachments[i].null_count;
		  state->attachments[i].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 (state->attachments[i].encoding &&
			  strcasecomp (state->attachments[i].encoding, ENCODING_7BIT) &&
			  strcasecomp (state->attachments[i].encoding, ENCODING_8BIT) &&
			  strcasecomp (state->attachments[i].encoding, ENCODING_BINARY))
			state->attachments[i].already_encoded_p = TRUE;

		  msg_pick_real_name(&state->attachments[i]);
		}
	}
  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;

	  state->attachment_count = 0;
	  while (attachments[state->attachment_count].url)
		state->attachment_count++;
	  state->attachments = (struct mime_attachment *)
		XP_ALLOC (sizeof (struct mime_attachment) * state->attachment_count);
	  if (! state->attachments)
		return MK_OUT_OF_MEMORY;
	  XP_MEMSET (state->attachments, 0,
				 sizeof (struct mime_attachment) * state->attachment_count);

	  for (i = 0; i < state->attachment_count; i++)
		{
		  state->attachments[i].state = state;
		  XP_ASSERT (attachments[i].url);
		  StrAllocCopy (state->attachments[i].url_string,
						attachments[i].url);
		  StrAllocCopy (state->attachments[i].override_type,
						attachments[i].real_type);
		  StrAllocCopy (state->attachments[i].override_encoding,
						attachments[i].real_encoding);
		  StrAllocCopy (state->attachments[i].desired_type,
						attachments[i].desired_type);
		  StrAllocCopy (state->attachments[i].description,
						attachments[i].description);
		  StrAllocCopy (state->attachments[i].x_mac_type,
						attachments[i].x_mac_type);
		  StrAllocCopy (state->attachments[i].x_mac_creator,
						attachments[i].x_mac_creator);
		  StrAllocCopy (state->attachments[i].encoding, "7bit");
		  state->attachments[i].url =
			NET_CreateURLStruct (state->attachments[i].url_string,
								 NET_DONT_RELOAD);

		  state->attachments[i].real_name = 0;

		  /* Count up attachments which are going to come from mail folders
			 and from NNTP servers. */
		  if (strncasecomp(state->attachments[i].url_string, "mailbox:",8))
			mailbox_count++;
		  else if (strncasecomp(state->attachments[i].url_string, "news:",5) ||
				   strncasecomp(state->attachments[i].url_string, "snews:",6))
			news_count++;

		  msg_pick_real_name(&state->attachments[i]);
		}

	  /* 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)
		state->be_synchronous_p = TRUE;

	  state->attachment_pending_count = state->attachment_count;

	  /* Start the URL attachments loading (eventually, an exit routine will
		 call the done_callback). */

	  if (state->attachment_count == 1)
		FE_Progress(state->context, XP_GetString(MK_MSG_LOAD_ATTACHMNT));
	  else
		FE_Progress(state->context, XP_GetString(MK_MSG_LOAD_ATTACHMNTS));

	  for (i = 0; i < state->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 = mime_snarf_attachment (&state->attachments [i]);
		  if (status < 0)
			return status;

		  if (state->be_synchronous_p)
			break;
		}
	}

  if (state->attachment_pending_count <= 0)
	/* No attachments - finish now (this will call the done_callback). */
	mime_gather_attachments (state);

  return 0;
}


void
msg_start_message_delivery(MWContext *context,
						  void      *fe_data,

						  const char *from,
						  const char *reply_to,
						  const char *to,
						  const char *cc,
						  const char *bcc,
						  const char *fcc,
						  const char *newsgroups,
						  const char *followup_to,
						  const char *organization,
						  const char *message_id,
						  const char *news_url,
						  const char *subject,
						  const char *references,
						  const char *other_random_headers,

						  XP_Bool digest_p,
						  XP_Bool dont_deliver_p,
						  XP_Bool queue_for_later_p,

						  XP_Bool encrypt_p,
						  XP_Bool sign_p,

						  const char *attachment1_type,
						  const char *attachment1_body,
						  uint32 attachment1_body_length,
						  const struct MSG_AttachmentData *attachments,
						  const struct MSG_AttachedFile *preloaded_attachments,
						  void (*message_delivery_done_callback)
						       (MWContext *context, 
								void *fe_data,
								int status,
								const char *error_message))
{
  struct mime_delivery_state *state = 0;
  int failure = 0;

  if (!attachment1_body || !*attachment1_body)
	attachment1_type = attachment1_body = 0;

  state = (struct mime_delivery_state *)
	XP_ALLOC (sizeof (struct mime_delivery_state));

  if (! state)
	{
	  failure = MK_OUT_OF_MEMORY;
	  goto FAIL;
	}

  XP_MEMSET (state, 0, sizeof(struct mime_delivery_state));

  state->context = context;
  state->fe_data = fe_data;
  state->message_delivery_done_callback = message_delivery_done_callback;

#ifdef GENERATE_MESSAGE_ID
  if (message_id) {
	state->message_id = XP_STRDUP(message_id);
	/* 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

  /* Strip whitespace from beginning and end of body. */
  if (attachment1_body)
	{
	  const char *old = attachment1_body;
	  while (attachment1_body_length > 0 &&
			 XP_IS_SPACE (*attachment1_body))
		{
		  attachment1_body++;
		  attachment1_body_length--;
		}
	  /* but allow leading whitespace on the first line...  just strip out
		 leading blank lines and lines consisting only of whitespace. */
	  while (attachment1_body > old &&
			 (attachment1_body[-1] == ' ' ||
			  attachment1_body[-1] == '\t'))
		{
		  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)
			{
			  failure = MK_OUT_OF_MEMORY;
			  goto FAIL;
			}
		  XP_MEMCPY (newb, attachment1_body, attachment1_body_length);
		  newb [attachment1_body_length] = 0;
		  state->attachment1_body = newb;
		  state->attachment1_body_length = attachment1_body_length;
		}
	}

  if (!news_url || !*news_url)
	news_url = "news:";
  
  StrAllocCopy (state->news_url, news_url);
  StrAllocCopy (state->attachment1_type, attachment1_type);
  StrAllocCopy (state->attachment1_encoding, "8bit");

  /* strip whitespace from and duplicate header fields. */
  state->from         = mime_fix_addr_header (from);
  state->reply_to     = mime_fix_addr_header (reply_to);
  state->to           = mime_fix_addr_header (to);
  state->cc           = mime_fix_addr_header (cc);
  state->fcc          = mime_fix_header (fcc);
  state->bcc          = mime_fix_addr_header (bcc);
  state->newsgroups   = mime_fix_news_header (newsgroups);
  state->followup_to  = mime_fix_news_header (followup_to);
  state->organization = mime_fix_header (organization);
  state->subject      = mime_fix_header (subject);
  state->references   = mime_fix_header (references);

  if (other_random_headers)
	state->other_random_headers = XP_STRDUP (other_random_headers);

  /* Check the fields for legitimacy, and run the callback if they're not
	 ok.  */
  failure = mime_sanity_check_fields (state->from, state->reply_to,
									  state->to, state->cc,
									  state->bcc, state->fcc,
									  state->newsgroups, state->followup_to,
									  state->subject, state->references,
									  state->organization,
									  state->other_random_headers);
  if (failure) goto FAIL;

  state->digest_p = digest_p;
  state->dont_deliver_p = dont_deliver_p;
  state->queue_for_later_p = queue_for_later_p;
  state->encrypt_p = encrypt_p;
  state->sign_p = sign_p;
  state->msg_file_name = WH_TempName (xpFileToPost, "nsmail");

  failure = msg_start_message_delivery_hack_attachments(state,
														attachments,
														preloaded_attachments);
  if (failure >= 0)
	return;

 FAIL:
  XP_ASSERT (failure);

  {
	char *err_msg = NET_ExplainErrorDetails (failure);
	message_delivery_done_callback (context, fe_data, failure, err_msg);
	if (err_msg) XP_FREE (err_msg);
	if (state) {
	  mime_free_message_state (state);
	  XP_FREE (state);
	}
  }
}



/* 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...)
 */
void
MSG_StartMessageDelivery (MWContext *context,
						  void      *fe_data,

						  const char *from,
						  const char *reply_to,
						  const char *to,
						  const char *cc,
						  const char *bcc,
						  const char *fcc,
						  const char *newsgroups,
						  const char *followup_to,
						  const char *organization,
						  const char *message_id,
						  const char *news_url,
						  const char *subject,
						  const char *references,
						  const char *other_random_headers,

						  XP_Bool digest_p,
						  XP_Bool dont_deliver_p,
						  XP_Bool queue_for_later_p,

						  XP_Bool encrypt_p,
						  XP_Bool sign_p,

						  const char *attachment1_type,
						  const char *attachment1_body,
						  uint32 attachment1_body_length,
						  const struct MSG_AttachmentData *attachments,
						  void (*message_delivery_done_callback)
						       (MWContext *context, 
								void *fe_data,
								int status,
								const char *error_message))
{
  msg_start_message_delivery (context, fe_data, from, reply_to, to, cc, bcc,
							  fcc, newsgroups, followup_to, organization,
							  message_id, news_url, subject, references,
							  other_random_headers,
							  digest_p, dont_deliver_p, queue_for_later_p,
							  encrypt_p, sign_p,
							  attachment1_type, attachment1_body,
							  attachment1_body_length,
							  attachments, 0,
							  message_delivery_done_callback);
}


void
msg_StartMessageDeliveryWithAttachments (MWContext *context,
						  void      *fe_data,

						  const char *from,
						  const char *reply_to,
						  const char *to,
						  const char *cc,
						  const char *bcc,
						  const char *fcc,
						  const char *newsgroups,
						  const char *followup_to,
						  const char *organization,
						  const char *message_id,
						  const char *news_url,
						  const char *subject,
						  const char *references,
						  const char *other_random_headers,

						  XP_Bool digest_p,
						  XP_Bool dont_deliver_p,
						  XP_Bool queue_for_later_p,

						  XP_Bool encrypt_p,
						  XP_Bool sign_p,

						  const char *attachment1_type,
						  const char *attachment1_body,
						  uint32 attachment1_body_length,
						  const struct MSG_AttachedFile *attachments,
						  void (*message_delivery_done_callback)
						       (MWContext *context, 
								void *fe_data,
								int status,
								const char *error_message))
{
  msg_start_message_delivery (context, fe_data, from, reply_to, to, cc, bcc,
							  fcc, newsgroups, followup_to, organization,
							  message_id, news_url, subject, references,
							  other_random_headers,
							  digest_p, dont_deliver_p, queue_for_later_p,
							  encrypt_p, sign_p,
							  attachment1_type, attachment1_body,
							  attachment1_body_length,
							  0, attachments,
							  message_delivery_done_callback);
}


void
msg_DownloadAttachments (MWContext *context,
						 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))
{
  struct mime_delivery_state *state = 0;
  int failure = 0;

  XP_ASSERT(attachments && attachments[0].url);
  if (!attachments || !attachments[0].url)
	{
	  failure = -1;
	  goto FAIL;
	}
  state = (struct mime_delivery_state *)
	XP_ALLOC (sizeof (struct mime_delivery_state));

  if (! state)
	{
	  failure = MK_OUT_OF_MEMORY;
	  goto FAIL;
	}
  XP_MEMSET (state, 0, sizeof(struct mime_delivery_state));

  state->context = context;
  state->fe_data = fe_data;
  state->attachments_only_p = TRUE;
  state->attachments_done_callback = attachments_done_callback;

  failure = msg_start_message_delivery_hack_attachments(state, attachments, 0);
  if (failure >= 0)
	return;

 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 (context, fe_data, failure, err_msg, 0);
		if (state) {
		  mime_free_message_state (state);
		  XP_FREE (state);
		}
		
		if (err_msg) XP_FREE (err_msg);
	}
		
	
	
  }
}



void
MIME_BuildRFC822File (MWContext *context,
					  void      *fe_data,

					  const char *from,
					  const char *reply_to,
					  const char *to,
					  const char *cc,
					  const char *bcc,
					  const char *fcc,
					  const char *newsgroups,
					  const char *followup_to,
					  const char *organization,
					  const char *news_url,
					  const char *subject,
					  const char *references,
					  const char *other_random_headers,

					  const char *attachment1_type,
					  const char *attachment1_body,
					  uint32 attachment1_body_length,
					  const struct MSG_AttachmentData *attachments,
					  void (*message_file_written_callback)
					  (MWContext *context, 
					   void *fe_data,
					   int status,
					   const char *error_message))
{
  msg_start_message_delivery (context, fe_data,
							  from, reply_to, to, cc, bcc, fcc,
							  newsgroups, followup_to, organization,
							  0, /* message_id */
							  news_url, subject, references,
							  other_random_headers,
							  FALSE, /* digest_p */
							  TRUE,  /* dont_deliver_p */
							  FALSE, /* queue_for_later_p */
							  FALSE, /* encrypt_p */
							  FALSE, /* sign_p */
							  attachment1_type, attachment1_body,
							  attachment1_body_length,
							  attachments,
							  0, /* preloaded_attachments */
							  message_file_written_callback);
}


static void
mime_free_message_state (struct mime_delivery_state *state)
{
  if (state->to) 					XP_FREE (state->to);
  if (state->newsgroups)			XP_FREE (state->newsgroups);
  if (state->news_url)	 			XP_FREE (state->news_url);
  if (state->from)	 				XP_FREE (state->from);
  if (state->organization) 			XP_FREE (state->organization);
  if (state->message_id)			XP_FREE (state->message_id);
  if (state->subject) 				XP_FREE (state->subject);
  if (state->references) 			XP_FREE (state->references);
  if (state->attachment1_type)	 	XP_FREE (state->attachment1_type);
  if (state->attachment1_encoding)	XP_FREE (state->attachment1_encoding);
  if (state->attachment1_body)		XP_FREE (state->attachment1_body);

  if (state->attachment1_encoder_data)
	{
	  MimeEncoderDestroy(state->attachment1_encoder_data, TRUE);
	  state->attachment1_encoder_data = 0;
	}

/*  if (state->headers)				XP_FREE (state->headers); */

  if (state->msg_file)
	{
	  XP_FileClose (state->msg_file);
	  state->msg_file = 0;
	  XP_ASSERT (state->msg_file_name);
	}
  if (state->msg_file_name)
	{
	  XP_FileRemove (state->msg_file_name, xpFileToPost);
	  XP_FREE (state->msg_file_name);
	  state->msg_file_name = 0;
	}

  if (state->attachments)
	{
	  int i;
	  for (i = 0; i < state->attachment_count; i++)
		{
		  if (state->attachments [i].encoder_data)
			{
			  MimeEncoderDestroy(state->attachments [i].encoder_data,
								 TRUE);
			  state->attachments [i].encoder_data = 0;
			}

		  FREEIF (state->attachments [i].url_string);
		  if (state->attachments [i].url)
			NET_FreeURLStruct (state->attachments [i].url);
		  FREEIF (state->attachments [i].type);
		  FREEIF (state->attachments [i].override_type);
		  FREEIF (state->attachments [i].override_encoding);
		  FREEIF (state->attachments [i].desired_type);
		  FREEIF (state->attachments [i].description);
		  FREEIF (state->attachments [i].x_mac_type);
		  FREEIF (state->attachments [i].x_mac_creator);
		  FREEIF (state->attachments [i].real_name);
		  FREEIF (state->attachments [i].encoding);
		  if (state->attachments [i].file)
			XP_FileClose (state->attachments [i].file);
		  if (state->attachments [i].file_name)
			{
			  if (!state->pre_snarfed_attachments_p)
				XP_FileRemove (state->attachments [i].file_name, xpFileToPost);
			  XP_FREE (state->attachments [i].file_name);
			}
#ifdef XP_MAC
			/* remove the appledoubled intermediate file after we done all.
			 */
		  if (state->attachments [i].ap_filename)
			{
			  XP_FileRemove (state->attachments [i].ap_filename, xpFileToPost);
			  XP_FREE (state->attachments [i].ap_filename);
			}
#endif /* XP_MAC */
		}
	  XP_MEMSET (state->attachments, 0,
				 sizeof (*state->attachments) * state->attachment_count);
	  XP_FREE (state->attachments);
	}

  XP_MEMSET (state, 0, sizeof (*state));
}


static void mime_deliver_file_as_news (struct mime_delivery_state *state);
static void mime_deliver_file_as_mail (struct mime_delivery_state *state);
static XP_Bool mime_do_fcc (struct mime_delivery_state *state);
static void mime_queue_for_later (struct mime_delivery_state *state);

static void
mime_deliver_message (struct mime_delivery_state *state)
{
  XP_Bool mail_p = (state->to || state->cc || state->bcc);
  XP_Bool news_p = !!state->newsgroups;

  XP_ASSERT(mail_p || news_p);

#if 0
  /* Figure out how many bytes we're actually going to be writing, total.
   */
  state->delivery_bytes = 0;
  state->delivery_total_bytes = 0;

  if (state->fcc && *state->fcc)
	state->delivery_total_bytes += state->msg_size;

  if (state->queue_for_later_p)
	state->delivery_total_bytes += state->msg_size;
  else
	{
	  if (mail_p)
		state->delivery_total_bytes += state->msg_size;
	  if (news_p)
		state->delivery_total_bytes += state->msg_size;
	}
#endif /* 0 */

  if (state->queue_for_later_p)
	{
	  mime_queue_for_later (state);
	  return;
	}

  if (state->fcc)
	if (!mime_do_fcc (state))
	  return;

#ifdef XP_UNIX
  {
	int status = msg_DeliverMessageExternally(state->context,
											  state->msg_file_name);
	if (status != 0)
	  {
		if (status < 0)
		  mime_fail (state, status, 0);
		else
		  {
			/* The message has now been delivered successfully. */
			MWContext *context = state->context;
			if (state->message_delivery_done_callback)
			  state->message_delivery_done_callback (context,
													 state->fe_data, 0, NULL);
			state->message_delivery_done_callback = 0;

			mime_free_message_state (state);

			/* 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 (state->attachment_count == 0)
			  MSG_MailCompositionAllConnectionsComplete (context);
		  }
		return;
	  }
  }
#endif /* XP_UNIX */


#ifdef MAIL_BEFORE_NEWS
  if (mail_p)
	mime_deliver_file_as_mail (state);   /* May call ...as_news() next. */
  else if (news_p)
	mime_deliver_file_as_news (state);
#else  /* !MAIL_BEFORE_NEWS */
  if (news_p)
	mime_deliver_file_as_news (state);   /* May call ...as_mail() next. */
  else if (mail_p)
	mime_deliver_file_as_mail (state);
#endif /* !MAIL_BEFORE_NEWS */
  else
	abort ();
}


#if 0
static void
mime_delivery_thermo (struct mime_delivery_state *state, int32 increment)
{
  int32 percent;
  state->delivery_bytes += increment;
  XP_ASSERT(state->delivery_total_bytes > 0);
  if (state->delivery_total_bytes <= 0) return;
  percent = 100 * (((double) state->delivery_bytes) /
				   ((double) state->delivery_total_bytes));
  FE_SetProgressBarPercent (state->context, percent);
#if 0
  FE_GraphProgress (state->context, 0,
					state->delivery_bytes, 0,
					state->delivery_total_bytes);
#endif
}
#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 *);

static void
mime_deliver_file_as_mail (struct mime_delivery_state *state)
{
  char *buf, *buf2;
  URL_Struct *url;
  char *fn = XP_STRDUP(state->msg_file_name);

  if (!fn)
	{
	  mime_fail (state, MK_OUT_OF_MEMORY, 0);
	  XP_FREE (state);
	  return;
	}

  FE_Progress (state->context, XP_GetString(MK_MSG_DELIV_MAIL));

  buf = (char *) XP_ALLOC ((state->to  ? XP_STRLEN (state->to)  + 10 : 0) +
						   (state->cc  ? XP_STRLEN (state->cc)  + 10 : 0) +
						   (state->bcc ? XP_STRLEN (state->bcc) + 10 : 0) +
						   10);
  if (! buf)
	{
	  XP_FREE(fn);
	  mime_fail (state, MK_OUT_OF_MEMORY, 0);
	  XP_FREE (state);
	  return;
	}
  XP_STRCPY (buf, "mailto:");
  buf2 = buf + XP_STRLEN (buf);
  if (state->to)
	{
	  XP_STRCAT (buf2, state->to);
	}
  if (state->cc)
	{
	  if (*buf2) XP_STRCAT (buf2, ",");
	  XP_STRCAT (buf2, state->cc);
	}
  if (state->bcc)
	{
	  if (*buf2) XP_STRCAT (buf2, ",");
	  XP_STRCAT (buf2, state->bcc);
	}

  url = NET_CreateURLStruct (buf, NET_DONT_RELOAD);
  XP_FREE (buf);
  if (! url)
	{
	  XP_FREE(fn);
	  mime_fail (state, MK_OUT_OF_MEMORY, 0);
	  XP_FREE (state);
	  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 = fn;
  url->post_data_size = XP_STRLEN(fn);
  url->post_data_is_file = TRUE;
  url->method = URL_POST_METHOD;
  url->fe_data = state;
  url->internal_url = TRUE;

  /* We can ignore the return value of NET_GetURL() because we have
	 handled the error in mime_deliver_as_mail_exit(). */
  NET_GetURL (url, FO_CACHE_AND_PRESENT, state->context,
			  mime_deliver_as_mail_exit);
}

static void
mime_deliver_file_as_news (struct mime_delivery_state *state)
{
  URL_Struct *url = NET_CreateURLStruct (state->news_url, NET_DONT_RELOAD);
  char *fn = XP_STRDUP(state->msg_file_name);

  if (!fn)
	{
	  mime_fail (state, MK_OUT_OF_MEMORY, 0);
	  XP_FREE (state);
	  return;
	}

  if (! url)
	{
	  XP_FREE(fn);
	  mime_fail (state, MK_OUT_OF_MEMORY, 0);
	  XP_FREE (state);
	  return;
	}

  FE_Progress (state->context, 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 = fn;
  url->post_data_size = XP_STRLEN(fn);
  url->post_data_is_file = TRUE;
  url->method = URL_POST_METHOD;

  url->fe_data = state;
  url->internal_url = TRUE;

  /* We can ignore the return value of NET_GetURL() because we have
	 handled the error in mime_deliver_as_news_exit(). */
  NET_GetURL (url, FO_CACHE_AND_PRESENT, state->context,
			  mime_deliver_as_news_exit);
}

static void 
mime_deliver_as_mail_exit (URL_Struct *url, int status, MWContext *context)
{
  struct mime_delivery_state *state =
	(struct mime_delivery_state *) url->fe_data;
  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)
	{
	  mime_fail (state, status, error_msg);
	  XP_FREE (state);
	}
#ifdef MAIL_BEFORE_NEWS
  else if (state->newsgroups)
	{
	  /* If we're sending this mail message to news as well, start it now.
		 Completion and further errors will be handled there.
	   */
	  mime_deliver_file_as_news (state);
	}
#endif /* MAIL_BEFORE_NEWS */
  else
	{
	  /* The message has now been sent successfully! */
	  FE_Progress (state->context, XP_GetString(MK_MSG_DELIV_MAIL_DONE));
	  if (state->message_delivery_done_callback)
		state->message_delivery_done_callback (state->context,
											   state->fe_data, 0, NULL);
	  state->message_delivery_done_callback = 0;
	  mime_free_message_state (state);
	  XP_FREE (state);
	}
}

static void 
mime_deliver_as_news_exit (URL_Struct *url, int status, MWContext *context)
{
  struct mime_delivery_state *state =
	(struct mime_delivery_state *) url->fe_data;
  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)
	{
	  mime_fail (state, status, error_msg);
	  XP_FREE (state);
	}
#ifndef MAIL_BEFORE_NEWS
  else if (state->to || state->cc || state->bcc)
	{
	  /* If we're sending this news message to mail as well, start it now.
		 Completion and further errors will be handled there.
	   */
	  mime_deliver_file_as_mail (state);
	}
#endif /* !MAIL_BEFORE_NEWS */
  else
	{
	  /* The message has now been sent successfully! */
	  FE_Progress (state->context, XP_GetString(MK_MSG_DELIV_NEWS_DONE));
	  if (state->message_delivery_done_callback)
		state->message_delivery_done_callback (state->context,
											   state->fe_data, 0, NULL);
	  state->message_delivery_done_callback = 0;
	  mime_free_message_state (state);
	  XP_FREE (state);
	}
}


/* #### move this to a header file -- also used by newsgrp.c */
int32 msg_do_fcc_handle_line(char* line, uint32 length, void* closure);

static int
mime_do_fcc_1 (MWContext *context,
			   const char *input_file_name,  XP_FileType input_file_type,
			   const char *output_file_name, XP_FileType output_file_type,
			   XP_Bool queue_p,
			   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 = 4096;
  char *obuffer = 0;
  int32 obuffer_size = 0, obuffer_fp = 0;
  int32 n;
  XP_Bool summary_valid = FALSE;
  XP_Bool mark_as_read = TRUE;
/*  XP_Bool mark_as_read = FALSE; */

  if (queue_p)
	FE_Progress (context, XP_GetString(MK_MSG_QUEUEING));
  else
	FE_Progress (context, XP_GetString(MK_MSG_WRITING_TO_FCC));


  ibuffer = (char *) XP_ALLOC (ibuffer_size);
  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)
	{
	  summary_valid = msg_IsSummaryValid(output_file_name, &st);
	  if (!msg_ConfirmMailFile (context, output_file_name)) {
		return MK_INTERRUPTED; /* #### What a 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
	{
	  /* We're creating a new file.  If this is a mail folder, and we
		 already have a mail context, tell it to create the new file and
		 display it. */
	  MWContext* mailctx = XP_FindContextOfType(NULL, MWContextMail);
	  if (mailctx) {
		const char* dir = FE_GetFolderDirectory(mailctx);
		if (dir && XP_STRNCMP(output_file_name, dir, XP_STRLEN(dir)) == 0) {
		  (void) msg_FindMailFolder(mailctx, output_file_name, TRUE);
		  if (!XP_Stat (output_file_name, &st, output_file_type)) {
			summary_valid = msg_IsSummaryValid(output_file_name, &st);
		  }
		}
	  }
	}


  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 = -1; /* How did this happen? */
	  goto FAIL;
	}

  /* 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.
   */
  {
	const char *envelope = msg_GetDummyEnvelope();
	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;
	  }
	}
	if (XP_FileWrite (envelope, XP_STRLEN (envelope),
					  out) < XP_STRLEN(envelope)) {
	  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 (queue_p || mark_as_read)
	{
	  char buf[X_MOZILLA_STATUS_LEN + 10];
	  uint16 flags = 0;

	  mark_as_read = TRUE;
	  flags |= MSG_FLAG_READ;
	  if (queue_p)
		flags |= MSG_FLAG_QUEUED;
	  PR_snprintf(buf, sizeof(buf),
				  X_MOZILLA_STATUS ": %04x" LINEBREAK, flags);
	  if (XP_FileWrite (buf, XP_STRLEN(buf), out) < XP_STRLEN(buf)) {
 		status = MK_MIME_ERROR_WRITING_FILE;
		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 (fcc_header && *fcc_header && queue_p)
	{
	  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);
	  L = XP_STRLEN(buf);
	  if (XP_FileWrite (buf, L, out) < L)
		{
		  status = MK_MIME_ERROR_WRITING_FILE;
		  goto FAIL;
		}
	}

  if (bcc_header && *bcc_header
#ifndef SAVE_BCC_IN_FCC_FILE
	  && queue_p
#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);
	  L = XP_STRLEN(buf);
	  if (XP_FileWrite (buf, L, out) < L)
		{
		  status = MK_MIME_ERROR_WRITING_FILE;
		  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 (queue_p && 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" : "");
		  int L;
		  FREEIF(orig_hap);
		  if (!line)
			{
			  status = MK_OUT_OF_MEMORY;
			  goto FAIL;
			}
		  L = XP_STRLEN(line);
		  if (XP_FileWrite (line, L, out) < L)
			{
			  status = MK_MIME_ERROR_WRITING_FILE;
			  goto FAIL;
			}
		}
	  FREEIF(orig_hap);
	}


  /* Read from the message file, and write to the FCC or Queue file.
	 There are two tricky parts: the first is thta 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,
						  out);
	  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, out);

  /* Terminate with a final newline. */
  if (XP_FileWrite (LINEBREAK, LINEBREAK_LEN, out) < LINEBREAK_LEN) {
	status = MK_MIME_ERROR_WRITING_FILE;
  }

 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 (summary_valid)
			msg_SetSummaryValid(output_file_name, 1, (mark_as_read ? 0 : 1));
		}
	  else if (! file_existed_p)
		{
		  XP_FileClose (out);
		  XP_FileRemove (output_file_name, output_file_type);
		}
	  else
		{
		  /* #### truncate file to `st.st_size' */
		  XP_FileClose (out);
		}
	}

  if (status < 0)
	{
	  /* Fail, and terminate. */

	  if (status == -1)
		status = MK_MIME_ERROR_WRITING_FILE; /* #### wrong error */

	  return status;
	}
  else
	{
	  /* Otherwise, continue on to _deliver_as_mail or _deliver_as_news
		 or mime_queue_for_later. */
	  return 0;
	}
}

int32
msg_do_fcc_handle_line(char* line, uint32 length, void* closure)
{
  XP_File out = (XP_File) closure;

#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 (length >= 5 && line[0] == 'F' && !XP_STRNCMP(line, "From ", 5))
	{
	  if (XP_FileWrite (">", 1, out) < 1) return MK_MIME_ERROR_WRITING_FILE;
	}
#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;
  }
  return 0;
}


int
msg_DoFCC (MWContext *context,
		   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(context &&
			input_file && *input_file &&
			output_file && *output_file);
  if (! (context &&
		 input_file && *input_file &&
		 output_file && *output_file))
	return -1;
  return mime_do_fcc_1 (context,
						input_file, input_file_type,
						output_file, output_file_type,
						FALSE,
						bcc_header_value,
						fcc_header_value,
						0);
}


/* Returns false if an error happened. */
static XP_Bool
mime_do_fcc (struct mime_delivery_state *state)
{
  if (!state->fcc || !*state->fcc)
	return TRUE;
  else
	{
	  int status = msg_DoFCC (state->context,
							  state->msg_file_name, xpFileToPost,
							  state->fcc, xpMailFolder,
							  state->bcc,
							  state->fcc);
	  if (status < 0)
		mime_fail (state, status, 0);
	  return (status >= 0);
	}
}


/* Queues the message for later delivery, and runs the completion/failure
   callback.
 */
static void
mime_queue_for_later (struct mime_delivery_state *state)
{
  char *name = 0;
  int status = 0;
  name = msg_QueueFolderName(state->context);
  if (!name)
	{
	  status = MK_OUT_OF_MEMORY;
	  goto FAIL;
	}
  msg_EnsureQueueFolderListed(state->context);
  status = mime_do_fcc_1 (state->context,
						  state->msg_file_name, xpFileToPost,
						  name, xpMailFolder,
						  TRUE, state->bcc, state->fcc,
						  (state->newsgroups && *state->newsgroups
						   ? state->news_url : 0));
  XP_FREE (name);
  if (status < 0)
	goto FAIL;

FAIL:

  if (status < 0)
	{
	  mime_fail (state, status, 0);
	}
  else
	{
	  MWContext *context = state->context;
	  FE_Progress(context, XP_GetString(MK_MSG_QUEUED));

	  /* The message has now been queued successfully. */
	  if (state->message_delivery_done_callback)
		state->message_delivery_done_callback (context,
											   state->fe_data, 0, NULL);
	  state->message_delivery_done_callback = 0;

	  mime_free_message_state (state);

	  /* 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 (state->attachment_count == 0)
		MSG_MailCompositionAllConnectionsComplete (context);
	}
}


#ifdef _USRDLL

PUBLIC void
NET_RegisterDLLContentConverters()
{

  RealNET_RegisterContentTypeConverter ("*", FO_MAIL_TO,
										NULL, mime_make_attachment_stream);
  RealNET_RegisterContentTypeConverter ("*", FO_CACHE_AND_MAIL_TO,
										NULL, mime_make_attachment_stream);

  /* #### I don't understand this -- what is this function for?
	 Is this right?  I've cloned this stuff from MSG_RegisterConverters()
	 above.  --jwz */

  RealNET_RegisterContentTypeConverter ("*", FO_MAIL_MESSAGE_TO,
									NULL, mime_make_attachment_stream);
  RealNET_RegisterContentTypeConverter ("*", FO_CACHE_AND_MAIL_MESSAGE_TO,
									NULL, mime_make_attachment_stream);

  RealNET_RegisterContentTypeConverter (MESSAGE_RFC822, FO_MAIL_MESSAGE_TO,
									NULL, MIME_MessageConverter);
  RealNET_RegisterContentTypeConverter (MESSAGE_NEWS, FO_MAIL_MESSAGE_TO,
									NULL, MIME_MessageConverter);
  RealNET_RegisterContentTypeConverter (MESSAGE_RFC822,
									FO_CACHE_AND_MAIL_MESSAGE_TO,
									NULL, MIME_MessageConverter);
  RealNET_RegisterContentTypeConverter (MESSAGE_NEWS,
									FO_CACHE_AND_MAIL_MESSAGE_TO,
									NULL, MIME_MessageConverter);
}

#endif