static char rcsid[] = "@(#)$Id: mime_encode.c,v 1.36 2006/05/11 17:39:44 hurtta Exp $";

/******************************************************************************
 *  The Elm (ME+) Mail System  -  $Revision: 1.36 $   $State: Exp $
 *
 *  Modified by: Kari Hurtta <hurtta+elm@posti.FMI.FI> 
 *                           (was hurtta+elm@ozone.FMI.FI)
 *
 *  Initially written by: Michael Elkins <elkins@aero.org>, 1995
 *****************************************************************************/

#include <string.h>

#include "def_elm.h"
#include "s_elm.h"

#include <sys/time.h>

DEBUG_VAR(Debug,__FILE__,"mime");

extern short mime_count;

int update_encoding(top_encoding,encoding)
     int *top_encoding;
     int encoding;
{
  if (encoding == ENCODING_8BIT &&
      (*top_encoding) != ENCODING_BINARY)
    (*top_encoding) = ENCODING_8BIT;
  if (encoding == ENCODING_BINARY)
    (*top_encoding) = ENCODING_BINARY;
  
  return (*top_encoding);
}

char * mime_generate_boundary (str, size)
     char *str;
     int size;
{
  time_t t = time (NULL);
	
  elm_sfprintf (str, size,
		FRM("ELM%d-%d-%d_"), t, getpid(), mime_count++);
  return str;
}

void add_parameter(opts,name,value,size,quoted) 
     char *opts, *name, *value;
     int size, quoted;
{
    int len = strlen(opts);
    int ln = strlen(name);
    char * ptr = opts + len;
    int need_quotation = 0;
    
    /* Following characters require quotation: ( ) < > @ , ; : \ " / [ ] ? =
     * Also whitespace requires quotation. See Mime Draft Standard
     */
    if (!quoted && (NULL != strpbrk(value,"()<>@,;:\\\"/[]?= \t")
		    || value[0] == '\0'))
	need_quotation = 1;
    
    if (len + strlen(value) + ln + 4 + 2 * need_quotation > size)
	return; /* Don't fit anyway */
    
    if (ptr != opts) {
	*ptr++ = ';'; *ptr++ = ' ';
    }
    if (need_quotation) 
	elm_sfprintf(ptr,size - (ptr-opts),FRM("%s=%Q"),
		 name,value);
    else
	elm_sfprintf(ptr,size - (ptr-opts),FRM("%s=%s"),
		     name,value);
    return;
}

int attach_message(part,mailer,mime_info,X)
     mime_t *part; 
     out_state_t *mailer;
     mime_send_t *mime_info;
     struct mime_send_part * X;
{
    int err;
    FILE *srcfp;
    int is_text;


    if (part->magic != MIME_magic)
	mime_panic(__FILE__,__LINE__,"attach_message",
		   "Bad magic number");

    if ((err = can_open(part->pathname0,"r")) != 0) {
	lib_error(CATGETS(elm_msg_cat, ElmSet, ElmFailErrnoS,
			  "Failed: %S: %.40s"),
		  part->dispname,error_description(err));
	return 0;
    }

    srcfp = fopen (part->pathname0, "r");
    if (!srcfp) {
	lib_error(CATGETS(elm_msg_cat, ElmSet, ElmErrorOpeningNameS,
			  "Error opening %S!"), 
		  part->dispname);
    }
    
    /* 1 if is text type (true)
     * 0 if not text type
     * -1 if can't be encoded (ie structured) Message/ or Multpart/
     */

    /* 7bit and 8bit are only allowed for line orienteed types */
    if (part->encoding == ENCODING_NONE || 
	part->encoding == ENCODING_7BIT || 
	part->encoding == ENCODING_8BIT) {
	DPRINT(Debug,11,(&Debug,
			 "attach_message: textual encoding (%s)\n",
			 ENCODING(part->encoding)));
	is_text = 1;
    } else
	is_text = give_text_type_code(part->TYPE);


    if (is_text < 0 && (part->encoding == ENCODING_QUOTED ||
			part->encoding == ENCODING_BASE64)) {
	lib_error(CATGETS(elm_msg_cat, ElmSet,ElmDontEncoding,
			  "Content-Type don't allow encoding -- ignoring this part."));
	return 0;
    }

    (void) update_encoding(&(mime_info->encoding_top),part->encoding);
	

    X -> TYPE    = part ->TYPE;

    if (part->TYPE_opts)
	X -> TYPE_opts_part = copy_mime_param(part->TYPE_opts);
    X ->disposition   = part -> disposition;
    if (part->DISPOSITION_opts)
	X -> DISPOSITION_opts = copy_mime_param(part->DISPOSITION_opts);
    if (part->description)
	X -> description      = dup_string(part->description);
    X -> encoding_part = part->encoding;

    X->start_loc = out_state_ftell(mailer);
    (void) write_encoded (srcfp, mailer, part->encoding, is_text,
			  mime_info);
    X->end_loc = out_state_ftell(mailer);

    fclose (srcfp);

    /* !! */
    if (ferror(out_state_FILE(mailer))) {
	lib_error(CATGETS(elm_msg_cat, ElmSet, 
			  ElmWriteFailedCopy,
			  "Write failed to temp file in copy"));
	return 0;   /* Just indicate failure */
    }

    DPRINT(Debug,4, 
	   (&Debug, 
	    "Preparing mail for sending: part %d (attach) is %d bytes (%s)\n",
	    X-mime_info->top_parts,
	    X->end_loc-X->start_loc,
	    ENCODING(X->encoding_part)));
    
    return 1; /* ok */
}

void
base64_encode (srcfp, mailer, istext, mime_info)
     FILE *srcfp;
     out_state_t *mailer;
     int istext;
     mime_send_t * mime_info;
{
  int c1, c2, c3 = 0;
  int ch1, ch2, ch3, ch4;
  int chars = 0;
  int last_c = 0;

  for (;;) {
      c1 = fgetc (srcfp);
      if (c1 == -1)
	  break;
      if (istext && last_c != '\r' && c1 == '\n') {
	  /* In text end of line must be coded as CR LF */
	  c1 = '\r';
	  c2 = '\n';
      }
      else
	  c2 = fgetc (srcfp);

      if (istext && c1 != '\r' && c2 == '\n') {
	  /* In text end of line must be coded as CR LF */
	  c2 = '\r';
	  c3 = '\n';
      }
      else if (c2 != -1)
	  c3 = fgetc (srcfp);
 
      if (istext && c2 != '\r' && c3 == '\n') {
	  /* In text end of line must be coded as CR LF */
	  ungetc(c3,srcfp);
	  c3 = '\r';
      }
      
      last_c = c3;
 
      ch1 = c1 >> 2;
      ch1 = to64(ch1);
      
      if (c2 != -1) {
	  ch2 = ((c1 & 0x3) << 4) | (c2 >> 4);
      ch2 = to64(ch2);
      
      if (c3 != -1) {
	  ch3 = ((c2 & 0xf) << 2) | (c3 >> 6);
	  ch3 = to64(ch3);
	  ch4 = c3 & 0x3f;
	  ch4 = to64(ch4);
      }
      else {
	  ch3 = (c2 & 0xf) << 2;
	  ch3 = to64(ch3);
	  ch4 = '=';
      }
      }
      else {
	  ch2 = (c1 & 0x3) << 4;
	  ch2 = to64(ch2);
	  ch3 = '=';
	  ch4 = '=';
      }
      
      state_putc (ch1, mailer);
      state_putc (ch2, mailer);
      state_putc (ch3, mailer);
      state_putc (ch4, mailer);
      chars += 4;
      
      if (chars >= 76) {
	  print_EOLN(mailer,mime_info->encoding_top);
	  chars = 0;
      }	
  }
  print_EOLN(mailer,mime_info->encoding_top);
  return;
}

void line_quoted_printable_encode (input,mailer,len,istext,mime_info) 
     char *input;
     out_state_t *mailer;
     int len; /* length of data -- be binary clean */
     int istext; /* if binary (not text) also CRLF need to be encoded */
     mime_send_t *mime_info;
{
    int chars = 0;
    char buffer[STRING];
    unsigned char c1, c2, c3;
    unsigned char lastchar = 0;
    unsigned char lastchar2 = 0;
  
    DPRINT(Debug,9, (&Debug,  
		     "line_quoted_printable_encode: len=%d, istext=%d\n",
		     len,istext));

    if (istext) {  
	if (len > 0 && input[len-1] == '\n') {
	    lastchar = input[len-1];
	    input[len-1] = '\0';
	    len--;
	    if (len > 0 && input[len-1] == '\r')  {   /* Was CR LF */
		lastchar2 = input[len-1];
		input[len-1] = '\0';
		len--;
	    } else if (mime_info->encoding_top == ENCODING_BINARY)
		lastchar2 = '\r';     /* Simulate it */
	}
    }
    
    /* I don't test agaist macros bacause these encodings are recommended
     * according MIME Draft Standard anyway and MIME encodings are
     * reversible.
     *
     * DONT_ESCAPE_MESSAGES refers '>' escaping -- not reversible
     * MIME encoding -- '>' escaping was not reversible -- this is.
     *
     * We also want do these encodings when sending (copy == 0)
     * not only when copying to another folder 
     *            -- K E H <hurtta@dionysos.FMI.FI>                     */

    while (len > 0)  {
	/* Assume that buffer don't have newlines (or they have binary data) ...
	   this routine encodes one line */
	c1 = (unsigned char) *input++;
	len--;

	if (c1 == '=') {
	    if (chars > 72) {	    
		buffer[chars++] = '=';
		buffer[chars++] = '\0';
		chars = 0;
		
		state_puts (buffer, mailer);
		print_EOLN(mailer,mime_info->encoding_top);
	    }
	    buffer[chars++] = '=';
	    buffer[chars++] = '3';
	    buffer[chars++] = 'D';
	}    
	/*  printable characters -- EXCEPT:   Encode "From " in beginning */
	else if (((c1 > 31 && c1 < 127) && 
		  ((c1 != ' ' || chars != 4 || 
		    strncmp(buffer,"From",4) != 0)
		   /* Encode "." if only in line alone */
		   && (c1 != '.' || chars != 0 || len > 0)
		   /* Last space must encode also */
		   && (c1 != ' ' || len > 0))) 
		 ||
		 (c1 == 9 && len > 0 && istext)) { 
	    /* Don't make sense wrap before last character, when last character
	     * is not encoded -- wrapping and character use equal number of columns.
	     * But left space for '=\n' if we will print it in end of function
	     * instead of '\n'. */
	    if (chars > 74 && (len > 0 || !lastchar)) {
		buffer[chars++] = '=';
		buffer[chars++] = '\0';
		chars = 0;
		
		state_puts (buffer, mailer);
		print_EOLN(mailer,mime_info->encoding_top);
	    }
	    buffer[chars++] = c1;
	}
	else {
	    if (chars > 72) {
		buffer[chars++] = '=';
		buffer[chars++] = '\0';
		chars = 0;
		
		state_puts (buffer, mailer);
		print_EOLN(mailer,mime_info->encoding_top);
	    }
	    c2 = (c1 >> 4) & 0xf;
	    c3 = c1 & 0xf;
	    buffer[chars++] = '=';
	    buffer[chars++] = hexchars[c2];
	    buffer[chars++] = hexchars[c3];
	}
    }
    
    /* Make sure to flush the buffer.  */
    if (chars > 0) {
	buffer[chars] = '\0';
	state_puts (buffer, mailer);
    }
    if (lastchar2)
	state_putc (lastchar2, mailer);
    if (lastchar) {
	state_putc(lastchar,mailer);
    } else { /* If input line don't terminate NL then print shoft wrap to end
	      * instead of hard NL */
	state_puts ("=", mailer);
	print_EOLN(mailer,mime_info->encoding_top);
    }
}

void quoted_printable_encode (srcfp, mailer, istext, mime_info)
     FILE *srcfp;
     out_state_t *mailer;
     int istext;  /* if binary (not text) also CRLF need to be encoded */
     mime_send_t *mime_info;
{
  char buffer[VERY_LONG_STRING];
  int len;

  DPRINT(Debug,10,(&Debug, 
		   "quoted_printable_encode: istext=%d\n",
		   istext));

  if (istext) {
      /* mail_gets is in ../lib -- it handles also NUL characters */
      while ((len = mail_gets (buffer, sizeof buffer, srcfp)) > 0)
	  line_quoted_printable_encode (buffer, mailer, len, istext,
					mime_info);
  } else {
      /* mail_gets may add LF to end of file if file don't end with LF
       * So it is not good for binary data */
      while ((len = fread(buffer, 1, sizeof(buffer), srcfp)) > 0)
	  line_quoted_printable_encode (buffer, mailer, len, istext,
					mime_info);
  }

  return;
}

void write_encoded (srcfp, mailer, encoding, is_text, mime_info)
     FILE *srcfp;
     out_state_t *mailer;
     int encoding, is_text;
     mime_send_t *mime_info;
{
    char buffer[VERY_LONG_STRING];
    int line_len;
    
    DPRINT(Debug,12,(&Debug, 
		     "write_encoded: encoding=%d, is_text=%d\n",
		     encoding,is_text));

    if (encoding == ENCODING_BASE64)
	base64_encode (srcfp, mailer, is_text, mime_info);
    else if (encoding == ENCODING_QUOTED)
	quoted_printable_encode (srcfp, mailer, is_text, mime_info);
    else if (mime_info-> encoding_top == ENCODING_BINARY && is_text > 0) {
	/* FIXME: Should condition
	           mime_info-> encoding_top == ENCODING_BINARY
		   removed ??
	*/
	/* It is better perhaps use canonical eol (CRLF) when mail have
	 * content transfer encoding BINARY somewhere (see notes about 
	 * BINARYMIME)
	 */
	while ((line_len = mail_gets(buffer, sizeof(buffer)-1, srcfp)) > 0) {

	    /* THIS assumes that
               mailer->EOLN_is_CRLF is set
	    */
            state_convert_EOLN(buffer,&line_len,sizeof buffer,mailer);

	    state_put(buffer, line_len, mailer);
	}
    } else {
	while (1) {
	    if ((line_len = fread(buffer, 1,sizeof(buffer)-1, srcfp)) <= 0)
		break;     

	    state_put(buffer, line_len, mailer);
	}
    }
    return;
}

void mime_write_part_headers(mailer,ptr,part)
     out_state_t *mailer;
     mime_send_t *ptr;
     struct mime_send_part *part;
{
    /* 1) Content-Transfer-Encoding */
    if (part->encoding_part < ENCODING_EXPERIMENTAL &&
	part->encoding_part > ENCODING_NONE) {
	state_printf(mailer,FRM("Content-Transfer-Encoding: %s"),
		     ENCODING(part->encoding_part));
	print_EOLN(mailer,ptr->encoding_top);
    } else if (part->encoding_part == ENCODING_EXPERIMENTAL &&
	       part->encoding_part_text) {
	state_printf(mailer,FRM("Content-Transfer-Encoding: %s"),
		     part->encoding_part_text);
	print_EOLN(mailer,ptr->encoding_top);
    }

    /* 2) Content-Type */
    state_printf(mailer,FRM("Content-Type: %s/%s"),
		 get_major_type_name(part->TYPE),
		 get_subtype_name(part->TYPE));
    if (part->TYPE_opts_part) {     
	char **X;
	char **V = encode_mime_params_v(part->TYPE_opts_part);
	state_putc(';',mailer);
	if (V && V[0] &&
	    strlen(get_major_type_name(part->TYPE)) + 
	    strlen(get_subtype_name(part->TYPE)) +
	    strlen(V[0]) > 70) {
	    print_EOLN(mailer,ptr->encoding_top);
	}
	for (X = V; X && *X; X++) {
	    state_putc(' ',mailer);
	    state_puts(*X,mailer);
	    if (*(X+1)) {
		state_putc(';',mailer);
		print_EOLN(mailer,ptr->encoding_top);
	    }
	    free(*X);  *X = NULL;
	}
	free(V);
    }
    print_EOLN(mailer,ptr->encoding_top);

    /* 3) Content-Disposition */
    if (part->disposition != DISP_INLINE ||
	part->DISPOSITION_opts) {
	state_printf(mailer,FRM("Content-Disposition: %s"),
		     DISPOSITION(part->disposition));
	if (part->DISPOSITION_opts) {      
	    char **X;
	    char **V = encode_mime_params_v(part->DISPOSITION_opts);
	    state_putc(';',mailer);
	    if (V && V[0] &&
		strlen(DISPOSITION(part->disposition)) +
		strlen(V[0]) > 70) {
		print_EOLN(mailer,ptr->encoding_top);
	    }

	    for (X = V; X && *X; X++) {
		state_putc(' ',mailer);
		state_puts(*X,mailer);
		if (*(X+1)) {
		    state_putc(';',mailer);
		    print_EOLN(mailer,ptr->encoding_top);
		}
		free(*X);  *X = NULL;
	    }
	    free(V);
	    
	}
	print_EOLN(mailer,ptr->encoding_top);
    }

    /* 4) Content-Description */
    if (part->description) {
	write_string_header(mailer,"Content-Description",
			    part->description,ptr->encoding_top,
			    ptr->encode_hdr,ptr->hdr_charset);
    }
}

void mime_write_top_headers(mailer, ptr)
     out_state_t *mailer;
     mime_send_t *ptr;
{
    state_puts(MIME_HEADER, mailer);
    print_EOLN(mailer, ptr->encoding_top);  

    if (ptr->msg_is_multipart) {
	char **X;
	char **V = encode_mime_params_v(ptr->TYPE_opts_top);

	state_puts("Content-Type: multipart/mixed;",mailer);

	for (X = V; X && *X; X++) {
	    state_putc(' ',mailer);
	    state_puts(*X,mailer);
	    if (*(X+1)) {
		state_putc(';',mailer);
		print_EOLN(mailer,ptr->encoding_top);
	    }
	    free(*X);  *X = NULL;
	}
	free(V);

	print_EOLN(mailer, ptr->encoding_top);  
	state_printf(mailer,FRM("Content-Transfer-Encoding: %s"),
		     ENCODING(ptr->encoding_top));
	print_EOLN(mailer, ptr->encoding_top);  
    } else if (1 == ptr->top_parts_count) {
	if (MIME_TYPE_TEXT == get_major_type_code(ptr->top_parts[0].TYPE) &&
	    0 == istrcmp("Plain",get_subtype_name(ptr->top_parts[0].TYPE)) &&
	    ptr->top_parts[0].result_charset &&
	    ptr->top_parts[0].result_charset->MIME_name &&
	    0 == istrcmp("US-ASCII",
			 ptr->top_parts[0].result_charset->MIME_name) &&
	    (ENCODING_NONE == ptr->top_parts[0].encoding_part ||
	     ENCODING_7BIT == ptr->top_parts[0].encoding_part) &&
	    !send_mime_plain) {
	    DPRINT(Debug,2,(&Debug, 
			    "mime_write_header: send_mime_plain==%d: Omitting mime headers.\n",
			    send_mime_plain));
	    return;
	} else {
	    mime_write_part_headers(mailer,ptr,&(ptr->top_parts[0]));
	} 
    } else {
	DPRINT(Debug,2,(&Debug, 
			"mime_write_header: msg_is_multipart=%d, top_parts_count=%d: Odd values?\n",
		   ptr->msg_is_multipart,ptr->top_parts_count));
    }
} 

/*
 * Local Variables:
 *  mode:c
 *  c-basic-offset:4
 *  buffer-file-coding-system: iso-8859-1
 * End:
 */


syntax highlighted by Code2HTML, v. 0.9.1