dthelp: Change to ANSI function definitions
[oweals/cde.git] / cde / programs / dtmail / libDtMail / RFC / MIMEBodyPart.C
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
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)
10  * any later version.
11  *
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
16  * details.
17  *
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
22  */
23 /*
24  *+SNOTICE
25  *
26  *
27  *      $TOG: MIMEBodyPart.C /main/11 1998/04/06 13:27:03 mgreess $
28  *
29  *      RESTRICTED CONFIDENTIAL INFORMATION:
30  *      
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
37  *      Sun's request.
38  *
39  *      Copyright 1993 Sun Microsystems, Inc.  All rights reserved.
40  *
41  *+ENOTICE
42  */
43
44 #include <EUSCompat.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <ctype.h>
49
50 #include <Dt/Dts.h>
51
52 #include <DtMail/DtMail.hh>
53 #include "RFCImpl.hh"
54 #include "RFCMIME.hh"
55 #include <DtMail/Threads.hh>
56 #include "md5.h"
57
58 // For CHARSET
59 #include <locale.h>
60 #include <DtHelp/LocaleXlate.h>
61 #include "str_utils.h"
62
63 MIMEBodyPart::MIMEBodyPart(DtMailEnv & error,
64                          DtMail::Message * parent,
65                          const char * start,
66                          const int len,
67                          RFCEnvelope * body_env)
68 : RFCBodyPart(error, parent, start, len, body_env)
69 {
70     // A single part message. We are done for now.
71     //
72     error.clear();
73     return;
74 }
75
76 MIMEBodyPart::MIMEBodyPart(DtMailEnv & error,
77                            DtMail::Message * parent,
78                            const char * start,
79                            const char ** end,
80                            const char * boundary)
81 : RFCBodyPart(error, parent, start, 0, NULL)
82 {
83     error.clear();
84
85     // We are sitting at the start of a boundary. We need to get
86     // past the boundary to do the real processing.
87     //
88     const char * body_end;
89     for (body_end = _body_text; body_end <= *end && *body_end != '\n'; body_end++) {
90         continue;
91     }
92     body_end += 1;
93
94     if (body_end > *end) {
95         // Don't know. Give up!
96         *end = body_end;
97         _body_len = *end - start + 1;
98
99         // Need a bogus envelope for other uses.
100         //
101         _body_env = new RFCEnvelope(error, parent, NULL, 0);
102         return;
103     }
104
105     const char * env_start = body_end;
106     _my_env = DTM_TRUE;
107     if (isTerm(env_start)) {
108         _body_env = new RFCEnvelope(error, parent, env_start, 0);
109     }
110     else {
111         // Find the blank line where the envelope ends.
112         //
113         for (; body_end <= *end; body_end++) {
114             if (*body_end == '\n') {
115                 int blank_only = 1;
116                 for (const char * blank = body_end + 1;
117                      blank <= *end && *blank != '\n'; blank++) {
118                     if (!isspace((unsigned char)*blank)) {
119                         blank_only = 0;
120                         break;
121                     }
122                 }
123                 if (blank_only) {
124                     break;
125                 }
126             }
127         }
128         _body_env = new RFCEnvelope(error,
129                                     parent,
130                                     env_start,
131                                     body_end - env_start + 1);
132         body_end += 1;
133     }
134
135     // Chew everything up to the next new line, which should be only
136     // a CRLF.
137     //
138     for (;body_end <= *end && *body_end != '\n'; body_end++) {
139         continue;
140     }
141     body_end += 1;
142
143     _body_text = body_end; // This is where the body really starts.
144
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.
147     //
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) {
154             break;
155         }
156     }
157
158     if (*(body_end - 1) == '\r') {
159         body_end -= 1;
160     }
161     _body_len = body_end - _body_text + 1;
162
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++) {
167         continue;
168     }
169     body_end += 1;
170
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
173     // the message.
174     //
175     const char * bndry_end = body_end + 2 + strlen(boundary);
176     if (*bndry_end == '-' && *(bndry_end + 1) == '-') {
177         *end = *end + 1;
178     }
179     else {
180         *end = body_end;
181     }
182
183     return;
184 }
185
186 MIMEBodyPart::~MIMEBodyPart(void)
187 {
188 }
189
190 #ifdef DEAD_WOOD
191 DtMailChecksumState
192 MIMEBodyPart::checksum(DtMailEnv & error)
193 {
194     error.clear();
195
196     // Look for the Content-MD5 header. If it is not present, then
197     // the state is unknown and we can punt.
198     //
199     DtMailEnv my_error;
200     DtMailValueSeq value;
201     _body_env->getHeader(my_error, "Content-MD5", DTM_FALSE, value);
202     if (my_error.isSet()) {
203         return(DtMailCheckUnknown);
204     }
205
206
207     if (_body_type == NULL) {
208         getDtType(error);
209         if (error.isSet()) {
210             return(DtMailCheckUnknown);
211         }
212     }
213
214     char stored_digest[32];
215     int stored_size = 0;
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.
220         //
221         return(DtMailCheckBad);
222     }
223
224     // See if we call this text. We need to handle md5 checksums
225     // different for text. They must be computed with CRLF line
226     // termination.
227     //
228     char * text_type = DtDtsDataTypeToAttributeValue(_body_type,
229                                                      DtDTS_DA_IS_TEXT,
230                                                      NULL);
231
232     unsigned char digest[16];
233     if (text_type && strcasecmp(text_type, "true") == 0) {
234         RFCMIME::md5PlainText(_body, _body_decoded_len, digest);
235     }
236     else {
237         MD5_CTX context;
238         MD5Init(&context);
239         MD5Update(&context, (unsigned char *)_body, _body_decoded_len);
240         MD5Final(digest, &context);
241     }
242
243     free(text_type);
244
245     if (memcmp(digest, stored_digest, sizeof(digest)) == 0) {
246         return(DtMailCheckGood);
247     }
248     else {
249         return(DtMailCheckBad);
250     }
251 }
252 #endif /* DEAD_WOOD */
253
254 static int
255 countTypes(char ** types)
256 {
257     int count;
258     for (count = 0; *types; types++, count++) {
259         continue;
260     }
261
262     return(count);
263 }
264
265 void
266 MIMEBodyPart::getContentType(DtMailEnv &error, char **mime_type)
267 {
268     DtMailValueSeq value;
269
270     if (mime_type) {
271         *mime_type = (char *)0;
272
273         if (_body_env) {
274             _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
275         }
276
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");
281             } else {
282                 *mime_type = strdup(*(value[0]));
283             }
284         } else {
285             error.clear();
286             *mime_type = strdup("text/plain");
287         }
288     }
289 }
290
291 void
292 MIMEBodyPart::getDtType(DtMailEnv & error)
293 {
294     MutexLock lock_scope(_obj_mutex);
295     MutexLock dt_lib_lock(_DtMutex);
296
297 //    error.clear();
298
299     char * end;
300     char * mime_type;
301     DtMailValueSeq value;
302
303     if (_body_env) {
304         _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
305     }
306
307     if (_body_env && !error.isSet()) {
308         // Handle "Content-Type: text" problem with /usr/lib/mail.local
309         //
310         if (strcasecmp(*(value[0]), "text")==0)
311           mime_type = strdup("text/plain");
312         else
313           mime_type = strdup(*(value[0]));
314     }
315     else {
316         error.clear();
317         mime_type = strdup("text/plain");
318     }
319
320     for (end = mime_type; *end; end++) {
321         if (*end == ';' || isspace((unsigned char)*end)) {
322             break;
323         }
324         if (isupper(*end)) {
325             *end = tolower(*end);
326         }
327     }
328     *end = 0;
329
330     char ** types = DtDtsFindAttribute(DtDTS_DA_MIME_TYPE, mime_type);
331
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.
336     //
337     if (NULL != types) {
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.
341             //
342             _body_type = strdup(types[0]);
343             DtDtsFreeDataTypeNames(types);
344             free(mime_type);
345             return;
346         }
347         DtDtsFreeDataTypeNames(types);
348     }
349
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.
353     //
354     loadBody(error);
355     if (error.isSet()) {
356         return;
357     }
358
359     int istext = (strcasecmp(mime_type, "text/plain") == 0);
360     char * name = getNameHeaderVal(error);
361     char * type = DtDtsBufferToDataType(_body, _body_decoded_len, name);
362
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".
367     //
368     if ( (0 == strcasecmp(mime_type, "text/plain")) &&
369          (NULL == type || 0 == strcasecmp(type, "DATA")) )
370     {
371         if (type)
372           DtDtsFreeDataType(type);
373         type = DtDtsBufferToDataType(_body, _body_decoded_len, "text");
374     }
375
376     if (NULL != type)
377       _body_type = strdup(type);
378     else
379       _body_type = strdup("UNKNOWN");
380
381     if (error.isSet())
382       error.clear();
383     if (type)
384       DtDtsFreeDataType(type);
385     free(mime_type);
386     if (name)
387       free(name);
388 }
389
390 void
391 MIMEBodyPart::loadBody(DtMailEnv & error)
392 {
393 // For CHARSET
394         char *cs = NULL, *to_cs = NULL, *from_cs = NULL;
395
396 // There is no reason to clear the error object because it is assumed 
397 // that whoever instantiated it, cleared it.
398 //  error.clear();
399   
400   if (_body) {
401     return;
402   }
403
404   // If there is any encoding done to the body, reverse it
405   //
406   DtMailValueSeq value;
407   if (_body_env) {
408     _body_env->getHeader(error, "Content-Transfer-Encoding",
409                          DTM_FALSE, value);
410   }
411   
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
416       // encoded bodies.
417       //_body = (char *)malloc(_body_len); 
418
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;
424
425       // Changed this temporarily until after release. We really should not
426       // be null terminating these buffers.
427       (_body)[_body_decoded_len] = 0;
428     }
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;
435
436       // Changed this temporarily until after release. We really should not
437       // be null terminating these buffers.
438       (_body)[_body_decoded_len] = 0;
439     }
440     else {
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;
445     }
446   }
447   else {
448     // Default case is no transfer encoding applies.
449     error.clear(); 
450     _body = (char *)_body_text;
451     _must_free_body = DTM_FALSE;
452     _body_decoded_len = _body_len;
453    }
454
455 // For CHARSET
456         // Get charset from content-type field
457         char *ret = NULL;
458         value.clear();
459         _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
460         if (error.isNotSet()) {
461        cs = csFromContentType(value);
462        if ( cs == NULL ) {
463               // Random allocation for cs
464                   cs = (char *)calloc(128, sizeof(char));
465                   DtXlateOpToStdLocale(DtLCX_OPER_SETLOCALE,
466                          setlocale(LC_CTYPE, NULL),
467                          NULL,
468                          NULL,
469                          &ret);
470                   strcpy(cs, "DEFAULT");
471                   strcat(cs, ".");
472                   strcat(cs, ret);
473            }
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.
477            error.clear();
478            // Random allocation for cs
479            cs = (char *)calloc(128, sizeof(char));
480            DtXlateOpToStdLocale(DtLCX_OPER_SETLOCALE,
481                  setlocale(LC_CTYPE, NULL),
482                  NULL,
483                  NULL,
484                  &ret);
485            strcpy(cs, "DEFAULT");
486            strcat(cs, ".");
487            strcat(cs, ret);
488         }
489
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.
494
495         // Get iconv name from charset - this is the "from" name.
496     from_cs = NULL;
497     from_cs = csToConvName(cs);
498
499         // Get current locale's iconv name - this is the "to" name.
500         to_cs = NULL;
501         to_cs = locToConvName();
502
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;
509           }
510           }
511         }
512         free ( cs );
513         if ( from_cs )
514             free( from_cs );
515         if ( to_cs )
516             free ( to_cs );
517         if ( ret )
518             free( ret );
519         
520 // End of For CHARSET
521
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)
525     // error.clear();  
526
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
531   //
532
533   value.clear();
534   if (_body_env) {
535     _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
536   }
537
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));
542     int size = 0;
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)
546       free(_body);
547     _must_free_body = DTM_TRUE;
548     _body_decoded_len = size;
549     _body = new_body;
550     // Changed this temporarily until after release. We really should not
551     // be null terminating these buffers.
552     (_body)[_body_decoded_len] = 0;
553   }
554   error.clear();
555   return;
556 }
557
558 // NOTES ON HANDLING OF "FILE NAMES" FOR BODY PARTS IN MIME COMPLIANT ENTITIES:
559 // (see full description in evaluation for bug 1189035)
560 // 
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].
564 // 
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".
568 // 
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.
572 // 
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).
576 // 
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
580 // 1521:
581 // 
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.
585 // 
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.
589 // 
590 // Given these facts:
591 // 
592 //     . OpenWindows mailtool cannot be changed until at least the Solaris
593 //       2.5 release (if at all).
594 // 
595 //     . OpenWindows mailtool is recognizing a valid MIME header field 
596 //       which is essentially "free form" in nature.
597 // 
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.
601 // 
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.
604 // 
605 // The reasonable approach to take to solve this problem is:
606 // 
607 // 1. Have DtMail use "Content-Description" to transmit the "file name"
608 //    for a body part - this achieves compatibility with mailtool.
609 // 
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.
613 // 
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
617 //    "X-Content-Name".
618 // 
619 // 4. When "Content-Disposition" is properly defined and included as part
620 //    of an updated MIME specification, revisit this issue.
621 //
622 // 
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.
628 //
629
630 //
631 // March 25, 1997
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.
635 //
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.
642 //
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.
647 //
648
649 char *
650 MIMEBodyPart::getDescription(DtMailEnv &)
651 {
652   // Don't have this return anything without checking
653   // ramifications with getNameHeaderValue
654
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.
657 //    error.clear();
658
659     return(NULL);
660 }
661
662 char *
663 MIMEBodyPart::getNameHeaderVal(DtMailEnv & error)
664 {
665     DtMailValueSeq value;
666
667     if (_body_env == NULL) {
668         // No need to clear the error object...it is unchanged from the 
669         // state we received it in.
670         //error.clear();
671         return(NULL);
672     }
673
674     // The current standard seems to be to use the "Content-Disposition"
675     // header as the primary mechanism for transmitting file names.
676     //
677     _body_env->getHeader(error, "Content-Disposition", DTM_FALSE, value);
678     if (error.isNotSet()) {
679         char *param = parameterValue(value, "filename", DTM_FALSE);
680         if (NULL != param)
681             return strdup(param);
682     }
683     error.clear();
684
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"
689     //
690     _body_env->getHeader(error, "Content-Description", DTM_FALSE, value);
691     if (error.isNotSet()) {
692         return(strdup(*(value[0])));
693     }
694     error.clear();
695
696     // For backward compatibility with older mail agents, check the "Name"
697     // parameter in the "Content-Type" header.
698     //
699     _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
700     if (error.isNotSet()) {
701         char *param = parameterValue(value, "name", DTM_FALSE);
702         if (NULL != param)
703             return strdup(param);
704     }
705     error.clear();
706
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)
711     //
712     _body_env->getHeader(error, "X-Content-Name",
713                          DTM_FALSE, value);
714     if (error.isNotSet()) {
715         return(strdup(*(value[0])));
716     }
717     error.clear();
718
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.
723     //
724     _body_env->getHeader(error, "Content-Name",
725                          DTM_FALSE, value);
726     if (error.isNotSet()) {
727         return(strdup(*(value[0])));
728     }
729     error.clear(); // NULL is the real error here.
730
731     // No name for this body part
732     //
733     return(NULL);
734 }
735
736 char *
737 MIMEBodyPart::getName(DtMailEnv & error)
738 {
739     char * h_name = getNameHeaderVal(error);
740     // don't care about the error returned by getNameHeaderVal()
741     error.clear();
742     if (h_name) {
743         return(h_name);
744     }
745
746     if (!_body_type) {
747         getDtType(error);
748         if (error.isSet()) {
749             error.clear();
750             return(strdup("Attachment"));
751         }
752     }
753
754     char * pat = DtDtsDataTypeToAttributeValue(_body_type,
755                                                "NAME_TEMPLATE",
756                                                NULL);
757
758     if (pat) {
759         int max_len = strlen(pat) + 20;
760         char * name = (char*) malloc((size_t) max_len);
761         sprintf(name, pat, "Attachment");
762         DtDtsFreeAttributeValue(pat);
763         return(name);
764     }
765
766     return(strdup("Attachment"));
767 }
768
769 void
770 MIMEBodyPart::setName(DtMailEnv & error, const char * name)
771 {
772     if (_body_env) {
773         _body_env->setHeader(error, "X-Content-Name", DTM_TRUE, name);
774     }
775 }
776
777 unsigned long
778 MIMEBodyPart::getLength(DtMailEnv & error)
779 {
780     MutexLock lock_scope(_obj_mutex);
781
782     loadBody(error);
783     if (error.isSet()) {
784         // propogate the error back to the caller.
785         return (0);
786     }
787
788     // We have to treat external bodies differently. The headers on these
789     // parts contain useful information to the client.
790     //
791     const char * mime_type;
792     DtMailValueSeq value;
793
794     if (_body_env) {
795         _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
796     }
797
798     if (_body_env && !error.isSet()) {
799         mime_type = *(value[0]);
800     }
801     else {
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.
805         error.clear();
806         mime_type = "text/plain";
807     }
808
809     unsigned long len;
810
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') {
815                 break;
816             }
817         }
818         contents += 1;
819
820         len = _body_len + (_body_text - contents);
821     }
822     else {
823         len = _body_decoded_len;
824     }
825
826     return(len);
827 }
828
829 int
830 MIMEBodyPart::rfcSize(const char *, DtMailBoolean &)
831 {
832     return(0);
833 }
834
835 char *
836 MIMEBodyPart::writeBodyParts(char * buf)
837 {
838   return(buf);
839 }
840
841 const void *
842 MIMEBodyPart::getBody(DtMailEnv & error)
843 {
844 // No need to clear the error here, should be cleared by the object
845 // that instantiated it.
846 //    error.clear();
847
848     if (!_body) {
849         loadBody(error);
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.
854         if (error.isSet()) {
855             return(NULL);
856         }
857     }
858
859     // We have to treat external bodies differently. The headers on these
860     // parts contain useful information to the client.
861     //
862     const char * mime_type;
863     DtMailValueSeq value;
864
865     if (_body_env) {
866         _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
867     }
868
869     if (_body_env && !error.isSet()) {
870       // Handle "Content-Type: text" problem with /usr/lib/mail.local
871       //
872       if (strcasecmp(*(value[0]), "text")==0)
873         mime_type = "text/plain";
874       else
875         mime_type = *(value[0]);
876     }
877     else {
878         error.clear();
879         mime_type = "text/plain";
880     }
881
882     const char * contents;
883
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') {
888                 break;
889             }
890         }
891         contents += 1;
892     }
893     else {
894         contents = _body;
895     }
896
897     return(contents);
898 }
899
900 // For CHARSET
901
902 // Given the Content-Type field, extract the charset value.
903 // Returns one of the following:
904 // 1) charset value,
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.
917 char *
918 MIMEBodyPart::csFromContentType(DtMailValueSeq &value)
919 {
920    char *cs_str = NULL;
921    char *val_ptr = NULL;
922    int quoted = 0;
923
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]);
928
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);
935                 return cs_str;
936       }
937    } 
938    // Get charset value
939    val_ptr = const_cast <char *> (strstr(val, "charset="));
940    if ( val_ptr == NULL ) {
941      val_ptr = const_cast <char *> (strstr(val, "CHARSET="));
942    }
943    if ( val_ptr == NULL ) {
944           return NULL;
945    }
946    val_ptr = val_ptr+8;
947
948    // Check if charset value is quoted
949    if ( val_ptr[0] == '"' ) {
950           val_ptr++;
951           quoted = 1;
952    }
953    if ( quoted ) {
954           cs_str = strdup(strtok(val_ptr, "\""));
955    } else {
956           cs_str = (char *)calloc(strlen(val_ptr)+1, sizeof(char));
957       sscanf(val_ptr, "%s", cs_str);
958    }
959
960    return cs_str;
961 }
962 // End of For CHARSET
963
964
965 char *
966 MIMEBodyPart::parameterValue(
967                         DtMailValueSeq &value,
968                         const char * parameter,
969                         DtMailBoolean isCaseSensitive)
970 {
971     char *lasts=NULL;
972     char *ptok, *vtok;
973     char *parm, *val;
974     int rtn = 0;
975
976     val = strdup(*(value[0]));
977     vtok = strrchr(val, ';');
978     while (NULL != vtok)
979     {
980         *vtok = '\0';
981         vtok++;
982
983         while(isspace(*vtok))
984           vtok++;
985
986         if (isCaseSensitive)
987           rtn = strncmp(vtok, parameter, sizeof(parameter));
988         else
989           rtn = strncasecmp(vtok, parameter, sizeof(parameter));
990
991         if (0 == rtn)
992         {
993             ptok = strrchr(vtok, '=');
994             if (NULL == ptok)
995             {
996                 free(val);
997                 return NULL;
998             }
999
1000             ptok++;
1001             if (*ptok == '"' )
1002             {
1003                 ptok++;
1004                 parm = strdup(strtok(ptok, (const char *) "\""));
1005             }
1006             else
1007               parm = strdup(ptok);
1008             
1009             free(val);
1010             return parm;
1011         }
1012
1013         vtok = strrchr(val, ';');
1014     }
1015
1016     free(val);
1017     return NULL;
1018 }