/*
 * This file is part of uudeview, the simple and friendly multi-part multi-
 * file uudecoder  program  (c) 1994-2001 by Frank Pilhofer. The author may
 * be contacted at fp@fpx.de
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#ifdef HAVE_CONFIG_H
    #include "config.h"
#endif

#ifdef SYSTEM_WINDLL
    #include <windows.h>
#endif
#ifdef SYSTEM_OS2
    #include <os2.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <time.h>

#ifdef STDC_HEADERS
    #include <stdlib.h>
    #include <string.h>
#endif
#ifdef HAVE_UNISTD_H
    #include <unistd.h>
#endif
#ifdef HAVE_IO_H
    #include <io.h>
#endif
#ifdef HAVE_ERRNO_H
    #include <errno.h>
#endif

#include <uudeview.h>
#include <uuint.h>
#include <fptools.h>
#include <uustring.h>
#include <gcrcall.h>
#include <gfilutil.h>

/* for braindead systems */
#ifndef SEEK_SET
    #ifdef L_BEGIN
        #define SEEK_SET L_BEGIN
    #else
        #define SEEK_SET 0
    #endif
#endif

char * uuencode_id = "$Id$";

#if 0
    /*
    * the End-Of-Line string. MIME enforces CRLF, so that's what we use. Some
    * implementations of uudecode will complain about a missing end line, since
    * they look for "end^J" but find "end^J^M". We don't care - especially be-
    * cause they still decode the file properly despite this complaint.
    */

    #ifndef EOLSTRING
        #define EOLSTRING   "\015\012"
    #endif

#else

    /*
    * Argh. Some delivery software (inews) has problems with the CRLF
    * line termination. Let's try native EOL and see if we run into
    * any problems.
    * This involves opening output files in text mode instead of binary
    */

    #ifndef EOLSTRING
        #define EOLSTRING   "\n"
    #endif

#endif


/*
 * =========================================================================
 * User-configurable settings end here. Don't spy below unless you know what
 * you're doing.
 * =========================================================================
 */

/*
 * Define End-Of-Line sequence
 */

#ifdef EOLSTRING
    static unsigned char *eolstring = (unsigned char *) EOLSTRING;
#else
    static unsigned char *eolstring = (unsigned char *) "\012";
#endif

/*
 * Content-Transfer-Encoding types for non-MIME encodings
 */

#define CTE_UUENC   "x-uuencode"
#define CTE_XXENC   "x-xxencode"
#define CTE_BINHEX  "x-binhex"
#define CTE_YENC    "x-yenc"

#define CTE_TYPE(y) (((y)==B64ENCODED) ? "Base64"  : \
             ((y)==UU_ENCODED) ? CTE_UUENC : \
             ((y)==XX_ENCODED) ? CTE_XXENC : \
                         ((y)==PT_ENCODED) ? "8bit" : \
                         ((y)==QP_ENCODED) ? "quoted-printable" : \
             ((y)==BH_ENCODED) ? CTE_BINHEX : \
             ((y)==YENC_ENCODED) ? CTE_YENC : "x-oops")

/*
 * encoding tables
 */

unsigned char UUEncodeTable[64] =
{
    '`', '!', '"', '#', '$', '%', '&', '\'',
    '(', ')', '*', '+', ',', '-', '.', '/',
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', ':', ';', '<', '=', '>', '?',
    '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
    'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
    'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
    'X', 'Y', 'Z', '[', '\\',']', '^', '_'
};


unsigned char B64EncodeTable[64] =
{
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
    'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
    'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
    'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
    'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
    'w', 'x', 'y', 'z', '0', '1', '2', '3',
    '4', '5', '6', '7', '8', '9', '+', '/'
};

unsigned char XXEncodeTable[64] =
{
    '+', '-', '0', '1', '2', '3', '4', '5',
    '6', '7', '8', '9', 'A', 'B', 'C', 'D',
    'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
    'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
    'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
    'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
    'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
    's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
};

unsigned char BHEncodeTable[64] =
{
    '!', '"', '#', '$', '%', '&', '\'', '(',
    ')', '*', '+', ',', '-', '0', '1', '2',
    '3', '4', '5', '6', '8', '9', '@', 'A',
    'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
    'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R',
    'S', 'T', 'U', 'V', 'X', 'Y', 'Z', '[',
    '`', 'a', 'b', 'c', 'd', 'e', 'f', 'h',
    'i', 'j', 'k', 'l', 'm', 'p', 'q', 'r'
};

unsigned char HexEncodeTable[16] =
{
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};

typedef struct
{
    char *extension;
    char *mimetype;
} mimemap;

/*
 * This table maps a file's extension into a Content-Type. The current
 * official list can be downloaded as
 *   ftp://ftp.isi.edu/in-notes/iana/assignments/media-type
 * I haven't listed any text types since it doesn't make sense to encode
 * them. Everything not on the list gets mapped to application/octet-stream
 */

static mimemap mimetable[] =
{
    { "gif",  "image/gif"        }, /* Grafics Interchange Format  */
    { "jpg",  "image/jpeg"       }, /* JFIF encoded files          */
    { "jpeg", "image/jpeg"       },
    { "tif",  "image/tiff"       }, /* Tag Image File Format       */
    { "tiff", "image/tiff"       },
    { "cgm",  "image/cgm"        }, /* Computer Graphics Metafile  */
    { "au",   "audio/basic"      }, /* 8kHz ulaw audio data        */
    { "mov",  "video/quicktime"  }, /* Apple Quicktime             */
    { "qt",   "video/quicktime"  }, /* Also infrequently used      */
    { "mpeg", "video/mpeg"       }, /* Motion Picture Expert Group */
    { "mpg",  "video/mpeg"       },
    { "mp2",  "video/mpeg"       }, /* dito, MPEG-2 encoded files  */
    { "mp3",  "audio/mpeg"       }, /* dito, MPEG-3 encoded files  */
    { "ps",   "application/postscript" }, /* Postscript Language   */
    { "zip",  "application/zip"  }, /* ZIP archive                 */
    { "doc",  "application/msword"},/* assume Microsoft Word       */
    { NULL,   NULL               }
};

/*
 * the order of the following two tables must match the
 * Encoding Types definition in uudeview.h
 */

/*
 * encoded bytes per line
 */

static int bpl[8] = { 0, 45, 57, 45, 45, 0, 0, 128 };

/*
 * tables
 */

static unsigned char *etables[5] =
{
    NULL,
    UUEncodeTable,
    B64EncodeTable,
    XXEncodeTable,
    BHEncodeTable
};

/*
 * variables to malloc upon initialization
 */

char *uuestr_itemp;
char *uuestr_otemp;

/*
 * Encode one part of the data stream
 */

static int
UUEncodeStream (FILE *outfile, FILE *infile, int encoding, long linperfile, dword *crc, dword *pcrc)
{
    unsigned char *itemp = (unsigned char *) uuestr_itemp;
    unsigned char *otemp = (unsigned char *) uuestr_otemp;
    unsigned char *optr, *table, *tptr;
    int index, count;
    long line=0;
    size_t llen;

    if (outfile==NULL || infile==NULL ||
            (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&&
             encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED))
    {
        UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                   uustring (S_PARM_CHECK), "UUEncodeStream()");
        return UURET_ILLVAL;
    }

    /*
     * Special handling for plain text and quoted printable. Text is
     * read line oriented.
     */

    if (encoding == PT_ENCODED || encoding == QP_ENCODED)
    {
        while (!feof (infile) && (linperfile <= 0 || line < linperfile))
        {
            if (_FP_fgets ((char *) itemp, 255, infile) == NULL)
            {
                break;
            }

            itemp[255] = '\0';
            count = strlen ((char *) itemp);

            llen = 0;
            optr = otemp;

            /*
             * Busy Callback
             */

            if (UUBUSYPOLL(ftell(infile)-progress.foffset,progress.fsize))
            {
                UUMessage (uuencode_id, __LINE__, UUMSG_NOTE,
                           uustring (S_ENCODE_CANCEL));
                return UURET_CANCEL;
            }

            if (encoding == PT_ENCODED)
            {
                /*
                 * If there is a line feed, replace by eolstring
                 */
                if (count > 0 && itemp[count-1] == '\n')
                {
                    itemp[--count] = '\0';
                    if (fwrite (itemp, 1, count, outfile) != (unsigned)count ||
                            fwrite ((char *) eolstring, 1,
                                    strlen((char *) eolstring), outfile) != strlen ((char *) eolstring))
                    {
                        return UURET_IOERR;
                    }
                }
                else
                {
                    if (fwrite (itemp, 1, count, outfile) != llen)
                    {
                        return UURET_IOERR;
                    }
                }
            }
            else if (encoding == QP_ENCODED)
            {
                for (index=0; index<count; index++)
                {
                    if (llen == 0 && itemp[index] == '.')
                    {
                        /*
                         * Special rule: encode '.' at the beginning of a line, so
                         * that some mailers aren't confused.
                         */
                        *optr++ = '=';
                        *optr++ = HexEncodeTable[itemp[index] >> 4];
                        *optr++ = HexEncodeTable[itemp[index] & 0x0f];
                        llen += 3;
                    }
                    else if ((itemp[index] >= 33 && itemp[index] <= 60) ||
                             (itemp[index] >= 62 && itemp[index] <= 126) ||
                             itemp[index] == 9 || itemp[index] == 32)
                    {
                        *optr++ = itemp[index];
                        llen++;
                    }
                    else if (itemp[index] == '\n')
                    {
                        /*
                         * If the last character before EOL was a space or tab,
                         * we must encode it. If llen > 74, there's no space to do
                         * that, so generate a soft line break instead.
                         */

                        if (index>0 && (itemp[index-1] == 9 || itemp[index-1] == 32))
                        {
                            *(optr-1) = '=';
                            if (llen <= 74)
                            {
                                *optr++ = HexEncodeTable[itemp[index-1] >> 4];
                                *optr++ = HexEncodeTable[itemp[index-1] & 0x0f];
                                llen += 2;
                            }
                        }

                        if (fwrite (otemp, 1, llen, outfile) != llen ||
                                fwrite ((char *) eolstring, 1,
                                        strlen((char *) eolstring), outfile) != strlen ((char *) eolstring))
                        {
                            return UURET_IOERR;
                        }

                        /*
                         * Fix the soft line break condition from above
                         */

                        if (index>0 && (itemp[index-1] == 9 || itemp[index-1] == 32) &&
                                *(optr-1) == '=')
                        {
                            otemp[0] = '=';
                            otemp[1] = HexEncodeTable[itemp[index-1] >> 4];
                            otemp[2] = HexEncodeTable[itemp[index-1] & 0x0f];

                            if (fwrite (otemp, 1, 3, outfile) != 3 ||
                                    fwrite ((char *) eolstring, 1,
                                            strlen((char *) eolstring), outfile) != strlen ((char *) eolstring))
                            {
                                return UURET_IOERR;
                            }
                        }

                        optr = otemp;
                        llen = 0;
                    }
                    else
                    {
                        *optr++ = '=';
                        *optr++ = HexEncodeTable[itemp[index] >> 4];
                        *optr++ = HexEncodeTable[itemp[index] & 0x0f];
                        llen += 3;
                    }

                    /*
                     * Lines must be shorter than 76 characters (not counting CRLF).
                     * If the line grows longer than that, we must include a soft
                     * line break.
                     */

                    if (itemp[index+1] != 0 && itemp[index+1] != '\n' &&
                            (llen >= 75 ||
                             (!((itemp[index+1] >= 33 && itemp[index+1] <= 60) ||
                                (itemp[index+1] >= 62 && itemp[index+1] <= 126)) &&
                              llen >= 73)))
                    {

                        *optr++ = '=';
                        llen++;

                        if (fwrite (otemp, 1, llen, outfile) != llen ||
                                fwrite ((char *) eolstring, 1,
                                        strlen((char *) eolstring), outfile) != strlen ((char *) eolstring))
                        {
                            return UURET_IOERR;
                        }

                        optr = otemp;
                        llen = 0;
                    }
                }
            }

            line++;
        }

        return UURET_OK;
    }

    /*
     * Special handling for yEnc
     */

    if (encoding == YENC_ENCODED)
    {
        llen = 0;
        optr = otemp;

        while (!feof (infile) && (linperfile <= 0 || line < linperfile))
        {
            if ((count = fread (itemp, 1, 128, infile)) != 128)
            {
                if (count == 0)
                {
                    break;
                }
                else if (ferror (infile))
                {
                    return UURET_IOERR;
                }
            }

            if (pcrc) *pcrc = memCrc32(*pcrc, itemp, count, false, CRC32_MASK_CCITT);
            if (crc) *crc = memCrc32(*crc, itemp, count, false, CRC32_MASK_CCITT);

            line++;

            /*
             * Busy Callback
             */

            if (UUBUSYPOLL(ftell(infile)-progress.foffset,progress.fsize))
            {
                UUMessage (uuencode_id, __LINE__, UUMSG_NOTE,
                           uustring (S_ENCODE_CANCEL));
                return UURET_CANCEL;
            }

            for (index=0; index<count; index++)
            {
                if (llen > 127)
                {
                    if (fwrite (otemp, 1, llen, outfile) != llen ||
                            fwrite ((char *) eolstring, 1,
                                    strlen((char *) eolstring), outfile) != strlen ((char *) eolstring))
                    {
                        return UURET_IOERR;
                    }
                    llen = 0;
                    optr = otemp;
                }

                switch ((char) ((int) itemp[index] + 42))
                {
                case '\0':
                case '\t':
                case '\n':
                case '\r':
                case '=':
                case '\033':
                    *optr++ = '=';
                    *optr++ = (char) ((int) itemp[index] + 42 + 64);
                    llen += 2;
                    break;

                case '.':
                    if (llen == 0)
                    {
                        *optr++ = '=';
                        *optr++ = (char) ((int) itemp[index] + 42 + 64);
                        llen += 2;
                    }
                    else
                    {
                        *optr++ = (char) ((int) itemp[index] + 42);
                        llen++;
                    }
                    break;

                default:
                    *optr++ = (char) ((int) itemp[index] + 42);
                    llen++;
                    break;
                }
            }
        }

        /*
         * write last line
         */

        if (llen)
        {
            if (fwrite (otemp, 1, llen, outfile) != llen ||
                    fwrite ((char *) eolstring, 1,
                            strlen((char *) eolstring), outfile) != strlen ((char *) eolstring))
            {
                return UURET_IOERR;
            }
        }

        return UURET_OK;
    }

    /*
     * Handling for binary encodings
     */

    /*
     * select charset
     */

    table = etables[encoding];

    if (table==NULL || bpl[encoding]==0)
    {
        UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                   uustring (S_PARM_CHECK), "UUEncodeStream()");
        return UURET_ILLVAL;
    }

    while (!feof (infile) && (linperfile <= 0 || line < linperfile))
    {
        if ((count = fread (itemp, 1, bpl[encoding], infile)) != bpl[encoding])
        {
            if (count == 0)
                break;
            else if (ferror (infile))
                return UURET_IOERR;
        }

        optr = otemp;
        llen = 0;

        /*
         * Busy Callback
         */

        if (UUBUSYPOLL(ftell(infile)-progress.foffset,progress.fsize))
        {
            UUMessage (uuencode_id, __LINE__, UUMSG_NOTE,
                       uustring (S_ENCODE_CANCEL));
            return UURET_CANCEL;
        }

        /*
         * for UU and XX, encode the number of bytes as first character
         */

        if (encoding == UU_ENCODED || encoding == XX_ENCODED)
        {
            *optr++ = table[count];
            llen++;
        }

        /*
         * Main encoding
         */

        for (index=0; index<=count-3; index+=3, llen+=4)
        {
            *optr++ = table[itemp[index] >> 2];
            *optr++ = table[((itemp[index  ] & 0x03) << 4)|(itemp[index+1] >> 4)];
            *optr++ = table[((itemp[index+1] & 0x0f) << 2)|(itemp[index+2] >> 6)];
            *optr++ = table[  itemp[index+2] & 0x3f];
        }

        /*
         * Special handling for incomplete lines
         */

        if (index != count)
        {
            if (encoding == B64ENCODED)
            {
                if (count - index == 2)
                {
                    *optr++ = table[itemp[index] >> 2];
                    *optr++ = table[((itemp[index  ] & 0x03) << 4) |
                                                             ((itemp[index+1] & 0xf0) >> 4)];
                    *optr++ = table[((itemp[index+1] & 0x0f) << 2)];
                    *optr++ = '=';
                }
                else if (count - index == 1)
                {
                    *optr++ = table[ itemp[index] >> 2];
                    *optr++ = table[(itemp[index] & 0x03) << 4];
                    *optr++ = '=';
                    *optr++ = '=';
                }
                llen += 4;
            }
            else if (encoding == UU_ENCODED || encoding == XX_ENCODED)
            {
                if (count - index == 2)
                {
                    *optr++ = table[itemp[index] >> 2];
                    *optr++ = table[((itemp[index  ] & 0x03) << 4) |
                                                             ( itemp[index+1] >> 4)];
                    *optr++ = table[((itemp[index+1] & 0x0f) << 2)];
                    *optr++ = table[0];
                }
                else if (count - index == 1)
                {
                    *optr++ = table[ itemp[index] >> 2];
                    *optr++ = table[(itemp[index] & 0x03) << 4];
                    *optr++ = table[0];
                    *optr++ = table[0];
                }
                llen += 4;
            }
        }

        /*
         * end of line
         */

        tptr = eolstring;

        while (*tptr)
            *optr++ = *tptr++;

        *optr++ = '\0';
        llen   += strlen ((char *) eolstring);

        if (fwrite (otemp, 1, llen, outfile) != llen)
            return UURET_IOERR;

        line++;
    }
    return UURET_OK;
}

/*
 * Encode as MIME multipart/mixed sub-message.
 */

int UUEXPORT
UUEncodeMulti (FILE *outfile, FILE *infile, char *infname, int encoding,
               char *outfname, char *mimetype, int filemode)
{
    mimemap *miter=mimetable;
    struct stat finfo;
    int res, themode;
    FILE *theifile;
    char *ptr;
    dword  crc;
    dword *crcptr=NULL;

    if (outfile==NULL ||
            (infile == NULL && infname==NULL) ||
            (outfname==NULL && infname==NULL) ||
            (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&&
             encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED))
    {
        UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                   uustring (S_PARM_CHECK), "UUEncodeMulti()");
        return UURET_ILLVAL;
    }

    progress.action = 0;

    if (infile==NULL)
    {
        if (stat (infname, &finfo) == -1)
        {
            UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                       uustring (S_NOT_STAT_FILE),
                       infname, strerror (uu_errno=errno));
            return UURET_IOERR;
        }
        if ((theifile = fopen (infname, "rb")) == NULL)
        {
            UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                       uustring (S_NOT_OPEN_FILE),
                       infname, strerror (uu_errno=errno));
            return UURET_IOERR;
        }
        themode = (filemode) ? filemode : ((int) finfo.st_mode & 0777);
        progress.fsize = (long) finfo.st_size;
    }
    else
    {
        if (fstat (fileno (infile), &finfo) != 0)
        {
            themode  = (filemode)?filemode:0644;
            progress.fsize = -1;
        }
        else
        {
            themode = (int) finfo.st_mode & 0777;
            progress.fsize = (long) finfo.st_size;
        }
        theifile = infile;
    }

    if (progress.fsize < 0)
        progress.fsize = -1;

    _FP_strncpy (progress.curfile, (outfname)?outfname:infname, 256);

    progress.partno   = 1;
    progress.numparts = 1;
    progress.percent  = 0;
    progress.foffset  = 0;
    progress.action   = UUACT_ENCODING;

    /*
     * If not given from outside, select an appropriate Content-Type by
     * looking at the file's extension. If it is unknown, default to
     * Application/Octet-Stream
     */

    if (mimetype == NULL)
    {
        if ((ptr = _FP_strrchr ((outfname)?outfname:infname, '.')))
        {
            while (miter->extension && _FP_stricmp (ptr+1, miter->extension) != 0)
                miter++;
            mimetype = miter->mimetype;
        }
    }

    if (mimetype == NULL && (encoding == PT_ENCODED || encoding == QP_ENCODED))
    {
        mimetype = "text/plain";
    }

    /*
     * print sub-header
     */

    if (encoding != YENC_ENCODED)
    {
        fprintf (outfile, "Content-Type: %s%s",
                 (mimetype)?mimetype:"Application/Octet-Stream",
                 (char *) eolstring);
        fprintf (outfile, "Content-Transfer-Encoding: %s%s",
                 CTE_TYPE(encoding), (char *) eolstring);
        fprintf (outfile, "Content-Disposition: attachment; filename=\"%s\"%s",
                 UUFNameFilter ((outfname)?outfname:infname), (char *) eolstring);
        fprintf (outfile, "%s", (char *) eolstring);
    }

    if (encoding == UU_ENCODED || encoding == XX_ENCODED)
    {
        fprintf (outfile, "begin %o %s%s",
                 (themode) ? themode : 0644,
                 UUFNameFilter ((outfname)?outfname:infname),
                 (char *) eolstring);
    }
    else if (encoding == YENC_ENCODED)
    {
        crc = 0;
        crcptr = &crc;
        if (progress.fsize == -1)
        {
            fprintf (outfile, "=ybegin line=128 name=%s%s",
                     UUFNameFilter ((outfname)?outfname:infname),
                     (char *) eolstring);
        }
        else
        {
            fprintf (outfile, "=ybegin line=128 size=%ld name=%s%s",
                     progress.fsize,
                     UUFNameFilter ((outfname)?outfname:infname),
                     (char *) eolstring);
        }
    }

    if ((res = UUEncodeStream (outfile, theifile, encoding, 0, crcptr, NULL)) != UURET_OK)
    {
        if (res != UURET_CANCEL)
        {
            UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                       uustring (S_ERR_ENCODING),
                       UUFNameFilter ((infname)?infname:outfname),
                       (res==UURET_IOERR)?strerror(uu_errno):UUstrerror(res));
        }
        progress.action = 0;
        return res;
    }

    if (encoding == UU_ENCODED || encoding == XX_ENCODED)
    {
        fprintf (outfile, "%c%s",
                 (encoding==UU_ENCODED) ? UUEncodeTable[0] : XXEncodeTable[0],
                 (char *) eolstring);
        fprintf (outfile, "end%s", (char *) eolstring);
    }
    else if (encoding == YENC_ENCODED)
    {
        if (progress.fsize == -1)
        {
            fprintf (outfile, "=yend crc32=%08lx%s",
                     crc,
                     (char *) eolstring);
        }
        else
        {
            fprintf (outfile, "=yend size=%ld crc32=%08lx%s",
                     progress.fsize,
                     crc,
                     (char *) eolstring);
        }
    }

    /*
     * empty line at end does no harm
     */

    fprintf (outfile, "%s", (char *) eolstring);

    if (infile==NULL)
        fclose (theifile);

    progress.action = 0;
    return UURET_OK;
}

/*
 * Encode as MIME message/partial
 */

int UUEXPORT
UUEncodePartial (FILE *outfile, FILE *infile,
                 char *infname, int encoding,
                 char *outfname, char *mimetype,
                 int filemode, int partno, long linperfile,
                 dword *crcptr)
{
    mimemap *miter=mimetable;
    static FILE *theifile;
    int themode, numparts = 1;
    struct stat finfo;
    long thesize;
    char *ptr;
    int res;
    dword  pcrc;
    dword *pcrcptr=NULL;

    if ((outfname==NULL&&infname==NULL) || partno<=0 ||
            (infile == NULL&&infname==NULL) || outfile==NULL ||
            (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&&
             encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED))
    {
        UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                   uustring (S_PARM_CHECK), "UUEncodePartial()");
        return UURET_ILLVAL;
    }

    /*
     * The first part needs a set of headers
     */

    progress.action = 0;

    if (partno == 1)
    {
        if (infile==NULL)
        {
            if (stat (infname, &finfo) == -1)
            {
                UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                           uustring (S_NOT_STAT_FILE),
                           infname, strerror (uu_errno=errno));
                return UURET_IOERR;
            }
            if ((theifile = fopen (infname, "rb")) == NULL)
            {
                UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                           uustring (S_NOT_OPEN_FILE),
                           infname, strerror (uu_errno=errno));
                return UURET_IOERR;
            }
            if (linperfile <= 0)
                numparts = 1;
            else
                numparts = (int) (((long)finfo.st_size+(linperfile*bpl[encoding]-1))/
                                  (linperfile*bpl[encoding]));

            themode  = (filemode) ? filemode : ((int) finfo.st_mode & 0777);
            thesize  = (long) finfo.st_size;
        }
        else
        {
            if (fstat (fileno (infile), &finfo) != 0)
            {
                UUMessage (uuencode_id, __LINE__, UUMSG_WARNING,
                           uustring (S_STAT_ONE_PART));
                numparts = 1;
                themode  = (filemode)?filemode:0644;
                thesize  = -1;
            }
            else
            {
                if (linperfile <= 0)
                    numparts = 1;
                else
                    numparts = (int) (((long)finfo.st_size+(linperfile*bpl[encoding]-1))/
                                      (linperfile*bpl[encoding]));

                themode =  (int) finfo.st_mode & 0777;
                thesize = (long) finfo.st_size;
            }
            theifile = infile;
        }

        _FP_strncpy (progress.curfile, (outfname)?outfname:infname, 256);

        progress.totsize  = (thesize>=0) ? thesize : -1;
        progress.partno   = 1;
        progress.numparts = numparts;
        progress.percent  = 0;
        progress.foffset  = 0;

        /*
         * If not given from outside, select an appropriate Content-Type by
         * looking at the file's extension. If it is unknown, default to
         * Application/Octet-Stream
         */

        if (mimetype == NULL)
        {
            if ((ptr = _FP_strrchr ((outfname)?outfname:infname, '.')))
            {
                while (miter->extension && _FP_stricmp (ptr+1, miter->extension) != 0)
                    miter++;
                mimetype = miter->mimetype;
            }
        }

        if (mimetype == NULL && (encoding==PT_ENCODED || encoding==QP_ENCODED))
        {
            mimetype = "text/plain";
        }

        /*
         * print sub-header
         */

        if (encoding != YENC_ENCODED)
        {
            fprintf (outfile, "MIME-Version: 1.0%s", (char *) eolstring);
            fprintf (outfile, "Content-Type: %s%s",
                     (mimetype)?mimetype:"Application/Octet-Stream",
                     (char *) eolstring);
            fprintf (outfile, "Content-Transfer-Encoding: %s%s",
                     CTE_TYPE(encoding), (char *) eolstring);
            fprintf (outfile, "Content-Disposition: attachment; filename=\"%s\"%s",
                     UUFNameFilter ((outfname)?outfname:infname), (char *) eolstring);
        }

        fprintf (outfile, "%s", (char *) eolstring);

        /*
         * for the first part of UU or XX messages, print a begin line
         */

        if (encoding == UU_ENCODED || encoding == XX_ENCODED)
        {
            fprintf (outfile, "begin %o %s%s",
                     (themode) ? themode : ((filemode)?filemode:0644),
                     UUFNameFilter ((outfname)?outfname:infname), (char *) eolstring);
        }
    }
    if (encoding == YENC_ENCODED)
    {
        pcrc = 0;
        pcrcptr = &pcrc;
        if (numparts != 1)
        {
            if (progress.totsize == -1)
            {
                fprintf (outfile, "=ybegin part=%d line=128 name=%s%s",
                         partno,
                         UUFNameFilter ((outfname)?outfname:infname),
                         (char *) eolstring);
            }
            else
            {
                fprintf (outfile, "=ybegin part=%d line=128 size=%ld name=%s%s",
                         partno,
                         progress.totsize,
                         UUFNameFilter ((outfname)?outfname:infname),
                         (char *) eolstring);
            }

            fprintf (outfile, "=ypart begin=%ld end=%ld%s",
                     (partno-1)*linperfile*128+1,
                     (partno*linperfile*128) < progress.totsize ?
                     (partno*linperfile*128) : progress.totsize,
                     (char *) eolstring);
        }
        else
        {
            if (progress.totsize == -1)
            {
                fprintf (outfile, "=ybegin line=128 name=%s%s",
                         UUFNameFilter ((outfname)?outfname:infname),
                         (char *) eolstring);
            }
            else
            {
                fprintf (outfile, "=ybegin line=128 size=%ld name=%s%s",
                         progress.totsize,
                         UUFNameFilter ((outfname)?outfname:infname),
                         (char *) eolstring);
            }
        }
    }

    /*
     * update progress information
     */

    progress.partno  = partno;
    progress.percent = 0;
    progress.foffset = ftell (theifile);

    if (progress.totsize <= 0)
        progress.fsize = -1;
    else if (linperfile <= 0)
        progress.fsize = progress.totsize;
    else if (progress.foffset+linperfile*bpl[encoding] > progress.totsize)
        progress.fsize = progress.totsize - progress.foffset;
    else
        progress.fsize = linperfile*bpl[encoding];

    progress.action  = UUACT_ENCODING;

    if ((res = UUEncodeStream (outfile, theifile, encoding, linperfile,
                               crcptr, pcrcptr)) != UURET_OK)
    {
        if (infile==NULL) fclose (theifile);
        if (res != UURET_CANCEL)
        {
            UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                       uustring (S_ERR_ENCODING),
                       UUFNameFilter ((outfname)?outfname:infname),
                       (res==UURET_IOERR)?strerror(uu_errno):UUstrerror (res));
        }
        progress.action = 0;
        return res;
    }

    /*
     * print end line
     */

    if (feof (theifile) &&
            (encoding == UU_ENCODED || encoding == XX_ENCODED))
    {
        fprintf (outfile, "%c%s",
                 (encoding==UU_ENCODED) ? UUEncodeTable[0] : XXEncodeTable[0],
                 (char *) eolstring);
        fprintf (outfile, "end%s", (char *) eolstring);
    }
    else if (encoding == YENC_ENCODED)
    {
        if (numparts != 1)
        {
            fprintf (outfile, "=yend size=%ld part=%d pcrc32=%08lx",
                     (partno*linperfile*128) < progress.totsize ?
                     linperfile*128 : (progress.totsize-(partno-1)*linperfile*128),
                     partno,
                     pcrc);
        }
        else
        {
            fprintf (outfile, "=yend size=%ld",
                     progress.totsize);
        }
        if (feof (theifile))
            fprintf (outfile, " crc32=%08lx", *crcptr);
        fprintf (outfile, "%s", (char *) eolstring);
    }

    /*
     * empty line at end does no harm
     */

    if (encoding != PT_ENCODED && encoding != QP_ENCODED)
    {
        fprintf (outfile, "%s", (char *) eolstring);
    }

    if (infile==NULL)
    {
        if (res != UURET_OK)
        {
            progress.action = 0;
            fclose (theifile);
            return res;
        }
        if (feof (theifile))
        {
            progress.action = 0;
            fclose (theifile);
            return UURET_OK;
        }
        return UURET_CONT;
    }

    /*
     * leave progress.action as-is
     */

    return UURET_OK;
}

/*
 * send output to a stream, don't do any headers at all
 */

int UUEXPORT
UUEncodeToStream (FILE *outfile, FILE *infile,
                  char *infname, int encoding,
                  char *outfname, int filemode)
{
    struct stat finfo;
    FILE *theifile;
    int themode;
    int res;
    dword  crc;
    dword *crcptr=NULL;

    if (outfile==NULL ||
            (infile == NULL&&infname==NULL) ||
            (outfname==NULL&&infname==NULL) ||
            (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&&
             encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED))
    {
        UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                   uustring (S_PARM_CHECK), "UUEncodeToStream()");
        return UURET_ILLVAL;
    }

    progress.action = 0;

    if (infile==NULL)
    {
        if (stat (infname, &finfo) == -1)
        {
            UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                       uustring (S_NOT_STAT_FILE),
                       infname, strerror (uu_errno=errno));
            return UURET_IOERR;
        }
        if ((theifile = fopen (infname, "rb")) == NULL)
        {
            UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                       uustring (S_NOT_OPEN_FILE),
                       infname, strerror (uu_errno=errno));
            return UURET_IOERR;
        }
        themode = (filemode) ? filemode : ((int) finfo.st_mode & 0777);
        progress.fsize = (long) finfo.st_size;
    }
    else
    {
        if (fstat (fileno (infile), &finfo) == -1)
        {
            /* gotta live with it */
            themode = 0644;
            progress.fsize = -1;
        }
        else
        {
            themode = (filemode) ? filemode : ((int) finfo.st_mode & 0777);
            progress.fsize = (long) finfo.st_size;
        }
        theifile = infile;
    }

    if (progress.fsize < 0)
        progress.fsize = -1;

    _FP_strncpy (progress.curfile, (outfname)?outfname:infname, 256);

    progress.partno   = 1;
    progress.numparts = 1;
    progress.percent  = 0;
    progress.foffset  = 0;
    progress.action   = UUACT_ENCODING;

    if (encoding == UU_ENCODED || encoding == XX_ENCODED)
    {
        fprintf (outfile, "begin %o %s%s",
                 (themode) ? themode : 0644,
                 UUFNameFilter ((outfname)?outfname:infname),
                 (char *) eolstring);
    }
    else if (encoding == YENC_ENCODED)
    {
        crc = 0;
        crcptr = &crc;
        if (progress.fsize == -1)
        {
            fprintf (outfile, "=ybegin line=128 name=%s%s",
                     UUFNameFilter ((outfname)?outfname:infname),
                     (char *) eolstring);
        }
        else
        {
            fprintf (outfile, "=ybegin line=128 size=%ld name=%s%s",
                     progress.fsize,
                     UUFNameFilter ((outfname)?outfname:infname),
                     (char *) eolstring);
        }
    }

    if ((res = UUEncodeStream (outfile, theifile, encoding, 0, crcptr, NULL)) != UURET_OK)
    {
        if (res != UURET_CANCEL)
        {
            UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                       uustring (S_ERR_ENCODING),
                       UUFNameFilter ((infname)?infname:outfname),
                       (res==UURET_IOERR)?strerror(uu_errno):UUstrerror (res));
        }
        progress.action = 0;
        return res;
    }

    if (encoding == UU_ENCODED || encoding == XX_ENCODED)
    {
        fprintf (outfile, "%c%s",
                 (encoding==UU_ENCODED) ? UUEncodeTable[0] : XXEncodeTable[0],
                 (char *) eolstring);
        fprintf (outfile, "end%s", (char *) eolstring);
    }
    else if (encoding == YENC_ENCODED)
    {
        if (progress.fsize == -1)
        {
            fprintf (outfile, "=yend crc32=%08lx%s",
                     crc,
                     (char *) eolstring);
        }
        else
        {
            fprintf (outfile, "=yend size=%ld crc32=%08lx%s",
                     progress.fsize,
                     crc,
                     (char *) eolstring);
        }
    }

    /*
     * empty line at end does no harm
     */

    fprintf (outfile, "%s", (char *) eolstring);

    if (infile==NULL) fclose (theifile);
    progress.action = 0;

    return UURET_OK;
}

/*
 * Encode to files on disk, don't generate any headers
 */

int UUEXPORT
UUEncodeToFile (FILE *infile, char *infname, int encoding,
                char *outfname, char *diskname, long linperfile)
{
    int part, numparts, len, filemode, res;
    char *oname=NULL, *optr, *ptr;
    FILE *theifile, *outfile;
    struct stat finfo;
    dword  pcrc, crc;
    dword *pcrcptr=NULL, *crcptr=NULL;

    if ((diskname==NULL&&infname==NULL) ||
            (outfname==NULL&&infname==NULL) || (infile==NULL&&infname==NULL) ||
            (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&&
             encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED))
    {
        UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                   uustring (S_PARM_CHECK), "UUEncodeToFile()");
        return UURET_ILLVAL;
    }

    if (diskname)
    {
        if ((ptr = strchr (diskname, GOLD_SLASH_CHR)) == NULL)
            ptr = strchr (diskname, GOLD_WRONG_SLASH_CHR);
        if (ptr)
        {
            len = strlen (diskname) + ((uuencodeext)?strlen(uuencodeext):3) + 5;

            if ((oname = (char*)malloc (len)) == NULL)
            {
                UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                           uustring (S_OUT_OF_MEMORY), len);
                return UURET_NOMEM;
            }
            sprintf (oname, "%s", diskname);
        }
        else
        {
            len = ((uusavepath)?strlen(uusavepath):0) + strlen (diskname)
                  + ((uuencodeext)?strlen(uuencodeext):0) + 5;

            if ((oname = (char*)malloc (len)) == NULL)
            {
                UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                           uustring (S_OUT_OF_MEMORY), len);
                return UURET_NOMEM;
            }
            sprintf (oname, "%s%s", (uusavepath)?uusavepath:"", diskname);
        }
    }
    else
    {
        len = ((uusavepath) ? strlen (uusavepath) : 0) +
              strlen(UUFNameFilter(infname)) +
              ((uuencodeext)?strlen(uuencodeext):0) + 5;

        if ((oname = (char*)malloc (len)) == NULL)
        {
            UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                       uustring (S_OUT_OF_MEMORY), len);
            return UURET_NOMEM;
        }
        optr = UUFNameFilter (infname);
        sprintf (oname, "%s%s",
                 (uusavepath)?uusavepath:"",
                 (*optr=='.')?optr+1:optr);
    }

    /*
     * optr points after the last dot, so that we can print the part number
     * there.
     */

    optr = _FP_strrchr (oname, '.');
    if (optr==NULL || strchr (optr, GOLD_SLASH_CHR)!=NULL || strchr (optr, GOLD_WRONG_SLASH_CHR)!=NULL)
    {
        optr = oname + strlen (oname);
        *optr++ = '.';
    }
    else if (optr==oname || *(optr-1)==GOLD_SLASH_CHR || *(optr-1)==GOLD_WRONG_SLASH_CHR)
    {
        optr = oname + strlen (oname);
        *optr++ = '.';
    }
    else
        optr++;

    progress.action = 0;

    if (infile==NULL)
    {
        if (stat (infname, &finfo) == -1)
        {
            UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                       uustring (S_NOT_STAT_FILE),
                       infname, strerror (uu_errno=errno));
            _FP_free (oname);
            return UURET_IOERR;
        }
        if ((theifile = fopen (infname, "rb")) == NULL)
        {
            UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                       uustring (S_NOT_OPEN_FILE),
                       infname, strerror (uu_errno=errno));
            _FP_free (oname);
            return UURET_IOERR;
        }
        if (linperfile <= 0)
            numparts = 1;
        else
            numparts = (int) (((long)finfo.st_size + (linperfile*bpl[encoding]-1)) /
                              (linperfile*bpl[encoding]));

        filemode = (int) finfo.st_mode & 0777;
        progress.totsize = (long) finfo.st_size;
    }
    else
    {
        if (fstat (fileno (infile), &finfo) == -1)
        {
            /* gotta live with it */
            filemode = 0644;
            numparts = -1;
            progress.totsize = -1;
        }
        else
        {
            if (linperfile <= 0)
                numparts = 1;
            else
                numparts = (int) (((long)finfo.st_size+(linperfile*bpl[encoding]-1))/
                                  (linperfile*bpl[encoding]));

            filemode = (int) finfo.st_mode & 0777;
            progress.totsize = -1;
        }
        theifile = infile;
    }

    _FP_strncpy (progress.curfile, (outfname)?outfname:infname, 256);

    progress.totsize  = (progress.totsize<0) ? -1 : progress.totsize;
    progress.numparts = numparts;

    for (part=1; !feof (theifile); part++)
    {
        /*
         * Attach extension
         */
        if (progress.numparts==1 && progress.totsize!=-1 && uuencodeext!=NULL)
            strcpy  (optr, uuencodeext);
        else
            sprintf (optr, "%03d", part);

        /*
         * check if target file exists
         */

        if (!uu_overwrite)
        {
            if (stat (oname, &finfo) == 0)
            {
                UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                           uustring (S_TARGET_EXISTS), oname);
                if (infile==NULL) fclose (theifile);
                progress.action = 0;
                free (oname);
                return UURET_EXISTS;
            }
        }

        /*
         * update progress information
         */

        progress.action  = 0;
        progress.partno  = part;
        progress.percent = 0;
        progress.foffset = ftell (theifile);

        if (progress.totsize == -1)
            progress.fsize = -1;
        else if (linperfile <= 0)
            progress.fsize = progress.totsize;
        else if (progress.foffset+linperfile*bpl[encoding] > progress.totsize)
            progress.fsize = progress.totsize - progress.foffset;
        else
            progress.fsize = linperfile*bpl[encoding];

        progress.action  = UUACT_ENCODING;

        if ((outfile = fopen (oname, "w")) == NULL)
        {
            UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                       uustring (S_NOT_OPEN_TARGET),
                       oname, strerror (uu_errno = errno));
            if (infile==NULL) fclose (theifile);
            progress.action = 0;
            free (oname);
            return UURET_IOERR;
        }

        if (encoding != YENC_ENCODED)
        {
            fprintf (outfile, "%s", (char *) eolstring);
            fprintf (outfile, "_=_ %s", (char *) eolstring);
            if (numparts == -1)
                fprintf (outfile, "_=_ Part %03d of file %s%s",
                         part, UUFNameFilter ((outfname)?outfname:infname),
                         (char *) eolstring);
            else
                fprintf (outfile, "_=_ Part %03d of %03d of file %s%s",
                         part, numparts,
                         UUFNameFilter ((outfname)?outfname:infname),
                         (char *) eolstring);
            fprintf (outfile, "_=_ %s", (char *) eolstring);
            fprintf (outfile, "%s", (char *) eolstring);
        }

        if (part==1 && (encoding == UU_ENCODED || encoding == XX_ENCODED))
        {
            fprintf (outfile, "begin %o %s%s",
                     (filemode)?filemode : 0644,
                     UUFNameFilter ((outfname)?outfname:infname),
                     (char *) eolstring);
        }
        else if (encoding == YENC_ENCODED)
        {
            if (!crcptr)
            {
                crc = 0;
                crcptr = &crc;
            }
            pcrc = 0;
            pcrcptr = &pcrc;
            if (numparts != 1)
            {
                if (progress.totsize == -1)
                {
                    fprintf (outfile, "=ybegin part=%d line=128 name=%s%s",
                             part,
                             UUFNameFilter ((outfname)?outfname:infname),
                             (char *) eolstring);
                }
                else
                {
                    fprintf (outfile, "=ybegin part=%d line=128 size=%ld name=%s%s",
                             part,
                             progress.totsize,
                             UUFNameFilter ((outfname)?outfname:infname),
                             (char *) eolstring);
                }

                fprintf (outfile, "=ypart begin=%ld end=%ld%s",
                         (part-1)*linperfile*128+1,
                         (part*linperfile*128) < progress.totsize ?
                         (part*linperfile*128) : progress.totsize,
                         (char *) eolstring);
            }
            else
            {
                if (progress.totsize == -1)
                {
                    fprintf (outfile, "=ybegin line=128 name=%s%s",
                             UUFNameFilter ((outfname)?outfname:infname),
                             (char *) eolstring);
                }
                else
                {
                    fprintf (outfile, "=ybegin line=128 size=%ld name=%s%s",
                             progress.totsize,
                             UUFNameFilter ((outfname)?outfname:infname),
                             (char *) eolstring);
                }
            }
        }

        if ((res = UUEncodeStream (outfile, theifile,
                                   encoding, linperfile, crcptr, pcrcptr)) != UURET_OK)
        {
            if (res != UURET_CANCEL)
            {
                UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                           uustring (S_ERR_ENCODING),
                           UUFNameFilter ((infname)?infname:outfname),
                           (res==UURET_IOERR)?strerror(uu_errno):UUstrerror (res));
            }
            if (infile==NULL) fclose (theifile);
            progress.action = 0;
            fclose (outfile);
            unlink (oname);
            _FP_free (oname);
            return res;
        }

        if (feof (theifile) &&
                (encoding == UU_ENCODED || encoding == XX_ENCODED))
        {
            fprintf (outfile, "%c%s",
                     (encoding==UU_ENCODED) ? UUEncodeTable[0] : XXEncodeTable[0],
                     (char *) eolstring);
            fprintf (outfile, "end%s", (char *) eolstring);
        }
        else if (encoding == YENC_ENCODED)
        {
            if (numparts != 1)
            {
                fprintf (outfile, "=yend size=%ld part=%d pcrc32=%08lx",
                         (part*linperfile*128) < progress.totsize ?
                         linperfile*128 : (progress.totsize-(part-1)*linperfile*128),
                         part,
                         pcrc);
            }
            else
            {
                fprintf (outfile, "=yend size=%ld",
                         progress.totsize);
            }
            if (feof (theifile))
                fprintf (outfile, " crc32=%08lx", crc);
            fprintf (outfile, "%s", (char *) eolstring);
        }

        /*
         * empty line at end does no harm
         */

        fprintf (outfile, "%s", (char *) eolstring);
        fclose  (outfile);
    }

    if (infile==NULL) fclose (theifile);
    progress.action = 0;
    _FP_free (oname);
    return UURET_OK;
}

/*
 * Encode a MIME Mail message or Newsgroup posting and send to a
 * stream. Still needs a somewhat smart MDA, since we only gene-
 * rate a minimum set of headers.
 */

int UUEXPORT
UUE_PrepSingle (FILE *outfile, FILE *infile,
                char *infname, int encoding,
                char *outfname, int filemode,
                char *destination, char *from,
                char *subject, int isemail)
{
    return UUE_PrepSingleExt (outfile, infile,
                              infname, encoding,
                              outfname, filemode,
                              destination, from,
                              subject, NULL,
                              isemail);
}

int UUEXPORT
UUE_PrepSingleExt (FILE *outfile, FILE *infile,
                   char *infname, int encoding,
                   char *outfname, int filemode,
                   char *destination, char *from,
                   char *subject, char *replyto,
                   int isemail)
{
    mimemap *miter=mimetable;
    char *subline, *oname;
    char *mimetype, *ptr;
    int res, len;

    if ((outfname==NULL&&infname==NULL) || (infile==NULL&&infname==NULL) ||
            (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&&
             encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED))
    {
        UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                   uustring (S_PARM_CHECK), "UUE_PrepSingle()");
        return UURET_ILLVAL;
    }

    oname = UUFNameFilter ((outfname)?outfname:infname);
    len   = ((subject)?strlen(subject):0) + strlen(oname) + 40;

    if ((ptr = _FP_strrchr (oname, '.')))
    {
        while (miter->extension && _FP_stricmp (ptr+1, miter->extension) != 0)
            miter++;
        mimetype = miter->mimetype;
    }
    else
        mimetype = NULL;

    if (mimetype == NULL && (encoding == PT_ENCODED || encoding == QP_ENCODED))
    {
        mimetype = "text/plain";
    }

    if ((subline = (char *) malloc (len)) == NULL)
    {
        UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                   uustring (S_OUT_OF_MEMORY), len);
        return UURET_NOMEM;
    }

    if (encoding == YENC_ENCODED)
    {
        if (subject)
            sprintf (subline, "- %s - %s (001/001)", oname, subject);
        else
            sprintf (subline, "- %s - (001/001)", oname);
    }
    else
    {
        if (subject)
            sprintf (subline, "%s (001/001) - [ %s ]", subject, oname);
        else
            sprintf (subline, "[ %s ] (001/001)", oname);
    }

    if (from)
    {
        fprintf (outfile, "From: %s%s", from, (char *) eolstring);
    }
    if (destination)
    {
        fprintf (outfile, "%s: %s%s",
                 (isemail)?"To":"Newsgroups",
                 destination, (char *) eolstring);
    }

    fprintf (outfile, "Subject: %s%s", subline, (char *) eolstring);

    if (replyto)
    {
        fprintf (outfile, "Reply-To: %s%s", replyto, (char *) eolstring);
    }

    if (encoding != YENC_ENCODED)
    {
        fprintf (outfile, "MIME-Version: 1.0%s", (char *) eolstring);
        fprintf (outfile, "Content-Type: %s; name=\"%s\"%s",
                 (mimetype)?mimetype:"Application/Octet-Stream",
                 UUFNameFilter ((outfname)?outfname:infname),
                 (char *) eolstring);
        fprintf (outfile, "Content-Transfer-Encoding: %s%s",
                 CTE_TYPE(encoding), (char *) eolstring);
    }

    fprintf (outfile, "%s", (char *) eolstring);

    res = UUEncodeToStream (outfile, infile, infname, encoding,
                            outfname, filemode);

    _FP_free (subline);
    return res;
}

int UUEXPORT
UUE_PrepPartial (FILE *outfile, FILE *infile,
                 char *infname, int encoding,
                 char *outfname, int filemode,
                 int partno, long linperfile, long filesize,
                 char *destination, char *from, char *subject,
                 int isemail)
{
    return UUE_PrepPartialExt (outfile, infile,
                               infname, encoding,
                               outfname, filemode,
                               partno, linperfile, filesize,
                               destination,
                               from, subject, NULL,
                               isemail);
}

int UUEXPORT
UUE_PrepPartialExt (FILE *outfile, FILE *infile,
                    char *infname, int encoding,
                    char *outfname, int filemode,
                    int partno, long linperfile, long filesize,
                    char *destination,
                    char *from, char *subject, char *replyto,
                    int isemail)
{
    static int numparts, themode;
    static char mimeid[64];
    static FILE *theifile;
    struct stat finfo;
    char *subline, *oname;
    long thesize;
    int res, len;
    static dword crc;
    dword *crcptr=NULL;

    if ((outfname==NULL&&infname==NULL) || (infile==NULL&&infname==NULL) ||
            (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&&
             encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED))
    {
        UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                   uustring (S_PARM_CHECK), "UUE_PrepPartial()");
        return UURET_ILLVAL;
    }

    oname = UUFNameFilter ((outfname)?outfname:infname);
    len   = ((subject)?strlen(subject):0) + strlen (oname) + 40;

    /*
     * if first part, get information about the file
     */

    if (partno == 1)
    {
        if (infile==NULL)
        {
            if (stat (infname, &finfo) == -1)
            {
                UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                           uustring (S_NOT_STAT_FILE),
                           infname, strerror (uu_errno=errno));
                return UURET_IOERR;
            }
            if ((theifile = fopen (infname, "rb")) == NULL)
            {
                UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                           uustring (S_NOT_OPEN_FILE),
                           infname, strerror (uu_errno=errno));
                return UURET_IOERR;
            }
            if (linperfile <= 0)
                numparts = 1;
            else
                numparts = (int) (((long)finfo.st_size+(linperfile*bpl[encoding]-1))/
                                  (linperfile*bpl[encoding]));

            themode  = (filemode) ? filemode : ((int) finfo.st_mode & 0777);
            thesize  = (long) finfo.st_size;
        }
        else
        {
            if (fstat (fileno (infile), &finfo) != 0)
            {
                if (filesize <= 0)
                {
                    UUMessage (uuencode_id, __LINE__, UUMSG_WARNING,
                               uustring (S_STAT_ONE_PART));
                    numparts = 1;
                    themode  = (filemode)?filemode:0644;
                    thesize  = -1;
                }
                else
                {
                    if (linperfile <= 0)
                        numparts = 1;
                    else
                        numparts = (int) ((filesize+(linperfile*bpl[encoding]-1))/
                                          (linperfile*bpl[encoding]));

                    themode  = (filemode)?filemode:0644;
                    thesize  = filesize;
                }
            }
            else
            {
                if (linperfile <= 0)
                    numparts = 1;
                else
                    numparts = (int) (((long)finfo.st_size+(linperfile*bpl[encoding]-1))/
                                      (linperfile*bpl[encoding]));

                filemode = (int) finfo.st_mode & 0777;
                thesize  = (long) finfo.st_size;
            }
            theifile = infile;
        }

        /*
         * if there's one part only, don't use Message/Partial
         */

        if (numparts == 1)
        {
            if (infile==NULL) fclose (theifile);
            return UUE_PrepSingleExt (outfile, infile, infname, encoding,
                                      outfname, filemode, destination,
                                      from, subject, replyto, isemail);
        }

        /*
         * we also need a unique ID
         */

        sprintf (mimeid, "UUDV-%ld.%ld.%s",
                 (long) time(NULL), thesize,
                 (strlen(oname)>16)?"oops":oname);
    }

    if ((subline = (char *) malloc (len)) == NULL)
    {
        UUMessage (uuencode_id, __LINE__, UUMSG_ERROR,
                   uustring (S_OUT_OF_MEMORY), len);
        if (infile==NULL) fclose (theifile);
        return UURET_NOMEM;
    }


    if (encoding == YENC_ENCODED)
    {
        if (partno == 1) crc = 0;
        crcptr = &crc;
        if (subject)
            sprintf (subline, "- %s - %s (%03d/%03d)", oname, subject,
                     partno, numparts);
        else
            sprintf (subline, "- %s - (%03d/%03d)", oname,
                     partno, numparts);
    }
    else
    {
        if (subject)
            sprintf (subline, "%s (%03d/%03d) - [ %s ]",
                     subject, partno, numparts, oname);
        else
            sprintf (subline, "[ %s ] (%03d/%03d)",
                     oname, partno, numparts);
    }

    if (from)
    {
        fprintf (outfile, "From: %s%s", from, (char *) eolstring);
    }

    if (destination)
    {
        fprintf (outfile, "%s: %s%s",
                 (isemail)?"To":"Newsgroups",
                 destination, (char *) eolstring);
    }

    fprintf (outfile, "Subject: %s%s", subline, (char *) eolstring);

    if (replyto)
    {
        fprintf (outfile, "Reply-To: %s%s", replyto, (char *) eolstring);
    }

    if (encoding != YENC_ENCODED)
    {
        fprintf (outfile, "MIME-Version: 1.0%s", (char *) eolstring);
        fprintf (outfile, "Content-Type: Message/Partial; number=%d; total=%d;%s",
                 partno, numparts, (char *) eolstring);
        fprintf (outfile, "\tid=\"%s\"%s",
                 mimeid, (char *) eolstring);
    }

    fprintf (outfile, "%s", (char *) eolstring);

    res = UUEncodePartial (outfile, theifile,
                           infname, encoding,
                           (outfname)?outfname:infname, NULL,
                           themode, partno, linperfile, crcptr);

    _FP_free (subline);

    if (infile==NULL)
    {
        if (res != UURET_OK)
        {
            fclose (theifile);
            return res;
        }
        if (feof (theifile))
        {
            fclose (theifile);
            return UURET_OK;
        }
        return UURET_CONT;
    }

    return res;
}
