/************************************************************************* * (C) 2012 AITNET ltd - Sofia/Bulgaria - * by Michael Pounov * * $Author: misho $ * $Id: mime.c,v 1.2 2012/03/10 00:26:49 misho Exp $ * ************************************************************************** The ELWIX and AITNET software is distributed under the following terms: All of the documentation and software included in the ELWIX and AITNET Releases is copyrighted by ELWIX - Sofia/Bulgaria Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 by Michael Pounov . All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by Michael Pounov ELWIX - Embedded LightWeight unIX and its contributors. 4. Neither the name of AITNET nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY AITNET AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "global.h" #include "mime.h" #include "tools.h" static int decode_quoted(char *, int, char *); static int decode_base64(char *, int, char *); static const char *n_encode[] = { "7bit", "8bit", "binary" }; static struct _tagEncode { char *name; float mul; int (*decode)(char *, int, char *); } encode[] = { { "quoted-printable", 1, decode_quoted }, { "base64", (float) 3 / 4, decode_base64 } }; static inline char * bd_begin(const char *str) { char *s; int len = strlen(str) + 6; s = malloc(len + 1); if (!s) { LOGERR; return NULL; } else { snprintf(s, len + 1, "\r\n--%s\r\n", str); s[len] = 0; } return s; } static inline char * bd_end(const char *str) { char *s; int len = strlen(str) + 8; s = malloc(len + 1); if (!s) { LOGERR; return NULL; } else { snprintf(s, len + 1, "\r\n--%s--\r\n", str); s[len] = 0; } return s; } static u_int powmod(int x, int y, int q) { u_int ret = 1; while (y) { if (y & 1) ret = ((unsigned long long)ret * x) % q; x = (unsigned long long) x * x % q; y = y / 2; } return ret; } static const char * findtextpos(const char *T, size_t tlen, const char *P, size_t plen) { const u_int q = 4294967291u; const u_int d = 256; u_int hash, p = 0, t = 0; register int i; hash = powmod(d, plen - 1, q); /* calculate initial hash tags */ for (i = 0; i < plen; i++) { p = (d * p + P[i]) % q; t = (d * t + T[i]) % q; } tlen -= plen; for (i = 0; i <= tlen; i++) { if (p == t) { /* match pattern */ if (!memcmp(P, T + i, plen)) return T + i; } /* rehashing */ if (i < tlen) t = (d * (t - T[i] * hash) + T[i + plen]) % q; } return NULL; } static inline void freeHeader(struct tagMIME * __restrict m) { struct tagCGI *c; while ((c = SLIST_FIRST(&m->mime_header))) { if (c->cgi_name) free(c->cgi_name); if (c->cgi_value) free(c->cgi_value); SLIST_REMOVE_HEAD(&m->mime_header, cgi_node); free(c); } } static char * hdrValue(const char *str, size_t len, const char **end) { const char *e, *crlf = NULL; char *tmp, *s = NULL; int off = 0; e = str + len; while (str < e) { if (!(crlf = findtextpos(str, e - str, CRLF, strlen(CRLF)))) { www_SetErr(EBADMSG, "Bad header format of MIME part"); return NULL; } tmp = realloc(s, crlf - str + off + 1); if (!tmp) { LOGERR; free(s); return NULL; } else s = tmp; memcpy(s + off, str, crlf - str); s[crlf - str + off] = 0; off += crlf - str; /* if is multi part header value */ tmp = (char*) crlf + strlen(CRLF); if (*tmp == ' ' || *tmp == '\t') str = ++tmp; else break; } *end = crlf + strlen(CRLF); return s; } static inline int hexdigit(char a) { if (a >= '0' && a <= '9') return a - '0'; if (a >= 'a' && a <= 'f') return a - 'a' + 10; if (a >= 'A' && a <= 'F') return a - 'A' + 10; /* error!!! */ return -1; } static int decode_quoted(char *in, int len, char *out) { register int i, cx; for (i = cx = 0; i < len; i++) if (in[i] == '=') { /* special handling */ i++; if ((in[i] >= '0' && in[i] <= '9') || (in[i] >= 'A' && in[i] <= 'F') || (in[i] >= 'a' && in[i] <= 'f')) { /* encoding a special char */ *out++ = hexdigit(in[i]) << 4 | hexdigit(in[i+ 1]); cx++; } else i += strlen(CRLF); } else { *out++ = in[i++]; cx++; } return cx; } static int decode_base64(char *in, int len, char *out) { register int cx, i, j; int bits, eqc; for (cx = i = eqc = bits = 0; i < len && !eqc; bits = 0) { for (j = 0; i < len && j < 4; i++) { switch (in[i]) { case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': bits = (bits << 6) | (in[i] - 'A'); j++; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': bits = (bits << 6) | (in[i] - 'a' + 26); j++; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': bits = (bits << 6) | (in[i] - '0' + 52); j++; break; case '+': bits = (bits << 6) | 62; j++; break; case '/': bits = (bits << 6) | 63; j++; break; case '=': bits <<= 6; j++; eqc++; break; default: break; } } if (!j && i >= len) continue; switch (eqc) { case 0: *out++ = (bits >> 16) & 0xff; *out++ = (bits >> 8) & 0xff; *out++ = bits & 0xff; cx += 3; break; case 1: *out++ = (bits >> 16) & 0xff; *out++ = (bits >> 8) & 0xff; cx += 2; break; case 2: *out++ = (bits >> 16) & 0xff; cx += 1; break; } } return cx; } /* ------------------------------------------------------------------ */ /* * mime_parseMultiPart() - Parse multi part MIME message * * @str = String * @len = String length * @bd = Boundary tag * @end = End of parsed part * return: NULL error or !=NULL allocated MIME session */ mime_t * mime_parseMultiPart(const char *str, size_t len, const char *bdtag, const char **end) { mime_t *mime = NULL; struct iovec bd[2]; struct tagMIME *m, *old = NULL; const char *next = NULL; if (!str | !bdtag) { www_SetErr(EINVAL, "String or boundary tag is NULL"); return NULL; } /* init MIME */ mime = malloc(sizeof(mime_t)); if (!mime) { LOGERR; return NULL; } else { memset(mime, 0, sizeof(mime_t)); SLIST_INIT(mime); } /* prepare boundary format */ bd[0].iov_base = bd_begin(bdtag); if (!bd[0].iov_base) { free(mime); return NULL; } else bd[0].iov_len = strlen(bd[0].iov_base); bd[1].iov_base = bd_end(bdtag); if (!bd[1].iov_base) { free(bd[0].iov_base); free(mime); return NULL; } else bd[1].iov_len = strlen(bd[1].iov_base); /* check boundary tag */ if (memcmp(str, strstr(bd[0].iov_base, "--"), strlen(strstr(bd[0].iov_base, "--")))) { www_SetErr(EBADMSG, "Bad content data, not found boundary tag"); free(bd[1].iov_base); free(bd[0].iov_base); free(mime); return NULL; } else { str += strlen(strstr(bd[0].iov_base, "--")); len -= strlen(strstr(bd[0].iov_base, "--")); } while (len > 0) { m = malloc(sizeof(struct tagMIME)); if (!m) { LOGERR; mime_close(&mime); free(bd[1].iov_base); free(bd[0].iov_base); return NULL; } else { memset(m, 0, sizeof(struct tagMIME)); SLIST_INIT(&m->mime_header); } if (!(next = findtextpos(str, len, bd[0].iov_base, bd[0].iov_len))) next = findtextpos(str, len, bd[1].iov_base, bd[1].iov_len); /* parse message between tags */ if (mime_readPart(m, str, next - str)) { mime_close(&mime); free(bd[1].iov_base); free(bd[0].iov_base); return NULL; } str += next - str; len -= next - str; /* add to mime session */ if (!old) SLIST_INSERT_HEAD(mime, m, mime_node); else SLIST_INSERT_AFTER(old, m, mime_node); old = m; /* match part termination tag */ if (!memcmp(str, bd[1].iov_base, bd[1].iov_len)) break; str += bd[0].iov_len; len -= bd[0].iov_len; } str += bd[0].iov_len; /* LLVM static code analyzer said for this - unusable * len -= bd[0].iov_len; */ free(bd[1].iov_base); free(bd[0].iov_base); if (end) *end = str; return mime; } static inline void freeMIME(struct tagMIME * __restrict m) { if (m->mime_body.iov_base) free(m->mime_body.iov_base); if (m->mime_prolog.iov_base) free(m->mime_prolog.iov_base); if (m->mime_epilog.iov_base) free(m->mime_epilog.iov_base); freeHeader(m); mime_close(&m->mime_attach); } /* * mime_close() - Close MIME session and free all resources * * @mime = Inited mime session * return: none */ void mime_close(mime_t ** __restrict mime) { struct tagMIME *m; if (!mime || !*mime) return; while ((m = SLIST_FIRST(*mime))) { SLIST_REMOVE_HEAD(*mime, mime_node); freeMIME(m); free(m); } free(*mime); *mime = NULL; } /* * mime_parseHeader() - Parse MIME header pairs * * @m = Mime part * @str = String * @len = String length * @end = End of parsed part * return: -1 error or 0 ok */ int mime_parseHeader(struct tagMIME * __restrict m, const char *str, size_t len, const char **end) { const char *e, *colon, *eoh; struct tagCGI *c, *old = NULL; if (!m || !str) { www_SetErr(EINVAL, "Mime part or string is NULL"); return -1; } else e = str + len; while (str < e) { if (!memcmp(str, CRLF, strlen(CRLF))) { str += 2; break; } colon = memchr(str, ':', e - str); eoh = findtextpos(str, e - str, CRLF, strlen(CRLF)); if (!colon || !eoh || colon > eoh) { www_SetErr(EBADMSG, "Bad MIME format message"); freeHeader(m); return -1; } c = malloc(sizeof(struct tagCGI)); if (!c) { LOGERR; freeHeader(m); return -1; } /* get name */ c->cgi_name = malloc(colon - str + 1); if (!c->cgi_name) { LOGERR; free(c); freeHeader(m); return -1; } else { memcpy(c->cgi_name, str, colon - str); c->cgi_name[colon - str] = 0; } /* get value */ c->cgi_value = hdrValue(colon + 1, e - colon - 1, &str); if (!c->cgi_value) { free(c->cgi_name); free(c); freeHeader(m); return -1; } if (!old) SLIST_INSERT_HEAD(&m->mime_header, c, cgi_node); else SLIST_INSERT_AFTER(old, c, cgi_node); old = c; } if (end) *end = str; return 0; } /* * mime_getValue() - Get value from MIME header * * @m = Mime part * @name = Header name * return: NULL not found or !=NULL value */ inline const char * mime_getValue(struct tagMIME * __restrict m, const char *name) { struct tagCGI *c; const char *v = NULL; SLIST_FOREACH(c, &m->mime_header, cgi_node) if (!strcasecmp(c->cgi_name, name)) { v = c->cgi_value; break; } return v; } /* * mime_readPart() Read and parse MIME part * * @m = Mime part * @str = String * @len = String length * return: -1 error or 0 ok */ int mime_readPart(struct tagMIME * __restrict m, const char *str, size_t len) { const char *eoh, *ct, *eb; cgi_t *attr; struct iovec bd; if (!m || !str || (ssize_t) len < 0) { www_SetErr(EINVAL, "Mime part, string is NULL or length is less 0"); return -1; } if (mime_parseHeader(m, str, len, &eoh)) return -1; ct = mime_getValue(m, "content-type"); if (!ct || www_cmptype(ct, "multipart")) { /* not multi part, assign like body element */ m->mime_body.iov_base = malloc(len - (eoh - str) + 1); if (!m->mime_body.iov_base) { LOGERR; freeHeader(m); return -1; } memcpy(m->mime_body.iov_base, eoh, len - (eoh - str)); ((char*) m->mime_body.iov_base)[len - (eoh - str)] = 0; m->mime_body.iov_len = len - (eoh - str) + 1; } else { /* multi part */ attr = www_parseAttributes(&ct); if (!attr) { freeHeader(m); return -1; } bd.iov_base = bd_begin(www_getAttribute(attr, "boundary")); bd.iov_len = strlen(bd.iov_base); eb = findtextpos(eoh, len - (eoh - str), bd.iov_base, bd.iov_len); free(bd.iov_base); /* set prolog if exists */ if (eb != eoh) { m->mime_prolog.iov_base = malloc(eb - eoh + 1); if (!m->mime_prolog.iov_base) { LOGERR; www_freeAttributes(&attr); freeHeader(m); return -1; } memcpy(m->mime_prolog.iov_base, eoh, eb - eoh); ((char*) m->mime_prolog.iov_base)[eb - eoh] = 0; m->mime_prolog.iov_len = eb - eoh + 1; } m->mime_attach = mime_parseMultiPart(eb + 1, len - (eb + 1 - str), www_getAttribute(attr, "boundary"), &eoh); /* set epilog if exists */ if (eoh - str < len) { m->mime_epilog.iov_base = malloc(len - (eoh - str) + 1); if (!m->mime_epilog.iov_base) { LOGERR; www_freeAttributes(&attr); freeHeader(m); return -1; } memcpy(m->mime_epilog.iov_base, str, len - (eoh - str)); ((char*) m->mime_epilog.iov_base)[len - (eoh - str)] = 0; m->mime_epilog.iov_len = len - (eoh - str) + 1; } www_freeAttributes(&attr); } return 0; } /* * mime_calcRawSize() - Calculate estimated memory for data from parsed MIME part * * @m = Mime part * return: -1 error or >-1 data size in mime part */ int mime_calcRawSize(struct tagMIME * __restrict m) { const char *s; char *t; int len; register int i; if (!m) { www_SetErr(EINVAL, "Mime part is NULL"); return -1; } /* no body */ if (m->mime_body.iov_len < 1) return 0; s = mime_getValue(m, "content-transfer-encoding"); if (!s) return m->mime_body.iov_len; /* strip whitespaces */ while (isspace(*s)) s++; t = strchr(s, ';'); len = t ? strlen(s) : t - s; /* find proper encoding */ for (i = 0; i < sizeof n_encode / sizeof *n_encode; i++) if (len == strlen(n_encode[i]) && !strncasecmp(s, n_encode[i], len)) return m->mime_body.iov_len; for (i = 0; i < sizeof encode / sizeof *encode; i++) if (len == strlen(encode[i].name) && !strncasecmp(s, encode[i].name, len)) return m->mime_body.iov_len * encode[i].mul; /* fail */ return -1; } /* * mime_getRawData() - Get ready parsed data from MIME part body * * @m = Mime part * @str = output data buffer * @len = output data buffer length * return: -1 error or >-1 data length in output buffer */ int mime_getRawData(struct tagMIME * __restrict m, char * __restrict str, int slen) { const char *s; char *t; int len; register int i; if (!m || !str) { www_SetErr(EINVAL, "Mime part or string is NULL"); return -1; } /* no body */ if (m->mime_body.iov_len < 1) return 0; s = mime_getValue(m, "content-transfer-encoding"); if (!s) { memcpy(str, m->mime_body.iov_base, m->mime_body.iov_len > (slen - 1) ? slen - 1 : m->mime_body.iov_len); return m->mime_body.iov_len; } /* strip whitespaces */ while (isspace(*s)) s++; t = strchr(s, ';'); len = t ? strlen(s) : t - s; /* decoding body */ for (i = 0; i < sizeof encode / sizeof *encode; i++) if (len == strlen(encode[i].name) && !strncasecmp(s, encode[i].name, len)) return encode[i].decode(m->mime_body.iov_base, m->mime_body.iov_len, str); /* fail */ return -1; }