2 * CDE - Common Desktop Environment
4 * Copyright (c) 1993-2012, The Open Group. All rights reserved.
6 * These libraries and programs are free software; you can
7 * redistribute them and/or modify them under the terms of the GNU
8 * Lesser General Public License as published by the Free Software
9 * Foundation; either version 2 of the License, or (at your option)
12 * These libraries and programs are distributed in the hope that
13 * they will be useful, but WITHOUT ANY WARRANTY; without even the
14 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU Lesser General Public License for more
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with these librararies and programs; if not, write
20 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21 * Floor, Boston, MA 02110-1301 USA
27 * $TOG: RFCMIME.C /main/13 1998/07/24 16:09:00 mgreess $
29 * RESTRICTED CONFIDENTIAL INFORMATION:
31 * The information in this document is subject to special
32 * restrictions in a confidential disclosure agreement bertween
33 * HP, IBM, Sun, USL, SCO and Univel. Do not distribute this
34 * document outside HP, IBM, Sun, USL, SCO, or Univel wihtout
35 * Sun's specific written approval. This documment and all copies
36 * and derivative works thereof must be returned or destroyed at
39 * Copyright 1993, 1994, 1995 Sun Microsystems, Inc. All rights reserved.
44 #ifndef I_HAVE_NO_IDENT
47 #include <EUSCompat.h>
60 #include <DtMail/DtMailP.hh>
61 #include <DtMail/IO.hh>
62 #include "str_utils.h"
66 static int converted = 0;
68 #if defined(POSIX_THREADS)
69 extern "C" int rand_r(unsigned int);
70 #define brand(a) rand_r(a)
72 #define brand(a) rand()
75 #define DIRECTION_STRING(dir) ((dir==CURRENT_TO_INTERNET) ? \
76 "CURRENT_TO_INTERNET" : \
77 "INTERNET_TO_CURRENT")
79 #define ENCODING_STRING(enc) ((enc==MIME_7BIT) ? "MIME_7BIT" : \
80 ((enc==MIME_8BIT) ? "MIME_8BIT" : \
81 ((enc==MIME_QPRINT) ? "MIME_QPRINT" : \
84 #define NON_MAIL_SAFE(char) (((char >= 0) && (char <= 31) && (char != 9) && \
85 (char != 0x0a)) || (char == 127))
88 base64size(const unsigned long len)
90 unsigned long b_len = len + (len / 3);
91 b_len += (b_len / 72 * 2) + 4;
93 return((unsigned int) b_len);
97 RFCMIME::getMIMEType(DtMail::BodyPart * bp, char * mime_type, DtMailBoolean & is_text)
99 // Get the Dt type name from the body part.
104 bp->getContents(error,
112 // It is possible there is *no* contents associated with this
113 // body part - in that case, fake text/plain
116 strcpy(mime_type, "text/plain");
119 assert(type != NULL);
121 // Look it up in the data typing system. Hopefully we will
122 // get a db based mime name.
124 char * db_type = DtDtsDataTypeToAttributeValue(type,
128 // See if we call this text. If so, then it will be text/plain,
129 // if not then application/octet-stream
131 char * text_type = DtDtsDataTypeToAttributeValue(type,
136 strcpy(mime_type, db_type);
139 if (text_type && strcasecmp(text_type, "true") == 0) {
140 strcpy(mime_type, "text/plain");
143 strcpy(mime_type, "application/octet-stream");
147 is_text = text_type ? DTM_TRUE : DTM_FALSE;
160 RFCMIME::RFCMIME(DtMail::Session * session)
165 RFCMIME::~RFCMIME(void)
171 RFCMIME::getEncodingType(const char * body,
172 const unsigned int len,
173 DtMailBoolean strict_mime,
176 // Our goal here is to produce the most readable, safe encoding.
177 // We have a couple of parameters that will guide our
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.
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.
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:
191 // 1) If the text is 7 bit clean, and all lines are <76 chars,
192 // then no encoding will be applied.
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.
198 // 3) If 1 & 2 are not true, then base64 will be applied.
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
205 if (body == NULL || len == 0) {
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;
215 const char * last_nl = body;
218 if (strncmp(body, "From ", 5) == 0) {
222 for (cur = body; cur < (body + len); cur++) {
225 if (curChar != (curChar & 0x7f)) {
226 eight_bit = DTM_TRUE;
231 else if (curChar == '=') {
232 // These characters don't force encoding, but will be
233 // encoded if we end up encoding.
236 else if ( ((curChar < ' ') || (curChar == 0x7f))
237 && (curChar != 0x09) && (curChar != 0x0A) ) {
238 // These characters force encoding
240 seven_nonprinting = DTM_TRUE;
245 if (curChar == '\n') {
247 DtMailProcessClientEvents();
248 #endif /* DEAD_WOOD */
250 if ((cur - last_nl) > 76) {
255 if ((cur != body && (*(cur - 1) == ' ' || *(cur - 1) == '\t'))) {
260 if ( ((cur + 6) < (body + len) )
261 && (strncmp((cur + 1), "From ", 5) == 0) ) {
271 // Deal with buffers that dont end with a new line.
273 if ((cur - last_nl) > 76) {
278 Encoding enc = MIME_7BIT;
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
287 // strict_mime is TRUE and we have reason to encode.
288 if (qprint_growth > base64_growth) {
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.
304 RFCMIME::getEncodingType(const char * body,
305 const unsigned int len,
306 DtMailBoolean strict_mime)
308 // Our goal here is to produce the most readable, safe encoding.
309 // We have a couple of parameters that will guide our
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.
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.
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:
323 // 1) If the text is 7 bit clean, and all lines are <76 chars,
324 // then no encoding will be applied.
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.
330 // 3) If 1 & 2 are not true, then base64 will be applied.
333 if (body == NULL || len == 0) {
337 const int base64_growth = base64size(len) - len;
338 int qprint_growth = 0;
339 DtMailBoolean eight_bit = DTM_FALSE;
340 DtMailBoolean base64 = DTM_FALSE;
342 const char * last_nl = body;
344 if (strncmp(body, "From ", 5) == 0) {
348 for (const char * cur = body; cur < (body + len); cur++) {
349 if (*cur != (*cur & 0x7f) || *cur == '=' || *cur == 0) {
350 eight_bit = DTM_TRUE;
356 DtMailProcessClientEvents();
357 #endif /* DEAD_WOOD */
359 if (strict_mime && ((cur - last_nl) > 76)) {
364 (cur != body && (*(cur - 1) == ' ' || *(cur - 1) == '\t'))) {
368 if ((cur + 6) < (body + len) && strncmp((cur + 1), "From ", 5) == 0) {
375 if (qprint_growth > base64_growth) {
381 // Deal with buffers that dont end with a new line.
383 if (strict_mime && ((cur - last_nl) > 76)) {
387 Encoding enc = MIME_7BIT;
389 if (base64 == DTM_TRUE) {
392 else if (eight_bit == DTM_TRUE || qprint_growth > 0) {
401 RFCMIME::getClearEncoding(const char * bp, const unsigned int bp_len, int *real8bit)
403 // Return the appropriate "non-encoding". If we have 8 bit data,
404 // then return 8bit. If not, then this is a 7bit encoding.
406 for (const char * cur = bp; cur < (bp + bp_len); cur++) {
407 if (*cur != (*cur & 0x7f)) {
417 RFCMIME::writeContentHeaders(Buffer & hdr_buf,
418 const char * type, const char * name,
421 DtMailBoolean show_as_attachment,
424 hdr_buf.appendData("Content-Type: ", 14);
425 hdr_buf.appendData(type, strlen(type));
427 if (strcasecmp(type, "text/plain") == 0) {
428 char default_charset[64];
429 if (!is2022ASCII && !converted) {
430 strcpy(default_charset, "us-ascii");
432 getCharSet(default_charset);
435 int len = strlen(default_charset);
437 hdr_buf.appendData("; charset=", 10);
439 hdr_buf.appendData(default_charset, len);
444 hdr_buf.appendData("Content-Transfer-Encoding: ", 27);
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".
451 DtMail::MailRc * mail_rc = _session->mailRc(error);
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;
460 // Read code below to see why and how strict_mime is used.
461 // End of For CHARSET
465 hdr_buf.appendData("7bit", 4);
470 default: // Assume the worst.
471 hdr_buf.appendData("8bit", 4);
476 if (converted && !strict_mime) {
477 hdr_buf.appendData("binary", 6);
479 hdr_buf.appendData("quoted-printable", 16);
485 if (converted && !strict_mime) {
486 hdr_buf.appendData("binary", 6);
488 hdr_buf.appendData("base64", 6);
494 hdr_buf.appendData("Content-MD5: ", 13);
495 writeBase64(hdr_buf, digest, 16);
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.
501 int nlen = strlen(name);
502 if (name[nlen - 1] == '\n') {
506 if (name[nlen - 1] == '\r') {
510 char * tempName = strdup(name);
511 assert(tempName != NULL);
514 // Now make sure that the name is "unix friendly"; convert
515 // any meta-characters into something innocuous
517 for (int i = 0; i < nlen; i++)
518 if (tempName[i] < ' ')
519 tempName[i] = '-'; // all control characters are unfriendly
521 switch(tempName[i]) {
523 if (i > 0) // ~ at beginning of a line is special
533 tempName[i] = '-'; // "-" in unix and dos in innocuous
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.
546 hdr_buf.appendData("Content-Disposition: ", 21);
547 if (show_as_attachment)
548 hdr_buf.appendData("attachment; ", 12);
550 hdr_buf.appendData("inline; ", 8);
551 hdr_buf.appendData("filename=", 9);
552 rfc1522cpy(hdr_buf, tempName);
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.
559 hdr_buf.appendData("Content-Description: ", 21);
560 rfc1522cpy(hdr_buf, tempName);
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
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
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.
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.
584 hdr_buf.appendData("X-Content-Name: ", 16);
585 hdr_buf.appendData(tempName, nlen);
589 free(tempName); // done with tempName
594 // Base64 Alphabet (65-character subset of US-ASCII as per RFC1521)
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', '+', '/'
606 base64inv(const char cur)
619 if ((cur - '0') < 10) {
620 return(cur - '0' + 52);
623 if ((cur - 'A') < 26) {
627 if ((cur - 'a') < 26) {
628 return(cur - 'a' + 26);
635 // RFCMIME::readBase64 -- decode base 64 text into clear text
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
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.
653 RFCMIME::readBase64(char * buf, int & off, const char * bp, const unsigned long len)
655 // Make local copy of base 64 text length
657 unsigned long main_len = len;
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
663 while (isspace(bp[main_len - 1])) {
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
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.
676 main_len -= 4; // backup over last 4-byte encoding containing padding
679 // Main octet decoding loop. We are assured that main_len is mod 4, so
680 // we can zip through decoding full octets
682 for (const char * cur = bp; cur < (bp + main_len - 3); cur += 4) {
683 while (*cur == ' ') {
693 unsigned char b1 = base64inv(*cur);
695 if (*(cur + 1) == '\r') {
698 if (*(cur + 1) == '\n') {
701 unsigned char b2 = base64inv(*(cur + 1));
703 if (*(cur + 2) == '\r') {
706 if (*(cur + 2) == '\n') {
709 unsigned char b3 = base64inv(*(cur + 2));
711 if (*(cur + 3) == '\r') {
714 if (*(cur + 3) == '\n') {
717 unsigned char b4 = base64inv(*(cur + 3));
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));
724 // Throw away any white space left in base 64 text, just in case.
726 while (main_len < len && isspace(bp[main_len])) {
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
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]);
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);
753 RFCMIME::writeBase64(Buffer & buf, const char * bp, const unsigned long len)
755 if (bp == NULL || len == 0) {
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.
764 unsigned long main_len = len - (len % 3);
765 const unsigned char * ubp = (const unsigned char *)bp;
769 unsigned int enc_char;
774 for (block = 0; block < main_len; block += 3) {
775 enc_char = (ubp[block] >> 2) & 0x3f;
776 line[lf++] = base64_chars[enc_char];
778 enc_char = ((ubp[block] & 0x3) << 4) | ((ubp[block+1] >> 4) & 0xf);
779 line[lf++] = base64_chars[enc_char];
781 enc_char = ((ubp[block+1] & 0xf) << 2) | ((ubp[block + 2] >> 6) & 0x3);
782 line[lf++] = base64_chars[enc_char];
784 enc_char = ubp[block + 2] & 0x3f;
785 line[lf++] = base64_chars[enc_char];
788 buf.appendData(line, lf);
790 DtMailProcessClientEvents();
791 #endif /* DEAD_WOOD */
798 buf.appendData(line, lf);
801 if (((lf + 4) % 72) == 0) {
807 enc_char = (ubp[block] >> 2) & 0x3f ;
808 buf.appendData(&base64_chars[enc_char], 1);
810 enc_char = ((ubp[block] & 0x3) << 4);
811 buf.appendData(&base64_chars[enc_char], 1);
813 buf.appendData("==", 2);
817 enc_char = (ubp[block] >> 2) & 0x3f;
818 buf.appendData(&base64_chars[enc_char], 1);
820 enc_char = ((ubp[block] & 0x3) << 4) | ((ubp[block+1] >> 4) & 0xf);
821 buf.appendData(&base64_chars[enc_char], 1);
823 enc_char = ((ubp[block + 1] & 0xf) << 2);
824 buf.appendData(&base64_chars[enc_char], 1);
826 buf.appendData("=", 1);
832 // Function: RFCMIME::readTextEnriched - convert enriched to plain text
834 // This function converts a buffer containing enriched text into a buffer
835 // containing the plain text translation of the enriched text
837 // This algorithm was adapted from the example simple enriched-to-plain
838 // text translator contained in RFC 1563, Appendix A.
841 RFCMIME::readTextEnriched(char * buf, int & off, const char * bp, const unsigned long bp_len)
843 char c, i, paramct=0, newlinect=0, nofill=0;
846 const char *ebp = bp+bp_len;
847 for (const char * cur = bp; cur < ebp; cur++) {
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;
866 if (strcmp(token, "param") == 0)
868 else if (strcmp(token, "nofill") == 0)
870 else if (strcmp(token, "/param") == 0)
872 else if (strcmp(token, "/nofill") == 0)
877 ; /* ignore params */
878 else if ((c == '\n') && (nofill <= 0)) {
897 const char *bp, const unsigned long bp_len)
899 for (const char * cur = bp; cur < (bp + bp_len); cur++) {
901 if (*(cur + 1) == '\n') {
905 else if (*(cur + 1) == '\r' && *(cur + 2) == '\n') {
910 if (isxdigit(*(cur + 1)) && isxdigit(*(cur + 2))) {
916 buf[off++] = (char) strtol(hex, NULL, 16);
930 RFCMIME::writeQPrint(Buffer & buf, const char * bp, const unsigned long bp_len)
932 if (bp == NULL || bp_len == 0) {
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.
944 char tmp[20]; // temp for constructing "octets"
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
952 if (strncmp(bp, "From ", 5) == 0) {
953 memcpy(&line_buf[off], "=46", 3);
961 // This loop will apply the encodings, following the rules identified
962 // in RFC1521 (though not necessarily in the order presented.
965 for (cur = start; cur < (bp + bp_len); cur++) {
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.
972 line_buf[off++] = '=';
973 buf.appendData(line_buf, off);
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 "="
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);
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.
997 if ((*cur >= 33 && *cur <= 60) ||
998 (*cur >= 62 && *cur <= 126)) {
999 line_buf[off++] = *cur;
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.
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
1014 if (*cur == ' ' || *cur == '\t') {
1015 // Find the end of this clump of white space.
1018 for (nw = cur; nw < (bp + bp_len) && *nw && *nw != '\n'; nw++) {
1019 if (!isspace((unsigned char)*nw)) {
1024 // Find the end of the next non-white region.
1028 white < (bp + bp_len) && *white && !isspace((unsigned char)*white);
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.
1040 for (const char *cp_w = cur; cp_w < nw; cp_w++) {
1041 line_buf[off++] = *cp_w;
1043 line_buf[off++] = '=';
1044 buf.appendData(line_buf, off);
1046 DtMailProcessClientEvents();
1047 #endif /* DEAD_WOOD */
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.
1059 line_buf[off++] = '=';
1060 buf.appendData(line_buf, off);
1062 DtMailProcessClientEvents();
1063 #endif /* DEAD_WOOD */
1069 // If we created a "From " at the front we need to wrap
1070 // it to protect from parsers.
1072 if ((nw + 5) < (bp + bp_len) && strncmp(nw, "From ", 5) == 0) {
1073 memcpy(&line_buf[off], "=46", 3);
1082 line_buf[off++] = *cur;
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
1101 char prev = *(cur - 1);
1102 if ((prev == ' ' || prev == '\t') && prev != '\n') {
1103 off = off ? off - 1 : off;
1106 sprintf(tmpbuf, "=%02X", *(cur - 1));
1107 memcpy(&line_buf[off], tmpbuf, 3);
1111 buf.appendData(line_buf, off);
1113 DtMailProcessClientEvents();
1114 #endif /* DEAD_WOOD */
1118 if (*(cur - 1) == '\r') {
1119 buf.appendData(cur, 1);
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.
1129 if ((cur + 5) < (bp + bp_len) && strncmp((cur + 1), "From ", 5) == 0) {
1130 memcpy(&line_buf[off], "=46", 3);
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)
1141 sprintf(tmp, "=%02X", (int)(unsigned char)*cur);
1142 memcpy(&line_buf[off], tmp, 3);
1145 } // end of big "for" loop
1148 buf.appendData(line_buf, off);
1151 if (*(cur - 1) != '\n') {
1157 RFCMIME::writePlainText(Buffer & buf, const char * bp, const unsigned long len)
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.
1162 if (bp == NULL || len == 0) {
1167 const char * line_start = bp;
1169 for (cur = bp; cur < (bp + len); cur++) {
1171 const char * real_end = cur;
1173 if (cur != bp && *(cur - 1) == '\r') {
1177 buf.appendData(line_start, real_end - line_start);
1179 DtMailProcessClientEvents();
1180 #endif /* DEAD_WOOD */
1181 line_start = cur + 1;
1186 if (line_start < cur) {
1187 buf.appendData(line_start, cur - line_start);
1190 // The body should end with a CRLF.
1192 if (*(cur - 1) != '\n') {
1198 RFCMIME::md5PlainText(const char * bp, const unsigned long len, unsigned char * digest)
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
1209 unsigned char * local_crlf = (unsigned char *)"\r\n";
1211 const char * last = bp;
1213 for (cur = bp; cur < (bp + len); cur++) {
1216 DtMailProcessClientEvents();
1217 #endif /* DEAD_WOOD */
1218 if (cur == bp || *(cur - 1) == '\r') {
1219 MD5Update(&context, (unsigned char *)last,
1223 MD5Update(&context, (unsigned char *)last,
1225 MD5Update(&context, local_crlf, 2);
1231 if (bp[len - 1] != '\n') {
1232 // Need to sum the trailing fraction with a CRLF.
1233 MD5Update(&context, (unsigned char *)last,
1235 MD5Update(&context, local_crlf, 2);
1238 MD5Final(digest, &context);
1242 RFCMIME::formatBodies(DtMailEnv & error,
1243 DtMail::Message & msg,
1244 DtMailBoolean include_content_length,
1245 char ** extra_headers,
1249 char *from_cs = NULL, *to_cs = NULL;
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.
1260 BufferMemory hdr_buf(1024);
1262 // We always put the Mime-Version on MIME messages, regardless
1265 hdr_buf.appendData("Mime-Version: 1.0", 17);
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.
1274 int body_count = msg.getBodyCount(error);
1275 srand((u_int)time(0));
1278 sprintf(boundary, "%x_%x-%x_%x-%x_%x", rand(), rand(), rand(),
1279 rand(), rand(), rand());
1281 DtMail::MailRc * mail_rc = _session->mailRc(error);
1283 DtMailBoolean strict_mime = DTM_FALSE;
1284 const char * rc_value;
1286 mail_rc->getValue(error, "strictmime", &rc_value);
1287 if (error.isNotSet()) {
1288 strict_mime = DTM_TRUE;
1292 char default_charset[64];
1293 getCharSet(default_charset);
1295 if (body_count <= 1) {
1296 DtMail::BodyPart * bp = msg.getFirstBodyPart(error);
1298 DtMailBoolean is_text;
1299 getMIMEType(bp, mime_type, is_text);
1301 unsigned long bp_len;
1302 char * bp_contents=NULL, * name=NULL;
1303 const void *tmp_ptr;
1305 bp->getContents(error, &tmp_ptr, &bp_len, NULL, &name, NULL, NULL);
1306 // We dont want to change the contents of the msg.
1308 bp_contents = (char*)malloc((unsigned int)bp_len);
1309 memcpy(bp_contents, (char*)tmp_ptr, (size_t)bp_len);
1313 if (strncasecmp(mime_type, "message/", 8) == 0) {
1314 enc = getClearEncoding(
1315 (char *)bp_contents, (unsigned int) bp_len,
1319 enc = getEncodingType(
1320 (char *)bp_contents, (unsigned int) bp_len,
1321 strict_mime, &eightbit);
1325 if (bp_contents && (is_text == DTM_TRUE) && eightbit ) {
1327 from_cs = _session->locToConvName();
1329 to_cs = _session->targetConvName();
1330 converted = _session->csConvert(
1331 (char **)&bp_contents, bp_len, 1,
1338 // End of For CHARSET
1340 // Do md5 check summing.
1342 unsigned char digest[16];
1343 memset(digest, 0, sizeof(digest));
1345 if (bp_contents && bp_len > 0) {
1346 if (is_text == DTM_TRUE) {
1347 md5PlainText((char *)bp_contents, bp_len, digest);
1354 (unsigned char *)bp_contents,
1355 (unsigned int) bp_len);
1356 MD5Final(digest, &context);
1358 DtMailProcessClientEvents();
1359 #endif /* DEAD_WOOD */
1363 writeContentHeaders(hdr_buf, mime_type, NULL, enc,
1364 (char *)digest, DTM_FALSE, eightbit);
1375 // Just copy the bits.
1376 writePlainText(buf, (char *)bp_contents, bp_len);
1380 if (converted && !strict_mime) {
1381 writePlainText(buf, (char *)bp_contents, bp_len);
1384 writeBase64(buf, (char *)bp_contents, bp_len);
1389 if (converted && !strict_mime) {
1390 writePlainText(buf, (char *)bp_contents, bp_len);
1393 writeQPrint(buf, (char *)bp_contents, bp_len);
1401 char *content_type = new char[100];
1403 sprintf(content_type, "Content-Type: multipart/mixed;boundary=%s",
1406 int len = strlen(content_type);
1407 hdr_buf.appendData(content_type, len);
1409 delete [] content_type;
1413 int bdry_len = strlen(boundary);
1415 DtMailBoolean show_as_attachment = DTM_FALSE;
1417 for (DtMail::BodyPart * bp = msg.getFirstBodyPart(error);
1418 bp && !error.isSet();
1419 bp = msg.getNextBodyPart(error, bp), cur_body += 1) {
1421 // Skip this body part if it is deleted.
1422 if (bp->flagIsSet(error, DtMailBodyPartDeletePending))
1425 // Put out the boundary.
1427 buf.appendData("--", 2);
1428 buf.appendData(boundary, bdry_len);
1432 DtMailBoolean is_text;
1433 getMIMEType(bp, mime_type, is_text);
1435 unsigned long bp_len;
1436 char* bp_contents=NULL, *name=NULL;
1437 const void *tmp_ptr;
1439 bp->getContents(error, &tmp_ptr, &bp_len, &type, &name, NULL, NULL);
1441 bp_contents = (char*)malloc((unsigned int)bp_len);
1442 memcpy(bp_contents, (char*)tmp_ptr, (size_t)bp_len);
1446 if (strncasecmp(mime_type, "message/", 8) == 0) {
1447 enc = getClearEncoding(
1448 (char *)bp_contents, (unsigned int) bp_len,
1452 enc = getEncodingType(
1453 (char *)bp_contents, (unsigned int) bp_len,
1454 strict_mime, &eightbit);
1458 if (bp_contents && (is_text == DTM_TRUE) && eightbit ) {
1460 from_cs = _session->locToConvName();
1462 to_cs = _session->targetConvName();
1463 converted = _session->csConvert((char **)&bp_contents,
1464 bp_len, 1, from_cs, to_cs);
1470 // End of For CHARSET
1472 unsigned char digest[16];
1473 memset(digest, 0, sizeof(digest));
1475 if (bp_contents && bp_len > 0) {
1476 if (is_text == DTM_TRUE) {
1477 md5PlainText((char *)bp_contents, bp_len, digest);
1484 (unsigned char *)bp_contents,
1485 (unsigned int) bp_len);
1486 MD5Final(digest, &context);
1488 DtMailProcessClientEvents();
1489 #endif /* DEAD_WOOD */
1493 if (cur_body == 0) {
1494 show_as_attachment = DTM_FALSE;
1501 show_as_attachment = DTM_TRUE;
1503 writeContentHeaders(buf, mime_type, name, enc,
1504 (char *)digest, show_as_attachment, eightbit);
1506 owCompat(buf, type, name, bp_contents, bp_len);
1508 free(type); // allocated by getContent above
1520 // Just copy the bits.
1521 writePlainText(buf, (char *)bp_contents, bp_len);
1525 if (converted && !strict_mime) {
1526 writePlainText(buf, (char *)bp_contents, bp_len);
1529 writeBase64(buf, (char *)bp_contents, bp_len);
1534 if (converted && !strict_mime) {
1535 writePlainText(buf, (char *)bp_contents, bp_len);
1538 writeQPrint(buf, (char *)bp_contents, bp_len);
1546 // Put out the last boundary.
1547 buf.appendData("--", 2);
1548 buf.appendData(boundary, bdry_len);
1549 buf.appendData("--", 2);
1555 if (include_content_length) {
1556 hdr_buf.appendData("Content-Length: ", 16);
1558 sprintf(tmpbuf, "%d", buf.getSize());
1559 hdr_buf.appendData(tmpbuf, strlen(tmpbuf));
1563 *extra_headers = (char*) malloc((size_t) hdr_buf.getSize() + 1);
1565 BufReader * rdr = hdr_buf.getReader();
1567 rdr->getData(*extra_headers, hdr_buf.getSize());
1568 (*extra_headers)[hdr_buf.getSize()] = 0;
1574 static const char * block_headers[] = {
1584 RFCMIME::formatHeaders(DtMailEnv & error,
1585 DtMail::Message & msg,
1586 DtMailBoolean include_unix_from,
1587 const char * extra_headers,
1592 // We can use the parent comment RFC header formatter with our list
1593 // of headers to suppress.
1595 writeHeaders(error, msg, include_unix_from, extra_headers, block_headers, buf);
1598 // This routine is same as getEncodingType except for certain rules
1599 // not applicable to header fields.
1601 RFCMIME::getHdrEncodingType(const char * body,
1602 const unsigned int len,
1603 DtMailBoolean strict_mime,
1604 const char * charset)
1606 if (body == NULL || len == 0) {
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;
1616 const char * last_nl = body;
1618 for (cur = body; cur < (body + len); cur++) {
1619 char curChar = *cur;
1621 if (curChar != (curChar & 0x7f)) {
1622 eight_bit = DTM_TRUE;
1626 else if (curChar == '=') {
1627 // These characters don't force encoding, but will be
1628 // encoded if we end up encoding.
1631 else if ( ((curChar < ' ') || (curChar == 0x7f))
1632 && (curChar != 0x09) && (curChar != 0x0A) ) {
1633 // These characters force encoding
1635 seven_nonprinting = DTM_TRUE;
1640 if (curChar == '\n') {
1642 DtMailProcessClientEvents();
1643 #endif /* DEAD_WOOD */
1645 if ((cur - last_nl) > 76) {
1650 if ((cur != body && (*(cur - 1) == ' ' || *(cur - 1) == '\t'))) {
1655 if ( ((cur + 6) < (body + len) )
1656 && (strncmp((cur + 1), "From ", 5) == 0) ) {
1666 // Deal with buffers that dont end with a new line.
1668 if ((cur - last_nl) > 76) {
1673 Encoding enc = MIME_7BIT;
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
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.
1687 if (strncasecmp(charset, "iso-2022-jp", strlen(charset)) == 0)
1689 else if (qprint_growth > base64_growth)
1701 return((c & 0x7f) == c && isspace(c));
1705 RFCMIME::rfc1522cpy(Buffer & buf, const char * value)
1708 DtMailBoolean eight_bit = DTM_FALSE;
1709 DtMailBoolean strict_mime = DTM_FALSE;
1711 DtMail::MailRc *mail_rc = _session->mailRc(error);
1712 const char *rc_value;
1714 char *from_cs = NULL, *to_cs = NULL, *convertbuf = NULL;
1716 int word_len = 0, mb_ret = 0, cs_len = 0;
1717 unsigned long tmpcount = 0;
1720 getCharSet(charset);
1721 cs_len = strlen(charset);
1724 mail_rc->getValue(error, "strictmime", &rc_value);
1725 if (error.isNotSet()) {
1726 strict_mime = DTM_TRUE;
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.
1733 for (const char * cur = value; *cur; cur++) {
1736 if (mbisspace(*cur)) {
1737 buf.appendData(cur, 1);
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;
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) ) {
1762 eight_bit = DTM_TRUE;
1763 // When for loop continues, scan_c and word_len get incremented by 1
1771 if (eight_bit == DTM_FALSE) {
1772 // Simple! Copy the chars to the output.
1773 buf.appendData(cur, scan_c - cur);
1777 // We have a word here. It has 8 bit data, so we will put
1778 // it out as RFC1522 chunk.
1780 BufferMemory tmp(1024);
1782 buf.appendData("=?", 2);
1783 buf.appendData(charset, strlen(charset));
1785 // Do codeset conversion
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,
1796 if ( from_cs ) free( from_cs );
1797 if ( to_cs ) free( to_cs );
1800 enc = getHdrEncodingType(
1802 (unsigned int) tmpcount,
1803 strict_mime, charset);
1805 enc = getHdrEncodingType(cur, scan_c-cur, strict_mime, charset);
1807 // Do transfer encoding
1810 buf.appendData("?b?", 3);
1812 writeBase64(tmp, convertbuf, tmpcount);
1814 writeBase64(tmp, cur, scan_c - cur);
1818 buf.appendData("?q?", 3);
1820 writeQPrint(tmp, convertbuf, tmpcount);
1822 writeQPrint(tmp, cur, scan_c - cur);
1827 char * cp_buf = new char[tmp.getSize()];
1828 BufReader * reader = tmp.getReader();
1830 reader->getData(cp_buf, tmp.getSize());
1834 for (tw = &cp_buf[tmp.getSize() - 1];
1835 tw > cp_buf && isspace((unsigned char)*tw); tw--) {
1842 buf.appendData(cp_buf, strlen(cp_buf));
1845 buf.appendData("?=", 2);
1849 eight_bit = DTM_FALSE;
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.
1866 RFCMIME::owCompat(Buffer & buf,
1870 unsigned long bp_len)
1872 char *v3type = NULL;
1875 v3type = (char *)DtDtsDataTypeToAttributeValue(type,
1879 if (!type || !v3type)
1882 buf_type = DtDtsBufferToDataType(bp_contents, (int) bp_len, name);
1885 v3type = (char *)DtDtsDataTypeToAttributeValue(
1889 DtDtsFreeDataType(buf_type);
1894 buf.appendData("X-Sun-Data-Type: ", 17);
1895 buf.appendData(v3type, strlen(v3type));
1897 DtDtsFreeAttributeValue(v3type);