head	1.22;
access;
symbols
	V0-99-8:1.21
	V0-99-7:1.19
	V0-99-6:1.18
	V0-99-5:1.18
	V0-99-4:1.18
	V0-99-2:1.17
	V0-99-1:1.17
	V0-93-14:1.17
	V0-93-13:1.17
	V0-93-12:1.17
	V0-93-11:1.17
	V0-93-10:1.17
	V0-93-9:1.17
	V0-93-8:1.17
	V0-93-7:1.17
	V0-93-5:1.16;
locks; strict;
comment	@ * @;


1.22
date	97.02.06.17.38.38;	author dumoulin;	state Exp;
branches;
next	1.21;

1.21
date	96.08.31.06.59.57;	author dumoulin;	state Exp;
branches;
next	1.20;

1.20
date	96.08.13.04.32.38;	author shimomai;	state Exp;
branches;
next	1.19;

1.19
date	95.11.07.22.59.16;	author dumoulin;	state Exp;
branches;
next	1.18;

1.18
date	95.05.19.22.47.55;	author dumoulin;	state Exp;
branches;
next	1.17;

1.17
date	94.11.21.21.20.46;	author jcooper;	state Exp;
branches;
next	1.16;

1.16
date	94.11.10.01.54.55;	author rushing;	state Exp;
branches;
next	;


desc
@jcoop's en/decoding routines
@


1.22
log
@Stub for RCS for deleted file.  wvcoding.c is now wvcoding.cpp
@
text
@This file (WVCODING.C) has been superceded by WVCODING.CPP.  This 
file is being kept around so that the WinVN source control system can 
continue to build older versions of WinVN. 

This text should generate a compile error to any known C/C++ compiler.
@


1.21
log
@cleanup some compile time errors
@
text
@d1 3
a3 32
/********************************************************************
 *                                                                  *
 *  MODULE    :  WVCODING.C                                         *
 *                                                                  *
 *  PURPOSE   : This file contains functions for en/decoding files  *
 *                                                                  *
 *  ENTRY POINTS:                                                   *
 *              DecodeInit  - initializes decoding resources        *
 *              DecodeDone  - frees decoding resources              *
 *              InitCoded   - initializes a coded block             *
 *                            called for each new art to decode     *
 *              DecodeLine  - Main decoding engine.  Called once    *
 *                            for each input line                   *
 *              DecodeFile  - Uses file instead of comm input as a  *
 *                            line provider to the decoding engine  *
 *              DecodeDoc   - Uses a Doc instead of comm input as a *
 *                            line provider to the decoding engine  *
 *      CompleteThisDecode  - Called at end of each article - adds  *
 *                            block to appropriate decode thread,   *
 *                            writes to disk if possible, etc       *
 *           EncodeAndSend  - Encodes a file and sends to net       *
 *       EncodeToTextBlock  - Encodes a file into a text block      *
 *            EncodeToFile  - Encodes a file and stores in new file *
 *                                                                  *
 *  NOTES:                                                          *
 *    Both the max # of batched decode files and the max #          *
 *    threads per file are fixed.  This should be made dynamic      *
 *    at some point I guess.                                        *
 *                                                                  *
 * Author: John S. Cooper (jcooper@@planetz.com)                     *
 *   Date: Sept 13, 1993                                            *
 ********************************************************************/
d5 1
a5 2421
/*
 * $Id: wvcoding.c 1.20 1996/08/13 04:32:38 shimomai Exp $
 */

#include <windows.h>
#include <windowsx.h>

#define WVCODING

#include "wvglob.h"
#include "winvn.h"
#pragma hdrstop
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>				/* for isspace, isalnum, etc */
#include <shellapi.h>			/* for FindExecutable */

#ifdef _DEBUG
#define DEBUG_MAP
#define DEBUG_MEM
#define DEBUG_DECODE
#endif

#define LOW		1				// sequence confidence levels
#define HIGH            2
#define UNUSED		127			// unused mapping slots

#define END_BLOCK	2
/*
 * Globals for this func
 */

char *codingTable;
char customTable[CODINGTABLESIZE+1];
TypDecodeThread *threadList[MAX_DECODE_THREADS];
int numDecodeThreads;			// number of active decode threads

int currentDecodeThread;
int numDumbDecoded;
int thisNumBlocks;				// MIME vars for current block

char thisContentType[80];
char thisContentDesc[MAXINTERNALLINE];
char thisBoundary[MAXINTERNALLINE], thisBoundaryEnded[MAXINTERNALLINE];
int thisContentEncoding, prevContentEncoding;
char prevBlockIdent[MAXFILENAME];

TypTextBlock *EncodeTextBlock;
HFILE hEncodeFile;

/*
 * Private functions
 */
unsigned long Encode (TypAttachment * thisAttach, unsigned long startByte, unsigned long maxBytes, BOOL (*AddLine) (char *));
unsigned long EncodeLine (unsigned char *outLine, unsigned char *line, int start, int num, int encodingType);
void EncodeUnit (unsigned char *out, unsigned char *in, int num, int encodingType);
int GetThreadByName (char *name);
int AddToThreadList ();
void DestroyThread (int num);
void DestroyCodedBlock (TypCoded ** thisCoded);
void InsertBlockInThread (int num, TypCoded * thisBlock, int index);
void DeleteBlockFromThread (int num, int index);
BOOL WriteDecodeThread (int num);
BOOL WriteBlockToFile (int num, TypCoded * thisDecode);
BOOL WriteTopBlockToFile (int num, int *endFlag);
BOOL AddDataToBlock (TypCoded * thisDecode, char *newData, unsigned int dataLen);
void PreAllocDataByLines (TypCoded * thisDecode);
void ParseInfoLine (TypCoded * thisDecode, char *line, BOOL guessIdent);
BOOL ParseAppSpecificLine (TypCoded * thisDecode, char *line);
BOOL ReadSubjectToken (char *dest, char **ptr);
BOOL isnumber (char *str);
BOOL SearchThreadNames (char *name);
int ParseMimeHeaderLine (TypCoded * thisDecode, char *line);
int ParseMimeContentType (TypCoded * thisDecode, char *line);

void CreateCodingStatusWnd (char *title, int show);
int DecodeDataLine (TypCoded * decode, char *line);
void CreateUUTable ();
BOOL TestIgnoreLine (char *line);
int IsDataLine (char *line);
BOOL TestDataLine (char *line);
int ThreadTable (char *dest, char *name);
void UpdateThreadStatus (int num, char *str);

void ExecuteDecodedFile (int num, char *fileName);

#if defined(DEBUG_DECODE) || defined(DEBUG_MEM)
#define DEBUG_FILE "coding.log"
void DebugLog ();
void InitDebugLog ();
char debug[MAXCOMMLINE];
OFSTRUCT debugFileStruct;
#endif

#if defined(DEBUG_DECODE) || defined(DEBUG_MEM)
/* ------------------------------------------------------------------------
 * Debug log handler
 * Open/close the file for every addition to log, so all info is safely
 * captured in case of GPF
 */
void
DebugLog ()
{
  HFILE hDebugFile;

  if ((hDebugFile = OpenFile ((char far *) NULL, &debugFileStruct, OF_REOPEN | OF_WRITE)) >= 0) {
	_llseek (hDebugFile, 0L, 2);	/* go to end */
	_lwrite (hDebugFile, debug, strlen (debug));
	_lclose (hDebugFile);
  }
}
void
InitDebugLog ()
{
  HFILE hDebugFile;

  if ((hDebugFile = OpenFile ((char far *) DEBUG_FILE, &debugFileStruct, OF_CREATE)) >= 0) {
	sprintf (debug, "New log");
	_lwrite (hDebugFile, debug, strlen (debug));
	_lclose (hDebugFile);
  }
}

#endif
/* ------------------------------------------------------------------------
 * Decode a file
 * Uses a file instead of the comm article as a line-provider to the
 * decode routines
 */
void
DecodeFile (HWND hParentWnd)
{
  FILE *DecodeFile;
  char mybuf[MAXINTERNALLINE], fileName[MAXFILENAME], *ptr;
  int result;

  if (AskForExistingFileName (hParentWnd, fileName, "Open Encoded File") == FAIL)
	return;

  DecodeInit ();
  if ((DecodeFile = fopen (fileName, "r")) == NULL) {
	MessageBox (hParentWnd, "Failure to read file", "File error", MB_OK);
	return;
  }
  if ((currentCoded = InitCoded (hParentWnd)) == NULL) {
	MessageBox (hParentWnd, "Unable to continue due to memory constraints.  Aborted", "Init Coded Object Error", MB_OK);
	return;
  }
  hSaveCursor = SetCursor (hHourGlass);
  SetCapture (hParentWnd);

  while (1) {
	if ((ptr = fgets (mybuf, MAXINTERNALLINE, DecodeFile)) == NULL)
	  strcpy (mybuf, "~EOF~");	// make sure wrap up occurs ('~' is not in any char map)

	result = DecodeLine (currentCoded, mybuf);
	if (result == FAIL) {
	  MessageBox (hParentWnd, "Aborted decode", "Problems during decode", MB_OK | MB_ICONEXCLAMATION);
	  goto abortDecodeFile;
	}
	if (result == END_BLOCK) {
	  if (CompleteThisDecode () == FAIL) {
		MessageBox (hParentWnd, "Aborted decode", "Problems during decode", MB_OK | MB_ICONEXCLAMATION);
		goto abortDecodeFile;
	  }
	  if ((currentCoded = InitCoded (hParentWnd)) == NULL) {
		MessageBox (hParentWnd, "Unable to continue due to memory constraints.  Aborted", "Init Coded Object Error", MB_OK);
		goto abortDecodeFile;
	  }
	}
	if (ptr == NULL)			// EOF

	  break;
  }

  if (currentCoded != NULL)		// finish final block

	CompleteThisDecode ();

abortDecodeFile:;
  SetCursor (hSaveCursor);
  ReleaseCapture ();

  fclose (DecodeFile);
  DecodeDone ();
}

/* ------------------------------------------------------------------------
 * DecodeOpenArticles and DecodeDoc
 * Uses a doc instead of the comm article as a line-provider to the
 * decode routines
 * Assumes DecodeInit is already called, and expects caller to call DecodeDone
 */
void
DecodeOpenArticles (HWND hParentWnd)
{
  register int i;
  // the following two functions are in wvgroup.c
  // extern BOOL article_operation (TypDoc far * Doc, long artindex,
  //	   BOOL (*art_fun) (header_p headers, TypGroup * group, long artindex));
  // extern BOOL selected_false (header_p headers, TypGroup * group, long artindex);

  for (i = 0; i < MAXARTICLEWNDS; i++) {
	if (ArticleDocs[i].InUse && (!CommBusy || CommDoc != &ArticleDocs[i])) {
	  // ensure the article is deselected now
	  if (ArticleDocs[i].ParentDoc) {
		article_operation (ArticleDocs[i].ParentDoc, ArticleDocs[i].LastSeenLineID, selected_false);
		InvalidateRect (ArticleDocs[i].ParentDoc->hDocWnd, NULL, FALSE);
	  }
	  DecodeDoc (hParentWnd, &ArticleDocs[i]);
	}
  }
}

void
DecodeDoc (HWND hParentWnd, TypDoc * Document)
{
  int result;
  TypBlock far *BlockPtr;
  TypLine far *LinePtr;

  if ((currentCoded = InitCoded (hParentWnd)) == NULL) {
	MessageBox (hParentWnd, "Unable to continue due to memory constraints.  Aborted", "Init Coded Object Error", MB_OK);
	return;
  }

  hSaveCursor = SetCursor (hHourGlass);
  SetCapture (hParentWnd);
  LockLine (Document->hFirstBlock, sizeof (TypBlock), (TypLineID) 0L, &BlockPtr, &LinePtr);
  while (LinePtr->length != END_OF_BLOCK) {
	result = DecodeLine (currentCoded,GetTextPtr(LinePtr));

	if (result == FAIL) {
	  MessageBox (hParentWnd, "Aborted decode", "Problems during decode", MB_OK | MB_ICONEXCLAMATION);
	  goto abortDecodeDoc;
	}

	if (result == END_BLOCK) {
	  if (CompleteThisDecode () == FAIL) {
		MessageBox (hParentWnd, "Aborted decode", "Problems during decode", MB_OK | MB_ICONEXCLAMATION);
		goto abortDecodeDoc;
	  }
	  if ((currentCoded = InitCoded (hParentWnd)) == NULL) {
		MessageBox (hParentWnd, "Unable to continue due to memory constraints.  Aborted", "Init Coded Object Error", MB_OK);
		goto abortDecodeDoc;
	  }
	}
	NextLine (&BlockPtr, &LinePtr);
  }
  if (currentCoded != NULL)		// finish final block

	CompleteThisDecode ();

abortDecodeDoc:;
  SetCursor (hSaveCursor);
  ReleaseCapture ();
  GlobalUnlock (BlockPtr->hCurBlock);
}
/* ------------------------------------------------------------------------
 *    Encode a file and ask the user for a file to name to store it in
 */
void
EncodeToFile (HWND hParentWnd, TypAttachment * thisAttach)
{
  char fileName[MAXFILENAME];
  HFILE hFile;
  OFSTRUCT ofs;

  fileName[0] = '\0';
  if (AskForNewFileName (hParentWnd, fileName, "", FALSE) != FAIL) {
	if ((hFile = OpenFile ((char far *) fileName, &ofs, OF_CREATE)) < 0) {
	  MessageBox (hParentWnd, "Unable to open output file", "File Error", MB_OK | MB_ICONSTOP);
	}
	else {
	  CreateStatusArea (hParentWnd, "Encoding To File", SW_SHOWNORMAL);
	  hSaveCursor = SetCursor (hHourGlass);
	  SetCapture (hParentWnd);
	  strcpy (currentCoded->ident, thisAttach->fileName);	// status window info

	  if (EncodeIntoFile (hFile, thisAttach, 0, 0) == 0) {
		MessageBox (hParentWnd, "Encode to file failed", "Encode Error", MB_OK | MB_ICONSTOP);
	  }
	  SetCursor (hSaveCursor);
	  ReleaseCapture ();
	  DestroyStatusArea ();
	}
  }
  _lclose (hFile);
}

/* ------------------------------------------------------------------------
 *    Create a status window a coding block used for status info
 *  the currentCoded block is purely for status in this case
 *  This is used by attach and EncodeFile, SendComposition, etc
 *  to show status in progress
 *  Use UpdateBlockStatus to update/display status
 *  Use DestroyStatusArea to clean up when done
 */
void
CreateStatusArea (HWND hParentWnd, char *title, int show)
{
  if ((currentCoded = InitCoded (hParentWnd)) == NULL)
	return;
  CreateCodingStatusWnd (title, show);
  currentCoded->sequence = 1;	// status window info
}

void
DestroyStatusArea ()
{
  if (currentCoded) {
	DestroyCodedBlock (&currentCoded);
  }
  if (hCodedBlockWnd) {
	DestroyWindow (hCodedBlockWnd);
  }
  hCodedBlockWnd = (HWND) NULL;
}

void
CreateCodingStatusWnd (char *title, int show)
{
  char temp[MAXINTERNALLINE];
  int x, y;

  /* Create coding status window, default top center */
  x = (xScreen - STATUSWIDTH) >> 1;
  y = 1;

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

  hCodedBlockWnd = CreateWindowEx (WS_EX_DLGMODALFRAME, "WinVnBlockCoding", title,
				   WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_THICKFRAME,
				   x, y, STATUSWIDTH, STATUSHEIGHT,
					   (HWND) NULL, (HMENU) NULL, hInst, (void far *) NULL);

  SetHandleBkBrush (hCodedBlockWnd, hStatusBackgroundBrush);
  ShowWindow (hCodedBlockWnd, show);
}


/* ------------------------------------------------------------------------
 *    Adds text to a thread coding status block, and refreshes
 *  the thread status window
 */
void
UpdateThreadStatus (int num, char *str)
{
  AddLineToTextBlock (threadList[num]->statusText, str);
  InvalidateRect (threadList[num]->statusText->hTextWnd, NULL, FALSE);
  UpdateWindow (threadList[num]->statusText->hTextWnd);
}

/* ------------------------------------------------------------------------
 *    Refreshes the block status window
 */
void
UpdateBlockStatus ()
{
  if (hCodedBlockWnd) {
	InvalidateRect (hCodedBlockWnd, NULL, FALSE);
	UpdateWindow (hCodedBlockWnd);
  }
}

/* ------------------------------------------------------------------------
 *    Encode a file to file, net, or textblock
 *  Assumes currentCoded is initialized
 *    startByte is file byte to start at (0 == beginning)
 *    maxBytes is # encoded bytes to generate before stopping (0 == no restriction)
 *    AddLine is a pointer to a function which will be used to deal with each
 *      encoded line appropriately (i.e. add to file, send to net...)
 * returns # bytes read from file or 0 on failure
 */
BOOL
EncodeIntoFileHelper (char *str)
{
  return (_lwrite (hEncodeFile, str, lstrlen (str)) == HFILE_ERROR) ? FAIL : SUCCESS;
}

unsigned long
EncodeIntoFile (HFILE hFile, TypAttachment * thisAttach,
				unsigned long startByte, unsigned long maxBytes)
{
  unsigned long result;
  hEncodeFile = hFile;
  CodingState = ATTACH_PROCESSING;
  result = Encode (thisAttach, startByte, maxBytes, (BOOL (*)(char *)) EncodeIntoFileHelper);
  CodingState = INACTIVE;
  return result;
}

unsigned long		// assumes appropriate sockets are opened and initialized
EncodeAndSend (TypAttachment * thisAttach, unsigned long startByte,
			   unsigned long maxBytes)
{
  unsigned long result;
  int oldState;
//  extern BOOL SendOneLine (char *str);	// in wvattach.c

  oldState = CodingState;
  CodingState = ATTACH_SENDING;
  UpdateBlockStatus ();
  result = Encode (thisAttach, startByte, maxBytes, SendOneLine);
  CodingState = oldState;
  UpdateBlockStatus ();
  return result;
}


#if 0							// No one is using this right now
BOOL
EncodeToTextBlockHelper (char *str)
{
  return AddLineToTextBlock (EncodeTextBlock, str);
}

unsigned long
EncodeToTextBlock (TypTextBlock * textBlock, TypAttachment * thisAttach,
				   unsigned long startByte, unsigned long maxBytes)
{
  int result;
  EncodeTextBlock = textBlock;
  CodingState = ATTACH_PROCESSING;
  result = Encode (thisAttach, startByte, maxBytes, (BOOL (*)(char *) EncodeToTextBlockHelper));
  CodingState = INACTIVE;
  return result;
}
#endif

unsigned long
Encode (TypAttachment * thisAttach, unsigned long startByte,
		unsigned long maxBytes, BOOL (*AddLine) (char *))
{
  HFILE hFile;
  int numRead, lineLen, start;
  register int i;
  unsigned char inBuf[ENCODE_LINE_LEN];
  unsigned char outLine[MAXINTERNALLINE];
  unsigned long totalNumAdded, totalNumRead;
  BOOL done;
  int len, encodingType;
  extern char *NameWithoutPath (char *tempName, char *fileName);

  totalNumAdded = totalNumRead = 0;
  encodingType = EncodingTypeToNum (thisAttach->encodingType);

  // Set up encoding map
  switch (encodingType) {
  case CODE_BASE64:
	codingTable = base64Table;
	break;
  case CODE_UU:
	codingTable = uuTable;
	break;
  case CODE_XX:
//  Note, XX table is hard coded in declarations for simplicity
	codingTable = xxTable;
	break;
  case CODE_CUSTOM:
	codingTable = UserCodingTable;
	// add 2 table lines
	if ((*AddLine) ("table\r\n"))
	  return (0);
	memmove (str, UserCodingTable, 32);
	str[32] = '\r';
	str[33] = '\n';
	str[34] = '\0';
	if ((*AddLine) (str))
	  return (0);
	memmove (str, &UserCodingTable[32], 32);
	if ((*AddLine) (str))
	  return (0);

	totalNumAdded += 75;		// table + \r\n + 32 + \r\n + 32 + \r\n

	currentCoded->numBytes += 75;
	currentCoded->numLines += 3;
	break;
  }

  // Prepare for encoding
  switch (encodingType) {
  case CODE_UU:
  case CODE_XX:
  case CODE_CUSTOM:
	// these are 3-to-4 encodings with 1st char indicating line len
	// and block starts with 'begin' line and ends with 'end' line
	// add 'begin' line
	if (startByte == 0) {
	  NameWithoutPath (str, thisAttach->fileName);
	  len = _snprintf ((char*)outLine, MAXINTERNALLINE, "begin 755 %s\r\n", str);
	  if ((*AddLine) ((char*)outLine))
		return (0);

	  totalNumAdded += len;
	  currentCoded->numBytes += len;
	  currentCoded->numLines++;
	}
	// set 1st char to appropriate line length value
	outLine[0] = codingTable[ENCODE_LINE_LEN];
	start = 1;					// count includes the count char itself

	break;
  case CODE_BASE64:
	// base64 is a 3-to-4 encoding, with no line len indicator and
	// no 'begin' or 'end' lines
	start = 0;
  }
  lineLen = ENCODE_LINE_LEN;

  // open the file and have at it
  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;
  while (!done && (!maxBytes || totalNumAdded < maxBytes)) {
	if ((numRead = _lread (hFile, inBuf, lineLen)) == HFILE_ERROR) {
	  _lclose (hFile);
	  return (0);
	}
	if (numRead < lineLen) {	// change last line length value

	  if (encodingType != CODE_BASE64) {
		outLine[0] = codingTable[numRead];
	  }
	  for (i = numRead; i < lineLen; i++) {
		inBuf[i] = 0;
	  }
	  done = TRUE;
	}
	EncodeLine (outLine, inBuf, start, numRead, encodingType);
	strcat ((char*)outLine, "\r\n");
	if ((*AddLine) ((char*)outLine)) {
	  _lclose (hFile);
	  return (0);
	}
	totalNumAdded += strlen ((char*)outLine);
	currentCoded->numLines++;
	currentCoded->numBytes += numRead;
	totalNumRead += numRead;

	if (currentCoded->numLines % STATUS_UPDATE_FREQ == 0)
	  UpdateBlockStatus ();
  }
  _lclose (hFile);

  if (done) {
	switch (encodingType) {
	case CODE_UU:
	case CODE_XX:
	case CODE_CUSTOM:
	  // add zero length line
	  outLine[0] = codingTable[0];
	  outLine[1] = '\r';
	  outLine[2] = '\n';
	  outLine[3] = '\0';
	  if ((*AddLine) ((char*)outLine))
		return (0);
	  if ((*AddLine) ("end\r\n"))
		return (0);
	  currentCoded->numLines += 2;
	}
  }
  UpdateBlockStatus ();

  return (totalNumRead);
}

/* ------------------------------------------------------------------------
 *    Encode num chars from line
 *  Put data in outLine starting at start
 */
unsigned long
EncodeLine (unsigned char *outLine, unsigned char *line, int start, int num, int encodingType)
{
  register int i, j;

  for (j = start, i = 0; i < num; j += 4, i += 3)
	EncodeUnit (&outLine[j], &line[i], (i + 3 > num) ? num - i : 3, encodingType);

  outLine[j] = '\0';
  return (j);
}

void
EncodeUnit (unsigned char *out, unsigned char *in, int num, int encodingType)
{
  out[0] = codingTable[((in[0] >> 2) & 63)];
  out[1] = codingTable[(((in[0] << 4) | (in[1] >> 4)) & 63)];
  if (num == 1) {
	if (encodingType == CODE_BASE64) {
	  strcpy ((char*)&out[2], "==");
	}
	else {
	  out[2] = 0;
	}
	return;
  }

  out[2] = codingTable[(((in[1] << 2) | (in[2] >> 6)) & 63)];
  if (num == 2) {
	if (encodingType == CODE_BASE64) {
	  strcpy ((char*)&out[3], "=");
	}
	else {
	  out[3] = 0;
	}
	return;
  }

  out[3] = codingTable[(in[2] & 63)];
}
/* ------------------------------------------------------------------------
 *    Init for decoding
 *  Initialize the coding tables
 *  Init threadList:  list of pointers to Thread lists
 *  (one thread for each file being decoded)
 *
 */
void
DecodeInit ()
{
  register int i;

#ifdef DEBUG_MEM
  InitDebugLog ();
#endif

  for (i = 0; i < MAX_DECODE_THREADS; i++)
	threadList[i] = NULL;

  currentCoded = (TypCoded *) NULL;
  CodingState = DECODE_SKIPPING;
  numDecodeThreads = 0;
  numDumbDecoded = 0;
  currentDecodeThread = -1;
  thisNumBlocks = -1;
  prevBlockIdent[0] = '\0';
  thisContentEncoding = CODE_UNKNOWN;
  prevContentEncoding = CODE_UNKNOWN;
  customTable[0] = '\0';

#ifdef DEBUG_MEM
  sprintf (debug, "\nInitialized decoding, %d threads", MAX_DECODE_THREADS);
  DebugLog ();
#endif
  // Create coding status window (open for duration of coding only)
  CreateCodingStatusWnd ("Decoding Status",
				MinimizeStatusWindows ? SW_SHOWMINNOACTIVE : SW_SHOWNORMAL);
  UpdateBlockStatus ();

}

/* ------------------------------------------------------------------------
 * At this point, all threads in sequence should have been written and freed,
 * If any threads left, we either had an incomplete decode of some file,
 * or the file was encoded w/out sequence info, and we were waiting to
 * hopefully receive it all.  If we had an endflag, then write it in order
 * received
 * Accepts handle of parent window so it can display any 'incomplete' messages
 */
void
DecodeDone ()
{
  char mybuf[MAXINTERNALLINE], name[MAXINTERNALLINE];
  int i;
#ifdef DEBUG_MEM
  sprintf (debug, "\nDone coding,  Wrapping up stray threads");
  DebugLog ();
#endif
  name[0] = '\0';				// only show name if not verbose mode

  while (numDecodeThreads > 0)	// always work on top of list [0]
   {
	if (!CodingStatusVerbose) {
	  if (threadList[0]->name[0] != '\0')
		_snprintf (name, MAXINTERNALLINE, "%s   ", threadList[0]->name);
	  else
		_snprintf (name, MAXINTERNALLINE, "%s   ", threadList[0]->ident);
	}

	if (threadList[0]->numBlocks == 0) {	// premature end of file

	  WriteDecodeThread (0);
	  _snprintf (mybuf, MAXINTERNALLINE,
				 "%sDecode is missing parts, written total size %ld", name, threadList[0]->totalBytes);
	  UpdateThreadStatus (0, mybuf);
	}
	else if (threadList[0]->contentEncoding != CODE_BASE64 &&
	!threadList[0]->codedBlockList[threadList[0]->numBlocks - 1]->endFlag) {
	  _snprintf (mybuf, MAXINTERNALLINE, "%sEnd never found.  Cancelling", name);
	  UpdateThreadStatus (0, mybuf);
	}
	else if (threadList[0]->expectedNumBlocks > 0) {	// there IS sequencing info, so we should not be here

	  WriteDecodeThread (0);
	  _snprintf (mybuf, MAXINTERNALLINE,
				 "%sDecode is missing parts, written total size %ld", name, threadList[0]->totalBytes);
	  UpdateThreadStatus (0, mybuf);
	}
	else {
	  _snprintf (mybuf, MAXINTERNALLINE,
				 "%sCompleteness confidence is medium, written total size %ld", name, threadList[0]->totalBytes);
	  UpdateThreadStatus (0, mybuf);
	  WriteDecodeThread (0);
	}
	DestroyThread (0);			// decrements numDecodeThreads

  }
  for (i = 0; i < NumStatusTexts; i++)
	CodingStatusText[i]->IsBusy = FALSE;

  currentCoded = NULL;
  CodingState = INACTIVE;
  CommDecoding = FALSE;
  DestroyWindow (hCodedBlockWnd);
  hCodedBlockWnd = (HWND) NULL;
}

/* ------------------------------------------------------------------------
 *    Initialize a coded object, and initial data space
 *  Returns ptr to object or NULL if failed
 */
TypCoded *
InitCoded (HWND hParentWnd)
{
  TypCoded *thisCoded = NULL;

#ifdef DEBUG_MEM
  sprintf (debug, "\nInitializing a coded object");
  DebugLog ();
#endif
  if ((thisCoded = (TypCoded *) GlobalAllocPtr (GMEM_MOVEABLE, sizeof (TypCoded))) == NULL)
	return ((TypCoded *) NULL);

#ifdef DEBUG_MEM
  sprintf (debug, "\nAllocing data size %d", BASE_BLOCK_SIZE);
  DebugLog ();
#endif
  if ((thisCoded->data = (char huge *) GlobalAllocPtr (GMEM_MOVEABLE, BASE_BLOCK_SIZE * sizeof (char))) == NULL)
	  return ((TypCoded *) NULL);

  thisCoded->maxBytes = BASE_BLOCK_SIZE;
  thisCoded->numBytes = 0;
  thisCoded->numLines = 0;
  thisCoded->sequence = -1;
  thisCoded->seqConfidence = 0;
  thisCoded->estNumLines = 0;
  thisCoded->name[0] = '\0';
  thisCoded->ident[0] = '\0';
  thisCoded->endFlag = thisCoded->beginFlag = FALSE;
  thisCoded->hParentWnd = hParentWnd;

  return (thisCoded);
}

/* ------------------------------------------------------------------------
 *    Destroy a thread
 *  Destroy all blocks in the thread
 *  Remove the thread from the threadList
 */
void
DestroyThread (int num)
{
  register int i;

#ifdef DEBUG_MEM
  sprintf (debug, "\nDestroying thread %d", num);
  DebugLog ();
#endif
  if (CodingStatusVerbose)
	threadList[num]->statusText->IsBusy = FALSE;

  for (i = 0; i < threadList[num]->numBlocks; i++)
	DestroyCodedBlock (&(threadList[num]->codedBlockList[i]));

  GlobalFreePtr (threadList[num]);

  for (i = num; i < numDecodeThreads; i++)
	threadList[i] = threadList[i + 1];

  numDecodeThreads--;
  if (currentDecodeThread == num)
	currentDecodeThread = max (0, currentDecodeThread - 1);
}
/* ------------------------------------------------------------------------
 *    Destroy a coded block structure
 *  Free the huge data.  Takes a ptr to a ptr so it can NULL it when done
 */
void
DestroyCodedBlock (TypCoded ** thisCoded)
{
#ifdef DEBUG_MEM
  sprintf (debug, "\nDestroying block sequence %d", (*thisCoded)->sequence);
  DebugLog ();
#endif
  GlobalFreePtr ((*thisCoded)->data);
  GlobalFreePtr (*thisCoded);
  *thisCoded = NULL;
}

/* ------------------------------------------------------------------------
 *    Given a file name, find the associated thread number
 *  Returns -1 if no thread by that name exists
 */
int
GetThreadByName (char *name)
{
  int i, result;

  for (result = -1, i = 0; i < numDecodeThreads && result == -1; i++)
	if (!_stricmp (name, threadList[i]->name))
	  result = i;

  return (result);
}
/* ------------------------------------------------------------------------
 *    Determine if a thread has already been started for this ident
 *  if yes, add decode object to that thread
 *  otherwise, alloc a new thread list structure, add this block to it
 *  At end, currentDecodeThread is set to handle of thread
 */
BOOL
AddToThreadList ()
{
  register int i;
  int num;
  char mybuf[MAXINTERNALLINE];
  int x, y, width, height, maxX, maxY;

  if (DumbDecode) {
	if (numDecodeThreads == 0)
	  num = -1;
	else
	  num = 0;
  }
  else
	/* Search existing threads for an ident match.  If
	 * no match found, or no threads exist yet, add new thread.
	 */
	for (num = -1, i = 0; i < numDecodeThreads; i++)
	  if (!_stricmp (currentCoded->ident, threadList[i]->ident))
		num = i;

  if (num == -1) {				/* add a new thread */
	if ((numDecodeThreads + 1) > MAX_DECODE_THREADS) {
	  return (FAIL);
/*          GlobalUnlock(decodeObjectHandles);
   decodeObjectHandles = GlobalRealloc (decodeFileHandles,
   (numDecodeThreads + LIST_SIZE_INC) * sizeof (HGLOBAL),
   GMEM_MOVEABLE);
   threadList = (HGLOBAL *) GlobalLock (decodeFileHandles);
 */
	}

// create a new DecodeThreadStruct object, store its handle in threadList,
#ifdef DEBUG_MEM
	sprintf (debug, "\nCreating new thread ident %s", currentCoded->ident);
	DebugLog ();
#endif
	num = numDecodeThreads;
	numDecodeThreads++;

	threadList[num] = (TypDecodeThread *) GlobalAllocPtr (GMEM_MOVEABLE, sizeof (TypDecodeThread));
	threadList[num]->expectedNumBlocks = 0;
	threadList[num]->numBlocksWritten = 0;
	threadList[num]->numBlocks = 0;
	threadList[num]->totalBytes = 0;
	threadList[num]->dosFileName[0] = '\0';
	if (thisContentEncoding == CODE_CUSTOM)
	  strncpy (threadList[num]->customTable, customTable, CODINGTABLESIZE);
	threadList[num]->contentEncoding = thisContentEncoding;
	strcpy (threadList[num]->ident, currentCoded->ident);
	if (currentCoded->name[0] != '\0')
	  strcpy (threadList[num]->name, currentCoded->name);
	else
	  threadList[num]->name[0] = '\0';

	if (CodingStatusVerbose || NumStatusTexts == 0) {
	  if ((threadList[num]->statusText = InitTextBlock (hCodedBlockWnd)) == NULL)
		MessageBox (hCodedBlockWnd, "Memory allocation error in text block", "Memory Error", MB_OK);

	  if (NumStatusTexts + 1 < MAX_DECODE_THREADS)
	  CodingStatusText[NumStatusTexts++] = threadList[num]->statusText;

	  width = xScreen >> 1;
	  height = (yScreen >> 1) - CaptionHeight;
	  maxX = 3 * (width >> 1);	/* 3/4 screen width  */
	  maxY = 3 * (height >> 1);	/* 3/4 screen height */

	  x = 1;
	  y = yScreen >> 2;

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

	  if (CodingStatusVerbose)
		_snprintf (mybuf, MAXINTERNALLINE, "Decoding Status for file %s", (threadList[num]->name[0] != '\0') ? threadList[num]->name : threadList[num]->ident);
	  else
		strcpy (mybuf, "Decoding Status");

	  threadList[num]->statusText->hTextWnd =
		CreateWindow ("WinVnCoding", mybuf,
					  WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
					  (x + (NumStatusTexts * StatusCharWidth)) % maxX,
					  (y + (NumStatusTexts * StatusLineHeight)) % maxY,
					  width, height,
					  (HWND) NULL, (HMENU) NULL, hInst, (void far *) NULL);

	  if (threadList[num]->statusText->hTextWnd == (HWND) NULL) {
		FreeTextBlock (threadList[num]->statusText);
		MessageBox (hCodedBlockWnd, "Couldn't create status text window", "Window Creation Error", MB_OK);
		return (FAIL);
	  }
	  SetHandleBkBrush (threadList[num]->statusText->hTextWnd,
						hStatusBackgroundBrush);
	  ShowWindow (threadList[num]->statusText->hTextWnd,
				MinimizeStatusWindows ? SW_SHOWMINNOACTIVE : SW_SHOWNORMAL);
	  UpdateWindow (threadList[num]->statusText->hTextWnd);
	}
	else
	  threadList[num]->statusText = CodingStatusText[0];


	threadList[num]->statusText->IsBusy = TRUE;

  }

/* If this new block enlightens us with any useful thread info, update
 * the thread
 */
  if (currentCoded->name[0] != '\0')
	strcpy (threadList[num]->name, currentCoded->name);
//  if (threadList[num]->contentType[0] == '\0')
  //      strcpy (threadList[num]->contentType, thisContentType);
  if (threadList[num]->expectedNumBlocks == 0 && thisNumBlocks > 0)
	threadList[num]->expectedNumBlocks = thisNumBlocks;

  threadList[num]->totalBytes += currentCoded->numBytes;
/*
 * Now, add this decode object to the thread codedBlockList
 * Insert in correct sequence.
 * If no sequence info available, add to end of list, but before any end block
 */
  if (DumbDecode)
	InsertBlockInThread (num, currentCoded, threadList[num]->numBlocks);
  else {
	if (threadList[num]->numBlocks != 0 && currentCoded->sequence == -1) {	/* sequence unknown, add at end or before end block */
	  if (currentCoded->beginFlag)
		InsertBlockInThread (num, currentCoded, 0);
	  else {
		if (threadList[num]->codedBlockList[threadList[num]->numBlocks - 1]->endFlag)
		  InsertBlockInThread (num, currentCoded, threadList[num]->numBlocks - 1);
		else
		  InsertBlockInThread (num, currentCoded, threadList[num]->numBlocks);
	  }
	}
	else {
	  for (i = 0; i < threadList[num]->numBlocks; i++) {
		if (threadList[num]->codedBlockList[i]->sequence == -1 ||
			threadList[num]->codedBlockList[i]->sequence >= currentCoded->sequence)
		  break;
	  }
	  InsertBlockInThread (num, currentCoded, i);
	}
  }
  currentDecodeThread = num;
  if (CodingStatusVerbose) {
	sprintf (mybuf, "Decoded block");
	if (currentCoded->sequence == -1) {
	  if (currentCoded->beginFlag || currentCoded->endFlag) {
		if (currentCoded->beginFlag)
		  strcat (mybuf, ", begin block");
		if (currentCoded->endFlag)
		  strcat (mybuf, ", end block");
	  }
	  else
		strcat (mybuf, " sequence unknown");
	}
	else {
	  sprintf (str, " %d", currentCoded->sequence);
	  strcat (mybuf, str);
	  if (threadList[num]->expectedNumBlocks > 0) {
		sprintf (str, " of %d.", threadList[num]->expectedNumBlocks);
		strcat (mybuf, str);
	  }
	  if (currentCoded->sequence == threadList[num]->numBlocksWritten + 1)
		strcat (mybuf, " In sequence");
	  else
		strcat (mybuf, " Out of sequence");
	}
	UpdateThreadStatus (num, mybuf);
  }
  thisNumBlocks = -1;

  return (SUCCESS);
}

/* ------------------------------------------------------------------------
 *    Insert/delete a block in a thread list
 *  Inserts and deletes will be O(n).  Since n will never be big,
 *  it's not worth the trouble (and space usage) of using a linked list
 *
 *  index should never be -1 (case of unkown sequence handled by caller)
 *  to insert at front of list, index = 0
 *  to insert at end of list, index = threadList[num]->numBlocks
 */
void
InsertBlockInThread (int num, TypCoded * block, int index)
{
  register int i;

  for (i = threadList[num]->numBlocks; i > index && i > 0; i--)
	threadList[num]->codedBlockList[i] = threadList[num]->codedBlockList[i - 1];

#ifdef DEBUG_DECODE
  sprintf (debug, "\nInserting into thread %s at posn %d", threadList[num]->ident, i);
  DebugLog ();
#endif
  threadList[num]->codedBlockList[i] = block;
  threadList[num]->numBlocks++;
  return;
}

void
DeleteBlockFromThread (int num, int index)
{
  register int i;

  for (i = index; i < threadList[num]->numBlocks; i++)
	threadList[num]->codedBlockList[i] = threadList[num]->codedBlockList[i + 1];

#ifdef DEBUG_DECODE
  sprintf (debug, "\nRemoved from thread %s at posn %d", threadList[num]->name, index);
  DebugLog ();
#endif
  threadList[num]->numBlocks--;

  return;
}

/* ------------------------------------------------------------------------
 *    Complete a decode block
 *  Adds to thread list
 *  Writes block to disk if possible (and any others now in sequence)
 *  If it ends a thread, make sure everything is written, and
 *  free up the thread
 */
BOOL
CompleteThisDecode ()
{
  char mybuf[MAXINTERNALLINE];
  int num, singleBlockDone, foundEnd;

  UpdateBlockStatus ();			// final size displayed
#ifdef DEBUG_DECODE
  sprintf (debug, "\nCompleteThisDecode for file %s, block %d",
		   currentCoded->ident, currentCoded->sequence);
  DebugLog ();
#endif
  CodingState = DECODE_SKIPPING;

  if (currentCoded->numBytes == 0) {
#ifdef DEBUG_DECODE
	sprintf (debug, "\nNon-data block.  Discarding it.");
	DebugLog ();
#endif
	DestroyCodedBlock (&currentCoded);
	return (SUCCESS);
  }

  if (DumbDecode) {
	if (numDumbDecoded == 0 && !currentCoded->beginFlag)
	  return (SUCCESS);			// Dumb skipping a data block

	if (numDumbDecoded > 0 && currentCoded->beginFlag) {	// found next begin without an end

	  num = currentDecodeThread;
	  if (CodingStatusVerbose)
		_snprintf (mybuf, MAXINTERNALLINE,
				   "Decode missing end, total size %ld, wrote to file %s", threadList[num]->totalBytes, threadList[num]->dosFileName);
	  else
		_snprintf (mybuf, MAXINTERNALLINE,
				"%s   Decode missing end, total size %ld, wrote to file %s",
				   threadList[num]->name, threadList[num]->totalBytes, threadList[num]->dosFileName);
	  UpdateThreadStatus (num, mybuf);
	  DestroyThread (num);
	  numDumbDecoded = 0;
	}
  }
  else
	// if for some reason (?) we didn't get a begin, but this is block 1
	if (currentCoded->sequence == 1 &&
		currentCoded->seqConfidence == HIGH &&
		!currentCoded->beginFlag) {
	currentCoded->beginFlag = TRUE;
	strcpy (currentCoded->name, currentCoded->ident);
  }

  if (AddToThreadList () == FAIL)
	return (FAIL);

  num = currentDecodeThread;

  if (threadList[num]->contentEncoding == CODE_BASE64 &&
	  threadList[num]->expectedNumBlocks != 0 &&
	  currentCoded->sequence == threadList[num]->expectedNumBlocks) {
	/* base-64 end flag if last block */
	currentCoded->endFlag = TRUE;
  }

  prevContentEncoding = thisContentEncoding;
  thisContentEncoding = CODE_UNKNOWN;

  singleBlockDone = currentCoded->beginFlag && currentCoded->endFlag;

  if (!CodingStatusVerbose &&
	  (threadList[num]->numBlocksWritten == 0 && threadList[num]->numBlocks == 1)) {
	_snprintf (mybuf, MAXINTERNALLINE,
			   "%s   Decode in progress", (threadList[num]->name[0] != '\0') ? threadList[num]->name : threadList[num]->ident);
	UpdateThreadStatus (num, mybuf);
  }

  if (DumbDecode) {
	if (WriteTopBlockToFile (num, &foundEnd) == FAIL)
	  return (FAIL);
	numDumbDecoded++;
  }
  else {
/* If this block is both begin and end, then go straight to WriteDecodeThread
 * If currentCoded is in sequence, then it was added as block 0 in the
 * thread's block list, and may have caused other blocks to now be in
 * sequence as well.  Write all blocks which are in sequence now.
 */
	if (!singleBlockDone && currentCoded->sequence != -1 &&
		currentCoded->seqConfidence != 0)
	  while (threadList[num]->numBlocks != 0) {
		if (threadList[num]->codedBlockList[0]->sequence == threadList[num]->numBlocksWritten) {	// skip duplicate block sequence

		  DestroyCodedBlock (&currentCoded);	// threadList[num]->codedBlockList[0]

		  DeleteBlockFromThread (num, 0);
		  break;
		}
		if (threadList[num]->codedBlockList[0]->sequence != threadList[num]->numBlocksWritten + 1)
		  break;
#ifdef DEBUG_DECODE
		sprintf (debug, "\nBlock in sequence, writing it, file %s, block %d",
		threadList[num]->name, threadList[num]->codedBlockList[0]->sequence);
		DebugLog ();
#endif
		if (WriteTopBlockToFile (num, &foundEnd) == FAIL)
		  return (FAIL);
	  }
  }
  if (singleBlockDone || (foundEnd && DumbDecode) ||
	  (foundEnd && threadList[num]->expectedNumBlocks > 0 &&
  threadList[num]->numBlocksWritten >= threadList[num]->expectedNumBlocks)) {
#ifdef DEBUG_DECODE
	sprintf (debug, "\nThread complete: file %s", threadList[num]->name);
	DebugLog ();
#endif
	if (WriteDecodeThread (num) == FAIL) {
	  DestroyThread (num);
	  currentCoded = NULL;
	  return (FAIL);
	}

	if (CodingStatusVerbose)
	  _snprintf (mybuf, MAXINTERNALLINE,
				 "Decode complete, total size %ld, wrote to file %s", threadList[num]->totalBytes, threadList[num]->dosFileName);
	else
	  _snprintf (mybuf, MAXINTERNALLINE,
				 "%s   Decode complete, total size %ld, wrote to file %s",
				 threadList[num]->name, threadList[num]->totalBytes, threadList[num]->dosFileName);
	UpdateThreadStatus (num, mybuf);

	DestroyThread (num);
	numDumbDecoded = 0;
  }
  currentCoded = NULL;
  return (SUCCESS);
}

/* ------------------------------------------------------------------------
 *    Write data for the top block of a thread to a file, and remove
 *  the top block from the thread
 */
BOOL
WriteTopBlockToFile (int num, int *endFlag)
{
  char mybuf[MAXINTERNALLINE];

  if (WriteBlockToFile (num, threadList[num]->codedBlockList[0]) == FAIL) {
	_snprintf (mybuf, MAXINTERNALLINE,
			   "Could not write to file %s", threadList[num]->dosFileName);
	UpdateThreadStatus (num, mybuf);
	DestroyThread (num);
	return (FAIL);
  }

  *endFlag = threadList[num]->codedBlockList[0]->endFlag;
  DestroyCodedBlock (&(threadList[num]->codedBlockList[0]));
  DeleteBlockFromThread (num, 0);
  threadList[num]->numBlocksWritten++;
  return (SUCCESS);
}

/* ------------------------------------------------------------------------
 *    Write data for a thread to a file
 */
BOOL
WriteDecodeThread (int num)
{
  register int i;
  char mybuf[MAXINTERNALLINE];

  for (i = 0; i < threadList[num]->numBlocks; i++) {
#ifdef DEBUG_DECODE
	sprintf (debug, "\nSaving sequence %d", threadList[num]->codedBlockList[i]->sequence);
	DebugLog ();
#endif
	if (WriteBlockToFile (num, threadList[num]->codedBlockList[i]) == FAIL) {
	  _snprintf (mybuf, MAXINTERNALLINE,
				 "Could not write to file %s", threadList[num]->dosFileName);
	  UpdateThreadStatus (num, mybuf);
	  return (FAIL);
	}
  }
  if (ExecuteDecodedFiles)
	ExecuteDecodedFile (num, threadList[num]->dosFileName);

  return (SUCCESS);
}

/* ------------------------------------------------------------------------
 *    Write block data to a file
 *  Verify thread file name will work in DOS, and request a new file
 *  name if necessary
 *  Accepts handle of parent window to allow any necessary dialogs
 */
BOOL
WriteBlockToFile (int num, TypCoded * thisDecode)
{
  register unsigned long i;
  unsigned int chunkSize;
  char actualFileName[MAXFILENAME], mybuf[MAXINTERNALLINE];
  OFSTRUCT outFileStruct;
  HFILE hWriteFile;
  UINT fileMode;

#define	CHUNK_SIZE 65500		/* must not exceed UINT (65534) */

  if (threadList[num]->dosFileName[0] != '\0') {
	fileMode = OF_WRITE;
	if (strchr (threadList[num]->dosFileName, '\\') == NULL) {
	  strcpy (actualFileName, DecodePathName);
	  if (actualFileName[strlen (actualFileName) - 1] != '\\')
		strcat (actualFileName, "\\");
	  strcat (actualFileName, threadList[num]->dosFileName);
	}
	else
	  strcpy (actualFileName, threadList[num]->dosFileName);
  }
  else {
	/* First time write to file
	 */
	fileMode = OF_CREATE;

	*actualFileName = '\0';
	if (threadList[num]->name[0] != '\0')
	  strcpy (threadList[num]->dosFileName, threadList[num]->name);
	else if (threadList[num]->ident[0] != '\0')
	  strcpy (threadList[num]->dosFileName, threadList[num]->ident);

	if (threadList[num]->dosFileName[0] == '\0')
	  return (FAIL);

	strcpy (actualFileName, DecodePathName);
	if (actualFileName[strlen (actualFileName) - 1] != '\\')
	  strcat (actualFileName, "\\");
	strcat (actualFileName, threadList[num]->dosFileName);

	if (SmartFile (hCodedBlockWnd, actualFileName) == FAIL)
	  return (FAIL);
  }
  strcpy (threadList[num]->dosFileName, actualFileName);

  if ((hWriteFile = OpenFile ((char far *) actualFileName, &outFileStruct, fileMode)) < 0) {
	/* last chance to fix... smart filer didn't get a good name... */
	if (AskForNewFileName (hCodedBlockWnd, actualFileName, NULL, FALSE) == FAIL) {
		MessageBox (hCodedBlockWnd, "Unable to open output file", "File Error", MB_OK | MB_ICONSTOP);
		return (FAIL);
	}
  }
  if (fileMode == OF_WRITE)
	_llseek (hWriteFile, 0L, 2);	/* append */

  for (i = 0L; i < thisDecode->numBytes; i += chunkSize) {
	if (i + CHUNK_SIZE > thisDecode->numBytes)
	  chunkSize = (unsigned int) (thisDecode->numBytes - i);
	else
	  chunkSize = CHUNK_SIZE;

	if (_lwrite (hWriteFile, &(thisDecode->data[i]), chunkSize) != chunkSize) {
	  MessageBox (hCodedBlockWnd, "Error writing to file", "File Error", MB_OK | MB_ICONSTOP);
	  _lclose (hWriteFile);
	  return (FAIL);
	}
  }

  if (CodingStatusVerbose) {
	strcpy (mybuf, "     Wrote block ");
	if (thisDecode->sequence > 0) {
	  sprintf (str, "%d ", thisDecode->sequence);
	  strcat (mybuf, str);
	}
//      sprintf (str, "to file %s", threadList[num]->dosFileName);
	//      strcat (mybuf, str);
	UpdateThreadStatus (num, mybuf);
  }
  _lclose (hWriteFile);
  return (SUCCESS);
}

/* ------------------------------------------------------------------------
 *    Decode a line in article.  Store in thisDecode
 */
BOOL
DecodeLine (TypCoded * thisDecode, char *line)
{
  register int i;
  static int table_count;
  char thisBlockName[MAXINTERNALLINE], *ptr;
  int mode;

  /* ignore blank lines */
  if (*line == '\0' || (*line == '\n' && *(line + 1) == '\0') ||
	  (*line == '\r' && *(line + 1) == '\n' && *(line + 2) == '\0'))
	return (SUCCESS);

  thisDecode->numLines++;
  if (thisDecode->numLines % STATUS_UPDATE_FREQ == 0)
	UpdateBlockStatus ();

  switch (CodingState) {
  case DECODE_SKIPPING:
	if (strlen (line) < 3)		// if we're skipping, ignore any really short line
	  return (SUCCESS);

	// encoded UU/XX/Custom data always starts with line
	// like 'begin <mode> <file-name>'
	if (!strncmp (line, "begin", 5) &&
		sscanf (line, "%*s %d %s", &mode, thisBlockName) == 2) {
#ifdef DEBUG_DECODE
	  sprintf (debug, "\nStart of new encoded file %s, mode %d", thisBlockName, mode);
	  DebugLog ();
#endif
	  if (thisBlockName[0] != '\0') {
		NameWithoutPath (thisDecode->name, strlwr (thisBlockName));
	  }
	  else {
		NameWithoutPath (thisDecode->name, thisDecode->ident);
	  }

	  thisDecode->sequence = 1;
	  thisDecode->beginFlag = TRUE;
//          thisDecode->mode = mode;

	  PreAllocDataByLines (thisDecode);
	  CodingState = DECODE_PROCESSING;
	  return (SUCCESS);
	}
/* Check for keywords in the non-data lines which may aid in describing
 * the name for this data, the sequence, a custom table, etc
 */
	else if (!DumbDecode && !_strnicmp (line, "subject:", 8))
	  ParseInfoLine (thisDecode, &line[8], TRUE);
	else if (!DumbDecode && !_strnicmp (line, "summary:", 8))
	  ParseInfoLine (thisDecode, &line[8], FALSE);
	else if (!UsingMIME && !_strnicmp (line, "MIME-Version:", 13))
	  UsingMIME = TRUE;
	else if (!DumbDecode && ParseAppSpecificLine (thisDecode, line))
	  return (SUCCESS);
	else if (!DumbDecode && !_strnicmp (line, "lines:", 6)) {
	  for (ptr = &line[6]; isspace (*ptr); ptr++);	/* skip spaces */
	  thisDecode->estNumLines = atol (ptr);
	  return (SUCCESS);
	}
	else if (!strncmp (line, "table", 5)) {		// next two lines are table

	  CodingState = DECODE_GET_TABLE;
	  table_count = 0;
	  return (SUCCESS);
	}

/*   else if (ParseMimeHeaderLine(thisDecode, line) == SUCCESS)
             return (SUCCESS);   */
 
// Skipping to find new block of data. Skip until a data line 
//   (a line with the correct length) is found
	else if (IsDataLine (line)) {
	  // found good line, process it and continue processing
#ifdef DEBUG_DECODE
	  sprintf (debug, "\nFound start of next data section");
	  DebugLog ();
#endif
	  PreAllocDataByLines (thisDecode);
	  CodingState = DECODE_PROCESSING;
	  return (DecodeDataLine (thisDecode, line));
	}
#ifdef DEBUG_DECODE
	sprintf (debug, "\nskipped: %s", line);
	DebugLog ();
#endif
	return (SUCCESS);			// continue skipping

  case DECODE_GET_TABLE:
	// This is one of two lines containing table info
	memmove ((customTable + table_count * 32), line, 32);

	if (++table_count == 2) {
#ifdef DEBUG_MAP
	  strncpy (str, customTable, CODINGTABLESIZE);
	  str[CODINGTABLESIZE] = '\0';
	  sprintf (debug, "\nfound table: %s", str);
	  DebugLog ();
#endif
	  CodingState = DECODE_SKIPPING;
	  if (i = CreateCodingMap (CodingMap[CODE_CUSTOM], customTable) != -1) {
#ifdef DEBUG_MAP
		sprintf (debug, "Invalid decoding table in block.  Duplicate character %c.", i);
		DebugLog ();
#endif
		customTable[0] = '\0';	// ditch the table

	  }
	  else
		thisContentEncoding = CODE_CUSTOM;
	}
	return (SUCCESS);

  case DECODE_PROCESSING:
/* Some encoders place an END line at end of a section, and length it 
 * like a proper encoded line.Make sure we catch it, don't decode it 
 * as data, and switch back to skipping. Note the only End statement 
 * which means End of entire file is lower case "end".  The other tags 
 * are ignored, i.e. "END", "End_of_section", etc
 */
	if ((thisContentEncoding != CODE_BASE64 && !_strnicmp (line, "end", 3)) || 
    !IsDataLine (line) ||
		(UsingMIME && *thisBoundary &&
		 (!strcmp (&line[2], thisBoundary) || !strcmp (&line[2], thisBoundaryEnded)))) {
#ifdef DEBUG_DECODE
	  sprintf (debug, "\nSwitching back to skipping\nskipped: %s", line);
	  DebugLog ();
#endif
	  CodingState = DECODE_SKIPPING;

/* If we are switching back to skipping and haven't really received any
 * data yet (< about a line's worth), then the line which gave us the coding type
 * was bogus (regular text line where first char happened to = encoded line
 * length, Here's one: 'From: marnold@@cwis.unomaha.edu (Matthew Eldon Arnold)'
 * Ditch all data so far and the current table  and reprocess
 * this line.  Note if we have a custom table, then we trust it.
 */
	  if (thisContentEncoding != CODE_CUSTOM && thisDecode->numBytes < 80) {
		thisDecode->numBytes = 0;
		thisContentEncoding = CODE_UNKNOWN;
#ifdef DEBUG_DECODE
		sprintf (debug, "\nBogus table!  Scrapping so far, reprocessing line: %s", line);
		DebugLog ();
#endif
		thisDecode->numLines--;
		return (DecodeLine (thisDecode, line));
	  }
	  strcpy (prevBlockIdent, thisDecode->ident);

	  if (!strncmp (line, "end", 3)) {
		thisDecode->endFlag = TRUE;
		if (thisNumBlocks == -1 && thisDecode->sequence != -1)
		  thisNumBlocks = thisDecode->sequence;
#ifdef DEBUG_DECODE
		sprintf (debug, "\nFound end of file %s", thisDecode->ident);
		DebugLog ();
#endif
	  }
	  return (END_BLOCK);
	}
	return (DecodeDataLine (thisDecode, line));
  }
  DEBUG_BREAK; // verify this as proper return value...
  return FAIL;
}

/* ------------------------------------------------------------------------
 *  4 to 3 line decoder
 */
BOOL
DecodeDataLine (TypCoded * thisDecode, char *line)
{
  register unsigned int i, j;
  unsigned int decodedCount, numEncoded, numDecoded, checkSum, startNum, stop;
  unsigned char buf[4], outLine[120];
  int offset;
  char *thisMap = CodingMap[thisContentEncoding];

  switch (thisContentEncoding) {
  case CODE_QP:
	// not implemented yet
	return (FAIL);

  case CODE_UU:
  case CODE_XX:
  case CODE_CUSTOM:
	// these are 4-to-3 decodings which have the line length encoded
	// as the first char (count is # of chars after decoding)
	checkSum = decodedCount = thisMap[(unsigned char)line[0]];
	// to precisely determine the numEncoded from this count char,
	// add (4 * the number of full 3-byte units) and a remainder offset
	// the remainder offset is 2 if 1 byte over, or 3 if 2 bytes over
	if ((offset = (decodedCount % 3)) != 0) {
	  offset++;
	}
	numEncoded = 4 * (decodedCount / 3) + offset;
	startNum = 1;
	break;

  case CODE_BASE64:
	// base64 is 4-to-3 decoding with nonexplicit line lengths
	numEncoded = strlen (line);
	decodedCount = 3 * (numEncoded / 4);
	startNum = 0;
	break;
  }
  for (i = startNum, numDecoded = 0; numDecoded < decodedCount;) {
	// Get the next group of four characters
	// Handle last group of characters in a base64 encoding specially -
	// padding '=' at end means we have fewer than 24 bits after decoding
	// Base64 end scenarios:
	//  'xx==' decodes to 8 bits
	//  'xxx=' decodes to 16 bits
	//  'xxxx' decodes to 24 bits
	//  'x===' can't happen
	if ((stop = numEncoded - i + 1) < 4) {
	  memset (buf, 0, 4);
	}
	else {
	  stop = 4;
	}
	for (j = 0; j < stop; j++, i++) {
	  if (thisContentEncoding == CODE_BASE64 && line[i] == '=') {
		buf[j] = 0;
		j--;
		break;
	  }
	  else {
		buf[j] = thisMap[(unsigned char)line[i]];
	  }
//          checkSum += buf[j];
	}
	outLine[numDecoded++] = buf[0] << 2 | buf[1] >> 4;
	if (j == 1 || numDecoded == decodedCount)
	  break;
	outLine[numDecoded++] = buf[1] << 4 | buf[2] >> 2;
	if (j == 2 || numDecoded == decodedCount)
	  break;
	outLine[numDecoded++] = buf[2] << 6 | buf[3];
  }
  if (numDecoded && AddDataToBlock (thisDecode, (char*)outLine, numDecoded) == FAIL)
	return (FAIL);

/*  if (checkSum%64 != line[i])
   {
   printf("\nChecksum error: %d vs. %d", checkSum%64, line[i]);
   break;
   } else
   printf("\nChecksum ok: %c vs. %c", checkSum%64, line[i]);
 */
  return (SUCCESS);
}

/* ------------------------------------------------------------------------
 * AddDataToBlock is called on a line-by-line basis.
 * dataLen should rarely be > 100 bytes
 * The data is type huge, but it's just pointing to a string of chars, so
 * the size does not have to be a power of 2
 */
BOOL
AddDataToBlock (TypCoded * thisDecode, char *newData, unsigned int dataLen)
{
  if (thisDecode->numBytes + (unsigned long) dataLen > thisDecode->maxBytes) {
	thisDecode->maxBytes += BASE_BLOCK_SIZE;
#ifdef DEBUG_MEM
	sprintf (debug, "\nReallocing data to %ld", thisDecode->maxBytes);
	DebugLog ();
#endif
	if ((thisDecode->data = (char huge *) GlobalReAllocPtr (thisDecode->data,
							thisDecode->maxBytes, GMEM_MOVEABLE)) == NULL) {
	  return (FALSE);
	}
  }
  memmove (thisDecode->data + thisDecode->numBytes, newData, dataLen);
  thisDecode->numBytes += dataLen;
  return (SUCCESS);
}

void
PreAllocDataByLines (TypCoded * thisDecode)
{
  unsigned long newSize;
  char huge *resizer;

  if (currentCoded->estNumLines) {
	/* pre-alloc data to rough estimate of # bytes for decode.  allocing
	 * up front saves time - reallocs are slow.  If this fails, allow
	 * to continue - the # lines may have been wrong...
	 */
	newSize = thisDecode->estNumLines * 45L;
	if ((resizer = (char huge *) GlobalReAllocPtr (thisDecode->data,
										 newSize, GMEM_MOVEABLE)) != NULL) {
	  thisDecode->data = resizer;
	  thisDecode->maxBytes = newSize;
#ifdef DEBUG_MEM
	  sprintf (debug, "\nRealloced data to %ld based on lines header %ld",
			   thisDecode->maxBytes, thisDecode->estNumLines);
	  DebugLog ();
#endif
	}
  }
}

/* ------------------------------------------------------------------------
 *    Determine if the line is a data line
 */
BOOL
IsDataLine (char *line)
{
  if (thisContentEncoding == CODE_UNKNOWN) {
/* If this block has same ident as prev block, use same decode table
 * If this block has same ident as any existing threads, use same decode table
 * as matching thread
 * Otherwise, test line for UU, XX, or Base64, returning if find a fit
 */
	if (currentCoded->ident[0] != '\0' &&
		!strcmp (currentCoded->ident, prevBlockIdent)) {
	  thisContentEncoding = prevContentEncoding;
#ifdef DEBUG_MAP
	  strcpy (debug, "\nUsing same table as prev decode block");
	  DebugLog ();
#endif
	}
	else if ((thisContentEncoding = ThreadTable (customTable, currentCoded->ident)) != CODE_UNKNOWN) {
	  if (thisContentEncoding == CODE_CUSTOM)
		CreateCodingMap (CodingMap[CODE_CUSTOM], customTable);
#ifdef DEBUG_MAP
	  strcpy (debug, "\nUsing stored thread table");
	  DebugLog ();
#endif
	}
	else {
	  thisContentEncoding = CODE_UU;
	  if (TestDataLine (line)) {
#ifdef DEBUG_MAP
		sprintf (debug, "\nUsing UU table: %s", line);
		DebugLog ();
#endif
		return (TRUE);
	  }
	  thisContentEncoding = CODE_XX;
	  if (TestDataLine (line)) {
#ifdef DEBUG_MAP
		sprintf (debug, "\nUsing XX table: %s", line);
		DebugLog ();
#endif
		return (TRUE);
	  }
	  thisContentEncoding = CODE_BASE64;
	  if (TestDataLine (line)) {
#ifdef DEBUG_MAP
		sprintf (debug, "\nUsing Base64 table: %s", line);
		DebugLog ();
#endif
		return (TRUE);
	  }
	  thisContentEncoding = CODE_UNKNOWN;
	  return (FALSE);
	}
  }
  return (TestDataLine (line));
}

BOOL
TestDataLine (char *line)
{
  unsigned int expectedLineLen, dataLen, lineLen;
  unsigned int decodedCount, offset, tolerance;
  register unsigned int i;
  char *thisMap;

  thisMap = CodingMap[thisContentEncoding];
  lineLen = strlen (line);
  switch (thisContentEncoding) {
  case CODE_UU:
  case CODE_XX:
  case CODE_CUSTOM:
/*
 * The first char of a good data UU/XX/Custom line is the encoded character
 * count for the line.
 * example M = ascii 77, decodes to 45.  This line will decode to 45 characters,
 * so the # encoded chars should be (int)(4 * ((n+2) / 3)) - (round up and re-encode)
 * So if line[0] is 'M', there should be 60 characters on the line
 * The count does not include the first count char, so the expected line
 * length is count+1 (i.e. 61).
 * If the line length is actually 2 + count, then we have a checksum (?)
 */
//  expectedLineLen = 4 * (thisMap[line[0]] + 2) / 3) + 1;

	decodedCount = thisMap[(unsigned char) line[0]];
	/* to precisely determine the numEncoded from this count char,
	 * add (4 * the number of full 3-byte units) and a remainder offset
	 * the remainder offset is 2 if 1 byte over, or 3 if 2 bytes over.
	 * Not all encoders are smart enough to do this though, and just leave
	 * the last (4-offset) bytes as junk (usually bytes found in the same
	 * location on the previous line).  So allow a tolerance of (4-offset)
	 * bytes
	 */
	if ((offset = (decodedCount % 3)) != 0) {
	  offset++;
	  tolerance = 4 - offset;
	}
	else {
	  tolerance = 0;
	}
	expectedLineLen = 4 * (decodedCount / 3) + offset + 1;	// add 1 for start char

	if (lineLen < expectedLineLen)
	  return (FALSE);
/*
 * Count the number of encoded data characters on the line (up to any whitespace)
 * (Some encoders pad with spaces at the end).  Don't forget, a space may be
 * a valid encoded char, so a proper line may very well end with a space.
 * Don't blindly remove all trailing white space!
 */
	// first strip trailing non-spaces (i.e. \n\r)
	for (dataLen = lineLen;
		 isspace (line[dataLen - 1]) && (thisMap[' '] == UNUSED || line[dataLen - 1] != ' ') && dataLen > 0;
		 --dataLen);

	// now deal with spaces
	if (thisMap[' '] != UNUSED) {
	  for (; line[dataLen - 1] == ' ' && dataLen > 0 && dataLen != expectedLineLen;
		   --dataLen);
	}
	// May have a checksum character, so allow for one extra char
	// also allow for encoders which include the count char in the line count
	if (expectedLineLen != dataLen && expectedLineLen + tolerance != dataLen &&
		expectedLineLen + 1 != dataLen && expectedLineLen + tolerance + 1 != dataLen &&
		expectedLineLen - 1 != dataLen && expectedLineLen + tolerance - 1 != dataLen)
	  return (FALSE);

	line[dataLen] = '\0';		// permanently chop off the white space
	// It's the right length, now check content for match w/ table

	for (i = 0; i < dataLen; i++)
	  if (thisMap[(unsigned char)line[i]] == UNUSED)
		return (FALSE);
	return (TRUE);

  case CODE_BASE64:
	// permanently remove all trailing space
	while (lineLen > 0 && isspace (line[lineLen - 1]))
	  line[--lineLen] = '\0';

	// for base 64, just check if all chars in coding map (allow pad '=')
	for (i = 0; i < lineLen; i++)
	  if (thisMap[(unsigned char)line[i]] == UNUSED && line[i] != '=')
		return (FALSE);
	return (TRUE);

  case CODE_QP:
	// not implemented yet
  default:
	return (FALSE);
  }
}

/* ------------------------------------------------------------------------
 *    Mappings for encoding/decoding
 */
int
CreateCodingMap (char *map, char *table)
{
  register unsigned int i;

  for (i = 0; i < 256; i++)
	map[i] = UNUSED;

  // if you see a table[i] character, decode it to i
  // i.e. UU, table[1] is a '!'.  So a '!' should decode to 1

  for (i = 0; i < CODINGTABLESIZE; i++)
	if (map[table[i]] != UNUSED)
	  return (table[i]);		// table[i] is duplicate

	else
	  map[table[i]] = i;

#ifdef DEBUG_MAP2
  sprintf (debug, "\nCoding Map:\n");
  DebugLog ();
  for (i = 0; i < 256; i++) {
	sprintf (debug, "%d ", map[i]);
	DebugLog ();
  }
#endif
  return (-1);
}

int
ThreadTable (char *dest, char *ident)
{
  int num;
  register int i;

  for (num = -1, i = 0; i < numDecodeThreads; i++)
	if (!_stricmp (ident, threadList[i]->ident))
	  num = i;

  if (num == -1)				// no thread by that name

	return (CODE_UNKNOWN);
  else {
	if (threadList[num]->contentEncoding == CODE_CUSTOM)
	  strncpy (dest, threadList[num]->customTable, CODINGTABLESIZE);
	return (threadList[num]->contentEncoding);
  }
}
#if 0
void
CreateUUTable ()
{
  register unsigned int i;

  for (i = 0; i < CODINGTABLESIZE; i++)
	uuTable[i] = i + 32;

  uuTable[0] = 96;				/* set space to map to back-quote */
}
#endif

/* ------------------------------------------------------------------------
 *    Search through a subject: or BEGIN line
 *  (for a Subject: line,  "Subject:" has already been removed).
 *
 *  Attempt to obtain a block number, and a total # of blocks
 *  Generate an identifier based on the subject, preferably based on the
 *  actual file name.  If guessIdent is TRUE, choose an ident even if
 *  it doesn't look like a fileName.  If guessIdent is FALSE, only fill
 *  ident if we see a fileName (word containing a dot).
 *
 *  Note: at this point, we haven't seen any MIME header, and we're not
 *  really expecting one.  So now is the time to get what we can
 *
 *  Common Subject styles which this function deals with:
 *  filename.ext 1/2 comment
 *  filename.ext(1/2)comment
 *  filename.ext (1/2) comment
 *  filename.ext [1/2] comment
 *  filename.ext 1 of 2 comment
 *  filename.ext part 1 of 2 comment
 *  filename.ext 1 of 2 comment
 *  This is part 1/2 of filename.ext
 *
 *  Also handles the following common BEGIN lines:
 *  BEGIN --- CUT HERE --- Cut Here --- cut here --- abcd.efg n/N
 *  BEGIN --- CUT HERE --- Cut Here --- cut here --- abcd.efg
 *  BEGIN-------cut here-------CUT HERE-------PART n               // This is TIN
 *  BEGIN -- CUT HERE -- cut here -- abcd.efg part n of N
 *  BEGIN---CUT HERE---BEGIN---CUT HERE---BEGIN PART n/N
 *
 *  For subject lines beginning with the comment, or free text, the
 *  name is harder to guess.  In these cases, prefer a word containing
 *  a dot (filename.ext), or if none, just take the first word.  Scan
 *  any existing threads to see if any of their identifiers matches.
 *  For example,
 *  another encoded file: filename.ext (1/2)
 *  testing encoded files (1/2) i.e. ident would be testing
 *
 *  Known to be incorrectly handled by this function:
 *  filename.ext 3.4 (1/2)      i.e. name w/ version number
 *  filename.ext 001        i.e. sequence w/ no # parts
 *  filename.ext1           i.e. part number appended to file name
 */
#define SEEK_PARTNUM	1
#define SEEK_NUMPARTS	2
#define IGNORE_NUMBERS	3
void
ParseInfoLine (TypCoded * thisDecode, char *line, BOOL guessIdent)
{
  char tok[MAXINTERNALLINE], next[MAXINTERNALLINE];
  char guessName[MAXINTERNALLINE];
  char *ptr, *thisTok, *tmp;
  int numberMode;

  numberMode = SEEK_PARTNUM;
  ptr = line;

  guessName[0] = '\0';
  while (*ptr) {
	if (!ReadSubjectToken (thisTok = tok, &ptr))
	  break;

//  NUMBERS
	if (numberMode != IGNORE_NUMBERS)
	  if (isnumber (thisTok)) {
		if (numberMode == SEEK_PARTNUM) {
		  thisDecode->sequence = atoi (thisTok);
		  thisDecode->seqConfidence = LOW;

		  if (!ReadSubjectToken (next, &ptr))
			break;
		  if (!_stricmp (next, "/") ||
			  !_stricmp (next, "of")) {
			numberMode = SEEK_NUMPARTS;
			continue;
		  }
		  else					// no numParts immediately following

			thisTok = next;		// process the next tok

		}
		else					// numberMode == SEEK_NUMPARTS
		 {
		  thisNumBlocks = atoi (tok);
		  numberMode = IGNORE_NUMBERS;
		  thisDecode->seqConfidence = HIGH;
		  continue;
		}
	  }
	  else if (numberMode == SEEK_NUMPARTS)
//         We had '# of non#', a red herring, seek again
		numberMode = SEEK_PARTNUM;

//  WORDS
	// Prefer a word containing a dot for the ident (but not a period
	// at the end of a sentence
	if ((tmp = strchr (thisTok, '.')) && isalpha (*(tmp + 1)))
	  strcpy (thisDecode->ident, thisTok);
	else if (guessIdent) {
	  // skip if already have name w/ confidence
	  if (thisDecode->ident[0] == '\0') {
		// check if any threads have this token as name
		if (SearchThreadNames (thisTok))
		  strcpy (thisDecode->ident, thisTok);

		// Save first word found for thread name
		// prefer ident not starting with 're'
		else if ((guessName[0] == '\0' || !_strnicmp (guessName, "re", 2)) && strlen (thisTok) > 1)
		  strcpy (guessName, thisTok);
	  }
	}
  }
  if (guessIdent && thisDecode->ident[0] == '\0')
	strcpy (thisDecode->ident, guessName);

#ifdef DEBUG_DECODE
  sprintf (debug, "\nSubject header: name %s, part number %d (confidence %d), num parts %d",
		   thisDecode->ident, thisDecode->sequence, thisDecode->seqConfidence, thisNumBlocks);
  DebugLog ();
#endif
}

BOOL
ReadSubjectToken (char *dest, char **ptr)
{
  int len;
  register int i;
  char *str = *ptr;

  if (*str == '\0')
	return FALSE;

  len = strcspn (str, " ()[]\\/:,\"'`<>{};\t\n\r");

/*  Ignore (skip) all of these delimiters except '/'
 *    Return '/' as a token of length 1
 */
  if (len == 0)
	if (*str == '/')
	  len = 1;
	else {						// skip this delimiter

	  (*ptr)++;
	  return (ReadSubjectToken (dest, ptr));
	}

  for (i = 0; i < len; i++)
	dest[i] = tolower (str[i]);
  dest[len] = '\0';

  *ptr = str + len;				// increment line ptr

  return TRUE;
}

BOOL
SearchThreadNames (char *name)
{
  register int i;
  int len;

  len = strlen (name);

  for (i = 0; i < numDecodeThreads; i++)
	if (!_strnicmp (name, threadList[i]->ident, len))
	  return (TRUE);

  return (FALSE);
}

/* ------------------------------------------------------------------------
 * simple MIME header handling - not very smart!
 */
BOOL
SkipSpace (char **ptr)
{
  while (**ptr && isspace (**ptr)) {
	(*ptr)++;
  }

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


BOOL
SkipToNextClause (char **ptr)
{
  while (**ptr && **ptr != ';') {	// skip to semi-colon or end

	(*ptr)++;
  }
  if (**ptr == ';') {			// skip semi-colon

	(*ptr)++;
  }
  if (!SkipSpace (ptr)) {		// skip space after semi-colon

	return FALSE;
  }
  return TRUE;
}

BOOL
GetNumber (int *dest, char *src)
{
  char temp[20];
  char *ptr, *destPtr;

  ptr = src;
  destPtr = temp;
  *destPtr = '\0';
  while (*ptr && isdigit (*ptr)) {
	*destPtr++ = *ptr++;
  }
  if (*temp == '\0')
	return FALSE;

  *dest = atoi (temp);
  return TRUE;
}

BOOL
GetPossiblyQuotedStr (char *dest, char *src, int maxLen)
{
  char *ptr, *destPtr;
  int len;
  BOOL quoted = FALSE;

  ptr = src;
  destPtr = dest;
  *destPtr = '\0';
  len = 0;
  if (*ptr == '"' || *ptr == '\'') {
	ptr++;
	quoted = TRUE;
  }

  // if quoted, seek to end quote (allow spaces and semicolons inside quotes)
  // if not in quotes, read up to space or semicolon
  while (*ptr && len < maxLen) {
	if (quoted) {
	  if (*ptr == '"' || *ptr == '\'') {
		break;
	  }
	}
	else {
	  if (isspace (*ptr) || *ptr == ';') {
		break;
	  }
	}
	len++;
	*destPtr++ = *ptr++;
  }
  *destPtr = '\0';

  if (*dest == '\0')
	return FALSE;

//  if (quoted && *ptr != '"' && *ptr != '\'')  // missing end-quot
  //      return FALSE;

  return TRUE;
}

/* ParseMIMEHeader
 * this a very simplisitc approach
 * returns TRUE if it was a MIME header (regardless of whether we handled it ok)
 */
BOOL
ParseMIMEHeader (TypCoded * thisDecode, char *line)
{
  char *ptr;
  int result, num;
  char temp[MAXINTERNALLINE];

  ptr = line;
  result = FALSE;

  if (thisContentEncoding == CODE_UNKNOWN &&
	  !_strnicmp (ptr, "Content-Transfer-Encoding:", 26)) {
	ptr += 26;
	if (SkipSpace (&ptr) &&
		GetPossiblyQuotedStr (temp, ptr, MAXINTERNALLINE - 1)) {
	  if (!_stricmp (temp, "base64")) {
		thisContentEncoding = CODE_BASE64;
		thisDecode->sequence = 1;
	  }
	  else if (!_strnicmp (temp, "x-uu", 4)) {
		thisContentEncoding = CODE_UU;
	  }
	  else if (!_strnicmp (temp, "x-xx", 4)) {
		thisContentEncoding = CODE_XX;
	  }
	}
	return TRUE;
  }
  while (*ptr) {
	if (!_strnicmp (ptr, "name=", 5)) {
	  if (GetPossiblyQuotedStr (thisDecode->ident, &ptr[5], MAXINTERNALLINE - 1)) {
		result = TRUE;
	  }
	}
	if (!_strnicmp (ptr, "number=", 7)) {
	  if (GetNumber (&(thisDecode->sequence), &ptr[7])) {
		result = TRUE;
		thisDecode->seqConfidence = HIGH;
	  }
	}
	if (!_strnicmp (ptr, "total=", 6)) {
	  if (GetNumber (&num, &ptr[6])) {
		result = TRUE;
		thisNumBlocks = num;
		thisDecode->seqConfidence = HIGH;
	  }
	}
	if (!_strnicmp (ptr, "boundary=", 9)) {
	  /* this is no good for encapsulated multipart sections. but ok for now */
	  if (GetPossiblyQuotedStr (thisBoundary, &ptr[9], MAXINTERNALLINE - 1)) {
		_snprintf (thisBoundaryEnded, MAXINTERNALLINE, "%s--", thisBoundary);
		result = TRUE;
	  }
	}
	if (!SkipToNextClause (&ptr)) {
	  break;
	}
  }
  return result;
}
#ifdef BEGINNINGS_OF_A_BETTER_MIME_APPROACH
if (!strncmp (line, "Content-Type:", 13)) {
  ptr = &copy[13];
  if (!SkipSpace (&ptr)) {
	return TRUE;
  }
  if (!strncmp (ptr, "application/octet-stream", 25)) {
	ptr += 25;
	while (*ptr) {
	  if (!SkipToNextClause (&ptr)) {
		return TRUE;
	  }
	  if (GetNameStr (ptr, name)) {
		return TRUE;
	  }
	}
  }
}
if (GetNameStr (ptr, name)) {
  strcpy (thisDecode->ident, name);
  return TRUE;
}
#endif

/* ------------------------------------------------------------------------
 * ParseAppSpecificLine
 * returns TRUE if it handles the line, else FALSE
 *
 * Example info lines handled by this function:
 *  part=n
 *  file=abc.def
 *  pfile=xyz.abc
 *  Archive-name: fileident/part0n      // no extension included for filename
 *
 *  section N of uuencode 5.10 of file abcd.efg   by R.E.M.
 *  section n/N   file abcd.efg   [ Wincode v2.3 ]
 *  [ Section: n/N  File: abcd.efg  Encoder: Wincode v1.4 ]
 *  section n/N abcd.efg  [EnUU 2.1]
 *  abcd.efg    section  n/N   UUXFER ver 2.0 by David M. Read
 *  POST V2.0.0 abcd.efg (Part n/N)
 *  Interim MIME support
 *  Content-Type: application/octet-stream; [tokens]; name="abcd.efg"; [tokens]
 *  name=abcd.efg
 */
BOOL
ParseAppSpecificLine (TypCoded * thisDecode, char *line)
{
  char *orig, *ptr;
  int seq, num, len;
  float ver;
  char name[MAXINTERNALLINE];
  char copy[MAXINTERNALLINE];

  for (orig = line; *orig && isspace (*orig); orig++);	// skip leading spaces

  if (*orig == '\0')			// blank line (or all spaces)

	return (SUCCESS);

  len = min (MAXINTERNALLINE - 1, strlen (orig));
  strntcpy (copy, orig, len);	// make a lower-case copy

  strlwr (copy);

  if (!strncmp (orig, "BEGIN", 5))	// specifically comparing case-sensitive
   {
	ParseInfoLine (thisDecode, copy, FALSE);
	return (TRUE);
  }

  if (sscanf (copy, "file=%s", name) == 1) {
	strcpy (thisDecode->ident, name);
	return (TRUE);
  }

  if (*copy == 'p')				// info headers starting with 'p'
   {
	if (sscanf (copy, "part=%d", &seq) == 1) {
	  thisDecode->sequence = seq;
	  if (!thisDecode->seqConfidence)
		thisDecode->seqConfidence = LOW;	// still don't have number of parts

	  return (TRUE);
	}
	if (sscanf (copy, "pfile=%s", name) == 1) {
	  strcpy (thisDecode->ident, name);
	  return (TRUE);
	}
	/* POST */
	if (sscanf (copy, "POST V%*s %s (Part %d/%d)", name, &seq, &num) == 3) {
	  thisDecode->sequence = seq;
	  thisNumBlocks = num;
	  thisDecode->seqConfidence = HIGH;
	  strcpy (thisDecode->ident, name);
	  return (TRUE);
	}
	return (FALSE);				// started with 'p' but we didn't have a template

  }

  /* note some of these look for a version number, then don't use it.  the
     version number is scanned simply to add to our confidence that this is
     a valid info line.  we want it to be included in the count - so we know
     if the line fits the template (hence don't use %*f)
   */

  if (UsingMIME && ParseMIMEHeader (thisDecode, orig)) {
	return TRUE;
  }

  /* R.E.M., EnUU, and WinCode v2.x all start with "section " */
  if (!strncmp (copy, "section", 7)) {
	ptr = &copy[7];
	/* R.E.M. */
	if (sscanf (ptr, " %d of uuencode %f of file %s", &seq, &ver, name) == 3) {
	  thisDecode->sequence = seq;
	  if (!thisDecode->seqConfidence)
		thisDecode->seqConfidence = LOW;	// still don't have number of parts

	  strcpy (thisDecode->ident, name);
	  thisContentEncoding = CODE_UU;
	  return (TRUE);
	}
	/* EnUU */
	if (sscanf (ptr, " %d/%d %s [EnUU %f]", &seq, &num, name, &ver) == 4) {
	  thisDecode->sequence = seq;
	  thisNumBlocks = num;
	  thisDecode->seqConfidence = HIGH;
	  strcpy (thisDecode->ident, name);
	  thisContentEncoding = CODE_UU;
	  return (TRUE);
	}
	/* Wincode v2.x */
	if (sscanf (ptr, " %d/%d  file %s [ Wincode v%f ]", &seq, &num, name, &ver) == 4) {
	  thisDecode->sequence = seq;
	  thisNumBlocks = num;
	  thisDecode->seqConfidence = HIGH;
	  strcpy (thisDecode->ident, name);
	  return (TRUE);
	}
	return (FALSE);				// started with 'section' but we didn't have a template

  }
  /* Wincode v1.x */
  if (sscanf (copy, "[ section: %d/%d  File: %s  Encoder: Wincode v%f", &seq, &num, name, &ver) == 4) {
	thisDecode->sequence = seq;
	thisNumBlocks = num;
	thisDecode->seqConfidence = HIGH;
	strcpy (thisDecode->ident, name);
	return (TRUE);
  }

  /* UUXFER */
  if (sscanf (copy, "%s section %d/%d  UUXFER ver %f", name, &seq, &num, &ver) == 4) {
	thisDecode->sequence = seq;
	thisNumBlocks = num;
	thisDecode->seqConfidence = HIGH;
	strcpy (thisDecode->ident, name);
	thisContentEncoding = CODE_UU;
	return (TRUE);
  }

  if (sscanf (copy, "archive-name: %[^/]/part%d", name, &seq) == 2)		// %[^/] reads a string up to a slash
   {
	strcpy (thisDecode->ident, name);
	thisDecode->sequence = seq;
	if (!thisDecode->seqConfidence)
	  thisDecode->seqConfidence = LOW;	// still don't have number of parts

	return (TRUE);
  }

  return (FALSE);
}
//-------------------------------------------------------------------------
void
ExecuteDecodedFile (int num, char *fileName)
{
  char association[MAXINTERNALLINE];
  char execute[MAXINTERNALLINE + MAXFILENAME];
  char ext[MAXFILENAME];
  unsigned int err;
#ifdef WIN32
  STARTUPINFO sInfo;
  PROCESS_INFORMATION pInfo;
#endif

  if (FindExecutable (fileName, ".", association) <= (HINSTANCE) 32) {
	_snprintf (str, MAXINTERNALLINE,
			   "Cannot execute - no association for file type %s", GetFileExtension (ext, fileName));
	UpdateThreadStatus (num, str);
	return;
  }

  _snprintf (execute, MAXINTERNALLINE + MAXFILENAME, "%s %s", association, fileName);
#ifdef WIN32
  memset (&sInfo, 0, sizeof (STARTUPINFO));
  sInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
  sInfo.wShowWindow = SW_SHOWNORMAL;
  err = CreateProcess (NULL, execute, NULL, NULL, FALSE,
					   DETACHED_PROCESS | NORMAL_PRIORITY_CLASS, NULL, NULL,
					   &sInfo, &pInfo);
  if (err == TRUE) {			// successful

	CloseHandle (pInfo.hThread);	/* let it run detached */
	CloseHandle (pInfo.hProcess);
  }
  else {
#else
  if ((err = WinExec (execute, SW_SHOWNORMAL)) < 32) {
#endif
	_snprintf (str, MAXINTERNALLINE, "Error executing %s, error %u", execute, err);
	UpdateThreadStatus (num, str);
	return;
  }

  if (CodingStatusVerbose) {
	_snprintf (str, MAXINTERNALLINE, "Executed %s", execute);
	UpdateThreadStatus (num, str);
  }
}

/* this is for emacs
 * Local variables:
 * tab-width: 2
 * end:
 */
@


1.20
log
@add support for HeaderLine editing
@
text
@d35 1
a35 1
 * $Id: wvcoding.c 1.19 1995/11/07 22:59:16 dumoulin Exp $
d1377 1
a1377 1
int
a1396 1

d1445 5
a1449 6
/*      else if (ParseMimeHeaderLine(thisDecode, line) == SUCCESS)
   return (SUCCESS);
 */
/* Skipping to find new block of data.
 * Skip until a data line (a line with the correct length) is found
 */
d1453 1
a1453 1
	  sprintf (debug, "\nFound start of next data section", line);
d2372 1
a2372 1
  if (sscanf (copy, "[ section: %d/%d  File: %s  Encoder: Wincode v%f", &seq, &num, &ver) == 4) {
@


1.19
log
@fixed bug in decoding algorithm where == was being used instead of =
@
text
@d10 1
a10 1
 *              InitCoded   - initializes a coded block             * 
d26 1
a26 1
 *    Both the max # of batched decode files and the max #          * 
d30 1
a30 1
 * Author: John S. Cooper (jcooper@@netcom.com)                      *
d34 2
a35 2
/* 
 * $Id: wvcoding.c 1.18 1995/05/19 22:47:55 dumoulin Exp $
d40 3
d66 1
a66 6
char
  xxTable[CODINGTABLESIZE] = "+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
char
  uuTable[CODINGTABLESIZE] = "`!\"#$%&'()*+,-./0123456789:;<=>?@@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_";
char
  base64Table[CODINGTABLESIZE] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
d68 1
a68 2
char customTable[CODINGTABLESIZE];

d104 1
a104 1
char *ReadSubjectToken (char *dest, char **ptr);
d161 1
a161 1
 * Uses a file instead of the comm article as a line-provider to the 
d224 1
a224 1
 * Uses a doc instead of the comm article as a line-provider to the 
d414 1
a414 1
BOOL 
d438 1
a438 1
  extern BOOL SendOneLine (char *str);	// in wvattach.c
d443 1
a443 1
  result = Encode (thisAttach, startByte, maxBytes, (BOOL (*)(char *)) SendOneLine);
d451 1
a451 1
BOOL 
d482 1
a482 1
  extern char *NameWithoutPath ();
d531 2
a532 2
	  len = _snprintf (outLine, MAXINTERNALLINE, "begin 755 %s\r\n", str);
	  if ((*AddLine) (outLine))
d545 1
a545 1
	// base64 is a 3-to-4 encoding, with no line len indicator and 
d551 1
a551 1
  // open the file and have at it 
d576 2
a577 2
	strcat (outLine, "\r\n");
	if ((*AddLine) (outLine)) {
d581 1
a581 1
	totalNumAdded += strlen (outLine);
d601 1
a601 1
	  if ((*AddLine) (outLine))
d636 1
a636 1
	  strcpy (&out[2], "==");
d647 1
a647 1
	  strcpy (&out[3], "=");
d660 1
a660 1
 *  Init threadList:  list of pointers to Thread lists 
d662 1
a662 1
 *      
d701 1
a701 1
 * or the file was encoded w/out sequence info, and we were waiting to 
d847 1
a847 1
 *    Given a file name, find the associated thread number  
d894 1
a894 1
   (numDecodeThreads + LIST_SIZE_INC) * sizeof (HGLOBAL), 
d927 1
a927 1
	  if (NumStatusTexts + 1 > MAX_DECODE_THREADS);
d954 1
a954 1
					  (y + (NumStatusTexts * StatusLineHeight)) % maxY, 
d990 1
a990 1
 * Insert in correct sequence. 
d1179 2
a1180 2
 * If currentCoded is in sequence, then it was added as block 0 in the 
 * thread's block list, and may have caused other blocks to now be in 
d1400 1
a1400 1
	// encoded UU/XX/Custom data always starts with line 
d1466 1
a1466 1
	return (SUCCESS);			// continue skipping 
d1474 2
a1475 2
	  strncpy (str, customTable, 64);
	  str[64] = '\0';
d1494 5
a1498 5
/* Some encoders place an END line at end of a section, and length it like 
 * a proper encoded line
 * Make sure we catch it, don't decode it as data, and switch back to skipping
 * Note the only End statement which means End of entire file is lower case
 * "end".  The other tags are ignored, i.e. "END", "End_of_section", etc
d1500 2
a1501 1
	if (!_strnicmp (line, "end", 3) || !IsDataLine (line) ||
d1512 1
a1512 1
 * was bogus (regular text line where first char happened to = encoded line 
d1542 2
d1549 1
a1549 1
BOOL 
d1568 1
a1568 1
	checkSum = decodedCount = thisMap[line[0]];
d1580 1
a1580 1
	// base64 is 4-to-3 decoding with nonexplicit line lengths 
d1587 1
a1587 1
	// Get the next group of four characters 
d1608 1
a1608 1
		buf[j] = thisMap[line[i]];
d1610 1
a1610 1
//          checkSum += buf[j]; 
d1620 1
a1620 1
  if (numDecoded && AddDataToBlock (thisDecode, outLine, numDecoded) == FAIL)
d1665 1
a1665 1
	/* pre-alloc data to rough estimate of # bytes for decode.  allocing 
d1759 1
a1759 1
 * count for the line.  
d1763 2
a1764 2
 * The count does not include the first count char, so the expected line 
 * length is count+1 (i.e. 61). 
d1769 1
a1769 1
	decodedCount = thisMap[line[0]];
d1816 1
a1816 1
	  if (thisMap[line[i]] == UNUSED)
d1827 1
a1827 1
	  if (thisMap[line[i]] == UNUSED && line[i] != '=')
d1846 1
a1846 1
  for (i = 0; i < 128; i++)
d1854 1
a1854 1
	  return (table[i]);		// table[i] is duplicate 
d1862 1
a1862 1
  for (i = 0; i < 128; i++) {
d1880 1
a1880 1
  if (num == -1)				// no thread by that name 
d1903 1
a1903 1
 *    Search through a subject: or BEGIN line 
d1961 1
a1961 1
	if (ReadSubjectToken (thisTok = tok, &ptr) == NULL)
d1971 1
a1971 1
		  if (ReadSubjectToken (next, &ptr) == NULL)
d2024 1
a2024 1
char *
d2032 1
a2032 1
	return ('\0');
d2034 1
a2034 1
  len = strcspn (str, " ()[]\\/:,\"'`<>{};\n\r");
d2052 1
a2052 1
  *ptr = str + len;				// increment line ptr   
d2054 1
d2075 1
a2075 1
BOOL 
d2086 1
a2086 1
BOOL 
d2104 1
a2104 1
BOOL 
d2123 1
a2123 1
BOOL 
d2168 1
a2168 1
 * returns TRUE if it was a MIME header (regardless of whether we handled it ok) 
d2261 1
a2261 1
 *  pfile=xyz.abc 
d2451 6
@


1.18
log
@modified pointer path to use accessor macros instead
This was a cleanup necessary to support crossposting (JD)
@
text
@d35 1
a35 1
 * $Id: wvcoding.c 1.17 1994/11/21 21:20:46 jcooper Exp $
d1993 1
a1993 1
		numberMode == SEEK_PARTNUM;
@


1.17
log
@93.7 update
@
text
@d35 1
a35 1
 * $Id: wvcoding.c 1.16 1994/11/10 01:54:55 rushing Exp $
d236 3
a238 3
  extern BOOL article_operation (TypDoc far * Doc, long artindex,
	   BOOL (*art_fun) (header_p headers, TypGroup * group, long artindex));
  extern BOOL selected_false (header_p headers, TypGroup * group, long artindex);
d268 1
a268 2
	result = DecodeLine (currentCoded,
			  ((char far *) LinePtr) + sizeof (TypText) + sizeof (TypLine));
a342 1

d435 1
a435 2
unsigned long					// assumes appropriate sockets are opened and initialized
 
a930 2
	  // ?????

a1071 1

a1072 1

@


1.16
log
@restart
@
text
@d332 1
a332 1
 *  This is used by attach and EncodeFile and ReadFileToTextBlock
d362 14
a375 1
  /* Create coding status window, top center */
d378 1
a378 1
				 (xScreen - STATUSWIDTH) >> 1, 1, STATUSWIDTH, STATUSHEIGHT,
a937 5
	  if (CodingStatusVerbose)
		_snprintf (mybuf, MAXINTERNALLINE, "Decoding Status for file %s", (threadList[num]->name[0] != '\0') ? threadList[num]->name : threadList[num]->ident);
	  else
		strcpy (mybuf, "Decoding Status");

d943 14
a956 2
	  x = (NumStatusTexts * 10) % maxX;
	  y = (yScreen >> 2) + (NumStatusTexts * CaptionHeight) % maxY;
d961 3
a963 1
					  x, y, width, height,
d1048 1
a1049 1
  UpdateThreadStatus (num, mybuf);
d1348 5
a1352 2
	MessageBox (hCodedBlockWnd, "Unable to open output file", "File Error", MB_OK | MB_ICONSTOP);
	return (FAIL);
@
