/* --- 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.41 1995/11/07 23:03:53 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>

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);
/*--- 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;
  char *TitlePtr, *WndClassName, *posnString;
  int maxNum,mstatus;
  WndEdit *wndEdits;
  char temp[MAXINTERNALLINE];

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

	AuthReq = AuthReqPost;
	posnString = "PostWindowPos";
	WndClassName = "WinVnPost";
	if (DocType == DOCTYPE_CANCEL)
	  TitlePtr = "Cancel Article";
	else if (Doc) {
	  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) {
	MessageBox (hWnd, "Too many composition windows are open.\nPlease send or close one before continuing.",
				"Cannot Continue", MB_OK | MB_ICONSTOP);
  }
  else if (AuthenticatePosting (AuthReq)) {

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

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

	/* If the screen position has been saved, use that instead. */
	GetPrivateProfileString (COMPOSE, posnString, "!",
							 temp, MAXINTERNALLINE, 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 (((width - 32 - SideSpace) / CompositionCharWidth) > 78) {
	   mstatus = MessageBox (hWnd, "Your Screen Width is wider than the Internet \n"
	                               "standard of 78 fixed point characters. This \n"
	                               "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 from the Window menu \n",
				             "Screen Size exceeds Recommend Size",
				              MB_OKCANCEL | MB_ICONEXCLAMATION);
	   switch(mstatus) {
	     case IDOK:
		   width = (78 * CompositionCharWidth) + 32 + SideSpace;
		   SaveWindowPositions();
		   break;

		 case IDCANCEL:
		   break;
	   }
	 }

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

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

/*--- 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.
 */
BOOL
GetHeaderLine (TypDoc * Doc, char *Prefix, char *Buf, int BufLen)
{
  char *bufptr = Buf;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  unsigned int Offset;
  HANDLE hBlock;
  TypLineID MyLineID;
  int found = FALSE;

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

		if ( _strnicmp (Buf, Prefix, strlen (Prefix)) == 0) {
		  found = TRUE;
		  break;
		}
		if (!NextLine (&BlockPtr, &LinePtr))
	 	 break;
  	}
  	UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
  }
  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 *left)
{
  while (--(*left) > 0 && *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;
  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 = 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

	  nmlen -= l;
	  strncpy (nameout, ptra, (int) l);
	  nameout[l] = 0;
	}
	if (ptrc = 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 = strchr (headerline, '(')) {	// name enclosed in parentheses

//	l = min (ptrb - ptra - 1, adlen);
	l = min (ptrb - ptra, adlen);  // HAB 19960706
	if (l > 0) {
	  adlen -= l;
	  strncpy (addressout, ptra, (int) l);
	  addressout[l] = 0;
	}
	if (ptrc = 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;
  }

// Clean Name   - remove multiple space and quotes and parens
  lastspace = TRUE;
   for (iptr = optr = nameout; *iptr; iptr++) {
	switch (*iptr) {
	case ' ':
	  if (!lastspace)
		*optr++ = *iptr;
	  lastspace = TRUE;
	  break;
	case '"':
	case '(':
	case ')':
	  break;
	default:
	  *optr++ = *iptr;
	  lastspace = FALSE;
	}
  }
  *optr = 0;

// Clean Address
  for (iptr = optr = addressout; *iptr; iptr++) {
	switch (*iptr) {
	case ' ':
	case '(':
	case ')':
	  break;
	default:
	  *optr++ = *iptr;
	  lastspace = FALSE;
	}
  }
  *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 (MessageBox (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) ||
                 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)
{
  char temp[MAXHEADERLINE];

  *buf = '\0';
  if (*MailAddress) {
	strntcpy (buf, MailAddress, len);
	if (*UserName) {
	  len -= strlen (MailAddress);
	  _snprintf (temp, len, " (%s)", UserName);
	  strcat (buf, temp);
	}
	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 (_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[MAXHEADERLINE];
  char *ptr = headerLine;

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

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

  if ((refDoc) &&
	  (GetHeaderLine (refDoc, "Followup-to:", headerLine, MAXHEADERLINE))) {
	 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[MAXHEADERLINE];
  char *ptr;
  int tokenLen, lenToGo = len;

  *buf = '\0';
  if (refDoc) {
	if (GetHeaderLine (refDoc, "References:", headerLine, MAXHEADERLINE)) {
	  ptr = headerLine;
	  NextToken (&ptr);
	  tokenLen = strlen (ptr);
	  strncpy (buf, ptr, len);
	  buf[tokenLen++] = ' ';
	  buf[tokenLen] = '\0';
	  lenToGo -= tokenLen;
	}
	if (GetHeaderLine (refDoc, "Message-ID:", headerLine, MAXHEADERLINE)) {
	  ptr = headerLine;
	  NextToken (&ptr);
	  strncat (buf, ptr, lenToGo);
	}
  }
  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 (GetHeaderLine (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)
{
  struct tm *timeptr;
  time_t curtime;

  if (TimeZone == NULL) {
#ifdef WIN32
  _tzset ();					/* get values from NT control panel */
#else
  if (!getenv ("TZ")) {
	MessageBox (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 varialbe \n"
				"For example, you could add the following to your\n"
				"'autoexec.bat' file:\n"
				"set TZ=EST5EDT\n",
				"Warning:",
				MB_OK | MB_ICONHAND);
  }
#endif
   }
   
  curtime = time ((time_t *) NULL);
  timeptr = localtime (&curtime);

  /* rfc1036&rfc822 acceptable format */
  /* Mon, 29 Jun 93 02:15:23 GMT */
  _snprintf (buf, len, "%s, %.2d %s %.2d %.2d:%.2d:%.2d %s",
			 days[timeptr->tm_wday],
			 timeptr->tm_mday,
			 months[timeptr->tm_mon],
			 timeptr->tm_year,
			 timeptr->tm_hour,
			 timeptr->tm_min,
			 timeptr->tm_sec,
			 tzname[0]);

  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 *template)
{
  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 = template, 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)
{
  HANDLE hBlock;
  unsigned int Offset, quoteIndSize;
  TypLineID MyLineID;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;
  char line[MAXHEADERLINE];
  register unsigned long i;

  if (Doc &&
	  TopOfDoc (Doc, &BlockPtr, &LinePtr)) {
	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 {
	  GenerateSaysLine (line, Doc,
						(DocType == DOCTYPE_POSTING) ? FollowupSaysTemplate : ReplySaysTemplate);
	  AppendTextToEditBuf (line, MesBuf, left);

	  /* 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) {
	    quoteIndSize = 0;
	  }
	  else {
	    quoteIndSize = 1;
	    line[0] = QuoteLineInd;
	  }
	  while (ExtractTextLine (Doc, LinePtr, line + quoteIndSize, MAXHEADERLINE - 1)) {
	    AppendTextToEditBuf (line, MesBuf, left);
	    if (!NextLine (&BlockPtr, &LinePtr))
		  break;
	  }
	}
	UnlockLine (BlockPtr, LinePtr, &hBlock, &Offset, &MyLineID);
  }

  if (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)
{
  char *ptr, *buf;
  long bytesLeft;

  /* 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.
   */
  bytesLeft = 0L;
  if (EnableSig && Signature->numLines) {
	// the 2 * Signature->numLines is to count \r\n for each line
	bytesLeft += Signature->numBytes + 2 * Signature->numLines + strlen (SIG_DELIMITER) + 10;
  }
  if (refDoc) {
	bytesLeft += (2 + NumBlocksInDoc (refDoc)) * refDoc->BlockSize;
  }
  if (bytesLeft) {
	if ((ptr = buf = (char *) GlobalAllocPtr (GMEM_MOVEABLE, bytesLeft * sizeof (char))) == NULL) {
	  MessageBox (hWnd, "Failed to Generate Article", "Memory Allocation Failure", MB_OK | MB_ICONSTOP);
	  return FAIL;
	}
	*ptr = '\0';
	MakeBodyContents (refDoc, &ptr, &bytesLeft, docType);
	*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) {
	MessageBox (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);
}
