Annotation of libaitwww/src/mime.c, revision 1.1.1.1.2.3
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.3! misho 6: * $Id: mime.c,v 1.1.1.1.2.2 2012/03/09 15:34:37 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) {
1.1.1.1.2.3! misho 172: if (!(crlf = findtextpos(str, e - str, CRLF, strlen(CRLF)))) {
! 173: www_SetErr(EBADMSG, "Bad header format of MIME part");
1.1 misho 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:
1.1.1.1.2.3! misho 423: free(bd[1].iov_base);
! 424: free(bd[0].iov_base);
! 425:
1.1 misho 426: if (end)
427: *end = str;
428: return mime;
429: }
430:
431: static inline void
432: freeMIME(struct tagMIME * __restrict m)
433: {
434: if (m->mime_body.iov_base)
435: free(m->mime_body.iov_base);
436: if (m->mime_prolog.iov_base)
437: free(m->mime_prolog.iov_base);
438: if (m->mime_epilog.iov_base)
439: free(m->mime_epilog.iov_base);
440:
441: freeHeader(m);
442: mime_close(&m->mime_attach);
443: }
444:
445: /*
446: * mime_close() - Close MIME session and free all resources
447: *
448: * @mime = Inited mime session
449: * return: none
450: */
451: void
452: mime_close(mime_t ** __restrict mime)
453: {
454: struct tagMIME *m;
455:
456: if (!mime || !*mime)
457: return;
458:
459: while ((m = SLIST_FIRST(*mime))) {
460: SLIST_REMOVE_HEAD(*mime, mime_node);
461: freeMIME(m);
462: free(m);
463: }
464:
465: free(*mime);
466: *mime = NULL;
467: }
468:
469: /*
470: * mime_parseHeader() - Parse MIME header pairs
471: *
472: * @m = Mime part
473: * @str = String
474: * @len = String length
475: * @end = End of parsed part
476: * return: -1 error or 0 ok
477: */
478: int
479: mime_parseHeader(struct tagMIME * __restrict m, const char *str, size_t len, const char **end)
480: {
481: const char *e, *colon, *eoh;
482: struct tagCGI *c, *old = NULL;
483:
484: if (!m || !str) {
485: www_SetErr(EINVAL, "Mime part or string is NULL");
486: return -1;
487: } else
488: e = str + len;
489:
490: while (str < e) {
491: if (!memcmp(str, CRLF, strlen(CRLF))) {
492: str += 2;
493: break;
494: }
495:
496: colon = memchr(str, ':', e - str);
497: eoh = findtextpos(str, e - str, CRLF, strlen(CRLF));
498: if (!colon || !eoh || colon > eoh) {
499: www_SetErr(EBADMSG, "Bad MIME format message");
500: freeHeader(m);
501: return -1;
502: }
503:
504: c = malloc(sizeof(struct tagCGI));
505: if (!c) {
506: LOGERR;
507: freeHeader(m);
508: return -1;
509: }
510: /* get name */
511: c->cgi_name = malloc(colon - str + 1);
512: if (!c->cgi_name) {
513: LOGERR;
514: free(c);
515: freeHeader(m);
516: return -1;
517: } else {
518: memcpy(c->cgi_name, str, colon - str);
519: c->cgi_name[colon - str] = 0;
520: }
521: /* get value */
522: c->cgi_value = hdrValue(colon + 1, e - colon - 1, &str);
1.1.1.1.2.2 misho 523: if (!c->cgi_value) {
524: free(c->cgi_name);
525: free(c);
526: freeHeader(m);
527: return -1;
528: }
1.1 misho 529:
530: if (!old)
531: SLIST_INSERT_HEAD(&m->mime_header, c, cgi_node);
532: else
533: SLIST_INSERT_AFTER(old, c, cgi_node);
534: old = c;
535: }
536:
537: if (end)
538: *end = str;
539: return 0;
540: }
541:
542: /*
543: * mime_getValue() - Get value from MIME header
544: *
545: * @m = Mime part
546: * @name = Header name
547: * return: NULL not found or !=NULL value
548: */
549: inline const char *
550: mime_getValue(struct tagMIME * __restrict m, const char *name)
551: {
552: struct tagCGI *c;
553: const char *v = NULL;
554:
555: SLIST_FOREACH(c, &m->mime_header, cgi_node)
1.1.1.1.2.2 misho 556: if (!strcasecmp(c->cgi_name, name)) {
1.1 misho 557: v = c->cgi_value;
558: break;
559: }
560: return v;
561: }
562:
563: /*
564: * mime_readPart() Read and parse MIME part
565: *
566: * @m = Mime part
567: * @str = String
568: * @len = String length
569: * return: -1 error or 0 ok
570: */
571: int
572: mime_readPart(struct tagMIME * __restrict m, const char *str, size_t len)
573: {
574: const char *eoh, *ct, *eb;
575: cgi_t *attr;
576: struct iovec bd;
577:
1.1.1.1.2.2 misho 578: if (!m || !str || (ssize_t) len < 0) {
579: www_SetErr(EINVAL, "Mime part, string is NULL or length is less 0");
1.1 misho 580: return -1;
581: }
582:
583: if (mime_parseHeader(m, str, len, &eoh))
584: return -1;
585:
586: ct = mime_getValue(m, "content-type");
587: if (!ct || www_cmptype(ct, "multipart")) {
588: /* not multi part, assign like body element */
589: m->mime_body.iov_base = malloc(len - (eoh - str) + 1);
1.1.1.1.2.3! misho 590: if (!m->mime_body.iov_base) {
! 591: LOGERR;
! 592: freeHeader(m);
! 593: return -1;
! 594: }
1.1 misho 595: memcpy(m->mime_body.iov_base, eoh, len - (eoh - str));
596: ((char*) m->mime_body.iov_base)[len - (eoh - str)] = 0;
597: m->mime_body.iov_len = len - (eoh - str) + 1;
598: } else {
599: /* multi part */
600: attr = www_parseAttributes(&ct);
1.1.1.1.2.3! misho 601: if (!attr) {
! 602: freeHeader(m);
1.1 misho 603: return -1;
1.1.1.1.2.3! misho 604: }
1.1 misho 605: bd.iov_base = bd_begin(www_getAttribute(attr, "boundary"));
606: bd.iov_len = strlen(bd.iov_base);
607: eb = findtextpos(eoh, len - (eoh - str), bd.iov_base, bd.iov_len);
608: free(bd.iov_base);
609:
610: /* set prolog if exists */
611: if (eb != eoh) {
612: m->mime_prolog.iov_base = malloc(eb - eoh + 1);
613: if (!m->mime_prolog.iov_base) {
614: LOGERR;
615: www_freeAttributes(&attr);
1.1.1.1.2.3! misho 616: freeHeader(m);
1.1 misho 617: return -1;
618: }
619: memcpy(m->mime_prolog.iov_base, eoh, eb - eoh);
620: ((char*) m->mime_prolog.iov_base)[eb - eoh] = 0;
621: m->mime_prolog.iov_len = eb - eoh + 1;
622: }
623:
624: m->mime_attach = mime_parseMultiPart(eb + 1, len - (eb + 1 - str),
625: www_getAttribute(attr, "boundary"), &eoh);
626:
627: /* set epilog if exists */
628: if (eoh - str < len) {
629: m->mime_epilog.iov_base = malloc(len - (eoh - str) + 1);
630: if (!m->mime_epilog.iov_base) {
631: LOGERR;
632: www_freeAttributes(&attr);
1.1.1.1.2.3! misho 633: freeHeader(m);
1.1 misho 634: return -1;
635: }
636: memcpy(m->mime_epilog.iov_base, str, len - (eoh - str));
637: ((char*) m->mime_epilog.iov_base)[len - (eoh - str)] = 0;
638: m->mime_epilog.iov_len = len - (eoh - str) + 1;
639:
640: }
1.1.1.1.2.3! misho 641:
! 642: www_freeAttributes(&attr);
1.1 misho 643: }
644:
645: return 0;
646: }
647:
648: /*
649: * mime_calcRawSize() - Calculate estimated memory for data from parsed MIME part
650: *
651: * @m = Mime part
652: * return: -1 error or >-1 data size in mime part
653: */
654: int
655: mime_calcRawSize(struct tagMIME * __restrict m)
656: {
657: const char *s;
658: char *t;
659: int len;
660: register int i;
661:
662: if (!m) {
663: www_SetErr(EINVAL, "Mime part is NULL");
664: return -1;
665: }
666:
667: /* no body */
668: if (m->mime_body.iov_len < 1)
669: return 0;
670:
671: s = mime_getValue(m, "content-transfer-encoding");
672: if (!s)
673: return m->mime_body.iov_len;
674: /* strip whitespaces */
675: while (isspace(*s))
676: s++;
677: t = strchr(s, ';');
678: len = t ? strlen(s) : t - s;
679:
680: /* find proper encoding */
681: for (i = 0; i < sizeof n_encode / sizeof *n_encode; i++)
682: if (len == strlen(n_encode[i]) && !strncasecmp(s, n_encode[i], len))
683: return m->mime_body.iov_len;
684:
685: for (i = 0; i < sizeof encode / sizeof *encode; i++)
686: if (len == strlen(encode[i].name) && !strncasecmp(s, encode[i].name, len))
687: return m->mime_body.iov_len * encode[i].mul;
688:
689: /* fail */
690: return -1;
691: }
692:
693: /*
694: * mime_getRawData() - Get ready parsed data from MIME part body
695: *
696: * @m = Mime part
697: * @str = output data buffer
698: * @len = output data buffer length
699: * return: -1 error or >-1 data length in output buffer
700: */
701: int
702: mime_getRawData(struct tagMIME * __restrict m, char * __restrict str, int slen)
703: {
704: const char *s;
705: char *t;
706: int len;
707: register int i;
708:
709: if (!m || !str) {
710: www_SetErr(EINVAL, "Mime part or string is NULL");
711: return -1;
712: }
713:
714: /* no body */
715: if (m->mime_body.iov_len < 1)
716: return 0;
717:
718: s = mime_getValue(m, "content-transfer-encoding");
719: if (!s) {
720: memcpy(str, m->mime_body.iov_base, m->mime_body.iov_len > (slen - 1) ?
721: slen - 1 : m->mime_body.iov_len);
722: return m->mime_body.iov_len;
723: }
724:
725: /* strip whitespaces */
726: while (isspace(*s))
727: s++;
728: t = strchr(s, ';');
729: len = t ? strlen(s) : t - s;
730:
731: /* decoding body */
732: for (i = 0; i < sizeof encode / sizeof *encode; i++)
733: if (len == strlen(encode[i].name) && !strncasecmp(s, encode[i].name, len))
734: return encode[i].decode(m->mime_body.iov_base,
735: m->mime_body.iov_len, str);
736:
737: /* fail */
738: return -1;
739: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>