/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is 
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or 
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the NPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
#include <stdio.h>
#include "modmimee.h"
#include "mimei.h"
#include "nsCRT.h"
#include "prmem.h"
#include "plstr.h"
#include "prlog.h"
#include "prprf.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 */
  nsresult (*write_buffer) (const char *buf, PRInt32 size, void *closure);
  void *closure;
};


static int
mime_decode_qp_buffer (MimeDecoderData *data, const char *buffer, PRInt32 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;

  PR_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.
			 */
		  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] == nsCRT::CR || token[1] == nsCRT::LF)
			{
			  /* =\n means ignore the newline. */
			  if (token[1] == nsCRT::CR && token[2] == nsCRT::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
		PR_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 */
	  PR_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, PRInt32 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;
  PRBool leftover = (data->token_size > 0);

  PR_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. */
		  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 = PR_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, PRInt32 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;

  PR_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 + strlen(line);
		while (input_length > 0 &&
			   out < line_end)
		  {
			*out++ = *input_buffer++;
			input_length--;

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

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

		/* Ignore blank lines.
		 */
		if (*line == nsCRT::CR || *line == nsCRT::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] = nsCRT::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] != nsCRT::CR && out[-1] != nsCRT::LF)
		  {
			PR_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] == nsCRT::CR ||
		   line[3] == nsCRT::LF))
		{
		  /* done! */
		  data->uue_state = UUE_END;
		  *line = 0;
		  break;
		}
	  else if (data->uue_state == UUE_BEGIN)
		{
		  if (!nsCRT::strncmp (line, "begin ", 6))
			data->uue_state = UUE_BODY;
		  *line = 0;
		  continue;
		}
	  else
		{
		  /* We're in UUE_BODY.  Decode the line. */
		  char *in, *out;
		  PRInt32 i;
		  long lost;

		  PR_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) 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;
			  PR_ASSERT(out <= in);

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

				  PR_ASSERT(out <= in+1);

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

				  PR_ASSERT(out <= in+2);

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

				  PR_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.)
				   */
				  PR_ASSERT (i > 0 && i < 3);

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

				  PR_ASSERT(out <= in+1);

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

					  PR_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.
		   */
		  PR_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, PRBool 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 ((unsigned int)data->token_size < sizeof (data->token))
		  data->token [data->token_size++] = '=';

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

  PR_Free (data);
  return status;
}


static MimeDecoderData *
mime_decoder_init (mime_encoding which,
				   nsresult (*output_fn) (const char *, PRInt32, void *),
				   void *closure)
{
  MimeDecoderData *data = PR_NEW(MimeDecoderData);
  if (!data) return 0;
  memset(data, 0, sizeof(*data));
  data->encoding = which;
  data->write_buffer = output_fn;
  data->closure = closure;
  return data;
}

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

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

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

int
MimeDecoderWrite (MimeDecoderData *data, const char *buffer, PRInt32 size)
{
  PR_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:
	  PR_ASSERT(0);
	  return -1;
	}
}


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

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

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

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

  /* Where to write the encoded data */
  nsresult (*write_buffer) (const char *buf, PRInt32 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++] = nsCRT::CR;
	data->uue_line_buf[data->current_column++] = nsCRT::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, PRInt32 size)
{
	/* If this is the first time through, write a begin statement. */
	if (!(data->uue_wrote_begin))
	{
		char firstLine[256];
		PR_snprintf(firstLine, sizeof(firstLine), "begin 644 %s\015\012", data->filename ? data->filename : "");
		data->write_buffer(firstLine, strlen(firstLine), data->closure);
		data->uue_wrote_begin = PR_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, PRInt32 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;
  PRUint32 i = 0, n = 0;
  PRUint32 off;

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


  /* If this input buffer is too small, wait until next time. */
  if (size < (3 - data->in_buffer_count))
	{
	  PR_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];
	  PR_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;
	  PR_ASSERT (! ((size + i) % 3));
	  end = (unsigned char *) (buffer + size);
	}

  /* Populate the out_buffer with base64 data, one line at a time.
   */
  while (in < end)
	{
	  PRInt32 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, PRInt32 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;
  PRBool white = PR_FALSE;
  PRBool mb_p = PR_FALSE;

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


  PR_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 == nsCRT::CR || *in == nsCRT::LF)
		{

      /* Whitespace cannot be allowed to occur at the end of           
         the line, so we backup and replace the whitespace with        
         it's code.                                                    
       */                                                               
      if (white)                                                       
      {                                                              
        out--;                                                       
        char whitespace_char = *out;                                 
        *out++ = '=';                                                
        *out++ = hexdigits[whitespace_char >> 4];                    
        *out++ = hexdigits[whitespace_char & 0xF];                   
      }                                                   

		  /* Now write out the newline. */
		  *out++ = nsCRT::CR;
		  *out++ = nsCRT::LF;
		  white = PR_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] == nsCRT::CR && in[1] == nsCRT::LF)
			in++;

		  out = out_buffer;
		  white = PR_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 = PR_FALSE;
		  *out++ = *in;
		  data->current_column++;
		}
	  else if (*in == ' ' || *in == '\t')		/* whitespace */
		{
		  white = PR_TRUE;
		  *out++ = *in;
		  data->current_column++;
		}
	  else										/* print as =FF */
		{
		HEX:
		  white = PR_FALSE;
		  *out++ = '=';
		  *out++ = hexdigits[*in >> 4];
		  *out++ = hexdigits[*in & 0xF];
		  data->current_column += 3;
		}

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

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

		  status = data->write_buffer (out_buffer, (out - out_buffer),
									   data->closure);
		  if (status < 0) return status;
		  out = out_buffer;
		  white = PR_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, PRBool 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.
   */

  PR_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 PRUint32 before shifting
	   */
	  PRUint32 n = ((PRUint32) data->in_buffer[0]) << 16;
	  if (data->in_buffer_count > 1)
		n = n | (((PRUint32) data->in_buffer[1]) << 8);

	  buf2[0] = nsCRT::CR;
	  buf2[1] = nsCRT::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);
	}

  PR_FREEIF(data->filename);
  PR_Free (data);
  return status;
}


static MimeEncoderData *
mime_encoder_init (mime_encoding which,
				   nsresult (*output_fn) (const char *, PRInt32, void *),
				   void *closure)
{
  MimeEncoderData *data = PR_NEW(MimeEncoderData);
  if (!data) return 0;
  memset(data, 0, sizeof(*data));
  data->encoding = which;
  data->write_buffer = output_fn;
  data->closure = closure;
  return data;
}

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

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

MimeEncoderData *
MimeUUEncoderInit (char *filename,
					nsresult (*output_fn) (const char *, PRInt32, void *),
					void *closure)
{
  MimeEncoderData *enc = mime_encoder_init (mime_uuencode, output_fn, closure);
  
  if (filename)
	  enc->filename = nsCRT::strdup(filename);
	  
  return enc;
}

int
MimeEncoderWrite (MimeEncoderData *data, const char *buffer, PRInt32 size)
{
  PR_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:
	  PR_ASSERT(0);
	  return -1;
	}
}
