mirror of
https://github.com/deltachat/deltachat-core.git
synced 2025-10-05 02:29:28 +02:00
1901 lines
61 KiB
C
1901 lines
61 KiB
C
/*******************************************************************************
|
|
*
|
|
* Delta Chat Core
|
|
* Copyright (C) 2017 Björn Petersen
|
|
* Contact: r10s@b44t.com, http://b44t.com
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
* Foundation, either version 3 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see http://www.gnu.org/licenses/ .
|
|
*
|
|
******************************************************************************/
|
|
|
|
|
|
#include "dc_context.h"
|
|
#include "dc_mimeparser.h"
|
|
#include "dc_mimefactory.h"
|
|
#include "dc_uudecode.h"
|
|
#include "dc_pgp.h"
|
|
#include "dc_simplify.h"
|
|
|
|
|
|
/*******************************************************************************
|
|
* debug output
|
|
******************************************************************************/
|
|
|
|
|
|
#ifdef DC_USE_MIME_DEBUG
|
|
|
|
/* if you need this functionality, define DC_USE_MIME_DEBUG in the project,
|
|
eg. in Codeblocks at "Project / Build options / <project or target> / Compiler settings / #defines" */
|
|
|
|
|
|
static void display_mime_content(struct mailmime_content * content_type);
|
|
|
|
static void display_mime_data(struct mailmime_data * data)
|
|
{
|
|
switch (data->dt_type) {
|
|
case MAILMIME_DATA_TEXT:
|
|
printf("data : %i bytes\n", (int) data->dt_data.dt_text.dt_length);
|
|
break;
|
|
case MAILMIME_DATA_FILE:
|
|
printf("data (file) : %s\n", data->dt_data.dt_filename);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void display_mime_dsp_parm(struct mailmime_disposition_parm * param)
|
|
{
|
|
switch (param->pa_type) {
|
|
case MAILMIME_DISPOSITION_PARM_FILENAME:
|
|
printf("filename: %s\n", param->pa_data.pa_filename);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void display_mime_disposition(struct mailmime_disposition * disposition)
|
|
{
|
|
clistiter * cur;
|
|
|
|
for(cur = clist_begin(disposition->dsp_parms) ;
|
|
cur != NULL ; cur = clist_next(cur)) {
|
|
struct mailmime_disposition_parm * param;
|
|
|
|
param = (struct mailmime_disposition_parm*)clist_content(cur);
|
|
display_mime_dsp_parm(param);
|
|
}
|
|
}
|
|
|
|
static void display_mime_field(struct mailmime_field * field)
|
|
{
|
|
switch (field->fld_type) {
|
|
case MAILMIME_FIELD_VERSION:
|
|
printf("MIME-Version: ...\n");
|
|
break;
|
|
|
|
case MAILMIME_FIELD_TYPE:
|
|
printf("content-type: ");
|
|
display_mime_content(field->fld_data.fld_content);
|
|
printf("\n");
|
|
break;
|
|
|
|
case MAILMIME_FIELD_DISPOSITION:
|
|
display_mime_disposition(field->fld_data.fld_disposition);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void display_mime_fields(struct mailmime_fields * fields)
|
|
{
|
|
clistiter * cur;
|
|
|
|
for(cur = clist_begin(fields->fld_list) ; cur != NULL ; cur = clist_next(cur)) {
|
|
struct mailmime_field * field;
|
|
|
|
field = (struct mailmime_field*)clist_content(cur);
|
|
display_mime_field(field);
|
|
}
|
|
}
|
|
|
|
static void display_date_time(struct mailimf_date_time * d)
|
|
{
|
|
printf("%02i/%02i/%i %02i:%02i:%02i %+04i",
|
|
d->dt_day, d->dt_month, d->dt_year,
|
|
d->dt_hour, d->dt_min, d->dt_sec, d->dt_zone);
|
|
}
|
|
|
|
static void display_orig_date(struct mailimf_orig_date * orig_date)
|
|
{
|
|
display_date_time(orig_date->dt_date_time);
|
|
}
|
|
|
|
static void display_mailbox(struct mailimf_mailbox * mb)
|
|
{
|
|
if (mb->mb_display_name != NULL)
|
|
printf("%s ", mb->mb_display_name);
|
|
printf("<%s>", mb->mb_addr_spec);
|
|
}
|
|
|
|
static void display_mailbox_list(struct mailimf_mailbox_list * mb_list)
|
|
{
|
|
clistiter * cur;
|
|
|
|
for(cur = clist_begin(mb_list->mb_list) ; cur != NULL ;
|
|
cur = clist_next(cur)) {
|
|
struct mailimf_mailbox * mb;
|
|
|
|
mb = (struct mailimf_mailbox*)clist_content(cur);
|
|
|
|
display_mailbox(mb);
|
|
if (clist_next(cur) != NULL) {
|
|
printf(", ");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void display_group(struct mailimf_group * group)
|
|
{
|
|
clistiter * cur;
|
|
|
|
printf("%s: ", group->grp_display_name);
|
|
for(cur = clist_begin(group->grp_mb_list->mb_list) ; cur != NULL ; cur = clist_next(cur)) {
|
|
struct mailimf_mailbox * mb;
|
|
|
|
mb = (struct mailimf_mailbox*)clist_content(cur);
|
|
display_mailbox(mb);
|
|
}
|
|
printf("; ");
|
|
}
|
|
|
|
static void display_address(struct mailimf_address * a)
|
|
{
|
|
switch (a->ad_type) {
|
|
case MAILIMF_ADDRESS_GROUP:
|
|
display_group(a->ad_data.ad_group);
|
|
break;
|
|
|
|
case MAILIMF_ADDRESS_MAILBOX:
|
|
display_mailbox(a->ad_data.ad_mailbox);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void display_address_list(struct mailimf_address_list * addr_list)
|
|
{
|
|
clistiter * cur;
|
|
|
|
for(cur = clist_begin(addr_list->ad_list) ; cur != NULL ;
|
|
cur = clist_next(cur)) {
|
|
struct mailimf_address * addr;
|
|
|
|
addr = (struct mailimf_address*)clist_content(cur);
|
|
|
|
display_address(addr);
|
|
|
|
if (clist_next(cur) != NULL) {
|
|
printf(", ");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void display_from(struct mailimf_from * from)
|
|
{
|
|
display_mailbox_list(from->frm_mb_list);
|
|
}
|
|
|
|
static void display_to(struct mailimf_to * to)
|
|
{
|
|
display_address_list(to->to_addr_list);
|
|
}
|
|
|
|
static void display_cc(struct mailimf_cc * cc)
|
|
{
|
|
display_address_list(cc->cc_addr_list);
|
|
}
|
|
|
|
static void display_subject(struct mailimf_subject * subject)
|
|
{
|
|
printf("%s", subject->sbj_value);
|
|
}
|
|
|
|
static void display_field(struct mailimf_field * field)
|
|
{
|
|
switch (field->fld_type)
|
|
{
|
|
case MAILIMF_FIELD_ORIG_DATE:
|
|
printf("Date: ");
|
|
display_orig_date(field->fld_data.fld_orig_date);
|
|
printf("\n");
|
|
break;
|
|
|
|
case MAILIMF_FIELD_FROM:
|
|
printf("From: ");
|
|
display_from(field->fld_data.fld_from);
|
|
printf("\n");
|
|
break;
|
|
|
|
case MAILIMF_FIELD_TO:
|
|
printf("To: ");
|
|
display_to(field->fld_data.fld_to);
|
|
printf("\n");
|
|
break;
|
|
|
|
case MAILIMF_FIELD_CC:
|
|
printf("Cc: ");
|
|
display_cc(field->fld_data.fld_cc);
|
|
printf("\n");
|
|
break;
|
|
|
|
case MAILIMF_FIELD_SUBJECT:
|
|
printf("Subject: ");
|
|
display_subject(field->fld_data.fld_subject);
|
|
printf("\n");
|
|
break;
|
|
|
|
case MAILIMF_FIELD_MESSAGE_ID:
|
|
printf("Message-ID: %s\n", field->fld_data.fld_message_id->mid_value);
|
|
break;
|
|
|
|
case MAILIMF_FIELD_OPTIONAL_FIELD:
|
|
{
|
|
struct mailimf_optional_field* of = field->fld_data.fld_optional_field;
|
|
if( of ) {
|
|
printf("%s: %s\n", of->fld_name? of->fld_name : "?", of->fld_value? of->fld_value : "?");
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
printf("MAILIMF_FIELD_%i\n", (int)field->fld_type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void display_fields(struct mailimf_fields * fields)
|
|
{
|
|
clistiter * cur;
|
|
|
|
for(cur = clist_begin(fields->fld_list) ; cur != NULL ;
|
|
cur = clist_next(cur)) {
|
|
struct mailimf_field * f;
|
|
|
|
f = (struct mailimf_field*)clist_content(cur);
|
|
|
|
display_field(f);
|
|
}
|
|
}
|
|
|
|
static void display_mime_discrete_type(struct mailmime_discrete_type * discrete_type)
|
|
{
|
|
switch (discrete_type->dt_type) {
|
|
case MAILMIME_DISCRETE_TYPE_TEXT:
|
|
printf("text");
|
|
break;
|
|
case MAILMIME_DISCRETE_TYPE_IMAGE:
|
|
printf("image");
|
|
break;
|
|
case MAILMIME_DISCRETE_TYPE_AUDIO:
|
|
printf("audio");
|
|
break;
|
|
case MAILMIME_DISCRETE_TYPE_VIDEO:
|
|
printf("video");
|
|
break;
|
|
case MAILMIME_DISCRETE_TYPE_APPLICATION:
|
|
printf("application");
|
|
break;
|
|
case MAILMIME_DISCRETE_TYPE_EXTENSION:
|
|
printf("%s", discrete_type->dt_extension);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void display_mime_composite_type(struct mailmime_composite_type * ct)
|
|
{
|
|
switch (ct->ct_type) {
|
|
case MAILMIME_COMPOSITE_TYPE_MESSAGE:
|
|
printf("message");
|
|
break;
|
|
case MAILMIME_COMPOSITE_TYPE_MULTIPART:
|
|
printf("multipart");
|
|
break;
|
|
case MAILMIME_COMPOSITE_TYPE_EXTENSION:
|
|
printf("%s", ct->ct_token);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void display_mime_type(struct mailmime_type * type)
|
|
{
|
|
switch (type->tp_type) {
|
|
case MAILMIME_TYPE_DISCRETE_TYPE:
|
|
display_mime_discrete_type(type->tp_data.tp_discrete_type);
|
|
break;
|
|
case MAILMIME_TYPE_COMPOSITE_TYPE:
|
|
display_mime_composite_type(type->tp_data.tp_composite_type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void display_mime_content(struct mailmime_content * content_type)
|
|
{
|
|
printf("type: ");
|
|
display_mime_type(content_type->ct_type);
|
|
printf("/%s\n", content_type->ct_subtype);
|
|
}
|
|
|
|
static void print_mime(struct mailmime * mime)
|
|
{
|
|
clistiter * cur;
|
|
|
|
if( mime == NULL ) {
|
|
printf("ERROR: NULL given to print_mime()\n");
|
|
return;
|
|
}
|
|
|
|
switch (mime->mm_type) {
|
|
case MAILMIME_SINGLE:
|
|
printf("single part\n");
|
|
break;
|
|
case MAILMIME_MULTIPLE:
|
|
printf("multipart\n");
|
|
break;
|
|
case MAILMIME_MESSAGE:
|
|
printf("message\n");
|
|
break;
|
|
}
|
|
|
|
if (mime->mm_mime_fields != NULL) {
|
|
if (clist_begin(mime->mm_mime_fields->fld_list) != NULL) {
|
|
printf("--------------------------------<mime-headers>--------------------------------\n");
|
|
display_mime_fields(mime->mm_mime_fields);
|
|
printf("--------------------------------</mime-headers>-------------------------------\n");
|
|
}
|
|
}
|
|
|
|
display_mime_content(mime->mm_content_type);
|
|
|
|
switch (mime->mm_type) {
|
|
case MAILMIME_SINGLE:
|
|
display_mime_data(mime->mm_data.mm_single);
|
|
break;
|
|
|
|
case MAILMIME_MULTIPLE:
|
|
for(cur = clist_begin(mime->mm_data.mm_multipart.mm_mp_list) ; cur != NULL ; cur = clist_next(cur)) {
|
|
printf("---------------------------<mime-part-of-multiple>----------------------------\n");
|
|
print_mime((struct mailmime*)clist_content(cur));
|
|
printf("---------------------------</mime-part-of-multiple>---------------------------\n");
|
|
}
|
|
break;
|
|
|
|
case MAILMIME_MESSAGE:
|
|
if (mime->mm_data.mm_message.mm_fields) {
|
|
if (clist_begin(mime->mm_data.mm_message.mm_fields->fld_list) != NULL) {
|
|
printf("-------------------------------<email-headers>--------------------------------\n");
|
|
display_fields(mime->mm_data.mm_message.mm_fields);
|
|
printf("-------------------------------</email-headers>-------------------------------\n");
|
|
}
|
|
|
|
if (mime->mm_data.mm_message.mm_msg_mime != NULL) {
|
|
printf("----------------------------<mime-part-of-message>----------------------------\n");
|
|
print_mime(mime->mm_data.mm_message.mm_msg_mime);
|
|
printf("----------------------------</mime-part-of-message>---------------------------\n");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void mailmime_print(struct mailmime* mime)
|
|
{
|
|
printf("====================================<mime>====================================\n");
|
|
print_mime(mime);
|
|
printf("====================================</mime>===================================\n\n");
|
|
}
|
|
|
|
|
|
#endif /* DEBUG_MIME_OUTPUT */
|
|
|
|
|
|
/*******************************************************************************
|
|
* low-level-tools for getting a list of all recipients
|
|
******************************************************************************/
|
|
|
|
|
|
static void mailimf_get_recipients__add_addr(dc_hash_t* recipients, struct mailimf_mailbox* mb)
|
|
{
|
|
/* only used internally by mailimf_get_recipients() */
|
|
if( mb ) {
|
|
char* addr_norm = dc_normalize_addr(mb->mb_addr_spec);
|
|
dc_hash_insert(recipients, addr_norm, strlen(addr_norm), (void*)1);
|
|
free(addr_norm);
|
|
}
|
|
}
|
|
|
|
|
|
dc_hash_t* mailimf_get_recipients(struct mailimf_fields* imffields)
|
|
{
|
|
/* the returned value must be dc_hash_clear()'d and free()'d. returned addresses are normalized. */
|
|
dc_hash_t* recipients = malloc(sizeof(dc_hash_t));
|
|
dc_hash_init(recipients, DC_HASH_STRING, 1/*copy key*/);
|
|
|
|
clistiter* cur1;
|
|
for( cur1 = clist_begin(imffields->fld_list); cur1!=NULL ; cur1=clist_next(cur1) )
|
|
{
|
|
struct mailimf_field* fld = (struct mailimf_field*)clist_content(cur1);
|
|
struct mailimf_to* fld_to;
|
|
struct mailimf_cc* fld_cc;
|
|
struct mailimf_address_list* addr_list = NULL;
|
|
switch( fld->fld_type )
|
|
{
|
|
case MAILIMF_FIELD_TO: fld_to = fld->fld_data.fld_to; if( fld_to ) { addr_list = fld_to->to_addr_list; } break;
|
|
case MAILIMF_FIELD_CC: fld_cc = fld->fld_data.fld_cc; if( fld_cc ) { addr_list = fld_cc->cc_addr_list; } break;
|
|
}
|
|
|
|
if( addr_list ) {
|
|
clistiter* cur2;
|
|
for( cur2 = clist_begin(addr_list->ad_list); cur2!=NULL ; cur2=clist_next(cur2) ) {
|
|
struct mailimf_address* adr = (struct mailimf_address*)clist_content(cur2);
|
|
if( adr ) {
|
|
if( adr->ad_type == MAILIMF_ADDRESS_MAILBOX ) {
|
|
mailimf_get_recipients__add_addr(recipients, adr->ad_data.ad_mailbox);
|
|
}
|
|
else if( adr->ad_type == MAILIMF_ADDRESS_GROUP ) {
|
|
struct mailimf_group* group = adr->ad_data.ad_group;
|
|
if( group && group->grp_mb_list ) {
|
|
clistiter* cur3;
|
|
for( cur3 = clist_begin(group->grp_mb_list->mb_list); cur3!=NULL ; cur3=clist_next(cur3) ) {
|
|
mailimf_get_recipients__add_addr(recipients, (struct mailimf_mailbox*)clist_content(cur3));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return recipients;
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* low-level-tools for working with mailmime structures directly
|
|
******************************************************************************/
|
|
|
|
|
|
struct mailmime_parameter* mailmime_find_ct_parameter(struct mailmime* mime, const char* name)
|
|
{
|
|
/* find a parameter in `Content-Type: foo/bar; name=value;` */
|
|
if( mime==NULL || name==NULL
|
|
|| mime->mm_content_type==NULL || mime->mm_content_type->ct_parameters==NULL )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
clistiter* cur;
|
|
for( cur = clist_begin(mime->mm_content_type->ct_parameters); cur != NULL; cur = clist_next(cur) ) {
|
|
struct mailmime_parameter* param = (struct mailmime_parameter*)clist_content(cur);
|
|
if( param && param->pa_name ) {
|
|
if( strcmp(param->pa_name, name)==0 ) {
|
|
return param;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
int mailmime_transfer_decode(struct mailmime* mime, const char** ret_decoded_data, size_t* ret_decoded_data_bytes, char** ret_to_mmap_string_unref)
|
|
{
|
|
int mime_transfer_encoding = MAILMIME_MECHANISM_BINARY;
|
|
struct mailmime_data* mime_data = NULL;
|
|
const char* decoded_data = NULL; /* must not be free()'d */
|
|
size_t decoded_data_bytes = 0;
|
|
char* transfer_decoding_buffer = NULL; /* mmap_string_unref()'d if set */
|
|
|
|
if( mime == NULL || ret_decoded_data == NULL || ret_decoded_data_bytes == NULL || ret_to_mmap_string_unref == NULL
|
|
|| *ret_decoded_data != NULL || *ret_decoded_data_bytes != 0 || *ret_to_mmap_string_unref != NULL ) {
|
|
return 0;
|
|
}
|
|
|
|
mime_data = mime->mm_data.mm_single;
|
|
|
|
if( mime->mm_mime_fields != NULL ) {
|
|
clistiter* cur;
|
|
for( cur = clist_begin(mime->mm_mime_fields->fld_list); cur != NULL; cur = clist_next(cur) ) {
|
|
struct mailmime_field* field = (struct mailmime_field*)clist_content(cur);
|
|
if( field && field->fld_type == MAILMIME_FIELD_TRANSFER_ENCODING && field->fld_data.fld_encoding ) {
|
|
mime_transfer_encoding = field->fld_data.fld_encoding->enc_type;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* regard `Content-Transfer-Encoding:` */
|
|
if( mime_transfer_encoding == MAILMIME_MECHANISM_7BIT
|
|
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT
|
|
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY )
|
|
{
|
|
decoded_data = mime_data->dt_data.dt_text.dt_data;
|
|
decoded_data_bytes = mime_data->dt_data.dt_text.dt_length;
|
|
if( decoded_data == NULL || decoded_data_bytes <= 0 ) {
|
|
return 0; /* no error - but no data */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int r;
|
|
size_t current_index = 0;
|
|
r = mailmime_part_parse(mime_data->dt_data.dt_text.dt_data, mime_data->dt_data.dt_text.dt_length,
|
|
¤t_index, mime_transfer_encoding,
|
|
&transfer_decoding_buffer, &decoded_data_bytes);
|
|
if( r != MAILIMF_NO_ERROR || transfer_decoding_buffer == NULL || decoded_data_bytes <= 0 ) {
|
|
return 0;
|
|
}
|
|
decoded_data = transfer_decoding_buffer;
|
|
}
|
|
|
|
*ret_decoded_data = decoded_data;
|
|
*ret_decoded_data_bytes = decoded_data_bytes;
|
|
*ret_to_mmap_string_unref = transfer_decoding_buffer;
|
|
return 1;
|
|
}
|
|
|
|
|
|
struct mailimf_fields* mailmime_find_mailimf_fields(struct mailmime* mime)
|
|
{
|
|
if( mime == NULL ) {
|
|
return NULL;
|
|
}
|
|
|
|
clistiter* cur;
|
|
switch (mime->mm_type) {
|
|
case MAILMIME_MULTIPLE:
|
|
for(cur = clist_begin(mime->mm_data.mm_multipart.mm_mp_list) ; cur != NULL ; cur = clist_next(cur)) {
|
|
struct mailimf_fields* header = mailmime_find_mailimf_fields(clist_content(cur));
|
|
if( header ) {
|
|
return header;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MAILMIME_MESSAGE:
|
|
return mime->mm_data.mm_message.mm_fields;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
char* mailimf_find_first_addr(const struct mailimf_mailbox_list* mb_list)
|
|
{
|
|
clistiter* cur;
|
|
|
|
if( mb_list == NULL ) {
|
|
return NULL;
|
|
}
|
|
|
|
for( cur = clist_begin(mb_list->mb_list); cur!=NULL ; cur=clist_next(cur) ) {
|
|
struct mailimf_mailbox* mb = (struct mailimf_mailbox*)clist_content(cur);
|
|
if( mb && mb->mb_addr_spec ) {
|
|
return dc_normalize_addr(mb->mb_addr_spec);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
struct mailimf_field* mailimf_find_field(struct mailimf_fields* header, int wanted_fld_type)
|
|
{
|
|
if( header == NULL || header->fld_list == NULL ) {
|
|
return NULL;
|
|
}
|
|
|
|
clistiter* cur1;
|
|
for( cur1 = clist_begin(header->fld_list); cur1!=NULL ; cur1=clist_next(cur1) )
|
|
{
|
|
struct mailimf_field* field = (struct mailimf_field*)clist_content(cur1);
|
|
if( field )
|
|
{
|
|
if( field->fld_type == wanted_fld_type ) {
|
|
return field;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
struct mailimf_optional_field* mailimf_find_optional_field(struct mailimf_fields* header, const char* wanted_fld_name)
|
|
{
|
|
/* Note: the function does not return fields with no value set! */
|
|
if( header == NULL || header->fld_list == NULL ) {
|
|
return NULL;
|
|
}
|
|
|
|
clistiter* cur1;
|
|
for( cur1 = clist_begin(header->fld_list); cur1!=NULL ; cur1=clist_next(cur1) )
|
|
{
|
|
struct mailimf_field* field = (struct mailimf_field*)clist_content(cur1);
|
|
if( field && field->fld_type == MAILIMF_FIELD_OPTIONAL_FIELD )
|
|
{
|
|
struct mailimf_optional_field* optional_field = field->fld_data.fld_optional_field;
|
|
if( optional_field && optional_field->fld_name && optional_field->fld_value && strcasecmp(optional_field->fld_name, wanted_fld_name)==0 ) {
|
|
return optional_field;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int mailmime_is_attachment_disposition(struct mailmime* mime)
|
|
{
|
|
if( mime->mm_mime_fields != NULL ) {
|
|
clistiter* cur;
|
|
for( cur = clist_begin(mime->mm_mime_fields->fld_list); cur != NULL; cur = clist_next(cur) ) {
|
|
struct mailmime_field* field = (struct mailmime_field*)clist_content(cur);
|
|
if( field && field->fld_type == MAILMIME_FIELD_DISPOSITION && field->fld_data.fld_disposition ) {
|
|
if( field->fld_data.fld_disposition->dsp_type
|
|
&& field->fld_data.fld_disposition->dsp_type->dsp_type==MAILMIME_DISPOSITION_TYPE_ATTACHMENT )
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int mailmime_get_mime_type(struct mailmime* mime, int* msg_type)
|
|
{
|
|
#define DC_MIMETYPE_MP_ALTERNATIVE 10
|
|
#define DC_MIMETYPE_MP_RELATED 20
|
|
#define DC_MIMETYPE_MP_MIXED 30
|
|
#define DC_MIMETYPE_MP_NOT_DECRYPTABLE 40
|
|
#define DC_MIMETYPE_MP_REPORT 45
|
|
#define DC_MIMETYPE_MP_SIGNED 46
|
|
#define DC_MIMETYPE_MP_OTHER 50
|
|
#define DC_MIMETYPE_TEXT_PLAIN 60
|
|
#define DC_MIMETYPE_TEXT_HTML 70
|
|
#define DC_MIMETYPE_IMAGE 80
|
|
#define DC_MIMETYPE_AUDIO 90
|
|
#define DC_MIMETYPE_VIDEO 100
|
|
#define DC_MIMETYPE_FILE 110
|
|
#define DC_MIMETYPE_AC_SETUP_FILE 111
|
|
|
|
struct mailmime_content* c = mime->mm_content_type;
|
|
int dummy; if( msg_type == NULL ) { msg_type = &dummy; }
|
|
*msg_type = DC_MSG_UNDEFINED;
|
|
|
|
if( c == NULL || c->ct_type == NULL ) {
|
|
return 0;
|
|
}
|
|
|
|
switch( c->ct_type->tp_type )
|
|
{
|
|
case MAILMIME_TYPE_DISCRETE_TYPE:
|
|
switch( c->ct_type->tp_data.tp_discrete_type->dt_type )
|
|
{
|
|
case MAILMIME_DISCRETE_TYPE_TEXT:
|
|
if( mailmime_is_attachment_disposition(mime) ) {
|
|
; /* DC_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. */
|
|
}
|
|
else if( strcmp(c->ct_subtype, "plain")==0 ) {
|
|
*msg_type = DC_MSG_TEXT;
|
|
return DC_MIMETYPE_TEXT_PLAIN;
|
|
}
|
|
else if( strcmp(c->ct_subtype, "html")==0 ) {
|
|
*msg_type = DC_MSG_TEXT;
|
|
return DC_MIMETYPE_TEXT_HTML;
|
|
}
|
|
*msg_type = DC_MSG_FILE;
|
|
return DC_MIMETYPE_FILE;
|
|
|
|
case MAILMIME_DISCRETE_TYPE_IMAGE:
|
|
if( strcmp(c->ct_subtype, "gif")==0 ) {
|
|
*msg_type = DC_MSG_GIF;
|
|
}
|
|
else if( strcmp(c->ct_subtype, "svg+xml")==0 ) {
|
|
*msg_type = DC_MSG_FILE;
|
|
return DC_MIMETYPE_FILE;
|
|
}
|
|
else {
|
|
*msg_type = DC_MSG_IMAGE;
|
|
}
|
|
return DC_MIMETYPE_IMAGE;
|
|
|
|
case MAILMIME_DISCRETE_TYPE_AUDIO:
|
|
*msg_type = DC_MSG_AUDIO; /* we correct this later to DC_MSG_VOICE, currently, this is not possible as we do not know the main header */
|
|
return DC_MIMETYPE_AUDIO;
|
|
|
|
case MAILMIME_DISCRETE_TYPE_VIDEO:
|
|
*msg_type = DC_MSG_VIDEO;
|
|
return DC_MIMETYPE_VIDEO;
|
|
|
|
default:
|
|
*msg_type = DC_MSG_FILE;
|
|
if( c->ct_type->tp_data.tp_discrete_type->dt_type == MAILMIME_DISCRETE_TYPE_APPLICATION
|
|
&& strcmp(c->ct_subtype, "autocrypt-setup")==0 ) {
|
|
return DC_MIMETYPE_AC_SETUP_FILE; /* application/autocrypt-setup */
|
|
}
|
|
return DC_MIMETYPE_FILE;
|
|
}
|
|
break;
|
|
|
|
case MAILMIME_TYPE_COMPOSITE_TYPE:
|
|
if( c->ct_type->tp_data.tp_composite_type->ct_type == MAILMIME_COMPOSITE_TYPE_MULTIPART )
|
|
{
|
|
if( strcmp(c->ct_subtype, "alternative")==0 ) {
|
|
return DC_MIMETYPE_MP_ALTERNATIVE;
|
|
}
|
|
else if( strcmp(c->ct_subtype, "related")==0 ) {
|
|
return DC_MIMETYPE_MP_RELATED;
|
|
}
|
|
else if( strcmp(c->ct_subtype, "encrypted")==0 ) {
|
|
return DC_MIMETYPE_MP_NOT_DECRYPTABLE; /* decryptable parts are already converted to other mime parts in dc_e2ee_decrypt() */
|
|
}
|
|
else if( strcmp(c->ct_subtype, "signed")==0 ) {
|
|
return DC_MIMETYPE_MP_SIGNED;
|
|
}
|
|
else if( strcmp(c->ct_subtype, "mixed")==0 ) {
|
|
return DC_MIMETYPE_MP_MIXED;
|
|
}
|
|
else if( strcmp(c->ct_subtype, "report")==0 ) {
|
|
return DC_MIMETYPE_MP_REPORT;
|
|
}
|
|
else {
|
|
return DC_MIMETYPE_MP_OTHER;
|
|
}
|
|
}
|
|
else if( c->ct_type->tp_data.tp_composite_type->ct_type == MAILMIME_COMPOSITE_TYPE_MESSAGE )
|
|
{
|
|
/* Enacapsulated messages, see https://www.w3.org/Protocols/rfc1341/7_3_Message.html
|
|
Also used as part "message/disposition-notification" of "multipart/report", which, however, will be handled separatedly.
|
|
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).
|
|
|
|
For now, we skip these parts at all; if desired, we could return DC_MIMETYPE_FILE/DC_MSG_FILE for selected and known subparts. */
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0; /* unknown */
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* a MIME part
|
|
******************************************************************************/
|
|
|
|
|
|
static dc_mimepart_t* dc_mimepart_new(void)
|
|
{
|
|
dc_mimepart_t* mimepart = NULL;
|
|
|
|
if( (mimepart=calloc(1, sizeof(dc_mimepart_t)))==NULL ) {
|
|
exit(33);
|
|
}
|
|
|
|
mimepart->type = DC_MSG_UNDEFINED;
|
|
mimepart->param = dc_param_new();
|
|
|
|
return mimepart;
|
|
}
|
|
|
|
|
|
static void dc_mimepart_unref(dc_mimepart_t* mimepart)
|
|
{
|
|
if( mimepart == NULL ) {
|
|
return;
|
|
}
|
|
|
|
if( mimepart->msg ) {
|
|
free(mimepart->msg);
|
|
mimepart->msg = NULL;
|
|
}
|
|
|
|
if( mimepart->msg_raw ) {
|
|
free(mimepart->msg_raw);
|
|
mimepart->msg_raw = NULL;
|
|
}
|
|
|
|
dc_param_unref(mimepart->param);
|
|
free(mimepart);
|
|
}
|
|
|
|
|
|
/*******************************************************************************
|
|
* Main interface
|
|
******************************************************************************/
|
|
|
|
|
|
/**
|
|
* Create a new mime parser object.
|
|
*
|
|
* @private @memberof dc_mimeparser_t
|
|
*
|
|
* @param blobdir Directrory to write attachments to.
|
|
* @param context Mailbox object, used for logging only.
|
|
*
|
|
* @return The MIME-parser object.
|
|
*/
|
|
dc_mimeparser_t* dc_mimeparser_new(const char* blobdir, dc_context_t* context)
|
|
{
|
|
dc_mimeparser_t* mimeparser = NULL;
|
|
|
|
if( (mimeparser=calloc(1, sizeof(dc_mimeparser_t)))==NULL ) {
|
|
exit(30);
|
|
}
|
|
|
|
mimeparser->context = context;
|
|
mimeparser->parts = carray_new(16);
|
|
mimeparser->blobdir = blobdir; /* no need to copy the string at the moment */
|
|
mimeparser->reports = carray_new(16);
|
|
mimeparser->e2ee_helper = calloc(1, sizeof(dc_e2ee_helper_t));
|
|
|
|
dc_hash_init(&mimeparser->header, DC_HASH_STRING, 0/* do not copy key */);
|
|
|
|
return mimeparser;
|
|
}
|
|
|
|
|
|
/**
|
|
* Free a MIME-parser object.
|
|
*
|
|
* Esp. all data allocated by dc_mimeparser_parse() will be free()'d.
|
|
*
|
|
* @private @memberof dc_mimeparser_t
|
|
*
|
|
* @param mimeparser The MIME-parser object.
|
|
*
|
|
* @return None.
|
|
*/
|
|
void dc_mimeparser_unref(dc_mimeparser_t* mimeparser)
|
|
{
|
|
if( mimeparser == NULL ) {
|
|
return;
|
|
}
|
|
|
|
dc_mimeparser_empty(mimeparser);
|
|
if( mimeparser->parts ) { carray_free(mimeparser->parts); }
|
|
if( mimeparser->reports ) { carray_free(mimeparser->reports); }
|
|
free(mimeparser->e2ee_helper);
|
|
free(mimeparser);
|
|
}
|
|
|
|
|
|
/**
|
|
* Empty all data in a MIME-parser object.
|
|
*
|
|
* This function is called implicitly by dc_mimeparser_parse() to free
|
|
* previously allocated data.
|
|
*
|
|
* @private @memberof dc_mimeparser_t
|
|
*
|
|
* @param mimeparser The MIME-parser object.
|
|
*
|
|
* @return None.
|
|
*/
|
|
void dc_mimeparser_empty(dc_mimeparser_t* mimeparser)
|
|
{
|
|
if( mimeparser == NULL ) {
|
|
return;
|
|
}
|
|
|
|
if( mimeparser->parts )
|
|
{
|
|
int i, cnt = carray_count(mimeparser->parts);
|
|
for( i = 0; i < cnt; i++ ) {
|
|
dc_mimepart_t* part = (dc_mimepart_t*)carray_get(mimeparser->parts, i);
|
|
if( part ) {
|
|
dc_mimepart_unref(part);
|
|
}
|
|
}
|
|
carray_set_size(mimeparser->parts, 0);
|
|
}
|
|
|
|
mimeparser->header_root = NULL; /* a pointer somewhere to the MIME data, must NOT be freed */
|
|
dc_hash_clear(&mimeparser->header);
|
|
|
|
if( mimeparser->header_protected ) {
|
|
mailimf_fields_free(mimeparser->header_protected); /* allocated as needed, MUST be freed */
|
|
mimeparser->header_protected = NULL;
|
|
}
|
|
|
|
mimeparser->is_send_by_messenger = 0;
|
|
mimeparser->is_system_message = 0;
|
|
|
|
free(mimeparser->subject);
|
|
mimeparser->subject = NULL;
|
|
|
|
if( mimeparser->mimeroot )
|
|
{
|
|
mailmime_free(mimeparser->mimeroot);
|
|
mimeparser->mimeroot = NULL;
|
|
}
|
|
|
|
mimeparser->is_forwarded = 0;
|
|
|
|
if( mimeparser->reports ) {
|
|
carray_set_size(mimeparser->reports, 0);
|
|
}
|
|
|
|
mimeparser->decrypting_failed = 0;
|
|
|
|
dc_e2ee_thanks(mimeparser->e2ee_helper);
|
|
}
|
|
|
|
|
|
static void do_add_single_part(dc_mimeparser_t* parser, dc_mimepart_t* part)
|
|
{
|
|
/* add a single part to the list of parts, the parser takes the ownership of the part, so you MUST NOT unref it after calling this function. */
|
|
if( parser->e2ee_helper->encrypted && dc_hash_count(parser->e2ee_helper->signatures)>0 ) {
|
|
dc_param_set_int(part->param, DC_PARAM_GUARANTEE_E2EE, 1);
|
|
}
|
|
else if( parser->e2ee_helper->encrypted ) {
|
|
dc_param_set_int(part->param, DC_PARAM_ERRONEOUS_E2EE, DC_E2EE_NO_VALID_SIGNATURE);
|
|
}
|
|
carray_add(parser->parts, (void*)part, NULL);
|
|
}
|
|
|
|
|
|
static void do_add_single_file_part(dc_mimeparser_t* parser, int msg_type, int mime_type,
|
|
const char* decoded_data, size_t decoded_data_bytes,
|
|
const char* desired_filename)
|
|
{
|
|
dc_mimepart_t* part = NULL;
|
|
char* pathNfilename = NULL;
|
|
|
|
/* create a free file name to use */
|
|
if( (pathNfilename=dc_get_fine_pathNfilename(parser->blobdir, desired_filename)) == NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* copy data to file */
|
|
if( dc_write_file(pathNfilename, decoded_data, decoded_data_bytes, parser->context)==0 ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
part = dc_mimepart_new();
|
|
part->type = msg_type;
|
|
part->int_mimetype = mime_type;
|
|
part->bytes = decoded_data_bytes;
|
|
dc_param_set(part->param, DC_PARAM_FILE, pathNfilename);
|
|
if( DC_MSG_MAKE_FILENAME_SEARCHABLE(msg_type) ) {
|
|
part->msg = dc_get_filename(pathNfilename);
|
|
}
|
|
else if( DC_MSG_MAKE_SUFFIX_SEARCHABLE(msg_type) ) {
|
|
part->msg = dc_get_filesuffix_lc(pathNfilename);
|
|
}
|
|
|
|
if( mime_type == DC_MIMETYPE_IMAGE ) {
|
|
uint32_t w = 0, h = 0;
|
|
if( dc_get_filemeta(decoded_data, decoded_data_bytes, &w, &h) ) {
|
|
dc_param_set_int(part->param, DC_PARAM_WIDTH, w);
|
|
dc_param_set_int(part->param, DC_PARAM_HEIGHT, h);
|
|
}
|
|
}
|
|
|
|
/* split author/title from the original filename (if we do it from the real filename, we'll also get numbers appended by dc_get_fine_pathNfilename()) */
|
|
if( msg_type == DC_MSG_AUDIO ) {
|
|
char* author = NULL, *title = NULL;
|
|
dc_msg_get_authorNtitle_from_filename(desired_filename, &author, &title);
|
|
dc_param_set(part->param, DC_PARAM_AUTHORNAME, author);
|
|
dc_param_set(part->param, DC_PARAM_TRACKNAME, title);
|
|
free(author);
|
|
free(title);
|
|
}
|
|
|
|
do_add_single_part(parser, part);
|
|
part = NULL;
|
|
|
|
cleanup:
|
|
free(pathNfilename);
|
|
dc_mimepart_unref(part);
|
|
}
|
|
|
|
|
|
static int dc_mimeparser_add_single_part_if_known(dc_mimeparser_t* mimeparser, struct mailmime* mime)
|
|
{
|
|
dc_mimepart_t* part = NULL;
|
|
int old_part_count = carray_count(mimeparser->parts);
|
|
|
|
int mime_type;
|
|
struct mailmime_data* mime_data;
|
|
char* file_suffix = NULL, *desired_filename = NULL;
|
|
int msg_type;
|
|
|
|
char* transfer_decoding_buffer = NULL; /* mmap_string_unref()'d if set */
|
|
char* charset_buffer = NULL; /* charconv_buffer_free()'d if set (just calls mmap_string_unref()) */
|
|
const char* decoded_data = NULL; /* must not be free()'d */
|
|
size_t decoded_data_bytes = 0;
|
|
dc_simplify_t* simplifier = NULL;
|
|
|
|
if( mime == NULL || mime->mm_data.mm_single == NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* get mime type from `mime` */
|
|
mime_type = mailmime_get_mime_type(mime, &msg_type);
|
|
|
|
/* get data pointer from `mime` */
|
|
mime_data = mime->mm_data.mm_single;
|
|
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 */
|
|
|| mime_data->dt_data.dt_text.dt_data == NULL
|
|
|| mime_data->dt_data.dt_text.dt_length <= 0 ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
/* regard `Content-Transfer-Encoding:` */
|
|
if( !mailmime_transfer_decode(mime, &decoded_data, &decoded_data_bytes, &transfer_decoding_buffer) ) {
|
|
goto cleanup; /* no always error - but no data */
|
|
}
|
|
|
|
switch( mime_type )
|
|
{
|
|
case DC_MIMETYPE_TEXT_PLAIN:
|
|
case DC_MIMETYPE_TEXT_HTML:
|
|
{
|
|
if( simplifier==NULL ) {
|
|
simplifier = dc_simplify_new();
|
|
if( simplifier==NULL ) {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
const char* charset = mailmime_content_charset_get(mime->mm_content_type); /* get from `Content-Type: text/...; charset=utf-8`; must not be free()'d */
|
|
if( charset!=NULL && strcmp(charset, "utf-8")!=0 && strcmp(charset, "UTF-8")!=0 ) {
|
|
size_t ret_bytes = 0;
|
|
int r = charconv_buffer("utf-8", charset, decoded_data, decoded_data_bytes, &charset_buffer, &ret_bytes);
|
|
if( r != MAIL_CHARCONV_NO_ERROR ) {
|
|
dc_log_warning(mimeparser->context, 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? */
|
|
(int)decoded_data_bytes, charset, (int)r); /* continue, however */
|
|
}
|
|
else if( charset_buffer==NULL || ret_bytes <= 0 ) {
|
|
goto cleanup; /* no error - but nothing to add */
|
|
}
|
|
else {
|
|
decoded_data = charset_buffer;
|
|
decoded_data_bytes = ret_bytes;
|
|
}
|
|
}
|
|
|
|
// add uuencoded stuff as DC_MSG_FILE/DC_MSG_IMAGE/etc. parts
|
|
char* txt = strndup(decoded_data, decoded_data_bytes);
|
|
{
|
|
char* uu_blob = NULL, *uu_filename = NULL, *new_txt = NULL;
|
|
size_t uu_blob_bytes = 0;
|
|
int uu_msg_type = 0, added_uu_parts = 0;
|
|
while( (new_txt=dc_uudecode_do(txt, &uu_blob, &uu_blob_bytes, &uu_filename)) != NULL )
|
|
{
|
|
dc_msg_guess_msgtype_from_suffix(uu_filename, &uu_msg_type, NULL);
|
|
if( uu_msg_type == 0 ) {
|
|
uu_msg_type = DC_MSG_FILE;
|
|
}
|
|
|
|
do_add_single_file_part(mimeparser, uu_msg_type, 0, uu_blob, uu_blob_bytes, uu_filename);
|
|
|
|
free(txt); txt = new_txt; new_txt = NULL;
|
|
free(uu_blob); uu_blob = NULL; uu_blob_bytes = 0; uu_msg_type = 0;
|
|
free(uu_filename); uu_filename = NULL;
|
|
|
|
added_uu_parts++;
|
|
if( added_uu_parts > 50/*fence against endless loops*/ ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add text as DC_MSG_TEXT part
|
|
char* simplified_txt = dc_simplify_simplify(simplifier, txt, strlen(txt), mime_type==DC_MIMETYPE_TEXT_HTML? 1 : 0);
|
|
free(txt);
|
|
txt = NULL;
|
|
if( simplified_txt && simplified_txt[0] )
|
|
{
|
|
part = dc_mimepart_new();
|
|
part->type = DC_MSG_TEXT;
|
|
part->int_mimetype = mime_type;
|
|
part->msg = simplified_txt;
|
|
part->msg_raw = strndup(decoded_data, decoded_data_bytes);
|
|
do_add_single_part(mimeparser, part);
|
|
part = NULL;
|
|
}
|
|
else
|
|
{
|
|
free(simplified_txt);
|
|
}
|
|
|
|
if( simplifier->is_forwarded ) {
|
|
mimeparser->is_forwarded = 1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DC_MIMETYPE_IMAGE:
|
|
case DC_MIMETYPE_AUDIO:
|
|
case DC_MIMETYPE_VIDEO:
|
|
case DC_MIMETYPE_FILE:
|
|
case DC_MIMETYPE_AC_SETUP_FILE:
|
|
{
|
|
/* try to get file name from
|
|
`Content-Disposition: ... filename*=...`
|
|
or `Content-Disposition: ... filename*0*=... filename*1*=... filename*2*=...`
|
|
or `Content-Disposition: ... filename=...` */
|
|
dc_strbuilder_t filename_parts;
|
|
dc_strbuilder_init(&filename_parts, 0);
|
|
for( clistiter* cur1 = clist_begin(mime->mm_mime_fields->fld_list); cur1 != NULL; cur1 = clist_next(cur1) )
|
|
{
|
|
struct mailmime_field* field = (struct mailmime_field*)clist_content(cur1);
|
|
if( field && field->fld_type == MAILMIME_FIELD_DISPOSITION && field->fld_data.fld_disposition )
|
|
{
|
|
struct mailmime_disposition* file_disposition = field->fld_data.fld_disposition;
|
|
if( file_disposition )
|
|
{
|
|
for( clistiter* cur2 = clist_begin(file_disposition->dsp_parms); cur2 != NULL; cur2 = clist_next(cur2) )
|
|
{
|
|
struct mailmime_disposition_parm* dsp_param = (struct mailmime_disposition_parm*)clist_content(cur2);
|
|
if( dsp_param )
|
|
{
|
|
if( dsp_param->pa_type==MAILMIME_DISPOSITION_PARM_PARAMETER
|
|
&& dsp_param->pa_data.pa_parameter
|
|
&& dsp_param->pa_data.pa_parameter->pa_name
|
|
&& strncmp(dsp_param->pa_data.pa_parameter->pa_name, "filename*", 9)==0 )
|
|
{
|
|
dc_strbuilder_cat(&filename_parts, dsp_param->pa_data.pa_parameter->pa_value); // we assume the filename*?* parts are in order, not seen anything else yet
|
|
}
|
|
else if( dsp_param->pa_type==MAILMIME_DISPOSITION_PARM_FILENAME )
|
|
{
|
|
desired_filename = dc_decode_header_words(dsp_param->pa_data.pa_filename); // this is used only if the parts buffer stays empty
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( strlen(filename_parts.buf) ) {
|
|
free(desired_filename);
|
|
desired_filename = dc_decode_ext_header(filename_parts.buf);
|
|
}
|
|
|
|
free(filename_parts.buf);
|
|
|
|
/* try to get file name from `Content-Type: ... name=...` */
|
|
if( desired_filename==NULL ) {
|
|
struct mailmime_parameter* param = mailmime_find_ct_parameter(mime, "name");
|
|
if( param && param->pa_value && param->pa_value[0] ) {
|
|
desired_filename = dc_strdup(param->pa_value);// is already decoded, see #162
|
|
}
|
|
}
|
|
|
|
/* if there is still no filename, guess one */
|
|
if( desired_filename==NULL ) {
|
|
if( mime->mm_content_type && mime->mm_content_type->ct_subtype ) {
|
|
desired_filename = dc_mprintf("file.%s", mime->mm_content_type->ct_subtype);
|
|
}
|
|
else {
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
dc_replace_bad_utf8_chars(desired_filename);
|
|
|
|
do_add_single_file_part(mimeparser, msg_type, mime_type, decoded_data, decoded_data_bytes, desired_filename);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* add object? (we do not add all objetcs, eg. signatures etc. are ignored) */
|
|
cleanup:
|
|
dc_simplify_unref(simplifier);
|
|
if( charset_buffer ) { charconv_buffer_free(charset_buffer); }
|
|
if( transfer_decoding_buffer ) { mmap_string_unref(transfer_decoding_buffer); }
|
|
free(file_suffix);
|
|
free(desired_filename);
|
|
dc_mimepart_unref(part);
|
|
|
|
return carray_count(mimeparser->parts)>old_part_count? 1 : 0; /* any part added? */
|
|
}
|
|
|
|
|
|
static int dc_mimeparser_parse_mime_recursive(dc_mimeparser_t* mimeparser, struct mailmime* mime)
|
|
{
|
|
int any_part_added = 0;
|
|
clistiter* cur;
|
|
|
|
if( mimeparser == NULL || mime == NULL ) {
|
|
return 0;
|
|
}
|
|
|
|
if( mailmime_find_ct_parameter(mime, "protected-headers") )
|
|
{
|
|
if( mime->mm_type==MAILMIME_SINGLE
|
|
&& mime->mm_content_type->ct_type->tp_type==MAILMIME_TYPE_DISCRETE_TYPE
|
|
&& mime->mm_content_type->ct_type->tp_data.tp_discrete_type->dt_type==MAILMIME_DISCRETE_TYPE_TEXT
|
|
&& mime->mm_content_type->ct_subtype
|
|
&& strcmp(mime->mm_content_type->ct_subtype, "rfc822-headers")==0 ) {
|
|
dc_log_info(mimeparser->context, 0, "Protected headers found in text/rfc822-headers attachment: Will be ignored."); /* we want the protected headers in the normal header of the payload */
|
|
return 0;
|
|
}
|
|
|
|
if( mimeparser->header_protected==NULL ) { /* use the most outer protected header - this is typically created in sync with the normal, unprotected header */
|
|
size_t dummy = 0;
|
|
if( mailimf_envelope_and_optional_fields_parse(mime->mm_mime_start, mime->mm_length, &dummy, &mimeparser->header_protected)!=MAILIMF_NO_ERROR
|
|
|| mimeparser->header_protected==NULL ) {
|
|
dc_log_warning(mimeparser->context, 0, "Protected headers parsing error.");
|
|
}
|
|
}
|
|
else {
|
|
dc_log_info(mimeparser->context, 0, "Protected headers found in MIME header: Will be ignored as we already found an outer one.");
|
|
}
|
|
}
|
|
|
|
switch( mime->mm_type )
|
|
{
|
|
case MAILMIME_SINGLE:
|
|
any_part_added = dc_mimeparser_add_single_part_if_known(mimeparser, mime);
|
|
break;
|
|
|
|
case MAILMIME_MULTIPLE:
|
|
switch( mailmime_get_mime_type(mime, NULL) )
|
|
{
|
|
case DC_MIMETYPE_MP_ALTERNATIVE: /* add "best" part */
|
|
/* Most times, mutlipart/alternative contains true alternatives as text/plain and text/html.
|
|
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") */
|
|
for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
|
|
struct mailmime* childmime = (struct mailmime*)clist_content(cur);
|
|
if( mailmime_get_mime_type(childmime, NULL) == DC_MIMETYPE_MP_MIXED ) {
|
|
any_part_added = dc_mimeparser_parse_mime_recursive(mimeparser, childmime);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if( !any_part_added ) {
|
|
/* search for text/plain and add this */
|
|
for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
|
|
struct mailmime* childmime = (struct mailmime*)clist_content(cur);
|
|
if( mailmime_get_mime_type(childmime, NULL) == DC_MIMETYPE_TEXT_PLAIN ) {
|
|
any_part_added = dc_mimeparser_parse_mime_recursive(mimeparser, childmime);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !any_part_added ) { /* `text/plain` not found - use the first part */
|
|
for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
|
|
if( dc_mimeparser_parse_mime_recursive(mimeparser, (struct mailmime*)clist_content(cur)) ) {
|
|
any_part_added = 1;
|
|
break; /* out of for() */
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DC_MIMETYPE_MP_RELATED: /* add the "root part" - the other parts may be referenced which is not interesting for us (eg. embedded images) */
|
|
/* we assume he "root part" being the first one, which may not be always true ... however, most times it seems okay. */
|
|
cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list);
|
|
if( cur ) {
|
|
any_part_added = dc_mimeparser_parse_mime_recursive(mimeparser, (struct mailmime*)clist_content(cur));
|
|
}
|
|
break;
|
|
|
|
case DC_MIMETYPE_MP_NOT_DECRYPTABLE:
|
|
{
|
|
dc_mimepart_t* part = dc_mimepart_new();
|
|
part->type = DC_MSG_TEXT;
|
|
|
|
char* msg_body = dc_stock_str(mimeparser->context, DC_STR_CANTDECRYPT_MSG_BODY);
|
|
part->msg = dc_mprintf(DC_EDITORIAL_OPEN "%s" DC_EDITORIAL_CLOSE, msg_body);
|
|
free(msg_body);
|
|
|
|
carray_add(mimeparser->parts, (void*)part, NULL);
|
|
any_part_added = 1;
|
|
mimeparser->decrypting_failed = 1;
|
|
}
|
|
break;
|
|
|
|
case DC_MIMETYPE_MP_SIGNED:
|
|
/* RFC 1847: "The multipart/signed content type contains exactly two body parts.
|
|
The first body part is the body part over which the digital signature was created [...]
|
|
The second body part contains the control information necessary to verify the digital signature."
|
|
We simpliy take the first body part and skip the rest.
|
|
(see https://k9mail.github.io/2016/11/24/OpenPGP-Considerations-Part-I.html for background information why we use encrypted+signed) */
|
|
if( (cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list)) != NULL )
|
|
{
|
|
any_part_added = dc_mimeparser_parse_mime_recursive(mimeparser, (struct mailmime*)clist_content(cur));
|
|
}
|
|
break;
|
|
|
|
case DC_MIMETYPE_MP_REPORT:
|
|
if( clist_count(mime->mm_data.mm_multipart.mm_mp_list) >= 2 ) /* RFC 6522: the first part is for humans, the second for machines */
|
|
{
|
|
struct mailmime_parameter* report_type = mailmime_find_ct_parameter(mime, "report-type");
|
|
if( report_type && report_type->pa_value
|
|
&& strcmp(report_type->pa_value, "disposition-notification") == 0 )
|
|
{
|
|
carray_add(mimeparser->reports, (void*)mime, NULL);
|
|
}
|
|
else
|
|
{
|
|
/* eg. `report-type=delivery-status`; maybe we should show them as a little error icon */
|
|
any_part_added = dc_mimeparser_parse_mime_recursive(mimeparser, (struct mailmime*)clist_content(clist_begin(mime->mm_data.mm_multipart.mm_mp_list)));
|
|
}
|
|
}
|
|
break;
|
|
|
|
default: /* eg. DC_MIMETYPE_MP_MIXED - add all parts (in fact, AddSinglePartIfKnown() later check if the parts are really supported) */
|
|
{
|
|
/* HACK: the following lines are a hack for clients who use multipart/mixed instead of multipart/alternative for
|
|
combined text/html messages (eg. Stock Android "Mail" does so). So, if I detect such a message below, I skip the HTML part.
|
|
However, I'm not sure, if there are useful situations to use plain+html in multipart/mixed - if so, we should disable the hack. */
|
|
struct mailmime* skip_part = NULL;
|
|
{
|
|
struct mailmime* html_part = NULL;
|
|
int plain_cnt = 0, html_cnt = 0;
|
|
for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
|
|
struct mailmime* childmime = (struct mailmime*)clist_content(cur);
|
|
if( mailmime_get_mime_type(childmime, NULL) == DC_MIMETYPE_TEXT_PLAIN ) {
|
|
plain_cnt++;
|
|
}
|
|
else if( mailmime_get_mime_type(childmime, NULL) == DC_MIMETYPE_TEXT_HTML ) {
|
|
html_part = childmime;
|
|
html_cnt++;
|
|
}
|
|
}
|
|
if( plain_cnt==1 && html_cnt==1 ) {
|
|
dc_log_warning(mimeparser->context, 0, "HACK: multipart/mixed message found with PLAIN and HTML, we'll skip the HTML part as this seems to be unwanted.");
|
|
skip_part = html_part;
|
|
}
|
|
}
|
|
/* /HACK */
|
|
|
|
for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
|
|
struct mailmime* childmime = (struct mailmime*)clist_content(cur);
|
|
if( childmime != skip_part ) {
|
|
if( dc_mimeparser_parse_mime_recursive(mimeparser, childmime) ) {
|
|
any_part_added = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case MAILMIME_MESSAGE:
|
|
if( mimeparser->header_root == NULL )
|
|
{
|
|
mimeparser->header_root = mime->mm_data.mm_message.mm_fields;
|
|
}
|
|
|
|
if( mime->mm_data.mm_message.mm_msg_mime )
|
|
{
|
|
any_part_added = dc_mimeparser_parse_mime_recursive(mimeparser, mime->mm_data.mm_message.mm_msg_mime);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return any_part_added;
|
|
}
|
|
|
|
|
|
static void hash_header(dc_hash_t* out, const struct mailimf_fields* in, dc_context_t* context)
|
|
{
|
|
if( in == NULL ) {
|
|
return;
|
|
}
|
|
|
|
clistiter* cur1;
|
|
for( cur1 = clist_begin(in->fld_list); cur1!=NULL ; cur1=clist_next(cur1) )
|
|
{
|
|
struct mailimf_field* field = (struct mailimf_field*)clist_content(cur1);
|
|
const char *key = NULL;
|
|
switch( field->fld_type )
|
|
{
|
|
case MAILIMF_FIELD_RETURN_PATH: key = "Return-Path"; break;
|
|
case MAILIMF_FIELD_ORIG_DATE: key = "Date"; break;
|
|
case MAILIMF_FIELD_FROM: key = "From"; break;
|
|
case MAILIMF_FIELD_SENDER: key = "Sender"; break;
|
|
case MAILIMF_FIELD_REPLY_TO: key = "Reply-To"; break;
|
|
case MAILIMF_FIELD_TO: key = "To"; break;
|
|
case MAILIMF_FIELD_CC: key = "Cc"; break;
|
|
case MAILIMF_FIELD_BCC: key = "Bcc"; break;
|
|
case MAILIMF_FIELD_MESSAGE_ID: key = "Message-ID"; break;
|
|
case MAILIMF_FIELD_IN_REPLY_TO: key = "In-Reply-To"; break;
|
|
case MAILIMF_FIELD_REFERENCES: key = "References"; break;
|
|
case MAILIMF_FIELD_SUBJECT: key = "Subject"; break;
|
|
case MAILIMF_FIELD_OPTIONAL_FIELD:
|
|
{
|
|
const struct mailimf_optional_field* optional_field = field->fld_data.fld_optional_field;
|
|
if( optional_field ) {
|
|
key = optional_field->fld_name;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if( key )
|
|
{
|
|
int key_len = strlen(key);
|
|
|
|
if( dc_hash_find(out, key, key_len) )
|
|
{
|
|
/* key already in hash, do only overwrite known types */
|
|
if( field->fld_type!=MAILIMF_FIELD_OPTIONAL_FIELD
|
|
|| (key_len>5 && strncasecmp(key, "Chat-", 5)==0) )
|
|
{
|
|
//dc_log_info(context, 0, "Protected headers: Overwriting \"%s\".", key);
|
|
dc_hash_insert(out, key, key_len, field);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* key not hashed before */
|
|
dc_hash_insert(out, key, key_len, field);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Parse raw MIME-data into a MIME-object.
|
|
*
|
|
* You may call this function several times on the same object; old data are cleared using
|
|
* dc_mimeparser_empty() before parsing is started.
|
|
*
|
|
* After dc_mimeparser_parse() is called successfully, all the functions to get information about the
|
|
* MIME-structure will work.
|
|
*
|
|
* @private @memberof dc_mimeparser_t
|
|
* @param mimeparser The MIME-parser object.
|
|
* @param body_not_terminated Plain text, no need to be null-terminated.
|
|
* @param body_bytes The number of bytes to read from body_not_terminated.
|
|
* body_not_terminated is null-terminated, use strlen(body_not_terminated) here.
|
|
* @return None.
|
|
*/
|
|
void dc_mimeparser_parse(dc_mimeparser_t* mimeparser, const char* body_not_terminated, size_t body_bytes)
|
|
{
|
|
int r;
|
|
size_t index = 0;
|
|
|
|
dc_mimeparser_empty(mimeparser);
|
|
|
|
/* parse body */
|
|
r = mailmime_parse(body_not_terminated, body_bytes, &index, &mimeparser->mimeroot);
|
|
if(r != MAILIMF_NO_ERROR || mimeparser->mimeroot == NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
//printf("before decryption:\n"); mailmime_print(mimeparser->mimeroot);
|
|
|
|
/* decrypt, if possible; handle Autocrypt:-header
|
|
(decryption may modifiy the given object) */
|
|
dc_e2ee_decrypt(mimeparser->context, mimeparser->mimeroot, mimeparser->e2ee_helper);
|
|
|
|
//printf("after decryption:\n"); mailmime_print(mimeparser->mimeroot);
|
|
|
|
/* recursively check, whats parsed, this also sets up header_old */
|
|
dc_mimeparser_parse_mime_recursive(mimeparser, mimeparser->mimeroot);
|
|
|
|
// TOCHECK: text parts may be moved to the beginning of the list - either here or in do_add_single_part()
|
|
// usecase: eg. the Buchungsbestätigungen of Deutsch Bahn have the PDF before the explaining text.
|
|
// may also be handy for extracting binaries from uuencoded text and just add the rest text after the binaries.
|
|
|
|
/* setup header */
|
|
hash_header(&mimeparser->header, mimeparser->header_root, mimeparser->context);
|
|
hash_header(&mimeparser->header, mimeparser->header_protected, mimeparser->context); /* overwrite the original header with the protected one */
|
|
|
|
/* set some basic data */
|
|
{
|
|
struct mailimf_field* field = dc_mimeparser_lookup_field(mimeparser, "Subject");
|
|
if( field && field->fld_type == MAILIMF_FIELD_SUBJECT ) {
|
|
mimeparser->subject = dc_decode_header_words(field->fld_data.fld_subject->sbj_value);
|
|
}
|
|
}
|
|
|
|
if( dc_mimeparser_lookup_optional_field2(mimeparser, "Chat-Version", "X-MrMsg") ) {
|
|
mimeparser->is_send_by_messenger = 1;
|
|
}
|
|
|
|
if( dc_mimeparser_lookup_field(mimeparser, "Autocrypt-Setup-Message") ) {
|
|
/* Autocrypt-Setup-Message header found - check if there is an application/autocrypt-setup part */
|
|
int i, has_setup_file = 0;
|
|
for( i = 0; i < carray_count(mimeparser->parts); i++ ) {
|
|
dc_mimepart_t* part = (dc_mimepart_t*)carray_get(mimeparser->parts, i);
|
|
if( part->int_mimetype==DC_MIMETYPE_AC_SETUP_FILE ) {
|
|
has_setup_file = 1;
|
|
}
|
|
}
|
|
if( has_setup_file ) {
|
|
/* delete all parts but the application/autocrypt-setup part */
|
|
mimeparser->is_system_message = DC_CMD_AUTOCRYPT_SETUP_MESSAGE;
|
|
for( i = 0; i < carray_count(mimeparser->parts); i++ ) {
|
|
dc_mimepart_t* part = (dc_mimepart_t*)carray_get(mimeparser->parts, i);
|
|
if( part->int_mimetype!=DC_MIMETYPE_AC_SETUP_FILE ) {
|
|
dc_mimepart_unref(part);
|
|
carray_delete_slow(mimeparser->parts, i);
|
|
i--; /* start over with the same index */
|
|
}
|
|
}
|
|
}
|
|
mimeparser->is_send_by_messenger = 0; /* do not treat a setup message as a messenger message (eg. do not move setup messages to the Chats-folder; there may be a 3rd device that wants to handle it) */
|
|
}
|
|
|
|
/* prepend subject to message? */
|
|
if( mimeparser->subject )
|
|
{
|
|
int prepend_subject = 1;
|
|
if( !mimeparser->decrypting_failed /* if decryption has failed, we always prepend the subject as this may contain cleartext hints from non-Delta MUAs. */ )
|
|
{
|
|
char* p = strchr(mimeparser->subject, ':');
|
|
if( (p-mimeparser->subject) == 2 /*Re: etc.*/
|
|
|| (p-mimeparser->subject) == 3 /*Fwd: etc.*/
|
|
|| mimeparser->is_send_by_messenger
|
|
|| strstr(mimeparser->subject, DC_CHAT_PREFIX)!=NULL ) {
|
|
prepend_subject = 0;
|
|
}
|
|
}
|
|
|
|
if( prepend_subject )
|
|
{
|
|
char* subj = dc_strdup(mimeparser->subject);
|
|
char* p = strchr(subj, '['); /* do not add any tags as "[checked by XYZ]" */
|
|
if( p ) {
|
|
*p = 0;
|
|
}
|
|
dc_trim(subj);
|
|
if( subj[0] ) {
|
|
int i, icnt = carray_count(mimeparser->parts); /* should be at least one - maybe empty - part */
|
|
for( i = 0; i < icnt; i++ ) {
|
|
dc_mimepart_t* part = (dc_mimepart_t*)carray_get(mimeparser->parts, i);
|
|
if( part->type == DC_MSG_TEXT ) {
|
|
#define DC_NDASH "\xE2\x80\x93"
|
|
char* new_txt = dc_mprintf("%s " DC_NDASH " %s", subj, part->msg);
|
|
free(part->msg);
|
|
part->msg = new_txt;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
free(subj);
|
|
}
|
|
}
|
|
|
|
/* add forward information to every part */
|
|
if( mimeparser->is_forwarded ) {
|
|
int i, icnt = carray_count(mimeparser->parts); /* should be at least one - maybe empty - part */
|
|
for( i = 0; i < icnt; i++ ) {
|
|
dc_mimepart_t* part = (dc_mimepart_t*)carray_get(mimeparser->parts, i);
|
|
dc_param_set_int(part->param, DC_PARAM_FORWARDED, 1);
|
|
}
|
|
}
|
|
|
|
if( carray_count(mimeparser->parts)==1 )
|
|
{
|
|
/* 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).
|
|
and read some additional parameters */
|
|
dc_mimepart_t* part = (dc_mimepart_t*)carray_get(mimeparser->parts, 0);
|
|
if( part->type == DC_MSG_AUDIO ) {
|
|
if( dc_mimeparser_lookup_optional_field2(mimeparser, "Chat-Voice-Message", "X-MrVoiceMessage") ) {
|
|
free(part->msg);
|
|
part->msg = strdup("ogg"); /* DC_MSG_AUDIO adds sets the whole filename which is useless. however, the extension is useful. */
|
|
part->type = DC_MSG_VOICE;
|
|
dc_param_set(part->param, DC_PARAM_AUTHORNAME, NULL); /* remove unneeded information */
|
|
dc_param_set(part->param, DC_PARAM_TRACKNAME, NULL);
|
|
}
|
|
}
|
|
|
|
if( part->type == DC_MSG_AUDIO || part->type == DC_MSG_VOICE || part->type == DC_MSG_VIDEO ) {
|
|
const struct mailimf_optional_field* field = dc_mimeparser_lookup_optional_field2(mimeparser, "Chat-Duration", "X-MrDurationMs");
|
|
if( field ) {
|
|
int duration_ms = atoi(field->fld_value);
|
|
if( duration_ms > 0 && duration_ms < 24*60*60*1000 ) {
|
|
dc_param_set_int(part->param, DC_PARAM_DURATION, duration_ms);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* some special system message? */
|
|
if( dc_mimeparser_lookup_field(mimeparser, "Chat-Group-Image")
|
|
&& carray_count(mimeparser->parts)>=1 ) {
|
|
dc_mimepart_t* textpart = (dc_mimepart_t*)carray_get(mimeparser->parts, 0);
|
|
if( textpart->type == DC_MSG_TEXT ) {
|
|
dc_param_set_int(textpart->param, DC_PARAM_CMD, DC_CMD_GROUPIMAGE_CHANGED);
|
|
if( carray_count(mimeparser->parts)>=2 ) {
|
|
dc_mimepart_t* imgpart = (dc_mimepart_t*)carray_get(mimeparser->parts, 1);
|
|
if( imgpart->type == DC_MSG_IMAGE ) {
|
|
imgpart->is_meta = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* check, if the message asks for a MDN */
|
|
if( !mimeparser->decrypting_failed )
|
|
{
|
|
const struct mailimf_optional_field* dn_field = dc_mimeparser_lookup_optional_field(mimeparser, "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. */
|
|
if( dn_field && dc_mimeparser_get_last_nonmeta(mimeparser)/*just check if the mail is not empty*/ )
|
|
{
|
|
struct mailimf_mailbox_list* mb_list = NULL;
|
|
size_t index = 0;
|
|
if( mailimf_mailbox_list_parse(dn_field->fld_value, strlen(dn_field->fld_value), &index, &mb_list)==MAILIMF_NO_ERROR && mb_list )
|
|
{
|
|
char* dn_to_addr = mailimf_find_first_addr(mb_list);
|
|
if( dn_to_addr )
|
|
{
|
|
struct mailimf_field* from_field = dc_mimeparser_lookup_field(mimeparser, "From"); /* we need From: as this MUST match Disposition-Notification-To: */
|
|
if( from_field && from_field->fld_type==MAILIMF_FIELD_FROM && from_field->fld_data.fld_from )
|
|
{
|
|
char* from_addr = mailimf_find_first_addr(from_field->fld_data.fld_from->frm_mb_list);
|
|
if( from_addr )
|
|
{
|
|
if( strcmp(from_addr, dn_to_addr)==0 )
|
|
{
|
|
/* we mark _only_ the _last_ part to send a MDN
|
|
(this avoids trouble with multi-part-messages who should send only one MDN.
|
|
Moreover the last one is handy as it is the one typically displayed if the message is larger) */
|
|
dc_mimepart_t* part = dc_mimeparser_get_last_nonmeta(mimeparser);
|
|
if( part ) {
|
|
dc_param_set_int(part->param, DC_PARAM_WANTS_MDN, 1);
|
|
}
|
|
}
|
|
free(from_addr);
|
|
}
|
|
}
|
|
free(dn_to_addr);
|
|
}
|
|
mailimf_mailbox_list_free(mb_list);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Cleanup - and try to create at least an empty part if there are no parts yet */
|
|
cleanup:
|
|
if( !dc_mimeparser_has_nonmeta(mimeparser) && carray_count(mimeparser->reports)==0 ) {
|
|
dc_mimepart_t* part = dc_mimepart_new();
|
|
part->type = DC_MSG_TEXT;
|
|
part->msg = dc_strdup(mimeparser->subject? mimeparser->subject : "Empty message");
|
|
carray_add(mimeparser->parts, (void*)part, NULL);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Lookup the given field name.
|
|
*
|
|
* Typical names are `From`, `To`, `Subject` and so on.
|
|
*
|
|
* @private @memberof dc_mimeparser_t
|
|
* @param mimparser The MIME-parser object.
|
|
* @param field_name The name of the field to look for.
|
|
* @return A pointer to a mailimf_field structure. Must not be freed!
|
|
* Before accessing the mailimf_field::fld_data, please always have a look at mailimf_field::fld_type!
|
|
* If field_name could not be found, NULL is returned.
|
|
*/
|
|
struct mailimf_field* dc_mimeparser_lookup_field(dc_mimeparser_t* mimeparser, const char* field_name)
|
|
{
|
|
return (struct mailimf_field*)dc_hash_find_str(&mimeparser->header, field_name);
|
|
}
|
|
|
|
|
|
/**
|
|
* Lookup the given field name.
|
|
*
|
|
* In addition to dc_mimeparser_lookup_field, this function also checks the mailimf_field::fld_type
|
|
* for being MAILIMF_FIELD_OPTIONAL_FIELD.
|
|
*
|
|
* @private @memberof dc_mimeparser_t
|
|
*
|
|
* @param mimparser The MIME-parser object.
|
|
* @param field_name The name of the field to look for.
|
|
*
|
|
* @return A pointer to a mailimf_optional_field structure. Must not be freed!
|
|
* If field_name could not be found or has another type, NULL is returned.
|
|
*/
|
|
struct mailimf_optional_field* dc_mimeparser_lookup_optional_field(dc_mimeparser_t* mimeparser, const char* field_name)
|
|
{
|
|
struct mailimf_field* field = dc_hash_find_str(&mimeparser->header, field_name);
|
|
if( field && field->fld_type == MAILIMF_FIELD_OPTIONAL_FIELD ) {
|
|
return field->fld_data.fld_optional_field;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Lookup the first name and return, if found.
|
|
* If not, try to lookup the second name.
|
|
*/
|
|
struct mailimf_optional_field* dc_mimeparser_lookup_optional_field2(dc_mimeparser_t* mimeparser, const char* field_name, const char* or_field_name)
|
|
{
|
|
struct mailimf_optional_field* of = dc_mimeparser_lookup_optional_field(mimeparser, field_name);
|
|
return of? of : dc_mimeparser_lookup_optional_field(mimeparser, or_field_name);
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the _last_ part _not_ flagged with is_meta.
|
|
*
|
|
* If you just want to check if there is a non-meta part preset, you can also
|
|
* use the macro dc_mimeparser_has_nonmeta().
|
|
*
|
|
* @private @memberof dc_mimeparser_t
|
|
*
|
|
* @param mimeparser The MIME-parser object.
|
|
*
|
|
* @return The last part that is not flagged with is_meta. The returned value
|
|
* must not be freed. If there is no such part, NULL is returned.
|
|
*/
|
|
dc_mimepart_t* dc_mimeparser_get_last_nonmeta(dc_mimeparser_t* mimeparser)
|
|
{
|
|
if( mimeparser && mimeparser->parts ) {
|
|
int i, icnt = carray_count(mimeparser->parts);
|
|
for( i = icnt-1; i >= 0; i-- ) {
|
|
dc_mimepart_t* part = (dc_mimepart_t*)carray_get(mimeparser->parts, i);
|
|
if( part && !part->is_meta ) {
|
|
return part;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks, if the header of the mail looks as if it is a message from a mailing list.
|
|
*
|
|
* @private @memberof dc_mimeparser_t
|
|
*
|
|
* @param mimeparser The MIME-parser object.
|
|
*
|
|
* @return 1=the message is probably from a mailing list,
|
|
* 0=the message is a normal messsage
|
|
*
|
|
* Some statistics:
|
|
*
|
|
* **Sorted out** by `List-ID`-header:
|
|
* - Mailman mailing list messages - OK, mass messages
|
|
* - Xing forum/event notifications - OK, mass messages
|
|
* - Xing welcome-back, contact-request - Hm, but it _has_ the List-ID header
|
|
*
|
|
* **Sorted out** by `Precedence`-header:
|
|
* - Majordomo mailing list messages - OK, mass messages
|
|
*
|
|
* **Not** sorted out:
|
|
* - Pingdom notifications - OK, individual message
|
|
* - Paypal notifications - OK, individual message
|
|
* - Linked in visits, do-you-know - OK, individual message
|
|
* - Share-It notifications - OK, individual message
|
|
* - Transifex, Github notifications - OK, individual message
|
|
*
|
|
* Current state of mailing list handling:
|
|
*
|
|
* As we currently do not have an extra handling for mailing list messages, the
|
|
* best is to ignore them completely.
|
|
*
|
|
* - if we know the sender, we should show them in the normal chat of the sender as this will lose the
|
|
* context of the mail
|
|
*
|
|
* - for unknown senders, mailing list messages are often replies to known messages (see is_reply_to_known_message__()) -
|
|
* which gives the sender some trust. this should not happen for mailing list messages.
|
|
* this may result in unwanted messages and contacts added to the address book that are not known.
|
|
*
|
|
* - of course, all this can be fixed, however, this may be a lot of work.
|
|
* moreover, if we allow answering to mailing lists, it might not be easy to follow the conventions used in typical mailing list,
|
|
* eg threads.
|
|
*
|
|
* "Mailing lists messages" in this sense are messages marked by List-Id or Precedence headers.
|
|
* For the future, we might want to show mailing lists as groups.
|
|
* (NB: typical mailing list header: `From: sender@gmx.net To: list@address.net)
|
|
*
|
|
*/
|
|
int dc_mimeparser_is_mailinglist_message(dc_mimeparser_t* mimeparser)
|
|
{
|
|
if( mimeparser == NULL ) {
|
|
return 0;
|
|
}
|
|
|
|
if( dc_mimeparser_lookup_field(mimeparser, "List-Id") != NULL ) {
|
|
return 1; /* mailing list identified by the presence of `List-ID` from RFC 2919 */
|
|
}
|
|
|
|
struct mailimf_optional_field* precedence = dc_mimeparser_lookup_optional_field(mimeparser, "Precedence");
|
|
if( precedence != NULL ) {
|
|
if( strcasecmp(precedence->fld_value, "list")==0
|
|
|| strcasecmp(precedence->fld_value, "bulk")==0 ) {
|
|
return 1; /* mailing list identified by the presence of `Precedence: bulk` or `Precedence: list` from RFC 3834 */
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks, if there is only one recipient address and if the recipient address
|
|
* matches the sender address. The function does _not_ check, if this address
|
|
* matches the address configured for a special account.
|
|
*
|
|
* The function searches in the outer MIME header, not in possibly protected
|
|
* memoryhole headers (if needed, we can change this; the reason for this is
|
|
* only that mailimf_get_recipients() was already there - and does not respect
|
|
* memoryhole as used on a lower level before memoryhole is calculated)
|
|
*
|
|
* @private @memberof dc_mimeparser_t
|
|
*
|
|
* @param mimeparser The MIME-parser object.
|
|
*
|
|
* @return 1=Sender matches recipient
|
|
* 0=Sender does not match recipient or there are more than one recipients
|
|
*/
|
|
int dc_mimeparser_sender_equals_recipient(dc_mimeparser_t* mimeparser)
|
|
{
|
|
int sender_equals_recipient = 0;
|
|
const struct mailimf_field* fld;
|
|
const struct mailimf_from* fld_from;
|
|
struct mailimf_mailbox* mb;
|
|
char* from_addr_norm = NULL;
|
|
dc_hash_t* recipients = NULL;
|
|
|
|
if( mimeparser == NULL || mimeparser->header_root == NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* get From: and check there is exactly one sender */
|
|
if( (fld=mailimf_find_field(mimeparser->header_root, MAILIMF_FIELD_FROM)) == NULL
|
|
|| (fld_from=fld->fld_data.fld_from) == NULL
|
|
|| fld_from->frm_mb_list == NULL
|
|
|| fld_from->frm_mb_list->mb_list == NULL
|
|
|| clist_count(fld_from->frm_mb_list->mb_list) != 1 ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
mb = (struct mailimf_mailbox*)clist_content(clist_begin(fld_from->frm_mb_list->mb_list));
|
|
if( mb == NULL ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
from_addr_norm = dc_normalize_addr(mb->mb_addr_spec);
|
|
|
|
/* get To:/Cc: and check there is exactly one recipent */
|
|
recipients = mailimf_get_recipients(mimeparser->header_root);
|
|
if( dc_hash_count(recipients) != 1 ) {
|
|
goto cleanup;
|
|
}
|
|
|
|
/* check if From: == To:/Cc: */
|
|
if( dc_hash_find_str(recipients, from_addr_norm) ) {
|
|
sender_equals_recipient = 1;
|
|
}
|
|
|
|
cleanup:
|
|
dc_hash_clear(recipients);
|
|
free(recipients);
|
|
free(from_addr_norm);
|
|
return sender_equals_recipient;
|
|
}
|
|
|