Spelling fixes
[oweals/cde.git] / cde / programs / dtmail / libDtMail / RFC / RFCMIME.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: RFCMIME.C /main/13 1998/07/24 16:09:00 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, 1994, 1995 Sun Microsystems, Inc.  All rights reserved.
40  *
41  *+ENOTICE
42  */
43
44 #ifndef I_HAVE_NO_IDENT
45 #endif
46
47 #include <EUSCompat.h>
48 #include <errno.h>
49 #include <stdio.h>
50 #include <unistd.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <ctype.h>
54 #include <assert.h>
55
56 #include <Dt/Dts.h>
57
58 #include "md5.h"
59 #include "RFCMIME.hh"
60 #include <DtMail/DtMailP.hh>
61 #include <DtMail/IO.hh>
62 #include "str_utils.h"
63
64 // For CHARSET
65 #include <limits.h>
66 static int converted = 0;
67
68 #if defined(POSIX_THREADS)
69 extern "C" int rand_r(unsigned int);
70 #define brand(a)        rand_r(a)
71 #else
72 #define brand(a)        rand()
73 #endif
74
75 #define DIRECTION_STRING(dir) ((dir==CURRENT_TO_INTERNET) ? \
76                                 "CURRENT_TO_INTERNET" : \
77                                 "INTERNET_TO_CURRENT")
78
79 #define ENCODING_STRING(enc) ((enc==MIME_7BIT) ? "MIME_7BIT" : \
80                               ((enc==MIME_8BIT) ? "MIME_8BIT" : \
81                                ((enc==MIME_QPRINT) ? "MIME_QPRINT" : \
82                                "MIME_BASE64")))
83
84 #define NON_MAIL_SAFE(char) (((char >= 0) && (char <= 31) && (char != 9) && \
85                 (char != 0x0a)) || (char == 127))
86
87 inline unsigned int
88 base64size(const unsigned long len)
89 {
90     unsigned long b_len = len + (len / 3);
91     b_len += (b_len / 72 * 2) + 4;
92
93     return((unsigned int) b_len);
94 }
95
96 void
97 RFCMIME::getMIMEType(DtMail::BodyPart * bp, char * mime_type, DtMailBoolean & is_text)
98 {
99     // Get the Dt type name from the body part.
100     //
101     char * type = 0;
102     DtMailEnv error;
103
104     bp->getContents(error,
105                     NULL,
106                     NULL,
107                     &type,
108                     NULL,
109                     NULL,
110                     NULL);
111
112     // It is possible there is *no* contents associated with this
113     // body part - in that case, fake text/plain
114     //
115     if (error.isSet()) {
116       strcpy(mime_type, "text/plain");
117       return;
118     }
119     assert(type != NULL);
120     
121     // Look it up in the data typing system. Hopefully we will
122     // get a db based mime name.
123     //
124     char * db_type = DtDtsDataTypeToAttributeValue(type,
125                                                    DtDTS_DA_MIME_TYPE,
126                                                    NULL);
127
128     // See if we call this text. If so, then it will be text/plain,
129     // if not then application/octet-stream
130     //
131     char * text_type = DtDtsDataTypeToAttributeValue(type,
132                                                      DtDTS_DA_IS_TEXT,
133                                                      NULL);
134
135     if (db_type) {
136         strcpy(mime_type, db_type);
137     }
138     else {
139         if (text_type && strcasecmp(text_type, "true") == 0) {
140             strcpy(mime_type, "text/plain");
141         }
142         else {
143             strcpy(mime_type, "application/octet-stream");
144         }
145     }
146
147     is_text = text_type ? DTM_TRUE : DTM_FALSE;
148
149     free(type);
150     if (db_type) {
151         free(db_type);
152     }
153     if (text_type) {
154         free(text_type);
155     }
156
157     return;
158 }
159
160 RFCMIME::RFCMIME(DtMail::Session * session)
161 : RFCFormat(session)
162 {
163 }
164
165 RFCMIME::~RFCMIME(void)
166 {
167 }
168
169
170 RFCMIME::Encoding
171 RFCMIME::getEncodingType(const char * body,
172                          const unsigned int len,
173                          DtMailBoolean strict_mime,
174                          int *real8bit)
175 {
176   // Our goal here is to produce the most readable, safe encoding.
177   // We have a couple of parameters that will guide our
178   // choices:
179   //
180   // 1) RFC822 allows lines to be a minimum of 1000 characters,
181   //    but MIME encourages mailers to keep lines to <76 characters
182   //    and use quoted-printable if necessary to achieve this.
183   //
184   // 2) The base64 encoding will grow the body size by 33%, and
185   //    also render it unreadable by humans. We don't want to use
186   //    it unless really necessary.
187   //
188   // Given the above 2 rules, we want to scan the body part and
189   // select an encoding. The 3 choices will be decided by:
190   //
191   // 1) If the text is 7 bit clean, and all lines are <76 chars,
192   //    then no encoding will be applied.
193   //
194   // 2) If the text is not 7 bit clean, or there are lines >76 chars,
195   //    and the quoted-printable size is less than the base64 size,
196   //    then quoted-printable will be done.
197   //
198   // 3) If 1 & 2 are not true, then base64 will be applied.
199   //
200   // If "strict_mime" is false we will only encode if the message:
201   //    - is not 7 bit clean
202   //    - if any non-printing characters non-spacing characers
203   //
204   
205   if (body == NULL || len == 0) {
206     return(MIME_7BIT);
207   }
208   
209   const int base64_growth = base64size(len) - len;
210   int qprint_growth = 0;
211   DtMailBoolean seven_nonprinting = DTM_FALSE;
212   DtMailBoolean eight_bit = DTM_FALSE;
213   DtMailBoolean encode = DTM_FALSE;
214   
215   const char * last_nl = body;
216   const char * cur;
217   
218   if (strncmp(body, "From ", 5) == 0) {
219     qprint_growth += 2;
220   }
221   
222   for (cur = body; cur < (body + len); cur++) {
223     char curChar = *cur;
224     
225     if (curChar != (curChar & 0x7f)) {
226       eight_bit = DTM_TRUE;
227       encode = DTM_TRUE;
228       qprint_growth += 2;
229           *real8bit = 1;
230     }
231     else if (curChar == '=') {
232       // These characters don't force encoding, but will be 
233       // encoded if we end up encoding.
234       qprint_growth += 2;
235     }
236     else if ( ((curChar < ' ') || (curChar == 0x7f)) 
237             && (curChar != 0x09) && (curChar != 0x0A) ) {
238       // These characters force encoding
239       //
240       seven_nonprinting = DTM_TRUE;
241       encode = DTM_TRUE;
242       qprint_growth += 2;
243     }
244
245     if (curChar == '\n') {
246 #ifdef DEAD_WOOD
247       DtMailProcessClientEvents();
248 #endif /* DEAD_WOOD */
249       
250       if ((cur - last_nl) > 76) {
251         encode = DTM_TRUE;
252         qprint_growth += 2;
253       }
254       
255       if ((cur != body && (*(cur - 1) == ' ' || *(cur - 1) == '\t'))) {
256         encode = DTM_TRUE;
257         qprint_growth += 2;
258       }
259       
260       if ( ((cur + 6) < (body + len) )
261            && (strncmp((cur + 1), "From ", 5) == 0) ) {
262         encode = DTM_TRUE;
263         qprint_growth += 2;
264       }
265       
266       last_nl = cur + 1;
267     }
268     
269   }
270   
271   // Deal with buffers that don't end with a new line.
272   //
273   if ((cur - last_nl) > 76) {
274     encode = DTM_TRUE;
275     qprint_growth += 2;
276   }
277   
278   Encoding enc = MIME_7BIT;
279   
280   if (!strict_mime && !eight_bit && !seven_nonprinting) {
281     // If strict_mime is off we only encode if we have 8 bit data
282     // of most of the non-printing 7-bit characters
283     //
284     enc = MIME_7BIT;
285   }
286   else if (encode) {
287     // strict_mime is TRUE and we have reason to encode.
288     if (qprint_growth > base64_growth) {
289       enc = MIME_BASE64;
290     }
291     else {
292       enc = MIME_QPRINT;
293     }
294   }
295   
296   return(enc);
297 }
298
299 #ifdef NEVER
300 // This was the original routine, but it did not handle
301 // strictmime correctly as it was too eager to encode.  I'm
302 // leaving it here for now since this is touchy code.
303 RFCMIME::Encoding
304 RFCMIME::getEncodingType(const char * body,
305                          const unsigned int len,
306                          DtMailBoolean strict_mime)
307 {
308     // Our goal here is to produce the most readable, safe encoding.
309     // We have a couple of parameters that will guide our
310     // choices:
311     //
312     // 1) RFC822 allows lines to be a minimum of 1000 characters,
313     //    but MIME encourages mailers to keep lines to <76 characters
314     //    and use quoted-printable if necessary to achieve this.
315     //
316     // 2) The base64 encoding will grow the body size by 33%, and
317     //    also render it unreadable by humans. We don't want to use
318     //    it unless really necessary.
319     //
320     // Given the above 2 rules, we want to scan the body part and
321     // select an encoding. The 3 choices will be decided by:
322     //
323     // 1) If the text is 7 bit clean, and all lines are <76 chars,
324     //    then no encoding will be applied.
325     //
326     // 2) If the text is not 7 bit clean, or there are lines >76 chars,
327     //    and the quoted-printable size is less than the base64 size,
328     //    then quoted-printable will be done.
329     //
330     // 3) If 1 & 2 are not true, then base64 will be applied.
331     //
332
333     if (body == NULL || len == 0) {
334         return(MIME_7BIT);
335     }
336
337     const int base64_growth = base64size(len) - len;
338     int qprint_growth = 0;
339     DtMailBoolean eight_bit = DTM_FALSE;
340     DtMailBoolean base64 = DTM_FALSE;
341
342     const char * last_nl = body;
343
344     if (strncmp(body, "From ", 5) == 0) {
345         qprint_growth += 2;
346     }
347
348     for (const char * cur = body; cur < (body + len); cur++) {
349         if (*cur != (*cur & 0x7f) || *cur == '=' || *cur == 0) {
350             eight_bit = DTM_TRUE;
351             qprint_growth += 2;
352         }
353
354         if (*cur == '\n') {
355 #ifdef DEAD_WOOD
356             DtMailProcessClientEvents();
357 #endif /* DEAD_WOOD */
358             
359             if (strict_mime && ((cur - last_nl) > 76)) {
360                 qprint_growth += 2;
361             }
362
363             if (strict_mime && 
364                 (cur != body && (*(cur - 1) == ' ' || *(cur - 1) == '\t'))) {
365                 qprint_growth += 2;
366             }
367
368             if ((cur + 6) < (body + len) && strncmp((cur + 1), "From ", 5) == 0) {
369                 qprint_growth += 2;
370             }
371
372             last_nl = cur + 1;
373         }
374
375         if (qprint_growth > base64_growth) {
376             base64 = DTM_TRUE;
377             break;
378         }
379     }
380
381     // Deal with buffers that don't end with a new line.
382     //
383     if (strict_mime && ((cur - last_nl) > 76)) {
384         qprint_growth += 2;
385     }
386
387     Encoding enc = MIME_7BIT;
388
389     if (base64 == DTM_TRUE) {
390         enc = MIME_BASE64;
391     }
392     else if (eight_bit == DTM_TRUE || qprint_growth > 0) {
393         enc = MIME_QPRINT;
394     }
395
396     return(enc);
397 }
398 #endif
399
400 RFCMIME::Encoding
401 RFCMIME::getClearEncoding(const char * bp, const unsigned int bp_len, int *real8bit)
402 {
403     // Return the appropriate "non-encoding". If we have 8 bit data,
404     // then return 8bit. If not, then this is a 7bit encoding.
405     //
406     for (const char * cur = bp; cur < (bp + bp_len); cur++) {
407         if (*cur != (*cur & 0x7f)) {
408                 *real8bit = 1;
409             return(MIME_8BIT);
410         }
411     }
412
413     return(MIME_7BIT);
414 }
415
416 void
417 RFCMIME::writeContentHeaders(Buffer & hdr_buf,
418                              const char * type, const char * name,
419                              const Encoding enc,
420                              const char * digest,
421                              DtMailBoolean show_as_attachment,
422                              int is2022ASCII )
423 {
424     hdr_buf.appendData("Content-Type: ", 14);
425     hdr_buf.appendData(type, strlen(type));
426
427     if (strcasecmp(type, "text/plain") == 0) {
428         char default_charset[64];
429         if (!is2022ASCII && !converted) {
430           strcpy(default_charset, "us-ascii");
431         } else {
432           getCharSet(default_charset);
433         }
434
435         int len = strlen(default_charset);
436
437         hdr_buf.appendData("; charset=", 10);
438
439         hdr_buf.appendData(default_charset, len);
440     }
441
442     crlf(hdr_buf);
443
444     hdr_buf.appendData("Content-Transfer-Encoding: ", 27);
445
446     // For CHARSET
447         // If codeset conversion was done and strict mime mode is off,
448         // then transfer encoding should not be done and therefore, should
449         // set transfer encoding value to "binary".
450         DtMailEnv error;
451     DtMail::MailRc * mail_rc = _session->mailRc(error);
452
453     DtMailBoolean strict_mime = DTM_FALSE;
454     const char * rc_value;
455     mail_rc->getValue(error, "strictmime", &rc_value);
456     if (error.isNotSet()) {
457         strict_mime = DTM_TRUE;
458     }
459     error.clear();
460         // Read code below to see why and how strict_mime is used.
461         // End of For CHARSET
462
463     switch (enc) {
464       case MIME_7BIT:
465         hdr_buf.appendData("7bit", 4);
466         crlf(hdr_buf);
467         break;
468
469       case MIME_8BIT:
470       default: // Assume the worst.
471         hdr_buf.appendData("8bit", 4);
472         crlf(hdr_buf);
473         break;
474
475       case MIME_QPRINT:
476         if (converted && !strict_mime) {
477         hdr_buf.appendData("binary", 6);
478         } else {
479         hdr_buf.appendData("quoted-printable", 16);
480         }
481         crlf(hdr_buf);
482         break;
483
484       case MIME_BASE64:
485         if (converted && !strict_mime) {
486         hdr_buf.appendData("binary", 6);
487         } else {
488         hdr_buf.appendData("base64", 6);
489         }
490         crlf(hdr_buf);
491         break;
492     }
493
494     hdr_buf.appendData("Content-MD5: ", 13);
495     writeBase64(hdr_buf, digest, 16);
496
497     if (name) {
498         // Determine real length of the file name for the body part.
499         // Make sure we don't have any trailing crlf to confuse things.
500         //
501         int nlen = strlen(name);
502         if (name[nlen - 1] == '\n') {
503             nlen -= 1;
504         }
505
506         if (name[nlen - 1] == '\r') {
507             nlen -= 1;
508         }
509
510         char * tempName = strdup(name);
511         assert(tempName != NULL);
512
513 #if 0
514         // Now make sure that the name is "unix friendly"; convert
515         // any meta-characters into something innocuous
516         //
517         for (int i = 0; i < nlen; i++)
518           if (tempName[i] < ' ')
519             tempName[i] = '-';          // all control characters are unfriendly
520           else
521             switch(tempName[i]) {
522             case '~':
523               if (i > 0)                // ~ at beginning of a line is special
524                 break;
525             case '?':
526             case '*':
527             case '/':
528             case ' ':
529             case '{':
530             case '}':
531             case '[':
532             case ']':
533               tempName[i] = '-';        // "-" in unix and dos in innocuous
534               break;
535             default:
536               break;
537             }
538 #endif
539         
540         // According to RFC1806, the Content-Disposition header field
541         // Should be used to specify filenames.  However, the content
542         // disposition field is also used to specify how a body part
543         // is presented, inline or attachment.  The CDE mailer always
544         // presents the first bp inline and all others as attachments.
545         // 
546         hdr_buf.appendData("Content-Disposition: ", 21);
547         if (show_as_attachment)
548           hdr_buf.appendData("attachment; ", 12);
549         else
550           hdr_buf.appendData("inline; ", 8);
551         hdr_buf.appendData("filename=", 9);
552         rfc1522cpy(hdr_buf, tempName);
553
554         // Output Header: Content-Description: <body-part-name>
555         // This is in keeping with the current undefined nature
556         // of file names for body parts in RFC 1521 and is
557         // compatible with OpenWindows mailtool.
558         //
559         hdr_buf.appendData("Content-Description: ", 21);
560         rfc1522cpy(hdr_buf, tempName);
561
562         // The next crlf() is not needed because it is taken care of in 
563         // rfc1522cpy().  This additional call to crlf() causes attachments
564         // to be typed incorrectly.  For example, a shell script will be typed
565         // as a text file.
566 //      crlf(hdr_buf);
567
568 #if 0
569     // THE FOLLOWING IS IFDEF'ED OUT BECAUSE (from MIMEBodyPart.C)
570         // CDE DtMail currently sends out and recognizes "X-Content-Name" on MIME
571         // compliant entities and uses that information as the "file name" for an
572         // attachment. This is essentially a non-standard header field as per RFC
573         // 1521:
574         //
575         //     "X-" fields may be created for experimental or private purposes,
576         //     with the recognition that the information they contain may be
577         //     lost at some gateways.
578         //
579         // Output Header: X-Content-Name: <body-part-name>
580         // This is only to maintain compatibility with older
581         // versions of DtMail that used this field only.
582         // WE SHOULD REMOVE THIS AFTER THE OFFICIAL CDE RELEASE.
583         //
584         hdr_buf.appendData("X-Content-Name: ", 16);
585         hdr_buf.appendData(tempName, nlen);
586         crlf(hdr_buf);
587 #endif
588
589         free(tempName);         // done with tempName
590     }
591 }
592
593 //
594 // Base64 Alphabet (65-character subset of US-ASCII as per RFC1521)
595 //
596
597 static const char base64_chars[] = 
598 {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
599  'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a',
600  'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
601  'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0',
602  '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
603 };
604
605 inline unsigned char
606 base64inv(const char cur)
607 {
608     switch (cur) {
609       case '+':
610         return(62);
611
612       case '/':
613         return(63);
614
615       default:
616         break;
617     }
618
619     if ((cur - '0') < 10) {
620         return(cur - '0' + 52);
621     }
622
623     if ((cur - 'A') < 26) {
624         return(cur - 'A');
625     }
626
627     if ((cur - 'a') < 26) {
628         return(cur - 'a' + 26);
629     }
630
631     return(0);
632 }
633
634 //
635 // RFCMIME::readBase64 -- decode base 64 text into clear text
636 // Arguments:
637 //  char * buf          -- buffer into which clear text is to be placed
638 //  int & off           -- offset into buffer when placing clear text
639 //                      -- incremented as bytes are placed into buffer
640 //  char *bp            -- buffer from which base 64 text is retrieved
641 //  const unsigned long len -- number of bytes of base 64 text at bp
642 // Returns:
643 //  <void>
644 // Description:
645 //  Given a pointer/length to base 64 text, decode the base 64 text into
646 //  ordinary clear text. The decoding process proceeds from left to right
647 //  (buffer forward) decoding groups of 4-byte encoded octets into groups
648 //  of 3-byte clear text triplets. The last octet may contain padding as
649 //  necessary to represent a data stream that is not a multiple of 24 bits.
650 //
651
652 void
653 RFCMIME::readBase64(char * buf, int & off, const char * bp, const unsigned long len)
654 {
655     // Make local copy of base 64 text length
656     //
657     unsigned long main_len = len;
658
659     // Strip trailing white space from base 64 text so we can find the magic '=' 
660     // characters at the end which indicate if padding has been used to pad the
661     // last octet in the data stream
662     //
663     while (isspace(bp[main_len - 1])) {
664         main_len -= 1;
665     }
666
667     // Check to see if the last byte of the final octet in the base 64 text has
668     // the magic '=' character - if it does, bump down the base 64 text stream 
669     // count by 1 octet and leave decoding of the last octet until after all
670     // unpadded octets have been decoded
671     //
672     if (bp[main_len - 1] == '=') {
673         // We have normal base64 padding, which consists of one or two '=' characters
674         // at the end of the stream. Ignore it for now and process at the very end.
675         //
676         main_len -= 4;  // backup over last 4-byte encoding containing padding
677     }
678
679     // Main octet decoding loop. We are assured that main_len is mod 4, so
680     // we can zip through decoding full octets
681     //
682     for (const char * cur = bp; cur < (bp + main_len - 3); cur += 4) {
683         while (*cur == ' ') {
684             cur += 1;
685         }
686
687         if (*cur == '\r') {
688             cur += 1;
689         }
690         if (*cur == '\n') {
691             cur += 1;
692         }
693         unsigned char b1 = base64inv(*cur);
694
695         if (*(cur + 1) == '\r') {
696             cur += 1;
697         }
698         if (*(cur + 1) == '\n') {
699             cur += 1;
700         }
701         unsigned char b2 = base64inv(*(cur + 1));
702
703         if (*(cur + 2) == '\r') {
704             cur += 1;
705         }
706         if (*(cur + 2) == '\n') {
707             cur += 1;
708         }
709         unsigned char b3 = base64inv(*(cur + 2));
710
711         if (*(cur + 3) == '\r') {
712             cur += 1;
713         }
714         if (*(cur + 3) == '\n') {
715             cur += 1;
716         }
717         unsigned char b4 = base64inv(*(cur + 3));
718
719         buf[off++] = (char)((unsigned char)(b1 << 2) & 0xfc) | ((unsigned char)(b2 >> 4) & 0x3);
720         buf[off++] = (char)((unsigned char)(b2 & 0xf) << 4) | ((unsigned char)(b3 >> 2) & 0xf);
721         buf[off++] = (char)((unsigned char)(b3 & 0x3) << 6) | ((unsigned char)(b4 & 0x3f));
722     }
723
724     // Throw away any white space left in base 64 text, just in case.
725     //
726     while (main_len < len && isspace(bp[main_len])) {
727         main_len += 1;
728     }
729
730     // If end of stream was padded, there will be additional data not yet processed
731     // from the stream. Since encoding is three bytes clear to four bytes encoded,
732     // padding means there are one or two bytes at most remaining to be decoded from
733     // the last octet.
734     //
735     if (main_len != len) {
736         unsigned char b1 = base64inv(bp[main_len]);
737         unsigned char b2 = base64inv(bp[main_len + 1]);
738         unsigned char b3 = 0;
739         if (bp[main_len + 2] != '=') {
740             b3 = base64inv(bp[main_len + 2]);
741         }
742
743         buf[off++] = (char)((unsigned char)(b1 << 2) & 0xfc) | ((unsigned char)(b2 >> 4) & 0x3);
744         if (bp[main_len + 2] != '=') {
745             buf[off++] = (char)((unsigned char)(b2 & 0xf) << 4) | ((unsigned char)(b3 >> 2) & 0xf);
746         }
747     }
748
749     return;
750 }
751
752 void
753 RFCMIME::writeBase64(Buffer & buf, const char * bp, const unsigned long len)
754 {
755     if (bp == NULL || len == 0) {
756         crlf(buf);
757         return;
758     }
759
760     // The length has to be a multiple of 3. We will need to pad
761     // any extra. Let's just work on the main body and save the
762     // fractional stuff for the end.
763     //
764     unsigned long main_len = len - (len % 3);
765     const unsigned char * ubp = (const unsigned char *)bp;
766
767     char line[80];
768
769     unsigned int enc_char;
770
771     int lf = 0;
772
773     int block;
774     for (block = 0; block < main_len; block += 3) {
775         enc_char = (ubp[block] >> 2) & 0x3f;
776         line[lf++] = base64_chars[enc_char];
777
778         enc_char = ((ubp[block] & 0x3) << 4) | ((ubp[block+1] >> 4) & 0xf);
779         line[lf++] = base64_chars[enc_char];
780
781         enc_char = ((ubp[block+1] & 0xf) << 2) | ((ubp[block + 2] >> 6) & 0x3);
782         line[lf++] = base64_chars[enc_char];
783
784         enc_char = ubp[block + 2] & 0x3f;
785         line[lf++] = base64_chars[enc_char];
786
787         if (lf == 72) {
788             buf.appendData(line, lf);
789 #ifdef DEAD_WOOD
790             DtMailProcessClientEvents();
791 #endif /* DEAD_WOOD */
792             crlf(buf);
793             lf = 0;
794         }
795     }
796
797     if (lf > 0) {
798         buf.appendData(line, lf);
799     }
800
801     if (((lf + 4) % 72) == 0) {
802         crlf(buf);
803     }
804
805     switch(len % 3) {
806       case 1:
807         enc_char = (ubp[block] >> 2) & 0x3f ;
808         buf.appendData(&base64_chars[enc_char], 1);
809
810         enc_char = ((ubp[block] & 0x3) << 4);
811         buf.appendData(&base64_chars[enc_char], 1);
812
813         buf.appendData("==", 2);
814         break;
815
816       case 2:
817         enc_char = (ubp[block] >> 2) & 0x3f;
818         buf.appendData(&base64_chars[enc_char], 1);
819
820         enc_char = ((ubp[block] & 0x3) << 4) | ((ubp[block+1] >> 4) & 0xf);
821         buf.appendData(&base64_chars[enc_char], 1);
822
823         enc_char = ((ubp[block + 1] & 0xf) << 2);
824         buf.appendData(&base64_chars[enc_char], 1);
825
826         buf.appendData("=", 1);
827     }
828
829     crlf(buf);
830 }
831
832 // Function: RFCMIME::readTextEnriched - convert enriched to plain text
833 // Description:
834 //  This function converts a buffer containing enriched text into a buffer
835 //  containing the plain text translation of the enriched text
836 // Method:
837 //  This algorithm was adapted from the example simple enriched-to-plain
838 //  text translator contained in RFC 1563, Appendix A.
839 //
840 void
841 RFCMIME::readTextEnriched(char * buf, int & off, const char * bp, const unsigned long bp_len)
842 {
843   char c, i, paramct=0, newlinect=0, nofill=0;
844   char token[256], *p;
845   
846   const char *ebp = bp+bp_len;
847   for (const char * cur = bp; cur < ebp; cur++) {
848     c = *cur;
849     if (c == '<') {
850       if (newlinect == 1)
851         buf[off++] = ' ';
852       newlinect = 0;
853       c = *(++cur);
854       if (c == '<') {
855         if (paramct <= 0)
856           buf[off++] = c;
857       }
858       else {
859         for (i=0, p=token; (c = *cur) && (cur < ebp) && (c != '>'); i++, cur++) {
860           if (i < sizeof(token)-1)
861             *p++ = isupper(c) ? tolower(c) : c;
862         }
863         *p = '\0';
864         if (cur >= ebp)
865           break;
866         if (strcmp(token, "param") == 0)
867           paramct++;
868         else if (strcmp(token, "nofill") == 0)
869           nofill++;
870         else if (strcmp(token, "/param") == 0)
871           paramct--;
872         else if (strcmp(token, "/nofill") == 0)
873           nofill--;
874       }
875     } else {
876       if (paramct > 0)
877         ; /* ignore params */
878       else if ((c == '\n') && (nofill <= 0)) {
879         if (++newlinect > 1)
880           buf[off++] = c;
881       } else {
882         if (newlinect == 1)
883           buf[off++] = ' ';
884         newlinect = 0;
885         buf[off++] = c;
886       }
887     }
888   }
889   buf[off++] = '\n';
890   
891   return;
892 }
893
894 void
895 RFCMIME::readQPrint(
896                 char *buf, int &off,
897                 const char *bp, const unsigned long bp_len)
898 {
899     for (const char * cur = bp; cur < (bp + bp_len); cur++) {
900         if (*cur == '=') {
901             if (*(cur + 1) == '\n') {
902                 cur += 1;
903                 continue;
904             }
905             else if (*(cur + 1) == '\r' && *(cur + 2) == '\n') {
906                 cur += 2;
907                 continue;
908             }
909             else {
910                 if (isxdigit(*(cur + 1)) && isxdigit(*(cur + 2))) {
911                     char hex[3];
912                     hex[0] = *(cur + 1);
913                     hex[1] = *(cur + 2);
914                     hex[2] = 0;
915
916                     buf[off++] = (char) strtol(hex, NULL, 16);
917                     cur += 2;
918                     continue;
919                 }
920             }
921         }
922
923         buf[off++] = *cur;
924     }
925
926     return;
927 }
928
929 void
930 RFCMIME::writeQPrint(Buffer & buf, const char * bp, const unsigned long bp_len)
931 {
932   if (bp == NULL || bp_len == 0) {
933     crlf(buf);
934     return;
935   }
936   
937   int last_nl = 0;
938   int off = 0;
939   
940   // A line buffer for improving formatting performance. Note that
941   // QP requires all lines to be < 72 characters plus CRLF. So, a
942   // fixed size 80 character buffer is safe.
943   //
944   char tmp[20];                 // temp for constructing "octets"
945   char line_buf[80];
946   
947   // There are probably more elegant ways to deal with a message that
948   // begins with "From ", but we will simply due it this more simplistic
949   // way.
950   //
951   const char * start;
952   if (strncmp(bp, "From ", 5) == 0) {
953     memcpy(&line_buf[off], "=46", 3);
954     start = bp + 1;
955     off += 3;
956   }
957   else {
958     start = bp;
959   }
960   
961   // This loop will apply the encodings, following the rules identified
962   // in RFC1521 (though not necessarily in the order presented.
963   //
964   const char *cur;
965   for (cur = start; cur < (bp + bp_len); cur++) {
966     
967     // Rule #5: Part 1! We will try to break at white space
968     // if possible, but it may not be possible. In any case,
969     // we want to force the lines to be less than 76 characters.
970     //
971     if (off > 72) {
972       line_buf[off++] = '=';
973       buf.appendData(line_buf, off);
974       crlf(buf);
975       last_nl = 0;
976       off = 0;
977     }
978     
979     // Rule #1: Any octet, except those indicating a line break
980     // according to the newline convention mabe represented by
981     // an = followed by a two digit hexadecimal representation
982     // of the octet's value. We will represent any non-7bit
983     // data this way, but let the rest slide. We do wrap "="
984     // just to be safe.
985     //
986     if (*cur != (*cur & 0x7f) || *cur == '=' || NON_MAIL_SAFE(*cur)) {
987       sprintf(tmp, "=%02X", (int)(unsigned char)*cur);
988       memcpy(&line_buf[off], tmp, 3);
989       off += 3;
990       continue;
991     }
992     
993     // Rule #2: Octets with decimal values of 33 through 60
994     // inclusive and 62 through 126, inclusive, MAY be represented
995     // as the ASCII characters which correspond to those octets.
996     //
997     if ((*cur >= 33 && *cur <= 60) ||
998         (*cur >= 62 && *cur <= 126)) {
999       line_buf[off++] = *cur;
1000       continue;
1001     }
1002     
1003     // Rule #5: The q-p encoding REQUIRES that encoded lines be
1004     // no more than 76 characters long. If longer, an equal sign
1005     // as the last character n the line indicates a soft line break.
1006     //
1007     // This is tricky if you want to leave it reasonably readable
1008     // (why else do this?). We only want to break on white space.
1009     // At each white gap, we need to count forward to the next
1010     // white gap and see if we exceed the 76 character limit.
1011     // We will cheat a few characters to allow us some room
1012     // for arithmetic.
1013     //
1014     if (*cur == ' ' || *cur == '\t') {
1015       // Find the end of this clump of white space.
1016       //
1017       const char *nw;
1018       for (nw = cur; nw < (bp + bp_len) && *nw && *nw != '\n'; nw++) {
1019         if (!isspace((unsigned char)*nw)) {
1020           break;
1021         }
1022       }
1023       
1024       // Find the end of the next non-white region.
1025       //
1026       const char *white;
1027       for (white = nw;
1028            white < (bp + bp_len) && *white && !isspace((unsigned char)*white);
1029            white++) {
1030         continue;
1031       }
1032       
1033       int line_len = (off - last_nl) + (white - cur);
1034       if (line_len > 72) {
1035         // Need a soft line break. Lets put it after the
1036         // current clump of white space. We will break
1037         // at 72 characters, even if we arent at the end
1038         // of the white space. This prevents buffer overruns.
1039         //
1040         for (const char *cp_w = cur; cp_w < nw; cp_w++) {
1041           line_buf[off++] = *cp_w;
1042           if (off > 72) {
1043             line_buf[off++] = '=';
1044             buf.appendData(line_buf, off);
1045 #ifdef DEAD_WOOD
1046             DtMailProcessClientEvents();
1047 #endif /* DEAD_WOOD */
1048             crlf(buf);
1049             off = 0;
1050             last_nl = 0;
1051           }
1052         }
1053         
1054         // There is an edge case that we may have written the last
1055         // white space character in the for loop above. This will
1056         // prevent us from spitting an extra continuation line.
1057         //
1058         if (off) {
1059           line_buf[off++] = '=';
1060           buf.appendData(line_buf, off);
1061 #ifdef DEAD_WOOD
1062           DtMailProcessClientEvents();
1063 #endif /* DEAD_WOOD */
1064           crlf(buf);
1065           last_nl = 0;
1066           off = 0;
1067         }
1068         
1069         // If we created a "From " at the front we need to wrap
1070         // it to protect from parsers.
1071         //
1072         if ((nw + 5) < (bp + bp_len) && strncmp(nw, "From ", 5) == 0) {
1073           memcpy(&line_buf[off], "=46", 3);
1074           off += 3;
1075           cur = nw;
1076         }
1077         else {
1078           cur = nw - 1;
1079         }
1080       }
1081       else {
1082         line_buf[off++] = *cur;
1083       }
1084       
1085       continue;
1086     }
1087     
1088     // Rule 3: Octets with values of 9 and 32 MAY be represented
1089     // as ASCII TAB and SPACE but MUST NOT be represented at the
1090     // end of an encoded line. We solve this be encoding the last
1091     // white space before a new line (except a new line) using
1092     // Rule #1.
1093     //
1094     if (*cur == '\n') {
1095       if (cur == start) {
1096         crlf(buf);
1097       }
1098       else {
1099         last_nl = off + 1;
1100         
1101         char prev = *(cur - 1);
1102         if ((prev == ' ' || prev == '\t') && prev != '\n') {
1103           off = off ? off - 1 : off;
1104           
1105           char tmpbuf[20];
1106           sprintf(tmpbuf, "=%02X", *(cur - 1));
1107           memcpy(&line_buf[off], tmpbuf, 3);
1108           off += 3;
1109         }
1110         
1111         buf.appendData(line_buf, off);
1112 #ifdef DEAD_WOOD
1113         DtMailProcessClientEvents();
1114 #endif /* DEAD_WOOD */
1115         last_nl = 0;
1116         off = 0;
1117         
1118         if (*(cur - 1) == '\r') {
1119           buf.appendData(cur, 1);
1120         }
1121         else {
1122           crlf(buf);
1123         }
1124       }
1125       // We need to munge a line that starts with "From " to it
1126       // protect from parsers. The simplest way is to encode the
1127       // "F" using rule #1.
1128       //
1129       if ((cur + 5) < (bp + bp_len) && strncmp((cur + 1), "From ", 5) == 0) {
1130         memcpy(&line_buf[off], "=46", 3);
1131         off += 3;
1132         cur += 1;
1133       }
1134       continue;
1135     }
1136
1137     // if we have gotten this far, we could not figure out *what* to
1138     // do with the "octet" at *cur - in this case, apply Rule #1
1139     // (General 8-bit representation)
1140     //
1141     sprintf(tmp, "=%02X", (int)(unsigned char)*cur);
1142     memcpy(&line_buf[off], tmp, 3);
1143     off += 3;
1144
1145   }     // end of big "for" loop
1146   
1147   if (off > 0) {
1148     buf.appendData(line_buf, off);
1149   }
1150   
1151   if (*(cur - 1) != '\n') {
1152     crlf(buf);
1153   }
1154 }
1155
1156 void
1157 RFCMIME::writePlainText(Buffer & buf, const char * bp, const unsigned long len)
1158 {
1159     // It may seem silly, but we do need to make sure every line ends with
1160     // a CRLF pair. Most buffers will end with only LF.
1161     //
1162     if (bp == NULL || len == 0) {
1163         crlf(buf);
1164         return;
1165     }
1166
1167     const char * line_start = bp;
1168     const char * cur;
1169     for (cur = bp; cur < (bp + len); cur++) {
1170         if (*cur == '\n') {
1171             const char * real_end = cur;
1172
1173             if (cur != bp && *(cur - 1) == '\r') {
1174                 real_end -= 1;
1175             }
1176
1177             buf.appendData(line_start, real_end - line_start);
1178 #ifdef DEAD_WOOD
1179             DtMailProcessClientEvents();
1180 #endif /* DEAD_WOOD */
1181             line_start = cur + 1;
1182             crlf(buf);
1183         }
1184     }
1185
1186     if (line_start < cur) {
1187         buf.appendData(line_start, cur - line_start);
1188     }
1189
1190     // The body should end with a CRLF.
1191     //
1192     if (*(cur - 1) != '\n') {
1193         crlf(buf);
1194     }
1195 }
1196
1197 void
1198 RFCMIME::md5PlainText(const char * bp, const unsigned long len, unsigned char * digest)
1199 {
1200     // We need to compute the md5 signature based on a message that has
1201     // the CRLF line terminator. Most of our buffers don't so we will need
1202     // to scan the body and do some magic. The approach will be to sum
1203     // one line at a time. If the buffer doesn't have CRLF we will do that
1204     // independently.
1205     //
1206
1207     MD5_CTX context;
1208     MD5Init(&context);
1209     unsigned char * local_crlf = (unsigned char *)"\r\n";
1210
1211     const char * last = bp;
1212     const char * cur;
1213     for (cur = bp; cur < (bp + len); cur++) {
1214         if (*cur == '\n') {
1215 #ifdef DEAD_WOOD
1216             DtMailProcessClientEvents();
1217 #endif /* DEAD_WOOD */
1218             if (cur == bp || *(cur - 1) == '\r') {
1219                 MD5Update(&context, (unsigned char *)last,
1220                           cur - last + 1);
1221             }
1222             else {
1223                 MD5Update(&context, (unsigned char *)last,
1224                           cur - last);
1225                 MD5Update(&context, local_crlf, 2);
1226             }
1227             last = cur + 1;
1228         }
1229     }
1230
1231     if (bp[len - 1] != '\n') {
1232         // Need to sum the trailing fraction with a CRLF.
1233         MD5Update(&context, (unsigned char *)last,
1234                   cur - last);
1235         MD5Update(&context, local_crlf, 2);
1236     }
1237
1238     MD5Final(digest, &context);
1239 }
1240
1241 void
1242 RFCMIME::formatBodies(DtMailEnv & error,
1243                       DtMail::Message & msg,
1244                       DtMailBoolean include_content_length,
1245                       char ** extra_headers,
1246                       Buffer & buf)
1247 {
1248 // For CHARSET
1249     char *from_cs = NULL, *to_cs = NULL;
1250         int eightbit = 0;
1251
1252         // This is a static global variable that determines charset and whether
1253         // transfer encoding should be done.  Must (re)set this variable at
1254         // beginning of formatBodies().
1255         // When MT safe is implemented, must re-work this.
1256     converted = 0;
1257
1258     error.clear();
1259
1260     BufferMemory hdr_buf(1024);
1261
1262     // We always put the Mime-Version on MIME messages, regardless
1263     // of content.
1264     //
1265     hdr_buf.appendData("Mime-Version: 1.0", 17);
1266     crlf(hdr_buf);
1267
1268     // We need to figure out what the content type will
1269     // be. If we have multiple message body parts, then
1270     // it is multipart/mixed. Otherwise we have to retrieve
1271     // the MIME type for the typing database and also identify
1272     // the charset and encoding.
1273     //
1274     int body_count = msg.getBodyCount(error);
1275     srand((u_int)time(0));
1276
1277     char boundary[256];
1278     sprintf(boundary, "%x_%x-%x_%x-%x_%x", rand(), rand(), rand(),
1279             rand(), rand(), rand());
1280
1281     DtMail::MailRc * mail_rc = _session->mailRc(error);
1282
1283     DtMailBoolean strict_mime = DTM_FALSE;
1284     const char * rc_value;
1285     error.clear();
1286     mail_rc->getValue(error, "strictmime", &rc_value);
1287     if (error.isNotSet()) {
1288         strict_mime = DTM_TRUE;
1289     }
1290     error.clear();
1291
1292     char default_charset[64];
1293     getCharSet(default_charset);
1294
1295     if (body_count <= 1) {
1296         DtMail::BodyPart * bp = msg.getFirstBodyPart(error);
1297         char mime_type[64];
1298         DtMailBoolean is_text;
1299         getMIMEType(bp, mime_type, is_text);
1300
1301         unsigned long bp_len;
1302         char * bp_contents=NULL, * name=NULL;
1303         const void *tmp_ptr;
1304         error.clear();
1305         bp->getContents(error, &tmp_ptr, &bp_len, NULL, &name, NULL, NULL);
1306         // We don't want to change the contents of the msg.
1307         if (bp_len > 0) {
1308                 bp_contents = (char*)malloc((unsigned int)bp_len);
1309                 memcpy(bp_contents, (char*)tmp_ptr, (size_t)bp_len);
1310         }
1311
1312         Encoding enc;
1313         if (strncasecmp(mime_type, "message/", 8) == 0) {
1314             enc = getClearEncoding(
1315                                 (char *)bp_contents, (unsigned int) bp_len,
1316                                 &eightbit);
1317         }
1318         else {
1319             enc = getEncodingType(
1320                                 (char *)bp_contents, (unsigned int) bp_len,
1321                                 strict_mime, &eightbit);
1322         }
1323
1324 // For CHARSET
1325         if (bp_contents && (is_text == DTM_TRUE) && eightbit ) {
1326                 from_cs = NULL;
1327                 from_cs = _session->locToConvName();
1328                 to_cs = NULL;
1329                 to_cs = _session->targetConvName();
1330                 converted = _session->csConvert(
1331                                 (char **)&bp_contents, bp_len, 1,
1332                                 from_cs, to_cs);
1333                 if ( from_cs )
1334                   free( from_cs );
1335                 if ( to_cs )
1336                   free( to_cs );
1337         }
1338 // End of For CHARSET
1339
1340         // Do md5 check summing.
1341         //
1342         unsigned char digest[16];
1343         memset(digest, 0, sizeof(digest));
1344
1345         if (bp_contents && bp_len > 0) {
1346             if (is_text == DTM_TRUE) {
1347                 md5PlainText((char *)bp_contents, bp_len, digest);
1348             }
1349             else {
1350                 MD5_CTX context;
1351                 MD5Init(&context);
1352                 MD5Update(
1353                         &context,
1354                         (unsigned char *)bp_contents,
1355                         (unsigned int) bp_len);
1356                 MD5Final(digest, &context);
1357 #ifdef DEAD_WOOD
1358                 DtMailProcessClientEvents();
1359 #endif /* DEAD_WOOD */
1360             }
1361         }
1362
1363         writeContentHeaders(hdr_buf, mime_type, NULL, enc,
1364                             (char *)digest, DTM_FALSE, eightbit);
1365
1366         if (name)
1367             free(name);
1368
1369         crlf(buf);
1370
1371         switch (enc) {
1372           case MIME_7BIT:
1373           case MIME_8BIT:
1374           default:
1375             // Just copy the bits.
1376             writePlainText(buf, (char *)bp_contents, bp_len);
1377             break;
1378
1379           case MIME_BASE64:
1380                 if (converted && !strict_mime) {
1381                         writePlainText(buf, (char *)bp_contents, bp_len);
1382                         converted = 0;
1383                 } else {
1384                         writeBase64(buf, (char *)bp_contents, bp_len);
1385                 }
1386             break;
1387
1388           case MIME_QPRINT:
1389             if (converted && !strict_mime) {
1390                 writePlainText(buf, (char *)bp_contents, bp_len);
1391                 converted = 0;
1392             } else {
1393                 writeQPrint(buf, (char *)bp_contents, bp_len);
1394             }
1395             break;
1396         }
1397         if (bp_contents)
1398                 free(bp_contents);
1399     }
1400     else {
1401         char *content_type = new char[100];
1402
1403         sprintf(content_type, "Content-Type: multipart/mixed;boundary=%s",
1404                 boundary);
1405
1406         int len = strlen(content_type);
1407         hdr_buf.appendData(content_type, len);
1408         crlf(hdr_buf);
1409         delete [] content_type;
1410
1411         crlf(buf);
1412         
1413         int bdry_len = strlen(boundary);
1414         int cur_body = 0;
1415         DtMailBoolean show_as_attachment = DTM_FALSE;
1416
1417         for (DtMail::BodyPart * bp = msg.getFirstBodyPart(error);
1418              bp && !error.isSet();
1419              bp = msg.getNextBodyPart(error, bp), cur_body += 1) {
1420
1421             // Skip this body part if it is deleted.
1422             if (bp->flagIsSet(error, DtMailBodyPartDeletePending))
1423                 continue;
1424
1425             // Put out the boundary.
1426             //
1427             buf.appendData("--", 2);
1428             buf.appendData(boundary, bdry_len);
1429             crlf(buf);
1430
1431             char mime_type[64];
1432             DtMailBoolean is_text;
1433             getMIMEType(bp, mime_type, is_text);
1434             
1435             unsigned long bp_len;
1436             char* bp_contents=NULL, *name=NULL;
1437             const void *tmp_ptr;
1438                 char *type = NULL;
1439             bp->getContents(error, &tmp_ptr, &bp_len, &type, &name, NULL, NULL);
1440             if (bp_len > 0) {
1441                 bp_contents = (char*)malloc((unsigned int)bp_len);
1442                 memcpy(bp_contents, (char*)tmp_ptr, (size_t)bp_len);
1443             }
1444
1445             Encoding enc;
1446             if (strncasecmp(mime_type, "message/", 8) == 0) {
1447                 enc = getClearEncoding(
1448                                 (char *)bp_contents, (unsigned int) bp_len,
1449                                 &eightbit);
1450             }
1451             else {
1452                 enc = getEncodingType(
1453                                 (char *)bp_contents, (unsigned int) bp_len,
1454                                 strict_mime, &eightbit);
1455             }
1456
1457 // For CHARSET
1458             if (bp_contents && (is_text == DTM_TRUE) && eightbit ) {
1459                 from_cs = NULL;
1460                 from_cs = _session->locToConvName();
1461                 to_cs = NULL;
1462                         to_cs = _session->targetConvName();
1463                         converted = _session->csConvert((char **)&bp_contents, 
1464                         bp_len, 1, from_cs, to_cs);
1465                 if ( from_cs )
1466                   free( from_cs );
1467                 if ( to_cs )
1468                   free( to_cs );
1469             }
1470 // End of For CHARSET
1471
1472             unsigned char digest[16];
1473             memset(digest, 0, sizeof(digest));
1474
1475             if (bp_contents && bp_len > 0) {
1476                 if (is_text == DTM_TRUE) {
1477                     md5PlainText((char *)bp_contents, bp_len, digest);
1478                 }
1479                 else {
1480                     MD5_CTX context;
1481                     MD5Init(&context);
1482                     MD5Update(
1483                         &context,
1484                         (unsigned char *)bp_contents,
1485                         (unsigned int) bp_len);
1486                     MD5Final(digest, &context);
1487 #ifdef DEAD_WOOD
1488                     DtMailProcessClientEvents();
1489 #endif /* DEAD_WOOD */
1490                 }
1491             }
1492
1493             if (cur_body == 0) {
1494                 show_as_attachment = DTM_FALSE;
1495                 if (name) {
1496                     free(name);
1497                     name = NULL;
1498                 }
1499             }
1500             else
1501                 show_as_attachment = DTM_TRUE;
1502
1503             writeContentHeaders(buf, mime_type, name, enc,
1504                                 (char *)digest, show_as_attachment, eightbit);
1505
1506                 owCompat(buf, type, name, bp_contents, bp_len);
1507                 if (type) {
1508                    free(type);   // allocated by getContent above
1509                 }
1510
1511             if (name)
1512                 free(name);
1513
1514             crlf(buf);
1515             
1516             switch (enc) {
1517               case MIME_7BIT:
1518               case MIME_8BIT:
1519               default:
1520                 // Just copy the bits.
1521                 writePlainText(buf, (char *)bp_contents, bp_len);
1522                 break;
1523                 
1524               case MIME_BASE64:
1525                 if (converted && !strict_mime) {
1526                         writePlainText(buf, (char *)bp_contents, bp_len);
1527                         converted = 0;
1528                 } else {
1529                         writeBase64(buf, (char *)bp_contents, bp_len);
1530                 }
1531                 break;
1532                 
1533               case MIME_QPRINT:
1534                 if (converted && !strict_mime) {
1535                         writePlainText(buf, (char *)bp_contents, bp_len);
1536                         converted = 0;
1537                 } else {
1538                         writeQPrint(buf, (char *)bp_contents, bp_len);
1539                 }
1540                 break;
1541             }
1542             if (bp_contents)
1543                 free(bp_contents);
1544         }
1545
1546         // Put out the last boundary.
1547         buf.appendData("--", 2);
1548         buf.appendData(boundary, bdry_len);
1549         buf.appendData("--", 2);
1550         crlf(buf);
1551     }
1552
1553     error.clear();
1554
1555     if (include_content_length) {
1556         hdr_buf.appendData("Content-Length: ", 16);
1557         char tmpbuf[20];
1558         sprintf(tmpbuf, "%d", buf.getSize());
1559         hdr_buf.appendData(tmpbuf, strlen(tmpbuf));
1560         crlf(hdr_buf);
1561     }
1562
1563     *extra_headers = (char*) malloc((size_t) hdr_buf.getSize() + 1);
1564
1565     BufReader * rdr = hdr_buf.getReader();
1566
1567     rdr->getData(*extra_headers, hdr_buf.getSize());
1568     (*extra_headers)[hdr_buf.getSize()] = 0;
1569
1570     delete rdr;
1571     return;
1572 }
1573
1574 static const char * block_headers[] = {
1575     "Mime-Version",
1576     "Content-Type",
1577     "Content-Length",
1578     "Content-MD5",
1579     "X-Sun-Charset",
1580     NULL
1581 };
1582
1583 void
1584 RFCMIME::formatHeaders(DtMailEnv & error,
1585                        DtMail::Message & msg,
1586                        DtMailBoolean include_unix_from,
1587                        const char * extra_headers,
1588                        Buffer & buf)
1589 {
1590     error.clear();
1591
1592     // We can use the parent comment RFC header formatter with our list
1593     // of headers to suppress.
1594     //
1595     writeHeaders(error, msg, include_unix_from, extra_headers, block_headers, buf);
1596 }
1597
1598 // This routine is same as getEncodingType except for certain rules
1599 // not applicable to header fields.
1600 RFCMIME::Encoding
1601 RFCMIME::getHdrEncodingType(const char * body,
1602                          const unsigned int len,
1603                          DtMailBoolean strict_mime,
1604                          const char * charset)
1605 {
1606   if (body == NULL || len == 0) {
1607     return(MIME_7BIT);
1608   }
1609   
1610   const int base64_growth = base64size(len) - len;
1611   int qprint_growth = 0;
1612   DtMailBoolean seven_nonprinting = DTM_FALSE;
1613   DtMailBoolean eight_bit = DTM_FALSE;
1614   DtMailBoolean encode = DTM_FALSE;
1615   
1616   const char * last_nl = body;
1617   const char * cur;
1618   for (cur = body; cur < (body + len); cur++) {
1619     char curChar = *cur;
1620     
1621     if (curChar != (curChar & 0x7f)) {
1622       eight_bit = DTM_TRUE;
1623       encode = DTM_TRUE;
1624       qprint_growth += 2;
1625     }
1626     else if (curChar == '=') {
1627       // These characters don't force encoding, but will be 
1628       // encoded if we end up encoding.
1629       qprint_growth += 2;
1630     }
1631     else if ( ((curChar < ' ') || (curChar == 0x7f)) 
1632             && (curChar != 0x09) && (curChar != 0x0A) ) {
1633       // These characters force encoding
1634       //
1635       seven_nonprinting = DTM_TRUE;
1636       encode = DTM_TRUE;
1637       qprint_growth += 2;
1638     }
1639     
1640     if (curChar == '\n') {
1641 #ifdef DEAD_WOOD
1642       DtMailProcessClientEvents();
1643 #endif /* DEAD_WOOD */
1644      
1645       if ((cur - last_nl) > 76) {
1646         encode = DTM_TRUE;
1647         qprint_growth += 2;
1648       }
1649       
1650       if ((cur != body && (*(cur - 1) == ' ' || *(cur - 1) == '\t'))) {
1651         encode = DTM_TRUE;
1652         qprint_growth += 2;
1653       }
1654       
1655       if ( ((cur + 6) < (body + len) )
1656            && (strncmp((cur + 1), "From ", 5) == 0) ) {
1657         encode = DTM_TRUE;
1658         qprint_growth += 2;
1659       }
1660       
1661       last_nl = cur + 1;
1662     }
1663     
1664   }
1665   
1666   // Deal with buffers that don't end with a new line.
1667   //
1668   if ((cur - last_nl) > 76) {
1669     encode = DTM_TRUE;
1670     qprint_growth += 2;
1671   }
1672   
1673   Encoding enc = MIME_7BIT;
1674   
1675   if (!strict_mime && !eight_bit && !seven_nonprinting) {
1676     // If strict_mime is off we only encode if we have 8 bit data
1677     // of most of the non-printing 7-bit characters
1678     //
1679     enc = MIME_7BIT;
1680   }
1681   else if (encode) {
1682     //
1683     // Strict_mime is TRUE and we have reason to encode.
1684     // Choose base64 if the charset is ISO-2022-JP (as per RFC 1468).
1685     // Otherwise, choose the most compact encoding.
1686     //
1687     if (strncasecmp(charset, "iso-2022-jp", strlen(charset)) == 0)
1688       enc = MIME_BASE64;
1689     else if (qprint_growth > base64_growth)
1690       enc = MIME_BASE64;
1691     else
1692       enc = MIME_QPRINT;
1693   }
1694   
1695   return(enc);
1696 }
1697
1698 static inline int
1699 mbisspace(int c)
1700 {
1701     return((c & 0x7f) == c && isspace(c));
1702 }
1703
1704 void
1705 RFCMIME::rfc1522cpy(Buffer & buf, const char * value)
1706 {
1707         Encoding enc;
1708         DtMailBoolean eight_bit = DTM_FALSE;
1709         DtMailBoolean strict_mime = DTM_FALSE;
1710         DtMailEnv error;
1711         DtMail::MailRc *mail_rc = _session->mailRc(error);
1712         const char *rc_value;
1713         char charset[64];
1714         char *from_cs = NULL, *to_cs = NULL, *convertbuf = NULL;
1715         int convert = 0;
1716         int word_len = 0, mb_ret = 0, cs_len = 0;
1717         unsigned long tmpcount = 0;
1718
1719
1720         getCharSet(charset);
1721         cs_len = strlen(charset);
1722
1723     error.clear();
1724     mail_rc->getValue(error, "strictmime", &rc_value);
1725         if (error.isNotSet()) {
1726           strict_mime = DTM_TRUE;
1727         }
1728
1729     // We are going to encode 8 bit data, one word at a time. This may
1730     // not be the best possible algorithm, but it will get the correct
1731     // information in the header.
1732     //
1733     for (const char * cur = value; *cur; cur++) {
1734
1735         // Skip over spaces
1736         if (mbisspace(*cur)) {
1737             buf.appendData(cur, 1);
1738             continue;
1739         }
1740
1741         // Get a group of characters (encoded-word).
1742         // Ensure encoded-word is not more than 75 bytes - including charset,
1743         // encoding, encoded-text, and delimiters as per RFC 1522.
1744         // Encoded-word = "=?charset?encoding?encoded-text?="
1745         // 7 is "=?" and "?=" and two ? and Q or B for the encoding.
1746         word_len = cs_len + 7;
1747
1748         const char *scan_c;
1749         for (scan_c = cur;
1750              *scan_c && !mbisspace(*scan_c) && word_len <= 75;
1751                  scan_c++, word_len++) {
1752             if (*scan_c != (*scan_c & 0x7f)) {
1753                 // Here, we've come across a non 7bit byte.
1754                 // Use mblen to get entire character.
1755                 // If the number of bytes for the entire character can fit within
1756                 // this group of text, then advance scan_c pointer, increase word_len,
1757                 // and mark this group as eight_bit so encoding is performed.
1758                 mb_ret = mblen(scan_c, MB_LEN_MAX);
1759                 if ( (mb_ret >= 0) && (word_len + mb_ret <= 75) ) {
1760                   scan_c += mb_ret;
1761                   word_len += mb_ret;
1762                   eight_bit = DTM_TRUE;
1763                   // When for loop continues, scan_c and word_len get incremented by 1
1764                   scan_c -= 1;
1765                   word_len -= 1;
1766                 }
1767             mb_ret = 0;
1768                 }
1769         }
1770
1771         if (eight_bit == DTM_FALSE) {
1772             // Simple! Copy the chars to the output.
1773             buf.appendData(cur, scan_c - cur);
1774             cur = scan_c - 1;
1775         }
1776         else {
1777             // We have a word here. It has 8 bit data, so we will put
1778             // it out as RFC1522 chunk.
1779             //
1780             BufferMemory tmp(1024);
1781
1782             buf.appendData("=?", 2);
1783             buf.appendData(charset, strlen(charset));
1784
1785             // Do codeset conversion
1786             convert = 0;
1787             tmpcount = (unsigned long) (scan_c - cur);
1788             convertbuf = (char*)calloc((unsigned int) tmpcount+1, sizeof(char));
1789             memcpy(convertbuf, cur, (size_t)tmpcount);
1790             from_cs = _session->locToConvName();
1791             to_cs = _session->targetConvName();
1792             convert = _session->csConvert(
1793                                         (char **)&convertbuf,
1794                                         tmpcount, 1,
1795                                         from_cs, to_cs);
1796             if ( from_cs ) free( from_cs );
1797             if ( to_cs ) free( to_cs );
1798
1799             if (convert)
1800               enc = getHdrEncodingType(
1801                                         convertbuf,
1802                                         (unsigned int) tmpcount,
1803                                         strict_mime, charset);
1804             else
1805               enc = getHdrEncodingType(cur, scan_c-cur, strict_mime, charset);
1806             
1807             // Do transfer encoding
1808             switch (enc) {
1809             case MIME_BASE64:
1810                 buf.appendData("?b?", 3);
1811                 if (convert) {
1812                     writeBase64(tmp, convertbuf, tmpcount);
1813                 } else {
1814                     writeBase64(tmp, cur, scan_c - cur);
1815                 }
1816                 break;
1817             case MIME_QPRINT:
1818                 buf.appendData("?q?", 3);
1819                 if (convert) {
1820                     writeQPrint(tmp, convertbuf, tmpcount);
1821                 } else {
1822                     writeQPrint(tmp, cur, scan_c - cur);
1823                 }
1824                 break;
1825             }
1826
1827             char * cp_buf = new char[tmp.getSize()];
1828             BufReader * reader = tmp.getReader();
1829
1830             reader->getData(cp_buf, tmp.getSize());
1831             delete reader;
1832
1833             char *tw;
1834             for (tw = &cp_buf[tmp.getSize() - 1];
1835                  tw > cp_buf && isspace((unsigned char)*tw); tw--) {
1836                 continue;
1837             }
1838
1839             tw += 1;
1840             *tw = 0;
1841
1842             buf.appendData(cp_buf, strlen(cp_buf));
1843             delete [] cp_buf;
1844
1845             buf.appendData("?=", 2);
1846
1847             cur = scan_c - 1;
1848
1849             eight_bit = DTM_FALSE;
1850         }
1851     }
1852
1853     crlf(buf);
1854 }
1855
1856 // For OW Mailtool compatibility so that MIME mail with attachment(s) sent
1857 // via dtmail will have OW Mailtool understandable message header identifying 
1858 // attachment type so that OW Mailtool can display the correct attachment icon
1859 // and invoke the correct double-click operation.
1860 // RFCMIME::owCompat()
1861 // Given the attribute type, get the attribute's SUNV3_TYPE, if SUNV3_TYPE
1862 // exists, put out X-Sun-Data-Type header and SUNV3_TYPE value.
1863 // If no type is given or there is no SUNV3_TYPE for the given attribute,
1864 // then re-type content.
1865 void
1866 RFCMIME::owCompat(Buffer & buf,
1867          char *type,
1868          char *name,
1869          char *bp_contents,
1870          unsigned long bp_len)
1871 {
1872     char *v3type = NULL;
1873
1874     if (type) {
1875            v3type = (char *)DtDtsDataTypeToAttributeValue(type,
1876                                                  "SUNV3_TYPE",
1877                                                  NULL);
1878         }
1879         if (!type || !v3type)
1880         {
1881            char *buf_type;
1882            buf_type = DtDtsBufferToDataType(bp_contents, (int) bp_len, name);
1883            if (buf_type)
1884            {
1885                v3type = (char *)DtDtsDataTypeToAttributeValue(
1886                                                         buf_type,
1887                                                         "SUNV3_TYPE",
1888                                                         NULL);
1889                DtDtsFreeDataType(buf_type);
1890            }
1891         }
1892
1893         if (v3type) {
1894            buf.appendData("X-Sun-Data-Type: ", 17);
1895            buf.appendData(v3type, strlen(v3type));
1896            crlf(buf);
1897            DtDtsFreeAttributeValue(v3type);
1898         }
1899 }