/* -*- 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.
 */
/*	mime2fun.c	*/
/*  Function related to MIME-2 support	*/

#include "intlpriv.h"
#include "prefapi.h"

/*
	"The character set names may be up to 40 characters taken from the
	printable characters of US-ASCII.  However, no distinction is made
	between use of upper and lower case letters." [RFC 1700]
*/
typedef struct {
	int16	csid_key;
	int16	csid_target;
} cs_csid_map_t;


	/* Maps a window encoding to the MIME encoding used for posting
	 * news and internet email.  String is for the MIME charset string.

		Currently, this table is only used for HEADER ENCODING!!!!
		It is not used for HEADER decoding!!!!
		It is not used for Body Decoding or Encoding!!!!
		NOTE: We need to change this  from win_csid base to doc_csid base
	
	 */

PRIVATE cs_csid_map_t	cs_mime_csidmap_tbl[] = {
			{CS_ASCII,		CS_ASCII	},
			
			{CS_LATIN1,		CS_LATIN1	},
			
			{CS_JIS,		CS_JIS		},
			{CS_SJIS,		CS_JIS		},
			{CS_EUCJP,		CS_JIS,		},
			
			{CS_KSC_8BIT,	CS_KSC_8BIT	},
				
			{CS_GB_8BIT,	CS_GB_8BIT	},
			
			{CS_BIG5,		CS_BIG5		},
			{CS_CNS_8BIT,	CS_BIG5		},
			
			{CS_MAC_ROMAN,	CS_LATIN1	},
			
			{CS_LATIN2,		CS_LATIN2	},
			{CS_MAC_CE,		CS_LATIN2	},
			{CS_CP_1250,	CS_LATIN2	},

			{CS_8859_5,			CS_KOI8_R	},
			{CS_KOI8_R,			CS_KOI8_R	},
			{CS_MAC_CYRILLIC,	CS_KOI8_R	},
			{CS_CP_1251,		CS_KOI8_R	},
			
			{CS_8859_7,		CS_8859_7	},
			{CS_CP_1253,		CS_8859_7	},
			{CS_MAC_GREEK,	CS_8859_7	},

			{CS_8859_9,		CS_8859_9	},
			{CS_MAC_TURKISH,CS_8859_9	},
			
			{CS_UTF8,		CS_UTF8		},
			{CS_UTF7,		CS_UTF7		},
			{CS_UCS2,		CS_UTF7		},
			{CS_UCS2_SWAP,	CS_UTF7		},
			{0,				0,			}
};

#define MAXLINELEN 72
#define IS_MAIL_SEPARATOR(p) ((*(p) == ',' || *(p) == ' ' || *(p) == '\"' || *(p) == ':' || \
  *(p) == '(' || *(p) == ')' || *(p) == '\\' || (unsigned char)*p < 0x20))

/*
	Prototype for Private Function
*/
PRIVATE void    intlmime_init_csidmap();
PRIVATE XP_Bool	intlmime_only_ascii_str(const char *s);
PRIVATE char *	intlmime_encode_mail_address(int wincsid, const char *src, CCCDataObject obj, 
											int maxLineLen);
PRIVATE char *	intlmime_encode_next8bitword(int wincsid, char *src);
PRIVATE int16	intlmime_get_outgoing_mime_csid(int16);
PRIVATE int16	intlmime_map_csid(cs_csid_map_t *csmimep, int16	csid_key);

/*	we should consider replace this base64 decodeing and encoding function with a better one */
PRIVATE int		intlmime_decode_base64 (const char *in, char *out);
PRIVATE char *	intlmime_decode_qp(char *in);
PRIVATE int		intlmime_encode_base64 (const char *in, char *out);
PRIVATE char *	intlmime_decode_base64_buf(char *subject);
PRIVATE char *	intlmime_encode_base64_buf(char *subject, size_t size);
PRIVATE char *	intlmime_encode_qp_buf(char *subject);


PRIVATE XP_Bool intlmime_is_hz(const char *header);
PRIVATE XP_Bool intlmime_is_mime_part2_header(const char *header);
PRIVATE XP_Bool intlmime_is_iso_2022_xxx(const char *, int16 );
PRIVATE	char *	intl_decode_mime_part2_str(const char *, int , XP_Bool );
PRIVATE	char *	intl_DecodeMimePartIIStr(const char *, int16 , XP_Bool );
PRIVATE char *	intl_EncodeMimePartIIStr(char *subject, int16 wincsid, XP_Bool bUseMime, int maxLineLen);


#if 0
/* Prototype of callback routine invoked by prefapi when the pref value changes 
 * Win16 build fails if this is declared as static.
 */
MODULE_PRIVATE
int PR_CALLBACK intlmime_get_mail_strictly_mime(const char * newpref, void * data);
#endif

/*	We probably should change these into private instead of PUBLIC */
PUBLIC char *DecodeBase64Buffer(char *subject);
PUBLIC char *EncodeBase64Buffer(char *subject, size_t size);


/* 4.0: Made Encode & Decode public for use by libpref; added size param.
 */
PUBLIC char *EncodeBase64Buffer(char *subject, size_t size)
{
	/* This function should be obsolete */
	/* We should not make this public in libi18n */
	/* We should use the new Base64 Encoder wrote by jwz in libmime */
	return intlmime_encode_base64_buf(subject, size);
}

PUBLIC char *DecodeBase64Buffer(char *subject)
{
	/* This function should be obsolete */
	/* We should not make this public in libi18n */
	/* We should use the new Base64 Decoder wrote by jwz in libmime */
	return intlmime_decode_base64_buf(subject);
}

#if defined(MOZ_MAIL_COMPOSE) || defined(MOZ_MAIL_NEWS)

/*
	Implementation 
*/
/*
	INTL_DefaultMailCharSetID,
*/
PUBLIC int16 INTL_DefaultMailCharSetID(int16 csid)
{
	int16 retcsid;
 	csid &= ~CS_AUTO;

	intlmime_init_csidmap();
	retcsid = intlmime_map_csid(cs_mime_csidmap_tbl, csid);

	return retcsid;
}

PUBLIC int16 INTL_DefaultNewsCharSetID(int16 csid)
{

	if (csid == 0)
		csid = INTL_DefaultDocCharSetID(0);
	csid &= ~CS_AUTO;
	if (csid == CS_KSC_8BIT)
		return CS_KSC_8BIT;
	intlmime_init_csidmap();
	return intlmime_map_csid(cs_mime_csidmap_tbl, csid);
}

PUBLIC
char *INTL_DecodeMimePartIIStr(const char *header, int16 wincsid, XP_Bool dontConvert)
{
	return intl_DecodeMimePartIIStr(header, wincsid, dontConvert);
}
PUBLIC
char *INTL_EncodeMimePartIIStr(char *subject, int16 wincsid, XP_Bool bUseMime)
{
	return intl_EncodeMimePartIIStr(subject, wincsid, bUseMime, MAXLINELEN);
}
#endif /* MOZ_MAIL_COMPOSE || MOZ_MAIL_NEWS */

#ifdef MOZ_MAIL_NEWS
/*  This is a routine used to re-encode subject lines for use in the summary file.
	The reason why we specify a different length here is because we are not encoding
	the string for use in a mail message, but rather want to stuff as much content
	into the subject string as possible. */
PUBLIC
char *INTL_EncodeMimePartIIStr_VarLen(char *subject, int16 wincsid, XP_Bool bUseMime, int encodedWordSize)
{
	return intl_EncodeMimePartIIStr(subject, wincsid, bUseMime, encodedWordSize);
}
#endif /* MOZ_MAIL_NEWS */


#if defined(MOZ_MAIL_COMPOSE) || defined(MOZ_MAIL_NEWS)
/*	some utility function used by this file */
PRIVATE XP_Bool intlmime_only_ascii_str(const char *s)
{
	for(; *s; s++)
		if(*s & 0x80)
			return FALSE;
	return TRUE;
}
#endif /* MOZ_MAIL_COMPOSE || MOZ_MAIL_NEWS */

#ifdef MOZ_MAIL_NEWS
PRIVATE void intlmime_update_csidmap(int16 csid_key, int16 csid_target)
{
	cs_csid_map_t * mapp;
	
	for(mapp = cs_mime_csidmap_tbl ; 
		mapp->csid_key != 0 ; 
		mapp++) 
		{
		if (mapp->csid_key == csid_key) 
			mapp->csid_target = csid_target;
		}
}
#endif /* MOZ_MAIL_NEWS */

#if defined(MOZ_MAIL_COMPOSE) || defined(MOZ_MAIL_NEWS)
PRIVATE void
intlmime_init_csidmap()
{
	static XP_Bool initialized = FALSE;

	if(initialized)
		return;
		
    /* Speical Hack for Cyrllic */
    /* We need to know wheater we should send KOI8-R or ISO-8859-5 */

	{
		static const char* pref_mailcharset_cyrillic = "intl.mailcharset.cyrillic";
		static const char* pref_mailcharset_korean = "intl.mailcharset.korean";
		char *mailcharset_pref = NULL;
		int16 mailcsid_pref = CS_UNKNOWN;

		if( PREF_NOERROR == PREF_CopyCharPref(pref_mailcharset_cyrillic, 
								&mailcharset_pref))
		{
			mailcsid_pref = INTL_CharSetNameToID(mailcharset_pref);
			XP_FREE(mailcharset_pref);

			if(CS_UNKNOWN != mailcsid_pref)
			{
				intlmime_update_csidmap(CS_KOI8_R, mailcsid_pref);
				intlmime_update_csidmap(CS_8859_5, mailcsid_pref);
				intlmime_update_csidmap(CS_MAC_CYRILLIC, mailcsid_pref);
				intlmime_update_csidmap(CS_CP_1251, mailcsid_pref);
			}
		}
		if( PREF_NOERROR == PREF_CopyCharPref(pref_mailcharset_korean, 
								&mailcharset_pref))
		{
			mailcsid_pref = INTL_CharSetNameToID(mailcharset_pref);
			XP_FREE(mailcharset_pref);

			if(CS_UNKNOWN != mailcsid_pref)
			{
				intlmime_update_csidmap(CS_KSC_8BIT, mailcsid_pref);
			}
		}
	}

	initialized = TRUE;
}

PRIVATE int16
intlmime_map_csid(cs_csid_map_t *mapp, int16	csid_key)
{
	for( ; mapp->csid_key != 0 ; mapp++) 
	{
		if (csid_key == mapp->csid_key) 
			return(mapp->csid_target);
	}
	return(csid_key);			/* causes no conversion */
}

PRIVATE int16
intlmime_get_outgoing_mime_csid(int16	win_csid)
{
 	win_csid &= ~CS_AUTO;
	intlmime_init_csidmap();
	return intlmime_map_csid(cs_mime_csidmap_tbl, win_csid);
}

PRIVATE char *intlmime_decode_qp(char *in)
{
	int i = 0, length;
	char token[3];
	char *out, *dest = 0;

	out = dest = (char *)XP_ALLOC(strlen(in)+1);
	if (dest == NULL)
		return NULL;
	memset(out, 0, strlen(in)+1);
	length = strlen(in);
  	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.
			 */
			strncpy (out, token, i);
		  	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.  Forget 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.  Forget 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;
		}
	}
	/* take care of special underscore case */
	for (out = dest; *out; out++)
		if (*out == '_') 	*out = ' ';
	return dest;
}

#endif  /* MOZ_MAIL_COMPOSE || MOZ_MAIL_NEWS */

PRIVATE int intlmime_decode_base64 (const char *in, char *out)
{
  /* reads 4, writes 3. */
  int j;
  unsigned long num = 0;

  for (j = 0; j < 4; j++)
	{
	  unsigned char c;
	  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;
	  else
	  {
		/*	abort ();	*/
		strcpy(out, in);	/* I hate abort */
		return 0;
	  }
	  num = (num << 6) | c;
	}

  *out++ = (unsigned char) (num >> 16);
  *out++ = (unsigned char) ((num >> 8) & 0xFF);
  *out++ = (unsigned char) (num & 0xFF);
  return 1;
}

PRIVATE char *intlmime_decode_base64_buf(char *subject)
{
	char *output = 0;
	char *pSrc, *pDest ;
	int i ;

	StrAllocCopy(output, subject); /* Assume converted text are always less than source text */

	pSrc = subject;
	pDest = output ;
	for (i = strlen(subject); i > 3; i -= 4)
	{
		if (intlmime_decode_base64(pSrc, pDest) == 0)
		{
			pSrc += 4;
			pDest += 4;
		}
		else
		{
			pSrc += 4;
			pDest += 3;
		}
	}
	
	*pDest = '\0';
	return output;
}

#if defined(MOZ_MAIL_COMPOSE) || defined(MOZ_MAIL_NEWS)

PRIVATE char *intlmime_convert_but_no_decode(const char *header, int16 mailcsid, int16 wincsid)
{
	char* tmpbuf = NULL, *convbuf = NULL;
    CCCDataObject	obj;
	CCCFunc cvtfunc;
	/* Copy buf to tmpbuf, this guarantee the convresion won't overwrite the origional buffer and  */
	/* It will always return something it any conversion occcur */
	StrAllocCopy(tmpbuf, header);
	
	if(tmpbuf == NULL)
		return NULL;
	
    obj = INTL_CreateCharCodeConverter();
	if (obj == NULL)
		return NULL;
	INTL_GetCharCodeConverter(mailcsid, wincsid, obj);
	convbuf = NULL;
	cvtfunc = INTL_GetCCCCvtfunc(obj);
	if (cvtfunc)
		convbuf = (char*)cvtfunc(obj, (unsigned char*)tmpbuf, (int32)XP_STRLEN((char*)tmpbuf));
	XP_FREE(obj);
	
	/*	if the conversion which use the origional buffer 
		them we return the tmpbuf */
	if(convbuf == NULL)	
		return tmpbuf;

	/*  if the conversion return a different buffer, we free the
		origional one and return the one return from conversion */
	if(convbuf != tmpbuf)
		XP_FREE(tmpbuf);
	return convbuf;
}
/* 
	intlmime_is_hz: it is CS_HZ 
*/
PRIVATE XP_Bool intlmime_is_hz(const char *header)
{
	return (XP_STRSTR(header, "~{") ? TRUE : FALSE);
}
/* 
	intlmime_is_iso_2022_xxx: it is statefule encoding with esc 
*/
PRIVATE XP_Bool intlmime_is_iso_2022_xxx(const char *header, int16 mailcsid)
{
	return (((mailcsid & STATEFUL) && (XP_STRCHR(header, '\033'))) ? TRUE : FALSE);
}
/* 
	intlmime_is_mime_part2_header: 
*/
PRIVATE XP_Bool intlmime_is_mime_part2_header(const char *header)
{
	return ((
		 	 XP_STRSTR(header, "=?") &&
			 ( 
			  XP_STRSTR(header, "?q?")  ||
			  XP_STRSTR(header, "?Q?")  ||
			  XP_STRSTR(header, "?b?")  ||
			  XP_STRSTR(header, "?B?")  
			 )
		    ) ? TRUE : FALSE );	
}

extern char *strip_continuations(char *original);
extern unsigned char *XP_WordWrapWithPrefix(int charset, unsigned char *str,
											int maxColumn, int checkQuoting,
											const char *prefix, int addCRLF);

PRIVATE
char *intl_decode_mime_part2_str(const char *header, int wincsid, XP_Bool dontConvert)
{
	char *work_buf = NULL;
	char *output_p = NULL;
	char *retbuff = NULL;
	char *p, *q, *decoded_text;
	char *begin; /* tracking pointer for where we are in the work buffer */
	int16	 csid = 0;
	int  ret = 0;


	StrAllocCopy(work_buf, header);  /* temporary buffer */
	StrAllocCopy(retbuff, header);

	if (work_buf == NULL || retbuff == NULL)
		return NULL;

	output_p = retbuff;
	begin = work_buf;

	while (*begin != '\0')
	{		
		char * output_text;

		/*		GetCharset();		*/
		p = strstr(begin, "=?");
		if (p == NULL)
			break;				/* exit the loop because the rest are not encoded */
		*p = '\0';
		/* skip strings don't need conversion */
		strncpy(output_p, begin, p - begin);
		output_p += p - begin;

		p += 2;
		begin = p;

		q = strchr(p, '?');  /* Get charset info */
		if (q == NULL)
			break;				/* exit the loop because there are no charset info */
		*q++ = '\0';
		csid = INTL_CharSetNameToID(p);
		if (csid == CS_UNKNOWN)
		{
			/*
			 * @@@ may want to use context's default doc_csid in the future
			 */
			break;				/* exit the loop because we don't know the charset */
		}

		if (*(q+1) == '?' &&
		    (*q == 'Q' || *q == 'q' || *q == 'B' || *q == 'b'))
		{			
			p = strstr(q+2, "?=");
			if(p != NULL)
				*p = '\0';
			if(*q == 'Q' || *q == 'q')
				decoded_text = intlmime_decode_qp(q+2);
			else
				decoded_text = intlmime_decode_base64_buf(q+2);
		}
		else 
			break;				/* exit the loop because we don't know the encoding method */

		begin = (p != NULL) ? p + 2 : (q + strlen(q));

		if (decoded_text == NULL) 
			break;				/* exit the loop because we have problem to decode */

		ret = 1;
		if ((! dontConvert) && (csid != wincsid))
			output_text = (char *)intlmime_convert_but_no_decode(decoded_text, csid, (int16)wincsid);
		else
			output_text = (char *)decoded_text;

		XP_ASSERT(output_text != NULL);
		XP_STRCPY(output_p, (char *)output_text);
		output_p += strlen(output_text);

		if (output_text != decoded_text)
			XP_FREE(output_text);
		XP_FREE(decoded_text);
	}
	XP_STRCPY(output_p, (char *)begin);	/* put the tail back  */

	if (work_buf)
		XP_FREE(work_buf);

	if (ret)
	{
		return retbuff;
	}
	else
	{
		XP_FREE(retbuff);
		return NULL;  /* null means no conversion */
	}
}

/* PRIVATE */
/* char* intl_strip_crlftab(char* str) */
/* { */
/* 	char* out, *in; */
/* 	if(str) { */
/* 		for(out = in = str; *in != NULL; in++) */
/* 			if((*in != CR) && (*in != LF) && (*in != TAB)) */
/* 				*out++ = *in; */
/* 		*out = NULL; */
/* 	} */
/* 	return str; */
/* } */


/* 
 IntlDecodeMimePartIIStr
  This functions converts mail charset to Window charset for subject
	Syntax:   =?MimeCharset?[B|Q]?text?=
		MimeCharset = ISO-2022-JP
		              ISO-8859-1
					  ISO-8859-?
					  ....
		?B? :  Base64 Encoding          (used for multibyte encoding)
		?Q? :  Quote Printable Encoding (used for single byte encoding)

  	eg. for Japanese mail, it looks like
    	 =?ISO-2022-JP?B?   ........   ?=
*/
/* IMPORTANT NOTE: */
/* Return NULL in this interface only mean ther are no conversion */
/* It does not mean the conversion is store in the origional buffer */
/* and the length is not change. This is differ from other conversion routine */


PRIVATE
char *intl_DecodeMimePartIIStr(const char *header, int16 wincsid, XP_Bool dontConvert)
{
	int16 mailcsid = INTL_DefaultMailCharSetID(wincsid);
	XP_Bool no8bitdata = TRUE;

	if (header == 0 || *header == '\0')
		return NULL;
	if (wincsid == 0) /* Use global if undefined */
		wincsid = INTL_DefaultWinCharSetID(0);

	no8bitdata = intlmime_only_ascii_str(header);

	/*	Start Special Case Handling		*/
	if(! dontConvert) 
	{
		/* Need to do conversion in here if necessary */
		if(! no8bitdata)
		{
			/*	Special Case 1: 8 Bit */
			/* then we assume it is not mime part 2  encoding, we convert from the internet encoding to wincsid */
			if(wincsid == CS_UTF8)
				return strip_continuations(intlmime_convert_but_no_decode(header, CS_UTF8, (int16)wincsid));
			else
				return strip_continuations(intlmime_convert_but_no_decode(header, mailcsid, (int16)wincsid));
		}
		else
		{
			/* 7bit- It could be MIME Part 2 Header */
			if ((wincsid == CS_GB_8BIT) && (intlmime_is_hz(header)) )
			{  
				/*	Special Case 2: HZ */
				/*	for subject list pane, if it's GB, we only do HZ conversion */
				return strip_continuations(intlmime_convert_but_no_decode(header, CS_GB_8BIT, CS_GB_8BIT));
			}
			else if((wincsid == CS_UTF8) && 
					(! intlmime_is_mime_part2_header(header)))
			{	
				/*	Special Case 3: UTF8, no mime2 */
				return strip_continuations(intlmime_convert_but_no_decode(header, CS_UTF8, CS_UTF8));
			} 
			else if(intlmime_is_iso_2022_xxx(header, mailcsid) && 
					(! intlmime_is_mime_part2_header(header)))
			{
				return strip_continuations(intlmime_convert_but_no_decode(header, mailcsid, wincsid));
			}
		}
	}
	/* Handle only Mime Part 2 after this point */
	return strip_continuations(intl_decode_mime_part2_str(header, wincsid, dontConvert));
}
#endif /* MOZ_MAIL_COMPOSE || MOZ_MAIL_NEWS */



PRIVATE char basis_64[] =
   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

PRIVATE int intlmime_encode_base64 (const char *in, char *out)
{
	unsigned char c1, c2, c3;
	c1 = in[0];
	c2 = in[1];
	c3 = in[2];

    *out++ = basis_64[c1>>2];
    *out++ = basis_64[((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4)];

    *out++ = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >>6)];
    *out++ = basis_64[c3 & 0x3F];
	return 1;
}

PRIVATE char *intlmime_encode_base64_buf(char *subject, size_t size)
{
	char *output = 0;
	char *pSrc, *pDest ;
	int i ;

	output = (char *)XP_ALLOC(size * 4 / 3 + 4);
	if (output == NULL)
		return NULL;

	pSrc = subject;
	pDest = output ;
	for (i = size; i >= 3; i -= 3)
	{
		if (intlmime_encode_base64(pSrc, pDest) == 0) /* error */
		{
			pSrc += 3;
			pDest += 3;
		}
		else
		{
			pSrc += 3;
			pDest += 4;
		}
	}
	/* in case (i % 3 ) == 1 or 2 */
	if(i > 0)
	{
		char in[3];
		int j;
		in[0] = in[1] = in[2] ='\0';
		for(j=0;j<i;j++)
			in[j] = *pSrc++;
		intlmime_encode_base64(in, pDest);
		for(j=i+1;j<4;j++)
			pDest[j] = '=';
		pDest += 4;
	}
	*pDest = '\0';
	return output;
}

#if defined(MOZ_MAIL_COMPOSE) || defined(MOZ_MAIL_NEWS)
PRIVATE char *intlmime_encode_qp_buf(char *subject)
{
	char *output = 0;
	unsigned char *p, *pDest ;
	int i, n, len ;

	if (subject == NULL || *subject == '\0')
		return NULL;
	len = strlen(subject);
	output = XP_ALLOC(len * 3 + 1);
	if (output == NULL)
		return NULL;

	p = (unsigned char*)subject;
	pDest = (unsigned char*)output ;

	for (i = 0; i < len; i++)
	{
		/* XP_IS_ALPHA(*p) || XP_IS_DIGIT(*p)) */
		if ((*p < 0x80) &&
			(((*p >= 'a') && (*p <= 'z')) ||
			 ((*p >= 'A') && (*p <= 'Z')) ||
			 ((*p >= '0') && (*p <= '9')))
		   )
			*pDest = *p;
		else
		{
			*pDest++ = '=';
			n = (*p & 0xF0) >> 4; /* high byte */
			if (n < 10)
				*pDest = '0' + n;
			else
				*pDest = 'A' + n - 10;
			pDest ++ ;

			n = *p & 0x0F;			/* low byte */
			if (n < 10)
				*pDest = '0' + n;
			else
				*pDest = 'A' + n - 10;
		}

		p ++;
		pDest ++;
	}

	*pDest = '\0';
	return output;
}

PRIVATE char *intlmime_encode_next8bitword(int wincsid, char *src)
{
	char *p;
	XP_Bool non_ascii = FALSE;
	if (src == NULL)
		return NULL;
	p = src;
	while (*p == ' ')
		p ++ ;
	while ( *p )
	{
		if ((unsigned char) *p > 0x7F)
			non_ascii = TRUE;
		if ( IS_MAIL_SEPARATOR(p) )
		{
			break;
		}
		p = INTL_NextChar(wincsid, p);
	}

	if (non_ascii)
		return p;
	else
		return NULL;
}

/*
lock then length of input buffer, so the return value is less than iThreshold bytes
*/
PRIVATE int ResetLen( int iThreshold, const char* buffer, int16 wincsid )
{
	const char *begin, *end, *tmp;

	tmp = begin = end = buffer;
	XP_ASSERT( iThreshold > 1 );
	XP_ASSERT( buffer != NULL );
	while( ( end - begin ) <= iThreshold ){
		tmp = end;
		if (!(*end))
			break;
		end = INTL_NextChar( wincsid, (char*)end );
	}

	XP_ASSERT( tmp > begin );
	return tmp - begin;
}

PRIVATE char * intlmime_encode_mail_address(int wincsid, const char *src, CCCDataObject obj,
											int maxLineLen)
{
	char *begin, *end;
	char *retbuf = NULL, *srcbuf = NULL;
	char sep = '\0';
	char *sep_p = NULL;
	char *name;
	int  retbufsize;
	int line_len = 0;
	int srclen;
	int default_iThreshold;
	int iThreshold;		/* how many bytes we can convert from the src */
	int iEffectLen;		/* the maximum length we can convert from the src */
	XP_Bool bChop = FALSE;
	XP_Bool is_being_used_in_email_summary_file = (maxLineLen > 120);
	CCCFunc cvtfunc = NULL;

	if (obj)
		cvtfunc = INTL_GetCCCCvtfunc(obj);

	if (src == NULL || *src == '\0')
		return NULL;
	/* make a copy, so don't need touch original buffer */
	StrAllocCopy(srcbuf, src);
	if (srcbuf == NULL)
		return NULL;
	begin = srcbuf;
	
	name = (char *)INTL_CsidToCharsetNamePt(intlmime_get_outgoing_mime_csid ((int16)wincsid));
	default_iThreshold = iEffectLen = ( maxLineLen - XP_STRLEN( name ) - 7 ) * 3 / 4;
	iThreshold = default_iThreshold;


	/* allocate enough buffer for conversion, this way it can avoid
	   do another memory allocation which is expensive
	 */
	   
	retbufsize = XP_STRLEN(srcbuf) * 3 + MAX_CSNAME + 8;
	retbuf =  XP_ALLOC(retbufsize);
	if (retbuf == NULL)  /* Give up if not enough memory */
	{
		XP_FREE(srcbuf);
		return NULL;
	}

	*retbuf = '\0';

	srclen = XP_STRLEN(srcbuf);
	while (begin < (srcbuf + srclen))
	{	/* get block of data between commas */
		char *p, *q;
		char *buf1, *buf2;
		int len, newsize, convlen, retbuflen;
		XP_Bool non_ascii;

		retbuflen = XP_STRLEN(retbuf);
		end = NULL;
		if (is_being_used_in_email_summary_file) {
		} else {
		/* scan for separator, conversion happens on 8bit
		   word between separators
		 */
		if (IS_MAIL_SEPARATOR(begin))
		{   /*  skip white spaces and separator */
			q = begin;
			while (	IS_MAIL_SEPARATOR(q) )
				q ++ ;
			sep = *(q - 1);
			sep_p = (q - 1);
			*(q - 1) = '\0';
			end = q - 1;
		}
		else
		{
			sep = '\0';
			/* scan for next separator */
			non_ascii = FALSE;
			for (q = begin; *q;)
			{
				if ((unsigned char) *q > 0x7F)
					non_ascii = TRUE;
				if ( IS_MAIL_SEPARATOR(q) )
				{
					if ((*q == ' ') && (non_ascii == TRUE))
					{
						while ((p = intlmime_encode_next8bitword(wincsid, q)) != NULL)
						{
							if (p == NULL)
								break;
							q = p;
							if (*p != ' ')
								break;
						}
					}
					sep = *q;
					sep_p = q;
					*q = '\0';
					end = q;
					break;
				}
				q = INTL_NextChar(wincsid, q);
			}
		}
		}

		/* get the to_be_converted_buffer's len */
		len = XP_STRLEN(begin);

		if ( !intlmime_only_ascii_str(begin) )
		{
			if (obj && cvtfunc)
			{
				/*
				   	the 30 lenght is calculated as follows (I think)
					total:             30 = 7 + 11 + 8 + 4
					--------------------------------------
					Mime Part II tags: 7  = "=?...?B?...?="
					Charset name:      11 = "iso-2022-jp"
					JIS excape seq.    8  = "<ESC>$B" + "<ESC>(B" * 4/3
					space for one char 4  = 2 * 4/3 rounded up to nearest 4
					Brian Stell 10/97
				*/
				if( ( maxLineLen - line_len < 30 ) || bChop ){
					/* chop first, then continue */
					buf1 = retbuf + retbuflen; 
	  				*buf1++ = CR;	*buf1++ = LF;	*buf1++ = '\t';
					line_len = 0;
					retbuflen += 3;
					*buf1 = '\0';
					bChop = FALSE;
					iThreshold = default_iThreshold;
				}
				/*	iEffectLen - the max byte-string length of JIS ( converted form S-JIS )
					name - such as "iso-2022-jp", the encoding name, MUST be shorter than 23 bytes
					7    - is the "=?:?:?="	*/
				iEffectLen = ( maxLineLen - line_len - XP_STRLEN( name ) - 7 ) * 3 / 4;
				while( TRUE ){
					int iBufLen;	/* converted buffer's length, not BASE64 */
					if( len > iThreshold )
						len = ResetLen( iThreshold, begin, (int16)wincsid );

					if ( iThreshold <= 1 )
					{
						/* Certain trashed mailboxes were causing an
						** infinite loop at this point, so we need a way of
						** getting out of trouble.
						**
						** BEFORE:    iThreshold was becoming 1, then 0, and
						**            we were looping indefinitely.
						** AFTER:     Now, first there will be
						**            an assert in the previous call to ResetLen,
						**            then we'll do that again on the repeat pass,
						**            then we'll exit more or less gracefully.
						**    - bug #83204, an oldie but goodie.
						**    - jrm 98/03/25
						*/
						return NULL;
					}
					buf1 = (char *) cvtfunc(obj, (unsigned char *)begin, len);
					iBufLen = XP_STRLEN( buf1 );
					XP_ASSERT( iBufLen > 0 );

					/* recal iThreshold each time based on last experience */
					iThreshold = len * iEffectLen / iBufLen;
					if( iBufLen > iEffectLen ){
						/* the converted buffer is too large, we have to
							1. free the buffer;
							2. redo again based on the new iThreshold
						*/
						bChop = TRUE;		/* append CRLFTAB */
						if (buf1 && (buf1 != begin)){
							XP_FREE(buf1);
							buf1 = NULL;
						}
					} else {
						end = begin + len - 1;
						break;
					}
				}
				if (bChop && (NULL!=sep_p)) {
					*sep_p = sep;	/* we are length limited so we do not need this */
					sep = '\0';		/* artifical terminator. So, restore the original character */
					sep_p = NULL;
				}

				if (!buf1)
				{
					XP_FREE(srcbuf);
					XP_FREE(retbuf);
					return NULL;
				}
			}
			else
			{
				buf1 = XP_ALLOC(len + 1);
				if (!buf1)
				{
					XP_FREE(srcbuf);
					XP_FREE(retbuf);
					return NULL;
				}
				XP_MEMCPY(buf1, begin, len);
				*(buf1 + len) = '\0';
			}

			if (wincsid & MULTIBYTE)
			{
				/* converts to Base64 Encoding */
				buf2 = (char *)intlmime_encode_base64_buf(buf1, strlen(buf1));
			}
			else
			{
				/* Converts to Quote Printable Encoding */
				buf2 = (char *)intlmime_encode_qp_buf(buf1);
			}


			if (buf1 && (buf1 != begin))
				XP_FREE(buf1);

			if (buf2 == NULL) /* QUIT if memory allocation failed */
			{
				XP_FREE(srcbuf);
				XP_FREE(retbuf);
				return NULL;
			}

			/* realloc memory for retbuff if necessary, 
			   7: =?...?B?..?=, 3: CR LF TAB */
			convlen = XP_STRLEN(buf2) + XP_STRLEN(name) + 7;
			newsize = convlen + retbuflen + 3 + 2;  /* 2:SEP '\0', 3:CRLFTAB */

			if (newsize > retbufsize)
			{
				char *tempbuf;
				tempbuf = XP_REALLOC(retbuf, newsize);
				if (tempbuf == NULL)  /* QUIT, if not enough memory left */
				{
					XP_FREE(buf2);
					XP_FREE(srcbuf);
					XP_FREE(retbuf);
					return NULL;
				}
				retbuf = tempbuf;
				retbufsize = newsize;
			}
			/* buf1 points to end of current retbuf */
			buf1 = retbuf + retbuflen; 

			if ((line_len > 10) && 
			    ((line_len + convlen) > maxLineLen))
			{
		  		*buf1++ = CR;
		  		*buf1++ = LF;
		  		*buf1++ = '\t';
				line_len = 0;
				iThreshold = default_iThreshold;
			}
			*buf1 = '\0';

			/* Add encoding tag for base62 and QP */
			XP_STRCAT(buf1, "=?");
			XP_STRCAT(buf1, name );
			if(wincsid & MULTIBYTE)
				XP_STRCAT(buf1, "?B?");
			else
				XP_STRCAT(buf1, "?Q?");
			XP_STRCAT(buf1, buf2);
			XP_STRCAT(buf1, "?=");

			line_len += convlen + 1;  /* 1: SEP */

			XP_FREE(buf2);	/* free base64 buffer */
		}
		else  /* if no 8bit data in the block */
		{
			newsize = retbuflen + len + 2 + 3; /* 2: ',''\0', 3: CRLFTAB */
			if (newsize > retbufsize)
			{
				char *tempbuf;
				tempbuf = XP_REALLOC(retbuf, newsize);
				if (tempbuf == NULL)
				{
					XP_FREE(srcbuf);
					XP_FREE(retbuf);
					return NULL;
				}
				retbuf = tempbuf;
				retbufsize = newsize;
			}
			buf1 = retbuf + retbuflen;

			if ((line_len > 10) && 
			    ((line_len + len) > maxLineLen))
			{
		  		*buf1++ = CR;
		  		*buf1++ = LF;
		  		*buf1++ = '\t';
				line_len = 0;
				iThreshold = default_iThreshold;
			}
			/* copy buffer from begin to buf1 stripping CRLFTAB */
			for (p = begin; *p; p++)
			{
				if (*p == CR || *p == LF || *p == TAB)
					len --;
				else
					*buf1++ = *p;
			}
			*buf1 = '\0';
			line_len += len + 1;  /* 1: SEP */
		}

		buf1 = buf1 + XP_STRLEN(buf1);
		if (sep == CR || sep == LF || sep == TAB) /* strip CR,LF,TAB */
			*buf1 = '\0';
		else
		{
			*buf1 = sep;
			*(buf1+1) = '\0';
		}

		if (end == NULL)
			break;
		begin = end + 1;
	}
	if (srcbuf)
		XP_FREE(srcbuf);
	return retbuf;
}

/*
	Latin1, latin2:
	   Source --> Quote Printable --> Encoding Info
	Japanese:
	   EUC,JIS,SJIS --> JIS --> Base64 --> Encoding Info
	Others:
	   No conversion  
    flag:   0:   8bit on
	        1:   mime_use_quoted_printable_p
	return:  NULL  if no conversion occured

*/

PRIVATE
char *intl_EncodeMimePartIIStr(char *subject, int16 wincsid, XP_Bool bUseMime, int maxLineLen)
{
	int iSrcLen;
	unsigned char *buf  = NULL;	/* Initial to NULL */
	int16 mail_csid;
   	CCCDataObject	obj = NULL;
	char  *name;
	CCCFunc cvtfunc = NULL;

	if (subject == NULL || *subject == '\0')
		return NULL;

	iSrcLen = XP_STRLEN(subject);
	if (wincsid == 0)
		wincsid = INTL_DefaultWinCharSetID(0) ;

	mail_csid = intlmime_get_outgoing_mime_csid ((int16)wincsid);
	name = (char *)INTL_CsidToCharsetNamePt(mail_csid);
	
	/* check to see if subject are all ascii or not */
	if(intlmime_only_ascii_str(subject))
		return (char *) XP_WordWrapWithPrefix(mail_csid, (unsigned char *)
											  subject, maxLineLen, 0, " ", 1);
		
	if (mail_csid != wincsid)
	{
   		obj = INTL_CreateCharCodeConverter();
		if (obj == NULL)
			return 0;
		/* setup converter from wincsid --> mail_csid */
		INTL_GetCharCodeConverter((int16)wincsid, mail_csid, obj) ;
		cvtfunc = INTL_GetCCCCvtfunc(obj);
	}
	/* Erik said in the case of STATEFUL mail encoding, we should FORCE it to use */
	/* MIME Part2 to get ride of ESC in To: and CC: field, which may introduce more trouble */
	if((bUseMime) || (mail_csid & STATEFUL))/* call intlmime_encode_mail_address */
	{
		buf = (unsigned char *)intlmime_encode_mail_address(wincsid, subject, obj, maxLineLen);
		if(buf == (unsigned char*)subject)	/* no encoding, set return value to NULL */
			buf =  NULL;
	}
	else
	{	/* 8bit, just do conversion if necessary */
		/* In this case, since the conversion routine may reuse the origional buffer */
		/* We better allocate one first- We don't want to reuse the origional buffer */
		
		if ((mail_csid != wincsid) && (cvtfunc))
		{
			char* newbuf = NULL;
			/* Copy buf to newbuf */
			StrAllocCopy(newbuf, subject);
			if(newbuf != NULL)
			{
				buf = (unsigned char *)cvtfunc(obj, (unsigned char*)newbuf, iSrcLen);
				if(buf != (unsigned char*)newbuf)
					XP_FREE(newbuf);
				/* time for wrapping long line */
				if (buf)
				{
					newbuf = (char*) buf;
					buf = XP_WordWrapWithPrefix(mail_csid, (unsigned char *)
												newbuf, maxLineLen, 0, " ", 1);

					if (buf != (unsigned char*) newbuf)
						XP_FREE(newbuf);
				}
			}
		}
	}
	if (obj)   
		XP_FREE(obj);
	return (char*)buf;
	
	/* IMPORTANT NOTE: */
	/* Return NULL in this interface only mean ther are no conversion */
	/* It does not mean the conversion is store in the origional buffer */
	/* and the length is not change. This is differ from other conversion routine */
}
#endif /* MOZ_MAIL_COMPOSE || MOZ_MAIL_NEWS */

#ifdef MOZ_MAIL_NEWS
#if 0
PUBLIC XP_Bool INTL_FindMimePartIIStr(int16 csid, XP_Bool searchcasesensitive, const char *mimepart2str,const char *s2)
{
	XP_Bool onlyAscii;
	char *ret = NULL;
	char *s1 = (char*)mimepart2str;
	char *conv;
	if((s2 == NULL) || (*s2 == '\0'))	/* if search for NULL string, return TRUE */
		return TRUE;
	if((s1 == NULL) || (*s1 == '\0'))	/* if string is NULL, return FALSE */
		return FALSE;
	
	conv= IntlDecodeMimePartIIStr(mimepart2str, csid, FALSE);
	if(conv)
		s1 = conv;
	onlyAscii = intlmime_only_ascii_str(s1) && intlmime_only_ascii_str(s2);
	if(onlyAscii)	/* for performance reason, let's call the ANSI C routine for ascii only case */
	{
		if(searchcasesensitive)
			ret= strstr( s1, s2);
		else
			ret= strcasestr(s1, s2);
	}
	else
	{
		if(searchcasesensitive)
			ret= INTL_Strstr(csid, s1, s2);
		else
			ret= INTL_Strcasestr(csid, s1, s2);
	}
	if(conv != mimepart2str)
		XP_FREE(conv);
	return (ret != NULL);		/* return TRUE if it find something */
}
#endif
/* 
	NNTP XPAT I18N Support
		INTL_FormatNNTPXPATInNonRFC1522Format and INTL_FormatNNTPXPATInRFC1522Format 
		return the a new string to the caller 
		that could be send to NNTP server for Mail Header Search (use XPAT)
		The caller must free the return string.		
*/

/* This function use the same buffer it pass in, it strip the leading and trialling ISO-2022 ESC */
PRIVATE void intl_strip_leading_and_trial_iso_2022_esc (char* iso_2022_str);
PRIVATE char *intl_xpat_escape ( char *str);

#define ISO_2022_I_CODE(c)	((0x20 <= (c)) && ((c) <= 0x2F))
#define ISO_2022_F_CODE(c)	((0x30 <= (c)) && ((c) <= 0x7E))

PRIVATE void intl_strip_leading_and_trial_iso_2022_esc(char* iso_2022_str)
{
	char* inp = iso_2022_str;
	char* outp = iso_2022_str;
	char* lastescp = NULL;
	
	/* strip leading Escape */
	if(ESC == *inp)	
	{
		for(inp++ ;((*inp) && (ISO_2022_I_CODE(*inp))); /* void */)
			inp++;	/* Skip I Code */
		if(ISO_2022_F_CODE(*inp))	/* Skip F Code */
			inp++;
	}
		
	for( ; (0 != *inp); inp++, outp++)	/* copy data including esc */
	{
		*outp = *inp;
		if(ESC == *outp)	/* remember the last position of esc */
			lastescp = outp;	
	}
	*outp = '\0';	/* NULL terminate */
	
	/* strip trialling Escape if necessary */
	if(lastescp)
	{		
		char* esc_p;	
		for(esc_p = lastescp + 1; ((*esc_p) && (ISO_2022_I_CODE(*esc_p))); /* void */ )
			esc_p++;	/* Skip I Code */
			
		if(ISO_2022_F_CODE(*esc_p))	/* Skip F Code */
			esc_p++;
			
		if('\0' == *esc_p)	/* if it point to our NULL terminate, it is the trialling esp, we take it out */
		{					/* otherwise, it is the esc in the middle, ignore it */
			*lastescp = '\0';
		}
	}
}
#define BETWEEN_A_Z(c)	(('A' <= (c)) && ((c) <= 'Z'))
#define BETWEEN_a_z(c)	(('a' <= (c)) && ((c) <= 'z'))
#define BETWEEN_0_9(c)	(('0' <= (c)) && ((c) <= '9'))
/* 
	Escape Everything except 0-9 A-Z a-z 
   "Common NNTP Extensions" and wildmat(3) does not state clearly what NEED TO BE Escape.
   I look like the * ? [ \ need to be escape , 
   But by trial and error I also find out ^ and $ need to be escape. 
   That's why I just do this ESCAPE MORE THAN WE NEEDED untill we figure out what relly NEED TO BE Escaped    
*/
#define XPAT_NEED_ESCAPE(c)		(! (BETWEEN_A_Z(c) || BETWEEN_a_z(c) || BETWEEN_0_9(c))) 
PRIVATE char *intl_xpat_escape( char *str)
{
	char *result = NULL;
	/* max escaped length is one extra characters for every character in the str. */
	char *scratchBuf = (char*) XP_ALLOC (2*XP_STRLEN(str) + 1);
	if(scratchBuf)
	{
		char *scratchPtr = scratchBuf;
		char ch;
		while ('\0' != (ch = *str++))
		{
			if (XPAT_NEED_ESCAPE(ch))
				*scratchPtr++ = '\\';
			*scratchPtr++ = ch;
		}
		*scratchPtr = '\0';
		result = XP_STRDUP (scratchBuf); /* realloc down to smaller size */
		XP_FREE (scratchBuf);
	}
	return result;
}


/*
	INTL_FormatNNTPXPATInNonRFC1522Format
	1. Convert the data from wincsid to newscsid
	2. Strip Out leading Esc Sequence and trialing Esc Sequence
	3. Always return memory unless memory is not enough. Never have side effect on the pass-in buffer
*/

PUBLIC unsigned char* INTL_FormatNNTPXPATInNonRFC1522Format(int16 wincsid, unsigned char* searchString)
{
	char* temp = NULL;
	char* conv = NULL;
	char* xpat_escape = NULL;
	StrAllocCopy(temp, (char*) searchString);
	XP_ASSERT(temp);	/* Should only come here if Memory Not Enough */
	if(NULL == temp)
		return NULL;
		
	/* Convert text from wincsid to newscsid */
	if(NULL != (conv = (char*)INTL_ConvertLineWithoutAutoDetect(wincsid, INTL_DefaultNewsCharSetID(wincsid), (unsigned char*)temp, XP_STRLEN((char*)temp))))
		XP_FREE(temp);	/* If the conversion do use the same buffer, free the origional one */
	else 
		conv = temp;
	intl_strip_leading_and_trial_iso_2022_esc(conv);
	
	/* Do XPAT escape */
	xpat_escape = intl_xpat_escape(conv);
	if(NULL != conv)
		XP_FREE(conv);
	return (unsigned char*) xpat_escape;
}

#if 0

PUBLIC unsigned char* INTL_FormatNNTPXPATInRFC1522Format(int16 wincsid, unsigned char* searchString)
{
	/* Temp Implementation untill we really support it : Just make a duplication. */
	char* result = NULL;
	StrAllocCopy(result, (char*) searchString);
	XP_ASSERT(result);	/* Should only come here if Memory Not Enough */
	return (unsigned char*) result;
}

#endif

#endif  /* MOZ_MAIL_NEWS */
