#include "elm_defs.h"
#include "elm_globals.h"
#include "mime.h"
#include "sndparts.h"
#include "s_elm.h"
#include <assert.h>


/*******************/
/* Part is parts!! */
/*******************/

/*
 * The values in this array must correspond to
 * the BP_CONT_xxxx definitions.
 */
char *Mime_header_names[BP_NUM_CONT_HEADERS] = {
	"Content-Type:",		/* BP_CONT_TYPE		*/
	"Content-Transfer-Encoding:",	/* BP_CONT_ENCODING	*/
	"Content-Description:",		/* BP_CONT_DESCRIPTION	*/
	"Content-Disposition:",		/* BP_CONT_DISPOSITION	*/
	"Content-MD5:"			/* BP_CONT_MD5		*/
};


static char *scan_mimetypes P_((const char *, const char *, char *, int));


PUBLIC int encoding_is_reasonable(value)
const char *value;
{
    switch (mime_encoding_type(value)) {
    case ENCODING_NONE:
    case ENCODING_7BIT:
    case ENCODING_8BIT:
    case ENCODING_QUOTED:
    case ENCODING_BASE64:
    case ENCODING_UUENCODE:
	return TRUE;
    case ENCODING_BINARY:
    case ENCODING_EXPERIMENTAL:
	error1("Content encoding value \"%s\" is not reasonable.", value);
	return FALSE;
    case ENCODING_ILLEGAL:
    default:
	error1("Content encoding value \"%s\" is illegal.", value);
	return FALSE;
    }
    /*NOTREACHED*/
}



/*****************************************************************************
 *
 * Body part management routines.
 *
 ****************************************************************************/


#ifndef NDEBUG
PUBLIC void bodypart_integrity_check(part)
const SEND_BODYPART *part;
{
    assert(part != NULL);

    switch (part->part_type) {

    case BP_IS_DUMMY:
	assert(part->link_count == 0);
	assert(part->fname != NULL);
	assert(part->subparts == NULL);
	assert(part->boundary == NULL);
	break;

    case BP_IS_MSSGTEXT:
	assert(part->link_count == 0);
	assert(part->fname != NULL);
	assert(part->subparts != NULL);
	break;

    case BP_IS_MIMEPART:
	assert(part->link_count >= 0);
	assert(part->fname != NULL);
	assert(part->subparts == NULL);
	assert(part->boundary == NULL);
	break;

    case BP_IS_MULTIPART:
	assert(part->link_count >= 0);
	assert(part->fname == NULL);
	assert(part->subparts != NULL);
	break;

    default:
	assert((part->part_type, FALSE));
	break;

    }

}
#endif /* !NDEBUG */



/*
 * bodypart_new - Create a new (SEND_BODYPART).
 *
 * fname - Pathname of the file that contains this part.
 * part_type - The BP_IS_xxxx type of this part.
 *
 * This routine merely returns an initialized (SEND_BODYPART) for the
 * part.  The only verification is that the filename exists.  All
 * of the MIME headers are initialized to NULL, and none of the
 * part statistics are filled in.
 */
PUBLIC SEND_BODYPART *bodypart_new(fname, part_type)
const char *fname;
int part_type;
{
    SEND_BODYPART *part;
    int i;

    assert(fname != NULL);
    if (part_type != BP_IS_DUMMY && file_access(fname, R_OK) < 0)
	return (SEND_BODYPART *) NULL;

    part = (SEND_BODYPART *) safe_malloc(sizeof(SEND_BODYPART));

    part->part_type = part_type;
    part->link_count = 0;
    part->fname = safe_strdup(fname);
    part->emit_hdr_proc = NULL;
    part->emit_body_proc = NULL;

    for (i = 0 ; i < BP_NUM_CONT_HEADERS ; ++i)
	part->content_header[i] = NULL;

    part->total_chars = 0L;
    part->ascii_chars = 0L;
    part->binhi_chars = 0L;
    part->binlo_chars = 0L;
    part->max_linelen = 0;

    if (part_type == BP_IS_MSSGTEXT || part_type == BP_IS_MULTIPART)
	part->subparts = multipart_new((SEND_BODYPART *) NULL, 0L);
    else
	part->subparts = NULL;
    part->boundary = NULL;

    bodypart_integrity_check(part);
    return part;
}


/*
 * bodypart_destroy - Destroy a (SEND_BODYPART).
 *
 * This routine releases the space created by bodypart_new().
 */
PUBLIC void bodypart_destroy(part)
SEND_BODYPART *part;
{
    int i;
    bodypart_integrity_check(part);
    assert(part->link_count == 0);
    if (part->fname != NULL)
	free((malloc_t)part->fname);
    for (i = 0 ; i < BP_NUM_CONT_HEADERS ; ++i) {
	if (part->content_header[i] != NULL)
	    free((malloc_t)part->content_header[i]);
    }
    if (part->subparts != NULL)
	multipart_destroy(part->subparts);
    if (part->boundary != NULL)
	free((malloc_t)part->boundary);
    free((malloc_t)part);
}


/*
 * bodypart_set_content() - Set a particular MIME header.
 *
 * part - The (SEND_BODYPART) to modify.
 * sel - The BP_CONT_xxxx header to set.
 * value - The header value.  A NULL or empty string de-assigns the header.
 */
PUBLIC void bodypart_set_content(part, sel, value)
SEND_BODYPART *part;
int sel;
const char *value;
{
    assert(sel >= 0 && sel < BP_NUM_CONT_HEADERS);
    bodypart_integrity_check(part);

    if (part->content_header[sel] != NULL)
	free((malloc_t)part->content_header[sel]);
    part->content_header[sel] = (value && *value ? safe_strdup(value) : NULL);
}


/*
 * bodypart_get_content() - Retrieve a particular MIME header.
 *
 * part - The (SEND_BODYPART) to examine.
 * sel - The BP_CONT_xxxx selector of the header.
 *
 * Returns the header value, or an empty string if the header is not set.
 */
PUBLIC const char *bodypart_get_content(part, sel)
SEND_BODYPART *part;
int sel;
{
    const char *value;

    assert(sel >= 0 && sel < BP_NUM_CONT_HEADERS);
    bodypart_integrity_check(part);

    value = part->content_header[sel];
    return (value ? value : "");
}



/*
 * bodypart_guess_content() - Set a particular MIME header.
 *
 * part - The (SEND_BODYPART) to modify.
 * sel - The BP_CONT_xxxx header to set.
 *
 * This routine applies some simple heuristics to guess a
 * reasonable value for the header.
 */
PUBLIC void bodypart_guess_content(part, sel)
SEND_BODYPART *part;
int sel;
{
    char buf[SLEN], *value, *fname_tmp, *bp;
    int len;
    FILE *fp;
    float p;

    assert(sel >= 0 && sel < BP_NUM_CONT_HEADERS);
    bodypart_integrity_check(part);

    switch (sel) {

    case BP_CONT_TYPE:
	/* FOO - scan a user mimetypes file? */
	value = scan_mimetypes(system_mimetypes_file, part->fname,
		    buf, sizeof(buf));
	if (value == NULL) {
	    value = "application/octet-stream";
	    /* following heuristic assumes reasonable text won't be this long */
	    if (part->max_linelen < 200) {
	        p = (float)((part->binlo_chars+part->binhi_chars)/part->total_chars);
		if (part->ascii_chars == part->total_chars) {
		    value = "text/plain; charset=us-ascii";
		} else if (part->binlo_chars == 0) {
		    sprintf(buf, "text/plain; charset=%s", charset);
		    value = buf;
		} else if ((1.5*p)+0.75 <= 1.0) {
		    if (part->binhi_chars == 0)
		        value = "text/plain; charset=us-ascii";
		    else {
		        sprintf(buf, "text/plain; charset=%s", charset);
			value = buf;
		    }
		}
	    }
	}
	break;

    case BP_CONT_ENCODING:
       {
 	    /* pick based upon rough estimates of the encoded sizes */
 	    long qp_size = part->ascii_chars
 		+ (part->total_chars-part->ascii_chars) * (sizeof("=00")-1);
	    long b64_size = (part->total_chars * 4) / 3;
 	    value = (qp_size < b64_size ? "quoted-printable" : "base64");
       }
 	if (part->max_linelen <= RFC821_MAXLEN) {
            if (part->ascii_chars == part->total_chars)
		value = "7bit";
	    else if (part->binlo_chars == 0)
		value = "8bit";
	}
	break;

    case BP_CONT_DESCRIPTION:
	value = NULL;
	if ((fname_tmp = tempnam(temp_dir, "fil.")) != NULL) {
	    MIME_FILE_CMD(buf, part->fname, fname_tmp);
	    if (system_call(buf, 0) == 0) {
		if ((fp = fopen(fname_tmp, "r")) != NULL) {
		    if (fgets(buf, sizeof(buf), fp) != NULL) {
			bp = trim_trailing_spaces(buf);
			len = strlen(part->fname);
			if (strncmp(buf, part->fname, len) == 0) {
			    for (bp += len ; *bp == ':' || isspace(*bp) ; ++bp)
				;
			}
			value = bp;
		    }
		    (void) fclose(fp);
		}
	    }
	    (void) unlink(fname_tmp);
	    (void) free((malloc_t)fname_tmp);
	}
	break;

    case BP_CONT_DISPOSITION:
	sprintf(buf, "attachment; filename=\"%s\"", basename(part->fname));
	value = buf;
	break;

    case BP_CONT_MD5:
	value = NULL; /* FOO - not implemented yet */
	break;

    default:
	value = NULL; /* can't happen */
	break;

    }

    bodypart_set_content(part, sel, value);
}



static char *scan_mimetypes(fname_mimetypes, fname_part, retbuf, retbufsiz)
const char *fname_mimetypes;
const char *fname_part;
char *retbuf;
int retbufsiz;
{
    char *s;
    int len_fname, len_ext, rc;
    FILE *fp;

    if ((fp = fopen(fname_mimetypes, "r")) == NULL)
	return (char *) NULL;

    len_fname = strlen(fname_part);

    while (fgets(retbuf, retbufsiz, fp) != NULL) {

	/* trim newline, skip comments and blank lines */
	(void) trim_trailing_spaces(retbuf);
	if (retbuf[0] == '\0' || retbuf[0] == '#')
	    continue;

	/* locate end of extension field */
	if (isspace(retbuf[0]))
	    continue;	/* blah - illegal line */
	for (s = retbuf ; *s != '\0' && !isspace(*s) ; ++s)
	    ;

	/* locate start of content type field */
	for (*s++ = '\0' ; isspace(*s) ; ++s)
	    ;
	if (*s == '\0')
	    continue; /* blah - illegal line */

	if ((len_ext = strlen(retbuf)) < len_fname
		    && fname_part[len_fname-len_ext-1] == '.'
		    && streq(fname_part+len_fname-len_ext, retbuf)) {
	    (void) fclose(fp);
	    return s;
	}

    }

    (void) fclose(fp);
    return (char *)NULL;
}


/*****************************************************************************
 *
 * Multipart management routines.
 *
 ****************************************************************************/


#ifndef NDEBUG
PUBLIC void multipart_integrity_check(multi)
const SEND_MULTIPART *multi;
{
    const SEND_MULTIPART *m1;

    assert(multi != NULL);
    assert(multi->part == NULL);
    assert(multi->prev->next == multi);
    assert(multi->next->prev == multi);

    for (m1 = multi->next ; m1 != multi ; m1 = m1->next) {
	assert(m1->prev->next == m1);
	assert(m1->next->prev == m1);
	assert(m1->part != NULL);
	assert(m1->part->link_count > 0);
    }
}
#endif /* !NDEBUG */


PUBLIC SEND_MULTIPART *multipart_new(part, id)
SEND_BODYPART *part;
long id;
{
    SEND_MULTIPART *multi;

    multi = (SEND_MULTIPART *) safe_malloc(sizeof(SEND_MULTIPART));
    multi->id = id;
    multi->part = part;
    multi->prev = multi->next = multi;
    return multi;
}


PUBLIC void multipart_destroy(multi)
SEND_MULTIPART *multi;
{
    SEND_MULTIPART *mnext;

    multipart_integrity_check(multi);
    mnext = multi->next;

    while (multi = mnext, multi->part != NULL) {
	mnext = multi->next;
	assert(multi->part->link_count >= 0);
	if (--multi->part->link_count == 0)
	    bodypart_destroy(multi->part);
	free((malloc_t)multi);
    }

    free((malloc_t)multi);
}


/*ARGSUSED*/
PUBLIC SEND_MULTIPART *multipart_insertpart(multi, mp_curr, part, id)
SEND_MULTIPART *multi, *mp_curr;
SEND_BODYPART *part;
long id;
{
    SEND_MULTIPART *mp_new;

    multipart_integrity_check(multi);
    mp_new = multipart_new(part, id);

    /* insert "mp_new" before "mp_curr */
    mp_new->next = mp_curr;
    mp_new->prev = mp_curr->prev;
    mp_curr->prev->next = mp_new;
    mp_curr->prev = mp_new;

    ++part->link_count;
    multipart_integrity_check(multi);
    return mp_new;
}


/*ARGSUSED*/
PUBLIC SEND_MULTIPART *multipart_appendpart(multi, mp_curr, part, id)
SEND_MULTIPART *multi, *mp_curr;
SEND_BODYPART *part;
long id;
{
    SEND_MULTIPART *mp_new;

    multipart_integrity_check(multi);
    mp_new = multipart_new(part, id);

    /* append "mp_new" after "mp_curr" */
    mp_new->prev = mp_curr;
    mp_new->next = mp_curr->next;
    mp_curr->next->prev = mp_new;
    mp_curr->next = mp_new;

    ++part->link_count;
    multipart_integrity_check(multi);
    return mp_new;
}


/*ARGSUSED*/
PUBLIC SEND_BODYPART *multipart_deletepart(multi, mp_curr)
SEND_MULTIPART *multi, *mp_curr;
{
    SEND_BODYPART *part;

    multipart_integrity_check(multi);
    mp_curr->prev->next = mp_curr->next;
    mp_curr->next->prev = mp_curr->prev;
    part = mp_curr->part;
    free((malloc_t)mp_curr);
    --part->link_count;
    multipart_integrity_check(multi);
    return part;
}



PUBLIC SEND_MULTIPART *multipart_next(multi, mp_curr)
SEND_MULTIPART *multi, *mp_curr;
{
    mp_curr = (mp_curr == NULL ? multi->next : mp_curr->next);
    return (mp_curr->part == NULL ? (SEND_MULTIPART *) NULL : mp_curr);
}


PUBLIC SEND_MULTIPART *multipart_find(multi, id)
SEND_MULTIPART *multi;
long id;
{
    while (multi = multi->next, multi->part != NULL && multi->id != id)
	;
    return multi;
}

