Logo Search packages:      
Sourcecode: pantomime version File versions  Download package

POP3Folder.m

/*
**  POP3Folder.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/POP3Folder.h>

#include <Pantomime/Connection.h>
#include <Pantomime/Constants.h>
#include <Pantomime/Message.h>
#include <Pantomime/POP3CacheManager.h>
#include <Pantomime/POP3CacheObject.h>
#include <Pantomime/POP3Message.h>
#include <Pantomime/POP3Store.h>
#include <Pantomime/TCPConnection.h>
#include <Pantomime/NSData+Extensions.h>
#include <Pantomime/NSString+Extensions.h>

#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSDebug.h>
#include <Foundation/NSException.h>
#include <Foundation/NSValue.h>

#include <limits.h>
#include <stdio.h>
#include <string.h>

#if !defined(UINT_MAX)
#define UINT_MAX (unsigned int)~0
#endif

//
//
//
@implementation POP3Folder

- (id) initWithName: (NSString *) theName
{
  self = [super initWithName: theName];

  [self setLeaveOnServer: YES];
  [self setRetainPeriod: 0];

  // We initialize our UID cache
  sizeCache = [[NSMutableDictionary alloc] init];
  UIDCache = [[NSMutableDictionary alloc] init];
  count = -1;
  size = -1;

  return self;
}


//
//
//
- (void) dealloc
{
  RELEASE(sizeCache);
  RELEASE(UIDCache);

  [super dealloc];
}


//
//
//
- (void) deleteMessageAtIndex: (int) theIndex
{
  POP3Store *aStore;
  
  aStore = (POP3Store *)[self store];
  
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"DELE %i", theIndex]];
  
  if ( ![aStore responseFromServerIsValid: NULL] ) 
    {
      NSDebugLog(@"Unable to delete the message on the POP3 server...");
    }
}


//
// This method is used to fetch a message at a specified index
// in the POP3 Inbox folder.
//
- (NSData *) prefetchMessageAtIndex: (int) theIndex
{
  return [self prefetchMessageAtIndex: theIndex  numberOfLines: UINT_MAX];
}


//
//
//
- (NSData *) prefetchMessageAtIndex: (int) theIndex
                  numberOfLines: (unsigned int) theNumberOfLines
{
  NSMutableData *aMutableData;
  NSAutoreleasePool *pool;
  POP3Store *aStore;

  int length;

  // We obtain the pointer to our store
  aStore = (POP3Store *)[self store];
  
  // We create our local autorelease pool
  pool = [[NSAutoreleasePool alloc] init];
  length = 0;
      
  // Then we transfer our messsage by getting the length of the message first
  length = [self lengthOfMessageAtIndex: theIndex];

  // We create our mutable data object holding the message as raw source
  aMutableData = [[NSMutableData alloc] initWithCapacity: length];

  // We ask for a number of lines or for the complete message
  if ( theNumberOfLines == UINT_MAX )
    {
      [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"RETR %i", theIndex]];
    }
  else
    {
      [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"TOP %i %i", theIndex, theNumberOfLines]];
    }

  if ( ![aStore responseFromServerIsValid: NULL] )
    {
      NSDebugLog(@"POP3Folder: Invalid request for message index = %d", theIndex);
      RELEASE(aMutableData);
      RELEASE(pool);
      return nil;
    }
  
  while ( YES )
    {
      NSData *theData;

      theData = [[aStore tcpConnection] readDataToEndOfLine];
 
      if ( [theData hasCSuffix: "\r\n"] )
      {
        // We verify if we're at the end of the message
        if ( ([theData length] > 2) && [theData hasCPrefix: ".\r\n"] )
          {
            break;
          }
        // If not, replace \r\n (CRLF) by \n (LF)
        else
          {
            NSMutableData *aSubData;
            
            aSubData = [NSMutableData dataWithData: [theData subdataToIndex: ([theData length] - 2)]];
            [aSubData appendCString: "\n"];
            theData = aSubData;
          }
      } // if ( [theLine hasSuffix: @"\r\n"] )
      
      //
      // We append the line in our message
      // RFC1939, chap. 3 says:
      //
      //    When examining a multi-line response, the client checks
      //    to see if the line begins with the termination octet.  If so and if
      //    octets other than CRLF follow, the first octet of the line (the
      //    termination octet) is stripped away.
      //
      if ( [theData length] )
      {
        char c;

        [theData getBytes: &c  length: 1];

        if ( c == '.' )
          {
            [aMutableData appendData: [theData subdataFromIndex: 1]];
          }
        else
          {
            [aMutableData appendData: theData];
          }
      }
    } // while ( YES ) ...
      
  // We release our local pool
  RELEASE(pool);

  return AUTORELEASE(aMutableData);
}


//
// This method is used to cache the messages from the POP3 server
// locally (in memory).
//
- (BOOL) prefetch
{
  BOOL didTransferMessages;
  NSData *aData;
  int i, aCount;

  didTransferMessages = NO;
  aCount = [self count];

  for (i = 1; i <= aCount; i++)
    {
      aData = [self prefetchMessageAtIndex: i];
      
      if ( aData )
      {
        POP3Message *aMessage;
        
        // The data read is valid, we can now add the message to our folder
        aMessage = [[POP3Message alloc] initWithData: aData];
        
        // We set some initial properties to our message
        [aMessage setInitialized: YES];
        [aMessage setMessageNumber: i];
        [aMessage setFolder: self];
        [aMessage setSize: [aData length]];
        [aMessage setUID: [self UIDOfMessageAtIndex: i]];
        
        [self appendMessage: aMessage];
        RELEASE(aMessage);

        didTransferMessages = YES;
      }
    } // for
  
  // We mark it as deleted if we need to
  if ( ![self leaveOnServer] )
    {
      for (i = 1; i <= aCount; i++)
      {
        [self deleteMessageAtIndex: i];
      }
    }
  else if ( [self retainPeriod] > 0 )
    {
      [self _deleteOldMessagesWithMessageCount: aCount];
    }
  
  return didTransferMessages;
}


//
// This method returns the number of message in this POP3 folder.
// It re-implements the method of the super class.
// 
- (int) count
{
  if ( count < 0 )
    {
      [self _stat];
    }
  
  return count;
}


//
// This method returns the size of the "INBOX" POP3 folder.
// It re-implements the method of the super class since we don't need to
// (and don't want to) prefetch all messages in order to know the total
// size of the folder.
//
- (long) size
{
  if ( size < 0 )
    {
      [self _stat];
    }

  return size;
}


//
// This method does nothing.
//
- (void) close
{
  // We do nothing.
}


//
// This method is used to read the UID (obtained from the command UIDL)
// of a message.
//
- (NSString *) UIDOfMessageAtIndex: (int) theIndex
{
  NSString *aString;

  aString = [UIDCache objectForKey: [NSNumber numberWithInt: theIndex]];

  // We cache all UIDs
  if ( !aString )
    {
      POP3Store *aStore;
      char buf;
      int num;
  
      aStore = (POP3Store *)[self store];
      [[aStore tcpConnection] writeLine: @"UIDL"];

      // We read our "+OK response"
      aString = [[aStore tcpConnection] readStringToEndOfLine];
      
      if ( ![aString hasCaseInsensitivePrefix: @"+OK"] )
      {
        return nil;
      }
      
      // We read the "unique-id listing" for the first message
      aString = [[aStore tcpConnection] readStringToEndOfLine];

      while ( [aString characterAtIndex: 0] != '.' )
      {
        memset(buf, 0, 71);
        sscanf([aString cString],"%i %s\r\n", &num, buf);
        aString = [NSString stringWithCString: buf];
      
        [UIDCache setObject: aString
                forKey: [NSNumber numberWithInt: num]];
        aString = [[aStore tcpConnection] readStringToEndOfLine];
      }
      
      // We get the UID from our newly created cache, for the index wanted.
      aString = [UIDCache objectForKey: [NSNumber numberWithInt: theIndex]];
    }
 
  return aString;
}


//
// This command is used to read the length of a message
// obtained by the command LIST
//
- (int) lengthOfMessageAtIndex: (int) theIndex
{
  NSNumber *aNumber;

  aNumber = [sizeCache objectForKey: [NSNumber numberWithInt: theIndex]];

  // We cache all size information
  if ( !aNumber )
    {
      POP3Store *aStore;
      NSString *aString;
      int num, length;
      
      aStore = (POP3Store *)[self store];
      [[aStore tcpConnection] writeLine: @"LIST"];

      // We read our "+OK response"
      aString = [[aStore tcpConnection] readStringToEndOfLine];
      
      if ( ![aString hasCaseInsensitivePrefix: @"+OK"] )
      {
        return 0;
      }
   
      // We read the size for the first message
      aString = [[aStore tcpConnection] readStringToEndOfLine];

      while ( [aString characterAtIndex: 0] != '.' )
      {
        sscanf([aString cString],"%i %i\r\n", &num, &length);

        [sizeCache setObject: [NSNumber numberWithInt: length]
                forKey: [NSNumber numberWithInt: num]];
        aString = [[aStore tcpConnection] readStringToEndOfLine];
      }
      
      // We get the UID from our newly created cache, for the index wanted.
      aNumber = [sizeCache objectForKey: [NSNumber numberWithInt: theIndex]];
    }

  return [aNumber intValue];
}


//
// Return YES/NO depending if we leave or not the messages on the POP3 Server
//
- (BOOL) leaveOnServer
{
  return leaveOnServer;
}


//
// This method is used to set the flag to leave or not the messages on the POP3
// server after fetching them.
//
- (void) setLeaveOnServer: (BOOL) aBOOL
{
  leaveOnServer = aBOOL;
}


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


//
// The retain period is set in days.
//
- (void) setRetainPeriod: (int) theRetainPeriod
{
  retainPeriod = theRetainPeriod;
}


//
//
//
- (int) mode
{
  return PantomimeReadOnlyMode;
}


//
// This method does nothing except returning an empty array.
// Expunging a POP3 folder doesn't make a lot of sense.
//
- (NSArray *) expunge: (BOOL) returnDeletedMessages
{
  int aCount;

  aCount = [self count];

  // We mark it as deleted if we need to
  if ( ![self leaveOnServer] )
    {
      int i;

      for (i = 1; i <= aCount; i++)
      {
        [self deleteMessageAtIndex: i];
      }
    }
  else if ( [self retainPeriod] > 0 )
    {
      [self _deleteOldMessagesWithMessageCount: aCount];
    }

  // FIXME : return the list of message if needed
  return [NSArray array];
}


//
// In POP3, we do nothing.
//
- (NSArray *) search: (NSString *) theString
                mask: (int) theMask
             options: (int) theOptions
{
  return [NSArray array];
}

@end


//
// Private methods
//
@implementation POP3Folder (Private)

- (void) _deleteOldMessagesWithMessageCount: (int) theCount
{
  int i;

  for (i = theCount; i >= 1; i--)
    {
      POP3CacheObject *pop3CacheObject;
      NSString *anUIDString;
      
      NS_DURING
      {
        // We ask for the UIDL of the message
        anUIDString = [self UIDOfMessageAtIndex: i];
        
        // We get our POP3 cached object
        pop3CacheObject = [[self cacheManager] findPOP3CacheObject: anUIDString];
        
        if ( pop3CacheObject )
          {
            NSCalendarDate *date;
            int days;
            
            // We get the days interval between our two dates
            date = [NSCalendarDate calendarDate];
            [date years: NULL
                months: NULL
                days: &days
                hours: NULL
                minutes: NULL
                seconds: NULL
                sinceDate: [pop3CacheObject date]];
            if ( days >= [self retainPeriod] )
            {
              NSDebugLog(@"Deleting message with UID %@ since it's %d days old", anUIDString, days);
              [self deleteMessageAtIndex: i];
            }
          }
      }
      NS_HANDLER
      {
        NSDebugLog(@"POP3Folder: Error occured while deleting message %d");
      }
      NS_ENDHANDLER
    }
}


//
//
//
- (void) _stat
{
  POP3Store *aStore;
  NSString *aString;
  
  aStore = (POP3Store *)[self store];
  count = size = 0;
  
  // We sent the STAT command to our POP3 server
  [[aStore tcpConnection] writeLine: @"STAT"];
  
  // We read back the response and we put it in our buffer buf
  aString = [[aStore tcpConnection] readStringToEndOfLine];
  
  if ( !aString )
    {
      NSDebugLog(@"POP3Folder: An error occured while STATing the POP3 folder.");
      return;
    }
  
  // We get the number of messages in the mailbox by scanning our buffer
  sscanf([aString cString], "+OK %i %li\r\n", &count, &size);
}

@end

Generated by  Doxygen 1.6.0   Back to index