dthelp: Change to ANSI function definitions
[oweals/cde.git] / cde / programs / dtmail / libDtMail / RFC / RFCMessage.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: RFCMessage.C /main/9 1998/04/06 13:29:56 mgreess $
28  *
29  *      RESTRICTED CONFIDENTIAL INFORMATION:
30  *      
31  *      The information in this document is subject to special
32  *      restrictions in a confidential disclosure agreement bertween
33  *      HP, IBM, Sun, USL, SCO and Univel.  Do not distribute this
34  *      document outside HP, IBM, Sun, USL, SCO, or Univel wihtout
35  *      Sun's specific written approval.  This documment and all copies
36  *      and derivative works thereof must be returned or destroyed at
37  *      Sun's request.
38  *
39  *      Copyright 1993 Sun Microsystems, Inc.  All rights reserved.
40  *
41  *+ENOTICE
42  */
43
44 #include <EUSCompat.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <ctype.h>
48 #include <assert.h>
49
50 #include <DtMail/DtMail.hh>
51 #include "RFCImpl.hh"
52 #include <DtMail/Threads.hh>
53 #include "str_utils.h"
54
55 const char * RFCDeleteHeader = "X-Dt-Delete-Time";
56 static unsigned long RFCMessageSignature = 0xff83421e;
57
58 // RFCMessage constructor for alternative message parts
59 // Arguments:
60 //  error       --
61 //  alt_start   -- beginning of alternative message (at interpart boundary)
62 //  alt_end     -- last byte of alternative message
63 // Outputs:
64 //  RFCMessage * (with _alternativeMessage == DTM_TRUE) that has a completely
65 //  parsed and setup message with body parts and minimal envelope.
66 // Description:
67 //  This constructor is used to create a message that is one of possibly
68 //  many alternative messages that are part of a single MIME multipart
69 //  alternative message.
70 //
71 RFCMessage::RFCMessage(DtMailEnv & error,
72                        const char * alt_start,
73                        const char * alt_end)
74 : DtMail::Message(error, NULL), _bp_cache(8), _alt_msg_cache(0)
75 {
76   error.clear();
77
78   // Initialize objects within RFCMessage structure
79   //
80   _object_signature = RFCMessageSignature;
81   _dirty = 0;                           // can never be dirty
82   _alternativeMultipart = DTM_FALSE;    // msg does not contain alternatives
83   _alternativeMessage = DTM_TRUE;       // msg is an alternative message
84   _alternativeValid = DTM_FALSE;        // msg is not valid (maybe later)
85   _msg_start = alt_start;
86   _msg_end = alt_end;
87   _msg_buf = NULL;
88   _body_start = NULL;
89
90   // parse the message, creating an envelope to encompass the first headers
91   // found and setting up the body part boundaries
92   //
93   (void) parseMsg(error, (char *) alt_end);
94   if (error.isSet()) {
95     return;
96   }
97
98   // parse the message bodies - this constructor is currently only called
99   // for a MIME multipart alternative message part. If other mail protocols
100   // are added that have similar functionality, this constructor will have
101   // to be augmented to take this into consideration
102   //
103   parseMIMEBodies(error);
104   if (error.isSet()) {
105     return;
106   }
107
108   // it worked!
109   return;
110 }
111
112 RFCMessage::RFCMessage(DtMailEnv & error, DtMail::MailBox * parent, 
113                        const char ** start,
114                        const char * end_of_file)
115 : DtMail::Message(error, parent), _bp_cache(8), _alt_msg_cache(8)
116 {
117     _object_signature = RFCMessageSignature;
118     error.clear();
119
120     if (_parent) {
121         _session = _parent->session();
122     }
123
124     _dirty = 0;
125     _alternativeMultipart = DTM_FALSE;
126     _alternativeMessage = DTM_FALSE;
127     _alternativeValid = DTM_FALSE;
128
129     _msg_start = *start;
130
131     *start = parseMsg(error, end_of_file);
132
133     // We want to be lazy about parsing the body. Most messages are
134     // never viewed in a folder. We have already hit the pages once,
135     // but with MADV_SEQUENTIAL on, they are marked for immediate
136     // disposal so they may be gone. Of course we are going to slow
137     // the first display of the message, but odds are that will never
138     // happen anyway.
139     //
140     // There is another reason for delaying the parsing on an MT hot
141     // platform. We are parsing on a separate thread. If we wait until
142     // we are asked for information about the bodies to parse them,
143     // we can get the main thread to do this work. So, we can actually
144     // get some concurrent parsing to occur.
145     //
146     // Conclusion: Lazy is better!
147     //
148     return;
149 }
150
151 RFCMessage::RFCMessage(DtMailEnv & error,
152                        DtMail::Session * session,
153                        DtMailObjectSpace space,
154                        void * arg,
155                        DtMailCallback,
156                        void *)
157 : DtMail::Message(error, NULL), _bp_cache(8), _alt_msg_cache(8)
158 {
159     // We do different processing, depending on the space for the
160     // object.
161     //
162     _session = session;
163
164     switch (space) {
165       case DtMailBufferObject:
166       {
167           _object_signature = RFCMessageSignature;
168           error.clear();
169           
170           _dirty = 0;
171           _alternativeMultipart = DTM_FALSE;
172           _alternativeMessage = DTM_FALSE;
173           _alternativeValid = DTM_FALSE;
174           
175           _msg_buf = (DtMailBuffer *)arg;
176
177           // If the buffer is not null, then we need to parse the
178           // buffer.
179           //
180           if (_msg_buf->size > 0) {
181               if (hasHeaders((const char *)_msg_buf->buffer, _msg_buf->size) == DTM_FALSE) {
182                   error.setError(DTME_UnknownFormat);
183                   return;
184               }
185
186               _msg_start = (char *)_msg_buf->buffer;
187               (void) parseMsg(error, (char *)_msg_buf->buffer + _msg_buf->size);
188               if (error.isSet()) {
189                   return;
190               }
191           }
192           else {
193               // We need to construct an empty message.
194               //
195               _msg_start = _msg_end = _body_start = NULL;
196               _envelope = new RFCEnvelope(error, this, NULL, 0);
197           }
198       }
199       break;
200
201       case DtMailFileObject:
202       default:
203         error.setError(DTME_NotSupported);
204     }
205 }
206
207 RFCMessage::~RFCMessage(void)
208 {
209   if (_object_signature == RFCMessageSignature) {
210     MutexLock lock_scope(_obj_mutex);
211     _object_signature = 0;
212
213     // spin through the body part cache deleting all parts cached.
214     // If _alternativeMultipart is set, this is the "main message" and
215     // as such all of the body parts are merely copied from one of the
216     // underlying alternative messages, so in this case we only delete the
217     // body part cache and not the contents of the cache, as that will be
218     // taken care of when the alternative message cache is purged.
219     //
220     while (_bp_cache.length()) {
221       BodyPartCache * bpc = _bp_cache[0];
222       if (!_alternativeMultipart)
223         delete bpc->body;
224       delete bpc;
225       _bp_cache.remove(0);
226     }
227
228     // spin through the alternative message cache destroying any
229     // alternative messages which have been built to support a
230     // multipart alternative message
231     //
232     if (_alternativeMultipart == DTM_TRUE)
233       while (_alt_msg_cache.length()) {
234         AlternativeMessageCache * amc = _alt_msg_cache[0];
235         assert(amc != NULL);
236         assert(amc->amc_msg != NULL);
237         delete amc->amc_msg;
238         assert(amc->amc_body != NULL);
239         delete amc->amc_body;
240         delete amc;
241         _alt_msg_cache.remove(0);
242       }
243   }
244 }
245
246 void
247 RFCMessage::toBuffer(DtMailEnv & error, DtMailBuffer & buf)
248 {
249     error.clear();
250
251     buf.size = _msg_end - _msg_start + 1;
252     buf.buffer = new char[buf.size];
253     if (buf.buffer == NULL) {
254         error.setError(DTME_NoMemory);
255         return;
256     }
257
258     memcpy(buf.buffer, _msg_start, (size_t) buf.size);
259 }
260
261 const char *
262 RFCMessage::impl(DtMailEnv & error)
263 {
264     // By definition, we are contained within some RFC container. We
265     // want to make sure we return the exact same string, so just return
266     // what our containing parent uses for its impl ID string.
267     //
268     if (_parent) {
269         return(_parent->impl(error));
270     }
271
272     return("Internet MIME");
273 }
274
275 DtMail::Envelope *
276 RFCMessage::getEnvelope(DtMailEnv & error)
277 {
278     error.clear();
279     return(_envelope);
280 }
281
282 int
283 RFCMessage::getBodyCount(DtMailEnv & error)
284 {
285   error.clear();
286
287     // We may need to parse the body!
288     //
289     if (_bp_cache.length() == 0) {
290         parseBodies(error);
291     }
292
293     return(_bp_cache.length());
294 }
295
296 DtMail::BodyPart *
297 RFCMessage::getFirstBodyPart(DtMailEnv & error)
298 {
299     error.clear();
300     
301     // We may need to parse the body!
302     //
303     if (_bp_cache.length() == 0 && _msg_start) {
304         parseBodies(error);
305     }
306     
307     if (_bp_cache.length() == 0) {
308         return(NULL);
309     }
310
311     return(bodyPart(error, 0));
312 }
313
314 DtMail::BodyPart *
315 RFCMessage::getNextBodyPart(DtMailEnv & error, DtMail::BodyPart * last)
316 {
317     error.clear();
318     
319     int slot = lookupByBody(last);
320     if (slot < 0 || (slot + 1) >= _bp_cache.length()) {
321         return(NULL);
322     }
323     
324     slot += 1;
325     return(bodyPart(error, slot));
326 }
327
328 DtMail::BodyPart *
329 RFCMessage::newBodyPart(DtMailEnv & error, DtMail::BodyPart * after)
330 {
331     error.clear();
332
333     int slot = -1;
334
335     if (after) {
336         slot = lookupByBody(after);
337     }
338
339     RFCBodyPart * bp = new MIMEBodyPart(error,
340                                         this,
341                                         NULL,
342                                         0,
343                                         NULL);
344
345     BodyPartCache * bpc = new BodyPartCache;
346     bpc->body = bp;
347     bpc->body_start = NULL;
348
349     if (slot < 0) {
350         _bp_cache.append(bpc);
351     }
352     else {
353         _bp_cache.insert(bpc, slot + 1);
354     }
355
356     return(bp);
357 }
358
359 void
360 RFCMessage::setFlag(DtMailEnv & error, const DtMailMessageState flag)
361 {       
362   error.clear();
363
364     time_t now;
365     char        str_time[20];
366
367     switch (flag) {
368       case DtMailMessageNew:
369         _envelope->setHeader(error, "Status", DTM_TRUE, "NR");
370         break;
371
372       case DtMailMessageDeletePending:
373         // Start the delete time out by setting the X-Delete-Time header.
374         //
375         now = time(NULL);
376         sprintf(str_time, "%08lX", (long)now);
377         _envelope->setHeader(error, RFCDeleteHeader, DTM_TRUE, str_time);
378         break;
379
380       case DtMailMessagePartial:
381         //
382         // There is nothing to do for partial. It is already
383         // marked as partial as it has the Content-Type: message/partial
384         // header. This case statment exists so that SetFlag will not
385         // drop into the default 'error' below.
386         break;
387
388       default:
389         error.setError(DTME_OperationInvalid);
390         break;
391     }
392
393     return;
394 }
395
396 void
397 RFCMessage::resetFlag(DtMailEnv & error, const DtMailMessageState flag)
398 {
399   error.clear();
400
401     switch (flag) {
402       case DtMailMessageNew:
403         _envelope->setHeader(error, "Status", DTM_TRUE, "RO");
404         break;
405
406       case DtMailMessageDeletePending:
407         _envelope->removeHeader(error, RFCDeleteHeader);
408         break;
409
410       case DtMailMessagePartial:
411         //
412         // There is nothing to do for partial. It is already
413         // marked as partial as it has the Content-Type: message/partial
414         // header. This case statment exists so that resetFlag will not
415         // drop into the default 'error' below.
416         //
417         // You can not 'reset' message partial, you can only
418         // delete the header.
419         break;
420
421       default:
422         error.setError(DTME_OperationInvalid);
423     }
424
425     return;
426 }
427
428 DtMailBoolean
429 RFCMessage::flagIsSet(DtMailEnv & error, const DtMailMessageState flag)
430 {
431     DtMailValueSeq       value;
432     DtMailBoolean       answer = DTM_FALSE;
433
434     switch (flag) {
435       case DtMailMessageNew:
436         _envelope->getHeader(error, "Status", DTM_FALSE, value);
437         if (error.isNotSet()) { 
438             const char * status = *(value[0]);
439             if (strcasecmp(status, "ro")) {
440                 answer = DTM_TRUE;
441             }
442         }
443         else {
444             // No Status: means a new message.
445             error.clear();
446             answer = DTM_TRUE;
447         }
448
449         break;
450
451       case DtMailMessageDeletePending:
452         _envelope->getHeader(error, RFCDeleteHeader, DTM_FALSE, value);
453         if (error.isNotSet()) {
454             answer = DTM_TRUE;
455         }
456         else {
457             error.clear();
458         }
459         break;
460
461       case DtMailMessageMultipart:
462         _envelope->getHeader(error, "Content-Type", DTM_FALSE, value);
463         if (error.isNotSet()) {
464             const char * type = *(value[0]);
465             if (strcasecmp(type, "X-Sun-Attachment") == 0 ||
466                 strncasecmp(type, "multipart", 9) == 0) {
467                 answer = DTM_TRUE;
468             }
469         }
470         else {
471             error.clear();
472         }
473         break;
474
475       case DtMailMessagePartial:
476         _envelope->getHeader(error, "Content-Type", DTM_FALSE, value);
477         if (error.isNotSet()) {
478             const char * type = *(value[0]);
479             if (strncasecmp(type, "message/partial", 15) == 0) {
480                 answer = DTM_TRUE;
481             }
482         } else {
483             error.clear();
484         }
485         break;
486
487       default:
488         break;
489     }
490
491     error.clear();
492
493     return(answer);
494 }
495
496 time_t
497 RFCMessage::getDeleteTime(DtMailEnv & error)
498 {
499     time_t      delete_time = 0;
500
501     DtMailValueSeq value;
502     _envelope->getHeader(error, RFCDeleteHeader, DTM_FALSE, value);
503     if (error.isNotSet()) {
504         delete_time = (time_t) strtol(*(value[0]), NULL, 16);
505     }
506
507     error.clear();
508
509     return(delete_time);
510 }
511
512 void
513 RFCMessage::markDirty(const int delta)
514 {
515     if (_parent) {
516         ((RFCMailBox *)_parent)->markDirty(delta);
517     }
518
519     _dirty += delta;
520     if (!_dirty) {
521         _dirty += 1;
522     }
523 }
524
525 // Function: RFCMessage::fixMessageLocation - return address/length of message
526 // Description:
527 //  This function is used to obtain an address and length in bytes of a
528 //  message.
529 // Method:
530 //  If the message is not dirty (e.g. already mapped into memory as one
531 //  contiguous stream of bytes) then simply return its address and length;
532 //  otherwise, must construct the message into a temporary area, the address
533 //  and length of which is then returned.
534 // Arguments:
535 //  long & msgLen       -- (output) length of message in bytes
536 //  int & msgTemporary  -- (output) 0 if message resides in previously
537 //                              allocated memory (e.g. mapped already),
538 //                              != 0 if temporary space allocated to hold
539 //                              message, requiring later deallocation
540 // Returns:
541 //  const char * -> first byte of message
542 //
543 void
544 RFCMessage::fixMessageLocation(char ** msgHeaderStart, long & msgHeaderLen,
545                                char ** msgBodyStart, long &msgBodyLen,
546                                int & msgTemporary, long & msgBodyOffset)
547 {
548   if (!_dirty) {
549     // This is easy as the message is complete as is, simply return
550     // its current memory location and size in bytes. The message is
551     // "permanent" in that it does not currently reside in a temporary
552     // memory address which later has to be deallocated.
553     //
554     *msgHeaderStart = (char *)_msg_start;
555     msgHeaderLen = _body_start - _msg_start;
556     *msgBodyStart = (char *)_body_start;
557     msgBodyLen = (_msg_end - _body_start)+1;
558     msgTemporary = 0;
559     msgBodyOffset = 0;  // no header offset fudge factor
560     return;
561   }
562
563   // Well, now it gets tricky. The message is not assembled in one
564   // place in memory, which prevents fixing an address and length
565   // for it, so we have to force the message to be constructed. This
566   // is done in a "temporary" memory area which has to be deallocated
567   // when the message is mapped in from a real file.
568   //
569   pinMessageDown(msgHeaderStart, msgHeaderLen, msgBodyStart, msgBodyLen);
570   msgBodyOffset = msgHeaderLen-(_body_start-_msg_start);
571   msgTemporary = 1;
572   return;
573 }
574
575 // Function: RFCMessage::pinMessageDown - fix complete message in memory
576 // Description:
577 //  This function is used on a "dirty" message to cause a complete
578 //  in memory copy of the message to be created so that it can be
579 //  written out (probably as a result of a mailbox save operation).
580 // Method:
581 // Arguments:
582 //  long & msgLen       -- (output) length of message in bytes
583 //  long & msgBodyOffset -- (output) +- adjustment (between original message
584 //                      and new message) of the first byte at which the
585 //                      body of the message begins -- this lets us determine
586 //                      the body start in cases where the headers are changed
587 //                      without reparsing the message
588 // Returns:
589 //  const char * -> first byte of message pinned in memory
590 //
591 void
592 RFCMessage::pinMessageDown(char ** msgHeaderStart, long & msgHeaderLen,
593                            char ** msgBodyStart, long &msgBodyLen)
594 {
595   // First, determine which write method to use. We have
596   // 2 choices, Sun V3, and MIME. We will need to compute
597   // the size of the message, then make another pass to
598   // actually store the body.
599   //
600   DtMailEnv error;
601   int content_length;
602   DtMailValueSeq        value;
603
604   // Compute content length of message
605   //
606   _envelope->getHeader(error, "Mime-Version", DTM_FALSE, value);
607   if (error.isNotSet()) {
608       content_length = sizeMIMEBodies(error);
609   }
610   else {
611       error.clear();
612       content_length = sizeV3Bodies(error);
613   }
614
615   content_length = content_length < 0 ? 0 : content_length;
616
617   char len_buf[20];
618   sprintf(len_buf, "%d", content_length);
619   _envelope->setHeader(error, "Content-Length", DTM_TRUE, len_buf);
620
621   // Allocate storage for the headers and write headers into it
622   //
623   const size_t maxHeaderLength =
624                         (size_t) ((RFCEnvelope *)_envelope)->headerLength();
625   const size_t fudgeAtEnd = 102;        // two extra \n's at end of msg + slop
626   size_t msgNewHeaderSize = (maxHeaderLength+fudgeAtEnd);
627   *msgHeaderStart = (char *)malloc(msgNewHeaderSize);
628   assert(*msgHeaderStart != NULL);
629
630   char * end = ((RFCEnvelope *)_envelope)->writeHeaders(*msgHeaderStart);
631   end += 1;
632   *end++ = '\n';
633   msgHeaderLen = end-*msgHeaderStart;
634
635   // Make sure we did not overflow the allocated area
636   //
637   assert(msgHeaderLen < msgNewHeaderSize);
638
639   // As of this implementation, body parts can never change, so we do not have to
640   // worry about the body part needing reconstruction
641   //
642   *msgBodyStart = (char *)_body_start;
643   msgBodyLen = (_msg_end - _body_start)+1;
644   
645   return;
646 }
647
648 void
649 RFCMessage::adjustMessageLocation(char * oldStart, char * newStart, long newLength, int msgTemporary, long newBodyOffset)
650 {
651   MutexLock lock_scope(_obj_mutex);
652   
653   assert((_dirty && msgTemporary) || (!_dirty && !msgTemporary));
654
655   // At this point the message has been mapped into a new location in memory
656   // Must adjust all pointers to data contained within this message
657   //
658   int len = (int) ((_msg_end - _msg_start) + 1 + newBodyOffset);
659   assert(len == newLength);
660   _body_start = (_body_start - _msg_start) + newStart + newBodyOffset;
661   _msg_end = newStart + len - 1;
662
663   ((RFCEnvelope *)_envelope)->adjustHeaderLocation(newStart, (int)(_body_start-newStart));
664   int bpMaxSlot = _bp_cache.length();
665   int bp;
666   for (bp = 0; bp < bpMaxSlot; bp++) {
667     char * new_loc = newStart + (_bp_cache[bp]->body_start - _msg_start) + newBodyOffset;
668       _bp_cache[bp]->body->adjustBodyPartsLocation(new_loc);
669       _bp_cache[bp]->body_start = new_loc;
670   }
671
672   // If this is the main portion of a multipart alternative message,
673   // we must handle each alternative and all of the body parts that
674   // comprise each alternative
675   //
676   if (_alternativeMultipart == DTM_TRUE) {
677     // Multipart Alternative message parent body
678     // Adjust all of the body parts cached for the alternatives
679     //
680     int altCacheMaxSlot = _alt_msg_cache.length();
681     for (int altCacheSlot = 0; altCacheSlot < altCacheMaxSlot; altCacheSlot++) {
682       AlternativeMessageCache *amc = _alt_msg_cache[altCacheSlot];
683       char *altNewStart = (((RFCEnvelope *) amc->amc_msg->_envelope)->headerLocation() - _msg_start) + newStart + newBodyOffset;
684       int altHeaderLength =
685                 (int) ((RFCEnvelope *) amc->amc_msg->_envelope)->headerLength();
686       ((RFCEnvelope *) amc->amc_msg->_envelope)->adjustHeaderLocation(altNewStart, altHeaderLength);
687       bpMaxSlot = amc->amc_msg->_bp_cache.length();
688       for (bp = 0; bp < bpMaxSlot; bp++) {
689         char *new_loc = newStart + (amc->amc_msg->_bp_cache[bp]->body_start - _msg_start) + newBodyOffset;
690         amc->amc_msg->_bp_cache[bp]->body->adjustBodyPartsLocation(new_loc);
691         amc->amc_msg->_bp_cache[bp]->body_start = new_loc;
692       }
693     }
694   }
695
696   _msg_start = newStart;
697
698   // If this message is "temporary", it was dirty at the time its location
699   // was fixed, and therefore was placed into an area allocated on the fly.
700   // Since the message and all associated pointers no longer refer to this
701   // area, it must be free'd
702   //
703   if (msgTemporary)
704     free(oldStart);
705   
706   _dirty = 0;
707   
708 }
709
710 void
711 RFCMessage::unfixMessageLocation(char * msgStart, int msgTemporary)
712 {
713   MutexLock lock_scope(_obj_mutex);
714
715   // If this message is "temporary", it was dirty at the time its location
716   // was fixed, and therefore was placed into an area allocated on the fly.
717   // Unfix is only called if an error has occurred which requires the
718   // new message to be tossed, so we must free any storage allocated to
719   // this message by a previous "fixMessageLocation".
720   //
721   if (msgTemporary)
722     free(msgStart);
723 }
724
725 DtMail::BodyPart *
726 RFCMessage::bodyPart(DtMailEnv & error, const int slot)
727 {
728   error.clear();
729
730     MutexLock lock_scope(_obj_mutex);
731
732     return(_bp_cache[slot]->body);
733 }
734
735 int
736 RFCMessage::lookupByBody(DtMail::BodyPart * bp)
737 {
738     for (int slot = 0; slot < _bp_cache.length(); slot++) {
739         if (_bp_cache[slot]->body == bp) {
740             return(slot);
741         }
742     }
743
744     return(-1);
745 }
746
747 inline const char *
748 backcrlf(const char * pos)
749 {
750     const char * back = pos;
751     if (*back == '\n') {
752         back -= 1;
753     }
754
755     if (*back == '\r') {
756         back -= 1;
757     }
758     return(back);
759 }
760
761 const char *
762 RFCMessage::parseMsg(DtMailEnv & error,
763                      const char * end_of_file)
764 {
765     // We need to find the end of the envelope. This is signified by a
766     // single blank line. This should simply be "\n\n" in the message
767     // but RFC says parsers should be forgiving so we will tolerate
768     // "\n<any-white-space>\n"
769     //
770     const char *hdr_end;
771     for (hdr_end = _msg_start; hdr_end <= end_of_file; hdr_end++) {
772         if (*hdr_end == '\n') {
773             int blanks_only = 1;
774             for (const char * blanks = hdr_end + 1; 
775                  *blanks != '\n' && blanks <= end_of_file; blanks++) {
776                 if (!isspace((unsigned char)*blanks)) {
777                     blanks_only = 0;
778                     break;
779                 }
780             }
781
782             if (blanks_only) {
783                 // Found the end of the headers.
784                 break;
785             }
786         }
787     }
788
789     if (hdr_end > end_of_file) { // Ran off the end.
790         error.setError(DTME_NotMailBox);
791         return(end_of_file + 1);
792     }
793
794     // We need to parse the headers now, because they will give us the
795     // content length, type, and a message id.
796     //
797     _envelope = new RFCEnvelope(error, this, _msg_start, hdr_end - _msg_start + 1);
798     if (error.isSet()) {
799         // Oops! We need to find the next "From " line if possible to at least
800         // let the rest of the parsing proceed.
801         //
802         const char *next_from;
803         for (next_from = hdr_end + 1;
804              next_from <= (end_of_file - 6); next_from++) {
805             if (strncmp(next_from, "\nFrom ", 6) == 0) {
806                 break;
807             }
808         }
809         const char * new_end;
810         if (next_from > (end_of_file - 6)) {
811             new_end = end_of_file + 1;
812         }
813         else {
814             new_end = next_from + 1;
815         }
816         return(new_end);
817     }
818
819     // The hdr_end now points at the last newline of the envelope.
820     // We want the body to start after the next newline, but we must
821     // be prepared to stop if the end of the file is seen before the
822     // body start is found, which can happen if the last message in
823     // the inbox has no contents at all.
824     //
825     for (_body_start = hdr_end + 1; _body_start <= end_of_file && *_body_start != '\n'; _body_start++) {
826         continue;
827     }
828     _body_start += 1;
829     if (_body_start > end_of_file)      // past the end??
830       _body_start = end_of_file;        // yes: body starts on last byte
831
832     // Need to know where this message ends.
833     //
834     const char *messageEnd = findMsgEnd(error, end_of_file);
835
836     // Need to guard against the case where the current body begins
837     // past the "end" of the current message - this can happen if a
838     // message consists of [this_msg_headers\n][\n][next_msg_headers]
839     // where there is a zero length (no) body.
840     //
841     if (_body_start > _msg_end)         // Does body start after last byte in message?
842       _body_start = _msg_end;           // Yes: force body to start at last byte of message
843
844     return(messageEnd);
845 }
846
847 // returns -> last byte of current message
848 //
849 const char *
850 RFCMessage::findMsgEnd(DtMailEnv & error, const char * eof)
851 {
852     // This function sets "_msg_end" to ( "_body_start" - 1 + length_of_message ).
853     // This function returns a pointer to the beginning of the next envelope "From " or (eof+1).
854     // See if we have a content length. If so, then will try it first.
855     //
856     long content_length;
857
858     error.clear();
859
860     _msg_end = _body_start;
861
862     DtMailValueSeq      value;
863     _envelope->getHeader(error, "content-length", DTM_FALSE, value);
864     if (error.isNotSet()) {
865         content_length = atol(*(value[0]));
866
867         // Look forward content_length amount and see if we are at
868         // the end (but not beyond) of file, or have a "\nFrom ".
869         // We must be careful here when using content_length as it *may*
870         // be a really large (bogus) number and the addition of that number
871         // to the start of the body *may* wrap around the top of the address
872         // space, thus resulting in a memory access violation.
873         //
874         const char * next_msg = _body_start + content_length;
875         const int contentLengthFudgeFactor = 30;        // "From" header min length
876
877         if (next_msg < _body_start)                     // message wrapped address space?
878           next_msg = eof+contentLengthFudgeFactor+1;    // yes: downsize it
879         
880
881         // A special case for content-length = 0. Some folders only
882         // have one new line in this case which will confuse us
883         // unless we do something special.
884         //
885         if (content_length == 0) {
886             next_msg -= 1;
887             if (next_msg <= eof && strncmp(next_msg, "\nFrom ", 6) == 0) {
888                 _msg_end = next_msg;
889                 return(next_msg + 1);
890             }
891         }
892
893         if (next_msg <= (eof - 6) && strncmp(next_msg, "\nFrom ", 6) == 0) {
894             // It worked!
895             _msg_end = backcrlf(next_msg);
896             return(next_msg + 1);
897         }
898
899         // Content length extends beyond the eof!
900         // Here it becomes tricky (see bug 1204026). We do not want to
901         // cause the partial receipt of a very large mail message to
902         // cause other valid mail messages to be "gobbled up" in this
903         // message; therefore, we apply a small fudge factor rule here.
904         // If the current end of message is within X bytes (about the
905         // size of a reasonable "From" header) then we take the current
906         // end to be fore this message; otherwise, err on the side of
907         // bursting this message into multiple smaller ones by doing
908         // the "From" scan dance.
909         //
910         if ( (next_msg > eof)
911           && (next_msg < eof+contentLengthFudgeFactor) ) {
912             _msg_end = eof;
913             return(next_msg);
914         }
915
916         // We need to deal with extraneous white space at the end of
917         // the file.
918         //
919         if (next_msg <= eof) {
920             const char *white;
921             for (white = next_msg; white <= eof && isspace((unsigned char)*white);
922                  white++) {
923                 continue;
924             }
925
926             if (white > eof) {
927                 _msg_end = backcrlf(next_msg);
928                 return(eof + 1);
929             }
930
931             // See if we are at a "\nFrom ". If so, consider it a
932             // case of off by one and just accept it. This will save
933             // us from scanning the entire message when the content-length
934             // simply didnt get trailing white space right.
935             //
936             if (strncmp(white - 1, "\nFrom ", 6) == 0) {
937                 _msg_end = white - 2;
938                 return(white);
939             }
940         }
941     }
942     else {
943         error.clear();
944     }
945
946     // If the message was sent with X-Lines, we can use it to help
947     // us find the end of the message.
948     //
949     value.clear();
950     _envelope->getHeader(error, "x-lines", DTM_FALSE, value);
951     if (error.isNotSet()) {
952         int xlines = (int) atol(*(value[0]));
953
954         int lcnt = 0;
955         for (_msg_end = _body_start; _msg_end <= eof; _msg_end++) {
956             if (*_msg_end == '\n') {
957                 lcnt += 1;
958                 if (lcnt == xlines) {
959                     break;
960                 }
961             }
962         }
963     }
964
965     if (strncmp(_msg_end, "\nFrom ", 6) != 0) {
966         // Well, looks like we have to do it the costly way. Scan the
967         // folder until we hit the end of file, or we hit a "From " at
968         // the start of a line.
969         //
970         for (_msg_end = _body_start - 1; _msg_end <= (eof - 6); _msg_end++) {
971             if (strncmp(_msg_end, "\nFrom ", 6) == 0) {
972                 break;
973             }
974         }
975     }
976
977     // The end is either the eof, or the from line.
978     //
979     const char * real_end = _msg_end + 1;
980     if (_msg_end > (eof - 6)) {
981         real_end = eof + 1;
982         _msg_end = eof;
983     }
984     else {
985         // Again, protect against NULL messages with 1 blank line
986         // before next message.  Size was < 0 and crashed in "memcpy".
987         if ( _msg_end >= _body_start )
988             _msg_end = backcrlf(_msg_end);
989     }
990
991     // Let's put a content length on this thing so we won't have to go
992     // through this silliness again!
993     //
994     content_length = _msg_end - _body_start + 1;
995     content_length = content_length < 0 ? 0 : content_length;
996
997     char buf[20];
998     sprintf(buf, "%lu", content_length);
999     _envelope->setHeader(error, "Content-Length", DTM_TRUE, buf);
1000
1001     return(real_end);
1002 }
1003
1004 // This is a list of all of the Sun V3 specific headers
1005 //
1006 static const char *SCANLIST[] = {
1007     "x-sun-charset",
1008     "x-sun-content-length",
1009     "x-sun-content-lines",
1010     "x-sun-data-description",
1011     "x-sun-data-file",
1012     "x-sun-data-name",
1013     "x-sun-data-type",
1014     "x-sun-encoding-info",
1015     "x-sun-reference-file",
1016     "x-sun-text-type",
1017     (const char *)0};
1018     
1019 void
1020 RFCMessage::parseBodies(DtMailEnv & error)
1021 {
1022     error.clear();
1023     
1024     // First we need to determine which type of message bodies we
1025     // have. There are actually 3 possibilities: single body part,
1026     // Sun V3, or MIME. The main difference in the last 2 is the
1027     // headers and delimiters used.
1028     //
1029     DtMailValueSeq      value;
1030     _envelope->getHeader(error, "Mime-Version", DTM_FALSE, value);
1031     if (error.isNotSet()) {
1032         parseMIMEBodies(error);
1033     }
1034     else {
1035         error.clear();
1036         
1037         // We need to try a common MIME header to see if maybe the
1038         // message is just poorly formed. We will examine the content
1039         // type to see if smells like a MIME type.
1040         //
1041         value.clear();
1042         _envelope->getHeader(error, "Content-Type", DTM_FALSE, value);
1043         if (error.isSet()) {
1044             // No content-type or Mime-Version header: treat as V3
1045             //
1046             error.clear();
1047             parseV3Bodies(error);
1048         }
1049         else {
1050             const char * content_type = *(value[0]);
1051             // Handle "Content-Type: text" problem with /usr/lib/mail.local:
1052             // Apparently /usr/lib/mail.local will insert a "Content-Type: text" 
1053             // header into received mail if no other Content-Type field is present.
1054             // The format of this header is consistent with RFC 1049 (March 1988)
1055             // which has since been superceeded by the MIME RFC 1521.
1056             //
1057             if (strpbrk(content_type, "/;")) {
1058                 // sub-types identify this as MIME formatted e-mail
1059                 //
1060                 parseMIMEBodies(error);
1061             }
1062             else if (strcasecmp(content_type, "text")==0) {
1063                 // Problem time - no Mime-Version is present yet there is a
1064                 // content-type: text which means it could either be a Sun V3 OR
1065                 // a very poorly formatted MIME message: see if any V3 message
1066                 // headers are found and if so treat as V3 else treat as MIME
1067                 //
1068                 const char **cp;
1069                 for (cp = SCANLIST; *cp; cp++) {
1070                     _envelope->getHeader(error, *cp, DTM_FALSE, value);
1071                     if (error.isNotSet())
1072                         break;
1073                     error.clear();
1074                 }
1075                 if (*cp) {
1076                     // Found v3 header - treat as such
1077                     //
1078                     parseV3Bodies(error);
1079                 }
1080                 else {
1081                     // No v3 headers found - treat as MIME
1082                     //
1083                     parseMIMEBodies(error);
1084                 }
1085             }
1086             else {
1087                 parseV3Bodies(error);
1088             }
1089         }
1090     }
1091     
1092     return;
1093 }
1094
1095 void
1096 RFCMessage::parseMIMEBodies(DtMailEnv & error)
1097 {
1098   error.clear();
1099   
1100   // MIME messages have many different forms, all of which we have to
1101   // take into account when parsing the bodies. If there is *no* MIME
1102   // Content-Type field, then we treat the entire body of the message
1103   // as a single body part of Content-Type: text/plain.
1104   //
1105   DtMailValueSeq        value;
1106   _envelope->getHeader(error, "Content-Type", DTM_FALSE, value);
1107   if (error.isSet()) {
1108     parseMIMETextPlain(error);
1109     return;
1110   }
1111
1112   // We have a MIME Content-Type field - handle the field appropriately
1113   const char * content_type = *(value[0]);
1114   if (strncasecmp(content_type, "multipart/", 10) == 0)
1115     parseMIMEMultipartSubtype(error, content_type+9);
1116   else if (strncasecmp(content_type, "message/", 8) == 0)
1117     parseMIMEMessageSubtype(error, content_type+7);
1118   else    // Unknown MIME Content-Type field - handle as text/plain.
1119     parseMIMETextPlain(error);
1120
1121   return;
1122 }
1123
1124 // parseMIMEMultipartSubtype -- parse a MIME Multipart content-type
1125 // Arguments:
1126 //  error --
1127 //  subtype     -- -> Multipart subtype string (begining with "/")
1128 // Outputs:
1129 //  Handle the parsing of the MIME Multipart content-type message according
1130 //  to the Multipart sub-type.
1131 // Returns:
1132 //  <<none>>
1133 // Description:
1134 //  Call this function to handle the parsing of a MIME Multipart subtype.
1135 //
1136 void
1137 RFCMessage::parseMIMEMultipartSubtype(DtMailEnv & error,
1138                                       const char * subtype)
1139 {
1140   // Have a multipart message to digest - determine type of multipart message
1141   //
1142   const char * boundary = (const char *) extractBoundary(subtype);
1143   if (!boundary) {
1144     // odd--there is no boundary in the multipart/ specification?
1145     // This is specifically required as per RFC 1521:
1146     //   The only mandatory parameter for the multipart Content-Type is
1147     //   the boundary parameter.
1148     // Treat as one big unbounded message message.
1149     //
1150     BodyPartCache * bpc = new BodyPartCache;
1151     bpc->body_start = _body_start;
1152     bpc->body = new MIMEBodyPart(error, this, _body_start, 
1153                                  _msg_end - _body_start + 1,
1154                                  (RFCEnvelope *)_envelope);
1155     
1156     _bp_cache.append(bpc);
1157     return;
1158   }
1159   
1160   if (strncasecmp(subtype, "/alternative", 12)==0)
1161     parseMIMEMultipartAlternative(error, boundary);
1162   else if (strncasecmp(subtype, "/mixed", 6)==0)
1163     parseMIMEMultipartMixed(error, boundary);
1164   else          // unknown types handled as Multipart/mixed
1165     parseMIMEMultipartMixed(error, boundary);
1166
1167   assert(boundary != NULL);
1168   free((void *)boundary);
1169   return;
1170 }
1171
1172 // parseMIMEMessageExternalBody -- parse a MIME Message/External-body message
1173 // Arguments:
1174 //  error --
1175 // Outputs:
1176 //  Process a MIME Message/External-body message
1177 // Returns:
1178 //  <<none>>
1179 // Description:
1180 //  Call this function to handle a MIME Message/External-body message
1181 //
1182 void
1183 RFCMessage::parseMIMEMessageExternalBody(DtMailEnv & error)
1184 {
1185   // Have a Message/External-Body message to digest - real dumb here,
1186   // include entire message including the headers as a single text/plain
1187   //
1188   
1189   BodyPartCache * bpc = new BodyPartCache;
1190   bpc->body_start = _msg_start;
1191   bpc->body = new MIMEBodyPart(error, this, _msg_start,
1192                                _msg_end - _msg_start + 1,
1193                                (RFCEnvelope *)_envelope);
1194   
1195   _bp_cache.append(bpc);
1196
1197   return;
1198 }
1199   
1200 // parseMIMEMessageSubtype -- parse a MIME Message content-type
1201 // Arguments:
1202 //  error --
1203 //  subtype     -- -> Message subtype string (begining with "/")
1204 // Outputs:
1205 //  Handle the parsing of the MIME Message content-type message according
1206 //  to the Message sub-type.
1207 // Returns:
1208 //  <<none>>
1209 // Description:
1210 //  Call this function to handle the parsing of a MIME Message subtype.
1211 //
1212 void
1213 RFCMessage::parseMIMEMessageSubtype(DtMailEnv & error,
1214                                       const char * subtype)
1215 {
1216   // Have a Message message to digest - determine type of Message message
1217   //
1218
1219   if (strncasecmp(subtype, "/external-body", 14) == 0)
1220     parseMIMEMessageExternalBody(error);
1221   else
1222     parseMIMETextPlain(error);  
1223   return;
1224 }
1225
1226 // parseMIMETextPlain -- parse a MIME Text/Plain message
1227 // Arguments:
1228 //  error --
1229 // Outputs:
1230 //  Construct single body part cache component encompassing the entire body
1231 //  of the message as defined by _body_start for (_msg_end-_body_start)+1 bytes
1232 // Returns:
1233 //  <<none>>
1234 // Description:
1235 //  Call this function to take the current message body and treat it as
1236 //  a single message of type text/plain.
1237 //
1238 void
1239 RFCMessage::parseMIMETextPlain(DtMailEnv & error)
1240 {
1241   error.clear();
1242   
1243   // This is a single body part of text/plain.
1244   //
1245   BodyPartCache * bpc = new BodyPartCache;
1246   bpc->body_start = _body_start;
1247   bpc->body = new MIMEBodyPart(error, this, _body_start, 
1248                                _msg_end - _body_start + 1,
1249                                (RFCEnvelope *)_envelope);
1250   
1251   _bp_cache.append(bpc);
1252   
1253   return;
1254 }
1255
1256 // parseMIMEMultipartAlternative -- parse a MIME multipart/alternative message
1257 // Arguments:
1258 //  error --
1259 //  boundary -- interpart boundary 
1260 // Outputs:
1261 //  updated message pointers, constructed body part cache, and
1262 //  constructed alternative message cache
1263 // Returns:
1264 //  <<none>>
1265 // Description:
1266 //  Given a multipart alternative MIME message, parse the message, creating
1267 //  a body part cache with an entry containing each body part, and ???GMG???
1268 //
1269 // Here is how a multipart alternative message will be parsed into memory,
1270 // where alternatives 1 and 2 can be displayed / processed on this system,
1271 // but alternative 3 can not.
1272 //
1273 //  +--------------------+ -> A
1274 //  | headers            |
1275 //  +--------------------+ -> B
1276 //  | interpart boundary |
1277 //  +--------------------+ -> C
1278 //  | alternative 1      |
1279 //  +--------------------+ -> D
1280 //  | interpart boundary |
1281 //  +--------------------+ -> E
1282 //  | alternative 2      |
1283 //  +--------------------+ -> F
1284 //  | interpart boundary |
1285 //  +--------------------+ -> G
1286 //  | alternative 3      |
1287 //  +--------------------+ -> H
1288 //  _alternativeMultipart == DTM_TRUE
1289 //  _alternativeMessage == DTM_FALSE
1290 //  _alternativeValid == DTM_FALSE
1291 //  _bp_cache = contents of alternative 2's bp_cache (because 1-2 valid, 3 not)
1292 //  _alt_msg_cache = entries for alternative 1, alternative 2, alternative 3
1293 //
1294 //  +----------------------+ -> C
1295 //  | headers              |
1296 //  +----------------------+
1297 //  | multipart boundary   |
1298 //  +----------------------+
1299 //  | alternative 1 part a |
1300 //  +----------------------+
1301 //  | multipart boundary   |
1302 //  +----------------------+
1303 //  | alternative 1 part b |
1304 //  +----------------------+ -> D
1305 //  _alternativeMultipart == DTM_FALSE
1306 //  _alternativeMessage == DTM_TRUE
1307 //  _alternativeValid == DTM_TRUE [this sample case can display this alt.]
1308 //  _bp_cache = entries for part a and part b
1309 //  _alt_msg_cache = empty
1310 //
1311 //  +----------------------+ -> E
1312 //  | headers              |
1313 //  +----------------------+
1314 //  | multipart boundary   |
1315 //  +----------------------+
1316 //  | alternative 2 part a |
1317 //  +----------------------+
1318 //  | multipart boundary   |
1319 //  +----------------------+
1320 //  | alternative 2 part b |
1321 //  +----------------------+ -> F
1322 //  _alternativeMultipart == DTM_FALSE
1323 //  _alternativeMessage == DTM_TRUE
1324 //  _alternativeValid == DTM_TRUE [this sample case can display this alt.]
1325 //  _bp_cache = entries for part a and part b
1326 //  _alt_msg_cache = empty
1327 //
1328 //  +----------------------+ -> G
1329 //  | headers              |
1330 //  +----------------------+
1331 //  | multipart boundary   |
1332 //  +----------------------+
1333 //  | alternative 3 part a |
1334 //  +----------------------+
1335 //  | multipart boundary   |
1336 //  +----------------------+
1337 //  | alternative 3 part b |
1338 //  +----------------------+ -> H
1339 //  _alternativeMultipart == DTM_FALSE
1340 //  _alternativeMessage == DTM_TRUE
1341 //  _alternativeValid == DTM_FALSE [this sample case can NOT display this alt.]
1342 //  _bp_cache = entries for part a and part b
1343 //  _alt_msg_cache = empty
1344 //
1345 void
1346 RFCMessage::parseMIMEMultipartAlternative(DtMailEnv & error, const char * boundary)
1347 {
1348   assert(_alternativeMessage == DTM_FALSE);
1349   assert(boundary != NULL);
1350
1351   // Chew through anything that appears before the first boundary.
1352   //
1353   const char * body = _body_start;
1354   const char * body_end = _msg_end;
1355   
1356   int bndry_len = strlen(boundary);
1357
1358   for (; body <= _msg_end; body++) {
1359     if (*body == '-' &&
1360         *(body + 1) == '-' &&
1361         strncmp(body + 2, boundary, bndry_len) == 0) {
1362       break;
1363     }
1364   }
1365
1366   if (body > _msg_end ||
1367       *(body + bndry_len + 2) == '-') {
1368     // No boundaries. Treat as one big message.
1369     //
1370     BodyPartCache * bpc = new BodyPartCache;
1371     bpc->body_start = _body_start;
1372     bpc->body = new MIMEBodyPart(error, this, _body_start, 
1373                                  _msg_end - _body_start + 1,
1374                                  (RFCEnvelope *)_envelope);
1375     
1376     _bp_cache.append(bpc);
1377     return;
1378   }
1379
1380   // We are sitting at the beginning of the first boundary
1381   // Construct an alternative message cache for the various alternative bodies
1382   // On input to this loop,
1383   //   body -> first byte of body part including boundary
1384   //   body_end -> end of message (_msg_end)
1385   // On each iteration through the loop,
1386   //   - MIMEBodyPart is called with a body boundary of [body]...[body_end]
1387   //   - MIMEBodyPart alters body_end to be the last byte of the message,
1388   //            containing the boundary *only if* it is the last body part
1389   //
1390   do {
1391     AlternativeMessageCache * amc = new AlternativeMessageCache;
1392     amc->amc_body_start = body;
1393     amc->amc_body_end = body_end;
1394     amc->amc_body = new MIMEBodyPart(error, this, body, &amc->amc_body_end, boundary);
1395     body = amc->amc_body_end;
1396     if ( (amc->amc_body_end == (body_end+1))
1397          && (strncmp(amc->amc_body_end-(bndry_len+3), boundary, bndry_len) == 0) ){
1398       amc->amc_body_end -= bndry_len+5;
1399     }
1400     amc->amc_msg = new RFCMessage(error, amc->amc_body_start, amc->amc_body_end);
1401     _alt_msg_cache.append(amc);
1402   } while (body <= _msg_end);
1403
1404   // Now we *should* go through the entire alternative message cache validating
1405   // each message to see if it can be displayed on this system. This is a task
1406   // left for a future exercise. For now, use the first alternative always.
1407   //
1408   AlternativeMessageCache *amc = _alt_msg_cache[_alt_msg_cache.length()-1];
1409   int slotMax = amc->amc_msg->_bp_cache.length();
1410   for (int slot = 0; slot < slotMax; slot++) {
1411     BodyPartCache * p_bpc = new BodyPartCache;
1412     BodyPartCache * c_bpc = amc->amc_msg->_bp_cache[slot];
1413     p_bpc->body = c_bpc->body;
1414     p_bpc->body_start = c_bpc->body_start;
1415     _bp_cache.append(p_bpc);
1416   }
1417
1418   _alternativeMultipart = DTM_TRUE;     // main msg of multipart alternative
1419 }
1420
1421 // parseMIMEMultipartMixed -- parse a MIME multipart/mixed message
1422 // Arguments:
1423 //  error --
1424 //  boundary -- bodypart boundary
1425 // Outputs:
1426 //  updated message pointers and constructed body part cache
1427 // Returns:
1428 //  <<none>>
1429 // Description:
1430 //  Given a multipart mixed MIME message, parse the message, creating a body
1431 //  part cache with an entry containing each body part.
1432 //
1433 void
1434 RFCMessage::parseMIMEMultipartMixed(DtMailEnv & error, const char * boundary)
1435 {
1436   assert(boundary != NULL);
1437   
1438   // Chew through anything that appears before the first boundary.
1439   //
1440   const char * body = _body_start;
1441   const char * body_end = _msg_end;
1442   
1443   int bndry_len = strlen(boundary);
1444
1445   for (; body <= _msg_end; body++) {
1446     if (*body == '-' &&
1447         *(body + 1) == '-' &&
1448         strncmp(body + 2, boundary, bndry_len) == 0) {
1449       break;
1450     }
1451   }
1452
1453   if (body > _msg_end ||
1454       *(body + bndry_len + 2) == '-') {
1455     // No boundaries. Treat as one big message.
1456     //
1457     BodyPartCache * bpc = new BodyPartCache;
1458     bpc->body_start = _body_start;
1459     bpc->body = new MIMEBodyPart(error, this, _body_start, 
1460                                  _msg_end - _body_start + 1,
1461                                  (RFCEnvelope *)_envelope);
1462     
1463     _bp_cache.append(bpc);
1464     return;
1465   }
1466
1467   // We are sitting at the beginning of the first boundary
1468   // Construct a body part cache from the various body parts
1469   // On input to this loop,
1470   //   body -> first byte of body part (including boundary)
1471   //   body_end -> end of message (_msg_end)
1472   // On each iteration through the loop,
1473   //   - MIMEBodyPart is called with a body boundary of [body]...[body_end]
1474   //   - MIMEBodyPart alters body_end to be the last byte of the message,
1475   //            containing the boundary *only if* it is the last body part
1476   //
1477   do {
1478     BodyPartCache * bpc = new BodyPartCache;
1479     bpc->body_start = body;
1480     bpc->body = new MIMEBodyPart(error, this, body, &body_end, boundary);
1481     _bp_cache.append(bpc);
1482     
1483     body = body_end;
1484     body_end = _msg_end;
1485     
1486   } while (body <= _msg_end);
1487   return;
1488 }
1489
1490 void
1491 RFCMessage::parseV3Bodies(DtMailEnv & error)
1492 {
1493   error.clear();
1494
1495     // We have 3 choices here. We may have no content-type field,
1496     // in which case it is a simple RFC822 message. We handle those
1497     // in the V3 body code because this is really the legacy branch.
1498     //
1499     // We can have a content-type = text which is a single body part
1500     // of either 7 bit text, or 8 bit unencoded text.
1501     //
1502     // Finally we could have a Sun V3 multipart document.
1503     //
1504     DtMailValueSeq      value;
1505     _envelope->getHeader(error, "Content-Type", DTM_FALSE, value);
1506     if (error.isSet()) {
1507         // Pretty simple. Pass the entire body and the envelope to
1508         // the V3 body constructor.
1509         //
1510         error.clear();
1511         BodyPartCache * bpc = new BodyPartCache;
1512         bpc->body_start = _body_start;
1513         bpc->body = new V3BodyPart(error, this, _body_start, 
1514                                _msg_end - _body_start + 1,
1515                                (RFCEnvelope *)_envelope);
1516
1517         _bp_cache.append(bpc);
1518
1519         return;
1520     }
1521
1522     const char * content_type = *(value[0]);
1523
1524     // If the type is text, or, if it isn't an attachment type we
1525     // understand treat the body as a single part.
1526     //
1527     if (strcasecmp(content_type, "text") == 0 ||
1528         strcasecmp(content_type, "x-sun-attachment") != 0) {
1529         BodyPartCache * bpc = new BodyPartCache;
1530         bpc->body_start = _body_start;
1531         bpc->body = new V3BodyPart(error, this, _body_start,
1532                                _msg_end - _body_start + 1,
1533                                (RFCEnvelope *)_envelope);
1534
1535         _bp_cache.append(bpc);
1536     }
1537     else {
1538         // We need to scan for each of the message boundaries and
1539         // let the body part object parse the important stuff.
1540         //
1541         // V3 bodies start with a sequence of 10 dashes. After that,
1542         // there *should* be a newline.  After such a sequence, 
1543         // the body part constructor will give us the body end.
1544         //
1545         const char *body;
1546         for (body = _body_start - 1; body <= _msg_end; body++) {
1547             if (*body == '\n' && 
1548                 strncmp(body + 1, "----------", 10) == 0 &&
1549                 (*(body + 11) == '\n' || 
1550                  *(body + 11) == '\r' && *(body + 12) == '\n')) {
1551                 break;
1552             }
1553         }
1554         if (body > _msg_end) {
1555             // Well, we have some kind of inconsistency here. Let's
1556             // treat it as one big body part so we can display something.
1557             //
1558             BodyPartCache * bpc = new BodyPartCache;
1559             bpc->body_start = _body_start;
1560             bpc->body = new V3BodyPart(error, this, _body_start,
1561                                    _msg_end - _body_start + 1,
1562                                    (RFCEnvelope *)_envelope);
1563             
1564             _bp_cache.append(bpc);
1565             return;
1566         }
1567
1568         body += 1;
1569
1570         const char * body_end = _msg_end;
1571
1572         do {
1573             BodyPartCache * bpc = new BodyPartCache;
1574             bpc->body_start = body;
1575             bpc->body = new V3BodyPart(error, this, body, &body_end);
1576             _bp_cache.append(bpc);
1577             body = body_end;
1578             body_end = _msg_end;
1579         } while (body <= _msg_end);
1580     }
1581
1582     return;
1583 }
1584
1585 int
1586 RFCMessage::sizeMIMEBodies(DtMailEnv &)
1587 {
1588   // We will use _msg_end - _body_start because this is
1589   // both the correct body length, and the one we updated.
1590   //
1591   return(_msg_end - _body_start + 1);
1592 }
1593
1594 int
1595 RFCMessage::sizeV3Bodies(DtMailEnv &)
1596 {
1597   // We will use _msg_end - _body_start because this is
1598   // both the correct body length, and the one we updated.
1599   //
1600   return(_msg_end - _body_start + 1);
1601 }
1602
1603 char *
1604 RFCMessage::extractBoundary(const char * content_type)
1605 {
1606     const char * boundary;
1607
1608     if (!content_type) {
1609         char * new_bdry = (char *)malloc(78);
1610         sprintf(new_bdry, "%p-%08lx-%p", new_bdry, (long)time(NULL), &new_bdry);
1611     }
1612
1613     // We will need the boundary to find the message boundaries.
1614     //
1615     for (boundary = content_type; *boundary; boundary++) {
1616         if (strncasecmp(boundary, "boundary=", 9) == 0) {
1617             break;
1618         }
1619     }
1620     if (!*boundary) {
1621         return(NULL);
1622     }
1623     
1624     // Get past uninteresting cruft.
1625     //
1626     boundary += 9;
1627     int quoted = 0;
1628     if (*boundary == '"') {
1629         boundary += 1;
1630         quoted = 1;
1631     }
1632     
1633     // Find the end of the boundary. This will be either the end of
1634     // the line, a quote, or a semi-colon.
1635     //
1636     const char *b_end;
1637     for (b_end = boundary; *b_end; b_end++) {
1638         if (quoted) {
1639             if (*b_end == '"') {
1640                 break;
1641             }
1642         }
1643         else {
1644             if (*b_end == ';' || *b_end == '\r' || *b_end == '\n') {
1645                 break;
1646             }
1647         }
1648     }
1649
1650     int len = b_end - boundary + 1;
1651     char * result = (char *)malloc(len);
1652     strncpy(result, boundary, len - 1);
1653     result[len - 1] = 0;
1654
1655     return(result);
1656 }
1657
1658 DtMailBoolean
1659 RFCMessage::hasHeaders(const char * buf, const unsigned long len)
1660 {
1661     const char * start = buf;
1662
1663     if (len > 5 && strncmp(buf, "From ", 5) == 0) {
1664         // Get past this line. Unix from line. It doesnt count as
1665         // a header for our purposes.
1666         //
1667         while (start < (buf + len) && *start != '\n') {
1668             start++;
1669         }
1670
1671         start += 1;
1672
1673         if (start >= (buf + len)) {
1674             return(DTM_FALSE);
1675         }
1676     }
1677
1678     // At this point we should have something that looks like a header.
1679     // This will be a string with no spaces, terminated with a colon,
1680     // followed by some text.
1681     //
1682     const char *colon;
1683     for (colon = start; colon < (buf + len) && *colon != ':'; colon++) {
1684         continue;
1685     }
1686
1687     if (colon >= (buf + len) || *colon != ':') {
1688         return(DTM_FALSE);
1689     }
1690
1691     // RFC822 states:
1692     // Once a field has been unfolded, it may be viewed as being com-
1693     // posed of a field-name followed by a colon (":"), followed by a
1694     // field-body, and  terminated  by  a  carriage-return/line-feed.
1695     // The  field-name must be composed of printable ASCII characters
1696     // (i.e., characters that  have  values  between  33.  and  126.,
1697     // decimal, except colon).
1698     //
1699     // We should only find characters between 33 and 126 from the start
1700     // to the colon. Any exception means this is not a valid field-name,
1701     // and therefore, not a valid RFC822 header.
1702     //
1703     for (const char * check = start; check < colon; check++) {
1704         if (*check < 33 || *check > 126) {
1705             return(DTM_FALSE);
1706         }
1707     }
1708
1709     // We passed the criteria, so this must be a header.
1710     //
1711     return(DTM_TRUE);
1712 }