/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/* mimeenc.c --- MIME encoders and decoders, version 2.
   Created: Jamie Zawinski <jwz@netscape.com>, 15-May-96.
 */


#include "mimeenc.h"
#include "mimei.h"

typedef enum mime_encoding {
  mime_Base64, mime_QuotedPrintable, mime_uuencode
} mime_encoding;

typedef enum mime_uue_state {
  UUE_BEGIN, UUE_BODY, UUE_END
} mime_uue_state;

struct MimeDecoderData {
  mime_encoding encoding;		/* Which encoding to use */

  /* A read-buffer used for QP and B64. */
  char token[4];
  int token_size;

  /* State and read-buffer used for uudecode. */
  mime_uue_state uue_state;
  char uue_line_buffer [128];

  /* Where to write the decoded data */
  int (*write_buffer) (const char *buf, int32 size, void *closure);
  void *closure;
};


static int
mime_decode_qp_buffer (MimeDecoderData *data, const char *buffer, int32 length)
{
  /* Warning, we are overwriting the buffer which was passed in.
	 This is ok, because decoding these formats will never result
	 in larger data than the input, only smaller. */
  const char *in  = buffer;
  char *out = (char *) buffer;
  char token [3];
  int i;

  XP_ASSERT(data->encoding == mime_QuotedPrintable);
  if (data->encoding != mime_QuotedPrintable) return -1;

  /* For the first pass, initialize the token from the unread-buffer. */
  i = 0;
  while (i < 3 && data->token_size > 0)
	{
	  token [i] = data->token[i];
	  data->token_size--;
	  i++;
	}

  /* #### BUG: when decoding quoted-printable, we are required to
	 strip trailing whitespace from lines -- since when encoding in
	 qp, one is required to quote such trailing whitespace, any
	 trailing whitespace which remains must have been introduced
	 by a stupid gateway. */

  while (length > 0 || i != 0)
	{
	  while (i < 3 && length > 0)
		{
		  token [i++] = *in;
		  in++;
		  length--;
		}

	  if (i < 3)
		{
		  /* Didn't get enough for a complete token.
			 If it might be a token, unread it.
			 Otherwise, just dump it.
			 */
		  XP_MEMCPY (data->token, token, i);
		  data->token_size = i;
		  i = 0;
		  length = 0;
		  break;
		}
	  i = 0;

	  if (token [0] == '=')
		{
		  unsigned char c = 0;
		  if (token[1] >= '0' && token[1] <= '9')
			c = token[1] - '0';
		  else if (token[1] >= 'A' && token[1] <= 'F')
			c = token[1] - ('A' - 10);
		  else if (token[1] >= 'a' && token[1] <= 'f')
			c = token[1] - ('a' - 10);
		  else if (token[1] == CR || token[1] == LF)
			{
			  /* =\n means ignore the newline. */
			  if (token[1] == CR && token[2] == LF)
				;		/* swallow all three chars */
			  else
				{
				  in--;	/* put the third char back */
				  length++;
				}
			  continue;
			}
		  else
			{
			  /* = followed by something other than hex or newline -
				 pass it through unaltered, I guess.  (But, if
				 this bogus token happened to occur over a buffer
				 boundary, we can't do this, since we don't have
				 space for it.  Oh well.  Screw it.)  */
			  if (in > out) *out++ = token[0];
			  if (in > out) *out++ = token[1];
			  if (in > out) *out++ = token[2];
			  continue;
			}

		  /* Second hex digit */
		  c = (c << 4);
		  if (token[2] >= '0' && token[2] <= '9')
			c += token[2] - '0';
		  else if (token[2] >= 'A' && token[2] <= 'F')
			c += token[2] - ('A' - 10);
		  else if (token[2] >= 'a' && token[2] <= 'f')
			c += token[2] - ('a' - 10);
		  else
			{
			  /* We got =xy where "x" was hex and "y" was not, so
				 treat that as a literal "=", x, and y.  (But, if
				 this bogus token happened to occur over a buffer
				 boundary, we can't do this, since we don't have
				 space for it.  Oh well.  Screw it.) */
			  if (in > out) *out++ = token[0];
			  if (in > out) *out++ = token[1];
			  if (in > out) *out++ = token[2];
			  continue;
			}

		  *out++ = (char) c;
		}
	  else
		{
		  *out++ = token[0];

		  token[0] = token[1];
		  token[1] = token[2];
		  i = 2;
		}
	}

  /* Now that we've altered the data in place, write it. */
  if (out > buffer)
	return data->write_buffer (buffer, (out - buffer), data->closure);
  else
	return 1;
}


static int
mime_decode_base64_token (const char *in, char *out)
{
  /* reads 4, writes 0-3.  Returns bytes written.
	 (Writes less than 3 only at EOF.) */
  int j;
  int eq_count = 0;
  unsigned long num = 0;

  for (j = 0; j < 4; j++)
	{
	  unsigned char c = 0;
	  if (in[j] >= 'A' && in[j] <= 'Z')		 c = in[j] - 'A';
	  else if (in[j] >= 'a' && in[j] <= 'z') c = in[j] - ('a' - 26);
	  else if (in[j] >= '0' && in[j] <= '9') c = in[j] - ('0' - 52);
	  else if (in[j] == '+')				 c = 62;
	  else if (in[j] == '/')				 c = 63;
	  else if (in[j] == '=')				 c = 0, eq_count++;
	  else
		XP_ASSERT(0);
	  num = (num << 6) | c;
	}

  *out++ = (char) (num >> 16);
  *out++ = (char) ((num >> 8) & 0xFF);
  *out++ = (char) (num & 0xFF);

  if (eq_count == 0)
	return 3;				/* No "=" padding means 4 bytes mapped to 3. */
  else if (eq_count == 1)
	return 2;				/* "xxx=" means 3 bytes mapped to 2. */
  else if (eq_count == 2)
	return 1;				/* "xx==" means 2 bytes mapped to 1. */
  else
	{						/* "x===" can't happen, because "x" would then */
	  XP_ASSERT(0);			/* be encoding only 6 bits, not the min of 8. */
	  return 1;
	}
}


static int
mime_decode_base64_buffer (MimeDecoderData *data,
						   const char *buffer, int32 length)
{
  /* Warning, we are overwriting the buffer which was passed in.
	 This is ok, because decoding these formats will never result
	 in larger data than the input, only smaller. */
  const char *in  = buffer;
  char *out = (char *) buffer;
  char token [4];
  int i;
  XP_Bool leftover = (data->token_size > 0);

  XP_ASSERT(data->encoding == mime_Base64);

  /* For the first pass, initialize the token from the unread-buffer. */
  i = 0;
  while (i < 4 && data->token_size > 0)
	{
	  token [i] = data->token[i];
	  data->token_size--;
	  i++;
	}

  while (length > 0)
	{
	  while (i < 4 && length > 0)
		{
		  if ((*in >= 'A' && *in <= 'Z') ||
			  (*in >= 'a' && *in <= 'z') ||
			  (*in >= '0' && *in <= '9') ||
			  *in == '+' || *in == '/' || *in == '=')
			token [i++] = *in;
		  in++;
		  length--;
		}

	  if (i < 4)
		{
		  /* Didn't get enough for a complete token. */
		  XP_MEMCPY (data->token, token, i);
		  data->token_size = i;
		  length = 0;
		  break;
		}
	  i = 0;

	  if (leftover)
		{
		  /* If there are characters left over from the last time around,
			 we might not have space in the buffer to do our dirty work
			 (if there were 2 or 3 left over, then there is only room for
			 1 or 2 in the buffer right now, and we need 3.)  This is only
			 a problem for the first chunk in each buffer, so in that
			 case, just write prematurely. */
		  int n;
		  n = mime_decode_base64_token (token, token);
		  n = data->write_buffer (token, n, data->closure);
		  if (n < 0) /* abort */
			return n;

		  /* increment buffer so that we don't write the 1 or 2 unused
			 characters now at the front. */
		  buffer = in;
		  out = (char *) buffer;

		  leftover = FALSE;
		}
	  else
		{
		  int n = mime_decode_base64_token (token, out);
		  /* Advance "out" by the number of bytes just written to it. */
		  out += n;
		}
	}

  /* Now that we've altered the data in place, write it. */
  if (out > buffer)
	return data->write_buffer (buffer, (out - buffer), data->closure);
  else
	return 1;
}


static int
mime_decode_uue_buffer (MimeDecoderData *data,
						const char *input_buffer, int32 input_length)
{
  /* First, copy input_buffer into state->uue_line_buffer until we have
	 a complete line.

	 Then decode that line in place (in the uue_line_buffer) and write
	 it out.

	 Then pull the next line into uue_line_buffer and continue.
   */
  int status = 0;
  char *line = data->uue_line_buffer;
  char *line_end = data->uue_line_buffer + sizeof (data->uue_line_buffer) - 1;

  XP_ASSERT(data->encoding == mime_uuencode);
  if (data->encoding != mime_uuencode) return -1;

  if (data->uue_state == UUE_END)
	{
	  status = 0;
	  goto DONE;
	}

  while (input_length > 0)
	{
	  /* Copy data from input_buffer to `line' until we have a complete line,
		 or until we've run out of input.

		 (line may have data in it already if the last time we were called,
		 we weren't called with a buffer that ended on a line boundary.)
	   */
	  {
		char *out = line + XP_STRLEN(line);
		while (input_length > 0 &&
			   out < line_end)
		  {
			*out++ = *input_buffer++;
			input_length--;

			if (out[-1] == CR || out[-1] == LF)
			  {
				/* If we just copied a CR, and an LF is waiting, grab it too.
				 */
				if (out[-1] == CR &&
					input_length > 0 &&
					*input_buffer == LF)
				  input_buffer++, input_length--;

				/* We have a line. */
				break;
			  }
		  }
		*out = 0;

		/* Ignore blank lines.
		 */
		if (*line == CR || *line == LF)
		  {
			*line = 0;
			continue;
		  }

		/* If this line was bigger than our buffer, truncate it.
		   (This means the data was way corrupted, and there's basically
		   no chance of decoding it properly, but give it a shot anyway.)
		 */
		if (out == line_end)
		  {
			out--;
			out[-1] = CR;
			out[0] = 0;
		  }

		/* If we didn't get a complete line, simply return; we'll be called
		   with the rest of this line next time.
		 */
		if (out[-1] != CR && out[-1] != LF)
		  {
			XP_ASSERT (input_length == 0);
			break;
		  }
	  }


	  /* Now we have a complete line.  Deal with it.
	   */


	  if (data->uue_state == UUE_BODY &&
		  line[0] == 'e' &&
		  line[1] == 'n' &&
		  line[2] == 'd' &&
		  (line[3] == CR ||
		   line[3] == LF))
		{
		  /* done! */
		  data->uue_state = UUE_END;
		  *line = 0;
		  break;
		}
	  else if (data->uue_state == UUE_BEGIN)
		{
		  if (!XP_STRNCMP (line, "begin ", 6))
			data->uue_state = UUE_BODY;
		  *line = 0;
		  continue;
		}
	  else
		{
		  /* We're in UUE_BODY.  Decode the line. */
		  char *in, *out;
		  int32 i;
		  long lost;

		  XP_ASSERT (data->uue_state == UUE_BODY);

		  /* We map down `line', reading four bytes and writing three.
			 That means that `out' always stays safely behind `in'.
		   */
		  in = line;
		  out = line;

# undef DEC
# define DEC(c) (((c) - ' ') & 077)
		  i = DEC (*in); /* get length */

		  /* all the parens and casts are because gcc was doing something evil.
		   */
		  lost = ((long) i) - (((((long) XP_STRLEN (in)) - 2L) * 3L) / 4L);

		  if (lost > 0) /* Short line!! */
			{
			  /* If we get here, then the line is shorter than the length byte
				 at the beginning says it should be.  However, the case where
				 the line is short because it was at the end of the buffer and
				 we didn't get the whole line was handled earlier (up by the
				 "didn't get a complete line" comment.)  So if we've gotten
				 here, then this is a complete line which is internally
				 inconsistent.  We will parse from it what we can...

				 This probably happened because some gateway stripped trailing
				 whitespace from the end of the line -- so pretend the line
				 was padded with spaces (which map to \000.)
			   */
			  i -= lost;
			}

		  for (++in; i > 0; in += 4, i -= 3)
			{
			  char ch;
			  XP_ASSERT(out <= in);

			  if (i >= 3)
				{
				  /* We read four; write three. */
				  ch = DEC (in[0]) << 2 | DEC (in[1]) >> 4;
				  *out++ = ch;

				  XP_ASSERT(out <= in+1);

				  ch = DEC (in[1]) << 4 | DEC (in[2]) >> 2;
				  *out++ = ch;

				  XP_ASSERT(out <= in+2);

				  ch = DEC (in[2]) << 6 | DEC (in[3]);
				  *out++ = ch;

				  XP_ASSERT(out <= in+3);
				}
			  else
				{
				  /* Handle a line that isn't a multiple of 4 long.
					 (We read 1, 2, or 3, and will write 1 or 2.)
				   */
				  XP_ASSERT (i > 0 && i < 3);

				  ch = DEC (in[0]) << 2 | DEC (in[1]) >> 4;
				  *out++ = ch;

				  XP_ASSERT(out <= in+1);

				  if (i == 2)
					{
					  ch = DEC (in[1]) << 4 | DEC (in[2]) >> 2;
					  *out++ = ch;

					  XP_ASSERT(out <= in+2);
					}
				}
			}

		  /* If the line was truncated, pad the missing bytes with 0 (SPC). */
		  while (lost > 0)
			{
			  *out++ = 0;
			  lost--;
			  in = out+1; /* just to prevent the assert, below. */
			}
# undef DEC

		  /* Now write out what we decoded for this line.
		   */
		  XP_ASSERT(out >= line && out < in);
		  if (out > line)
			status = data->write_buffer (line, (out - line), data->closure);

		  /* Reset the line so that we don't think it's partial next time. */
		  *line = 0;

		  if (status < 0) /* abort */
			goto DONE;
		}
	}

  status = 1;

 DONE:

  return status;
}


int
MimeDecoderDestroy (MimeDecoderData *data, XP_Bool abort_p)
{
  int status = 0;
  /* Flush out the last few buffered characters. */
  if (!abort_p &&
	  data->token_size > 0 &&
	  data->token[0] != '=')
	{
	  if (data->encoding == mime_Base64)
		while (data->token_size < sizeof (data->token))
		  data->token [data->token_size++] = '=';

	  status = data->write_buffer (data->token, data->token_size,
								   data->closure);
	}

  XP_FREE (data);
  return status;
}


static MimeDecoderData *
mime_decoder_init (enum mime_encoding which,
				   int (*output_fn) (const char *, int32, void *),
				   void *closure)
{
  MimeDecoderData *data = XP_NEW(MimeDecoderData);
  if (!data) return 0;
  XP_MEMSET(data, 0, sizeof(*data));
  data->encoding = which;
  data->write_buffer = output_fn;
  data->closure = closure;
  return data;
}

MimeDecoderData *
MimeB64DecoderInit (int (*output_fn) (const char *, int32, void *),
					void *closure)
{
  return mime_decoder_init (mime_Base64, output_fn, closure);
}

MimeDecoderData *
MimeQPDecoderInit (int (*output_fn) (const char *, int32, void *),
				   void *closure)
{
  return mime_decoder_init (mime_QuotedPrintable, output_fn, closure);
}

MimeDecoderData *
MimeUUDecoderInit (int (*output_fn) (const char *, int32, void *),
				   void *closure)
{
  return mime_decoder_init (mime_uuencode, output_fn, closure);
}

int
MimeDecoderWrite (MimeDecoderData *data, const char *buffer, int32 size)
{
  XP_ASSERT(data);
  if (!data) return -1;
  switch(data->encoding)
	{
	case mime_Base64:
	  return mime_decode_base64_buffer (data, buffer, size);
	case mime_QuotedPrintable:
	  return mime_decode_qp_buffer (data, buffer, size);
	case mime_uuencode:
	  return mime_decode_uue_buffer (data, buffer, size);
	default:
	  XP_ASSERT(0);
	  return -1;
	}
}



/* ================== Encoders.
 */

struct MimeEncoderData {
  mime_encoding encoding;		/* Which encoding to use */

  /* Buffer for the base64 encoder. */
  unsigned char in_buffer[3];
  int32 in_buffer_count;

	/* Buffer for uuencoded data. (Need a line because of the length byte.) */
	unsigned char uue_line_buf[128];
	XP_Bool uue_wrote_begin;
	
  int32 current_column, line_byte_count;
  
  char *filename; /* filename for use with uuencoding */

  /* Where to write the encoded data */
  int (*write_buffer) (const char *buf, int32 size, void *closure);
  void *closure;
};

/* Use what looks like a nice, safe value for a standard uue line length */
#define UUENCODE_LINE_LIMIT 60

#undef ENC
#define ENC(c) ((c & 0x3F) + ' ')

void
mime_uuencode_write_line(MimeEncoderData *data)
{
	/* Set the length byte at the beginning: 
	   encoded (data->line_byte_count). */
	data->uue_line_buf[0] = ENC(data->line_byte_count);

	/* Tack a CRLF onto the end. */
	data->uue_line_buf[data->current_column++] = CR;
	data->uue_line_buf[data->current_column++] = LF;

	/* Write the line to output. */
	data->write_buffer((const char*)data->uue_line_buf, data->current_column,
					   data->closure);

	/* Reset data based on having just written a complete line. */
	data->in_buffer_count = 0;
	data->line_byte_count = 0;
	data->current_column = 1;
}

void
mime_uuencode_convert_triplet(MimeEncoderData *data)
{
	/* 
	   If we have 3 bytes, encode them and add them to the current 
	   line. The way we want to encode them is like this 
	   (each digit corresponds to a bit in the binary source):
	   11111111 -> 00111111 + ' ' (six highest bits of 1)
	   22222222    00112222 + ' ' (low 2 of 1, high 4 of 2)
	   33333333    00222233 + ' ' (low 4 of 2, high 2 of 3)
	               00333333 + ' ' (low 6 of 3)
	*/
	char outData[4];
	int i;
	
	outData[0] = data->in_buffer[0] >> 2;
	
	outData[1] = ((data->in_buffer[0] << 4) & 0x30);
	outData[1] |= data->in_buffer[1] >> 4;

	outData[2] = ((data->in_buffer[1] << 2) & 0x3C);
	outData[2] |= data->in_buffer[2] >> 6;

	outData[3] = data->in_buffer[2] & 0x3F;

	for(i=0;i<4;i++)
		data->uue_line_buf[data->current_column++] = ENC(outData[i]);

	data->in_buffer_count = 0;
}

int
mime_uuencode_buffer(MimeEncoderData *data,
					   const char *buffer, int32 size)
{
	/* If this is the first time through, write a begin statement. */
	if (!(data->uue_wrote_begin))
	{
		char firstLine[256];
		XP_SPRINTF(firstLine, "begin 644 %s\015\012", data->filename ? data->filename : "");
		data->write_buffer(firstLine, strlen(firstLine), data->closure);
		data->uue_wrote_begin = TRUE;
		data->current_column = 1; /* initialization unique to uuencode */
	}

	/* Pick up where we left off. */
	while(size > 0)
	{
		/* If we've reached the end of a line, write the line out. */
		if (data->current_column >= UUENCODE_LINE_LIMIT) 
		{
			/* End of a line. Write the line out. */
			mime_uuencode_write_line(data);
		}

		/* Get the next 3 bytes if we have them, or whatever we can get. */
		while(size > 0 && data->in_buffer_count < 3)
		{
			data->in_buffer[data->in_buffer_count++] = *(buffer++);
			size--; data->line_byte_count++;
		}
		
		if (data->in_buffer_count == 3)
		{
			mime_uuencode_convert_triplet(data);
		}
	}
	return 0;
}

int
mime_uuencode_finish(MimeEncoderData *data)
{
	int i;
	static const char *endStr = "end\015\012";

	/* If we have converted binary data to write to output, do it now. */
	if (data->line_byte_count > 0)
	{
		/* If we have binary data yet to be converted, 
		   pad and convert it. */
		if (data->in_buffer_count > 0)
		{
			for(i=data->in_buffer_count;i<3;i++)
				data->in_buffer[i] = '\0'; /* pad with zeroes */

			mime_uuencode_convert_triplet(data);
		}

		mime_uuencode_write_line(data);
	}

	/* Write 'end' on a line by itself. */
	return data->write_buffer(endStr, strlen(endStr), data->closure);
}

#undef ENC

int
mime_encode_base64_buffer (MimeEncoderData *data,
						   const char *buffer, int32 size)
{
  int status = 0;
  const unsigned char *in = (unsigned char *) buffer;
  const unsigned char *end = in + size;
  char out_buffer[80];
  char *out = out_buffer;
  uint32 i = 0, n = 0;
  uint32 off;

  if (size == 0)
	return 0;
  else if (size < 0)
	{
	  XP_ASSERT(0);
	  return -1;
	}


  /* If this input buffer is too small, wait until next time. */
  if (size < (3 - data->in_buffer_count))
	{
	  XP_ASSERT(size < 3 && size > 0);
	  data->in_buffer[data->in_buffer_count++] = buffer[0];
	  if (size > 1)
		data->in_buffer[data->in_buffer_count++] = buffer[1];
	  XP_ASSERT(data->in_buffer_count < 3);
	  return 0;
	}


  /* If there are bytes that were put back last time, take them now.
   */
  i = 0;
  if (data->in_buffer_count > 0) n = data->in_buffer[0];
  if (data->in_buffer_count > 1) n = (n << 8) + data->in_buffer[1];
  i = data->in_buffer_count;
  data->in_buffer_count = 0;

  /* If this buffer is not a multiple of three, put one or two bytes back.
   */
  off = ((size + i) % 3);
  if (off)
	{
	  data->in_buffer[0] = buffer [size - off];
	  if (off > 1)
		data->in_buffer [1] = buffer [size - off + 1];
	  data->in_buffer_count = off;
	  size -= off;
	  XP_ASSERT (! ((size + i) % 3));
	  end = (unsigned char *) (buffer + size);
	}

  /* Populate the out_buffer with base64 data, one line at a time.
   */
  while (in < end)
	{
	  int32 j;

	  while (i < 3)
		{
		  n = (n << 8) | *in++;
		  i++;
		}
	  i = 0;

	  for (j = 18; j >= 0; j -= 6)
		{
		  unsigned int k = (n >> j) & 0x3F;
		  if (k < 26)       *out++ = k      + 'A';
		  else if (k < 52)  *out++ = k - 26 + 'a';
		  else if (k < 62)  *out++ = k - 52 + '0';
		  else if (k == 62) *out++ = '+';
		  else if (k == 63) *out++ = '/';
		  else abort ();
		}

	  data->current_column += 4;
	  if (data->current_column >= 72)
		{
		  /* Do a linebreak before column 76.  Flush out the line buffer. */
		  data->current_column = 0;
		  *out++ = '\015';
		  *out++ = '\012';
		  status = data->write_buffer (out_buffer, (out - out_buffer),
									   data->closure);
		  out = out_buffer;
		  if (status < 0) return status;
		}
	}

  /* Write out the unwritten portion of the last line buffer. */
  if (out > out_buffer)
	{
	  status = data->write_buffer (out_buffer, (out - out_buffer),
								   data->closure);
	  if (status < 0) return status;
	}

  return 0;
}


int
mime_encode_qp_buffer (MimeEncoderData *data, const char *buffer, int32 size)
{
  int status = 0;
  static const char hexdigits[] = "0123456789ABCDEF";
  const unsigned char *in = (unsigned char *) buffer;
  const unsigned char *end = in + size;
  char out_buffer[80];
  char *out = out_buffer;
  uint32 i = 0, n = 0;
  XP_Bool white = FALSE;
  XP_Bool mb_p = FALSE;

/*
  #### I don't know how to hook this back up:
  ####  mb_p = INTL_DefaultWinCharSetID(state->context) & 0x300 ;    
 */


  XP_ASSERT(data->in_buffer_count == 0);

  /* Populate the out_buffer with quoted-printable data, one line at a time.
   */
  for (; in < end; in++)
	{
	  if (*in == CR || *in == LF)
		{
		  /* Whitespace cannot be allowed to occur at the end of the line.
			 So we encode " \n" as " =\n\n", that is, the whitespace, a
			 soft line break, and then a hard line break.
		   */
		  if (white)
			{
			  *out++ = '=';
			  *out++ = CR;
			  *out++ = LF;
			}

		  /* Now write out the newline. */
		  *out++ = CR;
		  *out++ = LF;
		  white = FALSE;

		  status = data->write_buffer (out_buffer, (out - out_buffer),
									   data->closure);
		  if (status < 0) return status;
		  out = out_buffer;

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

		  out = out_buffer;
		  white = FALSE;
		  data->current_column = 0;
		}
	  else if (data->current_column == 0 && *in == '.')
		{
		  /* Just to be SMTP-safe, if "." appears in column 0, encode it.
			 (mmencode does this too.)
		   */
		  goto HEX;
		}
	  else if (data->current_column == 0 && *in == 'F'
			   && (in >= end-1 || in[1] == 'r')
			   && (in >= end-2 || in[2] == 'o')
			   && (in >= end-3 || in[3] == 'm')
			   && (in >= end-4 || in[4] == ' '))
		{
		  /* If this line begins with 'F' and we cannot determine that
			 this line does not begin with "From " then do the safe thing
			 and assume that it does, and encode the 'F' in hex to avoid
			 BSD mailbox lossage.  (We might not be able to tell that it
			 is really "From " if the end of the buffer was early.  So
			 this means that "\nFoot" will have the F encoded if the end of
			 the buffer happens to fall just after the F; but will not have
			 it encoded if it's after the first "o" or later.  Oh well.
			 It's a little inconsistent, but it errs on the safe side.)
		   */
		  goto HEX;
		}
	  else if ((*in >= 33 && *in <= 60) ||		/* safe printing chars */
			   (*in >= 62 && *in <= 126) ||
			   (mb_p && (*in == 61 || *in == 127 || *in == 0x1B)))
		{
		  white = FALSE;
		  *out++ = *in;
		  data->current_column++;
		}
	  else if (*in == ' ' || *in == '\t')		/* whitespace */
		{
		  white = TRUE;
		  *out++ = *in;
		  data->current_column++;
		}
	  else										/* print as =FF */
		{
		HEX:
		  white = FALSE;
		  *out++ = '=';
		  *out++ = hexdigits[*in >> 4];
		  *out++ = hexdigits[*in & 0xF];
		  data->current_column += 3;
		}

	  XP_ASSERT (data->current_column <= 76); /* Hard limit required by spec */

	  if (data->current_column >= 73)		/* soft line break: "=\r\n" */
		{
		  *out++ = '=';
		  *out++ = CR;
		  *out++ = LF;

		  status = data->write_buffer (out_buffer, (out - out_buffer),
									   data->closure);
		  if (status < 0) return status;
		  out = out_buffer;
		  white = FALSE;
		  data->current_column = 0;
		}
	}

  /* Write out the unwritten portion of the last line buffer. */
  if (out > out_buffer)
	{
	  status = data->write_buffer (out_buffer, (out - out_buffer),
								   data->closure);
	  if (status < 0) return status;
	}

  return 0;
}



int
MimeEncoderDestroy (MimeEncoderData *data, XP_Bool abort_p)
{
  int status = 0;

  /* If we're uuencoding, we have our own finishing routine. */
  if (data->encoding == mime_uuencode)
	 mime_uuencode_finish(data);

  /* Since Base64 (and uuencode) output needs to do some buffering to get 
	 a multiple of three bytes on each block, there may be a few bytes 
	 left in the buffer after the last block has been written.  We need to
	 flush those out now.
   */

  XP_ASSERT (data->encoding == mime_Base64 ||
			 data->in_buffer_count == 0);

  if (!abort_p &&
	  data->in_buffer_count > 0)
	{
	  char buf2 [6];
	  char *buf = buf2 + 2;
	  char *out = buf;
	  int j;
	  /* fixed bug 55998, 61302, 61866
	   * type casting to uint32 before shifting
	   */
	  uint32 n = ((uint32) data->in_buffer[0]) << 16;
	  if (data->in_buffer_count > 1)
		n = n | (((uint32) data->in_buffer[1]) << 8);

	  buf2[0] = CR;
	  buf2[1] = LF;

	  for (j = 18; j >= 0; j -= 6)
		{
		  unsigned int k = (n >> j) & 0x3F;
		  if (k < 26)       *out++ = k      + 'A';
		  else if (k < 52)  *out++ = k - 26 + 'a';
		  else if (k < 62)  *out++ = k - 52 + '0';
		  else if (k == 62) *out++ = '+';
		  else if (k == 63) *out++ = '/';
		  else abort ();
		}

	  /* Pad with equal-signs. */
	  if (data->in_buffer_count == 1)
		buf[2] = '=';
	  buf[3] = '=';

	  if (data->current_column >= 72)
		status = data->write_buffer (buf2, 6, data->closure);
	  else
		status = data->write_buffer (buf,  4, data->closure);
	}

  XP_FREEIF(data->filename);
  XP_FREE (data);
  return status;
}


static MimeEncoderData *
mime_encoder_init (enum mime_encoding which,
				   int (*output_fn) (const char *, int32, void *),
				   void *closure)
{
  MimeEncoderData *data = XP_NEW(MimeEncoderData);
  if (!data) return 0;
  XP_MEMSET(data, 0, sizeof(*data));
  data->encoding = which;
  data->write_buffer = output_fn;
  data->closure = closure;
  return data;
}

MimeEncoderData *
MimeB64EncoderInit (int (*output_fn) (const char *, int32, void *),
					void *closure)
{
  return mime_encoder_init (mime_Base64, output_fn, closure);
}

MimeEncoderData *
MimeQPEncoderInit (int (*output_fn) (const char *, int32, void *),
				   void *closure)
{
  return mime_encoder_init (mime_QuotedPrintable, output_fn, closure);
}

MimeEncoderData *
MimeUUEncoderInit (char *filename,
					int (*output_fn) (const char *, int32, void *),
					void *closure)
{
  MimeEncoderData *enc = mime_encoder_init (mime_uuencode, output_fn, closure);
  
  if (filename)
	  enc->filename = XP_STRDUP(filename);
	  
  return enc;
}

int
MimeEncoderWrite (MimeEncoderData *data, const char *buffer, int32 size)
{
  XP_ASSERT(data);
  if (!data) return -1;
  switch(data->encoding)
	{
	case mime_Base64:
	  return mime_encode_base64_buffer (data, buffer, size);
	case mime_QuotedPrintable:
	  return mime_encode_qp_buffer (data, buffer, size);
	case mime_uuencode:
	  return mime_uuencode_buffer(data, buffer, size);
	default:
	  XP_ASSERT(0);
	  return -1;
	}
}
