/*
**  Message.m
**
**  Copyright (c) 2001, 2002, 2003
**
**  Author: Ludovic Marcotte <ludovic@Sophos.ca>
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**  
**  This library is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**  Lesser General Public License for more details.
**  
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

#include <Pantomime/Message.h>

#include <Pantomime/Constants.h>
#include <Pantomime/Flags.h>
#include <Pantomime/Folder.h>
#include <Pantomime/InternetAddress.h>
#include <Pantomime/MimeMultipart.h>
#include <Pantomime/MimeUtility.h>
#include <Pantomime/NSRegEx.h>
#include <Pantomime/NSString+Extensions.h>
#include <Pantomime/NSData+Extensions.h>
#include <Pantomime/Parser.h>

#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSBundle.h>
#include <Foundation/NSEnumerator.h>
#include <Foundation/NSTimeZone.h>
#include <Foundation/NSUserDefaults.h>

#include <stdlib.h>
#include <string.h>
#include <time.h>

static int currentMessageVersion = 2;

// regexes used in _computeBaseSubjet
static NSRegEx *atLeastOneSpaceRegex;
static NSRegEx *suffixSubjTrailerRegex;
static NSRegEx *prefixSubjLeaderRegex;
static NSRegEx *prefixSubjBlobRegex;
static NSRegEx *prefixSubjFwdHdrAndSuffixSubjFwdTrlRegex;

@implementation Message

//
// We setup regexes used in _computeBaseSubject
//
+ (void) initialize
{
  NSString *blobChar = @"[^][]";
  NSString *subjTrailer = @"(\\(fwd\\))| ";
  NSString *subjFwdHdr = @"\\[fwd:";
  NSString *subjFwdTrl = @"\\]";
  NSString *subjBlob = [NSString stringWithFormat:@"\\[(%@)*\\] *", blobChar];
  NSString *subjReFwd = [NSString stringWithFormat:@"((re)|(fwd?)) *(%@)?:", subjBlob];
  NSString *subjLeader = [NSString stringWithFormat:@"((%@)*%@)| ", subjBlob, subjReFwd];
  atLeastOneSpaceRegex = [[NSRegEx alloc] initWithPattern: @"[[:space:]]+"
					  flags: REG_EXTENDED|REG_ICASE];
  suffixSubjTrailerRegex = [[NSRegEx alloc] initWithPattern: [NSString stringWithFormat:@"(%@)*$", subjTrailer]
					    flags: REG_EXTENDED|REG_ICASE];
  prefixSubjLeaderRegex = [[NSRegEx alloc] initWithPattern: [NSString stringWithFormat:@"^(%@)", subjLeader]
					   flags: REG_EXTENDED|REG_ICASE];
  prefixSubjBlobRegex = [[NSRegEx alloc] initWithPattern: [NSString stringWithFormat:@"^(%@)", subjBlob]
					 flags: REG_EXTENDED|REG_ICASE];
  prefixSubjFwdHdrAndSuffixSubjFwdTrlRegex = [[NSRegEx alloc] initWithPattern: [NSString stringWithFormat:@"^(%@)(.*)(%@)$", subjFwdHdr, subjFwdTrl]
							      flags: REG_EXTENDED|REG_ICASE];
}


//
//
//
- (id) init
{
  self = [super init];

  [Message setVersion: currentMessageVersion];
  
  // We initialize our recipiens array
  recipients = [[NSMutableArray alloc] init];

  // We initialize our dictionary that will hold all our headers
  // We initialize it with a capacity of 25. This is a empirical number 
  // that is used to speedup the addition of headers w/o reallocating
  // our array everytime we add a new element.
  headers = [[NSMutableDictionary alloc] initWithCapacity: 25];
  
  // We initialize our headers with some basic values in case they are missing in the messages we decode
  [headers setObject: @"text/plain"  forKey: @"Content-Type"];

  flags = [[Flags alloc] init];

  // By default, we want the subclass's rawSource method to be called so we set our
  // rawSource ivar to nil. If it's not nil (ONLY set in initWithData) it'll be returned,
  // for performances improvements.
  rawSource = nil;

  // We initialize our dictionary holding all extra properties a message might have
  properties = [[NSMutableDictionary alloc] init];
  
  // Initially, we don't have any references
  [self setReferences: nil];
  
  [self setFolder: nil];
  [self setInitialized: NO];
  [self setSize: 0];

  return self;
}


//
//
//
- (id) initWithData: (NSData *) theData
{
  self = [self init];
  self = [super initWithData: theData];

  // Part: -initWithData could fail. We return nil if it does.
  if ( !self )
    {
      return nil;
    }

  //
  // We can tell now that this message is fully initialized
  // NOTE: We must NOT call [self setInitialize: YES] since
  //       it will call the method from the subclass and may do
  //       extremely weird things.
  initialized = YES;

  // We set our rawSource ivar for performances reason
  [self setRawSource: theData];

  return self;
}


//
//
//
- (id) initWithData: (NSData *) theData
	    charset: (NSString *) theCharset
{
  [self setDefaultCharset: theCharset];

  return [self initWithData: theData];
}


//
//
//
- (id) initWithHeadersFromData: (NSData *) theHeaders
{
  self = [self init];

  [self setHeadersFromData: theHeaders];

  return self;
}


//
//
//
- (id) initWithHeaders: (NSDictionary *) theHeaders
{
  self = [self init];
  
  [self setHeaders: theHeaders];
  
  return self;
}


//
//
//
- (void) dealloc
{
  //NSDebugLog(@"Message: -dealloc");

  RELEASE(recipients);
  RELEASE(headers);
  RELEASE(flags);

  RELEASE(properties);
  
  RELEASE(references);

  TEST_RELEASE(rawSource);
  
  [super dealloc];
}


//
// NSCoding protocol
//
- (void) encodeWithCoder: (NSCoder *) theCoder
{
  // Must also encode Part's superclass
  [super encodeWithCoder: theCoder];

  [Message setVersion: currentMessageVersion];

  [theCoder encodeObject: [self receivedDate]];                              // Date
  [theCoder encodeObject: [self from]];                                      // From
  [theCoder encodeObject: [self recipients]];                                // To and Cc (Bcc, at worst)
  [theCoder encodeObject: [self subject]];                                   // Subject
  [theCoder encodeObject: [self messageID]];                                 // Message-ID
  [theCoder encodeObject: [self mimeVersion]];                               // MIME-Version
  [theCoder encodeObject: [self allReferences]];                             // References
  [theCoder encodeObject: [self inReplyTo]];                                 // In-Reply-To
  
  [theCoder encodeObject: [NSNumber numberWithInt: [self messageNumber]]];   // Message number
  [theCoder encodeObject: [self flags]];                                     // Message flags
}


//
//
//
- (id) initWithCoder: (NSCoder *) theCoder
{
  int version;
    
  // Must also decode Part's superclass
  self = [super initWithCoder: theCoder];

  version = [theCoder versionForClassName: @"Message"];

  // We initialize our dictionary holding all extra properties a message might have
  properties = [[NSMutableDictionary alloc] init];
  
  // We do the same thing for our headers dictionary
  headers = [[NSMutableDictionary alloc] initWithCapacity: 25];
  
  [self setReceivedDate: [theCoder decodeObject]];              // Date
  [self setFrom: [theCoder decodeObject]];                      // From
  [self setRecipients: [theCoder decodeObject]];                // To and Cc (Bcc, at worst)
  [self setSubject: [theCoder decodeObject]];                   // Subject
  [self setMessageID: [theCoder decodeObject]];                 // Message-ID
  [self setMimeVersion: [theCoder decodeObject]];               // MIME-Version
  [self setReferences: [theCoder decodeObject]];                // References
      
  [self setInReplyTo: [theCoder decodeObject]];                 // In-Reply-To
  [self setMessageNumber: [[theCoder decodeObject] intValue]];  // Message number
  
  // We decode our flags. We must not simply call [self setFlags: [theCoder decodeObject]]
  // since IMAPMessage is re-implementing flags and that would cause problems when
  // unarchiving IMAP caches.
  flags = [[Flags alloc] init];
  [flags replaceWithFlags: [theCoder decodeObject]];
  
  // It's very important to set the "initialized" ivar to NO since we didn't serialize the content.
  // or our message.
  initialized = NO;
  
  // We initialize the rest of our ivars
  folder = nil;
  rawSource = nil;

  return self;
}


//
// NSCopying protocol (FIXME)
//
- (id) copyWithZone: (NSZone *) zone
{
  return RETAIN(self);
}


//
//
//
- (InternetAddress *) from
{
  return [headers objectForKey: @"From"];
}


//
//
//
- (void) setFrom: (InternetAddress *) theInternetAddress
{
  if ( theInternetAddress )
    {
      [headers setObject: theInternetAddress
	   forKey: @"From"];
    }
}


//
//
//
- (int) messageNumber
{
  return messageNumber;
}


//
//
//
- (void) setMessageNumber: (int) theMessageNumber
{
  messageNumber = theMessageNumber;
}


//
//
//
- (NSString *) messageID
{
  return [headers objectForKey: @"Message-ID"];
}


//
//
//
- (void) setMessageID: (NSString *) theMessageID
{
  if ( theMessageID )
    {
      [headers setObject: theMessageID
	       forKey: @"Message-ID"];
    }
}


//
//
//
- (NSString *) inReplyTo
{
  return [headers objectForKey: @"In-Reply-To"];
}


//
//
//
- (void) setInReplyTo: (NSString *) theInReplyTo
{
  if ( theInReplyTo )
    {
      [headers setObject: theInReplyTo
	       forKey: @"In-Reply-To"];
    }
}


//
//
//
- (NSCalendarDate *) receivedDate
{
  return [headers objectForKey: @"Date"];
}


//
//
//
- (void) setReceivedDate: (NSCalendarDate*) theDate
{
  if ( theDate )
    {
      [headers setObject: theDate 
	       forKey: @"Date"];
    }
}


//
//
//
- (void) addToRecipients: (InternetAddress *) theAddress
{
  if ( theAddress )
    {
      [recipients addObject: theAddress];
    }
}


//
//
//
- (void) removeFromRecipients: (InternetAddress *) theAddress
{
  if ( theAddress )
    {
      [recipients removeObject: theAddress];
    }
}


//
//
//
- (NSArray *) recipients
{
  return recipients;
}


//
//
//
- (void) setRecipients: (NSArray *) theRecipients
{
  if ( theRecipients )
    {
      NSMutableArray *aMutableArray;
      
      aMutableArray = [NSMutableArray arrayWithArray: theRecipients];
      RELEASE(recipients);
      RETAIN(aMutableArray);
      recipients = aMutableArray;
    }
  else
    {
      DESTROY(recipients);
    }
}


//
//
//
- (int) recipientsCount
{
  return [[self recipients] count];
}


//
//
//
- (void) removeAllRecipients
{
  [recipients removeAllObjects];
}


//
//
//
- (InternetAddress *) replyTo
{
  return [headers objectForKey: @"Reply-To"];
}


//
//
//
- (void) setReplyTo: (InternetAddress *) theInternetAddress
{
  if ( theInternetAddress )
    {
      [headers setObject: theInternetAddress
	       forKey: @"Reply-To"];
    }
  else
    {
      [headers removeObjectForKey: @"Reply-To"];
    }
}


//
//
//
- (NSString *) subject
{
    return [headers objectForKey: @"Subject"];
}


//
//
//
- (void) setSubject: (NSString *) theSubject
{
  if ( theSubject )
    {
      [headers setObject: theSubject
	       forKey: @"Subject"];
      
      // We invalidate our previous base subject.
      [self setBaseSubject: nil];
    }
}


//
//
//
- (NSString *) baseSubject
{
  NSString *baseSubject;
  
  baseSubject = [self propertyForKey:@"baseSubject"];
  
  if ( !baseSubject )
    {
      baseSubject = [self _computeBaseSubject];
      [self setBaseSubject: baseSubject];
    }
  
  return baseSubject;
}


//
//
//
- (void) setBaseSubject: (NSString *) theBaseSubject
{
  [self setProperty: theBaseSubject
	forKey: @"baseSubject"];
}


//
//
//
- (BOOL) isInitialized
{
  return initialized;
}


//
//
//
- (void) setInitialized: (BOOL) aBOOL
{
  initialized = aBOOL;
}


//
// Implementation of the Part protocol follows...
//


- (NSString *) contentDisposition
{
  return [headers objectForKey: @"Content-Disposition"];
}

- (void) setContentDisposition: (NSString *) theContentDisposition
{
  [super setContentDisposition: theContentDisposition];
  
  [headers setObject: theContentDisposition
	   forKey: @"Content-Disposition"];
}

//
//
//
- (NSString *) contentID
{
  return [headers objectForKey: @"Content-Id"];
}        


//
//
//
- (void) setContentID: (NSString *) theContentID
{ 
  [super setContentID: theContentID];
  [headers setObject: theContentID
	   forKey: @"Content-Id"];
}  


//
//
//
- (NSString *) contentType
{
  return [headers objectForKey: @"Content-Type"];
}


//
//
//
- (void) setContentType: (NSString*) theContentType
{
  [super setContentType: theContentType];
  
  [headers setObject: theContentType
	   forKey: @"Content-Type"];
}


//
//
//
- (NSString *) organization
{
  return [headers objectForKey: @"Organization"];
}


//
//
//
- (void) setOrganization: (NSString *) theOrganization
{
  [headers setObject: theOrganization
	   forKey: @"Organization"];
}


//
//
//
- (id) propertyForKey: (id) theKey
{
  return [properties objectForKey: theKey];
}


//
//
//
- (void) setProperty: (id) theProperty
	      forKey: (id) theKey
{
  if ( theProperty )
    {
      [properties setObject: theProperty
		  forKey: theKey];
    }
  else
    {
      [properties removeObjectForKey: theKey];
    }
}


//
//
//
- (NSArray *) allReferences
{
  return references;
}


//
//
//
- (void) setReferences: (NSArray *) theReferences
{
  if ( theReferences )
    {
      RETAIN(theReferences);
      RELEASE(references);
      references = theReferences;
    }
  else
    {
      DESTROY(references);
    }
}


//
//
//
- (Flags *) flags
{
  return flags;
}


//
//
//
- (void) setFlags: (Flags*) theFlags
{
  if ( theFlags )
    {
      RETAIN(theFlags);
      RELEASE(flags);
      flags = theFlags;
    }
  else
    {
      DESTROY(flags);
    }
}


//
//
//
- (NSString *) mimeVersion
{
  return [headers objectForKey: @"MIME-Version"];
}


//
//
//
- (void) setMimeVersion: (NSString *) theMimeVersion
{
  if ( theMimeVersion )
    {
      [headers setObject: theMimeVersion
	       forKey: @"MIME-Version"];
    }
}


//
// The message will NOT have a content other than the PLAIN/TEXT one(s).
//
- (Message *) replyWithReplyToAll: (BOOL) flag
{
  InternetAddress *anInternetAddress;
  Message *theMessage;

  NSMutableString *aMutableString;
  id aContent;
  int i;

  BOOL needsToQuote = NO;
  
  theMessage = [[Message alloc] init];

  // We set the subject of our message 
  if ( ![self subject] )
    {
      [theMessage setSubject: @"Re: your mail"];
    }
  else if ( [[[self subject] stringByTrimmingWhiteSpaces] hasREPrefix] )
    {
      [theMessage setSubject: [self subject] ];
    }
  else 
    {
      [theMessage setSubject: [NSString stringWithFormat: @"Re: %@", [[self subject] stringByTrimmingWhiteSpaces]] ];
    }

  // If Reply-To is defined, we use it. Otherwise, we use From:
  if ( [self replyTo] == nil )
    {
      anInternetAddress = [self from];
    }
  else 
    {
      anInternetAddress = [self replyTo];
    }

  [anInternetAddress setType: TO];
  [theMessage addToRecipients: anInternetAddress];
  
  // We add our In-Reply-To header
  if ( [self messageID] )
    {
      [theMessage setInReplyTo: [self messageID]];
    }
  
  // If we reply to all, we add the other recipients
  if ( flag )
    {
      NSEnumerator *anEnumerator;

      anEnumerator = [[self recipients] objectEnumerator];
      
      while ((anInternetAddress = [anEnumerator nextObject]))
	{
	  [anInternetAddress setType: CC];
	  [theMessage addToRecipients: anInternetAddress];
	}
    }

  // We finally work on the content of the message
  aMutableString = [[NSMutableString alloc] init];

  // We verify if we have a Date value. We might receive messages w/o this field
  // (Yes, I know, it's borken but it's happening).
  if ( [self receivedDate] )
    {
      [aMutableString appendString: @"On "];
      [aMutableString appendString: [[self receivedDate] description] ];
      [aMutableString appendString: @" "];
    }
  
  [aMutableString appendString: [[self from] unicodeStringValue]];
  [aMutableString appendString: @" wrote:\n\n"];

  // We give a default value to aContent
  aContent = nil;
  
  //
  // We now get the right text part of the message.
  // 
  //
  if ( [self isMimeType: @"text": @"*"] )
    {
      aContent = [MimeUtility plainTextContentFromPart: self];
      needsToQuote = YES;
    }
  //
  // If our message only contains the following part types, we cannot
  // represent those in a reply.
  // 
  else if ( [self isMimeType: @"application": @"*"] ||
	    [self isMimeType: @"audio": @"*"] ||
	    [self isMimeType: @"image": @"*"] || 
	    [self isMimeType: @"message": @"*"] ||
	    [self isMimeType: @"video": @"*"] )
    {
      aContent = [NSString stringWithString: @"\t[NON-Text Body part not included]"];
      needsToQuote = NO;
    }
  //
  // We have a multipart type. It can be:
  //
  // multipart/appledouble, multipart/alternative, multipart/related,
  // multipart/mixed or even multipart/report.
  //
  // We must search for a text part to use in our reply.
  //
  else if ( [self isMimeType: @"multipart" : @"*"] )
    {
      MimeMultipart *aMimeMultipart;
      Part *aPart;
      
      aMimeMultipart = (MimeMultipart *)[self content];
      aContent = [[NSMutableString alloc] init];

      for (i = 0; i < [aMimeMultipart count]; i++)
	{
	  aPart = [aMimeMultipart bodyPartAtIndex: i];
	  
	  //
	  // We do a full verification on the Content-Type since we might
	  // have a text/x-{something} like text/x-vcard.
	  //
	  if ( [aPart isMimeType: @"text" : @"plain"] ||
	       [aPart isMimeType: @"text" : @"enriched"] ||
	       [aPart isMimeType: @"text" : @"html"] )
	    {
	      [aContent appendString: [MimeUtility plainTextContentFromPart: aPart]];
	      
	      // If our original Content-Type is multipart/alternative, no need to
	      // consider to the other text/* parts. Otherwise, we just append 
	      // all text/* parts.
	      if ( [self isMimeType: @"multipart" : @"alternative"] )
		{
		  break;
		}
	    }
	  //
	  // If we have a multipart/alternative contained in our multipart/mixed (or related)
	  // we find the text/plain part contained in this multipart/alternative object.
	  //
	  else if ( [aPart isMimeType: @"multipart" : @"alternative"] )
	    {
	      MimeMultipart *anOtherMimeMultipart;
	      NSString *aString;
	      int j;
	      
	      anOtherMimeMultipart = (MimeMultipart *)[aPart content];
	      aString = nil;

	      for (j = 0; j < [anOtherMimeMultipart count]; j++)
		{
		  aPart = [anOtherMimeMultipart bodyPartAtIndex: j];
		  
		  if ( [aPart isMimeType: @"text" : @"plain"] ||
		       [aPart isMimeType: @"text" : @"enriched"] ||
		       [aPart isMimeType: @"text" : @"html"] )
		    {
		      aString = [MimeUtility plainTextContentFromPart: aPart];
		      break;
		    } 
		}

	      if ( aString )
		{
		  [aContent appendString: [MimeUtility plainTextContentFromPart: aPart]];
		}
	    } // else if (...)
	} // for ( ... )

      if ( ![aContent length] )
	{
	  DESTROY(aContent);
	}
      else
	{
	  AUTORELEASE(aContent);
	}
      
      needsToQuote = YES;
    }
  
  //
  // It was impossible for use to find a text/plain part. Let's
  // inform our user that we can't do anything with this message.
  //
  if ( !aContent || [aContent isKindOfClass: [NSData class]])
    {
      aContent = [NSString stringWithString: @"\t[NON-Text Body part not included]"];
      needsToQuote = NO;
    }
  else
    {
      // We remove the signature
      NSRange aRange;
      
      aRange = [aContent rangeOfString: @"\n-- "
			 options: NSBackwardsSearch];
      
      // We found it!
      if ( aRange.length )
	{
	  aContent = [aContent substringToIndex: aRange.location];
	}
    }

  // We now have our content as string, let's 'quote' it
  if ( aContent && needsToQuote )
    {
      aContent = [MimeUtility unwrapPlainTextString: aContent
			      usingQuoteWrappingLimit: 78];
      [aMutableString appendString: [MimeUtility quotePlainTextString: aContent
						 quoteLevel: 1
						 wrappingLimit: 80]];
    }
  else if (aContent && !needsToQuote)
    {
      [aMutableString appendString: aContent];
    }
  
  [theMessage setContent: aMutableString];

  RELEASE(aMutableString);
  
  return AUTORELEASE(theMessage);
}


//
// The message WILL have a content.
//
- (Message *) forward
{
  NSMutableString *aMutableString;
  Message *theMessage;
 
  theMessage = [[Message alloc] init];
  
  // We set the subject of our message
  if ( [self subject] )
    {
      [theMessage setSubject: [NSString stringWithFormat: @"%@ (fwd)", [self subject]] ];
    }
  else
    {
      [theMessage setSubject: @"Forwarded mail..."];
    }

  // We create our generic forward message header
  aMutableString = [[NSMutableString alloc] init];
  AUTORELEASE(aMutableString);

  [aMutableString appendString: @"---------- Forwarded message ----------"];

  // We verify if we have a Date value. We might receive messages w/o this field
  // (Yes, I know, it's borken but it's happening).
  if ( [self receivedDate] )
    {
      [aMutableString appendString: @"\nDate: "];
      [aMutableString appendString: [[self receivedDate] description] ];
    }
  
  [aMutableString appendString: @"\nFrom: "];
  [aMutableString appendString: [[self from] unicodeStringValue]];
  
  if ( [self subject] )
    {
      [aMutableString appendString: @"\nSubject: "];
    }
  
  [aMutableString appendString: [NSString stringWithFormat: @"%@\n\n", [self subject]] ];
  
  //
  // If our Content-Type is text/plain, we represent it as a it is, otherwise,
  // we currently create a new body part representing the forwarded message.
  //
  if ( [self isMimeType: @"text": @"*"] )
    {
      // We set the content of our message
      [aMutableString appendString: [MimeUtility plainTextContentFromPart: self]];

      // We set the Content-Transfer-Encoding and the Charset to the previous one
      [theMessage setContentTransferEncoding: [self contentTransferEncoding]];
      [theMessage setCharset: [self charset]];
      
      [theMessage setContent: aMutableString];
      [theMessage setSize: [aMutableString length]];
    }
  //
  // If our Content-Type is a message/rfc822 or any other type like
  // application/*, audio/*, image/* or video/*
  // 
  else if ( [self isMimeType: @"application": @"*"] ||
	    [self isMimeType: @"audio": @"*"] ||
	    [self isMimeType: @"image": @"*"] || 
	    [self isMimeType: @"message": @"*"] ||
	    [self isMimeType: @"video": @"*"] )
    {
      MimeMultipart *aMimeMultipart;
      Part *aPart;
      
      aMimeMultipart = [[MimeMultipart alloc] init];
      
      // We add our text/plain part.
      aPart = [[Part alloc] init];
      [aPart setContent: aMutableString];
      [aPart setContentDisposition: @"inline"];
      [aPart setSize: [aMutableString length]];
      [aMimeMultipart addBodyPart: aPart];
      RELEASE(aPart);
      
      // We add our content as an attachment
      aPart = [[Part alloc] init];  
      [aPart setContentType: [self contentType]];
      [aPart setContent: [self content]];
      [aPart setContentTransferEncoding: [self contentTransferEncoding]];
      [aPart setContentDisposition: @"attachment"];
      [aPart setCharset: [self charset]];
      [aPart setFilename: [self filename]];
      [aPart setSize: [self size]];
      [aMimeMultipart addBodyPart: aPart];
      RELEASE(aPart);

      [theMessage setContent: aMimeMultipart];
      RELEASE(aMimeMultipart);
    }
  //
  // We have a multipart object. We must treat multipart/alternative
  // parts differently since we don't want to include multipart parts in the forward.
  //
  else if ( [self isMimeType: @"multipart" : @"*"] )
    {
      //
      // If we have multipart/alternative part, we only keep one part from it.
      //
      if ( [self isMimeType: @"multipart" : @"alternative"] )
	{
	  MimeMultipart *aMimeMultipart;
	  Part *aPart;
	  int i;
	  
	  aMimeMultipart = (MimeMultipart *)[self content];
	  aPart = nil;

	  // We search for our text/plain part
	  for (i = 0; i < [aMimeMultipart count]; i++)
	    {
	      aPart = [aMimeMultipart bodyPartAtIndex: i];

	      if ( [aPart isMimeType: @"text": @"plain"] )
		{
		  break;
		}
	      else
		{
		  aPart = nil;
		}
	    }

	  // We found one
	  if ( aPart )
	    {
	      NSString *aString;
	      
	      // If our part was base64 encoded, we must convert the 
	      // NSData object into a NSString
	      if ( [aPart contentTransferEncoding] == BASE64 &&
		   [[aPart content] isKindOfClass: [NSData class]] )
		{
		  aString = [[NSString alloc] initWithData: (NSData *)[aPart content]
					      encoding: NSASCIIStringEncoding];
		  AUTORELEASE(aString);
		}
	      else
		{
		  aString = (NSString*)[aPart content];
		}

	      // We set the content of our message
	      [aMutableString appendString: aString];
	      
	      // We set the Content-Transfer-Encoding and the Charset to our text part
	      [theMessage setContentTransferEncoding: [aPart contentTransferEncoding]];
	      [theMessage setCharset: [aPart charset]];
	      
	      [theMessage setContent: aMutableString];
	      [theMessage setSize: [aMutableString length]];
	    }
	  // We haven't found one! Inform the user that it happened.
	  else
	    {
	      [aMutableString appendString: @"No text/plain part from this multipart/alternative part has been found"];
	      [aMutableString appendString: @"\nNo parts have been included as attachments with this mail during the forward operation."];
	      [aMutableString appendString: @"\n\nPlease report this as a bug."];

	      [theMessage setContent: aMutableString];
	      [theMessage setSize: [aMutableString length]];
	    }
	}
      //
      // We surely have a multipart/mixed or multipart/related.
      // We search for a text/plain part inside our multipart object.
      // We 'keep' the other parts in a separate new multipart object too
      // that will become our new content.
      //
      else
	{
	  MimeMultipart *aMimeMultipart, *newMimeMultipart;
	  Part *aPart;
	  BOOL hasFoundTextPlain = NO;
	  int i;
	  
	  // We get our current mutipart object
	  aMimeMultipart = (MimeMultipart *)[self content];

	  // We create our new multipart object for holding all our parts.
	  newMimeMultipart = [[MimeMultipart alloc] init];
	  
	  for (i = 0; i < [aMimeMultipart count]; i++)
	    {
	      aPart = [aMimeMultipart bodyPartAtIndex: i];
	      
	      if ( [aPart isMimeType: @"text": @"plain"] 
		   && !hasFoundTextPlain )
		{
		  Part *newPart;
		  
		  newPart = [[Part alloc] init];
		  
		  // We set the content of our new part
		  [aMutableString appendString: (NSString*)[aPart content]];
		  [newPart setContent: aMutableString];
		  [newPart setSize: [aMutableString length]];
		  
		  // We set the Content-Transfer-Encoding and the Charset to the previous one
		  [newPart setContentTransferEncoding: [aPart contentTransferEncoding]];
		  [newPart setCharset: [aPart charset]];
		  
		  // We finally add our new part to our MIME multipart object
		  [newMimeMultipart addBodyPart: newPart];
		  RELEASE(newPart);
		  
		  hasFoundTextPlain = YES;
		}
	      // We set the Content-Disposition to "attachment"
	      // all the time.
	      else
		{
		  [aPart setContentDisposition: @"attachment"];
		  [newMimeMultipart addBodyPart: aPart];
		}
	    }
	  
	  [theMessage setContent: newMimeMultipart];
	  RELEASE(newMimeMultipart);
	}
    }
  //
  // We got an unknown part. Let's inform the user about this situation.
  //
  else
    {
      // We set the content of our message
      [aMutableString appendString: @"The original message contained an unknown part that was not included in this forward message."];
      [aMutableString appendString: @"\n\nPlease report this as a bug."];
      
      [theMessage setContent: aMutableString];
      [theMessage setSize: [aMutableString length]];
    }
  
  return AUTORELEASE(theMessage);
}


//
//
//
- (NSData *) dataValue
{
  NSMutableData *aMutableData;
  NSDictionary *aLocale;

  NSEnumerator *allHeaderKeyEnumerator;
  NSString *aKey;

  NSCalendarDate *aCalendarDate;
  NSData *aBoundary, *aData;


  // We get our locale in English
#ifndef MACOSX
  aLocale = [NSDictionary dictionaryWithContentsOfFile: [[NSBundle bundleForLibrary: @"gnustep-base"]
							  pathForResource: @"English"
							  ofType: nil
							  inDirectory: @"Languages"]];
#else
  aLocale = [NSDictionary dictionaryWithContentsOfFile: [[NSBundle bundleForClass: [NSObject class]]
							  pathForResource: @"English"
							  ofType: nil
							  inDirectory: @"Languages"] ];
#endif
  
  // We initialize our mutable data object holding the raw data of the
  // new message.
  aMutableData = [[NSMutableData alloc] init];
  aBoundary = [MimeUtility generateBoundary];
  
#ifndef MACOSX
  if ( [[NSUserDefaults standardUserDefaults] objectForKey: @"Local Time Zone"] )
    {
      aCalendarDate = [[[NSDate alloc] init] dateWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
					     timeZone: [NSTimeZone systemTimeZone] ];
    }
  else
    {
      tzset();
  
      aCalendarDate = [[[NSDate alloc] init] dateWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
					     timeZone: [NSTimeZone timeZoneWithAbbreviation: 
								     [NSString stringWithCString: tzname[1]]] ];
    }
#else
  aCalendarDate = [[[NSDate alloc] init] dateWithCalendarFormat: @"%a, %d %b %Y %H:%M:%S %z"
					 timeZone: [NSTimeZone systemTimeZone] ];
#endif
  [aMutableData appendCFormat: @"Date: %@%s", [aCalendarDate descriptionWithLocale: aLocale], LF];
  
  // We set the subject, if we have one!
  if ( [[[self subject] stringByTrimmingWhiteSpaces] length] > 0 )
    {
      [aMutableData appendCString: "Subject: "];
      [aMutableData appendData: [MimeUtility encodeWordUsingQuotedPrintable: [self subject]
					     prefixLength: 8]];
      [aMutableData appendCString: LF];
    }
  
  // We set our Message-ID
  [aMutableData appendCFormat:@"Message-ID: <"];
  [aMutableData appendData: [MimeUtility generateOSID]];
  [aMutableData appendCFormat: @">%s", LF];
  
  [aMutableData appendCFormat: @"MIME-Version: 1.0 (Generated by Pantomime %@)%s", PANTOMIME_VERSION, LF];

  // We encode our From: field
  [aMutableData appendCFormat: @"From: "];
  [aMutableData appendData: [[self from] dataValue]];
  [aMutableData appendCFormat: @"%s", LF];
  
  // We encode our To field
  aData = [self _formatRecipientsWithType: TO];
  
  if ( aData )
    {
      [aMutableData appendCString: "To: "];
      [aMutableData appendData: aData];
      [aMutableData appendCString: LF];
    }
  
  // We encode our Cc field
  aData = [self _formatRecipientsWithType: CC];
  
  if ( aData )
    {
      [aMutableData appendCString: "Cc: "];
      [aMutableData appendData: aData];
      [aMutableData appendCString: LF];
    }

  // We encode our Bcc field
  aData = [self _formatRecipientsWithType: BCC];
  
  if ( aData )
    {
      [aMutableData appendCString: "Bcc: "];
      [aMutableData appendData: aData];
      [aMutableData appendCString: LF];
    }
  
  // We set the Reply-To address in case we need to
  if ( [self replyTo] )
    {
      [aMutableData appendCFormat: @"Reply-To: "];
      [aMutableData appendData: [[self replyTo] dataValue]];
      [aMutableData appendCString: LF];
    }
  
  // We set the Organization header value if we need to
  if ( [self organization] )
    {
      [aMutableData appendCString: "Organization: "];
      [aMutableData appendData: [MimeUtility encodeWordUsingQuotedPrintable: [self organization]
					     prefixLength: 13]];
      [aMutableData appendCString: LF];
    }
  
  // We set the In-Reply-To header if we need to
  if ( [self headerValueForName: @"In-Reply-To"] )
    {
      [aMutableData appendCFormat: @"In-Reply-To: %@%s", [self inReplyTo], LF];
    }
  

  // We now set all X-* headers
  allHeaderKeyEnumerator = [[self allHeaders] keyEnumerator];
  
  while ( (aKey = [allHeaderKeyEnumerator nextObject]) ) 
    {
      if ( [aKey hasPrefix: @"X-"] )
	{
	  [aMutableData appendCFormat: @"%@: %@%s", aKey, [self headerValueForName: aKey], LF];
	}
    }
  
  //
  // We add our message header/body separator
  //
  [aMutableData appendData: [super dataValue]];

  return AUTORELEASE(aMutableData);
}


//
// This method is used to add an extra header to the list of headers
// of the message.
//
- (void) addHeader: (NSString *) theName
	 withValue: (NSString *) theValue
{
  if ( theName && theValue )
    {
      [headers setObject: theValue forKey: theName];
    }
}


- (id) headerValueForName: (NSString *) theName
{
  return [headers objectForKey: theName];
}

- (NSDictionary *) allHeaders
{
  return headers;
}

- (Folder *) folder
{
  return folder;
}

- (void) setFolder: (Folder *) theFolder
{
  folder = theFolder;
}



//
//
//
- (void) setHeaders: (NSDictionary *) theHeaders
{
  if ( theHeaders )
    {
      RELEASE(headers);
      
      headers = [[NSMutableDictionary alloc] initWithCapacity: [theHeaders count]];
      [headers addEntriesFromDictionary: theHeaders];
    }
  else
    {
      DESTROY(headers);
    }
}


//
// This method is used to optain tha raw source of a message.
// It's returned as a NSData object. The returned data should
// be the message like it's achived in a mbox format and it could
// easily be decoded with Message: -initWithData.
// 
// All subclasses of Message MUST implement this method.
//
- (NSData *) rawSource
{
  if ( !rawSource )
    {
      [self subclassResponsibility: _cmd];
      return nil;
    }
  
  return rawSource;
}


//
//
//
- (void) setRawSource: (NSData *) theRawSource
{
  if ( theRawSource )
    {
      RETAIN(theRawSource);
      RELEASE(rawSource);
      rawSource = theRawSource;
    }
  else
    {
      DESTROY(rawSource);
    }
}


//
//
//
- (NSCalendarDate *) resentDate
{
  return [headers objectForKey: @"Resent-Date"];
}


//
//
//
- (void) setResentDate: (NSCalendarDate *) theResentDate
{
  [headers setObject: theResentDate 
	   forKey: @"Resent-Date"];
}


//
//
//
- (InternetAddress *) resentFrom
{
  return [headers objectForKey: @"Resent-From"];
}


//
//
//
- (void) setResentFrom: (InternetAddress *) theInternetAddress
{
  [headers setObject: theInternetAddress
	   forKey: @"Resent-From"];
}


//
//
//
- (NSString *) resentMessageID
{
  return [headers objectForKey: @"Resent-Message-ID"];
}


//
//
//
- (void) setResentMessageID: (NSString *) theResentMessageID
{
  [headers setObject: theResentMessageID
	   forKey: @"Resent-Message-ID"];
}


//
//
//
- (NSString *) resentSubject
{
  return [headers objectForKey: @"Resent-Subject"];
}


//
//
//
- (void) setResentSubject: (NSString *) theResentSubject
{
  [headers setObject: theResentSubject
	   forKey: @"Resent-Subject"];
}


//
// This method won't erase the currently defined headers.
//
- (void) addHeadersFromData: (NSData *) theHeaders
{
  NSAutoreleasePool *pool;
  NSArray *allLines;
  int i;

  [super setHeadersFromData: theHeaders];

  // We initialize a local autorelease pool
  pool = [[NSAutoreleasePool alloc] init];

  // We MUST be sure to unfold all headers properly before
  // decoding the headers
  theHeaders = [MimeUtility unfoldLinesFromData: theHeaders];

  allLines = [theHeaders componentsSeparatedByCString: "\n"];
  
  for (i = 0; i < [allLines count]; i++)
    {
      NSData *aLine = [allLines objectAtIndex: i];

      // We stop if we found the header separator. (\n\n) since someone could
      // have called this method with the entire rawsource of a message.
      if ( [aLine length] == 0 )
	{
	  break;
	}

      if ( [aLine hasCaseInsensitiveCPrefix: "Bcc"] )
	{
	  [Parser parseDestination: aLine
		  forType: BCC
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "Cc"] )
	{
	  [Parser parseDestination: aLine
		  forType: CC
		  inMessage: self];
	}
      else if ([aLine hasCaseInsensitiveCPrefix: "Date"])
	{
	  [Parser parseDate: aLine
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "From"] &&
		![aLine hasCaseInsensitiveCPrefix: "From "] )
	{
	  [Parser parseFrom: aLine 
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "In-Reply-To"] )
	{
	  [Parser parseInReplyTo: aLine
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "Message-ID"] )
	{
	  [Parser parseMessageID: aLine
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "MIME-Version"] )
	{
	  [Parser parseMimeVersion: aLine 
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "Organization"] )
	{
	  [Parser parseOrganization: aLine 
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "References"] )
	{
	  [Parser parseReferences: aLine 
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "Reply-To"] )
	{
	  [Parser parseReplyTo: aLine 
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "Resent-From"] )
	{
	  [Parser parseResentFrom: aLine
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "Resent-Bcc"] )
	{
	  [Parser parseDestination: aLine
		  forType: RESENT_BCC
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "Resent-Cc"] )
	{
	  [Parser parseDestination: aLine
		  forType: RESENT_CC
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "Resent-To"] )
	{
	  [Parser parseDestination: aLine
		  forType: RESENT_TO
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "Status"] )
	{
	  [Parser parseStatus: aLine
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "To"] )
	{
	  [Parser parseDestination: aLine
		  forType: TO
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "X-Status"] )
	{
	  [Parser parseXStatus: aLine 
		  inMessage: self];
	}
      else if ( [aLine hasCaseInsensitiveCPrefix: "Subject"] )
	{
	  [Parser parseSubject: aLine
		  inMessage: self];
	}
      else
	{
	  // We MUST NOT parse the headers that we already parsed in
	  // Part as "unknown".
	  if ( ![aLine hasCaseInsensitiveCPrefix: "Content-Description"] &&
	       ![aLine hasCaseInsensitiveCPrefix: "Content-Disposition"] &&
	       ![aLine hasCaseInsensitiveCPrefix: "Content-ID"] &&
	       ![aLine hasCaseInsensitiveCPrefix: "Content-Length"] &&
	       ![aLine hasCaseInsensitiveCPrefix: "Content-Transfer-Encoding"] &&
	       ![aLine hasCaseInsensitiveCPrefix: "Content-Type"] )
	    {
	      [Parser parseUnknownHeader: aLine
		      inMessage: self];
	    }
	}
    }

  RELEASE(pool);
}


//
// This method initalize all the headers of a message
// from a raw data source. It removes all previously initialized
// headers.
//
- (void) setHeadersFromData: (NSData *) theHeaders
{  
  if ( !theHeaders || [theHeaders length] == 0 )
    {
      return;
    }

  // First of all, we remove all existing headers and recipients.
  [headers removeAllObjects];
  [self removeAllRecipients];

  [self addHeadersFromData: theHeaders];
}

@end


//
// Message's sorting category
//
@implementation Message (Comparing)

- (int) compareAccordingToNumber: (Message *) aMessage
{
  int num1, num2;
  num1 = [self messageNumber];
  num2 = [aMessage messageNumber];
  if (num1 < num2)
    {
      return NSOrderedAscending;
    }
  else if (num1 > num2)
    {
      return NSOrderedDescending;
    }
  else
    {
      return NSOrderedSame;
    }
}

- (int) reverseCompareAccordingToNumber: (Message *) aMessage
{
  int num1, num2;
  num2 = [self messageNumber];
  num1 = [aMessage messageNumber];
  if (num1 < num2)
    {
      return NSOrderedAscending;
    }
  else if (num1 > num2)
    {
      return NSOrderedDescending;
    }
  else
    {
      return NSOrderedSame;
    }
}

- (int) compareAccordingToDate: (Message *) aMessage
{
  NSDate *date1 = [self receivedDate];
  NSDate *date2 = [aMessage receivedDate];
  NSTimeInterval timeInterval;

  if (date1 == nil || date2 == nil)
    {
      return [self compareAccordingToNumber: aMessage]; 
    }

  timeInterval = [date1 timeIntervalSinceDate: date2];

  if (timeInterval < 0)
    {
      return NSOrderedAscending;
    }
  else if (timeInterval > 0)
    {
      return NSOrderedDescending;
    }
  else
    {
      return [self compareAccordingToNumber: aMessage];      
    }
}

- (int) reverseCompareAccordingToDate: (Message *) aMessage
{
  NSDate *date2 = [self receivedDate];
  NSDate *date1 = [aMessage receivedDate];
  NSTimeInterval timeInterval;

  if (date1 == nil || date2 == nil)
    {
      return [self reverseCompareAccordingToNumber: aMessage]; 
    }

  timeInterval = [date1 timeIntervalSinceDate: date2];

  if (timeInterval < 0)
    {
      return NSOrderedAscending;
    }
  else if (timeInterval > 0)
    {
      return NSOrderedDescending;
    }
  else
    {
      return [self reverseCompareAccordingToNumber: aMessage];      
    }
}

- (int) compareAccordingToSender: (Message *) aMessage
{
  InternetAddress *from1, *from2;
  NSString *fromString1, *fromString2;
  NSString *tempString;
  int result;

  from1 = [self from];
  from2 = [aMessage from];

  tempString = [from1 personal];
  if (tempString == nil || [tempString length] == 0)
    {
      fromString1 = [from1 address];
      if (fromString1 == nil)
	fromString1 = @"";
    }
  else
    {
      fromString1 = tempString;
    }


  tempString = [from2 personal];
  if (tempString == nil || [tempString length] == 0)
    {
      fromString2 = [from2 address];
      if (fromString2 == nil)
	fromString2 = @"";
    }
  else
    {
      fromString2 = tempString;
    }

  result = [fromString1 caseInsensitiveCompare: fromString2];
  if (result == NSOrderedSame)
    {
	  return [self compareAccordingToNumber: aMessage];
    }
  else
    {
      return result;
    }
}

- (int) reverseCompareAccordingToSender: (Message *) aMessage
{
  InternetAddress *from1, *from2;
  NSString *fromString1, *fromString2;
  NSString *tempString;
  int result;

  from2 = [self from];
  from1 = [aMessage from];

  tempString = [from1 personal];
  if (tempString == nil || [tempString length] == 0)
    {
      fromString1 = [from1 address];
      if (fromString1 == nil)
	fromString1 = @"";
    }
  else
    {
      fromString1 = tempString;
    }


  tempString = [from2 personal];
  if (tempString == nil || [tempString length] == 0)
    {
      fromString2 = [from2 address];
      if (fromString2 == nil)
	fromString2 = @"";
    }
  else
    {
      fromString2 = tempString;
    }


  result = [fromString1 caseInsensitiveCompare: fromString2];
  
  if (result == NSOrderedSame)
    {
      return [self reverseCompareAccordingToNumber: aMessage];
    }
  else
    {
      return result;
    }
}

- (int) compareAccordingToSubject: (Message *) aMessage
{
  NSString *subject1 = [self baseSubject];
  NSString *subject2 = [aMessage baseSubject];
  int result;
  
  if (subject1 == nil)
    subject1 = @"";
  if (subject2 == nil)
    subject2 = @"";

  result = [subject1 caseInsensitiveCompare: subject2];

  if (result == NSOrderedSame)
    {
      return [self compareAccordingToNumber: aMessage];      
    }
  else
    {
      return result;
    }
}

- (int) reverseCompareAccordingToSubject: (Message *) aMessage
{
  NSString *subject2 = [self baseSubject];
  NSString *subject1 = [aMessage baseSubject];
  int result;
  
  if (subject1 == nil)
    subject1 = @"";
  if (subject2 == nil)
    subject2 = @"";

  result = [subject1 caseInsensitiveCompare: subject2];

  if (result == NSOrderedSame)
    {
      return [self compareAccordingToNumber: aMessage];      
    }
  else
    {
      return result;
    }
}

- (int) compareAccordingToSize: (Message *) aMessage
{
  int size1 = [self size];
  int size2 = [aMessage size];


  if (size1 < size2)
    {
      return NSOrderedAscending;
    }
  else if (size1 > size2)
    {
      return NSOrderedDescending;
    }
  else
    {
      return [self compareAccordingToNumber: aMessage];
    }
}

- (int) reverseCompareAccordingToSize: (Message *) aMessage
{
  int size1 = [aMessage size];
  int size2 = [self size];


  if (size1 < size2)
    {
      return NSOrderedAscending;
    }
  else if (size1 > size2)
    {
      return NSOrderedDescending;
    }
  else
    {
      return [self reverseCompareAccordingToNumber: aMessage];      
    }
}

@end


//
// Private methods
//
@implementation Message (Private)

//
// Intended to be compatible for use with the draft specification for
// sorting by subject contained in
// INTERNET MESSAGE ACCESS PROTOCOL - SORT and THREAD EXTENSIONS
// draft document, May 2003
//
// At the time of this writing, it can be found at
// http://www.ietf.org/internet-drafts/draft-ietf-imapext-sort-13.txt
//
- (NSString *) _computeBaseSubject
{
  BOOL repeat, repeat2;
  NSArray *matches;
  NSRange range;
  NSMutableString *baseSubject;
  NSString *subject;
  int i;
  
  subject = [self subject];
  
  if (subject == nil)
    {
      return nil;
    }
  
  baseSubject = [NSMutableString stringWithString:subject];
  
  //
  // (1) Convert any RFC 2047 encoded-words in the subject to
  // UTF-8 as described in "internationalization
  // considerations."  Convert all tabs and continuations to
  // space.  Convert all multiple spaces to a single space.
  // 
  matches = [atLeastOneSpaceRegex matchString:baseSubject];
  for (i = [matches count] - 1; i >= 0; i--)
    {
      [baseSubject replaceCharactersInRange:[[matches objectAtIndex:i] rangeValue]
		   withString:@" "];
    }
  do
    {
      repeat = NO;
      
      //
      // (2) Remove all trailing text of the subject that matches
      // the subj-trailer ABNF, repeat until no more matches are
      // possible.
      //
      matches = [suffixSubjTrailerRegex matchString:baseSubject];
      if ([matches count] > 0)
        {
	  [baseSubject deleteCharactersInRange:[[matches objectAtIndex:0] rangeValue]];
        }
      do
        {
	  repeat2 = NO;
	  //
	  // (3) Remove all prefix text of the subject that matches the
	  // subj-leader ABNF.
          //
	  matches = [prefixSubjLeaderRegex matchString:baseSubject];
	  if ([matches count] > 0)
            {
	      range = [[matches objectAtIndex:0] rangeValue];
	      if (range.length > 0)
                {
		  [baseSubject deleteCharactersInRange:[[matches objectAtIndex:0] rangeValue]];
		  repeat2 = YES;
                }
            }
	  //
          // (4) If there is prefix text of the subject that matches the
          // subj-blob ABNF, and removing that prefix leaves a non-empty
          // subj-base, then remove the prefix text.
          //
            matches = [prefixSubjBlobRegex matchString:baseSubject];
            if ([matches count] > 0)
            {
                range = [[matches objectAtIndex:0] rangeValue];
                if ( (range.length > 0) &&
                     (range.length < [baseSubject length]))
                {
                    [baseSubject deleteCharactersInRange:[[matches objectAtIndex:0] rangeValue]];
                    repeat2 = YES;
                }
            }
            //
            // (5) Repeat (3) and (4) until no matches remain.
            //
        } while (repeat2);
      //
      // (6) If the resulting text begins with the subj-fwd-hdr ABNF
      // and ends with the subj-fwd-trl ABNF, remove the
      // subj-fwd-hdr and subj-fwd-trl and repeat from step (2).
      //
      matches = [prefixSubjFwdHdrAndSuffixSubjFwdTrlRegex matchString:baseSubject];
      if ([matches count] > 0)
        {
	  [baseSubject deleteCharactersInRange:NSMakeRange(0,5)];
	  [baseSubject deleteCharactersInRange:NSMakeRange([baseSubject length] - 1,1)];
	  repeat = YES;
        }
    } while (repeat);
  //
  // (7) The resulting text is the "base subject" used in the SORT.
  //
  return baseSubject;
}


//
//
//
- (NSData *) _formatRecipientsWithType: (int) theType
{
  NSMutableData *aMutableData;
  NSArray *anArray;
  int i;

  aMutableData = [[NSMutableData alloc] init];
  anArray = [self recipients];

  for (i = 0; i < [anArray count]; i++)
    {
      InternetAddress *anInternetAddress;

      anInternetAddress = [anArray objectAtIndex: i];

      if ([anInternetAddress type] == theType)
	{
	  [aMutableData appendData: [anInternetAddress dataValue]];
	  [aMutableData appendCString: ", "];
	}
    }
  
  if ( [aMutableData length] > 0)
    {
      [aMutableData setLength: [aMutableData length] - 2];

      return AUTORELEASE(aMutableData);
    }
  else
    {
      RELEASE(aMutableData);
      
      return nil;
    }
}

@end


