1 /* ====================================================================
2 * The Kannel Software License, Version 1.0
3 *
4 * Copyright (c) 2001-2014 Kannel Group
5 * Copyright (c) 1998-2001 WapIT Ltd.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution,
21 * if any, must include the following acknowledgment:
22 * "This product includes software developed by the
23 * Kannel Group (http://www.kannel.org/)."
24 * Alternately, this acknowledgment may appear in the software itself,
25 * if and wherever such third-party acknowledgments normally appear.
26 *
27 * 4. The names "Kannel" and "Kannel Group" must not be used to
28 * endorse or promote products derived from this software without
29 * prior written permission. For written permission, please
30 * contact org@kannel.org.
31 *
32 * 5. Products derived from this software may not be called "Kannel",
33 * nor may "Kannel" appear in their name, without prior written
34 * permission of the Kannel Group.
35 *
36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS
40 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
41 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
42 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
43 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
44 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
45 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
46 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47 * ====================================================================
48 *
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Kannel Group. For more information on
51 * the Kannel Group, please see <http://www.kannel.org/>.
52 *
53 * Portions of this software are based upon software originally written at
54 * WapIT Ltd., Helsinki, Finland for the Kannel project.
55 */
56
57 /*
58 * mime.c - Implement MIME multipart/related handling
59 *
60 * References:
61 * RFC 2387 (The MIME Multipart/Related Content-type)
62 * RFC 2025 (Multipurpose Internet Mail Extensions [MIME])
63 *
64 * See gwlib/mime.h for more details on the implementation.
65 *
66 * Stipe Tolj <stolj@kannel.org>
67 */
68
69 #include <string.h>
70 #include <limits.h>
71 #include <ctype.h>
72
73 #include "gwlib/gwlib.h"
74 #include "gwlib/mime.h"
75
76 struct MIMEEntity {
77 List *headers;
78 List *multiparts;
79 Octstr *body;
80 struct MIMEEntity *start; /* in case multipart/related */
81 };
82
83 /********************************************************************
84 * Creation and destruction routines.
85 */
86
mime_entity_create(void)87 MIMEEntity *mime_entity_create(void)
88 {
89 MIMEEntity *e;
90
91 e = gw_malloc(sizeof(MIMEEntity));
92 e->headers = http_create_empty_headers();
93 e->multiparts = gwlist_create();
94 e->body = NULL;
95 e->start = NULL;
96
97 return e;
98 }
99
mime_entity_destroy_item(void * e)100 void static mime_entity_destroy_item(void *e)
101 {
102 mime_entity_destroy(e);
103 }
104
mime_entity_destroy(MIMEEntity * e)105 void mime_entity_destroy(MIMEEntity *e)
106 {
107 gw_assert(e != NULL);
108
109 if (e->headers != NULL)
110 gwlist_destroy(e->headers, octstr_destroy_item);
111 if (e->multiparts != NULL)
112 gwlist_destroy(e->multiparts, mime_entity_destroy_item);
113 octstr_destroy(e->body);
114 e->start = NULL; /* will be destroyed on it's own via gwlist_destroy */
115
116 gw_free(e);
117 }
118
119
120
121 /********************************************************************
122 * Helper routines. Some are derived from gwlib/http.[ch]
123 */
124
125 /*
126 * Read some headers, i.e., until the first empty line (read and discard
127 * the empty line as well). Return -1 for error, 0 for all headers read.
128 */
read_mime_headers(ParseContext * context,List * headers)129 static int read_mime_headers(ParseContext *context, List *headers)
130 {
131 Octstr *line, *prev;
132
133 if (gwlist_len(headers) == 0)
134 prev = NULL;
135 else
136 prev = gwlist_get(headers, gwlist_len(headers) - 1);
137
138 for (;;) {
139 line = parse_get_line(context);
140 if (line == NULL) {
141 return -1;
142 }
143 if (octstr_len(line) == 0) {
144 octstr_destroy(line);
145 break;
146 }
147 if (isspace(octstr_get_char(line, 0)) && prev != NULL) {
148 octstr_append(prev, line);
149 octstr_destroy(line);
150 } else {
151 gwlist_append(headers, line);
152 prev = line;
153 }
154 }
155
156 return 0;
157 }
158
159 /* This function checks that there is a boundary parameter in the headers
160 * for a multipart MIME object. If not, it is inserted and passed back to caller
161 * in the variable boundary_elem.
162 */
fix_boundary_element(List * headers,Octstr ** boundary_elem)163 static void fix_boundary_element(List *headers, Octstr **boundary_elem)
164 {
165 Octstr *value, *boundary;
166 long len;
167
168 /*
169 * Check if we have an boundary parameter already in the
170 * Content-Type header. If no, add one, otherwise parse which one
171 * we should use.
172 * XXX this can be abstracted as function in gwlib/http.[ch].
173 */
174 value = http_header_value(headers, octstr_imm("Content-Type"));
175 boundary = value ? http_get_header_parameter(value, octstr_imm("boundary")) : NULL;
176
177 if (value == NULL) {
178 /* we got here because it is multi-part, so... */
179 value = octstr_create("multipart/mixed");
180 http_header_add(headers, "Content-Type", "multipart/mixed");
181 }
182 if (boundary == NULL) {
183 Octstr *v;
184 boundary = octstr_format("_boundary_%d_%ld_%c_%c_bd%d",
185 random(), (long) time(NULL), 'A' + (random() % 26),
186 'a' + (random() % 26), random());
187
188 octstr_format_append(value, "; boundary=%S", boundary);
189
190 http_header_remove_all(headers, "Content-Type");
191 http_header_add(headers, "Content-Type", octstr_get_cstr(value));
192 if ((v = http_header_value(headers, octstr_imm("MIME-Version"))) == NULL)
193 http_header_add(headers, "MIME-Version", "1.0");
194 else
195 octstr_destroy(v);
196 }
197 else if ((len = octstr_len(boundary)) > 0 &&
198 octstr_get_char(boundary, 0) == '"' &&
199 octstr_get_char(boundary, len - 1) == '"') {
200 octstr_delete(boundary, 0, 1);
201 octstr_delete(boundary, len - 2, 1);
202 }
203
204 octstr_destroy(value);
205 if (boundary_elem)
206 *boundary_elem = boundary;
207 else
208 octstr_destroy(boundary);
209 }
210
211
212 /********************************************************************
213 * Mapping function from other data types, mainly Octstr and HTTP.
214 */
215
mime_entity_to_octstr(MIMEEntity * m)216 Octstr *mime_entity_to_octstr(MIMEEntity *m)
217 {
218 Octstr *mime, *boundary = NULL;
219 List *headers;
220 long i;
221
222 gw_assert(m != NULL && m->headers != NULL);
223
224 mime = octstr_create("");
225
226 /*
227 * First of all check if we have further MIME entity dependencies,
228 * which means we have further MIMEEntities in our m->multiparts
229 * list. If no, then add headers and body and return. This is the
230 * easy case. Otherwise we have to loop inside our entities.
231 */
232 if (gwlist_len(m->multiparts) == 0) {
233 for (i = 0; i < gwlist_len(m->headers); i++) {
234 octstr_append(mime, gwlist_get(m->headers, i));
235 octstr_append(mime, octstr_imm("\r\n"));
236 }
237 octstr_append(mime, octstr_imm("\r\n"));
238 if (m->body != NULL)
239 octstr_append(mime, m->body);
240 goto finished;
241 }
242
243 /* This call ensures boundary exists, and returns it */
244 fix_boundary_element(m->headers, &boundary);
245 headers = http_header_duplicate(m->headers);
246
247 /* headers */
248 for (i = 0; i < gwlist_len(headers); i++) {
249 octstr_append(mime, gwlist_get(headers, i));
250 octstr_append(mime, octstr_imm("\r\n"));
251 }
252 http_destroy_headers(headers);
253 octstr_append(mime, octstr_imm("\r\n")); /* Mark end of headers. */
254
255 /* loop through all MIME multipart entities of this entity */
256 for (i = 0; i < gwlist_len(m->multiparts); i++) {
257 MIMEEntity *e = gwlist_get(m->multiparts, i);
258 Octstr *body;
259
260 octstr_append(mime, octstr_imm("\r\n--"));
261 octstr_append(mime, boundary);
262 octstr_append(mime, octstr_imm("\r\n"));
263
264 /* call ourself to produce the MIME entity body */
265 body = mime_entity_to_octstr(e);
266 octstr_append(mime, body);
267
268 octstr_destroy(body);
269 }
270
271 octstr_append(mime, octstr_imm("\r\n--"));
272 octstr_append(mime, boundary);
273 octstr_append(mime, octstr_imm("--\r\n"));
274
275 octstr_destroy(boundary);
276
277 finished:
278
279 return mime;
280 }
281
get_start_param(Octstr * content_type)282 static Octstr *get_start_param(Octstr *content_type)
283 {
284 Octstr *start;
285 int len;
286
287 if (!content_type)
288 return NULL;
289
290 start = http_get_header_parameter(content_type, octstr_imm("start"));
291 if (start && (len = octstr_len(start)) > 0 &&
292 octstr_get_char(start, 0) == '"' && octstr_get_char(start, len-1) == '"') {
293 octstr_delete(start, 0, 1);
294 octstr_delete(start, len-2, 1);
295 }
296
297 return start;
298 }
299
cid_matches(List * headers,Octstr * start)300 static int cid_matches(List *headers, Octstr *start)
301 {
302 Octstr *cid = http_header_value(headers, octstr_imm("Content-ID"));
303 char *cid_str;
304 int cid_len;
305 int ret;
306
307 if (cid == NULL)
308 return 0;
309
310 /* First, strip the <> if any. XXX some mime coders produce such messiness! */
311 cid_str = octstr_get_cstr(cid);
312 cid_len = octstr_len(cid);
313 if (cid_str[0] == '<') {
314 cid_str+=1;
315 cid_len-=2;
316 }
317 if (start != NULL && cid != NULL &&
318 (octstr_compare(start, cid) == 0 || octstr_str_ncompare(start, cid_str, cid_len) == 0))
319 ret = 1;
320 else
321 ret = 0;
322
323 octstr_destroy(cid);
324 return ret;
325 }
326
327 /*
328 * This routine is used for mime_[http|octstr]_to_entity() in order to
329 * reduce code duplication. Basically the only difference is how the headers
330 * are parsed or passed to the resulting MIMEEntity representation.
331 */
mime_something_to_entity(Octstr * mime,List * headers)332 static MIMEEntity *mime_something_to_entity(Octstr *mime, List *headers)
333 {
334 MIMEEntity *e;
335 ParseContext *context;
336 Octstr *value, *boundary, *start;
337 int len = 0;
338
339 gw_assert(mime != NULL);
340
341 value = boundary = start = NULL;
342 context = parse_context_create(mime);
343 e = mime_entity_create();
344
345 /* parse the headers up to the body. If we have headers already passed
346 * from our caller, then duplicate them and continue */
347 if (headers != NULL) {
348 /* we have some headers to duplicate, first ensure we destroy
349 * the list from the previous creation inside mime_entity_create() */
350 http_destroy_headers(e->headers);
351 e->headers = http_header_duplicate(headers);
352 } else {
353 /* parse the headers out of the mime block */
354 if ((read_mime_headers(context, e->headers) != 0) || e->headers == NULL) {
355 debug("mime.parse",0,"Failed to read MIME headers in Octstr block:");
356 octstr_dump(mime, 0);
357 mime_entity_destroy(e);
358 parse_context_destroy(context);
359 return NULL;
360 }
361 }
362
363 /*
364 * Now check if the body is a multipart. This is indicated by an 'boundary'
365 * parameter in the 'Content-Type' value. If yes, call ourself for the
366 * multipart entities after parsing them.
367 */
368 value = http_header_value(e->headers, octstr_imm("Content-Type"));
369 boundary = http_get_header_parameter(value, octstr_imm("boundary"));
370 start = get_start_param(value);
371
372 /* Beware that we need *unquoted* strings to compare against in the
373 * following parsing sections. */
374 if (boundary && (len = octstr_len(boundary)) > 0 &&
375 octstr_get_char(boundary, 0) == '"' && octstr_get_char(boundary, len-1) == '"') {
376 octstr_delete(boundary, 0, 1);
377 octstr_delete(boundary, len-2, 1);
378
379 }
380
381 if (boundary != NULL) {
382 /* we have a multipart block as body, parse the boundary blocks */
383 Octstr *entity, *seperator, *os;
384
385 /* loop by all boundary blocks we have in the body */
386 seperator = octstr_create("--");
387 octstr_append(seperator, boundary);
388 while ((entity = parse_get_seperated_block(context, seperator)) != NULL) {
389 MIMEEntity *m;
390 int del2 = 0;
391
392 /* we have still linefeeds at the beginning and end that we
393 * need to remove, these are from the separator.
394 * We check if it is LF only or CRLF! */
395 del2 = (octstr_get_char(entity, 0) == '\r');
396 if (del2)
397 octstr_delete(entity, 0, 2);
398 else
399 octstr_delete(entity, 0, 1);
400
401 /* we assume the same mechanism applies to beginning and end --
402 * seems reasonable! */
403 if (del2)
404 octstr_delete(entity, octstr_len(entity) - 2, 2);
405 else
406 octstr_delete(entity, octstr_len(entity) - 1, 1);
407
408 debug("mime.parse",0,"MIME multipart: Parsing entity:");
409 octstr_dump(entity, 0);
410
411 /* call ourself for this MIME entity and inject to list */
412 if ((m = mime_octstr_to_entity(entity))) {
413 gwlist_append(e->multiparts, m);
414
415 /* check if this entity is our start entity (in terms of related)
416 * and set our start pointer to it */
417 if (cid_matches(m->headers, start)) {
418 /* set only if none has been set before */
419 e->start = (e->start == NULL) ? m : e->start;
420 }
421 }
422
423 octstr_destroy(entity);
424 }
425 /* ok, we parsed all blocks, we expect to see now the end boundary */
426 octstr_append_cstr(seperator, "--");
427 os = parse_get_line(context);
428 if (os != NULL && octstr_compare(os, seperator) != 0) {
429 debug("mime.parse",0,"Failed to see end boundary, parsed line is '%s'.",
430 octstr_get_cstr(os));
431 }
432
433 octstr_destroy(seperator);
434 octstr_destroy(os);
435 }
436 else {
437
438 /* we don't have boundaries, so this is no multipart block,
439 * pass the body to the MIME entity. */
440 e->body = parse_get_rest(context);
441
442 }
443
444 parse_context_destroy(context);
445 octstr_destroy(value);
446 octstr_destroy(boundary);
447 octstr_destroy(start);
448
449 return e;
450 }
451
452
mime_octstr_to_entity(Octstr * mime)453 MIMEEntity *mime_octstr_to_entity(Octstr *mime)
454 {
455 gw_assert(mime != NULL);
456
457 return mime_something_to_entity(mime, NULL);
458 }
459
460
mime_http_to_entity(List * headers,Octstr * body)461 MIMEEntity *mime_http_to_entity(List *headers, Octstr *body)
462 {
463 gw_assert(headers != NULL && body != NULL);
464
465 return mime_something_to_entity(body, headers);
466 }
467
468
mime_entity_headers(MIMEEntity * m)469 List *mime_entity_headers(MIMEEntity *m)
470 {
471 List *headers;
472
473 gw_assert(m != NULL && m->headers != NULL);
474
475 /* Need a fixup before hand over. */
476 if (mime_entity_num_parts(m) > 0)
477 fix_boundary_element(m->headers, NULL);
478
479 headers = http_header_duplicate(m->headers);
480
481 return headers;
482 }
483
484
mime_entity_body(MIMEEntity * m)485 Octstr *mime_entity_body(MIMEEntity *m)
486 {
487 Octstr *os, *body;
488 ParseContext *context;
489 MIMEEntity *e;
490
491 gw_assert(m != NULL && m->headers != NULL);
492
493 /* For non-multipart, return body directly. */
494 if (mime_entity_num_parts(m) == 0)
495 return octstr_duplicate(m->body);
496
497 os = mime_entity_to_octstr(m);
498 context = parse_context_create(os);
499 e = mime_entity_create();
500
501 /* parse the headers up to the body */
502 if ((read_mime_headers(context, e->headers) != 0) || e->headers == NULL) {
503 debug("mime.parse",0,"Failed to read MIME headers in Octstr block:");
504 octstr_dump(os, 0);
505 mime_entity_destroy(e);
506 parse_context_destroy(context);
507 return NULL;
508 }
509
510 /* the rest is the body */
511 body = parse_get_rest(context);
512
513 octstr_destroy(os);
514 mime_entity_destroy(e);
515 parse_context_destroy(context);
516
517 return body;
518 }
519
520 /* Make a copy of a mime object. recursively. */
mime_entity_duplicate(MIMEEntity * e)521 MIMEEntity *mime_entity_duplicate(MIMEEntity *e)
522 {
523 MIMEEntity *copy = mime_entity_create();
524 int i, n;
525
526 mime_replace_headers(copy, e->headers);
527 copy->body = e->body ? octstr_duplicate(e->body) : NULL;
528
529 for (i = 0, n = gwlist_len(e->multiparts); i < n; i++)
530 gwlist_append(copy->multiparts,
531 mime_entity_duplicate(gwlist_get(e->multiparts, i)));
532 return copy;
533 }
534
535 /* Replace top-level MIME headers: Old ones removed completetly */
mime_replace_headers(MIMEEntity * e,List * headers)536 void mime_replace_headers(MIMEEntity *e, List *headers)
537 {
538 gw_assert(e != NULL);
539 gw_assert(headers != NULL);
540
541 http_destroy_headers(e->headers);
542 e->headers = http_header_duplicate(headers);
543 e->start = NULL; /* clear it, since header change means it could have changed.*/
544 }
545
546
547 /* Get number of body parts. Returns 0 if this is not
548 * a multipart object.
549 */
mime_entity_num_parts(MIMEEntity * e)550 int mime_entity_num_parts(MIMEEntity *e)
551 {
552 gw_assert(e != NULL);
553 return e->multiparts ? gwlist_len(e->multiparts) : 0;
554 }
555
556
557 /* Append a new part to list of body parts. Copy is made
558 * Note that if it was not multipart, this action makes it so!
559 */
mime_entity_add_part(MIMEEntity * e,MIMEEntity * part)560 void mime_entity_add_part(MIMEEntity *e, MIMEEntity *part)
561 {
562 gw_assert(e != NULL);
563 gw_assert(part != NULL);
564
565 gwlist_append(e->multiparts, mime_entity_duplicate(part));
566 }
567
568
569 /* Get part i in list of body parts. Copy is made*/
mime_entity_get_part(MIMEEntity * e,int i)570 MIMEEntity *mime_entity_get_part(MIMEEntity *e, int i)
571 {
572 MIMEEntity *m;
573 gw_assert(e != NULL);
574 gw_assert(i >= 0);
575 gw_assert(i < gwlist_len(e->multiparts));
576
577 m = gwlist_get(e->multiparts, i);
578 gw_assert(m);
579 return mime_entity_duplicate(m);
580 }
581
582
583 /* Remove part i in list of body parts. */
mime_entity_remove_part(MIMEEntity * e,int i)584 void mime_entity_remove_part(MIMEEntity *e, int i)
585 {
586 MIMEEntity *m;
587
588 gw_assert(e != NULL);
589 gw_assert(i >= 0);
590 gw_assert(i < gwlist_len(e->multiparts));
591
592
593 m = gwlist_get(e->multiparts, i);
594 gwlist_delete(e->multiparts, i, 1);
595 if (m == e->start) e->start = NULL;
596
597 mime_entity_destroy(m);
598 }
599
600 /* Replace part i in list of body parts. Old one will be deleted */
mime_entity_replace_part(MIMEEntity * e,int i,MIMEEntity * newpart)601 void mime_entity_replace_part(MIMEEntity *e, int i, MIMEEntity *newpart)
602 {
603
604 MIMEEntity *m;
605
606 gw_assert(e != NULL);
607 gw_assert(i >= 0);
608 gw_assert(i < gwlist_len(e->multiparts));
609
610 m = gwlist_get(e->multiparts, i);
611 gwlist_delete(e->multiparts, i, 1);
612 gwlist_insert(e->multiparts, i, mime_entity_duplicate(newpart));
613 if (m == e->start) e->start = NULL;
614
615 mime_entity_destroy(m);
616 }
617
618 /* Change body element of non-multipart entity.
619 * We don't check that object is multi part. Result is just that
620 * body will be ignored.
621 */
mime_entity_set_body(MIMEEntity * e,Octstr * body)622 void mime_entity_set_body(MIMEEntity *e, Octstr *body)
623 {
624 gw_assert(e != NULL);
625 gw_assert(body != NULL);
626
627 if (e->body)
628 octstr_destroy(e->body);
629 e->body = octstr_duplicate(body);
630 }
631
632 /* Returns (copy of) the 'start' element of a multi-part entity. */
mime_multipart_start_elem(MIMEEntity * e)633 MIMEEntity *mime_multipart_start_elem(MIMEEntity *e)
634 {
635 gw_assert(e != NULL);
636
637 /* If e->start element is not yet set, set it as follows:
638 * - if content type is not set, then set it to NULL
639 * - if the start element is not set but this is a multipart object, set
640 * it to first multipart element, else set it to null
641 * - if the start element of the content type is set, find a matching object
642 * and set e->start accordingly.
643 * Finally, return a copy of it.
644 */
645 if (!e->start) {
646 Octstr *ctype = http_header_value(e->headers, octstr_imm("Content-Type"));
647 Octstr *start = get_start_param(ctype);
648 int i;
649
650 if (!ctype)
651 e->start = NULL;
652 else if (!start) {
653 if (gwlist_len(e->multiparts) > 0)
654 e->start = gwlist_get(e->multiparts, 0);
655 else
656 e->start = NULL;
657 } else
658 for (i = 0; i < gwlist_len(e->multiparts); i++) {
659 MIMEEntity *x = gwlist_get(e->multiparts, i);
660 if (cid_matches(x->headers, start)) {
661 e->start = x;
662 break;
663 }
664 }
665
666 if (ctype)
667 octstr_destroy(ctype);
668 if (start)
669 octstr_destroy(start);
670 }
671
672 return (e->start) ? mime_entity_duplicate(e->start) : NULL;
673 }
674
675 /********************************************************************
676 * Routines for debugging purposes.
677 */
678
mime_entity_dump_real(MIMEEntity * m,unsigned int level)679 static void mime_entity_dump_real(MIMEEntity *m, unsigned int level)
680 {
681 long i, items;
682 Octstr *prefix, *type, *charset;
683 unsigned int j;
684
685 gw_assert(m != NULL && m->headers != NULL);
686
687 prefix = octstr_create("");
688 for (j = 0; j < level * 2; j++)
689 octstr_append_cstr(prefix, " ");
690
691 http_header_get_content_type(m->headers, &type, &charset);
692 debug("mime.dump",0,"%sContent-Type `%s'", octstr_get_cstr(prefix),
693 octstr_get_cstr(type));
694 if (m->start != NULL) {
695 Octstr *cid = http_header_value(m->start->headers, octstr_imm("Content-ID"));
696 debug("mime.dump",0,"%sRelated to Content-ID <%s> MIMEEntity at address `%p'",
697 octstr_get_cstr(prefix), octstr_get_cstr(cid), m->start);
698 octstr_destroy(cid);
699 }
700 items = gwlist_len(m->multiparts);
701 debug("mime.dump",0,"%sBody contains %ld MIME entities, size %ld", octstr_get_cstr(prefix),
702 items, (items == 0 && m->body) ? octstr_len(m->body) : -1);
703
704 octstr_destroy(prefix);
705 octstr_destroy(type);
706 octstr_destroy(charset);
707
708 for (i = 0; i < items; i++) {
709 MIMEEntity *e = gwlist_get(m->multiparts, i);
710
711 mime_entity_dump_real(e, level + 1);
712 }
713
714 }
715
716
mime_entity_dump(MIMEEntity * m)717 void mime_entity_dump(MIMEEntity *m)
718 {
719 gw_assert(m != NULL && m->headers != NULL);
720
721 debug("mms",0,"Dumping MIMEEntity at address %p", m);
722 mime_entity_dump_real(m, 0);
723 }
724
725