static char rcsid[] = "@(#)$Id: mime_parse.c,v 1.52 2006/05/30 16:33:21 hurtta Exp $"; /****************************************************************************** * The Elm (ME+) Mail System - $Revision: 1.52 $ $State: Exp $ * * Modified by: Kari Hurtta * (was hurtta+elm@ozone.FMI.FI) * * Initially written by: Michael Elkins , 1995 *****************************************************************************/ #include "def_melib.h" #include "s_me.h" DEBUG_VAR(Debug,__FILE__,"mime"); void mime_t_clear (mt) mime_t *mt; { DPRINT(Debug,15,(&Debug, "mime_t_clear(%p) --> BEGIN\n",mt)); if (mt->magic != MIME_magic) mime_panic(__FILE__,__LINE__,"mime_t_clear", "Bad magic number"); mt->offset = mt->begin_offset = 0; mt->length = -1; mt->encoding = ENCODING_7BIT; mt->TYPE = give_media_type2(MIME_TYPE_TEXT,"plain",0); if (!mt->TYPE) mime_panic(__FILE__,__LINE__,"mime_t_clear", "text/plain is not known"); mt->disposition = DISP_INLINE; mt->mime_flags = 0; if (mt->parser_data) mime_parser_free(&(mt->parser_data)); if (mt->handler_data) mime_selector_free(& (mt->handler_data)); if (mt->description) free_string (&(mt->description)); if (mt->TYPE_opts) free_mime_param (&(mt->TYPE_opts)); if (mt->DISPOSITION_opts) free_mime_param (&(mt->DISPOSITION_opts)); if (mt->pathname0) { if (mt->unlink) { DPRINT(Debug,15,(&Debug, "mime_t_clear: %s marked for unlink\n", mt->pathname0)); if (0 == unlink(mt->pathname0)) { DPRINT(Debug,14,(&Debug, "mime_t_clear: %s unlinked\n", mt->pathname0)); } } free (mt->pathname0); } else { if (mt->unlink) { DPRINT(Debug,3,(&Debug, "mime_t_clear: ERROR: Pathname marked for unlink, but no pathname\n")); } } mt->pathname0 = NULL; mt->unlink = 0; if (mt->dispname) free_string(& (mt->dispname)); DPRINT(Debug,15,(&Debug, "mime_t_clear(%p) <-- END\n",mt)); return; } void mime_get_disposition (str, mt, def_charset, header_error) char *str; mime_t *mt; charset_t def_charset; struct header_errors **header_error; { char *c, tmp[VERY_LONG_STRING]; DPRINT(Debug,9,(&Debug, "mime_get_disposition(): str=\"%s\"\n", str)); if (mt->magic != MIME_magic) mime_panic(__FILE__,__LINE__,"mime_get_disposition", "Bad magic number"); /* Don't harm "str" */ strfcpy (tmp, str, sizeof(tmp)); rfc822_reap_comments (tmp, NULL, 0); if (mt->DISPOSITION_opts) { free_mime_param(&(mt->DISPOSITION_opts)); } /* Look for the options field */ if ((c = strchr (tmp, ';')) != NULL) { char *d = c; while (d > tmp && whitespace(*(d-1))) d--; *d = '\0'; c++; while (*c && whitespace(*c)) c++; mt->DISPOSITION_opts = parse_mime_param("Content-Disposition", c, def_charset,header_error); } else { char *d = tmp + strlen(tmp); while (d > tmp && whitespace(*(d-1))) d--; *d = '\0'; } /* All that's left now is the main disposition */ c = tmp; while (*c && whitespace(*c)) c++; /* No Content-Disposition -header -> DISP_INLINE (DISP_AUTOATTACH) * Content-Disposition: inline -> DISP_INLINE * Content-Disposition: attachment -> DISP_ATTACH * Content-Disposition: {unknown} -> DISP_ATTACH * See RFC 1806 (Experimental protocol) for details. */ if (istrcmp (c, "inline") != 0) mt->disposition = DISP_ATTACH; else mt->disposition = DISP_INLINE; DPRINT(Debug,9,(&Debug, "mime_get_disposition(): disposition=\"%s\" (%d)\n", DISPOSITION(mt->disposition), mt->disposition)); } void mime_get_content (str, mt, def_charset, header_error) char *str; mime_t *mt; charset_t def_charset; struct header_errors **header_error; { char *c, tmp[VERY_LONG_STRING]; char * subtype = "???"; DPRINT(Debug,9,(&Debug, "mime_get_content(): str=\"%s\"\n", str)); if (mt->magic != MIME_magic) mime_panic(__FILE__,__LINE__,"mime_get_content", "Bad magic number"); /* Don't harm "str" */ strfcpy (tmp, str, sizeof(tmp)); rfc822_reap_comments (tmp, NULL, 0); if (mt->TYPE_opts) { free_mime_param(&(mt->TYPE_opts)); } /* Look for the options field */ if ((c = strchr (tmp, ';')) != NULL) { char *d = c; while (d > tmp && whitespace(*(d-1))) d--; *d = '\0'; c++; while (*c && whitespace(*c)) c++; mt->TYPE_opts = parse_mime_param("Content-Type", c, def_charset, header_error); } else { char *d = tmp + strlen(tmp); while (d > tmp && whitespace(*(d-1))) d--; *d = '\0'; } /* Get the subtype */ if ((c = strchr (tmp, '/')) != NULL) { char *d = c; while (d > tmp && whitespace(*(d-1))) d--; *d = '\0'; c++; while (*c && whitespace(*c)) c++; subtype = c; } /* All that's left now is the main type */ c = tmp; while (*c && whitespace(*c)) c++; mt->TYPE = give_media_type(c,subtype,1); if (! mt->TYPE) { DPRINT(Debug,1,(&Debug, "mime_get_content(): str=\"%s\" FAILED!\n", str)); return; } DPRINT(Debug,9,(&Debug, "mime_get_content(): %p -- type=\"%s\", subtype=\"%s\"\n", mt->TYPE, get_major_type_name(mt->TYPE), get_subtype_name(mt->TYPE))); return; } CONST char* mime_get_boundary (opts) struct mime_param *opts; { CONST char * pv = get_mime_param_compat(opts,"boundary"); if (!pv) lib_error(CATGETS(elm_msg_cat, MeSet, MeParseNoBoundary, "'boundary' parameter is missing from Multipart -type!")); return pv; } int mime_get_charset (charset_value, opts, display_charset, default_content_charset) charset_t *charset_value; struct mime_param *opts; charset_t * display_charset; /* vector */ charset_t default_content_charset; { /* Return > 0 (2) if charset is displayable with display_charset * Return < 0 (-2) if charset needs translating to display_charset -3 if charset can be translated to display_charset without loss */ int ret = 0,j; CONST char *pv; pv = get_mime_param_compat(opts,"charset"); if (!pv) { if (default_content_charset && default_content_charset->MIME_name) { DPRINT(Debug,9,(&Debug, "mime_get_charset: Using content default charset %s\n", default_content_charset->MIME_name)); *charset_value = default_content_charset; /* Charset name is also required on some confitions later */ pv = default_content_charset->MIME_name; } else { pv = "US-ASCII"; /* Default charset if nothing specified */ *charset_value = MIME_name_to_charset(pv,CHARSET_create); } } else *charset_value = MIME_name_to_charset(pv,CHARSET_create); DPRINT(Debug,9,(&Debug, " : charset_value=%p '%s' (type=%p)\n", *charset_value, (*charset_value)->MIME_name, (*charset_value)->charset_type)); if (display_charset) { for (j = 0; display_charset[j]; j++) { DPRINT(Debug,9,(&Debug, " [%d] : display_charset=%p '%s' (type=%p)\n", j,display_charset[j], display_charset[j]->MIME_name ? display_charset[j]->MIME_name : "", display_charset[j]->charset_type)); if (*charset_value == display_charset[j] || /* Accept also charsets with same name although charset is redefined */ display_charset[j]->MIME_name && 0 == istrcmp(pv,display_charset[j]->MIME_name)) { ret = 2; /* If display charset */ break; } else if (charset_superset_of(display_charset[j],*charset_value)) { ret = 2; /* if charset is subset of display */ break; } else if (0 == istrcmp(pv,"US-ASCII") && charset_ok_p(display_charset[j])) { ret = 2; /* If display_charset can show us-ascii? */ break; } } if ( 0 == ret) { int prop; if (CS_printable & charset_properties(*charset_value)) { ret = -2; /* convert (filter?) to display charset */ if (display_charset[0] && CS_universal_set & (prop = charset_properties(display_charset[0])) ) { DPRINT(Debug,9,(&Debug, " display charset '%s' -- universal charset (flags=%d)\n", display_charset[0]->MIME_name ? display_charset[0]->MIME_name : "", prop)); ret = -3; /* convert to display charset without loss */ } } else ret = 0; } } else { DPRINT(Debug,9,(&Debug," --- display_charset vector not given ... \n")); } DPRINT(Debug,9,(&Debug, "mime_get_charset=%d\n",ret)); return ret; } void mime_t_zero (ptr) mime_t *ptr; { /* This routine should be called whenever a new "mime_t" is created. It * makes sure that the pointers inside are correctly initialized to NULL * so that we don't end up calling free() on an uninitialized pointer. */ DPRINT(Debug,15,(&Debug, "mime_t_zero(%p)\n",ptr)); ptr->parser_data = NULL; ptr->handler_data = NULL; ptr->TYPE_opts = ptr->DISPOSITION_opts = NULL; ptr->pathname0 = NULL; ptr->dispname = NULL; ptr->description = NULL; ptr->magic = MIME_magic; ptr->offset = ptr->begin_offset = 0; ptr->length = -1; ptr->encoding = ENCODING_7BIT; ptr->unlink = 0; ptr->TYPE = give_media_type2(MIME_TYPE_TEXT,"plain",0); if (!ptr->TYPE) mime_panic(__FILE__,__LINE__,"mime_t_zero", "text/plain is not known"); ptr->disposition = DISP_INLINE; ptr->mime_flags = 0; } void mime_t_copy(trg, src) mime_t *trg, *src; { /* This routines make copy of mime_t structure ... */ DPRINT(Debug,15,(&Debug, "mime_t_copy(%p,%p) --> BEGIN\n",trg,src)); if (trg->magic != MIME_magic) mime_panic(__FILE__,__LINE__,"mime_t_copy", "Bad magic number (trg)"); if (src->magic != MIME_magic) mime_panic(__FILE__,__LINE__,"mime_t_copy", "Bad magic number (src)"); mime_t_clear(trg); trg->offset = src->offset; trg->begin_offset = src->begin_offset; trg->length = src->length; trg->encoding = src->encoding; trg->unlink = 0; /* Don't unlink ! */ trg->TYPE = src->TYPE; trg->disposition = src->disposition; trg->mime_flags = src->mime_flags; trg->parser_data = NULL; trg->handler_data = NULL; if (src->parser_data) { copy_parser_data(& (trg->parser_data), src->parser_data); } else { DPRINT(Debug,3,(&Debug, "mime_t_copy(src=%p,trg=%p): src->parser_data == NULL !\n",trg,src)); } if (src->description) { trg->description = dup_string(src->description); } if (src->TYPE_opts) { trg->TYPE_opts = copy_mime_param(src->TYPE_opts); } if (src->DISPOSITION_opts) { trg->DISPOSITION_opts = copy_mime_param(src->DISPOSITION_opts); } DPRINT(Debug,15,(&Debug, "mime_t_copy(%p,%p) <-- END\n",trg,src)); } mime_t * mime_t_alloc () { mime_t *ptr; DPRINT(Debug,15,(&Debug, "mime_t_alloc() --> BEGIN\n")); ptr = (mime_t *) safe_malloc (sizeof (mime_t)); /* Make sure to clear the pointers initially so that later we know when * to reclaim memory in mime_t_clear(). */ mime_t_zero (ptr); mime_t_clear (ptr); DPRINT(Debug,15,(&Debug, "mime_t_alloc() = %p <-- END\n",ptr)); return ptr; } void parse_mime_headers1 (ptr,headers,part_offset,body_offset,opts, hdr_charset,header_error) mime_t *ptr; header_list_ptr headers; long part_offset; long body_offset; int opts; charset_t hdr_charset; struct header_errors ** header_error; { header_list_ptr this_header; int no_content_disposition = 0; int x; /* set some defaults */ ptr->encoding = ENCODING_7BIT; if (opts & MIME_DIGEST) { ptr->TYPE = give_media_type2(MIME_TYPE_MESSAGE,"rfc822",0); if (!ptr->TYPE) mime_panic(__FILE__,__LINE__,"parse_mime_headers1", "message/rfc822 is not known"); } else { ptr->TYPE = give_media_type2(MIME_TYPE_TEXT,"plain", 0); if (!ptr->TYPE) mime_panic(__FILE__,__LINE__,"parse_mime_headers1", "text/plain is not known"); } ptr->disposition = DISP_INLINE; ptr->description = NULL; ptr->begin_offset = part_offset; ptr->offset = body_offset; ptr->length = -1; if (NULL != (this_header = locate_header_by_name(headers,"Content-Type")) && NULL != this_header->body) { mime_get_content (this_header->body, ptr, hdr_charset, header_error); if (this_header->next_this_header) process_header_error(header_error, CATGETS(elm_msg_cat, MeSet, MeParseErrorContent, "PARSE ERROR: Several Content-Type headers!")); } else { DPRINT(Debug,9,(&Debug, "parse_mime_headers1: No Content-Type -header\n")); } if (NULL != (this_header = locate_header_by_name(headers,"Content-Disposition")) && NULL != this_header->body) { mime_get_disposition (this_header->body, ptr, hdr_charset, header_error); if (this_header->next_this_header) process_header_error(header_error, CATGETS(elm_msg_cat, MeSet, MeParseErrorDisposition, "PARSE ERROR: Several Content-Disposition headers!")); } else { DPRINT(Debug,9,(&Debug, "parse_mime_headers1: No Content-Disposition -header\n")); no_content_disposition = 1; } if (NULL != (this_header = locate_header_by_name(headers,"Content-Transfer-Encoding")) && NULL != this_header->body) { char * value = this_header->body, *c; /* This removes comments from buffer this_header->body */ rfc822_reap_comments (value, NULL, 0); c = value; while (*c && isspace((unsigned char) *c)) c++; ptr->encoding = check_encoding (c); if (this_header->next_this_header) process_header_error(header_error, CATGETS(elm_msg_cat, MeSet, MeParseErrorTransfer, "PARSE ERROR: Several Content-Transfer-Encoding headers!")); } else { DPRINT(Debug,9,(&Debug, "parse_mime_headers1: No Content-Transfer-Encoding -header\n")); } if (NULL != (this_header = locate_header_by_name(headers,"Content-Description")) && NULL != this_header->body) { char * c = this_header->body; while (*c && whitespace(*c)) c++; ptr->description = NULL; ptr->description = hdr_to_string(HDR_TEXT,c, hdr_charset, 1); if (this_header->next_this_header) process_header_error(header_error, CATGETS(elm_msg_cat, MeSet, MeParseErrorDescription, "PARSE ERROR: Several Content-Description headers!")); } else { DPRINT(Debug,9,(&Debug, "parse_mime_headers1: No Content-Description -header\n")); } x = mime_classify_media(ptr, NULL /* ???? */); if (x) { ptr->mime_flags = x; /* TODO? -- should this check only NOTPLAIN_need_metamail ??*/ DPRINT(Debug,9,(&Debug, "parse_mime_headers1: 'Not plain'\n")); if (no_content_disposition) { int aa = give_dt_enumerate_as_int(&auto_attachment); DPRINT(Debug,9,(&Debug, " : no content-disposition -- auto-attachment = %d\n", aa)); if (get_major_type_code(ptr->TYPE) == MIME_TYPE_APPLICATION && aa >= 1 || get_major_type_code(ptr->TYPE) != MIME_TYPE_TEXT && !(get_type_flags(ptr->TYPE) & MIME_RFC822) && get_major_type_code(ptr->TYPE) != MIME_TYPE_MULTIPART && aa >= 2) { DPRINT(Debug,9,(&Debug, " : Set disposition = auto attachment\n")); ptr->disposition = DISP_AUTOATTACH; DPRINT(Debug,9,(&Debug, " : was %s/%s\n", get_major_type_name(ptr->TYPE), get_subtype_name(ptr->TYPE))); } } } } mime_t * parse_mime_headers(headers,part_offset,body_offset,opts, hdr_charset,header_error) header_list_ptr headers; long part_offset; long body_offset; int opts; charset_t hdr_charset; struct header_errors **header_error; { mime_t *ptr; DPRINT(Debug,9,(&Debug, "parse_mime_headers(): part_offset=%ld, body_offset=%ld, opts=%d\n", part_offset,body_offset,opts)); ptr = mime_t_alloc (); parse_mime_headers1(ptr,headers,part_offset,body_offset,opts,hdr_charset, header_error); DPRINT(Debug,10,(&Debug, "parse_mime_headers- type=%s/%s; flags=%d;\n", get_major_type_name(ptr->TYPE), get_subtype_name(ptr->TYPE), get_type_flags(ptr->TYPE))); DPRINT(Debug,10,(&Debug, "parse_mime_headers- begin=%ld, offset=%ld, length=%ld\n", ptr->begin_offset,ptr->offset,ptr->length)); DPRINT(Debug,9,(&Debug, "parse_mime_headers=%p <-- END\n",(void *)ptr)); return ptr; } mime_t * mime_read_header (fp, opts, defcharset, header_error) FILE *fp; int opts; charset_t defcharset; struct header_errors **header_error; { mime_t *ptr; header_list_ptr headers = NULL; long part_offset; long body_offset; DPRINT(Debug,19,(&Debug, "mime_read_header: opts=%d --> START\n",opts)); part_offset = ftell (fp); headers = file_read_headers(fp,0); body_offset = ftell(fp); ptr = parse_mime_headers(headers,part_offset,body_offset,opts, defcharset,header_error); delete_headers(&headers); return ptr; } void mime_warnings(hdr) struct header_rec *hdr; { if (hdr->status & PRE_MIME_CONTENT) { lib_error(CATGETS(elm_msg_cat, MeSet, MeParsePreMime, "Error: MIME-message has pre-MIME content-type!")); } if (hdr->status & MIME_UNSUPPORTED) { lib_error(CATGETS(elm_msg_cat, MeSet, MeParseUnsupportedMime, "Warning: Unsupported MIME-Version!")); } } #if ANSI_C parse_mime_callback mime_parse_routine; #endif int mime_parse_routine (folder,hdr, fp) struct folder_info *folder; struct header_rec *hdr; FILE *fp; { /* This routine checks to see if the multipart messages specified by * "hdr" has been parsed for its subparts, and if not, calls the routine * to do so. */ if (hdr->mime_rec.magic != MIME_magic) mime_panic(__FILE__,__LINE__,"mime_parse_routine", "Bad magic number (mime_rec)"); if (hdr->mime_rec.parser_data || !(hdr->status & MIME_MESSAGE)) { DPRINT(Debug,10,(&Debug,"mime_parse_routine=1 -- already parsed or no mime message\n")); return 1; } /* read_mailcaps() parses mailcaps only once */ read_mailcaps(); /* Copy value */ if (hdr -> content_length >= 0) hdr->mime_rec.length = hdr -> content_length; if (hdr->mime_rec.begin_offset <= 0) { fseek(fp,hdr->offset,SEEK_SET); hdr->mime_rec.begin_offset = hdr->offset; /* Skip mailbox's separator lines ... */ DPRINT(Debug,9,(&Debug, "mime_parse_routine: scanning begin_offset: %ld\n", (long) hdr->mime_rec.begin_offset)); hdr->mime_rec.begin_offset = skip_envelope(hdr,fp); DPRINT(Debug,9,(&Debug, "mime_parse_routine: begin_offset=%ld\n", (long) hdr->mime_rec.begin_offset)); if (hdr->mime_rec.begin_offset < 0) { lib_error(CATGETS(elm_msg_cat, MeSet, MeParseMailError, "Can't parse mail...")); return 0; } } lib_transient(CATGETS(elm_msg_cat, MeSet, MeParsingMime, "Parsing MIME structure...")); if (mime_parser_parse(&(hdr->mime_rec),hdr->header_charset,fp, &(hdr->header_error))) { /* mime_classify_media sets hdr->mime_rec.mime_flags !!! */ int old = hdr->mime_rec.mime_flags; int tmp = mime_classify_media(&(hdr->mime_rec),hdr); if (tmp >= 0) { if (tmp != old) { /* WARNING: Return value of mime_classify_media do not include NOTPLAIN_is_fallback, that may be set on hdr->mime_rec.mime_flags */ if (! (tmp & NOTPLAIN_need_metamail) && ! prompt_metamail) { lib_transient(CATGETS(elm_msg_cat, MeSet, MeParsingMimeNoMetamail, "Parsing MIME structure... metamail not needed")); } } hdr->mime_parsed = 1; } } return 1; } int is_pre_mime_content_type (ptr,content_type) mime_t *ptr; char *content_type; { int result; char *cptr = strpbrk(content_type,"/;()"); DPRINT(Debug,10,(&Debug, "is_pre_mime_content_type(): content_type=%s\n", content_type)); if (!cptr || ';' == *cptr) { char *ptr2; char *tmp = content_type; while (whitespace(*tmp)) tmp++; if (cptr) *cptr = '\0'; ptr2 = strpbrk(tmp," \t"); if (ptr2) *ptr2 = '\0'; if (istrcmp(tmp,"text")!=0) { char buf[STRING]; ptr -> mime_flags = NOTPLAIN_need_metamail; /* !!! */ /* Put some 'intelligent' value */ elm_sfprintf(buf,sizeof buf, FRM("X-RFC1049-%.30s"),tmp); ptr->TYPE = give_media_type2(MIME_TYPE_APPLICATION, buf,1); } else { ptr -> mime_flags = 0; ptr -> TYPE = give_media_type2(MIME_TYPE_TEXT,"plain",0); if (!ptr->TYPE) mime_panic(__FILE__,__LINE__,"is_pre_mime_content_type", "text/plain is not known"); } result = 1; } else result = 0; DPRINT(Debug,10,(&Debug, "is_pre_mime_content_type=%d\n",result)); return result; } /* * Local Variables: * mode:c * c-basic-offset:4 * buffer-file-coding-system: iso-8859-1 * End: */