/*
  Copyright (C) 2007-2021 Inverse inc.

  This file is part of SOGo.

  SOGo 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, or (at your option) any
  later version.

  SOGo 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 SOGo; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/

#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSDictionary.h>
#import <Foundation/NSURL.h>
#import <Foundation/NSValue.h>

#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGExtensions/NGBase64Coding.h>
#import <NGExtensions/NSNull+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSString+misc.h>
#import <NGImap4/NGImap4Connection.h>
#import <NGImap4/NGImap4Client.h>
#import <NGImap4/NSString+Imap4.h>

#import <SOGo/NSArray+Utilities.h>
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoAuthenticator.h>
#import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <SOGo/SOGoUserSettings.h>
#import <SOGo/SOGoUserManager.h>
#import <SOGo/SOGoSieveManager.h>

#import "SOGoDraftsFolder.h"
#import "SOGoMailNamespace.h"
#import "SOGoSentFolder.h"
#import "SOGoTrashFolder.h"
#import "SOGoJunkFolder.h"
#import "SOGoTemplatesFolder.h"
#import "SOGoUser+Mailer.h"

#import "SOGoMailAccount.h"
#import <Foundation/NSProcessInfo.h>


#define XMLNS_INVERSEDAV @"urn:inverse:params:xml:ns:inverse-dav"

@implementation SOGoMailAccount

static NSString *inboxFolderName = @"INBOX";

- (id) init
{
  if ((self = [super init]))
    {
      NSString *sieveFolderEncoding = [[SOGoSystemDefaults sharedSystemDefaults] sieveFolderEncoding];
      inboxFolder = nil;
      draftsFolder = nil;
      sentFolder = nil;
      trashFolder = nil;
      junkFolder = nil;
      templatesFolder = nil;
      imapAclStyle = undefined;
      identities = nil;
      otherUsersFolderName = nil;
      sharedFoldersName = nil;
      subscribedFolders = nil;
      sieveFolderUTF8Encoding = [sieveFolderEncoding isEqualToString: @"UTF-8"];
    }

  return self;
}

- (void) dealloc
{
  [inboxFolder release];
  [draftsFolder release];
  [sentFolder release];
  [trashFolder release];
  [junkFolder release];
  [templatesFolder release];
  [identities release];
  [otherUsersFolderName release];
  [sharedFoldersName release];
  [subscribedFolders release];
  [super dealloc];
}

- (BOOL) isInDraftsFolder
{
  return NO;
}

- (BOOL) isInTemplatesFolder
{
  return NO;
}

- (void) _appendNamespace: (NSArray *) namespace
                toFolders: (NSMutableArray *) folders
{
  NSString *newFolder;
  NSDictionary *currentPart;
  int count, max;

  max = [namespace count];
  for (count = 0; count < max; count++)
    {
      currentPart = [namespace objectAtIndex: count];
      newFolder
        = [[currentPart objectForKey: @"prefix"] substringFromIndex: 1];
      if ([newFolder length])
        [folders addObjectUniquely: newFolder];
    }
}

- (void) _appendNamespaces: (NSMutableArray *) folders
{
  NSDictionary *namespaceDict;
  NSArray *namespace;
  NGImap4Client *client;

  client = [[self imap4Connection] client];
  namespaceDict = [client namespace];

  namespace = [namespaceDict objectForKey: @"personal"];
  // FIXME: Improve MacOSX Ventura support 
  // Check if the problem will be fixed by Apple or if this fix should be kept in the future
  // Ticket #5639
  if ([[context request] isMacOSXVenturaCalendarApp]) {
    namespace = [namespaceDict objectForKey: @"PERSONAL"];
  }
  if (namespace)
    [self _appendNamespace: namespace toFolders: folders];

  namespace = [namespaceDict objectForKey: @"other users"];
  if (namespace)
    {
      [self _appendNamespace: namespace toFolders: folders];
      ASSIGN(otherUsersFolderName, [folders lastObject]);
    }

  namespace = [namespaceDict objectForKey: @"shared"];
  if (namespace)
    {
      [self _appendNamespace: namespace toFolders: folders];
      ASSIGN(sharedFoldersName, [folders lastObject]);
    }
}

- (NSArray *) _namespacesWithKey: (NSString *) nsKey
{
  NSDictionary *namespaceDict;
  NSArray *namespace;
  NGImap4Client *client;
  NSMutableArray *folders;

  client = [[self imap4Connection] client];
  namespaceDict = [client namespace];
  namespace = [namespaceDict objectForKey: nsKey];
  if (namespace)
    {
      folders = [NSMutableArray array];
      [self _appendNamespace: namespace toFolders: folders];
    }
  else
    folders = nil;

  return folders;
}

- (NSArray *) otherUsersFolderNamespaces
{
  return [self _namespacesWithKey: @"other users"];
}

- (NSArray *) sharedFolderNamespaces
{
  return [self _namespacesWithKey: @"shared"];
}

- (NSArray *) toManyRelationshipKeysWithNamespaces: (BOOL) withNSs
{
  NSMutableArray *folders;
  NSArray *imapFolders, *nss;

  imapFolders = [[self imap4Connection] subfoldersForURL: [self imap4URL]];
  folders = [imapFolders mutableCopy];
  [folders autorelease];
  if (withNSs)
    [self _appendNamespaces: folders];
  else
    { /* some implementation insist on returning NSs in the list of
         folders... */
      nss = [self otherUsersFolderNamespaces];
      if (nss)
          [folders removeObjectsInArray: nss];
      nss = [self sharedFolderNamespaces];
      if (nss)
        [folders removeObjectsInArray: nss];
    }

  return [[folders resultsOfSelector: @selector (asCSSIdentifier)]
           stringsWithFormat: @"folder%@"];
}

- (NSArray *) toManyRelationshipKeys
{
  return [self toManyRelationshipKeysWithNamespaces: YES];
}

- (SOGoIMAPAclStyle) imapAclStyle
{
  SOGoDomainDefaults *dd;

  if (imapAclStyle == undefined)
    {
      dd = [[context activeUser] domainDefaults];
      if ([[dd imapAclStyle] isEqualToString: @"rfc2086"])
        imapAclStyle = rfc2086;
      else
        imapAclStyle = rfc4314;
    }

  return imapAclStyle;
}

/* see http://tools.ietf.org/id/draft-ietf-imapext-acl */
- (BOOL) imapAclConformsToIMAPExt
{
  NGImap4Client *imapClient;
  NSArray *capability;
  int count, max;
  BOOL conforms;

  conforms = NO;

  imapClient = [[self imap4Connection] client];
  capability = [[imapClient capability] objectForKey: @"capability"];
  max = [capability count];
  for (count = 0; !conforms && count < max; count++)
    {
      if ([[capability objectAtIndex: count] hasPrefix: @"acl2"])
	conforms = YES;
    }

  return conforms;
}

/* capabilities */
- (BOOL) hasCapability: (NSString *) capability
{
  NGImap4Client *imapClient;
  NSArray *capabilities;

  imapClient = [[self imap4Connection] client];
  capabilities = [[imapClient capability] objectForKey: @"capability"];

  return [capabilities containsObject: capability];
}

- (BOOL) supportsQuotas
{
  return [self hasCapability: @"quota"];
}

- (BOOL) supportsQResync
{
  return [self hasCapability: @"qresync"];
}

- (BOOL) supportsMove
{
  return [self hasCapability: @"move"];
}

- (id) getInboxQuota
{
  SOGoMailFolder *inbox;
  NGImap4Client *client;
  NSString *inboxName;
  SOGoDomainDefaults *dd;
  id inboxQuota, infos;
  float quota;

  inboxQuota = nil;
  if ([self supportsQuotas])
    {
      dd = [[context activeUser] domainDefaults];
      quota = [dd softQuotaRatio];
      inbox = [self inboxFolderInContext: context];
      inboxName = [NSString stringWithFormat: @"/%@", [inbox relativeImap4Name]];
      client = [[inbox imap4Connection] client];
      infos = [[client getQuotaRoot: [inbox relativeImap4Name]] objectForKey: @"quotas"];
      inboxQuota = [infos objectForKey: inboxName];
      if (quota != 0 && inboxQuota != nil)
	{
	  // A soft quota ratio is imposed for all users
          if ([[inboxQuota allKeys] containsObject: @"maxQuota"])
            {
              // Storage quota
              quota = quota * [(NSNumber*)[inboxQuota objectForKey: @"maxQuota"] intValue];
              inboxQuota = [NSDictionary dictionaryWithObjectsAndKeys:
                                             [NSNumber numberWithLong: (long)(quota+0.5)], @"maxQuota",
                                             [NSNumber numberWithLong: [[inboxQuota objectForKey: @"usedSpace"] longLongValue]], @"usedSpace",
                                         nil];
            }
          else if ([[inboxQuota allKeys] containsObject: @"maxMessages"])
            {
              // Messages quota
              quota = quota * [(NSNumber*)[inboxQuota objectForKey: @"maxMessages"] intValue];
              inboxQuota = [NSDictionary dictionaryWithObjectsAndKeys:
                                             [NSNumber numberWithLong: (long)(quota+0.5)], @"maxMessages",
                                             [NSNumber numberWithLong: [[inboxQuota objectForKey: @"messagesCount"] longLongValue]], @"messagesCount",
                                         nil];
            }
	}
    }

  return inboxQuota;
}

- (NSException *) updateFiltersAndForceActivation: (BOOL) forceActivation
{
  return [self updateFiltersWithUsername: nil
                             andPassword: nil
                         forceActivation: forceActivation];
}

- (NSException *) updateFilters
{
  return [self updateFiltersWithUsername: nil
                             andPassword: nil
                         forceActivation: NO];
}

- (NSException *) updateFiltersWithUsername: (NSString *) theUsername
                       andPassword: (NSString *) thePassword
                   forceActivation: (BOOL) forceActivation
{
  SOGoSieveManager *manager;

  manager = [SOGoSieveManager sieveManagerForUser: [context activeUser]];

  return [manager updateFiltersForAccount: self
                             withUsername: theUsername
                              andPassword: thePassword
                          forceActivation: forceActivation];
}


/* hierarchy */

- (SOGoMailAccount *) mailAccountFolder
{
  return self;
}

- (NSArray *) _allFoldersFromNS: (NSString *) namespace
                 subscribedOnly: (BOOL) subscribedOnly
{
  NSArray *folders;
  NSURL *nsURL;
  NSString *baseURLString, *urlString;

  baseURLString = [self imap4URLString];
  urlString = [NSString stringWithFormat: @"%@%@/", baseURLString, [namespace stringByEscapingURL]];
  nsURL = [NSURL URLWithString: urlString];
  folders = [[self imap4Connection] allFoldersForURL: nsURL
                               onlySubscribedFolders: subscribedOnly];

  return folders;
}

//
//
//
- (NSArray *) allFolderPaths: (SOGoMailListingMode) theListingMode
{
  NSMutableArray *folderPaths, *namespaces;
  NSArray *folders, *mainFolders;
  NSString *namespace;

  BOOL subscribedOnly;
  int count, max;

  if (theListingMode == SOGoMailStandardListing)
      subscribedOnly = [[[context activeUser] userDefaults] mailShowSubscribedFoldersOnly];
  else
    {
      subscribedOnly = NO;
      DESTROY(subscribedFolders);
      subscribedFolders = [[NSMutableDictionary alloc] init];
      folders = [[self imap4Connection] allFoldersForURL: [self imap4URL]
				   onlySubscribedFolders: YES];
      max = [folders count];
      for (count = 0; count < max; count++)
	{
	  [subscribedFolders setObject: [NSNull null]
				forKey: [folders objectAtIndex: count]];
	}
      [[self imap4Connection] flushFolderHierarchyCache];
    }

  mainFolders = [[NSArray arrayWithObjects:
			    [self inboxFolderNameInContext: context],
			  [self draftsFolderNameInContext: context],
			  [self sentFolderNameInContext: context],
			  [self trashFolderNameInContext: context],
			  [self junkFolderNameInContext: context],
			  [self templatesFolderNameInContext: context],
		    nil] stringsWithFormat: @"/%@"];
  folders = [[self imap4Connection] allFoldersForURL: [self imap4URL]
			       onlySubscribedFolders: subscribedOnly];
  folderPaths = [folders mutableCopy];
  [folderPaths autorelease];

  [folderPaths removeObjectsInArray: mainFolders];
  namespaces = [NSMutableArray arrayWithCapacity: 10];
  [self _appendNamespaces: namespaces];
  max = [namespaces count];
  for (count = 0; count < max; count++)
    {
      namespace = [namespaces objectAtIndex: count];
      folders = [self _allFoldersFromNS: namespace
                         subscribedOnly: subscribedOnly];
      if ([folders count])
        {
          [folderPaths removeObjectsInArray: folders];
          [folderPaths addObjectsFromArray: folders];

          // We make sure our "shared" / "public" namespace is always defined. Cyrus does NOT
          // return them in LIST while Dovecot does.
          namespace = [NSString stringWithFormat: @"/%@", namespace];
          [folderPaths removeObject: namespace];
          [folderPaths addObject: namespace];
        }
    }
  [folderPaths sortUsingSelector: @selector (localizedCaseInsensitiveCompare:)];
  [folderPaths replaceObjectsInRange: NSMakeRange (0, 0)
                withObjectsFromArray: mainFolders];

  return folderPaths;
}

//
//
//
- (NSString *) _folderType: (NSString *) folderName
                     flags: (NSMutableArray *) flags
{
  static NSDictionary *metadata = nil;
  NSString *folderType, *key, *rights;
  SOGoUserDefaults *ud;

  if (!metadata)
    {
      ud = [[context activeUser] userDefaults];
      metadata = [[[self imap4Connection] allFoldersMetadataForURL: [self imap4URL]
                                             onlySubscribedFolders: [ud mailShowSubscribedFoldersOnly]]
                   objectForKey: @"list"];
      [metadata retain];
    }

  key = [NSString stringWithFormat: @"/%@", folderName];
  [flags addObjectsFromArray: [metadata objectForKey: key]];

  // RFC6154 (https://tools.ietf.org/html/rfc6154) describes special uses for IMAP mailboxes.
  // We do honor them, as long as your SOGo{Drafts,Trash,Sent,Junk}FolderName are properly configured
  // See http://wiki.dovecot.org/MailboxSettings for a Dovecot example.
  if ([folderName isEqualToString: inboxFolderName])
    folderType = @"inbox";
  else if ([flags containsObject: [self draftsFolderNameInContext: context]] ||
           [folderName isEqualToString: [self draftsFolderNameInContext: context]])
    folderType = @"draft";
  else if ([flags containsObject: [self sentFolderNameInContext: context]] ||
           [folderName isEqualToString: [self sentFolderNameInContext: context]])
    folderType = @"sent";
  else if ([flags containsObject: [self trashFolderNameInContext: context]] ||
           [folderName isEqualToString: [self trashFolderNameInContext: context]])
    folderType = @"trash";
  else if ([flags containsObject: [self junkFolderNameInContext: context]] ||
           [folderName isEqualToString: [self junkFolderNameInContext: context]])
    folderType = @"junk";
  else if ([flags containsObject: [self templatesFolderNameInContext: context]] ||
           [folderName isEqualToString: [self templatesFolderNameInContext: context]])
    folderType = @"templates";
  else if ([folderName isEqualToString: otherUsersFolderName])
    folderType = @"otherUsers";
  else if ([folderName isEqualToString: sharedFoldersName])
    folderType = @"shared";
  else
    {
      folderType = @"folder";
      if (([sharedFoldersName length] && [folderName hasPrefix: sharedFoldersName]) ||
          ([otherUsersFolderName length] && [folderName hasPrefix: otherUsersFolderName]))
        {
          rights = [[self imap4Connection] myRightsForMailboxAtURL: [NSURL URLWithString: folderName]];
          if (![rights isKindOfClass: [NSException class]] &&
              ([rights rangeOfString: @"r"].location == NSNotFound))
            {
              [flags addObjectUniquely: @"noselect"];
              if ([rights rangeOfString: @"i"].location != NSNotFound)
                // No read but insert = dropbox
                folderType = @"dropbox";
            }
        }
    }

  return folderType;
}

//
//
//
- (NSMutableDictionary *) _insertFolder: (NSString *) folderPath
                            foldersList: (NSMutableArray *) theFolders
{
  NSArray *pathComponents;
  NSMutableArray *folders, *flags;
  NSMutableDictionary *currentFolder, *parentFolder, *folder;
  NSString *currentFolderName, *currentPath, *sievePath, *fullName, *folderType;
  SOGoUserManager *userManager;

  BOOL last, isOtherUsersFolder, parentIsOtherUsersFolder, isSubscribed;
  int i, j, count;

  parentFolder = nil;
  parentIsOtherUsersFolder = NO;
  pathComponents = [folderPath pathComponents];
  count = [pathComponents count];
  currentPath = @"";

  // Make sure all ancestors exist.
  // The variable folderPath is something like '/INBOX/Junk' so pathComponents becomes ('/', 'INBOX', 'Junk').
  // That's why we always ignore the first element
  for (i = 1; i < count; i++)
    {
      last = ((count - i) == 1);
      folder = nil;
      if ([currentPath length])
        currentPath = [NSString stringWithFormat: @"%@/%@", currentPath, [pathComponents objectAtIndex: i]];
      else
        currentPath = [pathComponents objectAtIndex: i];

      // Search for the current path in the children of the parent folder.
      // For the first iteration, take the parent folder passed as argument.
      if (parentFolder)
	folders = [parentFolder objectForKey: @"children"];
      else
	folders = theFolders;

      for (j = 0; j < [folders count]; j++)
        {
          currentFolder = [folders objectAtIndex: j];
          if ([currentPath isEqualToString: [currentFolder objectForKey: @"path"]])
            {
              folder = currentFolder;
              // Make sure all branches are ready to receive children
              if (!last && ![folder objectForKey: @"children"])
                  [folder setObject: [NSMutableArray array] forKey: @"children"];

	      break;
            }
        }

      // Check if the current folder is the "Other users" folder (shared mailboxes)
      currentFolderName = [[pathComponents objectAtIndex: i] stringByDecodingImap4FolderName];
      if (otherUsersFolderName
          && [currentFolderName caseInsensitiveCompare: otherUsersFolderName] == NSOrderedSame)
	isOtherUsersFolder = YES;
      else
	isOtherUsersFolder = NO;

      if (folder == nil)
        {
          // Folder was not found; create it and add it to the folders list
          if (parentIsOtherUsersFolder)
            {
              // Parent folder is the "Other users" folder; translate the user's mailbox name
              // to the full name of the person
              userManager = [SOGoUserManager sharedUserManager];
              if ((fullName = [userManager getCNForUID: currentFolderName]) && [fullName length])
                currentFolderName = fullName;
	      else if ((fullName = [userManager getEmailForUID: currentFolderName]) && [fullName length])
		currentFolderName = fullName;
            }
          else if (isOtherUsersFolder)
	    currentFolderName = [self labelForKey: @"OtherUsersFolderName"];
          else if (sharedFoldersName
                   && [currentFolderName caseInsensitiveCompare: sharedFoldersName] == NSOrderedSame)
	    currentFolderName = [self labelForKey: @"SharedFoldersName"];

          flags = [NSMutableArray array];

          if (last)
            {
              folderType = [self _folderType: currentPath
                                       flags: flags];
            }
          else
            {
              folderType = @"additional";
              [flags addObject: @"noselect"];
            }

	  if ([subscribedFolders objectForKey: folderPath])
	    isSubscribed = YES;
	  else
	    isSubscribed = NO;

          folder = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                          currentPath, @"path",
                                          folderType, @"type",
                                          currentFolderName, @"name",
                                          [NSMutableArray array], @"children",
					  flags, @"flags",
					  [NSNumber numberWithBool: isSubscribed], @"subscribed",
					  nil];

          if (sieveFolderUTF8Encoding)
            sievePath = [currentPath stringByDecodingImap4FolderName];
          else
            sievePath = currentPath;
          [folder setObject: sievePath forKey: @"sievePath"];

          // Either add this new folder to its parent or the list of root folders
          [folders addObject: folder];
        }

      parentFolder = folder;
      parentIsOtherUsersFolder = isOtherUsersFolder;
    }

  return parentFolder;
}

//
// Return a tree representation of the mailboxes
//
- (NSArray *) allFoldersMetadata: (SOGoMailListingMode) theListingMode
{
  NSString *currentFolder;
  NSMutableArray *folders;
  NSEnumerator *rawFolders;
  NSAutoreleasePool *pool;
  NSArray *allFolderPaths;

  allFolderPaths = [self allFolderPaths: theListingMode];
  rawFolders = [allFolderPaths objectEnumerator];
  folders = [NSMutableArray array];

  while ((currentFolder = [rawFolders nextObject]))
    {
      // Using a local pool to avoid using too many file descriptors. This could
      // happen with tons of mailboxes under "Other Users" as LDAP connections
      // are never reused and "autoreleased" at the end. This loop would consume
      // lots of LDAP connections during its execution.
      pool = [[NSAutoreleasePool alloc] init];

      // Insert folder into folders tree
      [self _insertFolder: currentFolder
              foldersList: folders];

      [pool release];
    }

  return folders;
}

- (NSDictionary *) _mailAccount
{
  NSDictionary *mailAccount;
  NSArray *accounts;
  SOGoUser *user;

  user = [SOGoUser userWithLogin: [self ownerInContext: nil]];
  accounts = [user mailAccounts];
  mailAccount = [accounts objectAtIndex: [nameInContainer intValue]];

  return mailAccount;
}

- (void) _appendDelegatorIdentities
{
  NSArray *delegators;
  SOGoUser *delegatorUser;
  NSDictionary *delegatorAccount;
  NSInteger count, max;

  delegators = [[SOGoUser userWithLogin: owner] mailDelegators];
  max = [delegators count];
  for (count = 0; count < max; count++)
    {
      delegatorUser = [SOGoUser
                        userWithLogin: [delegators objectAtIndex: count]];
      if (delegatorUser)
        {
          delegatorAccount = [[delegatorUser mailAccounts]
                                       objectAtIndex: 0];
          [identities addObjectsFromArray:
                        [delegatorAccount objectForKey: @"identities"]];
        }
    }
}

- (NSArray *) identities
{
  if (!identities)
    {
      identities = [[[self _mailAccount] objectForKey: @"identities"]
                     mutableCopy];
      if ([nameInContainer isEqualToString: @"0"])
        [self _appendDelegatorIdentities];
    }

  return identities;
}

- (NSDictionary *) defaultIdentity
{
  NSDictionary *defaultIdentity, *currentIdentity;
  unsigned int count, max;

  defaultIdentity = nil;
  [self identities];

  max = [identities count];
  for (count = 0; count < max; count++)
    {
      currentIdentity = [identities objectAtIndex: count];
      if ([[currentIdentity objectForKey: @"isDefault"] boolValue])
        {
          defaultIdentity = currentIdentity;
          break;
        }
    }

  return defaultIdentity; // can be nil
}

- (BOOL) forceDefaultIdentity
{
  return [[[self _mailAccount] objectForKey: @"forceDefaultIdentity"] boolValue];
}

- (NSDictionary *) identityForEmail: (NSString *) email
{
  NSDictionary *identity, *currentIdentity;
  NSString *currentEmail;
  unsigned int count, max;

  identity = nil;
  [self identities];

  max = [identities count];
  for (count = 0; count < max; count++)
    {
      currentIdentity = [identities objectAtIndex: count];
      currentEmail = [currentIdentity objectForKey: @"email"];
      if ([currentEmail caseInsensitiveCompare: email] == NSOrderedSame)
        {
          identity = currentIdentity;
          break;
        }
    }

  return identity; // can be nil
}

- (NSString *) signature
{
  NSDictionary *identity;
  NSString *signature;

  identity = [self defaultIdentity];

  if (identity)
    signature = [identity objectForKey: @"signature"];
  else
    signature = nil;

  return signature;
}

- (NSString *) encryption
{
  NSString *encryption;

  encryption = [[self _mailAccount] objectForKey: @"encryption"];
  if (![encryption length])
    encryption = @"none";

  return encryption;
}

- (NSString *) tlsVerifyMode
{
  NSString *verifyMode;

  verifyMode = [[self _mailAccount] objectForKey: @"tlsVerifyMode"];
  if (!verifyMode || ![verifyMode length])
    verifyMode = @"default";

  return verifyMode;
}

- (NSMutableString *) imap4URLString
{
  NSMutableString *imap4URLString;
  NSDictionary *mailAccount;
  NSString *encryption, *protocol, *username, *escUsername;
  int defaultPort, port;

  mailAccount = [self _mailAccount];
  encryption = [mailAccount objectForKey: @"encryption"];
  defaultPort = 143;
  protocol = @"imap";

  if ([encryption isEqualToString: @"ssl"])
    {
      protocol = @"imaps";
      defaultPort = 993;
    }
  else if ([encryption isEqualToString: @"tls"])
    {
      protocol = @"imaps";
    }

  username = [mailAccount objectForKey: @"userName"];
  escUsername
    = [[username stringByEscapingURL] stringByReplacingString: @"@"
                                                   withString: @"%40"];
  imap4URLString = [NSMutableString stringWithFormat: @"%@://%@@%@",
                                    protocol, escUsername,
                           [mailAccount objectForKey: @"serverName"]];
  port = [[mailAccount objectForKey: @"port"] intValue];
  if (port && port != defaultPort)
    [imap4URLString appendFormat: @":%d", port];

  [imap4URLString appendString: @"/"];

  return imap4URLString;
}

- (NSMutableString *) traversalFromMailAccount
{
  return [NSMutableString string];
}

//
// Extract password from basic authentication.
//
- (NSString *) imap4PasswordRenewed: (BOOL) renewed
{
  NSString *password;
  NSURL *imapURL;

  // Default account - ie., the account that is provided with a default
  // SOGo installation. User-added IMAP accounts will have name >= 1.
  if ([nameInContainer isEqualToString: @"0"])
    {
      imapURL = [self imap4URL];

      password = [[self authenticatorInContext: context]
                   imapPasswordInContext: context
		                  forURL: imapURL
                              forceRenew: renewed];
      if (!password)
        [self errorWithFormat: @"no IMAP4 password available"];
    }
  else
    {
      password = [[self _mailAccount] objectForKey: @"password"];
      if (!password)
        password = @"";
    }

  return password;
}


- (NSDictionary *) imapFolderGUIDs
{
  NSDictionary *result, *nresult;
  NSMutableDictionary *folders;
  NGImap4Client *client;
  SOGoUserDefaults *ud;
  NSArray *folderList;
  NSEnumerator *e;
  NSString *guid;
  id currentFolder;

  BOOL hasAnnotatemore;

  ud = [[context activeUser] userDefaults];

  // We skip the Junk folder here, as EAS doesn't know about this
  if ([ud synchronizeOnlyDefaultMailFolders])
    folderList = [[NSArray arrayWithObjects:
                             [self inboxFolderNameInContext: context],
                           [self draftsFolderNameInContext: context],
                           [self sentFolderNameInContext: context],
                           [self trashFolderNameInContext: context],
                     nil] stringsWithFormat: @"/%@"];
  else
    folderList = [self allFolderPaths: SOGoMailStandardListing];

  folders = [NSMutableDictionary dictionary];

  client = [[self imap4Connection] client];
  hasAnnotatemore = [self hasCapability: @"annotatemore"];

  if (hasAnnotatemore)
    result = [client annotation: @"*"  entryName: @"/comment" attributeName: @"value.priv"];
  else
    result = [client lstatus: @"*" flags: [NSArray arrayWithObjects: @"x-guid", nil]];

  e = [folderList objectEnumerator];

  while ((currentFolder = [[e nextObject] substringFromIndex: 1]))
    {
      if (hasAnnotatemore)
        guid = [[[[result objectForKey: @"FolderList"] objectForKey: currentFolder] objectForKey: @"/comment"] objectForKey: @"value.priv"];
      else
        guid = [[[result objectForKey: @"status"] objectForKey: currentFolder] objectForKey: @"x-guid"];

      if (!guid || ![guid isNotNull])
        {
          // Don't generate a GUID for "Other users" and "Shared" namespace folders - user foldername instead
          if ((otherUsersFolderName && [currentFolder isEqualToString: otherUsersFolderName]) ||
              (sharedFoldersName && [currentFolder isEqualToString: sharedFoldersName]))
             guid = [NSString stringWithFormat: @"%@", currentFolder];
          // If Dovecot is used with mail_shared_explicit_inbox = yes we have to generate a guid for "shared/user".
          // * LIST (\NonExistent \HasChildren) "/" shared
          // * LIST (\NonExistent \HasChildren) "/" shared/jdoe@example.com
          // * LIST (\HasNoChildren) "/" shared/jdoe@example.com/INBOX
          else if (!hasAnnotatemore &&
		   (([[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"nonexistent"] != NSNotFound ||
                     [[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"noselect"] != NSNotFound)&&
		    [[[result objectForKey: @"list"] objectForKey: currentFolder] indexOfObject: @"haschildren"] != NSNotFound))
            guid = [NSString stringWithFormat: @"%@", currentFolder];
          else
            {
              // If folder doesn't exists - ignore it.
              nresult = [client status: currentFolder
                                 flags: [NSArray arrayWithObject: @"UIDVALIDITY"]];
              if (![[nresult valueForKey: @"result"] boolValue])
                continue;
              else if (hasAnnotatemore)
                 {
                   guid = [[NSProcessInfo processInfo] globallyUniqueString];
                   nresult = [client annotation: currentFolder entryName: @"/comment" attributeName: @"value.priv" attributeValue: guid];
                 }

             // setannotation failed or annotatemore is not available
             if ((hasAnnotatemore && ![[nresult objectForKey: @"result"] boolValue]) || !hasAnnotatemore)
               guid = [NSString stringWithFormat: @"%@", currentFolder];
            }
        }

      [folders setObject: [NSString stringWithFormat: @"folder%@", guid] forKey: [NSString stringWithFormat: @"folder%@", currentFolder]];
    }

  return folders;
}


/* name lookup */

- (id) lookupNameByPaths: (NSArray *) _paths
               inContext: (id)_ctx
                 acquire: (BOOL) _flag
{
  NSString *folderName;
  NSUInteger count, max;
  SOGoMailBaseObject *folder;

  max = [_paths count];
  folder = self;
  for (count = 0; folder && count < max; count++)
    {
      folderName = [_paths objectAtIndex: count];
      folder = [folder lookupName: folderName inContext: _ctx acquire: _flag];
    }

  return folder;
}

- (id) lookupName: (NSString *) _key
	inContext: (id)_ctx
	  acquire: (BOOL) _flag
{
  NSString *folderName;
  NSMutableArray *namespaces;
  Class klazz;
  id obj;

  [[[self imap4Connection] client] namespace];

  if ([_key hasPrefix: @"folder"])
    {
      folderName = [[_key substringFromIndex: 6] fromCSSIdentifier];

      namespaces = [NSMutableArray array];
      [self _appendNamespaces: namespaces];
      if ([namespaces containsObject: folderName])
        klazz = [SOGoMailNamespace class];
      else if ([folderName
		 isEqualToString: [self draftsFolderNameInContext: _ctx]])
	klazz = [SOGoDraftsFolder class];
      else if ([folderName
                 isEqualToString: [self sentFolderNameInContext: _ctx]])
	klazz = [SOGoSentFolder class];
      else if ([folderName
		 isEqualToString: [self trashFolderNameInContext: _ctx]])
	klazz = [SOGoTrashFolder class];
      else if ([folderName
		 isEqualToString: [self junkFolderNameInContext: _ctx]])
	klazz = [SOGoJunkFolder class];
      else if ([folderName
		 isEqualToString: [self templatesFolderNameInContext: _ctx]])
	klazz = [SOGoTemplatesFolder class];
      else
	klazz = [SOGoMailFolder class];

      obj = [klazz objectWithName: _key inContainer: self];
    }
  else
    obj = [super lookupName: _key inContext: _ctx acquire: NO];

  /* return 404 to stop acquisition */
  if (!obj)
    obj = [NSException exceptionWithHTTPStatus: 404 /* Not Found */];

  return obj;
}

/* special folders */

- (NSString *) inboxFolderNameInContext: (id)_ctx
{
  /* cannot be changed in Cyrus ? */
  return inboxFolderName;
}

- (NSString *) _userFolderNameWithPurpose: (NSString *) purpose
{
  SOGoUser *user;
  NSArray *accounts;
  int accountIdx;
  NSDictionary *account;
  NSString *folderName;

  folderName = nil;

  user = [SOGoUser userWithLogin: [self ownerInContext: nil]];
  accounts = [user mailAccounts];
  accountIdx = [nameInContainer intValue];
  account = [accounts objectAtIndex: accountIdx];
  folderName = [[account objectForKey: @"specialMailboxes"]
                 objectForKey: purpose];
  if (!folderName && accountIdx > 0)
    {
      account = [accounts objectAtIndex: 0];
      folderName = [[account objectForKey: @"specialMailboxes"]
                     objectForKey: purpose];
    }

  return folderName;
}

- (NSString *) draftsFolderNameInContext: (id) _ctx
{
  return [self _userFolderNameWithPurpose: @"Drafts"];
}

- (NSString *) sentFolderNameInContext: (id)_ctx
{
  return [self _userFolderNameWithPurpose: @"Sent"];
}

- (NSString *) trashFolderNameInContext: (id)_ctx
{
  return [self _userFolderNameWithPurpose: @"Trash"];
}

- (NSString *) junkFolderNameInContext: (id)_ctx
{
  return [self _userFolderNameWithPurpose: @"Junk"];
}

- (NSString *) templatesFolderNameInContext: (id)_ctx
{
  return [self _userFolderNameWithPurpose: @"Templates"];
}

- (NSString *) otherUsersFolderNameInContext: (id)_ctx
{
  return otherUsersFolderName;
}

- (NSString *) sharedFoldersNameInContext: (id)_ctx
{
  return sharedFoldersName;
}

- (id) folderWithTraversal: (NSString *) traversal
	      andClassName: (NSString *) className
{
  NSArray *paths;
  NSString *currentName;
  id currentContainer;
  unsigned int count, max;
  Class clazz;

  currentContainer = self;
  paths = [traversal componentsSeparatedByString: @"/"];

  if (!className)
    clazz = [SOGoMailFolder class];
  else
    clazz = NSClassFromString (className);

  max = [paths count];
  for (count = 0; count < max - 1; count++)
    {
      currentName = [NSString stringWithFormat: @"folder%@",
			      [paths objectAtIndex: count]];
      currentContainer = [SOGoMailFolder objectWithName: currentName
					 inContainer: currentContainer];
    }
  currentName = [NSString stringWithFormat: @"folder%@",
			  [paths objectAtIndex: max - 1]];

  return [clazz objectWithName: currentName inContainer: currentContainer];
}

- (SOGoMailFolder *) inboxFolderInContext: (id) _ctx
{
  // TODO: use some profile to determine real location, use a -traverse lookup
  if (!inboxFolder)
    {
      inboxFolder
	= [self folderWithTraversal: [self inboxFolderNameInContext: _ctx]
		andClassName: nil];
      [inboxFolder retain];
    }

  return inboxFolder;
}

- (SOGoDraftsFolder *) draftsFolderInContext: (id) _ctx
{
  // TODO: use some profile to determine real location, use a -traverse lookup

  if (!draftsFolder)
    {
      draftsFolder
	= [self folderWithTraversal: [self draftsFolderNameInContext: _ctx]
		andClassName: @"SOGoDraftsFolder"];
      [draftsFolder retain];
    }

  return draftsFolder;
}

- (SOGoSentFolder *) sentFolderInContext: (id) _ctx
{
  // TODO: use some profile to determine real location, use a -traverse lookup

  if (!sentFolder)
    {
      sentFolder
	= [self folderWithTraversal: [self sentFolderNameInContext: _ctx]
		andClassName: @"SOGoSentFolder"];
      [sentFolder retain];
    }

  return sentFolder;
}

- (SOGoTrashFolder *) trashFolderInContext: (id) _ctx
{
  if (!trashFolder)
    {
      trashFolder
	= [self folderWithTraversal: [self trashFolderNameInContext: _ctx]
		andClassName: @"SOGoTrashFolder"];
      [trashFolder retain];
    }

  return trashFolder;
}

- (SOGoJunkFolder *) junkFolderInContext: (id) _ctx
{
  if (!junkFolder)
    {
      junkFolder
	= [self folderWithTraversal: [self junkFolderNameInContext: _ctx]
                       andClassName: @"SOGoJunkFolder"];
      [junkFolder retain];
    }

  return junkFolder;
}

- (SOGoTemplatesFolder *) templatesFolderInContext: (id) _ctx
{
  if (!templatesFolder)
    {
      templatesFolder
	= [self folderWithTraversal: [self templatesFolderNameInContext: _ctx]
                       andClassName: @"SOGoTemplatesFolder"];
      [templatesFolder retain];
    }

  return templatesFolder;
}

/* account delegation */
- (NSArray *) delegates
{
  NSDictionary *mailSettings;
  SOGoUser *ownerUser;
  NSArray *delegates;

  if ([nameInContainer isEqualToString: @"0"])
    {
      ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context]];
      mailSettings = [[ownerUser userSettings] objectForKey: @"Mail"];
      delegates = [mailSettings objectForKey: @"DelegateTo"];
      if (!delegates)
        delegates = [NSArray array];
    }
  else
    delegates = nil;

  return delegates;
}

- (void) _setDelegates: (NSArray *) newDelegates
{
  NSMutableDictionary *mailSettings;
  SOGoUser *ownerUser;
  SOGoUserSettings *settings;

  ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context]];
  settings = [ownerUser userSettings];
  mailSettings = [settings objectForKey: @"Mail"];
  if (!mailSettings)
    {
      mailSettings = [NSMutableDictionary dictionaryWithCapacity: 1];
      [settings setObject: mailSettings forKey: @"Mail"];
    }
  [mailSettings setObject: newDelegates
                   forKey: @"DelegateTo"];
  [settings synchronize];
}

- (void) addDelegates: (NSArray *) newDelegates
{
  NSMutableArray *delegates;
  NSInteger count, max;
  NSString *currentDelegate;
  SOGoUser *delegateUser;

  if ([nameInContainer isEqualToString: @"0"])
    {
      delegates = [[self delegates] mutableCopy];
      [delegates autorelease];
      max = [newDelegates count];
      for (count = 0; count < max; count++)
        {
          currentDelegate = [newDelegates objectAtIndex: count];
          delegateUser = [SOGoUser userWithLogin: currentDelegate];
          if (delegateUser)
            {
              [delegates addObjectUniquely: currentDelegate];
              [delegateUser addMailDelegator: owner];
            }
        }

      [self _setDelegates: delegates];
    }
}

- (void) removeDelegates: (NSArray *) oldDelegates
{
  NSMutableArray *delegates;
  NSInteger count, max;
  NSString *currentDelegate;
  SOGoUser *delegateUser;

  if ([nameInContainer isEqualToString: @"0"])
    {
      delegates = [[self delegates] mutableCopy];
      [delegates autorelease];
      max = [oldDelegates count];
      for (count = 0; count < max; count++)
        {
          currentDelegate = [oldDelegates objectAtIndex: count];
          delegateUser = [SOGoUser userWithLogin: currentDelegate];
          [delegates removeObject: currentDelegate];
          if (delegateUser)
            [delegateUser removeMailDelegator: owner];
        }

      [self _setDelegates: delegates];
    }
}

/* WebDAV */

- (NSString *) davContentType
{
  return @"httpd/unix-directory";
}

- (BOOL) davIsCollection
{
  return YES;
}

- (NSException *) davCreateCollection: (NSString *) _name
			    inContext: (id) _ctx
{
  return [[self imap4Connection] createMailbox:_name atURL:[self imap4URL]];
}

- (NSString *) davDisplayName
{
  return [[self _mailAccount] objectForKey: @"name"];
}

- (NSData *) certificate
{
  NSData *mailCertificate;
  SOGoUserDefaults *ud;

  mailCertificate = nil;
  ud = [[context activeUser] userDefaults];

  if ([nameInContainer isEqualToString: @"0"])
    {
      mailCertificate = [[ud stringForKey: @"SOGoMailCertificate"] dataByDecodingBase64];
    }
  else
    {
      int accountIdx;
      NSArray* accounts;
      NSDictionary *account, *security;

      accountIdx = [nameInContainer intValue] - 1;
      accounts = [ud auxiliaryMailAccounts];
      if ([accounts count] > accountIdx)
        {
          account = [accounts objectAtIndex: accountIdx];
          security = [account objectForKey: @"security"];
          if (security && [[security objectForKey: @"certificate"] length])
            {
              mailCertificate = [[security objectForKey: @"certificate"] dataByDecodingBase64];
            }
        }
    }

  return mailCertificate;
}

- (void) setCertificate: (NSData *) theData
{
  SOGoUserDefaults *ud;

  ud = [[context activeUser] userDefaults];

  if ([nameInContainer isEqualToString: @"0"])
    {
      if ([theData length])
        [ud setObject: [theData stringByEncodingBase64]  forKey: @"SOGoMailCertificate"];
      else
        [ud removeObjectForKey: @"SOGoMailCertificate"];
    }
  else
    {
      int accountIdx;
      NSArray* accounts;
      NSMutableDictionary *account, *security;

      accountIdx = [nameInContainer intValue] - 1;
      accounts = [ud auxiliaryMailAccounts];
      if ([accounts count] > accountIdx)
        {
          account = [accounts objectAtIndex: accountIdx];
          security = [account objectForKey: @"security"];
          if (!security)
            {
              security = [NSMutableDictionary dictionary];
              [account setObject: security forKey: @"security"];
            }
          if ([theData length])
            [security setObject: [theData stringByEncodingBase64] forKey: @"certificate"];
          else
            [security removeObjectForKey: @"certificate"];
          [ud setAuxiliaryMailAccounts: accounts];
        }
    }

  [ud synchronize];
}


@end /* SOGoMailAccount */
