2 * CDE - Common Desktop Environment
4 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
6 * These libraries and programs are free software; you can
7 * redistribute them and/or modify them under the terms of the GNU
8 * Lesser General Public License as published by the Free Software
9 * Foundation; either version 2 of the License, or (at your option)
12 * These libraries and programs are distributed in the hope that
13 * they will be useful, but WITHOUT ANY WARRANTY; without even the
14 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU Lesser General Public License for more
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with these libraries and programs; if not, write
20 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21 * Floor, Boston, MA 02110-1301 USA
27 * $TOG: MIMEBodyPart.C /main/11 1998/04/06 13:27:03 mgreess $
29 * RESTRICTED CONFIDENTIAL INFORMATION:
31 * The information in this document is subject to special
32 * restrictions in a confidential disclosure agreement bertween
33 * HP, IBM, Sun, USL, SCO and Univel. Do not distribute this
34 * document outside HP, IBM, Sun, USL, SCO, or Univel wihtout
35 * Sun's specific written approval. This documment and all copies
36 * and derivative works thereof must be returned or destroyed at
39 * Copyright 1993 Sun Microsystems, Inc. All rights reserved.
44 #include <EUSCompat.h>
52 #include <DtMail/DtMail.hh>
55 #include <DtMail/Threads.hh>
60 #include <DtHelp/LocaleXlate.h>
61 #include "str_utils.h"
63 MIMEBodyPart::MIMEBodyPart(DtMailEnv & error,
64 DtMail::Message * parent,
67 RFCEnvelope * body_env)
68 : RFCBodyPart(error, parent, start, len, body_env)
70 // A single part message. We are done for now.
76 MIMEBodyPart::MIMEBodyPart(DtMailEnv & error,
77 DtMail::Message * parent,
80 const char * boundary)
81 : RFCBodyPart(error, parent, start, 0, NULL)
85 // We are sitting at the start of a boundary. We need to get
86 // past the boundary to do the real processing.
88 const char * body_end;
89 for (body_end = _body_text; body_end <= *end && *body_end != '\n'; body_end++) {
94 if (body_end > *end) {
95 // Don't know. Give up!
97 _body_len = *end - start + 1;
99 // Need a bogus envelope for other uses.
101 _body_env = new RFCEnvelope(error, parent, NULL, 0);
105 const char * env_start = body_end;
107 if (isTerm(env_start)) {
108 _body_env = new RFCEnvelope(error, parent, env_start, 0);
111 // Find the blank line where the envelope ends.
113 for (; body_end <= *end; body_end++) {
114 if (*body_end == '\n') {
116 for (const char * blank = body_end + 1;
117 blank <= *end && *blank != '\n'; blank++) {
118 if (!isspace((unsigned char)*blank)) {
128 _body_env = new RFCEnvelope(error,
131 body_end - env_start + 1);
135 // Chew everything up to the next new line, which should be only
138 for (;body_end <= *end && *body_end != '\n'; body_end++) {
143 _body_text = body_end; // This is where the body really starts.
145 // Now we need to find the end of the body. MIME doesn't have
146 // any predefined length fields so we have to use the boundaries.
148 int bndry_len = strlen(boundary);
149 for (;body_end <= *end; body_end++) {
150 if (*body_end == '\n' &&
151 *(body_end + 1) == '-' &&
152 *(body_end + 2) == '-' &&
153 strncmp(body_end + 3, boundary, bndry_len) == 0) {
158 if (*(body_end - 1) == '\r') {
161 _body_len = body_end - _body_text + 1;
163 // MIME says the CRLF preceding a boundary belongs to the boundary.
164 // We will pull it off here rather than do it on entry to this
165 // method for the next message.
166 for (;body_end <= *end && *body_end != '\n'; body_end++) {
171 // Computing the end here is a little different. If the boundary
172 // ends with a "--" as well, then we are at the real end of
175 const char * bndry_end = body_end + 2 + strlen(boundary);
176 if (*bndry_end == '-' && *(bndry_end + 1) == '-') {
186 MIMEBodyPart::~MIMEBodyPart(void)
192 MIMEBodyPart::checksum(DtMailEnv & error)
196 // Look for the Content-MD5 header. If it is not present, then
197 // the state is unknown and we can punt.
200 DtMailValueSeq value;
201 _body_env->getHeader(my_error, "Content-MD5", DTM_FALSE, value);
202 if (my_error.isSet()) {
203 return(DtMailCheckUnknown);
207 if (_body_type == NULL) {
210 return(DtMailCheckUnknown);
214 char stored_digest[32];
216 RFCMIME::readBase64(stored_digest, stored_size,
217 *(value[0]), strlen(*(value[0])));
218 if (stored_size != 16) {
219 // The MD5 sum must be 16 bytes, or we have a bad checksum.
221 return(DtMailCheckBad);
224 // See if we call this text. We need to handle md5 checksums
225 // different for text. They must be computed with CRLF line
228 char * text_type = DtDtsDataTypeToAttributeValue(_body_type,
232 unsigned char digest[16];
233 if (text_type && strcasecmp(text_type, "true") == 0) {
234 RFCMIME::md5PlainText(_body, _body_decoded_len, digest);
239 MD5Update(&context, (unsigned char *)_body, _body_decoded_len);
240 MD5Final(digest, &context);
245 if (memcmp(digest, stored_digest, sizeof(digest)) == 0) {
246 return(DtMailCheckGood);
249 return(DtMailCheckBad);
252 #endif /* DEAD_WOOD */
255 countTypes(char ** types)
258 for (count = 0; *types; types++, count++) {
266 MIMEBodyPart::getContentType(DtMailEnv &error, char **mime_type)
268 DtMailValueSeq value;
271 *mime_type = (char *)0;
274 _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
277 if (_body_env && !error.isSet()) {
278 // Handle "Content-Type: text" problem with /usr/lib/mail.local
279 if (strcasecmp(*(value[0]), "text")==0) {
280 *mime_type = strdup("text/plain");
282 *mime_type = strdup(*(value[0]));
286 *mime_type = strdup("text/plain");
292 MIMEBodyPart::getDtType(DtMailEnv & error)
294 MutexLock lock_scope(_obj_mutex);
295 MutexLock dt_lib_lock(_DtMutex);
301 DtMailValueSeq value;
304 _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
307 if (_body_env && !error.isSet()) {
308 // Handle "Content-Type: text" problem with /usr/lib/mail.local
310 if (strcasecmp(*(value[0]), "text")==0)
311 mime_type = strdup("text/plain");
313 mime_type = strdup(*(value[0]));
317 mime_type = strdup("text/plain");
320 for (end = mime_type; *end; end++) {
321 if (*end == ';' || isspace((unsigned char)*end)) {
325 *end = tolower(*end);
330 char ** types = DtDtsFindAttribute(DtDTS_DA_MIME_TYPE, mime_type);
332 // We must have an exact 1:1 mapping between the mime type and the
333 // CDE type to use this inverse mapping. If we have no hits then we
334 // don't have any thing to use. If we have several hits, then we have
335 // no idea which type is the correct type.
338 if (countTypes(types) == 1) {
339 // We will use the first name. It may be wrong, but
340 // it is the best we can do at this point.
342 _body_type = strdup(types[0]);
343 DtDtsFreeDataTypeNames(types);
347 DtDtsFreeDataTypeNames(types);
350 // We need the bits so we can type the buffer and get
351 // a type for the object. This is where things can get
352 // very slow for the user.
359 int istext = (strcasecmp(mime_type, "text/plain") == 0);
360 char * name = getNameHeaderVal(error);
361 char * type = DtDtsBufferToDataType(_body, _body_decoded_len, name);
363 // We have written a name pattern for text parts that will match
364 // the name "text" as a TEXT part. If the first attempt to get the
365 // data type fails and we have a MIMETYPE of text/plain, then we try
366 // again using the name "text".
368 if ( (0 == strcasecmp(mime_type, "text/plain")) &&
369 (NULL == type || 0 == strcasecmp(type, "DATA")) )
372 DtDtsFreeDataType(type);
373 type = DtDtsBufferToDataType(_body, _body_decoded_len, "text");
377 _body_type = strdup(type);
379 _body_type = strdup("UNKNOWN");
384 DtDtsFreeDataType(type);
391 MIMEBodyPart::loadBody(DtMailEnv & error)
394 char *cs = NULL, *to_cs = NULL, *from_cs = NULL;
396 // There is no reason to clear the error object because it is assumed
397 // that whoever instantiated it, cleared it.
404 // If there is any encoding done to the body, reverse it
406 DtMailValueSeq value;
408 _body_env->getHeader(error, "Content-Transfer-Encoding",
412 if (_body_env && !error.isSet()) {
413 const char * enc = *(value[0]);
414 if (strcasecmp(enc, "base64") == 0) {
415 // Decoded bodies will always be smaller than
417 //_body = (char *)malloc(_body_len);
419 _body = (char *)malloc(_body_len+1);
420 int size = _body_decoded_len = 0;
421 _must_free_body = DTM_TRUE;
422 RFCMIME::readBase64(_body, size, _body_text, _body_len);
423 _body_decoded_len = size;
425 // Changed this temporarily until after release. We really should not
426 // be null terminating these buffers.
427 (_body)[_body_decoded_len] = 0;
429 else if (strcasecmp(enc, "quoted-printable") == 0) {
430 _body = (char *)malloc(_body_len + 20);
431 _must_free_body = DTM_TRUE;
432 int size = _body_decoded_len = 0;
433 RFCMIME::readQPrint(_body, size, _body_text, _body_len);
434 _body_decoded_len = size;
436 // Changed this temporarily until after release. We really should not
437 // be null terminating these buffers.
438 (_body)[_body_decoded_len] = 0;
441 // Default case is no transfer encoding applies (7bit==8bit==binary)
442 _body = (char *)_body_text;
443 _must_free_body = DTM_FALSE;
444 _body_decoded_len = _body_len;
448 // Default case is no transfer encoding applies.
450 _body = (char *)_body_text;
451 _must_free_body = DTM_FALSE;
452 _body_decoded_len = _body_len;
456 // Get charset from content-type field
459 _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
460 if (error.isNotSet()) {
461 cs = csFromContentType(value);
463 // Random allocation for cs
464 cs = (char *)calloc(128, sizeof(char));
465 DtXlateOpToStdLocale(DtLCX_OPER_SETLOCALE,
466 setlocale(LC_CTYPE, NULL),
470 strcpy(cs, "DEFAULT");
474 } else { // No Content-Type
475 // We'll be flexible here. If Content-Type header is missing, we'll
476 // still try to convert from the locale specific default codeset.
478 // Random allocation for cs
479 cs = (char *)calloc(128, sizeof(char));
480 DtXlateOpToStdLocale(DtLCX_OPER_SETLOCALE,
481 setlocale(LC_CTYPE, NULL),
485 strcpy(cs, "DEFAULT");
490 // Handle ISO-2022-INT, RFC approved, or private encoding names
491 if ( strcasecmp(cs, "ISO-2022-INT-1") == 0 ) {
492 // Need to obtain charset from encoding
493 } // RFC approved and private names are not treated differently.
495 // Get iconv name from charset - this is the "from" name.
497 from_cs = csToConvName(cs);
499 // Get current locale's iconv name - this is the "to" name.
501 to_cs = locToConvName();
503 if ( from_cs && to_cs ) {
504 if ( strcasecmp(from_cs, to_cs) != 0 ) {
505 unsigned long tmp_len = (unsigned long) _body_decoded_len;
506 if (csConvert(&_body, tmp_len, (int)_must_free_body, from_cs, to_cs)) {
507 _must_free_body = DTM_TRUE;
508 _body_decoded_len = (int) tmp_len;
520 // End of For CHARSET
522 // Clear the error condition before proceeding. This is done
523 // because functions that take a DtMailEnv object as a parameter
524 // expect it to be cleared. (Already cleared above)
527 // If the body is text/enriched, convert it to text/plain
528 // At some point the front end should be able to handle this via
529 // data typing and do an 'intelligent' conversion, in which
530 // case this code can be removed
535 _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
538 if (_body_env && !error.isSet() &&
539 ((strncasecmp(*(value[0]), "text/enriched", 13) == 0)
540 || (strncasecmp(*(value[0]), "text/richtext", 13) == 0))) {
541 char *new_body = (char *)malloc((unsigned)(_body_len*2));
543 RFCMIME::readTextEnriched(new_body, size, _body, _body_decoded_len);
544 new_body = (char *)realloc(new_body, size+2);
545 if (_must_free_body == DTM_TRUE)
547 _must_free_body = DTM_TRUE;
548 _body_decoded_len = size;
550 // Changed this temporarily until after release. We really should not
551 // be null terminating these buffers.
552 (_body)[_body_decoded_len] = 0;
558 // NOTES ON HANDLING OF "FILE NAMES" FOR BODY PARTS IN MIME COMPLIANT ENTITIES:
559 // (see full description in evaluation for bug 1189035)
561 // The ability to provide a "file name" for a body part in a MIME compliant
562 // entity is partially addressed in RFC 1341 [MIME: Multipurpose Internet Mail
563 // Extentions] and RFC 1521 [which obsoleted RFC 1341].
565 // RFC 1341 proposed a solution to the file name problem (Content-Type:
566 // type; name=name); RFC 1521 subsequently depreciated this solution in
567 // favor of a to-be-defined future specification of "Content-Disposition".
569 // In essence there is an anticipation that "Content-Disposition" will
570 // be defined in a future RFC such that the notion of a "file name" may
571 // be given to a body part of a MIME compliant entity.
573 // OpenWindows mailtool currently recognizes "Content-Description" on MIME
574 // compliant entities and uses that information as the "file name" for
575 // an attachment (as per a loose interpretation of RFC 1521).
577 // CDE DtMail currently sends out and recognizes "X-Content-Name" on MIME
578 // compliant entities and uses that information as the "file name" for an
579 // attachment. This is essentially a non-standard header field as per RFC
582 // "X-" fields may be created for experimental or private purposes,
583 // with the recognition that the information they contain may be
584 // lost at some gateways.
586 // Since OpenWindows mailtool does not understand "X-Content-Name", any
587 // body part in e-mail originating from CDE DtMail does not appear to
588 // have a file name when read by mailtool.
590 // Given these facts:
592 // . OpenWindows mailtool cannot be changed until at least the Solaris
593 // 2.5 release (if at all).
595 // . OpenWindows mailtool is recognizing a valid MIME header field
596 // which is essentially "free form" in nature.
598 // . CDE DtMail is currently using an experimental or private field
599 // which is not part of the standard and is not guaranteed to survive
600 // transport across gateways.
602 // . There is no officially proscribed method for providing the "file
603 // name" of a body part in a MIME compliant entity in RFC 1521.
605 // The reasonable approach to take to solve this problem is:
607 // 1. Have DtMail use "Content-Description" to transmit the "file name"
608 // for a body part - this achieves compatibility with mailtool.
610 // 2. Have DtMail also continue to use "X-Content-Name" to transmit the
611 // "file name" - this maintains compatibility with previous versions
612 // of DtMail which only recognize this header.
614 // 3. Have Dtmail recognize both "Content-Description" and "X-Content-Name"
615 // as specifying the "file name" for a body part. In the case that both
616 // fields are present, "Content-Description" takes precedence over
619 // 4. When "Content-Disposition" is properly defined and included as part
620 // of an updated MIME specification, revisit this issue.
623 // So, since dtmail currently *ignores* the "Content-Description" field,
624 // we overload the "getNameHeaderVal" function to use "Content-Description"
625 // first, then the older unofficial experimental "X-Content-Name". If
626 // and when Content-Disposition takes hold, it will have to override
627 // Content-Description when both are present.
632 // The Content-Disposition field has been designated as the primary
633 // header field for transmitting file names. See RFC 1806. Therefore
634 // the algorithm has been updated as follows.
636 // 1. DtMail checks the following headers for the "filename" for a body part:
637 // o The "filename" parameter of the "Content-Disposition" header field.
638 // o The contents of the "Content-Description" header field.
639 // o The "name" parameter of the "Content-Type" header field.
640 // o The contents of the "Content-Name" header field.
641 // o The contents of the "X-Content-Name" header field.
643 // 2. DtMail uses the following fields to specify the "filename" for a body
644 // part in outgoing mail:
645 // o The "filename" parameter of the "Content-Disposition" header field.
646 // o The contents of the "Content-Description" header field.
650 MIMEBodyPart::getDescription(DtMailEnv &)
652 // Don't have this return anything without checking
653 // ramifications with getNameHeaderValue
655 // No need to clear error object here because we assume it has already
656 // been cleared by the caller and nothing has touched it in this method.
663 MIMEBodyPart::getNameHeaderVal(DtMailEnv & error)
665 DtMailValueSeq value;
667 if (_body_env == NULL) {
668 // No need to clear the error object...it is unchanged from the
669 // state we received it in.
674 // The current standard seems to be to use the "Content-Disposition"
675 // header as the primary mechanism for transmitting file names.
677 _body_env->getHeader(error, "Content-Disposition", DTM_FALSE, value);
678 if (error.isNotSet()) {
679 char *param = parameterValue(value, "filename", DTM_FALSE);
681 return strdup(param);
685 // In keeping with the current undefined nature of
686 // file names for body parts in RFC 1521, and to be
687 // compatible with OpenWindows mailtool, the first
688 // overriding "name" comes from "Content-Description"
690 _body_env->getHeader(error, "Content-Description", DTM_FALSE, value);
691 if (error.isNotSet()) {
692 return(strdup(*(value[0])));
696 // For backward compatibility with older mail agents, check the "Name"
697 // parameter in the "Content-Type" header.
699 _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
700 if (error.isNotSet()) {
701 char *param = parameterValue(value, "name", DTM_FALSE);
703 return strdup(param);
707 // Next we remain compatible with previous versions
708 // of DtMail that used "X-Content-Name" instead of
709 // "Content-Description" for the file name of a
710 // body part (1-27-95)
712 _body_env->getHeader(error, "X-Content-Name",
714 if (error.isNotSet()) {
715 return(strdup(*(value[0])));
719 // This code was in dtmail on 1-27-95 and even though
720 // that version of dtmail did not send out "Content-Name"
721 // (which is not part of RFC 1341 or 1521) it doesnt
722 // hurt to recognize it if its the only header there.
724 _body_env->getHeader(error, "Content-Name",
726 if (error.isNotSet()) {
727 return(strdup(*(value[0])));
729 error.clear(); // NULL is the real error here.
731 // No name for this body part
737 MIMEBodyPart::getName(DtMailEnv & error)
739 char * h_name = getNameHeaderVal(error);
740 // don't care about the error returned by getNameHeaderVal()
750 return(strdup("Attachment"));
754 char * pat = DtDtsDataTypeToAttributeValue(_body_type,
759 int max_len = strlen(pat) + 20;
760 char * name = (char*) malloc((size_t) max_len);
761 sprintf(name, pat, "Attachment");
762 DtDtsFreeAttributeValue(pat);
766 return(strdup("Attachment"));
770 MIMEBodyPart::setName(DtMailEnv & error, const char * name)
773 _body_env->setHeader(error, "X-Content-Name", DTM_TRUE, name);
778 MIMEBodyPart::getLength(DtMailEnv & error)
780 MutexLock lock_scope(_obj_mutex);
784 // propogate the error back to the caller.
788 // We have to treat external bodies differently. The headers on these
789 // parts contain useful information to the client.
791 const char * mime_type;
792 DtMailValueSeq value;
795 _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
798 if (_body_env && !error.isSet()) {
799 mime_type = *(value[0]);
802 // only need to clear the error object if getHeader returned
803 // an error condition. We don't want to propogate the error
804 // back up the calling sequence.
806 mime_type = "text/plain";
811 if (strncasecmp(mime_type, "message/external-body", 21) == 0) {
812 const char * contents = _body_start;
813 for (;contents < (_body_text + _body_len); contents++) {
814 if (*contents == '\n') {
820 len = _body_len + (_body_text - contents);
823 len = _body_decoded_len;
830 MIMEBodyPart::rfcSize(const char *, DtMailBoolean &)
836 MIMEBodyPart::writeBodyParts(char * buf)
842 MIMEBodyPart::getBody(DtMailEnv & error)
844 // No need to clear the error here, should be cleared by the object
845 // that instantiated it.
850 // loadBody currently (version 1.31) clears the error before
851 // returning, so the following check is not needed. We'll
852 // leave it alone on the chance that loadBody() will someday
853 // return an error condition.
859 // We have to treat external bodies differently. The headers on these
860 // parts contain useful information to the client.
862 const char * mime_type;
863 DtMailValueSeq value;
866 _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
869 if (_body_env && !error.isSet()) {
870 // Handle "Content-Type: text" problem with /usr/lib/mail.local
872 if (strcasecmp(*(value[0]), "text")==0)
873 mime_type = "text/plain";
875 mime_type = *(value[0]);
879 mime_type = "text/plain";
882 const char * contents;
884 if (strncasecmp(mime_type, "message/external-body", 21) == 0) {
885 contents = _body_start;
886 for (;contents < (_body_text + _body_len); contents++) {
887 if (*contents == '\n') {
902 // Given the Content-Type field, extract the charset value.
903 // Returns one of the following:
905 // Caller MUST FREE this return value.
906 // 2) NULL if charset is not specified but Content-Type specifies
907 // text/plain data. If this routine returns NULL, that means
908 // charset is not specified but the data is text/plain. Therefore,
909 // (as in the case of MIMEBodyPart::loadBody) caller
910 // will call csToConvName("DEFAULT.<locale>") and csToConvName will return
911 // a default iconv conversion name for the current locale.
912 // This is the case because some (old) mailers do not set the charset value
913 // but encodes the message in a "popular" codeset. The default conversion
914 // name for a particular locale assumes the "popular" codeset used.
915 // 3) non NULL but invalid charset value if Content-Type does not specify
916 // text/plain data. Caller MUST FREE this return value.
918 MIMEBodyPart::csFromContentType(DtMailValueSeq &value)
921 char *val_ptr = NULL;
924 // value[0] should be valid else error would have occurred before
925 // this routine ever gets called. And value index is 0 because
926 // previous value stored should have been removed.
927 const char *val = *(value[0]);
929 // Check to see if Content-Type field specifies text/plain data.
930 // If so, look for charset value
931 // else, returns value in Content-Type field
932 if ( strstr(val, "text") == NULL ) {
933 if ( strstr(val, "TEXT") == NULL ) {
934 cs_str = strdup(val);
939 val_ptr = const_cast <char *> (strstr(val, "charset="));
940 if ( val_ptr == NULL ) {
941 val_ptr = const_cast <char *> (strstr(val, "CHARSET="));
943 if ( val_ptr == NULL ) {
948 // Check if charset value is quoted
949 if ( val_ptr[0] == '"' ) {
954 cs_str = strdup(strtok(val_ptr, "\""));
956 cs_str = (char *)calloc(strlen(val_ptr)+1, sizeof(char));
957 sscanf(val_ptr, "%s", cs_str);
962 // End of For CHARSET
966 MIMEBodyPart::parameterValue(
967 DtMailValueSeq &value,
968 const char * parameter,
969 DtMailBoolean isCaseSensitive)
976 val = strdup(*(value[0]));
977 vtok = strrchr(val, ';');
983 while(isspace(*vtok))
987 rtn = strncmp(vtok, parameter, sizeof(parameter));
989 rtn = strncasecmp(vtok, parameter, sizeof(parameter));
993 ptok = strrchr(vtok, '=');
1004 parm = strdup(strtok(ptok, (const char *) "\""));
1007 parm = strdup(ptok);
1013 vtok = strrchr(val, ';');