Annotation of libaitwww/src/mime.c, revision 1.1.1.1.2.2
1.1 misho 1: /*************************************************************************
2: * (C) 2012 AITNET ltd - Sofia/Bulgaria - <misho@aitnet.org>
3: * by Michael Pounov <misho@elwix.org>
4: *
5: * $Author: misho $
1.1.1.1.2.2! misho 6: * $Id: mime.c,v 1.1.1.1.2.1 2012/03/09 09:38:55 misho Exp $
1.1 misho 7: *
8: **************************************************************************
9: The ELWIX and AITNET software is distributed under the following
10: terms:
11:
12: All of the documentation and software included in the ELWIX and AITNET
13: Releases is copyrighted by ELWIX - Sofia/Bulgaria <info@elwix.org>
14:
15: Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
16: by Michael Pounov <misho@elwix.org>. All rights reserved.
17:
18: Redistribution and use in source and binary forms, with or without
19: modification, are permitted provided that the following conditions
20: are met:
21: 1. Redistributions of source code must retain the above copyright
22: notice, this list of conditions and the following disclaimer.
23: 2. Redistributions in binary form must reproduce the above copyright
24: notice, this list of conditions and the following disclaimer in the
25: documentation and/or other materials provided with the distribution.
26: 3. All advertising materials mentioning features or use of this software
27: must display the following acknowledgement:
28: This product includes software developed by Michael Pounov <misho@elwix.org>
29: ELWIX - Embedded LightWeight unIX and its contributors.
30: 4. Neither the name of AITNET nor the names of its contributors
31: may be used to endorse or promote products derived from this software
32: without specific prior written permission.
33:
34: THIS SOFTWARE IS PROVIDED BY AITNET AND CONTRIBUTORS ``AS IS'' AND
35: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
36: IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
37: ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
38: FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
39: DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
40: OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
41: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
42: LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
43: OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44: SUCH DAMAGE.
45: */
46: #include "global.h"
47: #include "mime.h"
48: #include "tools.h"
49:
50:
51: static int decode_quoted(char *, int, char *);
52: static int decode_base64(char *, int, char *);
53:
54: static const char *n_encode[] = { "7bit", "8bit", "binary" };
55: static struct _tagEncode {
56: char *name;
57: float mul;
58:
59: int (*decode)(char *, int, char *);
60: } encode[] = {
61: { "quoted-printable", 1, decode_quoted },
62: { "base64", (float) 3 / 4, decode_base64 }
63: };
64:
65:
66: static inline char *
67: bd_begin(const char *str)
68: {
69: char *s;
70: int len = strlen(str) + 6;
71:
72: s = malloc(len + 1);
73: if (!s) {
74: LOGERR;
75: return NULL;
76: } else {
77: snprintf(s, len + 1, "\r\n--%s\r\n", str);
78: s[len] = 0;
79: }
80:
81: return s;
82: }
83:
84: static inline char *
85: bd_end(const char *str)
86: {
87: char *s;
88: int len = strlen(str) + 8;
89:
90: s = malloc(len + 1);
91: if (!s) {
92: LOGERR;
93: return NULL;
94: } else {
95: snprintf(s, len + 1, "\r\n--%s--\r\n", str);
96: s[len] = 0;
97: }
98:
99: return s;
100: }
101:
102: static u_int
103: powmod(int x, int y, int q)
104: {
105: u_int ret = 1;
106:
107: while (y) {
108: if (y & 1)
109: ret = ((unsigned long long)ret * x) % q;
110: x = (unsigned long long) x * x % q;
111: y = y / 2;
112: }
113: return ret;
114: }
115:
116: static const char *
117: findtextpos(const char *T, size_t tlen, const char *P, size_t plen)
118: {
119: const u_int q = 4294967291u;
120: const u_int d = 256;
121: u_int hash, p = 0, t = 0;
122: register int i;
123:
124: hash = powmod(d, plen - 1, q);
125:
126: /* calculate initial hash tags */
127: for (i = 0; i < plen; i++) {
128: p = (d * p + P[i]) % q;
129: t = (d * t + T[i]) % q;
130: }
131:
132: tlen -= plen;
133: for (i = 0; i <= tlen; i++) {
134: if (p == t) {
135: /* match pattern */
136: if (!memcmp(P, T + i, plen))
137: return T + i;
138: }
139:
140: /* rehashing */
141: if (i < tlen)
142: t = (d * (t - T[i] * hash) + T[i + plen]) % q;
143: }
144:
145: return NULL;
146: }
147:
148: static inline void
149: freeHeader(struct tagMIME * __restrict m)
150: {
151: struct tagCGI *c;
152:
153: while ((c = SLIST_FIRST(&m->mime_header))) {
154: if (c->cgi_name)
155: free(c->cgi_name);
156: if (c->cgi_value)
157: free(c->cgi_value);
158: SLIST_REMOVE_HEAD(&m->mime_header, cgi_node);
159: free(c);
160: }
161: }
162:
163: static char *
164: hdrValue(const char *str, size_t len, const char **end)
165: {
166: const char *e, *crlf = NULL;
167: char *tmp, *s = NULL;
168: int off = 0;
169:
170: e = str + len;
171: while (str < e) {
172: if ((crlf = findtextpos(str, e - str, CRLF, strlen(CRLF)))) {
173: www_SetErr(EBADMSG, "Bad MIME format message");
174: return NULL;
175: }
176:
177: tmp = realloc(s, crlf - str + off + 1);
178: if (!tmp) {
179: LOGERR;
180: free(s);
181: return NULL;
182: } else
183: s = tmp;
184:
185: memcpy(s + off, str, crlf - str);
186: s[crlf - str + off] = 0;
187: off += crlf - str;
188:
189: /* if is multi part header value */
190: tmp = (char*) crlf + strlen(CRLF);
191: if (*tmp == ' ' || *tmp == '\t')
192: str = ++tmp;
193: else
194: break;
195: }
196:
197: *end = crlf + strlen(CRLF);
198: return s;
199: }
200:
201: static inline int
202: hexdigit(char a)
203: {
204: if (a >= '0' && a <= '9')
205: return a - '0';
206: if (a >= 'a' && a <= 'f')
207: return a - 'a' + 10;
208: if (a >= 'A' && a <= 'F')
209: return a - 'A' + 10;
210: /* error!!! */
211: return -1;
212: }
213:
214: static int
215: decode_quoted(char *in, int len, char *out)
216: {
217: register int i, cx;
218:
219: for (i = cx = 0; i < len; i++)
220: if (in[i] == '=') {
221: /* special handling */
222: i++;
223: if ((in[i] >= '0' && in[i] <= '9') ||
224: (in[i] >= 'A' && in[i] <= 'F') ||
225: (in[i] >= 'a' && in[i] <= 'f')) {
226: /* encoding a special char */
227: *out++ = hexdigit(in[i]) << 4 | hexdigit(in[i+ 1]);
228: cx++;
229: } else
230: i += strlen(CRLF);
231: } else {
232: *out++ = in[i++];
233: cx++;
234: }
235:
236: return cx;
237: }
238:
239: static int
240: decode_base64(char *in, int len, char *out)
241: {
242: register int cx, i, j;
243: int bits, eqc;
244:
245: for (cx = i = eqc = bits = 0; i < len && !eqc; bits = 0) {
246: for (j = 0; i < len && j < 4; i++) {
247: switch (in[i]) {
248: case 'A': case 'B': case 'C': case 'D': case 'E':
249: case 'F': case 'G': case 'H': case 'I': case 'J':
250: case 'K': case 'L': case 'M': case 'N': case 'O':
251: case 'P': case 'Q': case 'R': case 'S': case 'T':
252: case 'U': case 'V': case 'W': case 'X': case 'Y':
253: case 'Z':
254: bits = (bits << 6) | (in[i] - 'A');
255: j++;
256: break;
257: case 'a': case 'b': case 'c': case 'd': case 'e':
258: case 'f': case 'g': case 'h': case 'i': case 'j':
259: case 'k': case 'l': case 'm': case 'n': case 'o':
260: case 'p': case 'q': case 'r': case 's': case 't':
261: case 'u': case 'v': case 'w': case 'x': case 'y':
262: case 'z':
263: bits = (bits << 6) | (in[i] - 'a' + 26);
264: j++;
265: break;
266: case '0': case '1': case '2': case '3': case '4':
267: case '5': case '6': case '7': case '8': case '9':
268: bits = (bits << 6) | (in[i] - '0' + 52);
269: j++;
270: break;
271: case '+':
272: bits = (bits << 6) | 62;
273: j++;
274: break;
275: case '/':
276: bits = (bits << 6) | 63;
277: j++;
278: break;
279: case '=':
280: bits <<= 6;
281: j++;
282: eqc++;
283: break;
284: default:
285: break;
286: }
287: }
288:
289: if (!j && i >= len)
290: continue;
291:
292: switch (eqc) {
293: case 0:
294: *out++ = (bits >> 16) & 0xff;
295: *out++ = (bits >> 8) & 0xff;
296: *out++ = bits & 0xff;
297: cx += 3;
298: break;
299: case 1:
300: *out++ = (bits >> 16) & 0xff;
301: *out++ = (bits >> 8) & 0xff;
302: cx += 2;
303: break;
304: case 2:
305: *out++ = (bits >> 16) & 0xff;
306: cx += 1;
307: break;
308: }
309: }
310:
311: return cx;
312: }
313:
314: /* ------------------------------------------------------------------ */
315:
316: /*
317: * mime_parseMultiPart() - Parse multi part MIME message
318: *
319: * @str = String
320: * @len = String length
321: * @bd = Boundary tag
322: * @end = End of parsed part
323: * return: NULL error or !=NULL allocated MIME session
324: */
325: mime_t *
326: mime_parseMultiPart(const char *str, size_t len, const char *bdtag, const char **end)
327: {
328: mime_t *mime = NULL;
329: struct iovec bd[2];
330: struct tagMIME *m, *old = NULL;
331: const char *next;
332:
333: if (!str | !bdtag) {
334: www_SetErr(EINVAL, "String or boundary tag is NULL");
335: return NULL;
336: }
337:
338: /* init MIME */
339: mime = malloc(sizeof(mime_t));
340: if (!mime) {
341: LOGERR;
342: return NULL;
343: } else {
344: memset(mime, 0, sizeof(mime_t));
345: SLIST_INIT(mime);
346: }
347:
348: /* prepare boundary format */
349: bd[0].iov_base = bd_begin(bdtag);
350: if (!bd[0].iov_base) {
351: free(mime);
352: return NULL;
353: } else
354: bd[0].iov_len = strlen(bd[0].iov_base);
355: bd[1].iov_base = bd_end(bdtag);
356: if (!bd[1].iov_base) {
357: free(bd[0].iov_base);
358: free(mime);
359: return NULL;
360: } else
361: bd[1].iov_len = strlen(bd[1].iov_base);
1.1.1.1.2.2! misho 362:
! 363: /* check boundary tag */
1.1 misho 364: if (memcmp(str, strstr(bd[0].iov_base, "--"), strlen(strstr(bd[0].iov_base, "--")))) {
365: www_SetErr(EBADMSG, "Bad content data, not found boundary tag");
366: free(bd[1].iov_base);
367: free(bd[0].iov_base);
368: free(mime);
369: return NULL;
370: } else {
371: str += strlen(strstr(bd[0].iov_base, "--"));
372: len -= strlen(strstr(bd[0].iov_base, "--"));
373: }
374:
375: while (42) {
376: m = malloc(sizeof(struct tagMIME));
377: if (!m) {
378: LOGERR;
379: mime_close(&mime);
380: free(bd[1].iov_base);
381: free(bd[0].iov_base);
382: return NULL;
383: } else {
384: memset(m, 0, sizeof(struct tagMIME));
385: SLIST_INIT(&m->mime_header);
386: }
387:
388: if (!(next = findtextpos(str, len, bd[0].iov_base, bd[0].iov_len)))
389: next = findtextpos(str, len, bd[1].iov_base, bd[1].iov_len);
390:
391: /* parse message between tags */
392: if (mime_readPart(m, str, next - str)) {
393: mime_close(&mime);
394: free(bd[1].iov_base);
395: free(bd[0].iov_base);
396: return NULL;
397: }
398:
399: str += next - str;
400: len -= next - str;
401:
402: /* add to mime session */
403: if (!old)
404: SLIST_INSERT_HEAD(mime, m, mime_node);
405: else
406: SLIST_INSERT_AFTER(old, m, mime_node);
407: old = m;
408:
409: /* match part termination tag */
410: if (!memcmp(str, bd[1].iov_base, bd[1].iov_len))
411: break;
412:
413: str += bd[0].iov_len;
414: len -= bd[0].iov_len;
415: }
416:
417: str += bd[0].iov_len;
1.1.1.1.2.1 misho 418: /* LLVM static code analyzer said for this - unusable
419: *
1.1 misho 420: len -= bd[0].iov_len;
1.1.1.1.2.1 misho 421: */
1.1 misho 422:
423: if (end)
424: *end = str;
425: return mime;
426: }
427:
428: static inline void
429: freeMIME(struct tagMIME * __restrict m)
430: {
431: if (m->mime_body.iov_base)
432: free(m->mime_body.iov_base);
433: if (m->mime_prolog.iov_base)
434: free(m->mime_prolog.iov_base);
435: if (m->mime_epilog.iov_base)
436: free(m->mime_epilog.iov_base);
437:
438: freeHeader(m);
439: mime_close(&m->mime_attach);
440: }
441:
442: /*
443: * mime_close() - Close MIME session and free all resources
444: *
445: * @mime = Inited mime session
446: * return: none
447: */
448: void
449: mime_close(mime_t ** __restrict mime)
450: {
451: struct tagMIME *m;
452:
453: if (!mime || !*mime)
454: return;
455:
456: while ((m = SLIST_FIRST(*mime))) {
457: SLIST_REMOVE_HEAD(*mime, mime_node);
458: freeMIME(m);
459: free(m);
460: }
461:
462: free(*mime);
463: *mime = NULL;
464: }
465:
466: /*
467: * mime_parseHeader() - Parse MIME header pairs
468: *
469: * @m = Mime part
470: * @str = String
471: * @len = String length
472: * @end = End of parsed part
473: * return: -1 error or 0 ok
474: */
475: int
476: mime_parseHeader(struct tagMIME * __restrict m, const char *str, size_t len, const char **end)
477: {
478: const char *e, *colon, *eoh;
479: struct tagCGI *c, *old = NULL;
480:
481: if (!m || !str) {
482: www_SetErr(EINVAL, "Mime part or string is NULL");
483: return -1;
484: } else
485: e = str + len;
486:
487: while (str < e) {
488: if (!memcmp(str, CRLF, strlen(CRLF))) {
489: str += 2;
490: break;
491: }
492:
493: colon = memchr(str, ':', e - str);
494: eoh = findtextpos(str, e - str, CRLF, strlen(CRLF));
495: if (!colon || !eoh || colon > eoh) {
496: www_SetErr(EBADMSG, "Bad MIME format message");
497: freeHeader(m);
498: return -1;
499: }
500:
501: c = malloc(sizeof(struct tagCGI));
502: if (!c) {
503: LOGERR;
504: freeHeader(m);
505: return -1;
506: }
507: /* get name */
508: c->cgi_name = malloc(colon - str + 1);
509: if (!c->cgi_name) {
510: LOGERR;
511: free(c);
512: freeHeader(m);
513: return -1;
514: } else {
515: memcpy(c->cgi_name, str, colon - str);
516: c->cgi_name[colon - str] = 0;
517: }
518: /* get value */
519: c->cgi_value = hdrValue(colon + 1, e - colon - 1, &str);
1.1.1.1.2.2! misho 520: if (!c->cgi_value) {
! 521: free(c->cgi_name);
! 522: free(c);
! 523: freeHeader(m);
! 524: return -1;
! 525: }
1.1 misho 526:
527: if (!old)
528: SLIST_INSERT_HEAD(&m->mime_header, c, cgi_node);
529: else
530: SLIST_INSERT_AFTER(old, c, cgi_node);
531: old = c;
532: }
533:
534: if (end)
535: *end = str;
536: return 0;
537: }
538:
539: /*
540: * mime_getValue() - Get value from MIME header
541: *
542: * @m = Mime part
543: * @name = Header name
544: * return: NULL not found or !=NULL value
545: */
546: inline const char *
547: mime_getValue(struct tagMIME * __restrict m, const char *name)
548: {
549: struct tagCGI *c;
550: const char *v = NULL;
551:
552: SLIST_FOREACH(c, &m->mime_header, cgi_node)
1.1.1.1.2.2! misho 553: if (!strcasecmp(c->cgi_name, name)) {
1.1 misho 554: v = c->cgi_value;
555: break;
556: }
557: return v;
558: }
559:
560: /*
561: * mime_readPart() Read and parse MIME part
562: *
563: * @m = Mime part
564: * @str = String
565: * @len = String length
566: * return: -1 error or 0 ok
567: */
568: int
569: mime_readPart(struct tagMIME * __restrict m, const char *str, size_t len)
570: {
571: const char *eoh, *ct, *eb;
572: cgi_t *attr;
573: struct iovec bd;
574:
1.1.1.1.2.2! misho 575: if (!m || !str || (ssize_t) len < 0) {
! 576: www_SetErr(EINVAL, "Mime part, string is NULL or length is less 0");
1.1 misho 577: return -1;
578: }
579:
580: if (mime_parseHeader(m, str, len, &eoh))
581: return -1;
582:
583: ct = mime_getValue(m, "content-type");
584: if (!ct || www_cmptype(ct, "multipart")) {
585: /* not multi part, assign like body element */
586: m->mime_body.iov_base = malloc(len - (eoh - str) + 1);
587: memcpy(m->mime_body.iov_base, eoh, len - (eoh - str));
588: ((char*) m->mime_body.iov_base)[len - (eoh - str)] = 0;
589: m->mime_body.iov_len = len - (eoh - str) + 1;
590: } else {
591: /* multi part */
592: attr = www_parseAttributes(&ct);
593: if (!attr)
594: return -1;
595: bd.iov_base = bd_begin(www_getAttribute(attr, "boundary"));
596: bd.iov_len = strlen(bd.iov_base);
597: eb = findtextpos(eoh, len - (eoh - str), bd.iov_base, bd.iov_len);
598: free(bd.iov_base);
599:
600: /* set prolog if exists */
601: if (eb != eoh) {
602: m->mime_prolog.iov_base = malloc(eb - eoh + 1);
603: if (!m->mime_prolog.iov_base) {
604: LOGERR;
605: www_freeAttributes(&attr);
606: return -1;
607: }
608: memcpy(m->mime_prolog.iov_base, eoh, eb - eoh);
609: ((char*) m->mime_prolog.iov_base)[eb - eoh] = 0;
610: m->mime_prolog.iov_len = eb - eoh + 1;
611: }
612:
613: m->mime_attach = mime_parseMultiPart(eb + 1, len - (eb + 1 - str),
614: www_getAttribute(attr, "boundary"), &eoh);
615:
616: /* set epilog if exists */
617: if (eoh - str < len) {
618: m->mime_epilog.iov_base = malloc(len - (eoh - str) + 1);
619: if (!m->mime_epilog.iov_base) {
620: LOGERR;
621: www_freeAttributes(&attr);
622: return -1;
623: }
624: memcpy(m->mime_epilog.iov_base, str, len - (eoh - str));
625: ((char*) m->mime_epilog.iov_base)[len - (eoh - str)] = 0;
626: m->mime_epilog.iov_len = len - (eoh - str) + 1;
627:
628: }
629: }
630:
631: www_freeAttributes(&attr);
632: return 0;
633: }
634:
635: /*
636: * mime_calcRawSize() - Calculate estimated memory for data from parsed MIME part
637: *
638: * @m = Mime part
639: * return: -1 error or >-1 data size in mime part
640: */
641: int
642: mime_calcRawSize(struct tagMIME * __restrict m)
643: {
644: const char *s;
645: char *t;
646: int len;
647: register int i;
648:
649: if (!m) {
650: www_SetErr(EINVAL, "Mime part is NULL");
651: return -1;
652: }
653:
654: /* no body */
655: if (m->mime_body.iov_len < 1)
656: return 0;
657:
658: s = mime_getValue(m, "content-transfer-encoding");
659: if (!s)
660: return m->mime_body.iov_len;
661: /* strip whitespaces */
662: while (isspace(*s))
663: s++;
664: t = strchr(s, ';');
665: len = t ? strlen(s) : t - s;
666:
667: /* find proper encoding */
668: for (i = 0; i < sizeof n_encode / sizeof *n_encode; i++)
669: if (len == strlen(n_encode[i]) && !strncasecmp(s, n_encode[i], len))
670: return m->mime_body.iov_len;
671:
672: for (i = 0; i < sizeof encode / sizeof *encode; i++)
673: if (len == strlen(encode[i].name) && !strncasecmp(s, encode[i].name, len))
674: return m->mime_body.iov_len * encode[i].mul;
675:
676: /* fail */
677: return -1;
678: }
679:
680: /*
681: * mime_getRawData() - Get ready parsed data from MIME part body
682: *
683: * @m = Mime part
684: * @str = output data buffer
685: * @len = output data buffer length
686: * return: -1 error or >-1 data length in output buffer
687: */
688: int
689: mime_getRawData(struct tagMIME * __restrict m, char * __restrict str, int slen)
690: {
691: const char *s;
692: char *t;
693: int len;
694: register int i;
695:
696: if (!m || !str) {
697: www_SetErr(EINVAL, "Mime part or string is NULL");
698: return -1;
699: }
700:
701: /* no body */
702: if (m->mime_body.iov_len < 1)
703: return 0;
704:
705: s = mime_getValue(m, "content-transfer-encoding");
706: if (!s) {
707: memcpy(str, m->mime_body.iov_base, m->mime_body.iov_len > (slen - 1) ?
708: slen - 1 : m->mime_body.iov_len);
709: return m->mime_body.iov_len;
710: }
711:
712: /* strip whitespaces */
713: while (isspace(*s))
714: s++;
715: t = strchr(s, ';');
716: len = t ? strlen(s) : t - s;
717:
718: /* decoding body */
719: for (i = 0; i < sizeof encode / sizeof *encode; i++)
720: if (len == strlen(encode[i].name) && !strncasecmp(s, encode[i].name, len))
721: return encode[i].decode(m->mime_body.iov_base,
722: m->mime_body.iov_len, str);
723:
724: /* fail */
725: return -1;
726: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>