# HG changeset patch # User Mychaela Falconia # Date 1683353643 0 # Node ID 7e0d08176f3280c3722267748ef49e6d522f916e f-demime starting code diff -r 000000000000 -r 7e0d08176f32 f-demime/attach_out.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/attach_out.c Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,57 @@ +/* + * This module contains code for creating (writing) secondary output files + * intended for storing attached base64 blobs. + */ + +#include +#include +#include +#include +#include +#include +#include "defs.h" + +extern char *att_filename_buf, *att_filename_tail; +extern void (*dec_outf)(); + +static unsigned att_count; +static FILE *att_outf; +static int giveup_flag; + +static void +output_func(ch) +{ + putc(ch, att_outf); +} + +init_attach_out() +{ + int fd; + + if (giveup_flag) + return(-1); + for (;;) { + if (att_count >= 10000) { + giveup_flag = 1; + return(-1); + } + sprintf(att_filename_tail, "%04u", att_count++); + fd = open(att_filename_buf, O_WRONLY|O_CREAT|O_EXCL, 0644); + if (fd >= 0) + break; + } + att_outf = fdopen(fd, "w"); + if (!att_outf) { + perror("fdopen"); + close(fd); + return(-1); + } + dec_outf = output_func; + return(0); +} + +void +attach_out_finish() +{ + fclose(att_outf); +} diff -r 000000000000 -r 7e0d08176f32 f-demime/b2q_in.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/b2q_in.c Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,45 @@ +/* + * This module implements the input side of base64-to-QP conversion. + */ + +#include +#include +#include +#include +#include "defs.h" + +extern void (*dec_outf)(); +extern FILE *tempfile; + +static int cr_state; + +static void +output_func(ch) +{ + if (cr_state) { + cr_state = 0; + if (ch == '\n') { + putc('\n', tempfile); + return; + } else + putc('\r', tempfile); + } + if (ch == '\r') + cr_state = 1; + else + putc(ch, tempfile); +} + +void +b2q_conv_init() +{ + dec_outf = output_func; + cr_state = 0; +} + +void +b2q_conv_finish() +{ + if (cr_state) + putc('\r', tempfile); +} diff -r 000000000000 -r 7e0d08176f32 f-demime/b2q_out.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/b2q_out.c Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,90 @@ +/* + * This module implements the output side of base64-to-QP conversion. + */ + +#include +#include +#include +#include +#include "defs.h" + +extern FILE *tempfile; + +static char linebuf[QP_MAX_LINE]; +static unsigned line_len, trailing_newline; + +static void +add_plain_char(ch) +{ + linebuf[line_len++] = ch; + linebuf[line_len] = '\0'; + if (!strcmp(linebuf, "From ")) { + strcpy(linebuf, "=46rom "); + line_len = 7; + } else if (!strcmp(linebuf, "--")) { + strcpy(linebuf, "=2D-"); + line_len = 4; + } else if (!strcmp(linebuf, ".")) { + strcpy(linebuf, "=2E"); + line_len = 3; + } +} + +static void +add_octet_char(ch) +{ + sprintf(linebuf + line_len, "=%02X", ch); + line_len += 3; +} + +static void +process_newline() +{ + if (line_len) { + if (linebuf[line_len-1] == ' ') + strcpy(linebuf + line_len - 1, "=20"); + fputs(linebuf, stdout); + line_len = 0; + } + putchar('\n'); +} + +static void +process_char(ch) +{ + if (ch == '\n') { + process_newline(); + trailing_newline++; + if (trailing_newline > 2) + trailing_newline = 2; + return; + } + if (line_len >= QP_MAX_LINE - 2) { + printf("%s=\n", linebuf); + line_len = 0; + } + if (ch == '=') + add_octet_char(ch); + else if (ch >= ' ' && ch <= '~') + add_plain_char(ch); + else + add_octet_char(ch); + trailing_newline = 0; +} + +void +b2q_emit_output() +{ + int c; + + rewind(tempfile); + line_len = 0; + trailing_newline = 1; + while ((c = getc(tempfile)) != EOF) + process_char(c); + fclose(tempfile); + while (trailing_newline < 2) { + process_newline(); + trailing_newline++; + } +} diff -r 000000000000 -r 7e0d08176f32 f-demime/base64.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/base64.c Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,112 @@ +/* + * This module implements base64 decoding. + */ + +#include +#include +#include +#include +#include +#include "defs.h" + +extern void (*dec_outf)(); + +static unsigned b64_accum, b64_eq_flag; +static int b64_state, last_partial; +int b64_err_flag, b64_nonempty; + +void +base64_dec_init() +{ + b64_accum = 0; + b64_eq_flag = 0; + b64_state = 0; + b64_err_flag = 0; + b64_nonempty = 0; + last_partial = 0; +} + +static void +process_unit() +{ + if (last_partial) + b64_err_flag = 1; + switch (b64_eq_flag) { + case 0: + dec_outf(b64_accum >> 16); + dec_outf((b64_accum >> 8) & 0xFF); + dec_outf(b64_accum & 0xFF); + last_partial = 0; + break; + case 1: + dec_outf(b64_accum >> 16); + dec_outf((b64_accum >> 8) & 0xFF); + last_partial = 1; + break; + case 3: + dec_outf(b64_accum >> 16); + last_partial = 1; + break; + default: + b64_err_flag = 1; + } + b64_accum = 0; + b64_eq_flag = 0; + b64_state = 0; +} + +static void +base64_input_char(ch) +{ + int code; + + b64_nonempty = 1; + if (ch >= 'A' && ch <= 'Z') + code = ch - 'A'; + else if (ch >= 'a' && ch <= 'z') + code = ch - 'a' + 26; + else if (ch >= '0' && ch <= '9') + code = ch - '0' + 52; + else switch (ch) { + case '+': + code = 62; + break; + case '/': + code = 63; + break; + case '=': + code = 64; + break; + default: + b64_err_flag = 1; + return; + } + b64_accum <<= 6; + b64_accum |= (code & 63); + b64_eq_flag <<= 1; + b64_eq_flag |= (code >> 6); + b64_state++; + if (b64_state >= 4) + process_unit(); +} + +void +base64_input_line(line) + char *line; +{ + char *cp; + int c; + + for (cp = line; *cp; ) { + c = *cp++; + if (!isspace(c)) + base64_input_char(c); + } +} + +void +base64_dec_finish() +{ + if (b64_state) + b64_err_flag = 1; +} diff -r 000000000000 -r 7e0d08176f32 f-demime/defs.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/defs.h Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,31 @@ +/* + * This header file holds miscellaneous definitions for f-demime, + * including various limits. + */ + +enum msg_state { + MSG_STATE_UNDEF, /* before From line */ + MSG_STATE_HEADER, /* message or body part header */ + MSG_STATE_BODY_PASS, /* body pass-through state */ + MSG_STATE_PTEXT_B64, /* text/plain decoding from base64 */ + MSG_STATE_PTEXT_QP, /* text/plain decoding from quoted-printable */ + MSG_STATE_BLOB_B64, /* blob extraction from base64 */ + MSG_STATE_B64_TO_QP, /* base64 to quoted-printable conversion */ +}; + +enum msg_hdr_state { + HDR_STATE_BEGIN, /* beginning of entity header */ + HDR_STATE_GOTSOME, /* got some header line(s), regular */ + HDR_STATE_CONT_TYPE, /* after Content-Type: header */ + HDR_STATE_CONT_TE, /* after Content-Transfer-Encoding: header */ + HDR_STATE_ERROR /* errored state */ +}; + +#define LINE_BUF_SIZE 1024 +#define HDR_BUF_SIZE 2048 + +#define MAX_MP_BOUNDARY 70 /* not counting the hyphens */ +#define MAX_MP_NESTING 8 + +#define OUTPUT_LINE_MAX 320 +#define QP_MAX_LINE 76 diff -r 000000000000 -r 7e0d08176f32 f-demime/finish.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/finish.c Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,74 @@ +/* + * This module implements the handling at the end of entity body input. + */ + +#include +#include +#include +#include +#include "defs.h" + +extern enum msg_state msg_state; +extern char cont_type_buf[HDR_BUF_SIZE], cont_te_buf[HDR_BUF_SIZE]; +extern int got_cont_type, got_cont_te; +extern int b64_err_flag, b64_nonempty, qpdec_err_flag; + +static void +check_b64_err() +{ + if (b64_err_flag) + puts("X-Fdemime-Error: bad base64 data"); +} + +static void +check_qpdec_err() +{ + if (qpdec_err_flag) + puts("X-Fdemime-Error: bad quoted-printable data"); +} + +void +finish_msg_body() +{ + switch (msg_state) { + case MSG_STATE_HEADER: + if (got_cont_type) + fputs(cont_type_buf, stdout); + if (got_cont_te) + fputs(cont_te_buf, stdout); + return; + case MSG_STATE_PTEXT_B64: + base64_dec_finish(); + check_b64_err(); + ptext_conv_finish(); + ptext_mark_transform("decode-base64"); + putchar('\n'); + ptext_emit_output(); + return; + case MSG_STATE_PTEXT_QP: + check_qpdec_err(); + ptext_conv_finish(); + ptext_mark_transform("decode-qp"); + putchar('\n'); + ptext_emit_output(); + return; + case MSG_STATE_BLOB_B64: + base64_dec_finish(); + check_b64_err(); + putchar('\n'); + attach_out_finish(); + if (b64_nonempty) + puts("[base64 blob stripped]"); + putchar('\n'); + return; + case MSG_STATE_B64_TO_QP: + base64_dec_finish(); + check_b64_err(); + b2q_conv_finish(); + puts("X-Fdemime-Transform: base64-to-qp"); + puts("Content-Transfer-Encoding: quoted-printable"); + putchar('\n'); + b2q_emit_output(); + return; + } +} diff -r 000000000000 -r 7e0d08176f32 f-demime/header.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/header.c Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,145 @@ +/* + * This module implements initial processing (pass-through and collection) + * of message and body part headers. + */ + +#include +#include +#include +#include +#include +#include "defs.h" + +extern enum msg_hdr_state hdr_state; +extern char cont_type_buf[HDR_BUF_SIZE], cont_te_buf[HDR_BUF_SIZE]; +extern int got_cont_type, got_cont_te; + +static void +error(line, msg) + char *line, *msg; +{ + if (got_cont_type) { + fputs(cont_type_buf, stdout); + got_cont_type = 0; + } + if (got_cont_te) { + fputs(cont_te_buf, stdout); + got_cont_te = 0; + } + printf("X-Fdemime-Error: %s\n", msg); + puts(line); + hdr_state = HDR_STATE_ERROR; +} + +static void +error_ct_cont(hdr_name) + char *hdr_name; +{ + printf("X-Fdemime-Error: %s header is too long\n", hdr_name); +} + +static void +cont_line(line) + char *line; +{ + unsigned prev_len; + + switch (hdr_state) { + case HDR_STATE_BEGIN: + error(line, "continuation line at the beginning of header"); + return; + case HDR_STATE_GOTSOME: + puts(line); + return; + case HDR_STATE_CONT_TYPE: + prev_len = strlen(cont_type_buf); + if (prev_len + strlen(line) + 2 > HDR_BUF_SIZE) { + error_ct_cont("Content-Type"); + fputs(cont_type_buf, stdout); + puts(line); + hdr_state = HDR_STATE_ERROR; + got_cont_type = 0; + return; + } + sprintf(cont_type_buf + prev_len, "%s\n", line); + return; + case HDR_STATE_CONT_TE: + prev_len = strlen(cont_te_buf); + if (prev_len + strlen(line) + 2 > HDR_BUF_SIZE) { + error_ct_cont("Content-Transfer-Encoding"); + fputs(cont_te_buf, stdout); + puts(line); + hdr_state = HDR_STATE_ERROR; + got_cont_te = 0; + return; + } + sprintf(cont_te_buf + prev_len, "%s\n", line); + return; + default: + fprintf(stderr, + "f-demime internal error: bad state in cont_line()\n"); + abort(); + } +} + +void +header_input_line(line) + char *line; +{ + char *cp, savech; + enum msg_hdr_state newhdr; + + if (!line[0]) { + process_header_end(); + return; + } + if (hdr_state == HDR_STATE_ERROR) { + puts(line); + return; + } + if (line[0] == ' ' || line[0] == '\t') { + cont_line(line); + return; + } + cp = index(line, ':'); + if (!cp) { + error(line, "header line has no colon"); + return; + } + if (cp == line) { + error(line, "null header field name"); + return; + } + while (cp[-1] == ' ' || cp[-1] == '\t') + cp--; + savech = *cp; + *cp = '\0'; + if (!strcasecmp(line, "Content-Type")) + newhdr = HDR_STATE_CONT_TYPE; + else if (!strcasecmp(line, "Content-Transfer-Encoding")) + newhdr = HDR_STATE_CONT_TE; + else + newhdr = HDR_STATE_GOTSOME; + *cp = savech; + switch (newhdr) { + case HDR_STATE_CONT_TYPE: + if (got_cont_type) { + error(line, "duplicate Content-Type"); + return; + } + sprintf(cont_type_buf, "%s\n", line); + got_cont_type = 1; + break; + case HDR_STATE_CONT_TE: + if (got_cont_te) { + error(line, "duplicate Content-Transfer-Encoding"); + return; + } + sprintf(cont_te_buf, "%s\n", line); + got_cont_te = 1; + break; + default: + puts(line); + } + hdr_state = newhdr; +} diff -r 000000000000 -r 7e0d08176f32 f-demime/header_end.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/header_end.c Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,288 @@ +/* + * This module implements final processing of message and body part headers, + * deciding what to do at the end of each header. + */ + +#include +#include +#include +#include +#include +#include "defs.h" + +extern enum msg_state msg_state; +extern enum msg_hdr_state hdr_state; +extern unsigned mp_nest_level; +extern char mp_boundaries[MAX_MP_NESTING][MAX_MP_BOUNDARY+1]; +extern int mp_is_digest[MAX_MP_NESTING]; +extern char cont_type_buf[HDR_BUF_SIZE], cont_te_buf[HDR_BUF_SIZE]; +extern int got_cont_type, got_cont_te; + +static int +is_valid_tchar(ch) +{ + if (ch < '!' || ch > '~') + return(0); + switch (ch) { + case '(': + case ')': + case '<': + case '>': + case '@': + case ',': + case ';': + case ':': + case '\\': + case '"': + case '/': + case '[': + case ']': + case '?': + case '=': + return(0); + default: + return(1); + } +} + +static int +gettoken(cpp, tokbuf) + char **cpp, *tokbuf; +{ + register char *cp, *dp; + register int i; + + /* skip initial white space and comments */ + for (cp = *cpp; ; ) { + if (isspace(*cp)) { + cp++; + continue; + } + if (*cp != '(') + break; + for (i = 0; ; ) { + if (!*cp) + break; + if (*cp == '\\') + cp++; + else if (*cp == '(') + i++; + else if (*cp == ')') + i--; + if (*cp) + cp++; + if (!i) + break; + } + } + if (!*cp) { + *cpp = cp; + return(0); + } + if (*cp == '/' || *cp == ';' || *cp == '=') { + i = *cp++; + *cpp = cp; + return(i); + } + if (*cp == '"') { + cp++; + for (dp = tokbuf; *cp; ) { + if (*cp == '"') { + cp++; + break; + } + if (cp[0] == '\\' && cp[1]) + cp++; + *dp++ = *cp++; + } + *dp = '\0'; + *cpp = cp; + return(2); + } + if (!is_valid_tchar(*cp)) { + *cpp = cp; + return(-1); + } + for (dp = tokbuf; is_valid_tchar(*cp); ) + *dp++ = *cp++; + *dp = '\0'; + *cpp = cp; + return(1); +} + +static int +parse_content_type(type, subtype, charset, boundary) + char *type, *subtype, *charset, *boundary; +{ + char *ctstr = cont_type_buf; + char tokbuf[HDR_BUF_SIZE], attr[HDR_BUF_SIZE]; + int rc; + + if (gettoken(&ctstr, type) != 1) + return(-1); + if (gettoken(&ctstr, tokbuf) != '/') + return(-1); + if (gettoken(&ctstr, subtype) != 1) + return(-1); + charset[0] = '\0'; + boundary[0] = '\0'; + for (;;) { + rc = gettoken(&ctstr, tokbuf); + if (!rc) + return(0); + if (rc != ';') + return(-1); + if (gettoken(&ctstr, attr) != 1) + return(-1); + if (gettoken(&ctstr, tokbuf) != '=') + return(-1); + rc = gettoken(&ctstr, tokbuf); + if (rc != 1 && rc != 2) + return(-1); + if (!strcasecmp(attr, "charset")) + strcpy(charset, tokbuf); + else if (!strcasecmp(attr, "boundary")) + strcpy(boundary, tokbuf); + } +} + +static int +parse_content_te(ctetoken) + char *ctetoken; +{ + char *ctestr = cont_te_buf; + char tokbuf[HDR_BUF_SIZE]; + + if (gettoken(&ctestr, ctetoken) != 1) + return(-1); + if (gettoken(&ctestr, tokbuf) == 0) + return(0); + else + return(-1); +} + +static void +handle_multipart(cont_subtype, boundary_attr) + char *cont_subtype, *boundary_attr; +{ + if (!boundary_attr[0]) { + puts("X-Fdemime-Error: multipart without boundary attr"); + putchar('\n'); + msg_state = MSG_STATE_BODY_PASS; + return; + } + if (index(boundary_attr, '\n')) { + puts("X-Fdemime-Error: multipart boundary attr contains newline"); + putchar('\n'); + msg_state = MSG_STATE_BODY_PASS; + return; + } + if (strlen(boundary_attr) > MAX_MP_BOUNDARY) { + puts("X-Fdemime-Error: multipart boundary attr is too long"); + putchar('\n'); + msg_state = MSG_STATE_BODY_PASS; + return; + } + if (mp_nest_level >= MAX_MP_NESTING) { + puts("X-Fdemime-Error: multipart nesting is too deep"); + putchar('\n'); + msg_state = MSG_STATE_BODY_PASS; + return; + } + putchar('\n'); + strcpy(mp_boundaries[mp_nest_level], boundary_attr); + mp_is_digest[mp_nest_level] = !strcasecmp(cont_subtype, "digest"); + mp_nest_level++; + msg_state = MSG_STATE_BODY_PASS; +} + +void +process_header_end() +{ + char cont_type[HDR_BUF_SIZE], cont_subtype[HDR_BUF_SIZE]; + char charset_attr[HDR_BUF_SIZE], boundary_attr[HDR_BUF_SIZE]; + char content_te[HDR_BUF_SIZE]; + int in_digest, rc; + + if (hdr_state == HDR_STATE_ERROR) { + if (got_cont_type) + fputs(cont_type_buf, stdout); + if (got_cont_te) + fputs(cont_te_buf, stdout); + putchar('\n'); + msg_state = MSG_STATE_BODY_PASS; + return; + } + if (mp_nest_level) + in_digest = mp_is_digest[mp_nest_level-1]; + else + in_digest = 0; + if (got_cont_type) { + fputs(cont_type_buf, stdout); + rc = parse_content_type(cont_type, cont_subtype, charset_attr, + boundary_attr); + if (rc < 0) { + puts("X-Fdemime-Error: unable to parse Content-Type"); + if (got_cont_te) + fputs(cont_te_buf, stdout); + putchar('\n'); + msg_state = MSG_STATE_BODY_PASS; + return; + } + } else { + if (in_digest) { + strcpy(cont_type, "message"); + strcpy(cont_subtype, "rfc822"); + } else { + strcpy(cont_type, "text"); + strcpy(cont_subtype, "plain"); + } + charset_attr[0] = '\0'; + boundary_attr[0] = '\0'; + } + if (!strcasecmp(cont_type, "multipart")) { + if (got_cont_te) + fputs(cont_te_buf, stdout); + handle_multipart(cont_subtype, boundary_attr); + return; + } + if (!strcasecmp(cont_type, "message")) { + if (got_cont_te) + fputs(cont_te_buf, stdout); + putchar('\n'); + msg_state = MSG_STATE_BODY_PASS; + return; + } + if (got_cont_te) { + rc = parse_content_te(content_te); + if (rc < 0) { + fputs(cont_te_buf, stdout); + puts( + "X-Fdemime-Error: unable to parse Content-Transfer-Encoding"); + putchar('\n'); + msg_state = MSG_STATE_BODY_PASS; + return; + } + } else + content_te[0] = '\0'; + if (!strcasecmp(content_te, "base64")) { + if (!strcasecmp(cont_type, "text")) { + if (!strcasecmp(cont_subtype, "plain")) + init_base64_text_plain(charset_attr); + else + init_base64_text_other(); + } else + init_base64_nontext(); + return; + } + if (!strcasecmp(content_te, "quoted-printable") && + !strcasecmp(cont_type, "text") && + !strcasecmp(cont_subtype, "plain")) { + init_qp_text_plain(charset_attr); + return; + } + if (got_cont_te) + fputs(cont_te_buf, stdout); + putchar('\n'); + msg_state = MSG_STATE_BODY_PASS; +} diff -r 000000000000 -r 7e0d08176f32 f-demime/initconv.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/initconv.c Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,124 @@ +/* + * This module contains functions that implement initiation of various + * conversions performed by f-demime. + */ + +#include +#include +#include +#include +#include +#include "defs.h" + +extern enum msg_state msg_state; +extern char cont_te_buf[HDR_BUF_SIZE]; +extern int got_cont_type, got_cont_te; +extern char *att_filename_base; +extern int qpdec_err_flag; + +void (*dec_outf)(); +FILE *tempfile; +int text_is_utf8; + +static int +create_tempfile() +{ + char template[16]; + int fd; + + strcpy(template, "/tmp/fdemXXXXXX"); + fd = mkstemp(template); + if (fd < 0) { + perror("mkstemp"); + return(-1); + } + unlink(template); + tempfile = fdopen(fd, "r+w"); + if (!tempfile) { + perror("fdopen"); + close(fd); + return(-1); + } + return(0); +} + +static int +init_tempfile() +{ + int rc; + + rc = create_tempfile(); + if (!rc) + return(0); + if (got_cont_te) + fputs(cont_te_buf, stdout); + puts("X-Fdemime-Error: unable to create temp file for conversion"); + putchar('\n'); + msg_state = MSG_STATE_BODY_PASS; + return(-1); +} + +static void +implicit_text_plain() +{ + if (!got_cont_type) + puts("Content-Type: text/plain (f-demime implied)"); +} + +static void +grok_charset_attr(csa) + char *csa; +{ + text_is_utf8 = !strcasecmp(csa, "UTF-8") || !strcmp(csa, "csUTF8"); +} + +void +init_base64_text_plain(charset_attr) + char *charset_attr; +{ + implicit_text_plain(); + if (init_tempfile() < 0) + return; + grok_charset_attr(charset_attr); + base64_dec_init(); + ptext_conv_init(); + msg_state = MSG_STATE_PTEXT_B64; +} + +void +init_qp_text_plain(charset_attr) + char *charset_attr; +{ + implicit_text_plain(); + if (init_tempfile() < 0) + return; + grok_charset_attr(charset_attr); + qpdec_err_flag = 0; + ptext_conv_init(); + msg_state = MSG_STATE_PTEXT_QP; +} + +void +init_base64_text_other() +{ + if (init_tempfile() < 0) + return; + base64_dec_init(); + b2q_conv_init(); + msg_state = MSG_STATE_B64_TO_QP; +} + +void +init_base64_nontext() +{ + fputs(cont_te_buf, stdout); + if (init_attach_out() < 0) { + puts("X-Fdemime-Error: unable to create save file"); + putchar('\n'); + msg_state = MSG_STATE_BODY_PASS; + return; + } + printf("X-Fdemime-Saved: %s\n", att_filename_base); + base64_dec_init(); + msg_state = MSG_STATE_BLOB_B64; +} diff -r 000000000000 -r 7e0d08176f32 f-demime/main.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/main.c Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,100 @@ +/* + * This module contains the main() function for f-demime. The following + * functions are called to pass input to further processing: + * + * begin_new_message(): called after processing a "From " line + * message_input_line(): called for all message lines after the "From " line + * finish_msg_body(): called when hitting EOF or a new "From " line + */ + +#include +#include +#include +#include +#include +#include "defs.h" + +char *att_filename_buf, *att_filename_base, *att_filename_tail; +unsigned input_lineno; + +static void +get_attachment_dir() +{ + static char varname[] = "FDEMIME_ATT_DIR"; + char *env, *cp; + unsigned dir_name_len; + time_t curtime; + struct tm *tm; + + env = getenv(varname); + if (!env) { + fprintf(stderr, + "error: required environment variable %s is not set\n", + varname); + exit(1); + } + if (!env[0]) { + fprintf(stderr, + "error: empty string value for %s is not allowed\n", + varname); + exit(1); + } + dir_name_len = strlen(env); + att_filename_buf = malloc(dir_name_len + 31); /* Y10K extra margin */ + if (!att_filename_buf) { + fprintf(stderr, + "error: unable to malloc buffer for attachment filenames\n"); + exit(1); + } + strcpy(att_filename_buf, env); + cp = att_filename_buf + dir_name_len; + *cp++ = '/'; + att_filename_base = cp; + time(&curtime); + tm = gmtime(&curtime); + sprintf(cp, "%u%02u%02ua", tm->tm_year + 1900, tm->tm_mon + 1, + tm->tm_mday); + att_filename_tail = index(cp, '\0'); +} + +main(argc, argv) + char **argv; +{ + char linebuf[LINE_BUF_SIZE], *cp; + + if (argc > 3) { + fprintf(stderr, "usage: %s [infile [outfile]]\n", argv[0]); + exit(1); + } + get_attachment_dir(); + if (argc > 1 && !freopen(argv[1], "r", stdin)) { + perror(argv[1]); + exit(1); + } + if (argc > 2 && !freopen(argv[2], "w", stdout)) { + perror(argv[2]); + exit(1); + } + for (input_lineno = 1; fgets(linebuf, sizeof linebuf, stdin); + input_lineno++) { + cp = index(linebuf, '\n'); + if (cp) { + *cp = '\0'; + if (cp > linebuf && cp[-1] == '\r') + *--cp = '\0'; + } else { + fprintf(stderr, + "f-demime warning: input line %u too long or unterminated\n", + input_lineno); + } + if (!strncmp(linebuf, "From ", 5)) { + finish_msg_body(); + puts(linebuf); + begin_new_message(); + continue; + } + message_input_line(linebuf); + } + finish_msg_body(); + exit(0); +} diff -r 000000000000 -r 7e0d08176f32 f-demime/msgstate.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/msgstate.c Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,110 @@ +/* + * This module implements the message state machine, receiving input lines + * and message start/end markers from the input processing main loop. + */ + +#include +#include +#include +#include +#include +#include "defs.h" + +enum msg_state msg_state; +enum msg_hdr_state hdr_state; +unsigned mp_nest_level; +char mp_boundaries[MAX_MP_NESTING][MAX_MP_BOUNDARY+1]; +int mp_is_digest[MAX_MP_NESTING]; +char cont_type_buf[HDR_BUF_SIZE], cont_te_buf[HDR_BUF_SIZE]; +int got_cont_type, got_cont_te; + +void +start_entity_hdr() +{ + msg_state = MSG_STATE_HEADER; + hdr_state = HDR_STATE_BEGIN; + got_cont_type = 0; + got_cont_te = 0; +} + +void +begin_new_message() +{ + mp_nest_level = 0; + start_entity_hdr(); +} + +static void +emit_prefrom_warning() +{ + static int done; + + if (done) + return; + fprintf(stderr, + "f-demime warning: data present before the first From\n"); + done = 1; +} + +static int +check_mp_terminator(line) + char *line; +{ + unsigned lev, bndlen; + char *bnd, *cp; + + if (line[0] != '-' || line[1] != '-') + return(0); + for (lev = 0; lev < mp_nest_level; lev++) { + bnd = mp_boundaries[lev]; + bndlen = strlen(bnd); + if (strncmp(line+2, bnd, bndlen)) + continue; + finish_msg_body(); + puts(line); + cp = line + 2 + bndlen; + if (!*cp || isspace(*cp)) { + mp_nest_level = lev + 1; + start_entity_hdr(); + return(1); + } + if (cp[0] != '-' || cp[1] != '-') + puts("X-Fdemime-Error: invalid delimiter line"); + mp_nest_level = lev; + msg_state = MSG_STATE_BODY_PASS; + return(1); + } + return(0); +} + +void +message_input_line(line) + char *line; +{ + if (check_mp_terminator(line)) + return; + switch (msg_state) { + case MSG_STATE_UNDEF: + emit_prefrom_warning(); + puts(line); + return; + case MSG_STATE_HEADER: + header_input_line(line); + return; + case MSG_STATE_BODY_PASS: + puts(line); + return; + case MSG_STATE_PTEXT_B64: + case MSG_STATE_BLOB_B64: + case MSG_STATE_B64_TO_QP: + base64_input_line(line); + return; + case MSG_STATE_PTEXT_QP: + qpdec_input_line(line); + return; + default: + fprintf(stderr, + "f-demime internal error: bad state in message_input_line()\n"); + abort(); + } +} diff -r 000000000000 -r 7e0d08176f32 f-demime/ptext_in.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/ptext_in.c Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,240 @@ +/* + * This module implements transformations that are specific to text/plain. + */ + +#include +#include +#include +#include +#include +#include "defs.h" + +extern void (*dec_outf)(); +extern FILE *tempfile; +extern int text_is_utf8; + +int ptext_has_backslash, ptext_has_linesplit; + +static enum { + FPS_GROUND, + FPS_CR, + FPS_UTF8 +} first_pass_state; +static u_char utf8_buf[4]; +static unsigned utf8_nbytes, utf8_wptr, unicode; +static unsigned out_line_len, trailing_newline; + +static void +unit_output(str) + char *str; +{ + unsigned newlen; + + newlen = strlen(str); + if (out_line_len + newlen >= OUTPUT_LINE_MAX) + putc('\\', tempfile); + putc('\n', tempfile); + out_line_len = 0; + ptext_has_linesplit = 1; + } + fputs(str, tempfile); + out_line_len += newlen; + trailing_newline = 0; +} + +static void +newline_out() +{ + putc('\n', tempfile); + out_line_len = 0; + trailing_newline++; + if (trailing_newline > 2) + trailing_newline = 2; +} + +static void +direct_output(ch) +{ + char buf[2]; + + buf[0] = ch; + buf[1] = '\0'; + unit_output(buf); +} + +static void +simple_escape(ch) +{ + char buf[3]; + + buf[0] = '\\'; + buf[1] = ch; + buf[2] = '\0'; + unit_output(buf); +} + +static void +hex_escape(ch) +{ + char buf[5]; + + sprintf(buf, "\\x%02X", ch); + unit_output(buf); +} + +static void +regular_byte(ch) +{ + if (ch == '\\') { + ptext_has_backslash = 1; + simple_escape(ch); + return; + } + if (ch >= ' ' && ch <= '~') { + direct_output(ch); + return; + } + switch (ch) { + case 0x07: + simple_escape('a'); + return; + case 0x08': + simple_escape('b'); + return; + case 0x09: + direct_output(ch); + return; + case 0x0B: + simple_escape('v'); + return; + case 0x0C: + simple_escape('f'); + return; + case 0x0D: + simple_escape('r'); + return; + case 0x1B: + simple_escape('e'); + return; + } + hex_escape(ch); +} + +static int +utf8_collect() +{ + switch (utf8_nbytes) { + case 2: + unicode = ((utf8_buf[0] & 0x1F) << 6) | (utf8_buf[1] & 0x3F); + return(1); + case 3: + unicode = ((utf8_buf[0] & 0x0F) << 12) | + ((utf8_buf[1] & 0x3F) << 6) | (utf8_buf[2] & 0x3F); + if (unicode & 0xF800) + return(1); + else + return(0); + case 4: + unicode = ((utf8_buf[0] & 0x07) << 18) | + ((utf8_buf[1] & 0x3F) << 12) | + ((utf8_buf[2] & 0x3F) << 6) | (utf8_buf[3] & 0x3F); + if (unicode & 0x1F0000) + return(1); + else + return(0); + default: + return(0); + } +} + +static void +unicode_out() +{ + char buf[9]; + + if (unicode >= 0x10000) + sprintf(buf, "\\U%06u", unicode); + else + sprintf(buf, "\\u%04u", unicode); + unit_output(buf); +} + +static void +flush_first_pass_state() +{ + unsigned n; + + switch (first_pass_state) { + case FPS_CR: + regular_byte('\r'); + break; + case FPS_UTF8: + for (n = 0; n < utf8_wptr; n++) + regular_byte(utf8_buf[n]); + break; + } + first_pass_state = FPS_GROUND; +} + +static void +first_pass(ch) +{ + if (first_pass_state == FPS_CR && ch == '\n') { + first_pass_state = FPS_GROUND; + newline_out(); + return; + } + if (first_pass_state == FPS_UTF8 && ch >= 0x80 && ch <= 0xBF) { + utf8_buf[utf8_wptr++] = ch; + if (utf8_wptr < utf8_nbytes) + return; + if (utf8_collect()) { + first_pass_state = FPS_GROUND; + unicode_out(); + return; + } + } + flush_first_pass_state(); + switch (ch) { + case '\n': + newline_out(); + return; + case '\r': + first_pass_state = FPS_CR; + return; + } + if (!text_is_utf8 || ch < 0xC2 || ch > 0xF7) { + regular_byte(ch); + return; + } + first_pass_state = FPS_UTF8; + utf8_buf[0] = ch; + utf8_wptr = 1; + if (ch < 0xE0) + utf8_nbytes = 2; + else if (ch < 0xF0) + utf8_nbytes = 3; + else + utf8_nbytes = 4; +} + +void +ptext_conv_init() +{ + dec_outf = first_pass; + ptext_has_backslash = 0; + ptext_has_linesplit = 0; + first_pass_state = FPS_GROUND; + out_line_len = 0; + trailing_newline = 1; +} + +void +ptext_conv_finish() +{ + flush_first_pass_state(); + while (trailing_newline < 2) { + putc('\n', tempfile); + trailing_newline++; + } +} diff -r 000000000000 -r 7e0d08176f32 f-demime/ptext_out.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/ptext_out.c Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,62 @@ +/* + * This module implements transformations that are specific to text/plain. + */ + +#include +#include +#include +#include +#include "defs.h" + +extern FILE *tempfile; +extern int ptext_has_backslash, ptext_has_linesplit; +extern unsigned mp_nest_level; +extern char mp_boundaries[MAX_MP_NESTING][MAX_MP_BOUNDARY+1]; + +void +ptext_mark_transform(base_xform) + char *base_xform; +{ + printf("X-Fdemime-Transform: %s", base_xform); + if (ptext_has_backslash) + fputs(", double-backslash", stdout); + if (ptext_has_linesplit) + fputs(", line-split", stdout); + putchar('\n'); +} + +static int +boundary_hit(line) + char *line; +{ + unsigned lev, bndlen; + char *bnd; + + if (line[0] != '-' || line[1] != '-') + return(0); + for (lev = 0; lev < mp_nest_level; lev++) { + bnd = mp_boundaries[lev]; + bndlen = strlen(bnd); + if (strncmp(line+2, bnd, bndlen)) + continue; + return(1); + } + return(0); +} + +void +ptext_emit_output() +{ + char line[LINE_BUF_SIZE]; + + rewind(tempfile); + while (fgets(line, sizeof line, tempfile)) { + if (!strncmp(line, "From ", 5) || boundary_hit(line) || + !strcmp(line, ".\n")) { + putchar('\\'); + putchar('&'); + } + fputs(line, stdout); + } + fclose(tempfile); +} diff -r 000000000000 -r 7e0d08176f32 f-demime/qpdec.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/f-demime/qpdec.c Sat May 06 06:14:03 2023 +0000 @@ -0,0 +1,76 @@ +/* + * This module implements quoted-printable decoding. + */ + +#include +#include +#include +#include +#include +#include "defs.h" + +extern void (*dec_outf)(); + +int qpdec_err_flag; + +static void +strip_trailing_lwsp(line) + char *line; +{ + char *cp; + + cp = index(line, '\0'); + while (cp > line && isspace(cp[-1]) + cp--; + *cp = '\0'; +} + +static int +decode_hex_digit(c) +{ + if (isdigit(c)) + return(c - '0'); + else if (isupper(c)) + return(c - 'A' + 10); + else + return(c - 'a' + 10); +} + +static int +decode_hex_byte(cp) + char *cp; +{ + int u, l; + + u = decode_hex_digit(cp[0]); + l = decode_hex_digit(cp[1]); + return (u << 4) | l; +} + +void +qpdec_input_line(line) + char *line; +{ + char *cp; + int c; + + strip_trailing_lwsp(line); + for (cp = line; *cp; ) { + c = *cp++ & 0xFF; + if (c != '=') { + dec_outf(c); + continue; + } + if (!*cp) + return; + if (isxdigit(cp[0]) && isxdigit(cp[1])) { + c = decode_hex_byte(cp); + cp += 2; + dec_outf(c); + continue; + } + qpdec_err_flag = 1; + dec_outf('='); + } + dec_outf('\n'); +}