dthelp: Change to ANSI function definitions
[oweals/cde.git] / cde / programs / dtmail / libDtMail / RFC / V3BodyPart.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  *      $TOG: V3BodyPart.C /main/8 1998/07/23 18:04:38 mgreess $
27  *
28  *      RESTRICTED CONFIDENTIAL INFORMATION:
29  *      
30  *      The information in this document is subject to special
31  *      restrictions in a confidential disclosure agreement bertween
32  *      HP, IBM, Sun, USL, SCO and Univel.  Do not distribute this
33  *      document outside HP, IBM, Sun, USL, SCO, or Univel wihtout
34  *      Sun's specific written approval.  This documment and all copies
35  *      and derivative works thereof must be returned or destroyed at
36  *      Sun's request.
37  *
38  *      Copyright 1993 Sun Microsystems, Inc.  All rights reserved.
39  *
40  *+ENOTICE
41  */
42
43 #include <EUSCompat.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <ctype.h>
48
49 #include <Dt/Dts.h>
50
51 #include <DtMail/DtMail.hh>
52 #include "RFCImpl.hh"
53 #include "SunV3.hh"
54 #include <DtMail/Threads.hh>
55 #include "str_utils.h"
56
57 #include <assert.h>
58
59 // For CHARSET
60 #include <locale.h>
61 #include <DtHelp/LocaleXlate.h>
62
63 V3BodyPart::V3BodyPart(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     // This constructor is used for single body parts. This
71     // includes generic RFC822 messages, i.e. no Mime-Version
72     // field.
73     //
74     // There really isn't anything we can do. We may have to
75     // apply a content encoding, but let's wait until the user
76     // expresses an interest.
77     //
78   error.clear();
79
80     return;
81 }
82
83 // V3BodyPart:V3BodyPart -- retrieve next V3 message body part
84 // Arguments:
85 //  DtMailEnv & error           --
86 //  DtMail::Message * parent    --
87 //  const char * start          -- -> beginning of boundary separator
88 //  const char ** end           -- -> end of message
89 // Outputs:
90 //  *end        -- is set to the first byte past the end of the retrieved
91 //              part. This either points to the next body part (if any) or
92 //              is 1 byte past the end of the message if no further parts.
93 //  _body_env   -- RFCEnvelope defining entire body part
94 //  _body_len   -- true length of contained body part (minus headers)
95 //  _body_text  -- first byte of contained body part (minus headers)
96 // Description:
97 //  This constructor is used for multi-part body parts. Given a
98 //  pointer to the beginning of the boundary separator and a 
99 //  pointer to the end of the message, reduce pointers to actual
100 //  body part data (minus headers) and update class variables
101 //  as appropriate.
102 //
103
104 V3BodyPart::V3BodyPart(DtMailEnv & error,
105                        DtMail::Message * parent,
106                        const char * start,
107                        const char ** end)
108 : RFCBodyPart(error, parent, start, 0, NULL)
109 {
110     // Get past the boundary separator.
111     //
112     const char * body_end;
113     for (body_end = start; body_end <= *end && *body_end != '\n'; body_end++) {
114         continue;
115     }
116
117     if (body_end > *end) {
118         // no data after separator?? Bogus body part!
119         _body_len = 0;
120         *end = body_end;
121         return;
122     }
123
124     // The body part headers begin here. We will look from here until
125     // the first blank line for the end of the headers.
126     //
127     body_end += 1; // Chew the newline.
128     const char * env_start = body_end;
129     for (; body_end <= *end; body_end++) {
130         if (*body_end == '\n') {
131             int blank_only = 1;
132             for (const char * blank = body_end + 1;
133                  blank <= *end && *blank != '\n'; blank++) {
134                 if (!isspace((unsigned char)*blank)) {
135                     blank_only = 0;
136                     break;
137                 }
138             }
139             if (blank_only) {
140                 break;
141             }
142         }
143     }
144
145     _my_env = DTM_TRUE;         // indicate that we created this envelope
146     _body_env = new RFCEnvelope(error, parent, env_start, body_end - env_start + 1);
147
148     // Now we need to find the start of the body (the real start, not
149     // where the boundary begins. This is immediately after the newline
150     // that is placed after the end of the headers.
151     //
152     _body_text = body_end + 1;
153     for (; _body_text <= *end && *_body_text != '\n'; _body_text++) {
154         continue;
155     }
156     _body_text += 1;
157
158     // We now need to figure out where the end of the attachment is.
159     // We have 2 choices. Content lines, or content length.
160     //
161     DtMailValueSeq      value;
162
163     _body_env->getHeader(error, "x-sun-content-length",
164                          DTM_FALSE, value);
165     if (error.isNotSet()) {
166       // Great! content-length is much easier.
167       // We do this by computing a -> where the end of the attachment
168       // is supposed to be as indicated by the specified content length.
169       // If we come to the end of the message altogether, or to the start
170       // of another header body, then we know we have bounded this body part.
171       //
172       int content_length = (int) strtol(*(value[0]), NULL, 10);
173       body_end = _body_text + content_length;   // "supposed" end of body part
174
175       for (int i = 0; i < 2; i++) {
176         if (body_end == *end) {         // at end of message??
177             // Yes, this is the last header body in this message
178             // We are at the end -- force this body part to end at the
179             // end of the message and use length as computed.
180             // Mark end of this body for caller as 1 byte past end of last body header
181             // to prevent from being called again.
182             //
183             _body_len = content_length;
184             *end += 1;
185             return;
186         }
187
188         if (body_end > *end) {          // past end of message??
189             // Yes, this is the last header body in this message
190             // We are past the end -- force this body part to end at the
191             // end of the message and compute length based upon that assumption.
192             // Mark end of this body for caller as 1 byte past end of last body header
193             // to prevent from being called again.
194             //
195             *end += 1;
196             _body_len = *end - _body_text;
197             return;
198         }
199
200         // V3BodyParts have a "----------" (10 dashes) at the beginning of a 
201         // line with no white space before them and a new line at the end to 
202         // signal the beginning of an attachment.
203
204         if ((*(body_end-1) == '\n') && 
205              strncmp(body_end, "----------", 10) == 0 &&
206              isTerm(body_end + 10)) { // start of another body header?
207             // Yes, this is one of many header bodies in this message
208             // Mark end of this body for caller as first byte of next body header
209             //
210             assert(body_end < *end);    // cant be last
211             _body_len = body_end - _body_text;
212             *end = body_end+1;          // current end
213             return;
214         }
215
216         // Chew through white space and test again.
217         //
218         while (isspace((unsigned char)*body_end)) {
219             body_end += 1;
220         }
221       }
222     }
223     else {
224         error.clear();
225     }
226
227     // We didn't have content-length, or it was wrong or bogus.
228     // Try for content lines.
229     //
230     value.clear();              // clear out any previous header(s) retrieved
231     
232     _body_env->getHeader(error, "x-sun-content-lines",
233                          DTM_FALSE, value);
234     if (error.isNotSet()) {
235         // content-lines requires a bit o' muching first.
236         // We do this by marching through the body counting new lines
237         // until the specified number of lines have been passed. This
238         // is the determined end of the body part.
239         //
240         int content_lines = (int) strtol(*(value[0]), NULL, 10);
241         int lines = 0;
242         for (body_end = _body_text;
243              body_end <= *end && lines < content_lines; body_end++) {
244             if (*body_end == '\n') {
245                 lines += 1;
246             }
247         }
248
249         if (body_end >= *end) {         // at end of message??
250             // Yes, this is the last header body in this message
251             // We are at or past the end -- force this body part to end at the
252             // end of the message and compute length based upon that assumption.
253             // Mark end of this body for caller as 1 byte past end of last body header
254             // to prevent from being called again.
255             //
256             *end += 1;
257             _body_len = *end - _body_text;
258             return;
259         }
260
261         // V3BodyParts have a "----------" (10 dashes) at the beginning of a 
262         // line with no white space  before them and a new line at the end to 
263         // signal the beginning of an attachment.
264
265         if ((*(body_end-1) == '\n') && 
266              strncmp(body_end, "----------", 10) == 0 &&
267              isTerm(body_end + 10) == DTM_TRUE) { // start of another body header?
268             // Yes, this is one of many header bodies in this message
269             // Mark end of this body for caller as first byte of next body header
270             //
271             assert(body_end < *end);    // cant be last
272             _body_len = body_end - _body_text;
273             *end = body_end+1;          // current end
274             return;
275         }
276     }
277     else {
278         error.clear();
279     }
280
281     // We either didn't have a length, or number of lines, or they
282     // didn't work. We will now apply a dangerous but last resort 
283     // effort to find the end of the body by looking for the 10 dash 
284     // separator.
285     // V3BodyParts have a "----------" (10 dashes) at the beginning of a 
286     // line with no white space  before them and a new line at the end to 
287     // signal the beginning of an attachment.
288     //
289
290     for (body_end = _body_text; body_end <= (*end - 12); body_end++) {
291         if (*body_end == '\n' && 
292             strncmp(body_end + 1, "----------", 10) == 0 &&
293             isTerm(body_end + 11) == DTM_TRUE) {
294               // Dangerously by successfully located what appears to be the end
295               // of this attachment.
296               //
297               *end = body_end;
298               _body_len = body_end - _body_text;
299               return;
300         }
301     }
302
303     // At this point we are at the end of the message and are without
304     // the benefit of content lines or content length or the discovery
305     // of what appears to be a message separator. Assume this body part
306     // consumes the remainder of the message. Mark end of this body for
307     // caller as 1 byte past end to prevent from being called again.
308     //
309     *end += 1;
310     _body_len = *end - _body_text;
311     return;
312 }
313
314 V3BodyPart::~V3BodyPart(void)
315 {
316 }
317
318 #ifdef DEAD_WOOD
319 DtMailChecksumState
320 V3BodyPart::checksum(DtMailEnv & error)
321 {
322     error.clear();
323
324     return(DtMailCheckUnknown);
325 }
326 #endif /* DEAD_WOOD */
327
328 static int
329 countTypes(char ** types)
330 {
331     int count;
332
333     if (NULL == types) return 0;
334
335     for (count = 0; *types; types++, count++) {
336         continue;
337     }
338
339     return(count);
340 }
341
342 void
343 V3BodyPart::getContentType(DtMailEnv &error, char **v3_type)
344 {
345     MutexLock lock_scope(_obj_mutex);
346     MutexLock dt_lib_lock(_DtMutex);
347     DtMailValueSeq value;
348
349     if (v3_type) {
350         *v3_type = (char *)0;
351
352         _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
353         if (error.isNotSet()) {
354             *v3_type = strdup(*(value[0]));
355         } else {
356             error.clear();
357             value.clear();
358             _body_env->getHeader(error, "X-Sun-Data-Type", DTM_FALSE, value);
359             if (error.isNotSet()) {
360                 *v3_type = strdup(*(value[0]));
361             } else {
362                 error.clear();
363                 *v3_type = strdup("text");
364             }
365         }
366     }
367 }
368
369 void
370 V3BodyPart::getDtType(DtMailEnv & error)
371 {
372     MutexLock lock_scope(_obj_mutex);
373     MutexLock dt_lib_lock(_DtMutex);
374
375 // No need to clear the error object here because it should have
376 // been cleared by the object that instantiated it.
377 //    error.clear();
378
379     char * v3_type;
380     DtMailValueSeq value;
381     _body_env->getHeader(error, "Content-Type", DTM_FALSE, value);
382     if (error.isNotSet()) {
383         v3_type = strdup(*(value[0]));
384     }
385     else {
386         error.clear();
387         value.clear();
388         _body_env->getHeader(error, "X-Sun-Data-Type", DTM_FALSE, 
389                              value);
390         if (error.isNotSet()) {
391             v3_type = strdup(*(value[0]));
392         }
393         else {
394             error.clear();
395             v3_type = strdup("text");
396         }
397     }
398
399     char ** types = DtDtsFindAttribute("SUNV3_TYPE", v3_type);
400
401     if (NULL != types)
402     {
403         if (countTypes(types) == 1) {
404             // We will use the first name. It may be wrong, but
405             // it is the best we can do at this point.
406             //
407             _body_type = strdup(types[0]);
408             DtDtsFreeDataTypeNames(types);
409             free(v3_type);
410             return;
411         }
412         DtDtsFreeDataTypeNames(types);
413     }
414
415     // We need the bits so we can type the buffer and get
416     // a type for the object. This is where things can get
417     // very slow for the user.
418     //
419     loadBody(error);
420     if (error.isSet()) {
421         return;
422     }
423
424     char * name = getName(error);
425     if (error.isSet()) {
426         // don't care about error conditions returned by getName().
427         error.clear();
428     }
429
430     // If the name is "Attachment" and the type is "text", then
431     // use the type as the name to avoid the buffer being called
432     // generic data.
433     //
434     if (name && strcasecmp(name, "attachment") == 0 &&
435         strcasecmp(v3_type, "text") == 0) {
436         free(name);
437         name = strdup(v3_type);
438     }
439
440     char * type = DtDtsBufferToDataType(_body, _body_decoded_len, name);
441     _body_type = strdup(type);
442     DtDtsFreeDataType(type);
443     free(name);
444     free(v3_type);
445 //    error.clear();
446
447 }
448
449 void
450 V3BodyPart::loadBody(DtMailEnv &)
451 {
452 // For CHARSET
453     char *to_cs = NULL, *from_cs = NULL;
454     char *cs = new char[64];
455
456 // End of For CHARSET
457     int do_decode = 1;
458
459     if (_body) {
460         delete [] cs;
461         return;
462     }
463
464     // See if we are using an encoding.
465     DtMailValueSeq value;
466     DtMailEnv lerror;
467     _body_env->getHeader(lerror, "X-Sun-Encoding-Info",
468                          DTM_FALSE, value);
469     if (lerror.isSet() || (_body_len == 0)) {
470         // No Encodings.
471         lerror.clear();
472         _body = (char *)_body_text;
473         _must_free_body = DTM_FALSE;
474         _body_decoded_len = _body_len;
475         // Before the codeset conversion code was put in, return here.
476         do_decode = 0;
477     }
478
479         if (do_decode) {
480     _body_decoded_len = 0;
481     _must_free_body = DTM_TRUE;
482
483     SunV3::decode(*(value[0]), &_body, _body_decoded_len,
484                   _body_text, _body_len);
485     }
486
487 // For CHARSET
488         // If Content-Type is text, then get charset and do conversion.
489         // If Content-Type is X-sun-attachment, then if X-Sun-Data-Type is text,
490         // then get charset and do conversion.
491         // If Content-Type is missing, check if X-Sun-Data-Type exists, and
492         // if X-Sun-Data-Type exists and is text, then get charset and do conversion.
493         // If Content-Type and X-Sun-Data-Type are missing, then assume text so get
494         // charset and do conversion.
495         // Else don't need codeset conversion.
496     const char *cstmp = NULL;
497         const char *ct = NULL;
498         value.clear();
499         _body_env->getHeader(lerror, "Content-Type", DTM_FALSE, value);
500         if (lerror.isNotSet()) {
501            // Sun's V.3 Mail File Format requires a Content-Type header field to be
502            // "X-sun-attachment" for attachment message.  X-Sun-Data-Type header
503            // field is also required for attachment message.
504            ct = *(value[0]);
505            if ( strcasecmp(ct, "X-sun-attachment") == 0 ) {
506               value.clear();
507               _body_env->getHeader(lerror, "X-Sun-Data-Type", DTM_FALSE, value);
508               if (lerror.isNotSet()) {
509                  ct = *(value[0]);
510                  if ( strcasecmp(ct, "text") == 0 ) {
511                                 // Get charset from X-Sun-Text-Type which contains the name of
512                                 // the codeset for the text-based body part.  It is a mandatory
513                                 // header iff X-Sun-Data-Type is of type text.
514                                 value.clear();
515                                 _body_env->getHeader(lerror, "X-Sun-Text-Type", DTM_FALSE, value);
516                                 if (lerror.isNotSet()) {
517                 cstmp = *(value[0]);
518                     strcpy(cs, cstmp);
519                                 } else {
520                                 // We are not returning yet.  We'll be flexible here.
521                                 // Some mailers may not set this field.  We'll try to obtain charset
522                                 // from X-Sun-Charset or use the locale default.  See below.
523                                 lerror.clear();
524                                 }
525                          } else {    // Attachment not text type
526                     delete [] cs;
527                     return;
528                  }
529               } else {
530                      // Required field for attachment message not set -- return!
531                      delete [] cs;
532                      return;
533               }
534            } else if ( strcasecmp(ct, "text") != 0 ) {
535               delete [] cs;
536               return;
537            } 
538         } else {    // Content-Type does not exist!
539            lerror.clear();
540            value.clear();
541            _body_env->getHeader(lerror, "X-Sun-Data-Type", DTM_FALSE, value);
542            if (lerror.isNotSet()) {
543               ct = *(value[0]);
544               if ( strcasecmp(ct, "text") == 0 ) {
545                         // Get charset from X-Sun-Text-Type which contains the name of
546                         // the codeset for the text-based body part.  It is a mandatory
547                         // header iff X-Sun-Data-Type is of type text.
548                         value.clear();
549                         _body_env->getHeader(lerror, "X-Sun-Text-Type", DTM_FALSE, value);
550                         if (lerror.isNotSet()) {
551             cstmp = *(value[0]);
552                 strcpy(cs, cstmp);
553                         } else {
554                         // We are not returning yet.  We'll be flexible here.
555                         // Some mailers may not set this field.  We'll try to obtain charset
556                         // from X-Sun-Charset or use the locale default.  See below.
557                         lerror.clear();
558                         }
559              } else {    // Attachment not text type
560                 delete [] cs;
561                 return;
562          } 
563            } else {
564            // Base on Sun's V.3 Mail File Format version 1.9:  If no Content-Type,
565            // and X-Sun-Data-Type is not set (means no/not attachment,
566            // then assume text.  Proceed with getting charset.
567            lerror.clear();
568            }
569         }
570         if (cstmp == NULL) {
571         value.clear();
572     // Get charset from charset field
573         _body_env->getHeader(lerror, "X-Sun-Charset", DTM_FALSE, value);
574         if (lerror.isNotSet()) {
575        cstmp = *(value[0]);
576            strcpy(cs, cstmp);
577         } else {    // No Charset
578            // We'll be flexible here.  If Content-Type is missing or if Content-Type
579            // is text, then we get charset but if charset is missing, we'll try to
580            // convert from the locale specific default codeset.
581            lerror.clear();
582            char *ret = NULL;
583            strcpy(cs, "DEFAULT");
584            DtXlateOpToStdLocale(DtLCX_OPER_SETLOCALE,
585                   setlocale(LC_CTYPE, NULL),
586                   NULL,
587                   NULL,
588                   &ret);
589            strcpy(cs, "DEFAULT");
590            strcat(cs, ".");
591            strcat(cs, ret);
592            free( ret );
593         }
594         }  // If cstmp is NULL
595
596         // Handle ISO-2022-INT, RFC approved, or private encoding names
597         if ( strcasecmp(cs, "ISO-2022-INT-1") == 0 ) {
598            // Need to obtain charset from encoding
599         }  // RFC approved and private names are not treated differently.
600
601         // Get iconv name from charset - this is the "from" name.
602         from_cs = NULL;
603         from_cs = csToConvName(cs);
604
605         // Get current locale's iconv name - this is the "to" name.
606         to_cs = NULL;
607         to_cs = locToConvName();
608
609         if ( from_cs && to_cs ) {
610           if ( strcasecmp(from_cs, to_cs) != 0 ) {
611           unsigned long tmp_len = (unsigned long) _body_decoded_len;
612           if (csConvert(&_body, tmp_len, (int)_must_free_body, from_cs, to_cs)) {
613             _must_free_body = DTM_TRUE;
614                 _body_decoded_len = (int) tmp_len;
615       }
616           }
617       }
618
619     if ( from_cs )
620         free( from_cs );
621     if ( to_cs )
622         free ( to_cs );
623 // End of For CHARSET
624     delete [] cs;
625 }
626
627 char *
628 V3BodyPart::getDescription(DtMailEnv & error)
629 {
630 // No need to clear the error object here, whoever created it, cleared it.
631 //    error.clear();
632
633     DtMailValueSeq value;
634     _body_env->getHeader(error, "X-Sun-Data-Description",
635                          DTM_FALSE, value);
636     if (error.isNotSet()) {
637         return(strdup(*(value[0])));
638     }
639     error.clear();      // clear error so it doesn't propogate back up.
640     return(NULL);
641 }
642
643 char *
644 V3BodyPart::getName(DtMailEnv & error)
645 {
646 // No need to clear the error object here, it should be passed in already
647 // cleared.
648 //    error.clear();
649
650     DtMailValueSeq value;
651     _body_env->getHeader(error, "X-Sun-Data-Name",
652                          DTM_FALSE, value);
653     if (error.isNotSet()) {
654         return(strdup(*(value[0])));
655     }
656
657     // Since we are returning a valid name in spite of the call to 
658     // getHeader returning an error, we should clear the error.
659     //
660     error.clear();
661     return(strdup("Attachment"));
662 }
663
664 void
665 V3BodyPart::setName(DtMailEnv & error, const char * name)
666 {
667     _body_env->setHeader(error, "X-Sun-Data-Name", DTM_TRUE, name);
668 }
669
670 unsigned long
671 V3BodyPart::getLength(DtMailEnv & error)
672 {
673     MutexLock lock_scope(_obj_mutex);
674
675     loadBody(error);
676     if (error.isSet()) {
677         // propogate the error back to the caller
678         return (0);
679     }
680
681     return(_body_decoded_len);
682 }
683
684 int
685 V3BodyPart::rfcSize(const char *, DtMailBoolean &)
686 {
687     return(0);
688 }
689
690 char *
691 V3BodyPart::writeBodyParts(char *buf)
692 {
693   return(buf);
694 }
695
696 // Do not need to implement this method because getHeader already
697 // returns the X-Sun-Charset value.
698 char *
699 V3BodyPart::csFromContentType(DtMailValueSeq&)
700 {
701    return NULL;
702 }