/*
 * Decode MIME parts.
 */
/* (C) Copyright 1993,1994 by Carnegie Mellon University
 * All Rights Reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of Carnegie
 * Mellon University not be used in advertising or publicity
 * pertaining to distribution of the software without specific,
 * written prior permission.  Carnegie Mellon University makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.  */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "xmalloc.h"
#include "common.h"
#include "part.h"
#include "md5.h"

extern char *os_idtodir();
extern FILE *os_newtypedfile();
extern char *md5contextTo64();

/* The possible content transfer encodings */
enum encoding { enc_none, enc_qp, enc_base64 };

char *ParseHeaders();
enum encoding parseEncoding();
params ParseContent();
char *getParam();
char *getDispositionFilename();

/*
 * Read and handle an RFC 822 message from the body-part 'inpart'.
 */
handleMessage(struct part *inpart, char *defaultContentType, int inAppleDouble, int extractText)
{
    char *headers, *subject, *contentType, *contentDisposition, *contentMD5;
    enum encoding contentEncoding;
    params contentParams;

    /* Parse the headers, getting the ones we're interested in */

    headers = ParseHeaders(inpart, &subject, &contentType, &contentEncoding,
                           &contentDisposition, &contentMD5);
    if (!headers) return 1;


    /* If no content type, or a non-MIME content type, use the default */
    if (!contentType || !strchr(contentType, '/')) {
        contentType = defaultContentType;
    }
    contentParams = ParseContent(&contentType);



    if (!cistrcmp(contentType, "message/rfc822")) {
        if (contentEncoding != enc_none) {
            warn("ignoring invalid content encoding on message/rfc822");
        }

        /* Simple recursion */
        return handleMessage(inpart, "text/plain", 0, extractText);
    }
    else if (!cistrcmp(contentType, "message/partial")) {
        if (contentEncoding != enc_none) {
            warn("ignoring invalid content encoding on message/partial");
        }
        return handlePartial(inpart, headers, contentParams, extractText);
    }
    else if (!cistrncmp(contentType, "message/", 8)) {
        /* Probably message/external.  We don't care--toss it */
        return ignoreMessage(inpart);
    }
    else if (!cistrncmp(contentType, "multipart/", 10)) {
        if (contentEncoding != enc_none) {
            warn("ignoring invalid content encoding on multipart");
        }
        return handleMultipart(inpart, contentType, contentParams,
                               extractText);
    }
    else if (part_depth(inpart) == 0 &&
             !cistrncmp(contentType, "text/", 5) &&
             contentEncoding == enc_none &&
             !getDispositionFilename(contentDisposition) &&
             !getParam(contentParams, "name")) {
        /* top-level text message, handle as possible uuencoded file */
        return handleUuencode(inpart, subject, extractText);
    }
    else if (!extractText && !inAppleDouble &&
             !cistrncmp(contentType, "text/", 5) &&
             !getDispositionFilename(contentDisposition) &&
             !getParam(contentParams, "name")) {
        return handleText(inpart, contentEncoding);
    }
    else {
        /* Some sort of attachment, extract it */
        return saveToFile(inpart, inAppleDouble, contentType, contentParams,
                          contentEncoding, contentDisposition, contentMD5);
    }
}

/*
 * Skip whitespace and RFC-822 comments.
 */
SkipWhitespace(char **s)
{
    char *p = *s;
    int commentlevel = 0;

    while (*p && (isspace(*p) || *p == '(')) {
        if (*p == '\n') {
            p++;
            if (*p != ' ' && *p != '\t') {
                *s = 0;
                return;
            }
        }
        else if (*p == '(') {
            p++;
            commentlevel++;
            while (commentlevel) {
                switch (*p) {
                case '\n':
                    p++;
                    if (*p == ' ' || *p == '\t') break;
                    /* FALL THROUGH */
                case '\0':
                    *s = 0;
                    return;
                    
                case '\\':
                    p++;
                    break;

                case '(':
                    commentlevel++;
                    break;

                case ')':
                    commentlevel--;
                    break;
                }
                p++;
            }
        }
        else p++;
    }
    if (*p == 0) {
        *s = 0;
    }
    else {
        *s = p;
    }
}

/*
 * Read and parse the headers of an RFC 822 message, returning them in
 * a pointer to a static buffer.  The headers are read from 'inpart'.
 * A pointer to the value of any Subject:, Content-Type:,
 * Content-Disposition:, or Content-MD5: header is stored in the space
 * pointed to by 'subjectp', 'contentTypep', contentDispositionp, and
 * contentMD5p, respectively.  The Content-Transfer-Encoding is stored
 * in the enum pointed to by 'contentEncodingp'.
 */
#define HEADGROWSIZE 1000
char *ParseHeaders(struct part *inpart, char **subjectp, char **contentTypep, enum encoding *contentEncodingp,
                   char **contentDispositionp, char **contentMD5p)
{
    static int alloced = 0;
    static char *headers;
    int left, len, i;
    char *next, *val;

    /* Read headers into buffer pointed to by "headers" */
    if (!alloced) {
        headers = xmalloc(alloced = HEADGROWSIZE);
    }
    next = headers;
    *next++ = '\n';             /* Leading newline to make matching header names easier */
    left = alloced - 2;         /* Allow room for terminating null */

    while (part_gets(next, left, inpart) && (*next != '\n' || next[-1] != '\n')) {
        len = strlen(next);

        if (next[-1] == '\n') {
            /* Check for valid header-ness of "next" */
            for (i = 0; i < len; i++) {
                if (next[i] == ':' ||
                    next[i] <= ' ' || next[i] >= '\177') break;
            }
            if (i == 0 || next[i] != ':') {
                /* Check for header continuation line */
                if (next == headers+1 || (next[0] != ' ' && next[0] != '\t')) {
                    /*
                     * Not a valid header, push back on input stream
                     * and stop reading input.
                     */
                    part_ungets(next, inpart);
                    break;
                }
            }
        }

        left -= len;
        next += len;

        if (left < 100) {
            len = next - headers;
            alloced += HEADGROWSIZE;
            left += HEADGROWSIZE;
            headers = xrealloc(headers, alloced);
            next = headers + len;
        }
    }

    *next = '\0';

    /* Look for the headers we find particularly interesting */
    *subjectp = *contentTypep = *contentDispositionp = *contentMD5p = 0;
    *contentEncodingp = enc_none;
    for (next = headers; *next; next++) {
        if (*next == '\n') {
            switch(next[1]) {
            case 's':
            case 'S':
                if (!cistrncmp(next+2, "ubject:", 7)) {
                    val = next+9;
                    SkipWhitespace(&val);
                    if (val) *subjectp = val;
                }
                break;

            case 'c':
            case 'C':
                if (!cistrncmp(next+2, "ontent-type:", 12)) {
                    val = next+14;
                    SkipWhitespace(&val);
                    if (val) *contentTypep = val;
                }
                else if (!cistrncmp(next+2, "ontent-transfer-encoding:", 25)) {
                    *contentEncodingp = parseEncoding(next+27);
                }
                else if (!cistrncmp(next+2, "ontent-disposition:", 19)) {
                    val = next+21;
                    SkipWhitespace(&val);
                    if (val) *contentDispositionp = val;
                }
                else if (!cistrncmp(next+2, "ontent-md5:", 11)) {
                    val = next+13;
                    SkipWhitespace(&val);
                    if (val) *contentMD5p = val;
                }
            }
        }
    }
    return headers;
}

/*
 * Parse the Content-Transfer-Encoding: value pointed to by 's'.
 * Returns the appropriate encoding enum.
 */
enum encoding parseEncoding(char *s)
{
    SkipWhitespace(&s);
    if (s) {
        switch (*s) {
        case 'q':
        case 'Q':
            if (!cistrncmp(s+1, "uoted-printable", 15) &&
                (isspace(s[16]) || s[16] == '(')) {
                return enc_qp;
            }
            break;

        case '7':
        case '8':
            if (!cistrncmp(s+1, "bit", 3) &&
                (isspace(s[4]) || s[4] == '(')) {
                return enc_none;
            }
            break;

        case 'b':
        case 'B':
            if (!cistrncmp(s+1, "ase64", 5) &&
                (isspace(s[6]) || s[6] == '(')) {
                return enc_base64;
            }
            if (!cistrncmp(s+1, "inary", 5) &&
                (isspace(s[6]) || s[6] == '(')) {
                return enc_none;
            }
        }
        warn("ignoring unknown content transfer encoding\n");   
    }
    return enc_none;
}

/*
 * Parse the value of a Content-Type: header.
 * 'headerp' points to a pointer to the input string.
 * The pointer pointed to by 'headerp' is changed to point to
 * a static buffer containing the content type stripped of whitespace
 * and parameters.  The parameters are converted to a type suitable for
 * getParm() and returned.
 */
#define PARAMGROWSIZE 10
params ParseContent(char **headerp)
{
    char *header;
    static int palloced = 0;
    static char **param;
    static int calloced = 0;
    static char *cbuf;
    char *p;
    int nparam;

    p = header = *headerp;

    /* Find end of header, including continuation lines */
    do {
        p = strchr(p+1, '\n');
    } while (p && isspace(p[1]));
    if (!p) {
        p = header + strlen(header);
    }

    /* If necessary, allocate/grow cbuf to hold header. */
    if (p - header >= calloced) {
        calloced = p - header + 1;
        if (calloced < 200) calloced = 200;
        cbuf = xrealloc(cbuf, calloced);
    }

    /* Copy header to cbuf */
    strncpy(cbuf, header, p - header);
    cbuf[p - header] = 0;
    header = *headerp = cbuf;
    
    nparam = 0;

    /* Strip whitespace from content type */
    /* ParseHeaders() stripped leading whitespace */
    p = header;
    while (header && *header && *header != ';') {
        while (*header && !isspace(*header) && *header != '(' &&
               *header != ';') {
            *p++ = *header++;
        }
        SkipWhitespace(&header);
    }
    if (!header || !*header) return 0;
    header++;
    *p = '\0';
    
    /* Parse the parameters */
    while (*header) {
        SkipWhitespace(&header);
        if (!header) break;

        if (nparam+1 >= palloced) {
            palloced += PARAMGROWSIZE;
            param = (char **) xrealloc((char *)param, palloced * sizeof(char *));
        }
        param[nparam++] = header;

        /* Find any separating semicolon.  Pay attention to quoted-strings */
        while (*header && *header != ';') {
            if (*header == '\"') {
                ++header;
                while (*header && *header != '\"') {
                    if (*header == '\\') {
                        ++header;
                        if (!*header) break;
                    }
                    ++header;
                }
                if (!*header) break;
            }
            else if (*header == '(') {
                /* Convert comments to spaces */
                p = header;
                SkipWhitespace(&p);
                if (!p) {
                    break;
                }
                while (header < p) *header++ = ' ';
                header--;
            }
            header++;
        }
        if (*header) *header++ = '\0';
    }
    param[nparam] = 0;
    return param;
}

/*
 * Get the value of the parameter named 'key' from the content-type
 * parameters 'cParams'.  Returns a pointer to a static bufer which
 * contains the value, or null if no such parameter was found.
 */
#define VALUEGROWSIZE 100
char *getParam(params cParams, char *key)
{
    static char *value;
    static int alloced = 0;
    int left;
    int keylen = strlen(key);
    char *from, *to;

    if (!cParams) return 0;

    if (!alloced) {
        value = xmalloc(alloced = VALUEGROWSIZE);
    }

    /* Find the named parameter */
    while (*cParams) {
        if (!cistrncmp(key, *cParams, keylen) &&
            ((*cParams)[keylen] == '=' || isspace((*cParams)[keylen]))) break;
        cParams++;
    }
    if (!*cParams) return 0;

    /* Skip over the "=" and any surrounding whitespace */
    from = *cParams + keylen;
    while (*from && isspace(*from)) from++;
    if (*from++ != '=') return 0;
    while (*from && isspace(*from)) from++;
    if (!*from) return 0;

    /* Copy value into buffer */
    to = value;
    left = alloced - 1;
    if (*from == '\"') {
        /* Quoted-string */
        from++;
        while (*from && *from != '\"') {
            if (!--left) {
                alloced += VALUEGROWSIZE;
                value = xrealloc(value, alloced);
                to = value + alloced - left - 2;
            }
            if (*from == '\\') {
                from++;
                if (!*from) return 0;
            }
            *to++ = *from++;
        }
        if (!*from) return 0;
    }
    else {
        /* Just a token */
        while (*from && !isspace(*from)) {
            if (!--left) {
                alloced += VALUEGROWSIZE;
                value = xrealloc(value, alloced);
                to = value + alloced - left - 2;
            }
            *to++ = *from++;
        }
    }
    *to = '\0';
    return value;
}

/*
 * Get the value of the "filename" parameter in a Content-Disposition:
 * header.  Returns a pointer to a static buffer containing the value, or
 * a null pointer if there was no such parameter.
 */
char *
getDispositionFilename(char *disposition)
{
    static char *value;
    static int alloced = 0;
    int left;
    char *to;

    if (!disposition) return 0;

    /* Skip until we find ";" "filename" "=" tokens. */
    for (;;) {
        /* Skip until we find ";" */
        while (*disposition != ';') {
            if (!*disposition) return 0;
            else if (*disposition == '\"') {
                ++disposition;
                while (*disposition && *disposition != '\"') {
                    if (*disposition == '\\') {
                        ++disposition;
                        if (!*disposition) return 0;
                    }
                    ++disposition;
                }
                if (!*disposition) return 0;
            }
            else if (*disposition == '(') {
                SkipWhitespace(&disposition);
                if (!disposition) return 0;
                disposition--;
            }
            disposition++;
        }

        /* Skip over ";" and trailing whitespace */
        disposition++;
        SkipWhitespace(&disposition);
        if (!disposition) return 0;

        /*
         * If we're not looking at a "filename" token, go back
         * and look for another ";".  Otherwise skip it and
         * trailing whitespace.
         */
        if (cistrncmp(disposition, "filename", 8) != 0) continue;
        disposition += 8;
        if (!isspace(*disposition) && *disposition != '=' &&
            *disposition != '(') {
            continue;
        }
        SkipWhitespace(&disposition);
        if (!disposition) return 0;

        /* If we're looking at a ";", we found what we're looking for */
        if (*disposition++ == '=') break;
    }

    SkipWhitespace(&disposition);
    if (!disposition) return 0;
      
    if (!alloced) {
        value = xmalloc(alloced = VALUEGROWSIZE);
    }

    /* Copy value into buffer */
    to = value;
    left = alloced - 1;
    if (*disposition == '\"') {
        /* Quoted-string */
        disposition++;
        while (*disposition && *disposition != '\"') {
            if (!--left) {
                alloced += VALUEGROWSIZE;
                value = xrealloc(value, alloced);
                to = value + alloced - left - 2;
            }
            if (*disposition == '\\') {
                disposition++;
                if (!*disposition) return 0;
            }
            *to++ = *disposition++;
        }
        if (!*disposition) return 0;
    }
    else {
        /* Just a token */
        while (*disposition && !isspace(*disposition) &&
               *disposition != '(') {
            if (!--left) {
                alloced += VALUEGROWSIZE;
                value = xrealloc(value, alloced);
                to = value + alloced - left - 2;
            }
            *to++ = *disposition++;
        }
    }
    *to = '\0';
    return value;
}    

/*
 * Read and handle a message/partial object from the file 'inpart'.
 */
handlePartial(struct part *inpart, char *headers, params contentParams, int extractText)
{
    char *id, *dir, *p;
    int thispart;
    int nparts = 0;
    char buf[1024];
    FILE *partfile, *outfile;
    struct part *outpart;
    int i, docopy;

    id = getParam(contentParams, "id");
    if (!id) {
        warn("partial message has no id parameter");
        goto ignore;
    }

    /* Get directory to store the parts being reassembled */
    dir = os_idtodir(id);
    if (!dir) goto ignore;

    p = getParam(contentParams, "number");
    if (!p) {
        warn("partial message doesn't have number parameter");
        goto ignore;
    }
    thispart = atoi(p);

    if (p = getParam(contentParams, "total")) {
        nparts = atoi(p);
        if (nparts <= 0) {
            warn("partial message has invalid number of parts");
            goto ignore;
        }
        /* Store number of parts in reassembly directory */
        sprintf(buf, "%sCT", dir);
        partfile = fopen(buf, "w");
        if (!partfile) {
            os_perror(buf);
            goto ignore;
        }
        fprintf(partfile, "%d\n", nparts);
        if (partfile) fclose(partfile);
    }
    else {
        /* Try to retrieve number of parts from reassembly directory */
        sprintf(buf, "%sCT", dir);
        if (partfile = fopen(buf, "r")) {
            if (fgets(buf, sizeof(buf), partfile)) {
                nparts = atoi(buf);
                if (nparts < 0) nparts = 0;
            }
            if (partfile) fclose(partfile);
        }
    }

    /* Sanity check */
    if (thispart <= 0 || (nparts && thispart > nparts)) {
        warn("partial message has invalid number");
        goto ignore;
    }

    sprintf(buf, "Saving part %d ", thispart);
    if (nparts) sprintf(buf+strlen(buf), "of %d ", nparts);
    strcat(buf, getParam(contentParams, "id"));
    chat(buf);

    /* Create file to store this part */
    sprintf(buf, "%s%d", dir, thispart);
    partfile = fopen(buf, "w");
    if (!partfile) {
        os_perror(buf);
        goto ignore;
    }

    /* Do special-case header handling for first part */
    if (thispart == 1) {
        int skippedfirstbyte = 0;

        while (*headers) {
            if (*headers == '\n' &&
                (!cistrncmp(headers, "\ncontent-", 9) ||
                 !cistrncmp(headers, "\nmessage-id:", 12))) {
                /* Special case, skip header */
                headers++;
                while (*headers && (*headers != '\n' || isspace(headers[1]))) {
                    headers++;
                }
            }
            else {
                /* First byte of headers is extra newline, don't write it to file */
                if (skippedfirstbyte++) putc(*headers, partfile);
                headers++;
            }
        }
        docopy = 0;
        /* Handle headers in the multipart/partial body */
        while (part_gets(buf, sizeof(buf), inpart)) {
            if (*buf == '\n') {
                putc('\n', partfile);
                break;
            }
            if (!cistrncmp(buf, "content-", 8) || !cistrncmp(buf, "message-id:", 11)) {
                docopy = 1;
            }
            else if (!isspace(*buf)) {
                docopy = 0;
            }

            if (docopy) fputs(buf, partfile);
            while(buf[strlen(buf)-1] != '\n' && part_gets(buf, sizeof(buf), inpart)) {
                if (docopy) fputs(buf, partfile);
            }
        }
    }

    /* Copy the contents to the file */
    while (part_gets(buf, sizeof(buf), inpart)) {
        fputs(buf, partfile);
    }
    if (partfile) fclose(partfile);

    /* Check to see if we have all parts.  Start from the highest numbers
     * as we are more likely not to have them.
     */
    for (i = nparts; i; i--) {
        sprintf(buf, "%s%d", dir, i);
        partfile = fopen(buf, "r");
        if (partfile) {
            fclose(partfile);
        }
        else {
            break;
        }
    }

    if (i || !nparts) {
        /* We don't have all the parts yet */
        return 0;
    }

    /* We have everything, concatenate all the parts into a single file */
    sprintf(buf, "%sFULL", dir);
    outfile = fopen(buf, "w");
    if (!outfile) {
        os_perror(buf);
        return 1;
    }
    for (i=1; i<=nparts; i++) {
        sprintf(buf, "%s%d", dir, i);
        partfile = fopen(buf, "r");
        if (!partfile) {
            os_perror(buf);
            return 1;
        }
        while (fgets(buf, sizeof(buf), partfile)) {
            fputs(buf, outfile);
        }
        if (partfile) fclose(partfile);

        /* Done with message part file, delete it */
        sprintf(buf, "%s%d", dir, i);
        remove(buf);
    }

    /* Open the concatenated file for reading and handle it */
    if (outfile) fclose(outfile);
    sprintf(buf, "%sFULL", dir);
    outfile = fopen(buf, "r");
    if (!outfile) {
        os_perror(buf);
        return 1;
    }
    outpart = part_init(outfile);
    handleMessage(outpart, "text/plain", 0, extractText);
    part_close(outpart);

    /* Clean up the rest of the reassembly directory */
    sprintf(buf, "%sFULL", dir);
    remove(buf);
    sprintf(buf, "%sCT", dir);
    remove(buf);
    os_donewithdir(dir);

    return 0;

 ignore:
    ignoreMessage(inpart);
    return 1;
}

/*
 * Skip over a message object from the file 'inpart'.
 */
ignoreMessage(struct part *inpart)
{
    while (part_getc(inpart) != EOF);
    return 0;
}

/*
 * Read and handle a multipart object from 'inpart'.
 */
handleMultipart(struct part *inpart, char *contentType, params contentParams, int extractText)
{
    char *id;
    char *defaultContentType = "text/plain";
    int isAppleDouble = 0;

    /* Components of multipart/digest have a different default content-type */
    if (!cistrcmp(contentType, "multipart/digest")) {
        defaultContentType = "message/rfc822";
    }
    if (!cistrcmp(contentType, "multipart/appledouble")) {
        isAppleDouble++;
    }

    if (!(id = getParam(contentParams, "boundary"))) {
        warn("multipart message has no boundary parameter");
        id="";
    }

    /* Add the new boundary id */
    part_addboundary(inpart, id);

#ifdef __riscos
    /*
     * "Marcel" encodes RISCOS directory structure in the multipart
     * structure.  That is the Wrong Way to do it, but we hold our
     * nose and pass the information to the OS layer.
     */
    os_boundaryhookopen(part_depth(inpart));
#endif

    /*
     * Skip over preamble.
     * HACK: The initial boundary doesn't have to start with a newline,
     * so we deal with this by stuffing an initial newline into the input
     * stream
     */
    part_ungetc('\n', inpart);
    ignoreMessage(inpart);

    /* Handle the component messages */
    while (!part_readboundary(inpart)) {
        handleMessage(inpart, defaultContentType, isAppleDouble, extractText);
    }

#ifdef __riscos
    os_boundaryhookclose(part_depth(inpart));
#endif

    /* Skip over postamble */
    ignoreMessage(inpart);

    /* Remove any lingering unused description file */
    (void) remove(TEMPFILENAME);

    return 0;
}

/*
 * Handle a text message object from 'inpart' by saving it to
 * the temporary description file.
 */
int handleText(struct part *inpart, enum encoding contentEncoding)
{
    FILE *descfile;

    descfile = fopen(TEMPFILENAME, "w");
    if (!descfile) {
        os_perror(TEMPFILENAME);
        ignoreMessage(inpart);
        return 1;
    }

    /* Write the file, handling the appropriate encoding */
    switch (contentEncoding) {
    case enc_none:
        fromnone(inpart, descfile, (char **)0);
        break;

    case enc_qp:
        fromqp(inpart, descfile, (char **)0);
        break;

    case enc_base64:
        from64(inpart, descfile, (char **)0, 1);
        break;
    }

    if (descfile) fclose(descfile);
    return 0;
}

/*
 * Read a message object from 'inpart' and save it to a file.
 */
saveToFile(struct part *inpart, int inAppleDouble, char *contentType, params contentParams,
           enum encoding contentEncoding, char *contentDisposition, char *contentMD5)
{
    FILE *outfile = 0;
    int flags = 0;
    int suppressCR = 0;
    char *outputmd5;
    char *fname;


    if (!cistrncmp(contentType, "text/", 5)) {
        suppressCR = 1;
    }
    else if (contentEncoding == enc_base64) {
        /*
         * HEURISTIC: It is not in general possible to determine whether
         * any non-text content type is line-oriented.  We guess
         * the "binary" status of a part from the composer's choice
         * of content transfer encoding.
         *
         * If the content transfer encoding is "binary" and the input is
         * not line-oriented, we're screwed anyway--the input file has
         * been opened in text mode.  So the "binary output file" heuristic
         * is not applied in this case.
         */
        flags |= FILE_BINARY;
    }


    if (inAppleDouble) flags |= FILE_INAPPLEDOUBLE;
    
    /* Find an appropriate filename and create the output file */
    fname = getDispositionFilename(contentDisposition);
    if (!fname) fname = getParam(contentParams, "name");
    if (fname) fname = strsave(fname);
    outfile = os_newtypedfile(fname, contentType, flags, contentParams);
    if (fname) free(fname);
    if (!outfile) {
        ignoreMessage(inpart);
        return 1;
    }

    /* Write the file, handling the appropriate encoding */
    switch (contentEncoding) {
    case enc_none:
        fromnone(inpart, outfile, &outputmd5);
        break;

    case enc_qp:
        fromqp(inpart, outfile, &outputmd5);
        break;

    case enc_base64:
        from64(inpart, outfile, &outputmd5, suppressCR);
        break;
    }
    rewind(outfile);

    /* Check the MD5 digest if it was supplied */
    if (contentMD5) {
        if (strncmp(outputmd5, contentMD5, strlen(outputmd5)) != 0) {
            os_warnMD5mismatch();
        }
    }
    free(outputmd5);

    os_closetypedfile(outfile);
    return 0;
}

#define XX 127
/*
 * Table for decoding hexadecimal in quoted-printable
 */
static char index_hex[256] = {
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
     0, 1, 2, 3,  4, 5, 6, 7,  8, 9,XX,XX, XX,XX,XX,XX,
    XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
};
#define HEXCHAR(c)  (index_hex[(unsigned char)(c)])

/*
 * Table for decoding base64
 */
static char index_64[256] = {
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63,
    52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
    XX, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
    15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
    XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
    41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
};
#define CHAR64(c)  (index_64[(unsigned char)(c)])

from64(struct part *inpart, FILE *outfile, char **digestp, int suppressCR)
{
    int c1, c2, c3, c4;
    int DataDone = 0;
    char buf[3];
    MD5_CTX context;

    if (digestp) MD5Init(&context);
    while ((c1 = part_getc(inpart)) != EOF) {
        if (c1 != '=' && CHAR64(c1) == XX) {
            continue;
        }
        if (DataDone) continue;
        do {
            c2 = part_getc(inpart);
        } while (c2 != EOF && c2 != '=' && CHAR64(c2) == XX);
        do {
            c3 = part_getc(inpart);
        } while (c3 != EOF && c3 != '=' && CHAR64(c3) == XX);
        do {
            c4 = part_getc(inpart);
        } while (c4 != EOF && c4 != '=' && CHAR64(c4) == XX);
        if (c2 == EOF || c3 == EOF || c4 == EOF) {
            warn("Premature EOF");
            break;
        }
        if (c1 == '=' || c2 == '=') {
            DataDone=1;
            continue;
        }
        c1 = CHAR64(c1);
        c2 = CHAR64(c2);
        buf[0] = ((c1<<2) | ((c2&0x30)>>4));
        if (!suppressCR || buf[0] != '\r') putc(buf[0], outfile);
        if (c3 == '=') {
            if (digestp) MD5Update(&context, buf, 1);
            DataDone = 1;
        } else {
            c3 = CHAR64(c3);
            buf[1] = (((c2&0x0F) << 4) | ((c3&0x3C) >> 2));
            if (!suppressCR || buf[1] != '\r') putc(buf[1], outfile);
            if (c4 == '=') {
                if (digestp) MD5Update(&context, buf, 2);
                DataDone = 1;
            } else {
                c4 = CHAR64(c4);
                buf[2] = (((c3&0x03) << 6) | c4);
                if (!suppressCR || buf[2] != '\r') putc(buf[2], outfile);
                if (digestp) MD5Update(&context, buf, 3);               
            }
        }
    }
    if (digestp) *digestp = md5contextTo64(&context);
}

fromqp(struct part *inpart, FILE *outfile, char **digestp)
{
    int c1, c2;
    MD5_CTX context;
    char c;

    if (digestp) MD5Init(&context);

    while ((c1 = part_getc(inpart)) != EOF) {
        if (c1 == '=') {
            c1 = part_getc(inpart);
            if (c1 != '\n') {
                c1 = HEXCHAR(c1);
                c2 = part_getc(inpart);
                c2 = HEXCHAR(c2);
                c = c1<<4 | c2;
                if (c != '\r') putc(c, outfile);
                if (digestp) MD5Update(&context, &c, 1);
            }
        } else {
            putc(c1, outfile);
            if (c1 == '\n') {
                if (digestp) MD5Update(&context, "\r", 1);
            }
            c = c1;
            if (digestp) MD5Update(&context, &c, 1);
        }
    }
    if (digestp) *digestp=md5contextTo64(&context);
}

fromnone(struct part *inpart, FILE *outfile, char **digestp)
{
    int c;
    char ch;
    MD5_CTX context;

    if (digestp) MD5Init(&context);

    while ((c = part_getc(inpart)) != EOF) {
        putc(c, outfile);
        if (c == '\n') {
            if (digestp) MD5Update(&context, "\r", 1);
        }
        ch = c;
        if (digestp) MD5Update(&context, &ch, 1);
    }
    if (digestp) *digestp=md5contextTo64(&context);
}

