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