/*
**  POP3Store.m
**
**  Copyright (c) 2001, 2002
**
**  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/POP3Store.h>

#include <Pantomime/Constants.h>
#include <Pantomime/MD5.h>
#include <Pantomime/POP3Folder.h>
#include <Pantomime/Store.h>
#include <Pantomime/TCPConnection.h>
#include <Pantomime/URLName.h>

#include <Foundation/NSArray.h>
#include <Foundation/NSBundle.h>
#include <Foundation/NSDebug.h>
#include <Foundation/NSException.h>
#include <Foundation/NSPathUtilities.h>

#include <stdio.h>

@implementation POP3Store

//
// This method implements a part of the Service Protocol.
//
- (id) initWithName: (NSString *) theName
               port: (int) thePort
{
  self = [super init];
  
  // We set the initial class properties
  [self setName: theName];
  [self setPort: thePort];

  messagesHaveBeenPrefetched = NO;
  username = nil;
  
  tcpConnection = [[TCPConnection alloc] initWithName: theName
					 port: thePort];

  if ( !tcpConnection )
    {
      AUTORELEASE(self);
      return nil;
    }
  
  pop3Folder = [[POP3Folder alloc] initWithName: @"Inbox"];
  [pop3Folder setStore: (Store *)self];

  if ( ![self _postInit] )
    {
      AUTORELEASE(self);
      NSDebugLog(@"POP3Store: Not connected!");
      return nil;
    }

  return self;
}


//
// This method provide a default init method using
// the default POP3 port - 110.
// 
- (id) initWithName: (NSString *) theName
{
  return [self initWithName: theName
	       port: 110];
}


//
//
//
- (id) initSSLWithName: (NSString *) theName
                  port: (int) thePort
{
  NSMutableArray *allPaths;
  NSBundle *aBundle;
  NSString *aPath;
  
  int i;

  self = [super init];

  // We set the initial class properties
  [self setName: theName];
  [self setPort: thePort];

  messagesHaveBeenPrefetched = NO;
  username = nil;
  

  // We load our TCPSSLConnection bundle.
  allPaths = [NSMutableArray array];
  [allPaths addObjectsFromArray: NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
								     NSLocalDomainMask|NSNetworkDomainMask|NSSystemDomainMask|NSUserDomainMask,
								     YES)];
#ifdef MACOSX
  [allPaths insertObject: [[NSBundle mainBundle] builtInPlugInsPath]  atIndex: 0];
#endif

  aBundle = nil;
  
  for (i = 0; i < [allPaths count]; i++)
    {
      aPath = [NSString stringWithFormat: @"%@/Pantomime/TCPSSLConnection.bundle",
			[allPaths objectAtIndex: i]];
      
      aBundle = [NSBundle bundleWithPath: aPath];

      if ( aBundle ) break;
    }
  
  if ( !aBundle )
    {
      NSDebugLog(@"POP3Store: Failed to load the TCPSSLConnection bundle");
      AUTORELEASE(self);
      return nil;
    }
  
  tcpConnection = [[[aBundle principalClass] alloc] initWithName: theName
						    port: thePort];

  if ( !tcpConnection )
    {
      AUTORELEASE(self);
      return nil;
    }
  
  pop3Folder = [[POP3Folder alloc] initWithName: @"Inbox"];
  [pop3Folder setStore: (Store*)self];

  if ( ![self _postInit] )
    {
      AUTORELEASE(self);
      NSDebugLog(@"POP3Store: Not connected!");
      return nil;
    }

  return self;
}


//
//
//
- (id) initWithURL: (NSString *) theURL
{
  URLName *urlName;
  
  urlName = [[URLName alloc] initWithString: theURL];
  
  self = [self initWithName: [urlName host]
	       port: 110];

  RELEASE(urlName);
  
  return self;
}


//
//
//
- (NSEnumerator *) folderEnumerator
{
  return [[NSArray arrayWithObject: @"Inbox"] objectEnumerator];
}


//
//
//
- (NSEnumerator *) subscribedFolderEnumerator
{
  return [self folderEnumerator];
}


//
//
//
- (NSEnumerator *) openedFoldersEnumerator
{
  return [[NSArray arrayWithObject: pop3Folder] objectEnumerator];
}


//
//
//
- (void) removeFolderFromOpenedFolders: (Folder *) theFolder
{
  // Do nothing
}


//
//
//
- (void) dealloc
{
  RELEASE(pop3Folder);
  RELEASE(name);

  TEST_RELEASE(username);
  
  TEST_RELEASE(timestamp);
  TEST_RELEASE((id<NSObject>)tcpConnection);
  
  [super dealloc];
}


//
// This method authenticates the Store to the POP3 server.
// In case of an error, it returns NO.
//
- (BOOL) authenticate: (NSString *) theUsername
	     password: (NSString *) thePassword
	    mechanism: (NSString *) theMechanism
{
  // We first retain the username for future use
  username = RETAIN(theUsername);
  
  // If we MUST use APOP
  if ( theMechanism && [theMechanism caseInsensitiveCompare: @"APOP"] == NSOrderedSame )
    {
      if ( [self _APOPAuthenticate: theUsername  password: thePassword] )
	{
	  return YES;
	}
    }

  // We verify if our connection has dropped. Lame POP3 servers can
  // usually drop the connection when APOP auth has failed.
  // FIXME
  if ( ![self isConnected] )
    {
      return NO;
    }
  
  //
  // We aren't using APOP *OR* APOP authentication failed (since our server
  // migth not support APOP. 
  // Let's try the standard authentication mecanism.
  //
  [[self tcpConnection] writeLine: [NSString stringWithFormat: @"USER %@", theUsername]];
  
  if ( ![self responseFromServerIsValid: NULL] )
    {  
      // We MUST use APOP since the standard authentication doesn't work.
      return [self _APOPAuthenticate: theUsername  password: thePassword];
    }
  
  [[self tcpConnection] writeLine: [NSString stringWithFormat: @"PASS %@", thePassword]];
  
  if ( ![self responseFromServerIsValid: NULL] )
    {
      NSDebugLog( [NSString stringWithFormat: @"POP3 password is invalid on %@.", [self name]] );
      return NO;
    }
 
  return YES;
}


//
//
//
- (BOOL) responseFromServerIsValid: (NSString **) theResponse;
{
  NSString *aString;
  
  aString = [[self tcpConnection] readStringToEndOfLine];
  
  if (theResponse != NULL)
    {
      *theResponse = aString;
    }
  
  if ( aString && [[aString substringToIndex: 3] isEqualToString: @"+OK"] )
    {
      return YES;
    }
  
  return NO;
}


//
//
//
- (NSString *) name
{
  return name;
}


//
//
//
- (void) setName: (NSString *) theName
{
  RETAIN(theName);
  RELEASE(name);
  name = theName;
}


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


//
//
//
- (void) setPort: (int) thePort
{
  port = thePort;
}


//
//
//
- (id<Connection>) tcpConnection
{
  return tcpConnection;
}


//
//
//
- (NSString *) username
{
  return username;
}


//
//
//
- (NSArray *) supportedMechanisms
{
  if ( [self timestamp] )
    {
      return [NSArray arrayWithObject: @"APOP"];
    }
  
  return [NSArray array];
}


//
// The default folder in POP3 is always Inbox. This method will prefetch
// the messages of a POP3 folder if they haven't been prefetched before.
//
- (id) defaultFolder
{
  if ( !messagesHaveBeenPrefetched )
    {
      messagesHaveBeenPrefetched = YES;
      
      return [self folderForName: @"Inbox"
		   prefetch: YES];
    }
  
  return nil;
}


//
// This method will always return nil if theName is not
// equal to Inbox (case-insensitive) since you cannot
// access an other mailbox (other than Inbox) using the
// POP3 protocol.
//
- (id) folderForName: (NSString *) theName
{

  if ( [theName caseInsensitiveCompare: @"Inbox"] == NSOrderedSame )
    {
      return [self defaultFolder];
    }
  
  return nil;
}


//
//
//
- (POP3Folder *) folderForName: (NSString *) theName
		      prefetch: (BOOL) aBOOL
{
  if ( aBOOL )
    {
      [pop3Folder prefetch];
    }
  
  return pop3Folder;
}


//
//
//
- (id) folderForURL: (NSString *) theURL
{
  return [self defaultFolder];
}


//
//
//
- (BOOL) folderForNameIsOpen: (NSString *) theName
{
  if ( [theName caseInsensitiveCompare: @"Inbox"] == NSOrderedSame )
    {
      return YES;
    }

  return NO;
}


//
// No other folder is allowed in POP3 other than the Inbox.
// Also, this folder can only holds messages.
//
- (int) folderTypeForFolderName: (NSString *) theName
{
  return PantomimeHoldsMessages;
}


//
//
//
- (NSString *) folderSeparator
{
  return nil;
}


//
//
//
- (void) close
{
  // FIXME
  if ( [self isConnected] )
    {
      [[self tcpConnection] writeLine: @"QUIT"];
      [[self tcpConnection] close];
    }
}


//
// In POP3, you are NOT allowed to create a folder.
//
- (BOOL) createFolderWithName: (NSString *) theName 
			 type: (int) theType
		     contents: (NSData *) theContents
{
  return NO;
}


//
// In POP3, you are NOT allowed to delete a folder.
//
- (BOOL) deleteFolderWithName: (NSString *) theName
{
  return NO;
}


//
// In POP3, you are NOT allowed to rename a folder.
//
- (BOOL) renameFolderWithName: (NSString *) theName
                       toName: (NSString *) theNewName
{
  return NO;
}


//
//
//
- (NSString *) timestamp
{
  return timestamp;
}


//
//
//
- (BOOL) isConnected
{
  return YES;
}

@end


//
// Private methods
//
@implementation POP3Store (Private)

- (BOOL) _APOPAuthenticate: (NSString*) theUsername
		  password: (NSString*) thePassword
{
  NSString *aString;
  MD5 *aMD5;
  
  aMD5 = [[MD5 alloc] initWithString: [NSString stringWithFormat: @"%@%@", [self timestamp], thePassword]
		      encoding: NSASCIIStringEncoding];
  [aMD5 computeDigest];
  
  aString = [aMD5 digestAsString];
  
  [[self tcpConnection] writeLine: [NSString stringWithFormat: @"APOP %@ %@", theUsername, aString]];
  
  RELEASE(aMD5);
  
  if ( [self responseFromServerIsValid: NULL] )
    {
      return YES;
    }
  
  NSDebugLog(@"POP3Store: APOP authentication failed.");
  
  return NO;
}


//
//
//
- (BOOL) _postInit
{
  NSString *aString;
  BOOL aBOOL;

  if ( (aBOOL = [self responseFromServerIsValid: &aString]) )
    {
      NSRange range1, range2;
      NSDebugLog(@"POP3Store: Connected!");

      range1 = [aString rangeOfString: @"<"];
      range2 = [aString rangeOfString: @">"];

      if (range1.length && range2.length)
	{
	  ASSIGN(timestamp, [aString substringWithRange: NSMakeRange(range1.location,range2.location-range1.location+1)]);
	}
      else
	{
	  DESTROY(timestamp);
	}
    }

  return aBOOL;
}

@end
