Delta Chat Core C-Library
mrmimeparser.c
1 /*******************************************************************************
2  *
3  * Delta Chat Core
4  * Copyright (C) 2017 Björn Petersen
5  * Contact: r10s@b44t.com, http://b44t.com
6  *
7  * This program is free software: you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License as published by the Free Software
9  * Foundation, either version 3 of the License, or (at your option) any later
10  * version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15  * details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program. If not, see http://www.gnu.org/licenses/ .
19  *
20  ******************************************************************************/
21 
22 
23 #include "mrmailbox_internal.h"
24 #include "mrmimeparser.h"
25 #include "mrmimefactory.h"
26 #include "mrsimplify.h"
27 
28 
29 /*******************************************************************************
30  * debug output
31  ******************************************************************************/
32 
33 
34 #ifdef MR_USE_MIME_DEBUG
35 
36 /* if you need this functionality, define MR_USE_MIME_DEBUG in the project,
37 eg. in Codeblocks at "Project / Build options / <project or target> / Compiler settings / #defines" */
38 
39 
40 static void display_mime_content(struct mailmime_content * content_type);
41 
42 static void display_mime_data(struct mailmime_data * data)
43 {
44  switch (data->dt_type) {
45  case MAILMIME_DATA_TEXT:
46  printf("data : %i bytes\n", (int) data->dt_data.dt_text.dt_length);
47  break;
48  case MAILMIME_DATA_FILE:
49  printf("data (file) : %s\n", data->dt_data.dt_filename);
50  break;
51  }
52 }
53 
54 static void display_mime_dsp_parm(struct mailmime_disposition_parm * param)
55 {
56  switch (param->pa_type) {
57  case MAILMIME_DISPOSITION_PARM_FILENAME:
58  printf("filename: %s\n", param->pa_data.pa_filename);
59  break;
60  }
61 }
62 
63 static void display_mime_disposition(struct mailmime_disposition * disposition)
64 {
65  clistiter * cur;
66 
67  for(cur = clist_begin(disposition->dsp_parms) ;
68  cur != NULL ; cur = clist_next(cur)) {
69  struct mailmime_disposition_parm * param;
70 
71  param = (struct mailmime_disposition_parm*)clist_content(cur);
72  display_mime_dsp_parm(param);
73  }
74 }
75 
76 static void display_mime_field(struct mailmime_field * field)
77 {
78  switch (field->fld_type) {
79  case MAILMIME_FIELD_TYPE:
80  printf("content-type: ");
81  display_mime_content(field->fld_data.fld_content);
82  printf("\n");
83  break;
84  case MAILMIME_FIELD_DISPOSITION:
85  display_mime_disposition(field->fld_data.fld_disposition);
86  break;
87  }
88 }
89 
90 static void display_mime_fields(struct mailmime_fields * fields)
91 {
92  clistiter * cur;
93 
94  for(cur = clist_begin(fields->fld_list) ; cur != NULL ; cur = clist_next(cur)) {
95  struct mailmime_field * field;
96 
97  field = (struct mailmime_field*)clist_content(cur);
98  display_mime_field(field);
99  }
100 }
101 
102 static void display_date_time(struct mailimf_date_time * d)
103 {
104  printf("%02i/%02i/%i %02i:%02i:%02i %+04i",
105  d->dt_day, d->dt_month, d->dt_year,
106  d->dt_hour, d->dt_min, d->dt_sec, d->dt_zone);
107 }
108 
109 static void display_orig_date(struct mailimf_orig_date * orig_date)
110 {
111  display_date_time(orig_date->dt_date_time);
112 }
113 
114 static void display_mailbox(struct mailimf_mailbox * mb)
115 {
116  if (mb->mb_display_name != NULL)
117  printf("%s ", mb->mb_display_name);
118  printf("<%s>", mb->mb_addr_spec);
119 }
120 
121 static void display_mailbox_list(struct mailimf_mailbox_list * mb_list)
122 {
123  clistiter * cur;
124 
125  for(cur = clist_begin(mb_list->mb_list) ; cur != NULL ;
126  cur = clist_next(cur)) {
127  struct mailimf_mailbox * mb;
128 
129  mb = (struct mailimf_mailbox*)clist_content(cur);
130 
131  display_mailbox(mb);
132  if (clist_next(cur) != NULL) {
133  printf(", ");
134  }
135  }
136 }
137 
138 static void display_group(struct mailimf_group * group)
139 {
140  clistiter * cur;
141 
142  printf("%s: ", group->grp_display_name);
143  for(cur = clist_begin(group->grp_mb_list->mb_list) ; cur != NULL ; cur = clist_next(cur)) {
144  struct mailimf_mailbox * mb;
145 
146  mb = (struct mailimf_mailbox*)clist_content(cur);
147  display_mailbox(mb);
148  }
149  printf("; ");
150 }
151 
152 static void display_address(struct mailimf_address * a)
153 {
154  switch (a->ad_type) {
155  case MAILIMF_ADDRESS_GROUP:
156  display_group(a->ad_data.ad_group);
157  break;
158 
159  case MAILIMF_ADDRESS_MAILBOX:
160  display_mailbox(a->ad_data.ad_mailbox);
161  break;
162  }
163 }
164 
165 static void display_address_list(struct mailimf_address_list * addr_list)
166 {
167  clistiter * cur;
168 
169  for(cur = clist_begin(addr_list->ad_list) ; cur != NULL ;
170  cur = clist_next(cur)) {
171  struct mailimf_address * addr;
172 
173  addr = (struct mailimf_address*)clist_content(cur);
174 
175  display_address(addr);
176 
177  if (clist_next(cur) != NULL) {
178  printf(", ");
179  }
180  }
181 }
182 
183 static void display_from(struct mailimf_from * from)
184 {
185  display_mailbox_list(from->frm_mb_list);
186 }
187 
188 static void display_to(struct mailimf_to * to)
189 {
190  display_address_list(to->to_addr_list);
191 }
192 
193 static void display_cc(struct mailimf_cc * cc)
194 {
195  display_address_list(cc->cc_addr_list);
196 }
197 
198 static void display_subject(struct mailimf_subject * subject)
199 {
200  printf("%s", subject->sbj_value);
201 }
202 
203 static void display_field(struct mailimf_field * field)
204 {
205  switch (field->fld_type) {
206  case MAILIMF_FIELD_ORIG_DATE:
207  printf("Date: ");
208  display_orig_date(field->fld_data.fld_orig_date);
209  printf("\n");
210  break;
211  case MAILIMF_FIELD_FROM:
212  printf("From: ");
213  display_from(field->fld_data.fld_from);
214  printf("\n");
215  break;
216  case MAILIMF_FIELD_TO:
217  printf("To: ");
218  display_to(field->fld_data.fld_to);
219  printf("\n");
220  break;
221  case MAILIMF_FIELD_CC:
222  printf("Cc: ");
223  display_cc(field->fld_data.fld_cc);
224  printf("\n");
225  break;
226  case MAILIMF_FIELD_SUBJECT:
227  printf("Subject: ");
228  display_subject(field->fld_data.fld_subject);
229  printf("\n");
230  break;
231  case MAILIMF_FIELD_MESSAGE_ID:
232  printf("Message-ID: %s\n", field->fld_data.fld_message_id->mid_value);
233  break;
234  }
235 }
236 
237 static void display_fields(struct mailimf_fields * fields)
238 {
239  clistiter * cur;
240 
241  for(cur = clist_begin(fields->fld_list) ; cur != NULL ;
242  cur = clist_next(cur)) {
243  struct mailimf_field * f;
244 
245  f = (struct mailimf_field*)clist_content(cur);
246 
247  display_field(f);
248  }
249 }
250 
251 static void display_mime_discrete_type(struct mailmime_discrete_type * discrete_type)
252 {
253  switch (discrete_type->dt_type) {
254  case MAILMIME_DISCRETE_TYPE_TEXT:
255  printf("text");
256  break;
257  case MAILMIME_DISCRETE_TYPE_IMAGE:
258  printf("image");
259  break;
260  case MAILMIME_DISCRETE_TYPE_AUDIO:
261  printf("audio");
262  break;
263  case MAILMIME_DISCRETE_TYPE_VIDEO:
264  printf("video");
265  break;
266  case MAILMIME_DISCRETE_TYPE_APPLICATION:
267  printf("application");
268  break;
269  case MAILMIME_DISCRETE_TYPE_EXTENSION:
270  printf("%s", discrete_type->dt_extension);
271  break;
272  }
273 }
274 
275 static void display_mime_composite_type(struct mailmime_composite_type * ct)
276 {
277  switch (ct->ct_type) {
278  case MAILMIME_COMPOSITE_TYPE_MESSAGE:
279  printf("message");
280  break;
281  case MAILMIME_COMPOSITE_TYPE_MULTIPART:
282  printf("multipart");
283  break;
284  case MAILMIME_COMPOSITE_TYPE_EXTENSION:
285  printf("%s", ct->ct_token);
286  break;
287  }
288 }
289 
290 static void display_mime_type(struct mailmime_type * type)
291 {
292  switch (type->tp_type) {
293  case MAILMIME_TYPE_DISCRETE_TYPE:
294  display_mime_discrete_type(type->tp_data.tp_discrete_type);
295  break;
296  case MAILMIME_TYPE_COMPOSITE_TYPE:
297  display_mime_composite_type(type->tp_data.tp_composite_type);
298  break;
299  }
300 }
301 
302 static void display_mime_content(struct mailmime_content * content_type)
303 {
304  printf("type: ");
305  display_mime_type(content_type->ct_type);
306  printf("/%s\n", content_type->ct_subtype);
307 }
308 
309 static void print_mime(struct mailmime * mime)
310 {
311  clistiter * cur;
312 
313  if( mime == NULL ) {
314  printf("ERROR: NULL given to print_mime()\n");
315  return;
316  }
317 
318  switch (mime->mm_type) {
319  case MAILMIME_SINGLE:
320  printf("single part\n");
321  break;
322  case MAILMIME_MULTIPLE:
323  printf("multipart\n");
324  break;
325  case MAILMIME_MESSAGE:
326  printf("message\n");
327  break;
328  }
329 
330  if (mime->mm_mime_fields != NULL) {
331  if (clist_begin(mime->mm_mime_fields->fld_list) != NULL) {
332  printf("--------------------------------<mime-headers>--------------------------------\n");
333  display_mime_fields(mime->mm_mime_fields);
334  printf("--------------------------------</mime-headers>-------------------------------\n");
335  }
336  }
337 
338  display_mime_content(mime->mm_content_type);
339 
340  switch (mime->mm_type) {
341  case MAILMIME_SINGLE:
342  display_mime_data(mime->mm_data.mm_single);
343  break;
344 
345  case MAILMIME_MULTIPLE:
346  for(cur = clist_begin(mime->mm_data.mm_multipart.mm_mp_list) ; cur != NULL ; cur = clist_next(cur)) {
347  printf("---------------------------<mime-part-of-multiple>----------------------------\n");
348  print_mime((struct mailmime*)clist_content(cur));
349  printf("---------------------------</mime-part-of-multiple>---------------------------\n");
350  }
351  break;
352 
353  case MAILMIME_MESSAGE:
354  if (mime->mm_data.mm_message.mm_fields) {
355  if (clist_begin(mime->mm_data.mm_message.mm_fields->fld_list) != NULL) {
356  printf("-------------------------------<email-headers>--------------------------------\n");
357  display_fields(mime->mm_data.mm_message.mm_fields);
358  printf("-------------------------------</email-headers>-------------------------------\n");
359  }
360 
361  if (mime->mm_data.mm_message.mm_msg_mime != NULL) {
362  printf("----------------------------<mime-part-of-message>----------------------------\n");
363  print_mime(mime->mm_data.mm_message.mm_msg_mime);
364  printf("----------------------------</mime-part-of-message>---------------------------\n");
365  }
366  }
367  break;
368  }
369 }
370 
371 
372 void mr_print_mime(struct mailmime* mime)
373 {
374  printf("====================================<mime>====================================\n");
375  print_mime(mime);
376  printf("====================================</mime>===================================\n\n");
377 }
378 
379 
380 #endif /* DEBUG_MIME_OUTPUT */
381 
382 
383 /*******************************************************************************
384  * low-level-tools for working with mailmime structures directly
385  ******************************************************************************/
386 
387 
388 struct mailimf_fields* mr_find_mailimf_fields(struct mailmime* mime)
389 {
390  if( mime == NULL ) {
391  return NULL;
392  }
393 
394  clistiter* cur;
395  switch (mime->mm_type) {
396  case MAILMIME_MULTIPLE:
397  for(cur = clist_begin(mime->mm_data.mm_multipart.mm_mp_list) ; cur != NULL ; cur = clist_next(cur)) {
398  struct mailimf_fields* header = mr_find_mailimf_fields(clist_content(cur));
399  if( header ) {
400  return header;
401  }
402  }
403  break;
404 
405  case MAILMIME_MESSAGE:
406  return mime->mm_data.mm_message.mm_fields;
407  }
408 
409  return NULL;
410 }
411 
412 
413 struct mailimf_field* mr_find_mailimf_field(struct mailimf_fields* header, int wanted_fld_type)
414 {
415  if( header == NULL || header->fld_list == NULL ) {
416  return NULL;
417  }
418 
419  clistiter* cur1;
420  for( cur1 = clist_begin(header->fld_list); cur1!=NULL ; cur1=clist_next(cur1) )
421  {
422  struct mailimf_field* field = (struct mailimf_field*)clist_content(cur1);
423  if( field )
424  {
425  if( field->fld_type == wanted_fld_type ) {
426  return field;
427  }
428  }
429  }
430 
431  return NULL;
432 }
433 
434 
435 struct mailimf_optional_field* mr_find_mailimf_field2(struct mailimf_fields* header, const char* wanted_fld_name)
436 {
437  /* Note: the function does not return fields with no value set! */
438  if( header == NULL || header->fld_list == NULL ) {
439  return NULL;
440  }
441 
442  clistiter* cur1;
443  for( cur1 = clist_begin(header->fld_list); cur1!=NULL ; cur1=clist_next(cur1) )
444  {
445  struct mailimf_field* field = (struct mailimf_field*)clist_content(cur1);
446  if( field && field->fld_type == MAILIMF_FIELD_OPTIONAL_FIELD )
447  {
448  struct mailimf_optional_field* optional_field = field->fld_data.fld_optional_field;
449  if( optional_field && optional_field->fld_name && optional_field->fld_value && strcasecmp(optional_field->fld_name, wanted_fld_name)==0 ) {
450  return optional_field;
451  }
452  }
453  }
454 
455  return NULL;
456 }
457 
458 
459 struct mailmime_parameter* mr_find_ct_parameter(struct mailmime* mime, const char* name)
460 {
461  /* find a parameter in `Content-Type: foo/bar; name=value;` */
462  if( mime==NULL || name==NULL
463  || mime->mm_content_type==NULL || mime->mm_content_type->ct_parameters==NULL )
464  {
465  return NULL;
466  }
467 
468  clistiter* cur;
469  for( cur = clist_begin(mime->mm_content_type->ct_parameters); cur != NULL; cur = clist_next(cur) ) {
470  struct mailmime_parameter* param = (struct mailmime_parameter*)clist_content(cur);
471  if( param && param->pa_name ) {
472  if( strcmp(param->pa_name, name)==0 ) {
473  return param;
474  }
475  }
476  }
477 
478  return NULL;
479 }
480 
481 
482 char* mr_normalize_addr(const char* addr__)
483 {
484  /* Not sure if we should also unifiy international characters before the @,
485  see also https://autocrypt.readthedocs.io/en/latest/address-canonicalization.html */
486  char* addr = safe_strdup(addr__);
487  mr_trim(addr);
488  if( strncmp(addr, "mailto:", 7)==0 ) {
489  char* old = addr;
490  addr = safe_strdup(&old[7]);
491  free(old);
492  mr_trim(addr);
493  }
494  return addr;
495 }
496 
497 
498 char* mr_find_first_addr(const struct mailimf_mailbox_list* mb_list)
499 {
500  clistiter* cur;
501 
502  if( mb_list == NULL ) {
503  return NULL;
504  }
505 
506  for( cur = clist_begin(mb_list->mb_list); cur!=NULL ; cur=clist_next(cur) ) {
507  struct mailimf_mailbox* mb = (struct mailimf_mailbox*)clist_content(cur);
508  if( mb && mb->mb_addr_spec ) {
509  return mr_normalize_addr(mb->mb_addr_spec);
510  }
511  }
512  return NULL;
513 }
514 
515 
516 /*******************************************************************************
517  * a MIME part
518  ******************************************************************************/
519 
520 
521 static mrmimepart_t* mrmimepart_new(void)
522 {
523  mrmimepart_t* ths = NULL;
524 
525  if( (ths=calloc(1, sizeof(mrmimepart_t)))==NULL ) {
526  exit(33);
527  }
528 
529  ths->m_type = MR_MSG_UNDEFINED;
530  ths->m_param = mrparam_new();
531 
532  return ths;
533 }
534 
535 
536 static void mrmimepart_unref(mrmimepart_t* ths)
537 {
538  if( ths == NULL ) {
539  return;
540  }
541 
542  if( ths->m_msg ) {
543  free(ths->m_msg);
544  ths->m_msg = NULL;
545  }
546 
547  if( ths->m_msg_raw ) {
548  free(ths->m_msg_raw);
549  ths->m_msg_raw = NULL;
550  }
551 
552  mrparam_unref(ths->m_param);
553  free(ths);
554 }
555 
556 
557 /*******************************************************************************
558  * Main interface
559  ******************************************************************************/
560 
561 
562 mrmimeparser_t* mrmimeparser_new(const char* blobdir, mrmailbox_t* mailbox)
563 {
564  mrmimeparser_t* ths = NULL;
565 
566  if( (ths=calloc(1, sizeof(mrmimeparser_t)))==NULL ) {
567  exit(30);
568  }
569 
570  ths->m_mailbox = mailbox;
571  ths->m_parts = carray_new(16);
572  ths->m_blobdir = blobdir; /* no need to copy the string at the moment */
573  ths->m_reports = carray_new(16);
574 
575  return ths;
576 }
577 
578 
579 void mrmimeparser_unref(mrmimeparser_t* ths)
580 {
581  if( ths == NULL ) {
582  return;
583  }
584 
585  mrmimeparser_empty(ths);
586  if( ths->m_parts ) { carray_free(ths->m_parts); }
587  if( ths->m_reports ) { carray_free(ths->m_reports); }
588  free(ths);
589 }
590 
591 
592 void mrmimeparser_empty(mrmimeparser_t* ths)
593 {
594  if( ths == NULL ) {
595  return;
596  }
597 
598  if( ths->m_parts )
599  {
600  int i, cnt = carray_count(ths->m_parts);
601  for( i = 0; i < cnt; i++ ) {
602  mrmimepart_t* part = (mrmimepart_t*)carray_get(ths->m_parts, i);
603  if( part ) {
604  mrmimepart_unref(part);
605  }
606  }
607  carray_set_size(ths->m_parts, 0);
608  }
609 
610  ths->m_header = NULL; /* a pointer somewhere to the MIME data, must not be freed */
611  ths->m_is_send_by_messenger = 0;
612  ths->m_is_system_message = 0;
613 
614  free(ths->m_subject);
615  ths->m_subject = NULL;
616 
617  if( ths->m_mimeroot )
618  {
619  mailmime_free(ths->m_mimeroot);
620  ths->m_mimeroot = NULL;
621  }
622 
623  ths->m_is_forwarded = 0;
624 
625  if( ths->m_reports ) {
626  carray_set_size(ths->m_reports, 0);
627  }
628 
629  ths->m_decrypted_and_validated = 0;
630  ths->m_decrypted_with_validation_errors = 0;
631  ths->m_decrypting_failed = 0;
632 }
633 
634 
635 static int is_attachment_disposition(struct mailmime* mime)
636 {
637  if( mime->mm_mime_fields != NULL ) {
638  clistiter* cur;
639  for( cur = clist_begin(mime->mm_mime_fields->fld_list); cur != NULL; cur = clist_next(cur) ) {
640  struct mailmime_field* field = (struct mailmime_field*)clist_content(cur);
641  if( field && field->fld_type == MAILMIME_FIELD_DISPOSITION && field->fld_data.fld_disposition ) {
642  if( field->fld_data.fld_disposition->dsp_type
643  && field->fld_data.fld_disposition->dsp_type->dsp_type==MAILMIME_DISPOSITION_TYPE_ATTACHMENT )
644  {
645  return 1;
646  }
647  }
648  }
649  }
650  return 0;
651 }
652 
653 
654 static int mrmimeparser_get_mime_type(struct mailmime* mime, int* msg_type)
655 {
656  #define MR_MIMETYPE_MP_ALTERNATIVE 10
657  #define MR_MIMETYPE_MP_RELATED 20
658  #define MR_MIMETYPE_MP_MIXED 30
659  #define MR_MIMETYPE_MP_NOT_DECRYPTABLE 40
660  #define MR_MIMETYPE_MP_REPORT 45
661  #define MR_MIMETYPE_MP_SIGNED 46
662  #define MR_MIMETYPE_MP_OTHER 50
663  #define MR_MIMETYPE_TEXT_PLAIN 60
664  #define MR_MIMETYPE_TEXT_HTML 70
665  #define MR_MIMETYPE_IMAGE 80
666  #define MR_MIMETYPE_AUDIO 90
667  #define MR_MIMETYPE_VIDEO 100
668  #define MR_MIMETYPE_FILE 110
669 
670  struct mailmime_content* c = mime->mm_content_type;
671  int dummy; if( msg_type == NULL ) { msg_type = &dummy; }
672  *msg_type = MR_MSG_UNDEFINED;
673 
674  if( c == NULL || c->ct_type == NULL ) {
675  return 0;
676  }
677 
678  switch( c->ct_type->tp_type )
679  {
680  case MAILMIME_TYPE_DISCRETE_TYPE:
681  switch( c->ct_type->tp_data.tp_discrete_type->dt_type )
682  {
683  case MAILMIME_DISCRETE_TYPE_TEXT:
684  if( is_attachment_disposition(mime) ) {
685  ; /* MR_MIMETYPE_FILE is returned below - we leave text attachments as attachments as they may be too large to display as a normal message, eg. complete books. */
686  }
687  else if( strcmp(c->ct_subtype, "plain")==0 ) {
688  *msg_type = MR_MSG_TEXT;
689  return MR_MIMETYPE_TEXT_PLAIN;
690  }
691  else if( strcmp(c->ct_subtype, "html")==0 ) {
692  *msg_type = MR_MSG_TEXT;
693  return MR_MIMETYPE_TEXT_HTML;
694  }
695  *msg_type = MR_MSG_FILE;
696  return MR_MIMETYPE_FILE;
697 
698  case MAILMIME_DISCRETE_TYPE_IMAGE:
699  if( strcmp(c->ct_subtype, "gif")==0 ) {
700  *msg_type = MR_MSG_GIF;
701  }
702  else {
703  *msg_type = MR_MSG_IMAGE;
704  }
705  return MR_MIMETYPE_IMAGE;
706 
707  case MAILMIME_DISCRETE_TYPE_AUDIO:
708  *msg_type = MR_MSG_AUDIO; /* we correct this later to MR_MSG_VOICE, currently, this is not possible as we do not know the main header */
709  return MR_MIMETYPE_AUDIO;
710 
711  case MAILMIME_DISCRETE_TYPE_VIDEO:
712  *msg_type = MR_MSG_VIDEO;
713  return MR_MIMETYPE_VIDEO;
714 
715  default:
716  *msg_type = MR_MSG_FILE;
717  return MR_MIMETYPE_FILE;
718  }
719  break;
720 
721  case MAILMIME_TYPE_COMPOSITE_TYPE:
722  if( c->ct_type->tp_data.tp_composite_type->ct_type == MAILMIME_COMPOSITE_TYPE_MULTIPART )
723  {
724  if( strcmp(c->ct_subtype, "alternative")==0 ) {
725  return MR_MIMETYPE_MP_ALTERNATIVE;
726  }
727  else if( strcmp(c->ct_subtype, "related")==0 ) {
728  return MR_MIMETYPE_MP_RELATED;
729  }
730  else if( strcmp(c->ct_subtype, "encrypted")==0 ) {
731  return MR_MIMETYPE_MP_NOT_DECRYPTABLE; /* decryptable parts are already converted to other mime parts in mre2ee_decrypt() */
732  }
733  else if( strcmp(c->ct_subtype, "signed")==0 ) {
734  return MR_MIMETYPE_MP_SIGNED;
735  }
736  else if( strcmp(c->ct_subtype, "mixed")==0 ) {
737  return MR_MIMETYPE_MP_MIXED;
738  }
739  else if( strcmp(c->ct_subtype, "report")==0 ) {
740  return MR_MIMETYPE_MP_REPORT;
741  }
742  else {
743  return MR_MIMETYPE_MP_OTHER;
744  }
745  }
746  else if( c->ct_type->tp_data.tp_composite_type->ct_type == MAILMIME_COMPOSITE_TYPE_MESSAGE )
747  {
748  /* Enacapsulated messages, see https://www.w3.org/Protocols/rfc1341/7_3_Message.html
749  Also used as part "message/disposition-notification" of "multipart/report", which, however, will be handled separatedly.
750  I've not seen any messages using this, so we do not attach these parts (maybe they're used to attach replies, which are unwanted at all).
751 
752  For now, we skip these parts at all; if desired, we could return MR_MIMETYPE_FILE/MR_MSG_FILE for selected and known subparts. */
753  return 0;
754  }
755  break;
756 
757  default:
758  break;
759  }
760 
761  return 0; /* unknown */
762 }
763 
764 
765 #if 0 /* currently no needed */
766 static char* get_file_disposition_suffix_(struct mailmime_disposition* file_disposition)
767 {
768  if( file_disposition ) {
769  clistiter* cur;
770  for( cur = clist_begin(file_disposition->dsp_parms); cur != NULL; cur = clist_next(cur) ) {
771  struct mailmime_disposition_parm* dsp_param = (struct mailmime_disposition_parm*)clist_content(cur);
772  if( dsp_param ) {
773  if( dsp_param->pa_type==MAILMIME_DISPOSITION_PARM_FILENAME ) {
774  return mr_get_filesuffix_lc(dsp_param->pa_data.pa_filename);
775  }
776  }
777  }
778  }
779  return NULL;
780 }
781 #endif
782 
783 
784 int mr_mime_transfer_decode(struct mailmime* mime, const char** ret_decoded_data, size_t* ret_decoded_data_bytes, char** ret_to_mmap_string_unref)
785 {
786  int mime_transfer_encoding = MAILMIME_MECHANISM_BINARY;
787  struct mailmime_data* mime_data = mime->mm_data.mm_single;
788  const char* decoded_data = NULL; /* must not be free()'d */
789  size_t decoded_data_bytes = 0;
790  char* transfer_decoding_buffer = NULL; /* mmap_string_unref()'d if set */
791 
792  if( mime == NULL || ret_decoded_data == NULL || ret_decoded_data_bytes == NULL || ret_to_mmap_string_unref == NULL
793  || *ret_decoded_data != NULL || *ret_decoded_data_bytes != 0 || *ret_to_mmap_string_unref != NULL ) {
794  return 0;
795  }
796 
797  if( mime->mm_mime_fields != NULL ) {
798  clistiter* cur;
799  for( cur = clist_begin(mime->mm_mime_fields->fld_list); cur != NULL; cur = clist_next(cur) ) {
800  struct mailmime_field* field = (struct mailmime_field*)clist_content(cur);
801  if( field && field->fld_type == MAILMIME_FIELD_TRANSFER_ENCODING && field->fld_data.fld_encoding ) {
802  mime_transfer_encoding = field->fld_data.fld_encoding->enc_type;
803  break;
804  }
805  }
806  }
807 
808  /* regard `Content-Transfer-Encoding:` */
809  if( mime_transfer_encoding == MAILMIME_MECHANISM_7BIT
810  || mime_transfer_encoding == MAILMIME_MECHANISM_8BIT
811  || mime_transfer_encoding == MAILMIME_MECHANISM_BINARY )
812  {
813  decoded_data = mime_data->dt_data.dt_text.dt_data;
814  decoded_data_bytes = mime_data->dt_data.dt_text.dt_length;
815  if( decoded_data == NULL || decoded_data_bytes <= 0 ) {
816  return 0; /* no error - but no data */
817  }
818  }
819  else
820  {
821  int r;
822  size_t current_index = 0;
823  r = mailmime_part_parse(mime_data->dt_data.dt_text.dt_data, mime_data->dt_data.dt_text.dt_length,
824  &current_index, mime_transfer_encoding,
825  &transfer_decoding_buffer, &decoded_data_bytes);
826  if( r != MAILIMF_NO_ERROR || transfer_decoding_buffer == NULL || decoded_data_bytes <= 0 ) {
827  return 0;
828  }
829  decoded_data = transfer_decoding_buffer;
830  }
831 
832  *ret_decoded_data = decoded_data;
833  *ret_decoded_data_bytes = decoded_data_bytes;
834  *ret_to_mmap_string_unref = transfer_decoding_buffer;
835  return 1;
836 }
837 
838 
839 static int mrmimeparser_add_single_part_if_known(mrmimeparser_t* ths, struct mailmime* mime)
840 {
841  mrmimepart_t* part = mrmimepart_new();
842  int do_add_part = 0;
843 
844  int mime_type;
845  struct mailmime_data* mime_data;
846  char* pathNfilename = NULL;
847  char* file_suffix = NULL, *desired_filename = NULL;
848  int msg_type;
849 
850  char* transfer_decoding_buffer = NULL; /* mmap_string_unref()'d if set */
851  char* charset_buffer = NULL; /* charconv_buffer_free()'d if set (just calls mmap_string_unref()) */
852  const char* decoded_data = NULL; /* must not be free()'d */
853  size_t decoded_data_bytes = 0;
854  mrsimplify_t* simplifier = NULL;
855 
856  if( mime == NULL || mime->mm_data.mm_single == NULL || part == NULL ) {
857  goto cleanup;
858  }
859 
860  /* get mime type from `mime` */
861  mime_type = mrmimeparser_get_mime_type(mime, &msg_type);
862 
863  /* get data pointer from `mime` */
864  mime_data = mime->mm_data.mm_single;
865  if( mime_data->dt_type != MAILMIME_DATA_TEXT /* MAILMIME_DATA_FILE indicates, the data is in a file; AFAIK this is not used on parsing */
866  || mime_data->dt_data.dt_text.dt_data == NULL
867  || mime_data->dt_data.dt_text.dt_length <= 0 ) {
868  goto cleanup;
869  }
870 
871 
872  /* regard `Content-Transfer-Encoding:` */
873  if( !mr_mime_transfer_decode(mime, &decoded_data, &decoded_data_bytes, &transfer_decoding_buffer) ) {
874  goto cleanup; /* no always error - but no data */
875  }
876 
877  switch( mime_type )
878  {
879  case MR_MIMETYPE_TEXT_PLAIN:
880  case MR_MIMETYPE_TEXT_HTML:
881  {
882  if( simplifier==NULL ) {
883  simplifier = mrsimplify_new();
884  if( simplifier==NULL ) {
885  goto cleanup;
886  }
887  }
888 
889  const char* charset = mailmime_content_charset_get(mime->mm_content_type); /* get from `Content-Type: text/...; charset=utf-8`; must not be free()'d */
890  if( charset!=NULL && strcmp(charset, "utf-8")!=0 && strcmp(charset, "UTF-8")!=0 ) {
891  size_t ret_bytes = 0;
892  int r = charconv_buffer("utf-8", charset, decoded_data, decoded_data_bytes, &charset_buffer, &ret_bytes);
893  if( r != MAIL_CHARCONV_NO_ERROR ) {
894  mrmailbox_log_warning(ths->m_mailbox, 0, "Cannot convert %i bytes from \"%s\" to \"utf-8\"; errorcode is %i.", /* if this warning comes up for usual character sets, maybe libetpan is compiled without iconv? */
895  (int)decoded_data_bytes, charset, (int)r); /* continue, however */
896  }
897  else if( charset_buffer==NULL || ret_bytes <= 0 ) {
898  goto cleanup; /* no error - but nothing to add */
899  }
900  else {
901  decoded_data = charset_buffer;
902  decoded_data_bytes = ret_bytes;
903  }
904  }
905 
906  part->m_type = MR_MSG_TEXT;
907  part->m_msg_raw = strndup(decoded_data, decoded_data_bytes);
908  part->m_msg = mrsimplify_simplify(simplifier, decoded_data, decoded_data_bytes, mime_type==MR_MIMETYPE_TEXT_HTML? 1 : 0);
909 
910  if( part->m_msg && part->m_msg[0] ) {
911  do_add_part = 1;
912  }
913 
914  if( simplifier->m_is_forwarded ) {
915  ths->m_is_forwarded = 1;
916  }
917  }
918  break;
919 
920  case MR_MIMETYPE_IMAGE:
921  case MR_MIMETYPE_AUDIO:
922  case MR_MIMETYPE_VIDEO:
923  case MR_MIMETYPE_FILE:
924  {
925  /* get desired file name */
926  struct mailmime_disposition* file_disposition = NULL; /* must not be free()'d */
927  clistiter* cur;
928  for( cur = clist_begin(mime->mm_mime_fields->fld_list); cur != NULL; cur = clist_next(cur) ) {
929  struct mailmime_field* field = (struct mailmime_field*)clist_content(cur);
930  if( field && field->fld_type == MAILMIME_FIELD_DISPOSITION && field->fld_data.fld_disposition ) {
931  file_disposition = field->fld_data.fld_disposition;
932  break;
933  }
934  }
935 
936  if( file_disposition ) {
937  for( cur = clist_begin(file_disposition->dsp_parms); cur != NULL; cur = clist_next(cur) ) {
938  struct mailmime_disposition_parm* dsp_param = (struct mailmime_disposition_parm*)clist_content(cur);
939  if( dsp_param ) {
940  if( dsp_param->pa_type==MAILMIME_DISPOSITION_PARM_FILENAME ) {
941  desired_filename = safe_strdup(dsp_param->pa_data.pa_filename);
942  }
943  }
944  }
945  }
946 
947  if( desired_filename==NULL ) {
948  struct mailmime_parameter* param = mr_find_ct_parameter(mime, "name");
949  if( param && param->pa_value && param->pa_value[0] ) {
950  desired_filename = safe_strdup(param->pa_value);
951  }
952  }
953 
954  if( desired_filename==NULL ) {
955  if( mime->mm_content_type && mime->mm_content_type->ct_subtype ) {
956  desired_filename = mr_mprintf("file.%s", mime->mm_content_type->ct_subtype);
957  }
958  else {
959  goto cleanup;
960  }
961  }
962 
963  mr_replace_bad_utf8_chars(desired_filename);
964 
965  /* create a free file name to use */
966  if( (pathNfilename=mr_get_fine_pathNfilename(ths->m_blobdir, desired_filename)) == NULL ) {
967  goto cleanup;
968  }
969 
970  /* copy data to file */
971  if( mr_write_file(pathNfilename, decoded_data, decoded_data_bytes, ths->m_mailbox)==0 ) {
972  goto cleanup;
973  }
974 
975  part->m_type = msg_type;
976  part->m_bytes = decoded_data_bytes;
977  mrparam_set(part->m_param, MRP_FILE, pathNfilename);
978  if( MR_MSG_MAKE_FILENAME_SEARCHABLE(msg_type) ) {
979  part->m_msg = mr_get_filename(pathNfilename);
980  }
981  else if( MR_MSG_MAKE_SUFFIX_SEARCHABLE(msg_type) ) {
982  part->m_msg = mr_get_filesuffix_lc(pathNfilename);
983  }
984 
985  if( mime_type == MR_MIMETYPE_IMAGE ) {
986  uint32_t w = 0, h = 0;
987  if( mr_get_filemeta(decoded_data, decoded_data_bytes, &w, &h) ) {
988  mrparam_set_int(part->m_param, MRP_WIDTH, w);
989  mrparam_set_int(part->m_param, MRP_HEIGHT, h);
990  }
991  }
992 
993  /* split author/title from the original filename (if we do it from the real filename, we'll also get numbers appended by mr_get_fine_pathNfilename()) */
994  if( msg_type == MR_MSG_AUDIO ) {
995  char* author = NULL, *title = NULL;
996  mrmsg_get_authorNtitle_from_filename(desired_filename, &author, &title);
997  mrparam_set(part->m_param, MRP_AUTHORNAME, author);
998  mrparam_set(part->m_param, MRP_TRACKNAME, title);
999  free(author);
1000  free(title);
1001  }
1002 
1003  do_add_part = 1;
1004  }
1005  break;
1006 
1007  default:
1008  break;
1009  }
1010 
1011  /* add object? (we do not add all objetcs, eg. signatures etc. are ignored) */
1012 cleanup:
1013  if( simplifier ) {
1014  mrsimplify_unref(simplifier);
1015  }
1016 
1017  if( charset_buffer ) {
1018  charconv_buffer_free(charset_buffer);
1019  }
1020 
1021  if( transfer_decoding_buffer ) {
1022  mmap_string_unref(transfer_decoding_buffer);
1023  }
1024 
1025  free(pathNfilename);
1026  free(file_suffix);
1027  free(desired_filename);
1028 
1029  if( do_add_part ) {
1030  if( ths->m_decrypted_and_validated ) {
1031  mrparam_set_int(part->m_param, MRP_GUARANTEE_E2EE, 1);
1032  }
1033  else if( ths->m_decrypted_with_validation_errors ) {
1034  mrparam_set_int(part->m_param, MRP_ERRONEOUS_E2EE, ths->m_decrypted_with_validation_errors);
1035  }
1036  carray_add(ths->m_parts, (void*)part, NULL);
1037  return 1; /* part used */
1038  }
1039  else {
1040  mrmimepart_unref(part);
1041  return 0;
1042  }
1043 }
1044 
1045 
1046 static int mrmimeparser_parse_mime_recursive(mrmimeparser_t* ths, struct mailmime* mime)
1047 {
1048  int any_part_added = 0;
1049  clistiter* cur;
1050 
1051  if( ths == NULL || mime == NULL ) {
1052  return 0;
1053  }
1054 
1055  switch( mime->mm_type )
1056  {
1057  case MAILMIME_SINGLE:
1058  any_part_added = mrmimeparser_add_single_part_if_known(ths, mime);
1059  break;
1060 
1061  case MAILMIME_MULTIPLE:
1062  switch( mrmimeparser_get_mime_type(mime, NULL) )
1063  {
1064  case MR_MIMETYPE_MP_ALTERNATIVE: /* add "best" part */
1065  /* Most times, mutlipart/alternative contains true alternatives as text/plain and text/html.
1066  If we find a multipart/mixed inside mutlipart/alternative, we use this (happens eg in apple mail: "plaintext" as an alternative to "html+PDF attachment") */
1067  for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
1068  struct mailmime* childmime = (struct mailmime*)clist_content(cur);
1069  if( mrmimeparser_get_mime_type(childmime, NULL) == MR_MIMETYPE_MP_MIXED ) {
1070  any_part_added = mrmimeparser_parse_mime_recursive(ths, childmime);
1071  break;
1072  }
1073  }
1074 
1075 
1076  if( !any_part_added ) {
1077  /* search for text/plain and add this */
1078  for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
1079  struct mailmime* childmime = (struct mailmime*)clist_content(cur);
1080  if( mrmimeparser_get_mime_type(childmime, NULL) == MR_MIMETYPE_TEXT_PLAIN ) {
1081  any_part_added = mrmimeparser_parse_mime_recursive(ths, childmime);
1082  break;
1083  }
1084  }
1085  }
1086 
1087  if( !any_part_added ) { /* `text/plain` not found - use the first part */
1088  for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
1089  if( mrmimeparser_parse_mime_recursive(ths, (struct mailmime*)clist_content(cur)) ) {
1090  any_part_added = 1;
1091  break; /* out of for() */
1092  }
1093  }
1094  }
1095  break;
1096 
1097  case MR_MIMETYPE_MP_RELATED: /* add the "root part" - the other parts may be referenced which is not interesting for us (eg. embedded images) */
1098  /* we assume he "root part" being the first one, which may not be always true ... however, most times it seems okay. */
1099  cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list);
1100  if( cur ) {
1101  any_part_added = mrmimeparser_parse_mime_recursive(ths, (struct mailmime*)clist_content(cur));
1102  }
1103  break;
1104 
1105  case MR_MIMETYPE_MP_NOT_DECRYPTABLE:
1106  {
1107  mrmimepart_t* part = mrmimepart_new();
1108  part->m_type = MR_MSG_TEXT;
1109  part->m_msg = mrstock_str(MR_STR_ENCRYPTEDMSG); /* not sure if the text "Encrypted message" is 100% sufficient here (bp) */
1110  carray_add(ths->m_parts, (void*)part, NULL);
1111  any_part_added = 1;
1112  ths->m_decrypting_failed = 1;
1113  }
1114  break;
1115 
1116  case MR_MIMETYPE_MP_SIGNED:
1117  /* RFC 1847: "The multipart/signed content type contains exactly two body parts.
1118  The first body part is the body part over which the digital signature was created [...]
1119  The second body part contains the control information necessary to verify the digital signature."
1120  We simpliy take the first body part and skip the rest.
1121  (see https://k9mail.github.io/2016/11/24/OpenPGP-Considerations-Part-I.html for background information why we use encrypted+signed) */
1122  if( (cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list)) != NULL )
1123  {
1124  any_part_added = mrmimeparser_parse_mime_recursive(ths, (struct mailmime*)clist_content(cur));
1125  }
1126  break;
1127 
1128  case MR_MIMETYPE_MP_REPORT:
1129  if( clist_count(mime->mm_data.mm_multipart.mm_mp_list) >= 2 ) /* RFC 6522: the first part is for humans, the second for machines */
1130  {
1131  struct mailmime_parameter* report_type = mr_find_ct_parameter(mime, "report-type");
1132  if( report_type && report_type->pa_value
1133  && strcmp(report_type->pa_value, "disposition-notification") == 0 )
1134  {
1135  carray_add(ths->m_reports, (void*)mime, NULL);
1136  }
1137  else
1138  {
1139  /* eg. `report-type=delivery-status`; maybe we should show them as a little error icon */
1140  any_part_added = mrmimeparser_parse_mime_recursive(ths, (struct mailmime*)clist_content(clist_begin(mime->mm_data.mm_multipart.mm_mp_list)));
1141  }
1142  }
1143  break;
1144 
1145  default: /* eg. MR_MIME_MP_MIXED - add all parts (in fact, AddSinglePartIfKnown() later check if the parts are really supported) */
1146  {
1147  /* HACK: the following lines are a hack for clients who use multipart/mixed instead of multipart/alternative for
1148  combined text/html messages (eg. Stock Android "Mail" does so). So, if I detect such a message below, I skip the HTML part.
1149  However, I'm not sure, if there are useful situations to use plain+html in multipart/mixed - if so, we should disable the hack. */
1150  struct mailmime* skip_part = NULL;
1151  {
1152  struct mailmime* html_part = NULL;
1153  int plain_cnt = 0, html_cnt = 0;
1154  for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
1155  struct mailmime* childmime = (struct mailmime*)clist_content(cur);
1156  if( mrmimeparser_get_mime_type(childmime, NULL) == MR_MIMETYPE_TEXT_PLAIN ) {
1157  plain_cnt++;
1158  }
1159  else if( mrmimeparser_get_mime_type(childmime, NULL) == MR_MIMETYPE_TEXT_HTML ) {
1160  html_part = childmime;
1161  html_cnt++;
1162  }
1163  }
1164  if( plain_cnt==1 && html_cnt==1 ) {
1165  mrmailbox_log_warning(ths->m_mailbox, 0, "HACK: multipart/mixed message found with PLAIN and HTML, we'll skip the HTML part as this seems to be unwanted.");
1166  skip_part = html_part;
1167  }
1168  }
1169  /* /HACK */
1170 
1171  for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
1172  struct mailmime* childmime = (struct mailmime*)clist_content(cur);
1173  if( childmime != skip_part ) {
1174  if( mrmimeparser_parse_mime_recursive(ths, childmime) ) {
1175  any_part_added = 1;
1176  }
1177  }
1178  }
1179  }
1180  break;
1181  }
1182  break;
1183 
1184  case MAILMIME_MESSAGE:
1185  if( ths->m_header == NULL && mime->mm_data.mm_message.mm_fields )
1186  {
1187  ths->m_header = mime->mm_data.mm_message.mm_fields;
1188  for( cur = clist_begin(ths->m_header->fld_list); cur!=NULL ; cur=clist_next(cur) ) {
1189  struct mailimf_field* field = (struct mailimf_field*)clist_content(cur);
1190  if( field->fld_type == MAILIMF_FIELD_SUBJECT ) {
1191  if( ths->m_subject == NULL && field->fld_data.fld_subject ) {
1192  ths->m_subject = mr_decode_header_string(field->fld_data.fld_subject->sbj_value);
1193  }
1194  }
1195  else if( field->fld_type == MAILIMF_FIELD_OPTIONAL_FIELD ) {
1196  struct mailimf_optional_field* optional_field = field->fld_data.fld_optional_field;
1197  if( optional_field ) {
1198  if( strcasecmp(optional_field->fld_name, "X-MrMsg")==0 || strcasecmp(optional_field->fld_name, "Chat-Version")==0 ) {
1199  ths->m_is_send_by_messenger = 1;
1200  }
1201  }
1202  }
1203  }
1204  }
1205 
1206  if( mime->mm_data.mm_message.mm_msg_mime )
1207  {
1208  any_part_added = mrmimeparser_parse_mime_recursive(ths, mime->mm_data.mm_message.mm_msg_mime);
1209  }
1210  break;
1211  }
1212 
1213  return any_part_added;
1214 }
1215 
1216 
1217 static struct mailimf_optional_field* mrmimeparser_find_xtra_field(mrmimeparser_t* ths, const char* wanted_fld_name)
1218 {
1219  return mr_find_mailimf_field2(ths->m_header, wanted_fld_name);
1220 }
1221 
1222 
1223 void mrmimeparser_parse(mrmimeparser_t* ths, const char* body_not_terminated, size_t body_bytes)
1224 {
1225  int r;
1226  size_t index = 0;
1227 
1228  mrmimeparser_empty(ths);
1229 
1230  /* parse body */
1231  r = mailmime_parse(body_not_terminated, body_bytes, &index, &ths->m_mimeroot);
1232  if(r != MAILIMF_NO_ERROR || ths->m_mimeroot == NULL ) {
1233  goto cleanup;
1234  }
1235 
1236  #if 0
1237  printf("-----------------------------------------------------------------------\n");
1238  mr_print_mime(m_mimeroot);
1239  printf("-----------------------------------------------------------------------\n");
1240  #endif
1241 
1242  /* decrypt, if possible; handle Autocrypt:-header
1243  (decryption may modifiy the given object) */
1244  int validation_errors = 0;
1245  if( mrmailbox_e2ee_decrypt(ths->m_mailbox, ths->m_mimeroot, &validation_errors) ) {
1246  if( validation_errors == 0 ) {
1247  ths->m_decrypted_and_validated = 1;
1248  }
1249  else {
1250  ths->m_decrypted_with_validation_errors = validation_errors;
1251  }
1252  }
1253 
1254  /* recursively check, whats parsed */
1255  mrmimeparser_parse_mime_recursive(ths, ths->m_mimeroot);
1256 
1257  /* prepend subject to message? */
1258  if( ths->m_subject )
1259  {
1260  int prepend_subject = 1;
1261  if( !ths->m_decrypting_failed /* if decryption has failed, we always prepend the subject as this may contain cleartext hints from non-Delta MUAs. */ )
1262  {
1263  char* p = strchr(ths->m_subject, ':');
1264  if( (p-ths->m_subject) == 2 /*Re: etc.*/
1265  || (p-ths->m_subject) == 3 /*Fwd: etc.*/
1266  || ths->m_is_send_by_messenger
1267  || strstr(ths->m_subject, MR_CHAT_PREFIX)!=NULL ) {
1268  prepend_subject = 0;
1269  }
1270  }
1271 
1272  if( prepend_subject )
1273  {
1274  char* subj = safe_strdup(ths->m_subject);
1275  char* p = strchr(subj, '['); /* do not add any tags as "[checked by XYZ]" */
1276  if( p ) {
1277  *p = 0;
1278  }
1279  mr_trim(subj);
1280  if( subj[0] ) {
1281  int i, icnt = carray_count(ths->m_parts); /* should be at least one - maybe empty - part */
1282  for( i = 0; i < icnt; i++ ) {
1283  mrmimepart_t* part = (mrmimepart_t*)carray_get(ths->m_parts, i);
1284  if( part->m_type == MR_MSG_TEXT ) {
1285  #define MR_NDASH "\xE2\x80\x93"
1286  char* new_txt = mr_mprintf("%s " MR_NDASH " %s", subj, part->m_msg);
1287  free(part->m_msg);
1288  part->m_msg = new_txt;
1289  break;
1290  }
1291  }
1292  }
1293  free(subj);
1294  }
1295  }
1296 
1297  /* add forward information to every part */
1298  if( ths->m_is_forwarded ) {
1299  int i, icnt = carray_count(ths->m_parts); /* should be at least one - maybe empty - part */
1300  for( i = 0; i < icnt; i++ ) {
1301  mrmimepart_t* part = (mrmimepart_t*)carray_get(ths->m_parts, i);
1302  mrparam_set_int(part->m_param, MRP_FORWARDED, 1);
1303  }
1304  }
1305 
1306  if( carray_count(ths->m_parts)==1 )
1307  {
1308  /* mark audio as voice message, if appropriate (we have to do this on global level as we do not know the global header in the recursice parse).
1309  and read some additional parameters */
1310  mrmimepart_t* part = (mrmimepart_t*)carray_get(ths->m_parts, 0);
1311  if( part->m_type == MR_MSG_AUDIO ) {
1312  if( mrmimeparser_find_xtra_field(ths, "X-MrVoiceMessage") || mrmimeparser_find_xtra_field(ths, "Chat-Voice-Message") ) {
1313  free(part->m_msg);
1314  part->m_msg = strdup("ogg"); /* MR_MSG_AUDIO adds sets the whole filename which is useless. however, the extension is useful. */
1315  part->m_type = MR_MSG_VOICE;
1316  mrparam_set(part->m_param, MRP_AUTHORNAME, NULL); /* remove unneeded information */
1317  mrparam_set(part->m_param, MRP_TRACKNAME, NULL);
1318  }
1319  }
1320 
1321  if( part->m_type == MR_MSG_AUDIO || part->m_type == MR_MSG_VOICE || part->m_type == MR_MSG_VIDEO ) {
1322  const struct mailimf_optional_field* field = mrmimeparser_find_xtra_field(ths, "X-MrDurationMs");
1323  if( field==NULL ) { field = mrmimeparser_find_xtra_field(ths, "Chat-Duration"); }
1324  if( field ) {
1325  int duration_ms = atoi(field->fld_value);
1326  if( duration_ms > 0 && duration_ms < 24*60*60*1000 ) {
1327  mrparam_set_int(part->m_param, MRP_DURATION, duration_ms);
1328  }
1329  }
1330  }
1331  }
1332 
1333  /* some special system message? */
1334  if( mrmimeparser_find_xtra_field(ths, "Chat-Group-Image")
1335  && carray_count(ths->m_parts)>=1 ) {
1336  mrmimepart_t* textpart = (mrmimepart_t*)carray_get(ths->m_parts, 0);
1337  if( textpart->m_type == MR_MSG_TEXT ) {
1338  mrparam_set_int(textpart->m_param, MRP_SYSTEM_CMD, MR_SYSTEM_GROUPIMAGE_CHANGED);
1339  if( carray_count(ths->m_parts)>=2 ) {
1340  mrmimepart_t* imgpart = (mrmimepart_t*)carray_get(ths->m_parts, 1);
1341  if( imgpart->m_type == MR_MSG_IMAGE ) {
1342  imgpart->m_is_meta = 1;
1343  }
1344  }
1345  }
1346  }
1347 
1348  /* check, if the message asks for a MDN */
1349  if( !ths->m_decrypting_failed )
1350  {
1351  const struct mailimf_optional_field* dn_field = mrmimeparser_find_xtra_field(ths, "Chat-Disposition-Notification-To"); /* we use "Chat-Disposition-Notification-To" as replies to "Disposition-Notification-To" are weired in many cases, are just freetext and/or do not follow any standard. */
1352  if( dn_field && mrmimeparser_get_last_nonmeta(ths)/*just check if the mail is not empty*/ )
1353  {
1354  struct mailimf_mailbox_list* mb_list = NULL;
1355  size_t index = 0;
1356  if( mailimf_mailbox_list_parse(dn_field->fld_value, strlen(dn_field->fld_value), &index, &mb_list)==MAILIMF_NO_ERROR && mb_list )
1357  {
1358  char* dn_to_addr = mr_find_first_addr(mb_list);
1359  if( dn_to_addr )
1360  {
1361  struct mailimf_field* from_field = mr_find_mailimf_field(ths->m_header, MAILIMF_FIELD_FROM); /* we need From: as this MUST match Disposition-Notification-To: */
1362  if( from_field && from_field->fld_data.fld_from )
1363  {
1364  char* from_addr = mr_find_first_addr(from_field->fld_data.fld_from->frm_mb_list);
1365  if( from_addr )
1366  {
1367  if( strcmp(from_addr, dn_to_addr)==0 )
1368  {
1369  /* we mark _only_ the _last_ part to send a MDN
1370  (this avoids trouble with multi-part-messages who should send only one MDN.
1371  Moreover the last one is handy as it is the one typically displayed if the message is larger) */
1372  mrmimepart_t* part = mrmimeparser_get_last_nonmeta(ths);
1373  if( part ) {
1374  mrparam_set_int(part->m_param, MRP_WANTS_MDN, 1);
1375  }
1376  }
1377  free(from_addr);
1378  }
1379  }
1380  free(dn_to_addr);
1381  }
1382  mailimf_mailbox_list_free(mb_list);
1383  }
1384  }
1385  }
1386 
1387  /* Cleanup - and try to create at least an empty part if there are no parts yet */
1388 cleanup:
1389  if( !mrmimeparser_has_nonmeta(ths) && carray_count(ths->m_reports)==0 ) {
1390  mrmimepart_t* part = mrmimepart_new();
1391  part->m_type = MR_MSG_TEXT;
1392  part->m_msg = safe_strdup(ths->m_subject? ths->m_subject : "Empty message");
1393  carray_add(ths->m_parts, (void*)part, NULL);
1394  }
1395 }
1396 
1397 
1398 mrmimepart_t* mrmimeparser_get_last_nonmeta(mrmimeparser_t* ths)
1399 {
1400  if( ths && ths->m_parts ) {
1401  int i, icnt = carray_count(ths->m_parts);
1402  for( i = icnt-1; i >= 0; i-- ) {
1403  mrmimepart_t* part = (mrmimepart_t*)carray_get(ths->m_parts, i);
1404  if( part && !part->m_is_meta ) {
1405  return part;
1406  }
1407  }
1408  }
1409  return NULL;
1410 }
1411 
1412 
1413 int mrmimeparser_is_mailinglist_message(mrmimeparser_t* ths)
1414 {
1415  /* the function checks if the header of the mail looks as if it is a message from a mailing list
1416 
1417  Some statistics:
1418  => sorted out by `List-ID`-header:
1419  - Mailman mailing list messages - OK, mass messages
1420  - Xing forum/event notifications - OK, mass messages
1421  - Xing welcome-back, contact-reqest - Hm, but it _has_ the List-ID header
1422 
1423  => sorted out by `Precedence`-header:
1424  - Majordomo mailing list messages - OK, mass messages
1425 
1426  => NOT sorted out:
1427  - Pingdom notifications - OK, individual message
1428  - Paypal notifications - OK, individual message
1429  - Linked in visits, do-you-know - OK, individual message
1430  - Share-It notifications - OK, individual message
1431  - Transifex, Github notifications - OK, individual message
1432  */
1433 
1434  if( ths == NULL ) {
1435  return 0;
1436  }
1437 
1438  if( mr_find_mailimf_field2(ths->m_header, "List-Id") != NULL ) {
1439  return 1; /* mailing list identified by the presence of `List-ID` from RFC 2919 */
1440  }
1441 
1442  struct mailimf_optional_field* precedence = mr_find_mailimf_field2(ths->m_header, "Precedence");
1443  if( precedence != NULL ) {
1444  if( strcasecmp(precedence->fld_value, "list")==0
1445  || strcasecmp(precedence->fld_value, "bulk")==0 ) {
1446  return 1; /* mailing list identified by the presence of `Precedence: bulk` or `Precedence: list` from RFC 3834 */
1447  }
1448  }
1449 
1450  return 0;
1451 }
1452 
An object representing a single mailbox.
Definition: mrmailbox.h:141
void mrparam_unref(mrparam_t *param)
Free an parameter list object created eg.
Definition: mrparam.c:90
void mrparam_set(mrparam_t *param, int key, const char *value)
Set parameter to a string.
Definition: mrparam.c:253
mrparam_t * mrparam_new()
Create new parameter list object.
Definition: mrparam.c:69
void mrparam_set_int(mrparam_t *param, int key, int32_t value)
Set parameter to an integer.
Definition: mrparam.c:318