dthelp: Change to ANSI function definitions
[oweals/cde.git] / cde / programs / dtmail / libDtMail / RFC / RFCMailValues.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: RFCMailValues.C /main/10 1998/09/03 07:01:06 mgreess $
28  *
29  *      RESTRICTED CONFIDENTIAL INFORMATION:
30  *      
31  *      The information in this document is subject to special
32  *      restrictions in a confidential disclosure agreement bertween
33  *      HP, IBM, Sun, USL, SCO and Univel.  Do not distribute this
34  *      document outside HP, IBM, Sun, USL, SCO, or Univel wihtout
35  *      Sun's specific written approval.  This documment and all copies
36  *      and derivative works thereof must be returned or destroyed at
37  *      Sun's request.
38  *
39  *      Copyright 1993 Sun Microsystems, Inc.  All rights reserved.
40  *
41  *+ENOTICE
42  */
43
44 #ifndef I_HAVE_NO_IDENT
45 #endif
46
47 #include <EUSCompat.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <strings.h>
51 #include <ctype.h>
52 #include <stdint.h>
53
54 #include <DtMail/IO.hh>
55 #include "RFCImpl.hh"
56 #include "RFCMIME.hh"
57 #include "str_utils.h"
58
59 // String values. These assume an RFC format for now. They will
60 // apply RFC1522 coding rules to the strings for dealing with
61 // non-ASCII text in RFC headers.
62 //
63
64
65 RFCValue::RFCValue(const char * str, int size) : DtMailValue(NULL)
66 {
67     _value = (char *)malloc(size + 1);
68     memcpy(_value, str, size);
69     _value[size] = 0;
70
71     _decoded = NULL;
72     _session = NULL;
73 }
74
75 RFCValue::RFCValue(const char * str, int size, DtMail::Session *s) : DtMailValue(NULL)
76 {
77     _value = (char *)malloc(size + 1);
78     memcpy(_value, str, size);
79     _value[size] = 0;
80
81     _decoded = NULL;
82         _session = s;
83 }
84
85 RFCValue::~RFCValue(void)
86 {
87     if (_decoded) {
88         free(_decoded);
89     }
90 }
91
92 static const char *
93 decode1522(const char * enc_start, const char * max_end, char **output, DtMail::Session *s)
94 {
95     // Find the end of the encoded region.
96     //
97     int qs = 0;
98     const char *enc_end;
99     for (enc_end = enc_start;
100          *enc_end && enc_end < max_end;
101          enc_end++) {
102
103         if (*enc_end == '?') {
104             qs += 1;
105             if (qs > 3 && *(enc_end + 1) == '=') {
106                 break;
107             }
108         }
109     }
110
111     if (*enc_end != '?') {
112         return(enc_start);
113     }
114
115     enc_end += 1;
116
117     // Pull off the char set name.
118     //
119     const char *cs_end;
120     for (cs_end = enc_start + 2; *cs_end != '?'; cs_end++) {
121         continue;
122     }
123
124     int cs_name_length = cs_end - enc_start - 2;
125     char *cs_name = (char*) malloc(cs_name_length + 1);
126
127     strncpy(cs_name, enc_start + 2, cs_name_length);
128     cs_name[cs_name_length] = 0;
129
130     // Set the encoding method and start of buffer.
131     //
132     char encoding = *(cs_end + 1);
133     const char * buf_start = cs_end + 3;
134
135     switch (toupper(encoding)) {
136       case 'Q':
137       {
138           int len = 0;
139           RFCMIME::readQPrint(*output, len, buf_start, enc_end - buf_start - 1);
140           (*output)[len] = 0;
141           break;
142       }
143         
144       case 'B':
145       {
146           int len = 0;
147           RFCMIME::readBase64(*output, len, buf_start, enc_end - buf_start - 1);
148           (*output)[len] = 0;
149           break;
150       }
151
152       default:
153           // Invalid encoding.  Assume a false match.
154           free(cs_name);
155           return (enc_start);
156     }
157
158     // Do codeset conversion if charset is present
159     char *from_cs = s->csToConvName(cs_name);
160     char *to_cs = s->locToConvName();
161     if ( from_cs && to_cs ) {
162         if ( strcasecmp(from_cs, to_cs) != 0 ) {
163             unsigned long tmplen = (unsigned long) strlen(*output);
164             (void) s->csConvert(&(*output), tmplen, 1, from_cs, to_cs);
165         }
166     }
167
168     if (NULL != from_cs)
169         free( from_cs );
170     if (NULL != to_cs)
171         free ( to_cs );
172     free(cs_name);
173
174     return(enc_end);
175 }
176
177
178 RFCValue::operator const char *(void)
179 {
180     if (_decoded) {
181         return(_decoded);
182     }
183
184     decodeValue();
185
186     return(_decoded);
187 }
188
189 const char *
190 RFCValue::operator= (const char * str)
191 {
192     if (_decoded) {
193         free(_decoded);
194         _decoded = NULL;
195     }
196
197     if (_value) {
198         free(_value);
199     }
200
201     _value = strdup(str);
202
203     return(_value);
204 }
205
206 static const char * DaysOfTheWeek[] = {
207 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
208 };
209
210 static const char * MonthsOfTheYear[] = {
211 "Jan", "Feb", "Mar",
212 "Apr", "May", "Jun",
213 "Jul", "Aug", "Sep",
214 "Oct", "Nov", "Dec"
215 };
216
217 static int
218 matchDay(const char * start, const char * end)
219 {
220     int len = end - start + 1;
221
222     for (int i = 0; i < 7; i++) {
223         if (strncmp(DaysOfTheWeek[i], start, len) == 0) {
224             return(i);
225         }
226     }
227
228     return(-1);
229 }
230
231 static int
232 matchMonth(const char * start, const char * end)
233 {
234     int len = end - start + 1;
235
236     for (int i = 0; i < 12; i++) {
237         if (strncmp(MonthsOfTheYear[i], start, len) == 0) {
238             return(i);
239         }
240     }
241
242     return(-1);
243 }
244
245 static void
246 parseTime(const char * start, const char * end, tm & val)
247 {
248     int size = end - start + 1;
249
250     // Time will be in the form hh:mm:ss where seconds are optional.
251
252     char num_buf[10];
253     strncpy(num_buf, start, 2);
254     num_buf[2] = 0;
255
256     val.tm_hour = (int) strtol(num_buf, NULL, 10);
257
258     strncpy(num_buf, &start[3], 2);
259     num_buf[2] = 0;
260
261     val.tm_min = (int) strtol(num_buf, NULL, 10);
262
263     if (size > 6) {
264         strncpy(num_buf, &start[6], 2);
265         num_buf[2] = 0;
266         val.tm_sec = (int) strtol(num_buf, NULL, 10);
267     }
268     else {
269         val.tm_sec = 0;
270     }
271
272     return;
273 }
274
275 static const char * TZNames[] = {
276 "EST", "CST", "MST", "PST"
277 };
278
279 static const char * TZNamesDST[] = {
280 "EDT", "CDT", "MDT", "PDT"
281 };
282
283 static time_t
284 parseTZ(const char * start, const char * end)
285 {
286     int size = end - start + 1;
287
288     // There are at 3 possibilities that we understand. There
289     // is the single letter military time zone. In that case
290     // Z is 0 UTC. A-M is -1 to -12, skipping J. N-Y is +1 to +12
291     // from UTC.
292     //
293     // Lets start with that one because it is the easiest.
294
295     if (size == 1) {
296         int hours_from = 0;
297         if (*start >= 'A' && *start <= 'I') {
298             hours_from = *start - 'A' + 1;
299         }
300         else if (*start >= 'L' && *start <= 'M') {
301             hours_from = *start - 'K' + 10;
302         }
303         else if (*start >= 'N' && *start <= 'Y') {
304             hours_from = ('N' - *start) - 1;
305         }
306
307         return(hours_from * 3600);
308     }
309
310     // The next option is one of the ANSI standard time zones. These
311     // are three letter abbrievations that tell us where DST in in effect.
312     // So, if we have a length of three, lets see if it is in the table.
313     if (size == 3) {
314         // First normal zones.
315         int i;
316         for (i = 0; i < 4; i++) {
317             if (strncmp(start, TZNames[i], 3) == 0) {
318                 return((5 + i) * -3600);
319             }
320         }
321
322         // Now DST zones
323         for (i = 0; i < 4; i++) {
324             if (strncmp(start, TZNames[i], 3) == 0) {
325                 return((4 + i) * -3600);
326             }
327         }
328     }
329
330     // Finally we understand +/- HHMM from UTC.
331     if (size == 5) {
332         int sign = (*start == '+') ? 1 : -1;
333
334         char num_buf[10];
335         strncpy(num_buf, &start[1], 2);
336         num_buf[2] = 0;
337         int hours = (int) strtol(num_buf, NULL, 10);
338
339         strncpy(num_buf, &start[3], 2);
340         num_buf[2] = 0;
341         int minutes = (int) strtol(num_buf, NULL, 10);
342
343         return(sign * ((hours * 3600) + (minutes * 60)));
344     }
345
346     // We have no idea at this point, and it is very unlikely that the
347     // text is meaningful to the reader either. Set the zone to UTC and
348     // punt. It is also possible that the text is "UT" or "GMT" in which
349     // case offset 0 is the right answer.
350
351     return(0);
352 }
353
354 DtMailValueDate
355 RFCValue::toDate(void)
356 {
357     DtMailValueDate date;
358     const char * pos = _value;
359     tm new_time;
360
361     memset(&date, 0, sizeof(date));
362     memset(&new_time, 0, sizeof(new_time));
363
364     date.dtm_date = 0;
365     date.dtm_tz_offset_secs = 0;
366
367     // Before doing anything, check to see if _value is valid.
368     // Some messages have no Date string.  Return date with zeroed fields
369     // in those cases.
370
371     if (!_value || (strlen(_value) == 0)) return (date);
372
373     // Find the first non-blank
374     for (; *pos && isspace((unsigned char)*pos); pos++) {
375         continue;
376     }
377
378     // There are usually no more than 6 tokens in an RFC date. We will
379     // have a few extras just in case we are given a weird string.
380     const char *token_begin[12];
381     const char *token_end[12];
382     int n_tokens = 0;
383
384     // Look for the end of each token. Date tokens are white space
385     // separated.
386     while (*pos) {
387         token_begin[n_tokens] = pos;
388         for (; *pos && !isspace((unsigned char)*pos); pos++) {
389             continue;
390         }
391
392         if (*pos) {
393             token_end[n_tokens++] = pos - 1;
394         }
395         else {
396             token_end[n_tokens++] = pos;
397         }
398
399         for (; *pos && isspace((unsigned char)*pos); pos++) {
400             continue;
401         }
402         // This means the message is most likely corrupted so just bail out
403         if (n_tokens == 12) 
404                 break;
405     }
406
407     // Some dates will have a comma after the day of the week. We
408     // want to remove that. It will always be the first token if
409     // we have the day of the week.
410     if (*token_end[0] == ',') {
411         token_end[0]--;
412     }
413
414     if (n_tokens < 2) {
415         return(date);
416     }
417
418     // There are two possible formats, and many variations, that we
419     // will see in an RFC message. They are:
420     //
421     // Tue Oct 12 10:36:10 1993
422     // Tue, 12 Oct 1993 10:35:05 PDT
423     //
424     // The first is the 821 format put on by sendmail. The second is
425     // one of the many variants of the 822 format. The big difference
426     // we must detect is "mon dd time year" vs "dd mon year time tz"
427     //
428     // The first qualifier is usually the day of the week. For our purposes,
429     // we will simply throw it away. This information will be recomputed
430     // based on the date and time.
431
432     int this_token = 0;
433
434     int day = matchDay(token_begin[this_token], token_end[this_token]);
435     if (day >= 0) {
436         // Ignore the day.
437         this_token += 1;
438     }
439
440     // This token should either be a numeric day, or an alpha month.
441     // Lets see if it is a month. If so, we know what the rest of
442     // the date will look like.
443
444     int month = matchMonth(token_begin[this_token], token_end[this_token]);
445     if (month >= 0) {
446         new_time.tm_mon = month;
447
448         // Now should be the day of the month.
449         char num_buf[20];
450         this_token += 1;
451
452         if (this_token == n_tokens) {
453             return(date);
454         }
455
456         strncpy(num_buf, token_begin[this_token], 2);
457         num_buf[2] = 0;
458         new_time.tm_mday = (int) strtol(num_buf, NULL, 10);
459
460         this_token += 1;
461         if (this_token == n_tokens) {
462             return(date);
463         }
464
465         parseTime(token_begin[this_token], token_end[this_token], new_time);
466
467         this_token += 1;
468         if (this_token == n_tokens) {
469             return(date);
470         }
471
472
473         // Sometimes the Unix date will include the time zone.
474         //
475         if (isalpha(*token_begin[this_token])) {
476             this_token += 1;
477             if (this_token == n_tokens) {
478                 return(date);
479             }
480         }
481
482         strncpy(num_buf, token_begin[this_token], 4);
483         // Don't remove last digit from year and get bad dates in header.
484         num_buf[token_end[this_token] - token_begin[this_token] + 1] = 0;
485         new_time.tm_year = (int) strtol(num_buf, NULL, 10);
486         if (new_time.tm_year > 1900) {
487             new_time.tm_year -= 1900;
488         }
489
490         new_time.tm_isdst = -1;
491         date.dtm_date = SafeMktime(&new_time);
492 #ifdef SVR4
493         date.dtm_tz_offset_secs = timezone;
494 #else
495         date.dtm_tz_offset_secs = new_time.tm_gmtoff;
496 #endif
497     }
498     else {
499         // In this format, we should have a day of the month.
500         char num_buf[20];
501         strncpy(num_buf, token_begin[this_token], 2);
502         num_buf[2] = 0;
503         new_time.tm_mday = (int) strtol(num_buf, NULL, 10);
504
505         this_token += 1;
506         if (this_token == n_tokens) {
507             return(date);
508         }
509
510         // Now the month name.
511         new_time.tm_mon = matchMonth(token_begin[this_token], token_end[this_token]);
512
513         this_token += 1;
514         if (this_token == n_tokens) {
515             return(date);
516         }
517
518         // The year, which is either 2 or 4 digits.
519         int t_size = token_end[this_token] - token_begin[this_token] + 1;
520         strncpy(num_buf, token_begin[this_token], t_size);
521         num_buf[t_size] = 0;
522         new_time.tm_year = (int) strtol(num_buf, NULL, 10);
523         if (new_time.tm_year > 1900) {
524             new_time.tm_year -= 1900;
525         }
526
527         this_token += 1;
528         if (this_token == n_tokens) {
529             return(date);
530         }
531
532         // The time, in the specified time zone.
533         parseTime(token_begin[this_token], token_end[this_token], new_time);
534
535         this_token += 1;
536         if (this_token == n_tokens) {
537             return(date);
538         }
539
540         time_t offset = parseTZ(token_begin[this_token], token_end[this_token]);
541
542 #ifdef SVR4
543         time_t orig_zone = timezone;
544         timezone = offset;
545 #endif
546         // Tell "mktime" to figure "dst" on or not.
547         new_time.tm_isdst = -1;
548
549         date.dtm_date = SafeMktime(&new_time);
550         date.dtm_tz_offset_secs = offset;
551
552 #ifdef SVR4
553         timezone = orig_zone;
554 #endif
555     }
556
557     return(date);
558 }
559
560 static char *
561 findParenComment(const char * value)
562 {
563     int in_quote = 0;
564     const char *sparen;
565     for (sparen = value; *sparen; sparen++) {
566         // We must ignore stuff in quotes.
567         //
568         if (*sparen == '"') {
569             if (in_quote) {
570                 in_quote = 0;
571             }
572             else {
573                 in_quote = 1;
574             }
575             continue;
576         }
577
578         if (in_quote) {
579             continue;
580         }
581
582         if (*sparen == '(') {
583             break;
584         }
585     }
586
587     if (*sparen != '(') {
588         return(NULL);
589     }
590
591     in_quote = 0;
592     const char *lparen;
593     for (lparen = (sparen + 1); *lparen; lparen++) {
594         // We will support nested comments of the form (Joe (Hi) Blow)
595         //
596         if (*lparen == '(') {
597             in_quote += 1;
598             continue;
599         }
600
601         if (*lparen == ')') {
602             in_quote -= 1;
603         }
604
605         if (in_quote < 0) {
606             break;
607         }
608     }
609
610     if (*lparen != ')') {
611         return(NULL);
612     }
613
614     char * comment = (char *)malloc(lparen - sparen + 1);
615     memcpy(comment, (sparen + 1), lparen - sparen - 1);
616     comment[lparen - sparen - 1] = 0;
617
618     return(comment);
619 }
620
621 static char *
622 stripAngleAddr(const char * value)
623 {
624     int in_quote = 0;
625
626     const char *lt;
627     for (lt = value; *lt; lt++)
628     {
629         if (*lt == '"')
630         {
631             if (in_quote) in_quote = 0;
632             else in_quote = 1;
633             continue;
634         }
635         if (in_quote) continue;
636         if (*lt == '<') break;
637     }
638
639     if (*lt != '<') return(NULL);
640
641     in_quote = 0;
642
643     const char *gt;
644     for (gt = (lt + 1); *gt; gt++)
645     {
646         if (*gt == '"')
647         {
648             if (in_quote) in_quote = 0;
649             else in_quote = 1;
650             continue;
651         }
652         if (in_quote) continue;
653         if (*gt == '>') break;
654
655     }
656
657     if (*gt != '>') return(NULL);
658
659     // Copy everything not in the angle brackets.
660     //
661     char * name = (char *)malloc(strlen(value) + 1);
662     char * out = name;
663
664     for (const char * cp = value; *cp; cp++)
665     {
666         if (cp >= lt && cp <= gt) continue;
667
668         *out++ = *cp;
669     }
670
671     *out = 0;
672
673     if (strlen(name) == 0)
674     {
675         free(name);
676         return(NULL);
677     }
678
679     return(name);
680 }
681
682 static char *
683 stripQuotesWhiteSpace(const char * value)
684 {
685     int   found_alphanum = 0;
686     char *name = NULL;
687     char *out = NULL;
688
689     //
690     // Skip past leading white space.
691     //
692     const char *cp = value;
693     while (isspace(*cp)) cp++;
694
695     //
696     // If there are no quotes, copy and return.
697     //
698     if (*cp != '"') 
699     {
700         name = strdup(cp);
701         return name;
702     }
703
704     //
705     // Strip out the quotes.
706     //
707     cp++;
708
709     out = name = (char*) malloc(strlen(value)+1);
710     if (NULL == out) return NULL;
711
712     while (*cp != '"')
713     {
714         *out = *cp;
715         out++;
716         cp++;
717     }
718
719     *out = 0;
720     return name;
721 }
722
723 DtMailAddressSeq *
724 RFCValue::toAddress(void)
725 {
726     // Count the commas, to figure out how big to make the
727     // sequence.
728     //
729     int commas = 3;
730     for (const char * comma = _value; *comma; comma++)
731       if (*comma == ',') commas += 1;
732
733     DtMailAddressSeq * seq = new DtMailAddressSeq(commas);
734
735     if (!_decoded) decodeValue();
736
737     RFCTransport::arpaPhrase(_decoded, *seq);
738
739     // If we have only one address, then let's try to find a comment
740     // so the person can be set. This is trivial to do for one address
741     // and can have a win for displaying the headers in the message
742     // scrolling list.
743     //
744     if (seq->length() == 1)
745     {
746         // This is less than perfect, but we will look for (Name) and
747         // use it first. If we can't find that, then see if we can
748         // find something outside <addr>. If not that, then simply
749         // give up.
750         //
751         DtMailValueAddress * addr = (*seq)[0];
752
753         addr->dtm_person = findParenComment(_decoded);
754         if (!addr->dtm_person)
755         {
756             char *name = stripAngleAddr(_decoded);
757             if (name)
758             {
759                 addr->dtm_person = stripQuotesWhiteSpace(name);
760                 free(name);
761             }
762         }
763     }
764
765     return(seq);
766 }
767
768 const char *
769 RFCValue::raw(void)
770 {
771     return(_value);
772 }
773
774 void
775 RFCValue::decodeValue(void)
776 {
777     // Create the output buffer. We will assume that it is
778     // the header will only shrink by applying RFC1522.
779     //
780     int outleft = strlen(_value);
781     char * output = (char *)malloc(outleft + 2);
782     
783     *output = 0;
784     char * cur_c = output;
785     
786     char *buf = NULL;
787     
788     // Scan the value, looking for =? which indicates the start
789     // of a encoded string.
790     //
791     for (const char * in_c = _value; *in_c; in_c++) {
792         if (*in_c == '=' && *(in_c + 1) == '?') {
793             //
794             // Decode the encoding. Return the last character so the loop
795             // continues to work. Also reset cur_c because the output buffer
796             // has been updated.
797             //
798             // Allocate space for buf to contain rest of output because it
799             // is enough space for the decoded quoted-printable or base64.
800             // If codeset conversion is done, then csConvert will re-allocate
801             // enough space.
802             //
803             size_t _valueLen = strlen(_value);
804             const char *in_c_sav = in_c;
805
806             buf = (char *)malloc(outleft + 2);
807             strcpy(buf, in_c);
808             in_c = decode1522(in_c, _value + _valueLen - 1, &buf, _session);
809
810             if (in_c > in_c_sav) {
811                 size_t bufLen = strlen(buf);
812                 if (bufLen > outleft) {
813                     output =
814                         (char*) realloc((char*)output, _valueLen + bufLen + 2);
815                     outleft += bufLen;
816                 } 
817                 strncat(output, buf, bufLen);
818                 cur_c = output + strlen(output);
819                 outleft -= bufLen;
820                 free(buf);
821                 continue;
822             }
823             free(buf);
824         }
825         
826         // Just copy the byte and reset the null pointer, unless
827         // we are dealing with carriage return.
828         //
829         if (*in_c != '\r') {
830             if (outleft == 0) {
831                 output = (char*) realloc((char*) output, strlen(output) * 2);
832                 outleft = strlen(output);
833             }
834             *cur_c = *in_c;
835             cur_c++;
836             *cur_c = 0;
837             outleft--;
838         }
839     }
840     
841     // Kill any trailing white space.
842     //
843     *cur_c = 0;
844     for (cur_c -= 1;
845          cur_c >= output && isspace((unsigned char)*cur_c);
846          cur_c--)
847     {
848         *cur_c = 0;
849     }
850     
851     _decoded = output;
852 }