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