/* --- WVHEADER.C ---------------------------------------------
 *  This file contains the code necessary to create the initial skeleton
 *  version of an article, which will be edited by the user.
 *
 *  Mark Riordan   24-JAN-1990
 * 
 *  Major overhaul by John S. Cooper  October 1994 
 */

/*
 * $Id: wvheader.c 1.44 1997/03/18 19:20:11 dumoulin Exp $
 */

#include <windows.h>
#include <windowsx.h>
#include "wvglob.h"
#include "winvn.h"
#pragma hdrstop
#include <ctype.h>
#include <time.h>
#include <stdlib.h>

#define FIX_df__20010207
#ifdef FIX_df__20010207    /* to be included at the start of the file */
#include <stdio.h>
#endif

static char *days[] =
{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static char *months[] =
{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};


BOOL AuthenticatePosting (BOOL AuthReq);


#define MAXEDITTEXT (30000L)

int	CalcReplyWidth (TypDoc * Doc, int DocType)
{
	HANDLE hBlock;
	unsigned int Offset, quoteIndSize;
	TypLineID MyLineID;
	TypBlock far *BlockPtr;
	TypLine far *LinePtr;
	char line[MAXHEADERLINE];
	int replywidth;

	replywidth = BEGINPOSTWIDTH;

	if (DocType == DOCTYPE_MAIL)
		replywidth = BEGINMAILWIDTH;

	if (DocType == DOCTYPE_FORWARD)
		replywidth = BEGINFORWARDWIDTH;

	if (DocType == DOCTYPE_CANCEL)
		replywidth = BEGINCANCELWIDTH;

	if (Doc && TopOfDoc (Doc, &BlockPtr, &LinePtr)) 
	{

		/* Skip past headers */
		while (ExtractTextLine (Doc, LinePtr, line, MAXHEADERLINE) && !IsBlankStr (line)) 
		{
			if (!NextLine (&BlockPtr, &LinePtr))
				break;
		}


		/* Copy body of reply document into the body of this article,  */
		/* prepending a ">" to each line */

		/* following line commented after a merge of MRB code (SMR) */
		/* line[0] = '>'; */

		if (DocType != DOCTYPE_CANCEL) 
		{
			if (DocType == DOCTYPE_FORWARD || DocType == DOCTYPE_SUPERSEDE) 
			{
				quoteIndSize = 0;
			} 
			else 
			{
				quoteIndSize = 1;
			}
			while (ExtractTextLine (Doc, LinePtr, line, MAXHEADERLINE)) 
			{
				replywidth = max(replywidth, strlen(line)+quoteIndSize);
				if (!NextLine (&BlockPtr, &LinePtr))
					break;
			}
		}
		UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
	}

	return replywidth;
}


/*--- function CreateComposeWnd -----------------------------------------
 *
 *    Create the window for composing the text of a posting,
 *    if it's OK to post.
 *
 *    Entry    Hwnd     Handle to parnet window
 *             Doc      points to the document to which we are posting
 *                      a followup--NULL if it's a new posting.
 *
 *    Exit     Returns the handle of the newly-created window
 *              (zero if failure).
 */
HWND CreateComposeWnd (HWND hWnd, TypDoc * Doc, int DocType)
{
	register int i;
	BOOL found;
	BOOL AuthReq;
	int x, y, height, width, actualwidth;
	char *TitlePtr, *WndClassName, *posnString;
	int maxNum,mstatus;
	WndEdit *wndEdits;
	char temp[TEMPBUFSIZE];

	if ((DocType == DOCTYPE_POSTING) || (DocType == DOCTYPE_CANCEL) || (DocType == DOCTYPE_SUPERSEDE )) 
	{
		wndEdits = WndPosts;
		maxNum = MAXPOSTWNDS;

		AuthReq = AuthReqPost;
		posnString = "PostWindowPos";
		WndClassName = "WinVnPost";
		
		if (DocType == DOCTYPE_CANCEL)
			TitlePtr = "Cancel Article";
		else if (Doc) 
		{
			if (DocType == DOCTYPE_SUPERSEDE)
				TitlePtr = "Replace Article";
			else 
				TitlePtr = "Followup Article";
		}
		else 
		{
			TitlePtr = "New Article";
		}
	}
	else 
	{
		wndEdits = WndMails;
		maxNum = MAXMAILWNDS;

		AuthReq = AuthReqMail;
		posnString = "MailWindowPos";
		WndClassName = "WinVnMail";
		if (Doc) 
		{
			if (DocType == DOCTYPE_FORWARD)
				TitlePtr = "Forward Article";
			else
				TitlePtr = "Followup Mail";
		}
		else 
		{
			TitlePtr = "New Mail Message";
		}
	}

	for (i = 0, found = FALSE; !found && i < maxNum; i++) 
	{
		if (!wndEdits[i].hWnd) 
		{
			found = TRUE;
			break;
		}
	}
	if (!found) 
	{
		WVMessageBox (hWnd, "Too many composition windows are open.\nPlease send or close one before continuing.",
		"Cannot Continue", MB_OK | MB_ICONSTOP);
	}
	else if (AuthenticatePosting (AuthReq)) 
	{
		int nScroll = ::GetSystemMetrics(SM_CXVSCROLL);
		int Border = ::GetSystemMetrics(SM_CXSIZEFRAME) * 2 ; //edit border & window border

		x = i * CompositionCharWidth;
		y = (int) (yScreen * 3 / 8) + (1 + i) * CompositionLineHeight;
		height = (int) (yScreen * 5 / 8) - (2 * CompositionLineHeight);
		
		if ((xScreen - Border*2 - nScroll) > (DEFAULTPOSTWIDTH * CompositionCharWidth))/* nScroll for scroll bar */ 
		{    
			width = (DEFAULTPOSTWIDTH * CompositionCharWidth) + SideSpace + nScroll + Border*2;
		}
		else 
		{
			width = (xScreen - nScroll - Border*2);
		}

		/* now width is max we could allow */
		/* try to make smaller if the referenced article allows */
		width = min(width, (CalcReplyWidth( Doc, DocType) * CompositionCharWidth) + SideSpace + nScroll + Border*2 );
		
		wndEdits[i].nFixedWidth	= width;
		wndEdits[i].bRestrictWidth = 1;

		wndEdits[i].ParenthWnd = hWnd;
		wndEdits[i].Doc = Doc;
		wndEdits[i].composeType = DocType;

		/* If the screen position has been saved, use that instead. */
		GetPrivateProfileString (COMPOSE, posnString, "!", temp, TEMPBUFSIZE, szAppProFile);
		if (*temp != '!') 
		{
			sscanf (temp, "%d,%d,%d,%d", &x, &y, &width, &height);
		}

		/* So we can get the "seal of approval" in the Broken Newsreader FAQ (JD 4/20/95)*/
		if ((actualwidth = ((width - nScroll - SideSpace - Border*2) / CompositionCharWidth)) > DEFAULTPOSTWIDTH) 
		{
			_snprintf(temp, TEMPBUFSIZE,
				"Your screen width (%d) is wider then the Internet \n"
				"standard of %d fixed point characters. \n"
				"This will make your post look bad on some viewers.  \n"
				"Select OK if you wish WinVN to make your \n"
				"edit window conform to the Internet standard.\n"
				"To make this a permanent change, click on \n"
				"\"Save Window Positions\" in the Window menu \n",
				actualwidth, DEFAULTPOSTWIDTH);
			mstatus = WVMessageBox (hWnd, temp, "Screen size exceeds recommend size",
			MB_OKCANCEL | MB_ICONEXCLAMATION);
			
			switch(mstatus) 
			{
				case IDOK:
					width = (DEFAULTPOSTWIDTH * CompositionCharWidth) + SideSpace + nScroll + Border*2;
					wndEdits[i].nFixedWidth	= width;
					SaveWindowPositions();
					break;

				case IDCANCEL:
					wndEdits[i].bRestrictWidth = 0;
					break;
			}
		}

		wndEdits[i].hWnd = CreateWindow (WndClassName, TitlePtr,WS_OVERLAPPEDWINDOW /* | WS_VSCROLL */ ,
											CW_USEDEFAULT,//x + (i * CompositionCharWidth),
											y + (i * CompositionLineHeight),
											width, height,
											NULL, NULL, hInst,
											(void *)&wndEdits[i]);

	}
	return (wndEdits[i].hWnd);
}


void TrimmMultiLineHeader( char *Buf )
{
	char *src, *dst;
	BOOL WhiteSpace;

	if (Buf == NULL)
		return; /* nothing to do */

	src = dst = Buf;

	WhiteSpace = FALSE;
	while (*src) 
	{
		if ((*src == '\r') || (*src == '\n')) 
		{
			while ((*src == '\r') || (*src == '\n'))
				src++;
			while ((*src == '\t') || (*src == ' '))
				src++;
			WhiteSpace = TRUE;
		} 
		else 
		{
			if (WhiteSpace) 
			{
				*dst++ = ' ';
				WhiteSpace = FALSE;
			}
			*dst++ = *src++;
		}
	}
	*dst = '\0';
}


/*--- function GetHeaderLine -------------------------------------------
 *
 *  Given a document, get a line from the header portion of that document
 *  whose prefix matches a given prefix.  Prefix = firsComposition word in line.
 *  For instance, you might call this routine to say "Get the 'Subject:'"
 *  line from this document.
 *
 *    Entry    Doc      points to the document whose header we are scanning.
 *                      The header is all the lines up to the first blank line.
 *             Prefix   is the character string which will identify the
 *                      line we are seeking.  It is the first word
 *                      (blank-delimited) in a line in the header.
 *             BufLen   is the number of bytes left in the buffer Buf.
 *
 *    Exit     Returns TRUE iff we returned a line.
 *             Buf      if line was returned, contains that line, zero-
 *                      terminated.
 */

#define myisspace(c) (((char)(c) == ' ' ) || (((char)(c) >= (char)0x09) && ((char)(c) <= (char)0x0d)))

BOOL GetHeaderLine (TypDoc * Doc, char *Prefix, char *Buf, int BufLen)
{
	TypBlock far *BlockPtr;
	TypLine far *LinePtr;
	unsigned int Offset;
	HANDLE hBlock;
	TypLineID MyLineID;
	int found = FALSE;
	int len;

	if (TopOfDoc (Doc, &BlockPtr, &LinePtr)) 
	{
		while (ExtractTextLine (Doc, LinePtr, Buf, BufLen-3)) 
		{
			/* Is this a blank line signifying the end of the header?      */
			if (IsBlankStr (Buf))
				break;

			if ( _strnicmp (Buf, Prefix, strlen (Prefix)) == 0) 
			{
				found = TRUE;

				// for continuous lines by shimomai
				while (BufLen-3 > 0 && NextLine (&BlockPtr, &LinePtr)) 
				{
					BufLen -= (len = strlen(Buf) + 2);
					Buf += len;
					if (!ExtractTextLine (Doc, LinePtr, Buf, BufLen-3) ||
						!myisspace(*Buf) || IsBlankStr (Buf)) 
					{
						break;
					}
					else 
					{
						*(Buf-2) = '\r';
						*(Buf-1) = '\n';
					}
				}
				break;
			}
			if (!NextLine (&BlockPtr, &LinePtr))
				break;
		}
		UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
	}
	return (found);
}

//if there are multiple entries of a Prefix, get the line with the
//last one
BOOL GetLastHeaderLine (TypDoc * Doc, char *Prefix, char *Buf, int BufLen)
{
	TypBlock far *BlockPtr;
	TypLine far *LinePtr;
	unsigned int Offset;
	HANDLE hBlock;
	TypLineID MyLineID;
	int found = FALSE;
	char* tBuf = (char*) GlobalAllocPtr(GHND, BufLen);


	if ( tBuf && TopOfDoc (Doc, &BlockPtr, &LinePtr)) 
	{
		while (ExtractTextLine (Doc, LinePtr, tBuf, BufLen-3)) 
		{
			/* Is this a blank line signifying the end of the header?      */
			if (IsBlankStr (tBuf))
				break;

			while( _strnicmp (tBuf, Prefix, strlen (Prefix)) == 0) 
			{
				BOOL sameLine = TRUE;

				found = TRUE;
				//start a new ref line
				strncpy(Buf, tBuf, BufLen);

				while (sameLine)
				{
			     
					if (NextLine (&BlockPtr, &LinePtr) && 
						ExtractTextLine (Doc, LinePtr, tBuf, BufLen-3) &&
						myisspace(*Buf) &&
						!IsBlankStr (Buf)
						)
					{
						int len = strlen(Buf);
						strncpy(Buf+len, tBuf, BufLen-3-len);

					}
					else
						sameLine = FALSE;
				}
				
				if (IsBlankStr (tBuf))
					break;



			}
			if (!NextLine (&BlockPtr, &LinePtr))
				break;
		}
		UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);

		GlobalFreePtr(tBuf);
	}
	return (found);
}

// some header erroneously appear multiple times in header, retrieve & combine them
// Cancel-Lock: string1
//     string2
// Cancel-Lock: string3
// returns
// Cancel-Lock: string1 \r\n  string2\r\n  string3
BOOL GetSameHeaderLines(TypDoc * Doc, char *Prefix, char *Buf, int BufLen)
{
	TypBlock far *BlockPtr;
	TypLine far *LinePtr;
	unsigned int Offset;
	HANDLE hBlock;
	TypLineID MyLineID;
	int found = FALSE;
	int pLen = 0;
	
	if (!Prefix)
		return false;

	pLen = strlen (Prefix);



	char* tBuf = (char*) GlobalAllocPtr(GHND, BufLen);
	*Buf='\0';

	if ( tBuf && TopOfDoc (Doc, &BlockPtr, &LinePtr)) 
	{
		while (ExtractTextLine (Doc, LinePtr, tBuf, BufLen-3)) 
		{
			/* Is this a blank line signifying the end of the header?      */
			if (IsBlankStr (tBuf))
				break;

			while( _strnicmp (tBuf, Prefix, pLen ) == 0) 
			{
				BOOL sameLine = TRUE;
				int len = strlen(Buf);

				found = TRUE;
				//start a new ref line
				if (!len) 
					strncpy(Buf, tBuf, BufLen);
				else
				{
					strncpy(Buf+len, tBuf+pLen, BufLen-len);
				}

				while (sameLine)
				{
			     
					if (NextLine (&BlockPtr, &LinePtr) && 
						ExtractTextLine (Doc, LinePtr, tBuf, BufLen-3) &&
						myisspace(*Buf) &&
						!IsBlankStr (Buf))
					{
						len = strlen(Buf);
						strncpy(Buf+len, tBuf, BufLen-3-len);

					}
					else
						sameLine = FALSE;
				}
				
				if (IsBlankStr (tBuf))
					break;



			}
			if (!NextLine (&BlockPtr, &LinePtr))
				break;
		}
		UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);

		GlobalFreePtr(tBuf);
	}
	return (found);
}


/*--- function NextToken -----------------------------------------------
 *
 *  Position a pointer to the next token in a string.
 *  Delimiters are space and tab.
 *
 *    Entry    cptr     points to a position in a zero-terminated string.
 *
 *    Exit     Returns TRUE iff a next token was found.
 *             cptr     points to the next token, if found--else
 *                      it is unchanged.
 */
BOOL NextToken (char **cptr)
{
	/* Skip to end of current token, if any. */
	while (**cptr != ' ' && **cptr != '\t' && **cptr)
		(*cptr)++;

	/* Skip past white space. */
	while (**cptr && (**cptr == ' ' || **cptr == '\t'))
		(*cptr)++;

	return (**cptr != '\0');
}

/*--- function AppendTextToEditBuf ----------------------------------------
 *
 *    Appends a zero-terminated text line to a buffer to be given
 *    to an Edit window.  Used in building messages to be displayed
 *    and edited by an Edit window.
 *
 *    Entry    instr    points to a text line to add.  It is terminated
 *                      by a zero byte and does not end in CR or LF.
 *             left     is the number of characters left in buf.
 *
 *    Exit     buf      contains the line, terminated by CR and LF.
 *                      buf now points to the next available byte.
 *             left     has been decremented as appropriate.
 */
void AppendTextToEditBuf (char *instr, char far ** buf, long int *left)
{
	while (--(*left) > 3 && *instr) 
	{
		*((*buf)++) = *(instr++);
	}
	if (--(*left) > 0)
		*((*buf)++) = '\r';

	if (--(*left) > 0)
		*((*buf)++) = '\n';

	if ((*left) > 0)
		**buf = '\0';
}

/*--- function ParseAddress --------------------------
 *   Scan the
 *       address field (null terminated ascii string)
 *  representing any email address and optional
 *       name
 *
 *   and extract
 *   addressout which is the email address
 *   nameout    which is the name (enclosed in parenthises)
 *
 */
void ParseAddress(const char *headerline, char *addressout, long addressoutlen, char *nameout, long nameoutlen)
{
	const char *ptra = headerline;
	char *ptrb, *ptrc;
	char *iptr, *optr;
	int lastspace, escaped;
	long int adlen = addressoutlen - 1, nmlen = nameoutlen - 1;
	long int l;

	*nameout = *addressout = '\0';

	// Note parsing not strictly correct as anything in quotes '"'
	// overrides the meaning of lexical constructs '<' and '('

	// Seems this works and is robust but is now in the main
	// group list read and should be extremely efficient
	// suggest a single pass 'stream' parse more appropriate
	// but is more work

	if (ptrb = (char*) strchr (headerline, '<')) // address enclosed in brackets
	{          

		l = min (ptrb - ptra - 1, nmlen);
		if (l < 0)
			l = 0;         /* this was killing print??? */
		if (l > 0) // otherwise strncpy crashes
		{    
			
			if (*(ptra + l) != ' ') 
				l++;  //dumoulin 3/8/97

			nmlen -= l;
			strncpy (nameout, ptra, (int) l);
			nameout[l] = 0;
		}
		if (ptrc = (char*) strchr (headerline, '>')) 
		{
			if (nmlen > 0) 
			{
				strncpy (nameout + l, ptrc + 1, (int) nmlen);
				nameout[nameoutlen - 1] = 0;
			}
			l = min (ptrc - ptrb - 1, adlen);
		}
		else 
		{
			l = adlen;
		}
		if (l > 0) 
		{
			strncpy (addressout, ptrb + 1, (int) l);
			addressout[l] = 0;
		}

	}
	else if (ptrb = (char*)strchr (headerline, '('))   // name enclosed in braces
	{
		l = min (ptrb - ptra - 1, adlen);
		if (l < 0) 
			l = 0;                                       // JD 8/3/96
		if (l > 0) 
		{
		
			if (*(ptra + 1) != ' ') 
				l++;    // JD 3/8/97
			adlen -= l;
			strncpy (addressout, ptra, (int) l);
			addressout[l] = 0;
		}
		if (ptrc = (char*)strchr (headerline, ')')) 
		{
			if ((adlen > 0) && *(ptrc+1))
			{
				strncpy (addressout + l, ptrc + 1, (int) adlen);
				addressout[addressoutlen - 1] = 0;
			}
			l = min (ptrc - ptrb - 1, nmlen);
		}
		else 
		{
			l = nmlen;
		}
		if (l > 0) 
		{
			strncpy (nameout, ptrb + 1, (int) l);
			nameout[l] = 0;
		}

	}
	else // just an address
	{                                               
		strncpy (addressout, ptra, (int) adlen);
		addressout[addressoutlen - 1] = 0;
	}

	/* some brain dead people using a single byte escape using backslash to encode a nickname */
	/* (Richard \"Watergate\" Nixon) is a example, however this is supported by RFC :-( */

	// Clean Name   - remove multiple space and quotes and parens
	lastspace = TRUE;
	escaped = FALSE;
	
	for (iptr = optr = nameout; *iptr; iptr++) 
	{
		switch (*iptr) 
		{
			case '\\':
				if (escaped) 
				{
					*optr++ = *iptr;
					lastspace = FALSE;
					escaped = FALSE;
				} 
				else 
				{
					escaped  = TRUE;
				}
				break;
			case ' ':
				if (!lastspace)
					*optr++ = *iptr;
				lastspace = TRUE;
				escaped = FALSE;
				break;
			case '"':
			case '(':
			case ')':
				if (escaped) 
				{
					*optr++ = *iptr;
					lastspace = FALSE;
					escaped = FALSE;
				}
				break;
			default:
				if ((unsigned char)*iptr > 0x20 || isleadbyte(*iptr)) //no control chars
				{
					*optr++ = *iptr;
					lastspace = FALSE;
					escaped = FALSE;
				}
				break;
		}
	}
	*optr = 0;

	// Clean Address
	for (iptr = optr = addressout; *iptr; iptr++) 
	{
		switch (*iptr) 
		{
			case ' ':
			case '(':
			case ')':
				break;
			default:
				if ((unsigned char)*iptr > 0x20 || isleadbyte(*iptr)) //no control chars, or space //MDS 2010-03-31
					*optr++ = *iptr;
					

		}
	}
	*optr = 0;

	if (*nameout == 0) // if no name default to address
	{                  
		strncpy (nameout, addressout, (int) nmlen);
		addressout[nameoutlen - 1] = 0;
	}

}

/*------------------------------------------------------------------------
 * Getxxx functions
 * Fill a buffer with appropriate string for use in header
 * possibly based on a refDoc (doc to which we are replying/following-up)
 * jsc 10/3/94
 */
BOOL GetToAddress (char *buf, int len, TypDoc * refDoc, HWND hWnd)
{
	char headerLine[MAXHEADERLINE];
	char *ptr = headerLine;
	BOOL gotwho;
	char replyAddress[MAXHEADERLINE], replyName[MAXHEADERLINE];

	*buf = '\0';
	if (refDoc) 
	{
		if (GetHeaderLine (refDoc, "From:", headerLine, MAXHEADERLINE)) 
		{
			NextToken (&ptr);
			ParseAddress (ptr, AddressString, MAXDIALOGSTRING, NameString, MAXDIALOGSTRING);
		}
		if (gotwho = GetHeaderLine (refDoc, "Reply-To:", headerLine, MAXHEADERLINE)) 
		{
			NextToken (&ptr);
			ParseAddress (ptr, replyAddress, MAXHEADERLINE, replyName, MAXHEADERLINE);
		}
		if (gotwho && strcmp (AddressString, replyAddress)) 
		{
			if (*AddressString && ConfirmReplyTo) 
			{
				sprintf (str, "Use Reply-To: (%s) instead of\nFrom: (%s) in reply?",
				replyAddress, AddressString);

				if (WVMessageBox (hWnd, str, "Confirm Reply-To", MB_YESNO | MB_ICONQUESTION) == IDNO) 
				{
					gotwho = FALSE;
				}
			}
			if (gotwho) 
			{
				strcpy (AddressString, replyAddress);
			}
		}
		if (*AddressString) 
		{
			strntcpy (buf, AddressString, len);
			return TRUE;
		}
	}
	return FALSE;
}

BOOL GetNewsgroups (char *buf, int len, TypDoc * refDoc)
{
	char headerLine[MAXHEADERLINE];
	char *ptr = headerLine;
	BOOL gotnews = FALSE;

	if (refDoc) 
	{
		if (refDoc->DocType == DOCTYPE_CANCEL)
			gotnews = GetHeaderLine (refDoc, "Newsgroups:", headerLine, MAXHEADERLINE);
		else
		{

			gotnews = GetHeaderLine (refDoc, "Followup-to:", headerLine, MAXHEADERLINE);
			//make sure followup-to: isn't an address

			if (!gotnews || strchr(headerLine, '@') )
				gotnews =GetHeaderLine (refDoc, "Newsgroups:", headerLine, MAXHEADERLINE);
		}
		if (gotnews) 
		{
			NextToken (&ptr);
			strntcpy (buf, ptr, len);
			return TRUE;
		}
	}
	if (NewsgroupsPtr && *NewsgroupsPtr) 
	{
		strntcpy (buf, NewsgroupsPtr, len);
		return TRUE;
	}
	return FALSE;
}

BOOL GetFromAddress (char *buf, int len, TypDoc * refDoc)
{
	*buf = '\0';
	if (*MailAddress) 
	{
#define FIX_df__20010207
#ifdef FIX_df__20010207 /* requires <stdio.h> */
		int   nEmail = -1;
		/* if user name is available and no name is set in the RFC 822 address... */
		/* (if a name is set the address is at minimum "a <b@c.d>" => 9 chars) */
		if (*UserName &&
			!((0 == sscanf(MailAddress, "%*[^<] <%*[^> ]>%n", &nEmail)) &&
			(nEmail >= 9)) )
		{ /* add UserName to MailAddress, quoting any RFC 822 special characters*/
			char * quot = "";
			const char * szRFC822Specials = "()<>@,;:\\\".[]";

			if (strcspn(UserName, szRFC822Specials) < strlen(UserName))
			quot = "\"";

			_snprintf (buf, len, "%s%s%s <%s>", quot, UserName, quot,
			MailAddress);
		}
		else
			strntcpy (buf, MailAddress, len);
#else
		strntcpy (buf, MailAddress, len);
		if (*UserName) 
		{
			char temp[MAXHEADERLINE];
			len -= strlen (MailAddress);
			_snprintf (temp, len, " (%s)", UserName);
			strcat (buf, temp);
		}
#endif
		return TRUE;
	}
	return FALSE;
}



BOOL GetSubject (char *buf, int len, TypDoc * refDoc, int docType)
{
	char headerLine[MAXHEADERLINE];
	char *ptr = headerLine;

	*buf = '\0';
	if (refDoc) 
	{
		if (GetHeaderLine (refDoc, "Subject:", headerLine, MAXHEADERLINE)) 
		{
			NextToken (&ptr);
			if (docType == DOCTYPE_FORWARD ) 
			{
				_snprintf (buf, len, "%s (fwd)", ptr);
			}
			else if (docType == DOCTYPE_SUPERSEDE)
			{
				strntcpy (buf, ptr, len);
			}
			else 
			{
				if (_strnicmp (ptr, "re:", 3)) 
				{
					_snprintf (buf, len, "Re: %s", ptr);
				}
				else 
				{
					strntcpy (buf, ptr, len);
				}
			}
			return TRUE;
		}
	}
	return FALSE;
}

BOOL GetCcAddress (char *buf, int len, TypDoc * refDoc)
{
	char from[MAXHEADERLINE], cc[MAXHEADERLINE];
	char *ptr = from;

	*buf = '\0';
	if (refDoc) 
	{
		if (GetHeaderLine (refDoc, "Cc:", from, MAXHEADERLINE - 1)) 
		{
			NextToken (&ptr);
			ParseAddress (ptr, cc, MAXHEADERLINE, str, MAXINTERNALLINE);
			if (_stricmp(cc, MailAddress)) 
			{      
				// ignore if Cc: is me
				strntcpy (buf, cc, len);
			}
			return TRUE;
		}
	}
	return FALSE;
}


BOOL GetReplyToAddress (char *buf, int len, TypDoc * refDoc)
{
	*buf = '\0';
	if (*ReplyTo && strcmp (ReplyTo, MailAddress)) 
	{
		strntcpy (buf, ReplyTo, len);
		return TRUE;
	}
	return FALSE;
}

BOOL GetOrganization (char *buf, int len, TypDoc * refDoc)
{
	*buf = '\0';
	if (*Organization) 
	{
		strntcpy (buf, Organization, len);
		return TRUE;
	}
	return FALSE;
}

BOOL GetFollowupTo (char *buf, int len, TypDoc * refDoc)
{
	char headerLine[512];
	char *ptr = headerLine;

	*buf = '\0';
	if (refDoc) 
	{
		//Disallow Followup-To: containing @, newsgroups are not addresses
		if (GetHeaderLine (refDoc, "Followup-to:", headerLine, 512) &&
			!strchr(headerLine, '@')) 
		{
			NextToken (&ptr);       
			strntcpy (buf, ptr, len);
			return TRUE;
		}
	}
	return FALSE;
}

// Checks a special case of RFC 1036
BOOL FollowupToPoster (TypDoc * refDoc)
{
	char headerLine[MAXHEADERLINE];

	if ((refDoc) &&
		(GetHeaderLine (refDoc, "Followup-to:", headerLine, MAXHEADERLINE)) &&
		(!strchr(headerLine, '@')) )
	{
		if (string_compare_insensitive("poster",headerLine+14) != NULL) 
			return TRUE; 
		else
			return FALSE;
	}
	return FALSE;
}

BOOL GetHeaderContents (char *buf, int len, TypDoc * refDoc, char *hdr)
{
	char headerLine[MAXHEADERLINE];
	char *ptr = headerLine;

	*buf = '\0';
	if (refDoc) 
	{
		if (GetHeaderLine (refDoc, hdr, headerLine, MAXHEADERLINE)) 
		{
			NextToken (&ptr);
			strntcpy (buf, ptr, len);
			return TRUE;
		}
	}
	return FALSE;
}

/* If refDoc has a references then use that followed by one space. 
 * Then add the refDoc message-id.
 */
BOOL GetReferences (char *buf, int len, TypDoc * refDoc)
{
	char *headerLine;
	char *ptr;
	int tokenLen, lenToGo = len;

	*buf = '\0';
	headerLine = (char *) calloc(MAXREFERENCELINE, sizeof(char));
	if ((headerLine) && (refDoc)) 
	{
		if (GetHeaderLine (refDoc, "References:", headerLine, MAXREFERENCELINE)) 
		{
			ptr = headerLine;
			NextToken (&ptr);
			tokenLen = strlen (ptr);
			// shimomai
			while(tokenLen > 0 && ptr[tokenLen-1] != '>') 
				tokenLen--;
			//
			strncpy (buf, ptr, tokenLen);
			buf[tokenLen++] = ' ';
			buf[tokenLen] = '\0';
			lenToGo -= tokenLen;
		}
		if (GetHeaderLine (refDoc, "Message-ID:", headerLine, MAXREFERENCELINE)) 
		{
			ptr = headerLine;
			NextToken (&ptr);
			// shimomai
			if((tokenLen=strlen(ptr)) >= lenToGo) 
			{
				char *p, *q;
				p = &(buf[tokenLen-lenToGo]);
				if(p = strchr(p+1,'<')) 
				{
					for(q = buf ; *p != '\0' ; p++,q++) *q = *p;
					*q= '\0';
				}
				else 
				{
					buf[0] = '\0';
				}
				lenToGo = len - strlen(buf);
			}
			strncat (buf, ptr, tokenLen);
		}
	}
	free( headerLine );
	return (*buf != '\0');
}     

/*  If the article has an Xref header, we should check to see if the news server 
    that calculated the message ID matches the news server to which we are 
    connected.  This is hard since WinVN doesn't have direct access to the
    WinSock routines.  Since WinVN currently doesn't support multiple simultaneous
    servers, we'll not validate the server and just skip over this entry.  This
    routine then returns a list of crossposted references in "buf" and
    return TRUE, else return FALSE (no Xref or not our server)   
                                                          JD 5/3/95          */
BOOL GetXref (char *buf, int len, TypDoc * refDoc)
{
	char headerLine[MAXHEADERLINE];
	char *ptr,*ptr1;

	*buf = '\0';
	if (refDoc) 
	{
		if (GetLastHeaderLine (refDoc, "Xref:", headerLine, MAXHEADERLINE)) 
		{
			ptr = headerLine;
			NextToken (&ptr);          /* Skip over Xref: */
			ptr1 = ptr;
			while (*ptr1 != ' ' && *ptr1 != '\t' && *ptr1) 
				(ptr1)++;
			strntcpy (buf, ptr, ptr1-ptr);
			//        if ((NNTPHost == 0) || (compare_hostname (NNTPHost,buf) != 0)) return FALSE;
			NextToken (&ptr);
			strntcpy (buf, ptr, len);
		}
	}
	return (*buf != '\0');
}

/* For mail, an In-Reply-To field contains the msg-id of the msg 
 * being replied to.
 */
BOOL GetInReplyTo (char *buf, int len, TypDoc * refDoc)
{
	char headerLine[MAXHEADERLINE];
	char *ptr;

	*buf = '\0';
	if (refDoc) 
	{
		if (GetHeaderLine (refDoc, "Message-ID:", headerLine, MAXHEADERLINE)) 
		{
			ptr = headerLine;
			NextToken (&ptr);
			strntcpy (buf, ptr, len);
		}
	}
	return (*buf != '\0');
}

/* Returned date str is computed from operating system time.
 * Be sure to set the TZ environment variable correctly for
 * "gmtime" to work properly.  Typical setting:  SET TZ=EST5
 */
BOOL GetDate (char *buf, int len)
{
#ifdef _WIN32
	TIME_ZONE_INFORMATION tzinfo;
	SYSTEMTIME local_time;
	DWORD dstflag;
	DWORD errorcode;
	long timezonebias = 0;

	dstflag = GetTimeZoneInformation(&tzinfo);
	switch (dstflag) 
	{
		case 0xfffffff:
			errorcode = GetLastError();
			wsprintf(str, "Failed to retrieve system timezone information\nError code:%d", errorcode);
			WVMessageBox (NULL, str, "Timezone info retrieve failure", MB_OK | MB_ICONHAND);
			return FALSE;
			break;

		case TIME_ZONE_ID_UNKNOWN:
		case TIME_ZONE_ID_STANDARD:
			timezonebias = (tzinfo.Bias/60) * 100 + (tzinfo.Bias%60);
			break;

		case TIME_ZONE_ID_DAYLIGHT:
			timezonebias = ((tzinfo.Bias + tzinfo.DaylightBias)/60) * 100 + ((tzinfo.Bias + tzinfo.DaylightBias)%60);
			break;
	}

	GetLocalTime(&local_time);

	/* rfc1036&rfc822 acceptable format */
	/* Mon, 29 Jun 93 02:15:23 -0800 */
	_snprintf (buf, len, "%s, %.2d %s %.4d %.2d:%.2d:%.2d %+05d",	 //update format specifier 20010421 J.David Byran
			days[local_time.wDayOfWeek],
			local_time.wDay,
			months[local_time.wMonth-1],
			local_time.wYear,
			local_time.wHour,
			local_time.wMinute,
			local_time.wSecond,
			-timezonebias);

#else
	struct tm *timeptr;
	time_t curtime;
	long TimezoneBias;
	char TimeZoneBuf[MAXTZONESIZE+5];

	if (TimeZone == NULL) 
	{
		if (!getenv ("TZ")) 
		{
			WVMessageBox (NetDoc.hDocWnd,
						"Timezone environment variable 'TZ' not set.\n"
						"You must set this variable for your messages\n"
						"to be timestamped correctly.  Set it from the \n"
						"config menu or add it as an environment variable \n"
						"For example, you could add the following to your\n"
						"'autoexec.bat' file:\n"
						"set TZ=EST5EDT\n",
						"Warning:",
						MB_OK | MB_ICONHAND);
		}
	}

	strcpy(TimeZoneBuf,"TZ=");
	strcat(TimeZoneBuf,TimeZone);
	_putenv(TimeZoneBuf);
	_tzset();
	curtime = time ((time_t *) NULL);
	timeptr = localtime (&curtime);
	TimezoneBias = (_timezone/3600) * 100 + ((_timezone/60)%60);

	/* rfc1036&rfc822 acceptable format */
	/* Mon, 29 Jun 93 02:15:23 -0800 */
	_snprintf (buf, len, "%s, %.2d %s %.4d %.2d:%.2d:%.2d %+05d",
				days[timeptr->tm_wday],
				timeptr->tm_mday,
				months[timeptr->tm_mon],
				timeptr->tm_year +1900,
				timeptr->tm_hour,
				timeptr->tm_min,
				timeptr->tm_sec,
				-TimezoneBias);
#endif
	return TRUE;
}


/* Returned date str is computed from operating system time.
 */
BOOL GetMailboxDate (char *buf, int len)
{
	struct tm *timeptr;
	time_t curtime;

	curtime = time ((time_t *) NULL);
	timeptr = localtime (&curtime);

	// mailbox acceptable format */
	// i.e.       Tue 15 Mar 1994 11:11:22 */
	// changed to Tue Mar 15 11:11:22 1994 for compatibility with elm (JD 5/3/95)
	_snprintf (buf, len, "%s %s %.2d %.2d:%.2d:%.2d %.2d",
			days[timeptr->tm_wday],
			months[timeptr->tm_mon],
			timeptr->tm_mday, 
			timeptr->tm_hour,
			timeptr->tm_min,
			timeptr->tm_sec,
			1900+timeptr->tm_year);

	return TRUE;
}

/* ------------------------------------------------------------------------
 * Generate 'says' line, given orig doc, template, and a dest buffer
 *  Replace %i with article ID
 *  Replace %d article date
 *  Replace %a with author email address
 *  Replace %n with author full name
 *
 * called by MakeBodyContents
 */
void GenerateSaysLine (char *line, TypDoc * Doc, char *templ)
{
	register char *src, *dest, *ptr;
	char mesId[MAXHEADERLINE];
	char from[MAXHEADERLINE];
	char date[MAXHEADERLINE];
	char address[MAXDIALOGSTRING];
	char name[MAXDIALOGSTRING];
	char *mesPtr = mesId;
	char *fromPtr = from;
	char *datePtr = date;
	char *addrPtr = address;
	char *namePtr = name;
	BOOL gotMesId, gotFrom, gotDate;

	gotMesId = GetHeaderLine (Doc, "Message-ID:", mesId, MAXHEADERLINE);
	
	if (gotMesId)
		NextToken (&mesPtr);
	else
		mesPtr = "<Unknown>";

	gotDate = GetHeaderLine (Doc, "Date:", date, MAXHEADERLINE);
	
	if (gotDate)
		NextToken (&datePtr);
	else
		datePtr = "<Unknown>";

	*address = *name = '\0';
	gotFrom = GetHeaderLine (Doc, "From:", from, MAXHEADERLINE);
	
	if (gotFrom) 
	{
		NextToken (&fromPtr);
		ParseAddress (fromPtr, address, MAXDIALOGSTRING, name, MAXDIALOGSTRING);
		if (!(*addrPtr))
			addrPtr = "<Unknown>";
		if (!(*namePtr))
			namePtr = "<Unknown>";

	}
	else 
	{
		fromPtr = "<Unknown>";
	}

	for (src = templ, dest = line; *src != '\0';) 
	{
		if (*src == '%') 
		{
			src++;
			switch (*src) 
			{
				case '%':  // literal percent is %%
					*dest++ = '%';
					break;
				case 'i':
					for (ptr = mesPtr; *ptr; *dest++ = *ptr++);
					break;
				case 'd':
					for (ptr = datePtr; *ptr; *dest++ = *ptr++);
					break;
				case 'n':
					for (ptr = namePtr; *ptr; *dest++ = *ptr++);
					break;
				case 'a':
					for (ptr = addrPtr; *ptr; *dest++ = *ptr++);
					break;

				default:
					*dest++ = '%';
					*dest++ = *src;
					break;

			}
			src++;  // skip control char after %

			continue;
		}
		*dest++ = *src++;
	}
	*dest = '\0';
}

/*--- function MakeBodyContents ----------------------------------------------
 *
 *    Make the body of the article.  This is null if there's no article
 *    to reply to, else it's text of the form:
 *    In article <Message-ID>, <user> says:
 *    >line 1
 *    >line 2
 *    > .....
 *
 *    Entry    Doc      points to the article being replied to, else
 *                      NULL if none.
 *             left     is the number of bytes left in MesBuf.
 *
 *    Exit     MesBuf   contains the added lines, and has been updated
 *                      to point to just after the last added byte.
 *             left     has been decremented appropriately.
 *             Return value is not used, for now.
 *
 * called by MakeBody
 */
BOOL MakeBodyContents (TypDoc * Doc, char far ** MesBuf, long *left, int DocType, BOOL bNoBody)
{
	HANDLE hBlock;
	unsigned int Offset, quoteIndSize;
	TypLineID MyLineID;
	TypBlock far *BlockPtr;
	TypLine far *LinePtr;
	char line[MAXHEADERLINE];
	register unsigned long i;
	long nSigSize = 0;
	long nDocLines = 0;
	BOOL bInSig = FALSE;
	BOOL bInBinary = FALSE;
	int  nBinaryLineCount = 0;


	if (EnableSig && Signature->numLines) 
	{
		// the 2 * Signature->numLines is to count \r\n for each line
		nSigSize = Signature->numBytes + 2 * Signature->numLines + strlen (SIG_DELIMITER) + 10;
	}


	if (Doc && TopOfDoc (Doc, &BlockPtr, &LinePtr)) 
	{
		SizeOfDoc (Doc, &nDocLines);

		if (DocType == DOCTYPE_FORWARD) 
		{
			AppendTextToEditBuf ("----Forwarded----", MesBuf, left);
			/* include original headers in forwarded doc */
		} 
		else if (DocType == DOCTYPE_CANCEL) 
		{ 
			AppendTextToEditBuf ("", MesBuf, left);
			GenerateSaysLine (line, Doc, "   Article %i canceled by WinVN");
			AppendTextToEditBuf (line, MesBuf, left);
			AppendTextToEditBuf ("", MesBuf, left);
			AppendTextToEditBuf ("Cancel Reason:", MesBuf, left);
			AppendTextToEditBuf ("", MesBuf, left);
			AppendTextToEditBuf ("   NONE GIVEN", MesBuf, left);
		}
		else 
		{
			if (DocType != DOCTYPE_SUPERSEDE)
			{	

				GenerateSaysLine (line, Doc,
								(DocType == DOCTYPE_POSTING) ? FollowupSaysTemplate : ReplySaysTemplate);
				AppendTextToEditBuf (line, MesBuf, left);
			}

			BOOL bBlank = FALSE;
			/* Skip past headers */
			while (ExtractTextLine (Doc, LinePtr, line, MAXHEADERLINE) ) 
			{
				if (bBlank && !IsBlankStr (line))
					break;

				if (nDocLines)
					nDocLines--;
				
				if (!NextLine (&BlockPtr, &LinePtr))
					break;


				if (IsBlankStr (line))
					bBlank = TRUE;
			}
			//PrevLine (&BlockPtr, &LinePtr);
		}

		/* Copy body of reply document into the body of this article,  */
		/* prepending a ">" to each line */

		/* following line commented after a merge of MRB code (SMR) */
		/* line[0] = '>'; */

		//leave space for sig
		*left -= nSigSize;

		if (DocType != DOCTYPE_CANCEL) 
		{

			if (DocType == DOCTYPE_FORWARD || DocType == DOCTYPE_SUPERSEDE) 
			{
				quoteIndSize = 0;
			}
			else 
			{
				quoteIndSize = 1;
				line[0] = QuoteLineInd;
			}
			while (!bNoBody && 
				   ExtractTextLine (Doc, LinePtr, line + quoteIndSize, MAXHEADERLINE - 1) && *left > 0) 
			{
				if (!bInBinary || nBinaryLineCount < 2)
				{
					if ( !strncmp(line + quoteIndSize, "=ybegin ", 8) ||
						 !strncmp(line + quoteIndSize,  "=ypart ", 7) )
					{
						bInBinary = TRUE;
						nBinaryLineCount = 0;
					}
				}
				if (bInBinary)
				{
					
					if (!strncmp(line + quoteIndSize, "=yend ", 6))
					{
						bInBinary = FALSE;
						char buf[80];
						_snprintf(buf,79,"%c(%d lines of yEnc data removed)", QuoteLineInd, nBinaryLineCount); 
						AppendTextToEditBuf (buf, MesBuf, left);
						nBinaryLineCount = 0;

					}
					else
					{
						nBinaryLineCount++;
					}

				}
				
				
				if (nDocLines)
					nDocLines--;

				if ( (DocType != DOCTYPE_FORWARD && DocType != DOCTYPE_SUPERSEDE) && TruncateSignatureFollowup && nDocLines < 10 && 
					isSignatureDelimiter(line+quoteIndSize))
				{
					bInSig = TRUE;

				}
				if (!bInSig && (!bInBinary || nBinaryLineCount < 2)	)
					AppendTextToEditBuf (line, MesBuf, left);
	

				if (!NextLine (&BlockPtr, &LinePtr))
					break;
			}
		}
		UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
		*left += nSigSize;
	}

	if (DocType != DOCTYPE_SUPERSEDE  && EnableSig && Signature->numLines > 0) 
	{
		AppendTextToEditBuf ("", MesBuf, left);         /* blank line before sig */
		if (strcmp (TextBlockLine (Signature, 0), SIG_DELIMITER)) 
		{
			AppendTextToEditBuf (SIG_DELIMITER, MesBuf, left);
		}
		for (i = 0; i < Signature->numLines; i++) 
		{
			AppendTextToEditBuf (TextBlockLine (Signature, i), MesBuf, left);
		}
	}
	return TRUE;
}

/* MakeBody sets endBuf to an allocated buf containing body, or NULL if no body.
 * It is the caller's responsibility to free the buf.
 * This can fail.
 */
BOOL MakeBody (char **endBuf, TypDoc * refDoc, int docType, HWND hWnd, HWND hWndEdit, BOOL bNoBody)
{
	char *ptr, *buf;
	long bytesNeeded;
	UINT nMax = MAXEDITTEXT; 

	/* Compute the number of bytes we need to hold a straight ASCII representation
	* of the initial text of the reply, and allocate a buffer of that size.
	*/
	bytesNeeded = 0L;
	if (EnableSig && Signature->numLines) 
	{
		// the 2 * Signature->numLines is to count \r\n for each line
		bytesNeeded += Signature->numBytes + 2 * Signature->numLines + strlen (SIG_DELIMITER) + 10;
	}
	if (refDoc) 
	{
		long nLines;
		//bytesNeeded += (2 + NumBlocksInDoc (refDoc)) * refDoc->BlockSize; //MDS Use better size calc below
		bytesNeeded += SizeOfDoc (refDoc, &nLines);
		bytesNeeded += (nLines * 3); //cr,lf,null
		if (docType != DOCTYPE_FORWARD && docType != DOCTYPE_CANCEL && docType != DOCTYPE_SUPERSEDE)
			bytesNeeded += nLines; //> indicator

		bytesNeeded += 1000; //padding to avoid truncations
	}
#ifdef _WIN32

	if (hWndEdit)
	{
		int nVal = min(1000000, bytesNeeded);
		SendMessage( hWndEdit, EM_LIMITTEXT, nVal, 0);
		
		if (  SendMessage( hWndEdit, EM_GETLIMITTEXT, 0, 0) == (nVal & 0xFFFF) )
			SendMessage( hWndEdit, EM_LIMITTEXT, 0, 0);
		
		nMax = (UINT) SendMessage( hWndEdit, EM_GETLIMITTEXT, 0, 0); 
		
		if (nMax == (UINT) 0xFFFF )
			nMax = MAXEDITTEXT; //it lies
		else if (nMax == 0)
			nMax = nVal;
	}

	if (bNoBody)
		bytesNeeded = 20000; //actually only need enough space for header & sig & a few lines
#endif
	if (bytesNeeded > nMax && WVMessageBox(hWnd, IDS_COMPOS_BIG_MESSAGE, IDS_COMPOS_LOWTITLE, MB_ICONQUESTION | MB_YESNO) == IDYES) 
	{
		bytesNeeded = nMax;
	}

	if (bytesNeeded) 
	{
		if ((ptr = buf = (char *) GlobalAllocPtr (GHND, bytesNeeded * sizeof (char))) == NULL) 
		{
			WVMessageBox (hWnd, "Failed to Generate Article", "Memory Allocation Failure", MB_OK | MB_ICONSTOP);
			return FAIL;
		}
		else
		{
			*ptr = '\0';
			MakeBodyContents (refDoc, &ptr, &bytesNeeded, docType, bNoBody);
			*endBuf = buf;
		}
	}
	else 
	{
		*endBuf = (char *) NULL;
	}
	return SUCCESS;
}




/*--- function AuthenticatePosting --------------------------------
 *
 *   Determine whether the user has sent valid authentication
 *   information to the host.
 *
 *    Entry    Authenticated  is TRUE if we're already authorized.
 *             AuthReq        is TRUE if we need authorization.
 *
 *    Exit     Returns TRUE if we are authorized, else FALSE.
 *                                                             
 *    This routine is largely unimplemented.
 *    Intended
 *    Method   If we're already authorized, just return TRUE.
 *             Otherwise, make sure the comm channel to the server
 *             isn't busy.  If it is, put up a message to that
 *             effect and quit.
 *             If not busy, get authorization information from the
 *             user and present it to the server (in encoded form).
 *             Wait until the server responds or until the Abort
 *             Protocol flag is set (by the user via a menu).
 *             If authorized, return TRUE, else put up a
 *             message to the user and return FALSE.
 */
BOOL AuthenticatePosting (BOOL AuthReq)
{
  BOOL whetherOK = FALSE;

  if (AuthReq && !Authenticated) 
  {
        WVMessageBox (NetDoc.hDocWnd,
					"Sorry, you must identify yourself to the news server"
					"before sending.  Choose the Config/Configure Comm... menu option"
					"and enter your username and password."
					"Then Choose Network/Disconnect and Network/Connect.",
					"No permission", MB_OK);
  }
  else 
  {
        whetherOK = TRUE;
  }

  return (whetherOK);
}
