X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=unmime.c;h=f799ff92f51b5a22836a9135c43f9b4df8878bfb;hb=d31db10231e9ed89f64fdf6e0fb7cae182aa377e;hp=5a9841c8caccfd71d44fc751817db5fe0467fea9;hpb=e28b5c24dd820c6b31878e3aa88f3cc433bc443a;p=~andy%2Ffetchmail diff --git a/unmime.c b/unmime.c index 5a9841c8..f799ff92 100644 --- a/unmime.c +++ b/unmime.c @@ -10,12 +10,16 @@ * Configuration file support for fetchmail 4.3.8 by * Frank Damgaard * + * For license terms, see the file COPYING in this directory. */ +#include "config.h" #include #include +#include #include #include "fetchmail.h" +#include "i18n.h" static unsigned char unhex(unsigned char c) { @@ -26,10 +30,10 @@ static unsigned char unhex(unsigned char c) else if ((c >= 'a') && (c <= 'f')) return (c - 'a' + 10); else - return c; + return 16; /* invalid hex character */ } -static int qp_char(unsigned char c1, unsigned char c2, unsigned char *c_out) +static int qp_char(unsigned char c1, unsigned char c2, char *c_out) { c1 = unhex(c1); c2 = unhex(c2); @@ -43,7 +47,6 @@ static int qp_char(unsigned char c1, unsigned char c2, unsigned char *c_out) } - /* * Routines to decode MIME QP-encoded headers, as per RFC 2047. */ @@ -56,7 +59,7 @@ static int qp_char(unsigned char c1, unsigned char c2, unsigned char *c_out) static const char MIMEHDR_INIT[] = "=?"; /* Start of coded sequence */ static const char MIMEHDR_END[] = "?="; /* End of coded sequence */ -void UnMimeHeader(unsigned char *hdr) +void UnMimeHeader(char *hdr) { /* Decode a buffer containing data encoded according to RFC * 2047. This only handles content-transfer-encoding; conversion @@ -68,12 +71,12 @@ void UnMimeHeader(unsigned char *hdr) /* Note: Decoding is done "in-situ", i.e. without using an * additional buffer for temp. storage. This is possible, since the * decoded string will always be shorter than the encoded string, - * due to the en- coding scheme. + * due to the encoding scheme. */ int state = S_COPY_PLAIN; - unsigned char *p_in, *p_out, *p; - unsigned char enc; + char *p_in, *p_out, *p; + char enc = '\0'; /* initialization pacifies -Wall */ int i; /* Speed up in case this is not a MIME-encoded header */ @@ -120,7 +123,7 @@ void UnMimeHeader(unsigned char *hdr) /* *(p+1) is the transfer encoding, *(p+2) must be a '?' */ if (*(p+2) == '?') { - enc = tolower(*(p+1)); + enc = tolower((unsigned char)*(p+1)); p_in = p+3; state = S_COPY_MIME; } @@ -168,7 +171,7 @@ void UnMimeHeader(unsigned char *hdr) int decoded_count; delimsave = *p; *p = '\r'; - decoded_count = from64tobits(p_out, p_in); + decoded_count = from64tobits(p_out, p_in, 0); *p = delimsave; if (decoded_count > 0) p_out += decoded_count; @@ -197,11 +200,11 @@ void UnMimeHeader(unsigned char *hdr) * There is more MIME data later on. Is there * whitespace only before the delimiter? */ - unsigned char *q; + char *q; int wsp_only = 1; for (q=p_in; (wsp_only && (q < p)); q++) - wsp_only = isspace(*q); + wsp_only = isspace((unsigned char)*q); if (wsp_only) { /* @@ -210,7 +213,7 @@ void UnMimeHeader(unsigned char *hdr) * and prepare to process the new MIME charset/encoding * header. */ - p_in = p + strlen(MIMEHDR_INIT); + p_in = p + sizeof(MIMEHDR_INIT) - 1; state = S_SKIP_MIMEINIT; } } @@ -248,6 +251,7 @@ static int BodyState = S_BODY_DATA; * a quoted-printable body part. */ static int CurrEncodingIsQP = 0; +static int CurrTypeNeedsDecode = 0; /* * Delimiter for multipart messages. RFC 2046 states that this must @@ -255,24 +259,105 @@ static int CurrEncodingIsQP = 0; * at the beginning, and a terminating null. */ #define MAX_DELIM_LEN 70 -static unsigned char MultipartDelimiter[MAX_DELIM_LEN+3]; +static char MultipartDelimiter[MAX_DELIM_LEN+3]; /* This string replaces the "Content-Transfer-Encoding: quoted-printable" - * string in all headers, including those in body-parts. It must be - * no longer than the original string. + * string in all headers, including those in body-parts. The replacement + * must be no longer than the original string. */ static const char ENC8BIT[] = "Content-Transfer-Encoding: 8bit"; -static void SetEncoding8bit(unsigned char *XferEncOfs) +static void SetEncoding8bit(char *XferEncOfs) { - unsigned char *p; + char *p; if (XferEncOfs != NULL) { - memcpy(XferEncOfs, ENC8BIT, strlen(ENC8BIT)); + memcpy(XferEncOfs, ENC8BIT, sizeof(ENC8BIT) - 1); /* If anything left, in this header, replace with whitespace */ - for (p=XferEncOfs+strlen(ENC8BIT); (*p >= ' '); p++) *p=' '; + for (p=XferEncOfs+sizeof(ENC8BIT)-1; ((unsigned char)*p >= ' '); p++) + *p=' '; + } +} + +static char *GetBoundary(char *CntType) +{ + char *p1, *p2; + int flag; + + /* Find the "boundary" delimiter. It must be preceded with a ';' + * and optionally some whitespace. + */ + p1 = CntType; + do { + p2 = strchr(p1, ';'); + if (p2) + for (p2++; isspace((unsigned char)*p2); p2++) { } + + p1 = p2; + } while ((p1) && (strncasecmp(p1, "boundary", 8) != 0)); + + if (p1 == NULL) + /* No boundary delimiter */ + return NULL; + + /* Skip "boundary", whitespace and '='; check that we do have a '=' */ + for (p1+=8, flag=0; (isspace((unsigned char)*p1) || (*p1 == '=')); p1++) + flag |= (*p1 == '='); + if (!flag) + return NULL; + + /* Find end of boundary delimiter string */ + if (*p1 == '\"') { + /* The delimiter is inside quotes */ + p1++; + p2 = strchr(p1, '\"'); + if (p2 == NULL) + return NULL; /* No closing '"' !?! */ } + else { + /* There might be more text after the "boundary" string. */ + p2 = strchr(p1, ';'); /* Safe - delimiter with ';' must be in quotes */ + } + + /* Zero-terminate the boundary string */ + if (p2 != NULL) + *p2 = '\0'; + + return (p1 && strlen(p1)) ? p1 : NULL; +} + + +static int CheckContentType(char *CntType) +{ + /* + * Static array of Content-Type's for which we will do + * quoted-printable decoding, if requested. + * It is probably wise to do this only on known text-only types; + * be really careful if you change this. + */ + + static const char *DecodedTypes[] = { + "text/", /* Will match ALL content-type's starting with 'text/' */ + "message/rfc822", + NULL + }; + + char *p = CntType; + int i; + + /* If no Content-Type header, it isn't MIME - don't touch it */ + if (CntType == NULL) return 0; + + /* Skip whitespace, if any */ + for (; isspace((unsigned char)*p); p++) ; + + for (i=0; + (DecodedTypes[i] && + (strncasecmp(p, DecodedTypes[i], strlen(DecodedTypes[i])))); + i++) ; + + return (DecodedTypes[i] != NULL); } @@ -287,147 +372,137 @@ static void SetEncoding8bit(unsigned char *XferEncOfs) * - All other messages are assumed NOT to include 8-bit data. * 2) It determines the delimiter-string used in multi-part message * bodies. - * 3) It sets the initial values of the CurrEncodingIsQP and BodyState - * variables, from the header contents. + * 3) It sets the initial values of the CurrEncodingIsQP, + * CurrTypeNeedsDecode, and BodyState variables, from the header + * contents. * * The return value is a bitmask. */ -int MimeBodyType(unsigned char *hdrs) +int MimeBodyType(char *hdrs, int WantDecode) { - unsigned char *NxtHdr = hdrs; - unsigned char *XferEnc, *XferEncOfs, *CntType, *MimeVer, *p; - int HdrsFound = 0; /* We only look for three headers */ - int BodyType; /* Return value */ - - /* Setup for a standard (no MIME, no QP, 7-bit US-ASCII) message */ - MultipartDelimiter[0] = '\0'; - CurrEncodingIsQP = 0; - BodyState = S_BODY_DATA; - BodyType = 0; - - /* Just in case ... */ - if (hdrs == NULL) - return BodyType; + char *NxtHdr = hdrs; + char *XferEnc, *XferEncOfs, *CntType, *MimeVer, *p; + int HdrsFound = 0; /* We only look for three headers */ + int BodyType; /* Return value */ + + /* Setup for a standard (no MIME, no QP, 7-bit US-ASCII) message */ + MultipartDelimiter[0] = '\0'; + CurrEncodingIsQP = CurrTypeNeedsDecode = 0; + BodyState = S_BODY_DATA; + BodyType = 0; + + /* Just in case ... */ + if (hdrs == NULL) + return BodyType; + + XferEnc = XferEncOfs = CntType = MimeVer = NULL; + + do { + if (strncasecmp("Content-Transfer-Encoding:", NxtHdr, 26) == 0) { + XferEncOfs = NxtHdr; + p = nxtaddr(NxtHdr); + if (p != NULL) { + xfree(XferEnc); + XferEnc = xstrdup(p); + HdrsFound++; + } + } + else if (strncasecmp("Content-Type:", NxtHdr, 13) == 0) { + /* + * This one is difficult. We cannot use the standard + * nxtaddr() routine, since the boundary-delimiter is + * (probably) enclosed in quotes - and thus appears + * as an rfc822 comment, and nxtaddr() "eats" up any + * spaces in the delimiter. So, we have to do this + * by hand. + */ + + /* Skip the "Content-Type:" part and whitespace after it */ + for (NxtHdr += 13; ((*NxtHdr == ' ') || (*NxtHdr == '\t')); NxtHdr++) { } - XferEnc = XferEncOfs = CntType = MimeVer = NULL; + /* + * Get the full value of the Content-Type header; + * it might span multiple lines. So search for + * a newline char, but ignore those that have a + * have a TAB or space just after the NL (continued + * lines). + */ + p = NxtHdr-1; + do { + p=strchr((p+1),'\n'); + } while ( (p != NULL) && ((*(p+1) == '\t') || (*(p+1) == ' ')) ); + if (p == NULL) p = NxtHdr + strlen(NxtHdr); + + xfree(CntType); + CntType = (char *)xmalloc(p-NxtHdr+1); + strlcpy(CntType, NxtHdr, p-NxtHdr+1); + HdrsFound++; + } + else if (strncasecmp("MIME-Version:", NxtHdr, 13) == 0) { + p = nxtaddr(NxtHdr); + if (p != NULL) { + xfree(MimeVer); + MimeVer = xstrdup(p); + HdrsFound++; + } + } - do { - if (strncasecmp("Content-Transfer-Encoding:", NxtHdr, 26) == 0) { - XferEncOfs = NxtHdr; - p = nxtaddr(NxtHdr); - if (p != NULL) { - XferEnc = (char *)xmalloc(strlen(p) + 1); - strcpy(XferEnc, p); - HdrsFound++; - } - } - else if (strncasecmp("Content-Type:", NxtHdr, 13) == 0) { - /* - * This one is difficult. We cannot use the standard - * nxtaddr() routine, since the boundary-delimiter is - * (probably) enclosed in quotes - and thus appears - * as an rfc822 comment, and nxtaddr() "eats" up any - * spaces in the delimiter. So, we have to do this - * by hand. - */ + NxtHdr = (strchr(NxtHdr, '\n')); + if (NxtHdr != NULL) NxtHdr++; + } while ((NxtHdr != NULL) && (*NxtHdr) && (HdrsFound != 3)); - /* Skip the "Content-Type:" part and whitespace after it */ - for (NxtHdr += 13; ((*NxtHdr == ' ') || (*NxtHdr == '\t')); NxtHdr++); - /* - * Get the full value of the Content-Type header; - * it might span multiple lines. So search for - * a newline char, but ignore those that have a - * have a TAB or space just after the NL (continued - * lines). - */ - p = NxtHdr-1; - do { - p=strchr((p+1),'\n'); - } while ( (p != NULL) && ((*(p+1) == '\t') || (*(p+1) == ' ')) ); - if (p == NULL) p = NxtHdr + strlen(NxtHdr); - - CntType = (char *)xmalloc(p-NxtHdr+2); - strncpy(CntType, NxtHdr, (p-NxtHdr)); - *(CntType+(p-NxtHdr)) = '\0'; - HdrsFound++; - } - else if (strncasecmp("MIME-Version:", NxtHdr, 13) == 0) { - p = nxtaddr(NxtHdr); - if (p != NULL) { - MimeVer = (char *)xmalloc(strlen(p) + 1); - strcpy(MimeVer, p); - HdrsFound++; - } - } - - NxtHdr = (strchr(NxtHdr, '\n')); - if (NxtHdr != NULL) NxtHdr++; - } while ((NxtHdr != NULL) && (*NxtHdr) && (HdrsFound != 3)); + /* Done looking through the headers, now check what they say */ + if ((MimeVer != NULL) && (strcmp(MimeVer, "1.0") == 0)) { + CurrTypeNeedsDecode = CheckContentType(CntType); - /* Done looking through the headers, now check what they say */ - if ((MimeVer != NULL) && (strcmp(MimeVer, "1.0") == 0)) { + /* Check Content-Type to see if this is a multipart message */ + if ( (CntType != NULL) && + ((strncasecmp(CntType, "multipart/mixed", 16) == 0) || + (strncasecmp(CntType, "message/", 8) == 0)) ) { - /* Check Content-Type to see if this is a multipart message */ - if (CntType != NULL) { - if ((strncasecmp(CntType, "multipart/", 10) == 0) || - (strncasecmp(CntType, "message/", 8) == 0)) { + char *p1 = GetBoundary(CntType); - char *p1, *p2; - - /* Search for "boundary=" */ - p1 = strchr(CntType, '='); - if (p1 != NULL) { - /* Skip the '=' and any whitespace after it */ - for (p1++; (isspace(*p1)); p1++); - - /* The delimiter might be inside quotes */ - if (*p1 == '\"') { - p1++; - p2 = strchr(p1, '\"'); - if (p2 != NULL) - *p2 = '\0'; - } + if (p1 != NULL) { + /* The actual delimiter is "--" followed by + the boundary string */ + strcpy(MultipartDelimiter, "--"); + strlcat(MultipartDelimiter, p1, sizeof(MultipartDelimiter)); + MultipartDelimiter[sizeof(MultipartDelimiter)-1] = '\0'; + BodyType = (MSG_IS_8BIT | MSG_NEEDS_DECODE); + } + } - if (strlen(p1) > 0) { - /* The actual delimiter is "--" followed by - the boundary string */ - strcpy(MultipartDelimiter, "--"); - strncat(MultipartDelimiter, p1, MAX_DELIM_LEN); - BodyType = (MSG_IS_8BIT | MSG_NEEDS_DECODE); - } + /* + * Check Content-Transfer-Encoding, but + * ONLY for non-multipart messages (BodyType == 0). + */ + if ((XferEnc != NULL) && (BodyType == 0)) { + if (strcasecmp(XferEnc, "quoted-printable") == 0) { + CurrEncodingIsQP = 1; + BodyType = (MSG_IS_8BIT | MSG_NEEDS_DECODE); + if (WantDecode && CurrTypeNeedsDecode) { + SetEncoding8bit(XferEncOfs); + } + } + else if (strcasecmp(XferEnc, "7bit") == 0) { + CurrEncodingIsQP = 0; + BodyType = (MSG_IS_7BIT); + } + else if (strcasecmp(XferEnc, "8bit") == 0) { + CurrEncodingIsQP = 0; + BodyType = (MSG_IS_8BIT); + } } - } - } - /* - * Check Content-Transfer-Encoding, but - * ONLY for non-multipart messages (BodyType == 0). - */ - if ((XferEnc != NULL) && (BodyType == 0)) { - if (strcasecmp(XferEnc, "quoted-printable") == 0) { - CurrEncodingIsQP = 1; - BodyType = (MSG_IS_8BIT | MSG_NEEDS_DECODE); - SetEncoding8bit(XferEncOfs); - } - else if (strcasecmp(XferEnc, "7bit") == 0) { - CurrEncodingIsQP = 0; - BodyType = (MSG_IS_7BIT); - } - else if (strcasecmp(XferEnc, "8bit") == 0) { - CurrEncodingIsQP = 0; - BodyType = (MSG_IS_8BIT); - } } - } - - if (MimeVer) free(MimeVer); - if (XferEnc) free(XferEnc); - if (CntType) free(CntType); + xfree(XferEnc); + xfree(CntType); + xfree(MimeVer); - return BodyType; + return BodyType; } @@ -436,15 +511,26 @@ int MimeBodyType(unsigned char *hdrs) * Return flag set if this line ends with a soft line-break. * 'bufp' is modified to point to the end of the output buffer. */ -static int DoOneQPLine(unsigned char **bufp, int collapsedoubledot) +static int DoOneQPLine(char **bufp, flag delimited, flag issoftline) { - unsigned char *buf = *bufp; - unsigned char *p_in, *p_out, *p; + char *buf = *bufp; + char *p_in, *p_out, *p; int n; int ret = 0; + /* + * Special case: line consists of a single =2E and messages are + * dot-terminated. Line has to be dot-stuffed after decoding. + */ + if (delimited && !issoftline && buf[0]=='=' && !strncmp(*bufp, "=2E\r\n", 5)) + { + strcpy(buf, "..\r\n"); + *bufp += 5; + return(FALSE); + } + p_in = buf; - if (collapsedoubledot && (strncmp(buf, "..", 2) == 0)) + if (delimited && issoftline && (strncmp(buf, "..", 2) == 0)) p_in++; for (p_out = buf; (*p_in); ) { @@ -507,25 +593,38 @@ static int DoOneQPLine(unsigned char **bufp, int collapsedoubledot) * 'bufp' is modified to point to the end of the output buffer. */ -int UnMimeBodyline(unsigned char **bufp, int collapsedoubledot) +int UnMimeBodyline(char **bufp, flag delimited, flag softline) { - unsigned char *buf = *bufp; + char *buf = *bufp; int ret = 0; switch (BodyState) { case S_BODY_HDR: UnMimeHeader(buf); /* Headers in body-parts can be encoded, too! */ - if (strncasecmp("Content-Transfer-Encoding:", buf, 26) == 0) { + if ((*buf == '\0') || (*buf == '\n') || (strcmp(buf, "\r\n") == 0)) { + BodyState = S_BODY_DATA; + } + else if (strncasecmp("Content-Transfer-Encoding:", buf, 26) == 0) { char *XferEnc; XferEnc = nxtaddr(buf); if ((XferEnc != NULL) && (strcasecmp(XferEnc, "quoted-printable") == 0)) { CurrEncodingIsQP = 1; - SetEncoding8bit(buf); + + /* + * Hmm ... we cannot be really sure that CurrTypeNeedsDecode + * has been set - we may not have seen the Content-Type header + * yet. But *usually* the Content-Type header comes first, so + * this will work. And there is really no way of doing it + * "right" as long as we stick with the line-by-line processing. + */ + if (CurrTypeNeedsDecode) + SetEncoding8bit(buf); } } - else if ((*buf == '\0') || (*buf == '\n') || (strcmp(buf, "\r\n") == 0)) - BodyState = S_BODY_DATA; + else if (strncasecmp("Content-Type:", buf, 13) == 0) { + CurrTypeNeedsDecode = CheckContentType(nxtaddr(buf)); + } *bufp = (buf + strlen(buf)); break; @@ -534,11 +633,11 @@ int UnMimeBodyline(unsigned char **bufp, int collapsedoubledot) if ((*MultipartDelimiter) && (strncmp(buf, MultipartDelimiter, strlen(MultipartDelimiter)) == 0)) { BodyState = S_BODY_HDR; - CurrEncodingIsQP = 0; + CurrEncodingIsQP = CurrTypeNeedsDecode = 0; } - if (CurrEncodingIsQP) - ret = DoOneQPLine(bufp, collapsedoubledot); + if (CurrEncodingIsQP && CurrTypeNeedsDecode) + ret = DoOneQPLine(bufp, delimited, softline); else *bufp = (buf + strlen(buf)); break; @@ -552,12 +651,13 @@ int UnMimeBodyline(unsigned char **bufp, int collapsedoubledot) #include #include -char *program_name = "unmime"; +const char *program_name = "unmime"; +int outlevel = 0; #define BUFSIZE_INCREMENT 4096 #ifdef DEBUG -#define DBG_FWRITE(B,L,BS,FD) fwrite(B, L, BS, FD) +#define DBG_FWRITE(B,L,BS,FD) do { if (fwrite((B), (L), (BS), (FD))) { } } while(0) #else #define DBG_FWRITE(B,L,BS,FD) #endif @@ -565,23 +665,28 @@ char *program_name = "unmime"; int main(int argc, char *argv[]) { unsigned int BufSize; - unsigned char *buffer, *buf_p; + char *buffer, *buf_p; int nl_count, i, bodytype; + /* quench warnings about unused arguments */ + (void)argc; + (void)argv; + #ifdef DEBUG pid_t pid; FILE *fd_orig, *fd_conv; char fnam[100]; + /* we don't need snprintf here, but for consistency, we'll use it */ pid = getpid(); - sprintf(fnam, "/tmp/i_unmime.%x", pid); + snprintf(fnam, sizeof(fnam), "/tmp/i_unmime.%lx", (long)pid); fd_orig = fopen(fnam, "w"); - sprintf(fnam, "/tmp/o_unmime.%x", pid); + snprintf(fnam, sizeof(fnam), "/tmp/o_unmime.%lx", (long)pid); fd_conv = fopen(fnam, "w"); #endif BufSize = BUFSIZE_INCREMENT; /* Initial size of buffer */ - buf_p = buffer = (unsigned char *) xmalloc(BufSize); + buf_p = buffer = (char *) xmalloc(BufSize); nl_count = 0; do { @@ -600,9 +705,9 @@ int main(int argc, char *argv[]) } buf_p++; - if ((buf_p - buffer) == BufSize) { + if ((unsigned)(buf_p - buffer) == BufSize) { /* Buffer is full! Get more room. */ - buffer = xrealloc(buffer, BufSize+BUFSIZE_INCREMENT); + buffer = (char *)xrealloc(buffer, BufSize+BUFSIZE_INCREMENT); buf_p = buffer + BufSize; BufSize += BUFSIZE_INCREMENT; } @@ -612,11 +717,14 @@ int main(int argc, char *argv[]) DBG_FWRITE(buffer, strlen(buffer), 1, fd_orig); UnMimeHeader(buffer); - bodytype = MimeBodyType(buffer); + bodytype = MimeBodyType(buffer, 1); i = strlen(buffer); - fwrite(buffer, i, 1, stdout); DBG_FWRITE(buffer, i, 1, fd_conv); + if (fwrite(buffer, i, 1, stdout) < 1) { + perror("fwrite"); + goto barf; + } do { buf_p = (buffer - 1); @@ -631,15 +739,19 @@ int main(int argc, char *argv[]) if (buf_p > buffer) { if (bodytype & MSG_NEEDS_DECODE) { buf_p = buffer; - UnMimeBodyline(&buf_p, 0); + UnMimeBodyline(&buf_p, 0, 0); } - fwrite(buffer, (buf_p - buffer), 1, stdout); DBG_FWRITE(buffer, (buf_p - buffer), 1, fd_conv); + if (fwrite(buffer, (buf_p - buffer), 1, stdout) < 1) { + perror("fwrite"); + goto barf; + } } } while (buf_p > buffer); +barf: free(buffer); - fflush(stdout); + if (EOF == fflush(stdout)) perror("fflush"); #ifdef DEBUG fclose(fd_orig); @@ -649,4 +761,3 @@ int main(int argc, char *argv[]) return 0; } #endif -