/********************************************************************
 *                                                                  *
 *  MODULE    :  WVSEND.C                                           *
 *                                                                  *
 *  PURPOSE   : Send/attachment engine                              *
 *                                                                  *
 *  ENTRY POINTS: SendComposition(WndEdit *compWnd)                 *
 *                DoSend (int action)                               *
 *                CompleteSend ()                                   *
 *                ReadTextFileIntoEditWnd ()                        *
 *                                                                  *
 * Author: John S. Cooper (jcooper@planetz.com)                     *
 *   Date: Nov 28, 1993                                             *
 *                                                                  *
 *  Massive overhaul on oct 8, 1994 (jsc)                           *
 *   This is now the main post/mail engine, and now supports        *
 *   multi-attachments.  Changed name from wvattach to wvsend       *
 ********************************************************************/

/* 
 * $Id: wvsend.c 1.28 1997/03/18 19:17:07 dumoulin Exp $
 */

#include <windows.h>
#include <windowsx.h>
#include "wvglob.h"
#include "winvn.h"
#pragma hdrstop
#include <string.h>
#include <ctype.h>				/* for isspace, isalnum, etc */
#include <stdlib.h>				/* for itoa */
#include <assert.h>
/*
 * Forward Declarations
 */
BOOL InitiateSend (WndEdit * compWnd);
void CompleteSend (WndEdit * compWnd, int action);
BOOL EndSendSection (WndEdit * compWnd);

BOOL GenerateMIMEVersion (TypTextBlock * header);
void GenerateMIMEAttachmentID ();
BOOL GenerateMIMEMultipartMixedHeader (TypTextBlock * textBlock);
BOOL GenerateMIMEPartialHeader (TypTextBlock * textBlock);
BOOL GenerateMIMEContentHeader (TypTextBlock * textBlock, TypAttachment * thisAttach);
BOOL CalcNumParts (WndEdit * compWnd);
void GenerateSubject (WndEdit * compWnd);

BOOL SendOneLineWithLen (char *str, unsigned long len);
BOOL SendTextBlock (TypTextBlock * textBlock);
BOOL SendEditText (HWND hWnd);
unsigned long SendPartialEditText (HWND hWnd, unsigned long startByte, unsigned long maxBytes);
unsigned long SendPartialTextFile (TypAttachment * thisAttach, unsigned long startByte, unsigned long maxBytes);

extern void UpdateBlockStatus ();	/* in wvcoding.c */

/*
 * Globals 
 */

#define ATTACH_NONE              0	/* state constants for DoSend */
#define ATTACH_START             1
#define ATTACH_FIRST_IN_THIS_WND 2
#define ATTACH_FIRST_IN_NEXT_WND 3
#define ATTACH_START_NEXT_SEND   4
#define ATTACH_MAIN              5
#define ATTACH_DONE              6
#define ATTACH_BEGIN_ATTACHMENT  7

int SendAttachNum;
TypTextBlock *MainHeader, *SectionHeader, *WorkBlock;
int SendingPart, SendNumParts;
int AttachmentState;
char origSubject[MAXHEADERLINE], attachmentID[MAXINTERNALLINE];
unsigned long SentNumBytes;

HFILE hMailLog, hPostLog;

/* To diagnose send/attachments by writing output to file instead of sending,
 * undef ENABLE_COMM_SEND, and activate mail/post logging 
 */
#define ENABLE_COMM_SEND

/* ---------------------------------------------------------------------------
 * EncodingTypeToNum converts a string describing a coding type into the
 * internal numeric representation
 */
int
EncodingTypeToNum (char *str)
{
  if (!stricmp (str, "Base-64"))
	return CODE_BASE64;
  else if (!stricmp (str, "UU"))
	return CODE_UU;
  else if (!stricmp (str, "XX"))
	return CODE_XX;
  else if (!stricmp (str, "Custom"))
	return CODE_CUSTOM;
  else if (!stricmp (str, "None"))
	return CODE_NONE;
  else
	return CODE_UNKNOWN;
}

/* ---------------------------------------------------------------------------
 * Logging
 */
void 
AbortMailLog (HWND hWnd)
{
  _snprintf (str, MAXINTERNALLINE, "Mail log error in file %s.  Logging disabled.", MailLogFile);
  MessageBox (hWnd, str, "Logging Error", MB_OK | MB_ICONINFORMATION);
  MailLog = FALSE;
  if (hMailLog) {
	_lclose (hMailLog);
	hMailLog = (HFILE) NULL;
  }
}
void 
AbortPostLog (HWND hWnd)
{
  _snprintf (str, MAXINTERNALLINE, "Post log error in file %s.  Logging disabled.", PostLogFile);
  MessageBox (hWnd, str, "Logging Error", MB_OK | MB_ICONINFORMATION);
  PostLog = FALSE;
  if (hPostLog) {
	_lclose (hPostLog);
	hPostLog = (HFILE) NULL;
  }
}

void 
LogLine (HWND hWnd, char *str, unsigned int len)
{
  if (SendingMail && MailLog && hMailLog) {
	if (_lwrite (hMailLog, str, len) == HFILE_ERROR) {
	  AbortMailLog (hWnd);
	}
  }
  if (SendingPost && PostLog && hPostLog) {
	if (_lwrite (hPostLog, str, len) == HFILE_ERROR) {
	  AbortPostLog (hWnd);
	}
  }
}

void 
InitLogging (HWND hWnd)
{
  OFSTRUCT ofs;
  hMailLog = hPostLog = (HFILE) NULL;

  if (SendingPost && PostLog) {
	if (OpenFile (PostLogFile, &ofs, OF_EXIST) != HFILE_ERROR) {
	  if ((hPostLog = OpenFile (PostLogFile, &ofs, OF_WRITE)) == HFILE_ERROR) {
		AbortPostLog (hWnd);
	  }
	  else {
		if (_llseek (hPostLog, 0L, 2) == HFILE_ERROR) {		/* seek eof */
		  AbortPostLog (hWnd);
		}
	  }
	}
	else {
	  if ((hPostLog = OpenFile (PostLogFile, &ofs, OF_CREATE)) == HFILE_ERROR) {
		AbortPostLog (hWnd);
	  }
	}
  }

  // avoid logging mail if also logging post, and log files have same name
  if (SendingMail && MailLog &&
	  (!SendingPost || !PostLog || strcmp (MailLogFile, PostLogFile))) {
	if (OpenFile (MailLogFile, &ofs, OF_EXIST) != HFILE_ERROR) {
	  if ((hMailLog = OpenFile (MailLogFile, &ofs, OF_WRITE)) == HFILE_ERROR) {
		AbortMailLog (hWnd);
	  }
	  else {
		if (_llseek (hMailLog, 0L, 2) == HFILE_ERROR) {		/* seek eof */
		  AbortMailLog (hWnd);
		}
	  }
	}
	else {
	  if ((hMailLog = OpenFile (MailLogFile, &ofs, OF_CREATE)) == HFILE_ERROR) {
		AbortMailLog (hWnd);
	  }
	}
  }
}

void 
EndLogging (HWND hWnd)
{
  /* add a blank line to the end */
  LogLine (hWnd, "\r\n", 2);
  if (SendingMail && MailLog && hMailLog) {
	_lclose (hMailLog);
  }
  if (SendingPost && PostLog && hPostLog) {
	_lclose (hPostLog);
  }
  hMailLog = hPostLog = (HFILE) NULL;
}

void 
AbortSend (HWND hNotifyWnd)
{
  SendingMail = FALSE;
  if (SendingPost) {
	SendingPost = FALSE;
	CommBusy = FALSE;
	CommState = ST_NONE;
	SetStatbarText (NetDoc.hWndFrame, "", &NetDoc, TRUE, TRUE);
	InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
	if (CommDoc != &NetDoc) {
	SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
	InvalidateRect (CommDoc->hDocWnd, NULL, TRUE);
	}
  }
}

void 
AbortSendMail (HWND hNotifyWnd)
{
  MessageBox (hNotifyWnd, "Mail has failed.  Send aborted.", "Mail Failure", MB_OK | MB_ICONSTOP);
  AbortSend(hNotifyWnd);
}

void 
AbortSendPost (HWND hNotifyWnd)
{
  MessageBox (hNotifyWnd, "Post has failed.  Send aborted.", "Post Failure", MB_OK | MB_ICONSTOP);
  AbortSend(hNotifyWnd);
}


char*
GenerateMIMEBoundaryString()
{
  char* pStr;
  static char sRetval[32];
  const char szBChars[] =
    "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'()+_,-./:=?";

  strcpy(sRetval, "*- Boundary ");
  for(pStr = sRetval + strlen(sRetval); pStr < sRetval + sizeof(sRetval) - 1; pStr++)
  {
    *pStr = szBChars[rand() % sizeof(szBChars)];
  }
  *pStr = '\0';
  return sRetval;
}


/* ---------------------------------------------------------------------------
 * Processes the Attachments in WndEdit->attachments
 */
BOOL 
SendComposition (WndEdit * compWnd)
{
  /* Initialize text blocks */
  if ((SectionHeader = InitTextBlock (compWnd->hWnd)) == NULL)
	return FAIL;
  if ((WorkBlock = InitTextBlock (compWnd->hWnd)) == NULL)
	return FAIL;
  if ((MainHeader = InitTextBlock (compWnd->hWnd)) == NULL)
	return FAIL;

  if (GenerateHeadersForSend (compWnd, MainHeader, origSubject) == FAIL)
	return FAIL;

  SendingMail = (MailCtrl.MailType == MT_SMTP && 
	(compWnd->composeType == DOCTYPE_MAIL ||
	compWnd->composeType == DOCTYPE_FORWARD ||
	(compWnd->composeType == DOCTYPE_POSTING && *CcAddress)));
	
  SendingPost = ((compWnd->composeType == DOCTYPE_POSTING) ||
                 (compWnd->composeType == DOCTYPE_CANCEL));

  InitLogging (compWnd->hWnd);

  /* init hCodedBlockWnd and currentCoded */
  CodingState = ATTACH_WAITING;	/* status info */
  CreateStatusArea (compWnd->hWnd, "Send Status", SW_SHOWNORMAL);
  InvalidateRect (hCodedBlockWnd, NULL, TRUE);	/* clear background */

  /* set body (attachment 0) size */
  compWnd->attachment[0]->numBytes =
	(unsigned long) SendMessage (compWnd->hWndEdit, WM_GETTEXTLENGTH, 0, 0L);

  if (GenerateMIME) {
  MIMEBoundary = GenerateMIMEBoundaryString();
	if (GenerateMIMEVersion (MainHeader)) {
	  CompleteSend (compWnd, ABORT);
	  return FAIL;
	}
  }
  else {
	if (AddEndedLineToTextBlock (MainHeader, "")) {
	  CompleteSend (compWnd, ABORT);
	  return FAIL;
	}
  }

  // we need this global since when wvutil.c calls DoSend, it doesn't
  // have a handle to the current compose window
  ComposeWnd = compWnd;
  compWnd->busy = TRUE;

  // lock out changes during send
  SendMessage (compWnd->hWndEdit, EM_SETREADONLY, TRUE, 0L);
  EnableSendComposition (compWnd, FALSE);

  AttachmentState = ATTACH_START;
  DoSend (CONTINUE);
  return SUCCESS;
}

/* ---------------------------------------------------------------------------
 * DoSend
 * this is written as a state machine to allow interruption in !ReviewMode
 * to wait for News Server comm responses.
 * Thus in !ReviewMode, after an Initiate/EndSendSection, we give up control
 * and wvutil calls DoSend when ready to continue
 */
void
DoSend (int action)
{
  static unsigned long attachSentNumBytes;
  static BOOL MultipartMixed;
  char temp[MAXHEADERLINE], dateStr[MAXINTERNALLINE];
  static int abort, sectionNumInThisSend, nextState;
  unsigned long maxAttach;
  unsigned long offset;
  TypAttachment *thisAttach;
  int len;

  if (action == ABORT || (!SendingMail && !SendingPost)) {
	abort = ABORT;
	AttachmentState = ATTACH_DONE;
  }

  while (AttachmentState != ATTACH_NONE) {
	switch (AttachmentState) {

	case ATTACH_START:
	  abort = ABORT;
	  MultipartMixed = FALSE;
	  currentCoded->numLines = 0;
	  currentCoded->numBytes = 0;
	  SentNumBytes = 0;
	  GenerateMIMEAttachmentID ();

	  if (ComposeWnd->attachment[0]->numBytes) {	// skip empty body

		SendAttachNum = 0;
	  }
	  else if (ComposeWnd->numAttachments > 1) {
		SendAttachNum = 1;
	  }
	  else {
		MessageBox (ComposeWnd->hWnd, "Nothing to send.", "Send Aborted", MB_OK | MB_ICONHAND);
		AttachmentState = ATTACH_DONE;
		break;
	  }

	  if (CalcNumParts (ComposeWnd) == FAIL)	// sets SendNumParts
	   {
		AttachmentState = ATTACH_DONE;
		break;
	  }

	  SendingPart = 1;
	  AttachmentState = ATTACH_START_NEXT_SEND;
	  nextState = ATTACH_BEGIN_ATTACHMENT;
	  // falls into next state

	case ATTACH_START_NEXT_SEND:
	  if (InitiateSend (ComposeWnd)) {
		AttachmentState = ATTACH_DONE;
		break;
	  }

	  ResetTextBlock (SectionHeader);
	  if (SendNumParts > 1 && GenerateMIME &&
		  GenerateMIMEPartialHeader (SectionHeader) == FAIL) {
		AttachmentState = ATTACH_DONE;
		break;
	  }

	  if (SendingPart == 1 && ComposeWnd->numAttachments > SendAttachNum + 1 && GenerateMIME) {
		MultipartMixed = TRUE;
		if (GenerateMIMEMultipartMixedHeader (SectionHeader)) {
		  AttachmentState = ATTACH_DONE;
		  break;
		}
	  }

	  AttachmentState = nextState;
	  sectionNumInThisSend = 0;
	  if (SendingPost) {
		return;					// waiting for post authorization

	  }
	  break;

	case ATTACH_BEGIN_ATTACHMENT:
	  thisAttach = ComposeWnd->attachment[SendAttachNum];	// convenient

	  if (SendAttachNum == 0) {	// status info
	    attachSentNumBytes = ComposeWnd->bodyOffset;
		strcpy (currentCoded->ident, "Body");
	  }
	  else {
	    attachSentNumBytes = 0;
		strcpy (currentCoded->ident, NameWithoutPath (str, thisAttach->fileName));
	  }
	  currentCoded->sequence = SendingPart;
//      currentCoded->numLines = 0;
	  //      currentCoded->numBytes = 0;
	  if (GenerateMIME) {
		GenerateMIMEContentHeader (SectionHeader, thisAttach);
	  }
	  AttachmentState = ATTACH_MAIN;
	  break;

	case ATTACH_MAIN:
	  thisAttach = ComposeWnd->attachment[SendAttachNum];	// convenient

	  if (SendingPost) {
		CommState = ST_POST_WAIT_END;
	  }
	  UpdateBlockStatus ();

	  if (!GenerateMIME && (SendNumParts > 1) && (SendAttachNum > 0)) {
		if (AddEndedLineToTextBlock (SectionHeader, CUT_HERE_TEXT)) {
		  AttachmentState = ATTACH_DONE;
		  break;
		}
	  }

	  // if 1st section of a new send, then need to show main headers
	  if (sectionNumInThisSend == 0) {
		if (ComposeWnd->numAttachments > 1 || SendNumParts > 1) {
		  GenerateSubject (ComposeWnd);
		}
		/* if logging, start with a mailbox header */
		if (hMailLog || hPostLog) {
			GetMailboxDate (dateStr, MAXINTERNALLINE - 1);
			len = _snprintf (temp, MAXHEADERLINE, "From %s %s\r\n",
						 (*MailAddress) ? MailAddress : "Unknown", dateStr);
			LogLine (ComposeWnd->hWnd, temp, len);
		}
		SendTextBlock (MainHeader);
	  }
	  // send stuff prepared in START_NEXT_SEND/BEGIN_ATTACHMENT
	  SendTextBlock (SectionHeader);
	  ResetTextBlock (SectionHeader);

	  if (SendingPart == SendNumParts) {
		// add the rest of the attachment (this is the case for numParts == 1)
		maxAttach = 0;
	  }
	  else {
		maxAttach = ArticleSplitLength - SentNumBytes;
	  }
	  if (SendAttachNum == 0) {	// first attachment is body (from editWnd)
		if ((offset = SendPartialEditText (ComposeWnd->hWndEdit, attachSentNumBytes, maxAttach)) == 0) {
		  AttachmentState = ATTACH_DONE;
		  break;
		}
	  }
	  else {
		if (EncodingTypeToNum (thisAttach->encodingType) != CODE_NONE) {
		  if ((offset = EncodeAndSend (thisAttach, attachSentNumBytes, maxAttach)) == 0) {
			AttachmentState = ATTACH_DONE;
			break;
		  }
		}
		else {
		  if ((offset = SendPartialTextFile (thisAttach, attachSentNumBytes, maxAttach)) == 0) {
			AttachmentState = ATTACH_DONE;
			break;
		  }
		}
	  }
	  attachSentNumBytes += offset;

	  if (maxAttach && attachSentNumBytes < thisAttach->numBytes) {
		SendingPart++;
//          currentCoded->numLines = 0;
		currentCoded->sequence = SendingPart;	// status window info

		AttachmentState = ATTACH_START_NEXT_SEND;
		nextState = ATTACH_MAIN;

		if (EndSendSection (ComposeWnd) == FAIL) {
		  AttachmentState = ATTACH_DONE;
		  abort = ABORT;
		}
		else if (SendingPost)
		  return;
	  }
	  else						// Done with attachment
	   {
		if (SendAttachNum + 1 < ComposeWnd->numAttachments) {
		  if (GenerateMIME) {
			// we are multipart/mixed if we are here
			_snprintf (temp, MAXHEADERLINE, "--%s\r\n", MIMEBoundary);
			SendOneLine (temp);
		  }

		  SendAttachNum++;
		  if (ComposeWnd->attachment[SendAttachNum]->attachInNewArt) {
			SendingPart++;
			AttachmentState = ATTACH_START_NEXT_SEND;
			nextState = ATTACH_BEGIN_ATTACHMENT;
		  }
		  else {
			// begin new attachment in this same send
			sectionNumInThisSend++;
			AttachmentState = ATTACH_BEGIN_ATTACHMENT;
		  }
		}
		else {
		  // done with all attachments successfully
		  AttachmentState = ATTACH_DONE;
		  abort = CONTINUE;
		  if (GenerateMIME && MultipartMixed) {
			// if multi-part/mixed then need to attach final boundary
			_snprintf (temp, MAXHEADERLINE, "--%s--\r\n", MIMEBoundary);
			SendOneLine (temp);
		  }
		}
		if (AttachmentState != ATTACH_BEGIN_ATTACHMENT) {
		  if (EndSendSection (ComposeWnd) == FAIL) {
			AttachmentState = ATTACH_DONE;
			abort = ABORT;
		  }
		  else if (SendingPost)
			return;
		}
	  }
	  break;

	case ATTACH_DONE:
	  AttachmentState = ATTACH_NONE;
	  CompleteSend (ComposeWnd, abort);
	  break;

	}							// end of switch (AttachmentState)

  }								// end of while (AttachmentState != ATTACH_NONE)

  return;
}


/* ------------------------------------------------------------------------
 *    Send routines
 */
BOOL
InitiateSend (WndEdit * compWnd)
{
  int result = 0;

  CodingState = ATTACH_WAITING;
  UpdateBlockStatus ();

#ifdef ENABLE_COMM_SEND
  if (SendingMail) {
	if ((compWnd->composeType == DOCTYPE_POSTING) ||
	    (compWnd->composeType == DOCTYPE_CANCEL)){
	  result = prepare_cc_smtp_message (CcAddress, MainHeader);
	}
	else {
	  result = prepare_smtp_message (MainHeader);
	}
	if (result != 0) {
	  AbortSendMail (compWnd->hWnd);
	}
  }

  if (!result && SendingPost) {
	CommLinePtr = CommLineIn;
	CommBusy = TRUE;
	CommState = ST_POST_WAIT_PERMISSION;
	PutCommLine ("POST");
  }
#endif
  SentNumBytes = 0;

  return (result);
}

BOOL
EndSendSection (WndEdit * compWnd)
{
  int result = 0;

  CodingState = ATTACH_WAITING;
  UpdateBlockStatus ();

  /* ensure ends with blank line */
  SendOneLine("\r\n");

#ifdef ENABLE_COMM_SEND
  if (SendingMail) {
	result = end_send_smtp ();
	if (result != 0) {
	  AbortSendMail (compWnd->hWnd);
	}
  }
  if (!result && SendingPost) {
	result = end_post ();
	if (result != 0) {
	  AbortSendPost (compWnd->hWnd);
	}
  }
#endif
  SentNumBytes = 0;

  return (result);
}

void
CompleteSend (WndEdit * compWnd, int action)
{
  EndLogging (compWnd->hWnd);

  FreeTextBlock (MainHeader);
  FreeTextBlock (SectionHeader);
  FreeTextBlock (WorkBlock);

  DestroyStatusArea ();
  CodingState = INACTIVE;
  ComposeWnd = (WndEdit *) NULL;
  if (SendingPost) {
	CommBusy = FALSE;
	CommState = ST_NONE;
  }
  if (CommDoc) {
	SetStatbarText (CommDoc->hWndFrame, "", CommDoc, TRUE, TRUE);
	InvalidateRect (CommDoc->hDocWnd, NULL, TRUE);
  }
  SetStatbarText (NetDoc.hWndFrame, "", &NetDoc, TRUE, TRUE);
  InvalidateRect (NetDoc.hDocWnd, NULL, TRUE);
  SendingMail = SendingPost = FALSE;
  compWnd->busy = FALSE;

  if (action == CONTINUE) {
	/* if successful, close the compose window */
	DestroyWindow (compWnd->hWnd);

	/* if we're sending a batch, send the next */
	if (compWnd->nextBatchIndex) {
	  BatchSend (compWnd->composeType);
	}
  } 
  else {
	/* unsuccessful, unset read-only bit to allow fixes, and allow send */
	SendMessage (compWnd->hWndEdit, EM_SETREADONLY, FALSE, 0L);
	EnableSendComposition (compWnd, TRUE);
	/* cancel any batch */
	compWnd->nextBatchIndex = 0;
  }
}

BOOL
SendOneLineWithLen (char *str, unsigned long len)
{
  int result = 0;

#ifdef ENABLE_COMM_SEND
  if (SendingMail) {
	result = send_smtp_line (str, len);
	if (result != 0) {
	  AbortSendMail(ComposeWnd->hWnd);
	}
  }
  if (!result && SendingPost) {
	result = post_line (str, len);
	if (result != 0) {
	  AbortSendPost(ComposeWnd->hWnd);
	}
  }
#endif
  SentNumBytes += len;
  currentCoded->numLines++;
  if (currentCoded->numLines % STATUS_UPDATE_FREQ == 0)
	UpdateBlockStatus ();

  LogLine (ComposeWnd->hWnd, str, (unsigned int) len);

  return (result);
}

BOOL
SendOneLine (char *str)
{
  return (SendOneLineWithLen (str, (unsigned long) strlen (str)));
}

BOOL
SendTextBlock (TypTextBlock * textBlock)
{
  register unsigned long i;
  unsigned long len;
  for (i = 0; i < textBlock->numLines; i++) {
	len = (unsigned long) strlen (TextBlockLine (textBlock, i));
	SendOneLineWithLen (TextBlockLine (textBlock, i), len);
	currentCoded->numBytes += len;
  }
  return SUCCESS;
}

unsigned long
SendPartialEditText (HWND hWnd, unsigned long startByte,
					 unsigned long maxBytes)
{
  unsigned long len, numBytes;
  char *text;
  char *ptr, *endPtr;
  int result = SUCCESS;

  CodingState = ATTACH_SENDING;
  UpdateBlockStatus ();

  if ((text = GetEditText (hWnd)) == NULL) {
	return 0L;
  }

  numBytes = 0L;
  ptr = &text[startByte];
  while (*ptr && result == SUCCESS && (!maxBytes || numBytes < maxBytes)) {
	if ((endPtr = strchr (ptr, '\r')) == NULL) {
	  /* last line didn't end with \r\n */
	  if ((result = SendOneLine (ptr)) == SUCCESS) {
		result = SendOneLineWithLen ("\r\n", 2);
	  }
	  numBytes += strlen (ptr) + 2;
	  break;
	}
	endPtr++;					/* move past \r */
	/* handle soft line break \r\r\n (see EM_FMTLINES) */
	if (*endPtr == '\r') {
	  *endPtr = '\n';
	}
	endPtr++;					/* move past \n */

	len = (unsigned long) (endPtr - ptr);
	result = SendOneLineWithLen (ptr, len);
	ptr = endPtr;
	numBytes += len;

	if (*ptr == '\n') {
      numBytes++;
	  ptr++;					/* move past extra \n (in case of soft line break extra \n */
	}
  }
  GlobalFreePtr (text);

  return numBytes;
}

BOOL
SendEditText (HWND hWnd)
{
  if (SendPartialEditText (hWnd, 0L, 0L) == 0L) {
	return FAIL;
  }
  else {
	return SUCCESS;
  }
}

unsigned long
SendPartialTextFile (TypAttachment * thisAttach, unsigned long startByte,
					 unsigned long maxBytes)
{
  HFILE hFile;
  int numRead;
  char inBuf[MAXOUTLINE];
  unsigned long totalNumRead;
  register int i;
  BOOL done;
  int result = SUCCESS;

  CodingState = ATTACH_READFILE;
  UpdateBlockStatus ();

  if ((hFile = _lopen (thisAttach->fileName, LOPEN_READ)) == HFILE_ERROR) {
	return (0);
  }
  if (startByte && _llseek (hFile, startByte, 0) == HFILE_ERROR) {
	_lclose (hFile);
	return (0);
  }
  done = FALSE;
  totalNumRead = 0L;
  while (!done && result == SUCCESS && (!maxBytes || totalNumRead < maxBytes)) {
	if ((numRead = _lread (hFile, inBuf, MAXOUTLINE)) == HFILE_ERROR) {
	  _lclose (hFile);
	  return (0);
	}
	if (numRead < MAXOUTLINE) {
	  done = TRUE;
	}
	else if (maxBytes && totalNumRead + numRead > maxBytes) {
	  // seek cr/lf
	  for (i = 0; i < numRead && !strchr ("\r\n", inBuf[i]); i++);
	  // skip over cr/lf's
	  for (; i < numRead && strchr ("\r\n", inBuf[i]); i++);
	  numRead = i;
	  done = TRUE;
	}
	totalNumRead += numRead;
	currentCoded->numBytes += numRead;
	currentCoded->numLines++;

	result = SendOneLineWithLen (inBuf, numRead);

	if (currentCoded->numLines % STATUS_UPDATE_FREQ == 0)
	  UpdateBlockStatus ();
  }

  _lclose (hFile);

  UpdateBlockStatus ();
  return (totalNumRead);
}


/* ------------------------------------------------------------------------
 * Calculate the # parts needed for body and all attachments
 * This basically does a pre-flight of the entire send-generation process,
 * specifically paying attention to what headers will be generated, etc.
 * It's a bit clunky, and hard to maintain - since it must match the 
 * logic flow in the DoSend switch.  However, this _should_ give us
 * accurate NumParts _up front_ so we don't need to generate the full contents
 * in memory before sending it
 *
 */
BOOL
CalcNumParts (WndEdit * compWnd)
{
  register int i;
  unsigned long workSize, addSize, pSize, sizeLeft, offset;
  unsigned long aSize[MAX_NUM_ATTACHMENTS];
  BOOL msgBreak = FALSE;

  if (ArticleSplitLength == 0) {
	// just count # of attachInNewArt flags
	SendNumParts = 1;
	// if empty body, make first attachment act like the body
	i = (compWnd->attachment[0]->numBytes) ? 1 : 2;
	for (; i < compWnd->numAttachments; i++) {
	  if (compWnd->attachment[i]->attachInNewArt) {
		SendNumParts++;
	  }
	}
	return SUCCESS;
  }

  // generate an approximate subject line (only if it's obvious that
  // we'll need one)
  if (compWnd->numAttachments > 1 ||
	compWnd->attachment[0]->numBytes > (unsigned long) ArticleSplitLength) {
	SendingPart = SendNumParts = 20;	// dummy #s

	GenerateSubject (compWnd);
  }

  // calculate total number of bytes for all the body + attachments

  // body is attachment 0 
  aSize[0] = 0L;
  if (GenerateMIME && compWnd->numAttachments > 1 &&
	  compWnd->attachment[0]->numBytes > 0) {	// non-empty body

	ResetTextBlock (WorkBlock);
	if (GenerateMIMEMultipartMixedHeader (WorkBlock) == FAIL) {
	  return FAIL;
	}
	aSize[0] += WorkBlock->numBytes;
  }
  aSize[0] += compWnd->attachment[0]->numBytes;
  workSize = aSize[0];

  for (i = 1; i < compWnd->numAttachments; i++) {
	if (GenerateMIME) {
	  aSize[i] = WorkBlock->numBytes + strlen (MIMEBoundary) + 2;
	  ResetTextBlock (WorkBlock);
	  if (GenerateMIMEContentHeader (WorkBlock, compWnd->attachment[i]) == FAIL) {
		return FAIL;
	  }
	  aSize[i] += WorkBlock->numBytes;
	}
	else {
	  aSize[i] = strlen (CUT_HERE_TEXT);
	}
	if (EncodingTypeToNum (compWnd->attachment[i]->encodingType) != CODE_NONE) {
	  addSize = (compWnd->attachment[i]->numBytes * 4) / 3;
	  addSize += (addSize / 60) * 2;	// estimate \r\n for each line

	}
	else {
	  addSize = compWnd->attachment[i]->numBytes;
	}
	aSize[i] += addSize;
	workSize += aSize[i];
	if (compWnd->attachment[i]->attachInNewArt) {
	  msgBreak = TRUE;
	}
  }

  // if they all fit in one art, and the attachments were not broken by an
  // attachInNewArt flag, then done
  if (!msgBreak &&
	  workSize + MainHeader->numBytes < (unsigned long) ArticleSplitLength) {
	SendNumParts = 1;
	return SUCCESS;
  }

  // they don't all fit, or there was an attachInNewArt break, so do message/partial
  if (GenerateMIME) {
	ResetTextBlock (WorkBlock);
	GenerateMIMEPartialHeader (WorkBlock);
	pSize = WorkBlock->numBytes;
  }
  else {
	pSize = strlen (CUT_HERE_TEXT);
  }

  // calc number of bytes that will fit in each subsequent send (after headers)
  offset = ArticleSplitLength - MainHeader->numBytes - pSize;

  SendNumParts = 1;
  workSize = offset;			// space left in current art

  for (i = 0; i < compWnd->numAttachments; i++) {
	sizeLeft = aSize[i];

	while (sizeLeft > workSize) {	// sizeLeft - workSize > 0

	  sizeLeft -= workSize;
	  SendNumParts++;
	  workSize = offset;		// new article full-sized

	}
	// start new art with sizeLeft already used up
	if (i + 1 < compWnd->numAttachments) {
	  if (!compWnd->attachment[i + 1]->attachInNewArt) {
		workSize -= sizeLeft;
	  }
	  else {
		workSize = offset;
		SendNumParts++;
	  }
	}
  }
	return SUCCESS;
}

//  RFC-1521 says MIME encoding must use the lowest common denominator
//  character set possible.  Thus is a message contains US-ASCII
//  characters only, it must be marked as US-ASCII and not ISO-8859-1
//  which is a superset of US-ASCII.
//
//    Check Routines submitted by:  TANAKA Goh, <goh@yamato.ibm.co.jp>
//                                  IBM Japan Ltd.  Yamato Lab.

BOOL
isasciistr(char *string)
{
  unsigned char c;

  while ((c = *string++) != 0) {
    if (!isascii(c)) {
      return FALSE;
    }
  }
  return TRUE;
}

BOOL
MakeTextContentTypeField(char *string, TypAttachment *thisAttach)
{
  char  *text;
  HFILE hf;
  int   cbRead;
  BYTE  bBuf[MAXOUTLINE];

  if (SendAttachNum == 0) {
    if ((text = GetEditText(ComposeWnd->hWndEdit)) == NULL) {
      return FAIL;
    }

    if (isasciistr(text)) {
      _snprintf(string, MAXHEADERLINE, "Content-Type: %s; charset=%s", thisAttach->contentType, "US-ASCII");
      return SUCCESS;
    } else {
      _snprintf(string, MAXHEADERLINE, "Content-Type: %s; charset=%s", thisAttach->contentType, MIMECharset);
      return SUCCESS;
    }
  } else {
    if ((hf = _lopen(thisAttach->fileName, LOPEN_READ)) == HFILE_ERROR) {
      return FAIL;
    }

    do {
      if ((cbRead = _lread(hf, bBuf, MAXOUTLINE)) == HFILE_ERROR) {
        _lclose(hf);
        return FAIL;
      }

      if (cbRead && !isasciistr((char *) bBuf)) {
        _lclose(hf);
        _snprintf(string, MAXHEADERLINE, "Content-Type: %s; charset=%s", thisAttach->contentType, MIMECharset);
        return SUCCESS;
      }
    } while (cbRead != 0);

    _lclose(hf);
    _snprintf(string, MAXHEADERLINE, "Content-Type: %s; charset=%s", thisAttach->contentType, "US-ASCII");
    return SUCCESS;
  }
}

/* ------------------------------------------------------------------------
 *    MIME block generation routines
 */
BOOL
GenerateMIMEVersion (TypTextBlock * textBlock)
{
  char temp[MAXINTERNALLINE];

  sprintf (temp, "MIME-Version: %s", MIME_VERSION);
  return (AddEndedLineToTextBlock (textBlock, temp));
}

void
GenerateMIMEAttachmentID ()
{
  time_t theTime;

  time (&theTime);
  _snprintf (attachmentID, MAXINTERNALLINE, "\"%ld@%s\"", theTime, UserName);
}

BOOL
GenerateMIMEMultipartMixedHeader (TypTextBlock * textBlock)
{
  char temp[MAXINTERNALLINE];

  if (AddEndedLineToTextBlock (textBlock, "Content-Type: multipart/mixed;"))
	return FAIL;

  sprintf (temp, "     Boundary=\"%s\"", MIMEBoundary);
  if (AddEndedLineToTextBlock (textBlock, temp))
	return FAIL;

  /* blank line to end headers */
  if (AddEndedLineToTextBlock (textBlock, ""))
	return FAIL;

  /* empty preamble here */
  sprintf (temp, "--%s", MIMEBoundary);
  if (AddEndedLineToTextBlock (textBlock, temp))
	return FAIL;
  return SUCCESS;
}

BOOL
GenerateMIMEPartialHeader (TypTextBlock * textBlock)
{
  char temp[MAXHEADERLINE];

  if (AddEndedLineToTextBlock (textBlock, "Content-Type: message/partial;"))
	return FAIL;

  _snprintf (temp, MAXHEADERLINE, "     id=%s;", attachmentID);
  if (AddEndedLineToTextBlock (textBlock, temp))
	return FAIL;

  _snprintf (temp, MAXHEADERLINE, "     number=%d; total=%d", SendingPart, SendNumParts);
  if (AddEndedLineToTextBlock (textBlock, temp))
	return FAIL;

  if (AddEndedLineToTextBlock (textBlock, ""))
	return FAIL;

  return SUCCESS;
}

BOOL
GenerateMIMEContentHeader (TypTextBlock * textBlock, TypAttachment * thisAttach)
{
  int type;
  register int i;
  char temp[MAXHEADERLINE], name[MAXFILENAME];

  for (i = 0; i < NUM_CONTENT_TYPES; i++)
	if (!_stricmp (thisAttach->contentType, ContentTypes[i]))
	  break;

  type = EncodingTypeToNum (thisAttach->encodingType);

#if 0	// jsc: never use default content-type.  always be explicit
  // if this is Text/Plain, non-encoded, don't need the Content-Type header
  if (!_stricmp (thisAttach->contentType, "Text/Plain") && type == CODE_NONE) {
	if (AddEndedLineToTextBlock (textBlock, ""))
	  return FAIL;

	return SUCCESS;
  }
#endif
  if (!_stricmp (thisAttach->contentType, "Text/Plain") && type == CODE_NONE) {
	// _snprintf (temp, MAXHEADERLINE, "Content-Type: %s; charset=%s", 
	//			thisAttach->contentType, MIMECharset);
    if (MakeTextContentTypeField(temp, thisAttach) == FAIL) {
       return FAIL;
    }
  } else
  // note Text/Plain encoded gets turned into Application/octet-stream
  if (!_stricmp (thisAttach->contentType, "Other") || i == NUM_CONTENT_TYPES ||
	  !_stricmp (thisAttach->contentType, "Text/Plain")) {
	_snprintf (temp, MAXHEADERLINE, "Content-Type: Application/octet-stream; name=%s",
			   NameWithoutPath (name, thisAttach->fileName));
  }
  else {
	_snprintf (temp, MAXHEADERLINE, "Content-Type: %s", thisAttach->contentType);
  }

  if (AddEndedLineToTextBlock (textBlock, temp))
	return FAIL;

  switch (type) {
  case CODE_UU:
	_snprintf (temp, MAXHEADERLINE, "Content-Transfer-Encoding: %s", MIMEUUType);
	break;
  case CODE_XX:
	_snprintf (temp, MAXHEADERLINE, "Content-Transfer-Encoding: %s", MIMEXXType);
	break;
  case CODE_CUSTOM:
	_snprintf (temp, MAXHEADERLINE, "Content-Transfer-Encoding: %s", MIMECustomType);
	break;
  case CODE_BASE64:
	strcpy (temp, "Content-Transfer-Encoding: Base64");
	break;
  case CODE_NONE:
  default:
	*temp = '\0';
  }
  if (*temp && AddEndedLineToTextBlock (textBlock, temp))
	return FAIL;

  if (AddEndedLineToTextBlock (textBlock, ""))
	return FAIL;

  return SUCCESS;
}

/* ------------------------------------------------------------------------
 *    Generate a subject line based on orig subject line, and the
 *  subject template
 *      Replace %f with fileName
 *      Replace %p with part #
 *      Replace %0p with part # 0 padded to length of total # parts
 *      Replace %t with total # parts
 *      Replace %s with orig subject content
 *
 */
void
GenerateSubject (WndEdit * compWnd)
{
  register char *src, *dest, *ptr;
  register unsigned long num;
  char newSubject[MAXHEADERLINE];
  char numStr[10];
  int numZeros, part, numParts;
  extern char *NameWithoutPath (char *name, char *fileName);

  strcpy (newSubject, "Subject: ");

  numParts = SendNumParts;
  part = SendingPart;

  // if non-empty body, and attachments exist, and first attachment starts 
  // in new art, then show count from zero instead of one.  (kinda kludgey!)
  if (compWnd->numAttachments > 1 && compWnd->attachment[0]->numBytes > 0 &&
	  compWnd->attachment[1]->attachInNewArt) {
	numParts--;
	part--;
  }

  for (src = SubjectTemplate, dest = &newSubject[9]; *src;) {
	if (*src == '%') {
	  src++;
	  switch (*src) {
	  case '%':				// literal percent is %%

		*dest++ = '%';
		break;
	  case 's':
		for (ptr = origSubject; *ptr; *dest++ = *ptr++);
		break;
	  case 'f':
		if (compWnd->numAttachments == 1) {
		  break;
		}
		else if (compWnd->numAttachments == 2) {
		  NameWithoutPath (str, compWnd->attachment[1]->fileName);
		}
		else {
		  _snprintf (str, MAXINTERNALLINE, "%d Attachments", compWnd->numAttachments - 1);
		}
		for (ptr = str; *ptr; *dest++ = *ptr++);
		break;

	  case '0':
		if (*(src + 1) == 'p') {
		  src++;				// skip zero
		  // zero-pad number to length of numParts string

		  itoa (numParts, numStr, 10);	// always length >= to part

		  itoa (part, str, 10);

		  for (numZeros = strlen (numStr) - strlen (str); numZeros; numZeros--)
			*dest++ = '0';

		  for (ptr = str; *ptr; *dest++ = *ptr++);
		}
		break;

	  case 'p':
		itoa (part, numStr, 10);
		for (ptr = numStr; *ptr; *dest++ = *ptr++);
		break;

	  case 't':
		itoa (numParts, numStr, 10);
		for (ptr = numStr; *ptr; *dest++ = *ptr++);
		break;

	  default:
		*dest++ = '%';
		*dest++ = *src;
		break;

	  }
	  src++;					// skip control char after %

	  continue;
	}
	*dest++ = *src++;
  }
  *dest = '\0';
  strcat (dest, "\r\n");

  /* find subject line in headers to replace */
  for (num = 0; num < MainHeader->numLines; num++)
	if (!_strnicmp (TextBlockLine (MainHeader, num), "subject:", 8))
	  break;

  ReplaceLineInTextBlock (MainHeader, num, newSubject);
}

/* ------------------------------------------------------------------------
 *    Directly modifies the Edit buffer of the given multi-line edit wnd
 *  Appends contents of fileName to buffer
 */
void
ReadTextFileIntoEditWnd (HWND hEditWnd, char *fileName)
{
  HFILE hFile;
  register unsigned int i;
  char *editBuf;
  unsigned int numRead;
  unsigned long size, expectedLen;

  expectedLen = GetFileLength (fileName);
  size = (unsigned long) SendMessage (hEditWnd, WM_GETTEXTLENGTH, 0, 0L);

  if (size + expectedLen > 60000L) {
	sprintf (str, "File %s is too large to read into the edit buffer.\nTry attaching it instead.", fileName);
	MessageBox (hEditWnd, str, "File Too Large", MB_OK | MB_ICONSTOP);
	return;
  }
  editBuf = (char *) GlobalAllocPtr (GMEM_MOVEABLE, expectedLen + 3);

  if ((hFile = _lopen (fileName, LOPEN_READ)) == HFILE_ERROR) {
	sprintf (str, "Could not open file %s for read", fileName);
	MessageBox (hEditWnd, str, "Open File Error", MB_OK);
	goto abortReadFile;
  }

  if ((numRead = _lread (hFile, editBuf, (unsigned int) expectedLen)) != expectedLen) {
	sprintf (str, "File %s not read correctly", fileName);
	MessageBox (hEditWnd, str, "File Read Error", MB_OK | MB_ICONSTOP);
	goto abortReadFile;
  }
  for (i = 0; i < numRead; i++)
	if (!__isascii (editBuf[i])) {
	  sprintf (str, "File %s appears to contain non-ASCII data.\n"
	  				"Binary files should be encoded for attachment.\n",
					"Continue?", fileName);
	  if (MessageBox (hEditWnd, str, "Non-ASCII File", MB_YESNO | MB_ICONSTOP) == IDNO) {
		  _lclose (hFile);
		  goto abortReadFile;
	  }
	}
  _lclose (hFile);

  editBuf[numRead] = '\r';
  editBuf[numRead + 1] = '\n';
  editBuf[numRead + 2] = '\0';

  // insert at current position
  if (GetFocus () != hEditWnd) {
#ifdef _WIN32
	SendMessage (hEditWnd, EM_SETSEL, (WPARAM) (INT) size, (LPARAM) (INT) size);
#else
	SendMessage (hEditWnd, EM_SETSEL, (WPARAM) (UINT) 1, MAKELONG (size, size));
#endif
  }
  SendMessage (hEditWnd, EM_REPLACESEL, 0, (LPARAM) (LPCSTR) editBuf);

abortReadFile:
  GlobalFreePtr (editBuf);
}

#if 0
void
ReadTextFileIntoEditWnd (HWND hEditWnd, char *fileName)
{
  HFILE hFile;
  register unsigned int i;
  char *editBuf, *endPtr;
  unsigned int numRead;
  unsigned long size, expectedLen;

  if ((editBuf = GetEditText (hEditWnd)) == NULL)
	return;

  expectedLen = GetFileLength (fileName);
  size = strlen (editBuf) + expectedLen + 3;

  if (size > 60000L) {
	sprintf (str, "File %s is too large to read into the edit buffer.\nTry attaching it instead.", fileName);
	MessageBox (hEditWnd, str, "File Too Large", MB_OK | MB_ICONSTOP);
	goto abortReadFile;
  }
  editBuf = GlobalReAllocPtr (editBuf, size, GMEM_MOVEABLE);

  if ((hFile = _lopen (fileName, OF_READ)) == HFILE_ERROR) {
	sprintf (str, "Could not open file %s for read", fileName);
	MessageBox (hEditWnd, str, "Attachment Open File Error", MB_OK);
	return;
  }

  endPtr = editBuf + strlen (editBuf);

  if ((numRead = _lread (hFile, endPtr, (unsigned int) expectedLen)) != expectedLen) {
	sprintf (str, "File %s not read correctly", fileName);
	MessageBox (hEditWnd, str, "File Read Error", MB_OK | MB_ICONSTOP);
	goto abortReadFile;
  }
  for (i = 0; i < numRead; i++)
	if (!__isascii (endPtr[i])) {
	  sprintf (str, "File %s appears to contain non-ASCII data.\nBinary files must be encoded for attachment", fileName);
	  MessageBox (hEditWnd, str, "Non-ASCII File Error", MB_OK | MB_ICONSTOP);
	  _lclose (hFile);
	  goto abortReadFile;
	}
  _lclose (hFile);
  endPtr[numRead] = '\r';
  endPtr[numRead + 1] = '\n';
  endPtr[numRead + 2] = '\0';

  SetEditText (hEditWnd, editBuf);

abortReadFile:
  GlobalFreePtr (editBuf);
}
#endif

#ifdef OLD_HEADER
/* ------------------------------------------------------------------------
 * Returns true for lines which look like standard header lines
 * Returns non-zero if should skip this line, else zero
 * A binary search through a list of words would be really smart
 * Lazy approach
 */
int
TestRfc822HeaderLine (char *line)
{
  switch (tolower (line[0])) {
  case 'd':
	return (!_strnicmp (line, "date", 4));
  case 'f':
	return (!_strnicmp (line, "follow", 6) ||
			!_strnicmp (line, "from", 4));
  case 'k':
	return (!_strnicmp (line, "keywords", 8));

  case 'l':
	return (!_strnicmp (line, "lines", 5));
  case 'm':
	return (!_strnicmp (line, "message", 7));
  case 'n':
	return (!_strnicmp (line, "newsgrou", 8) ||
			!_strnicmp (line, "nntp", 4));
  case 'o':
	return (!_strnicmp (line, "organiza", 8) ||
			!_strnicmp (line, "originat", 8));
  case 'p':
	return (!_strnicmp (line, "path", 4));
  case 'r':
	return (!_strnicmp (line, "referenc", 8) ||
			!_strnicmp (line, "reply", 5));
  case 's':
	return (!_strnicmp (line, "sender", 6) ||
			!_strnicmp (line, "subject:", 8) ||
			!_strnicmp (line, "summary", 7));
  case 'x':
	return (!_strnicmp (line, "xref:", 5) ||
			!_strnicmp (line, "x-news", 6));
  default:
	return (0);
  }
}

#endif


#if 0
/* ------------------------------------------------------------------------
 * Calculate the # parts needed for this attachment
 * offset is the length of any headers/sections sent in this article, before 
 * this attachment
 */
void
CalcNumParts (unsigned long offset, TypAttachment * thisAttach)
{
  unsigned long workSize, cSize, pSize;

  if (!ArticleSplitLength) {
	thisAttach->numParts = 1;
	return;
  }

  // pre-flight the MIME stuff
  if (GenerateMIME) {
	GenerateMIMEAttachmentID ();

	ResetTextBlock (WorkBlock);
	GenerateMIMEContentHeader (WorkBlock, thisAttach);
	cSize = WorkBlock->numBytes;	// goes only in 1st send

	ResetTextBlock (WorkBlock);
	GenerateMIMEPartialHeader (WorkBlock, thisAttach);
	pSize = WorkBlock->numBytes;	// goes in all partial sends

  }
  else {
	cSize = pSize = 0;
//      pSize = strlen(CUT_HERE_TEXT);
  }
  // gen subject before calculating numParts      
  GenerateSubject (MainHeader, thisAttach, 1);	// dummy part num

  offset += cSize;

  // total size of encoded attachment 
  if (EncodingTypeToNum (thisAttach->encodingType) != CODE_NONE) {
	workSize = (thisAttach->numBytes * 4) / 3;
	workSize += (workSize / ENCODE_LINE_LEN) * 2;	// \r\n for each line

  }
  else {
	workSize = thisAttach->numBytes;
  }
  thisAttach->numParts = 1;
  if (workSize + offset > (unsigned long) ArticleSplitLength) {
	// total # attachment bytes to go after first send
	workSize -= (ArticleSplitLength - offset - pSize);

	// calc number of bytes from attachment that will fit in each subsequent send
	offset = ArticleSplitLength - MainHeader->numBytes - pSize;

	// how many more sends required after first for this attachment
	thisAttach->numParts += (int) ((workSize + offset) / offset);
  }
  return;
}
#endif
/*
 * Local Variables:
 * tab-width: 2
 * end:
 */
