From 803bf3b2a07681fc0fea8af98fdf8437095a0c8d Mon Sep 17 00:00:00 2001
From: root <root@example.com>
Date: Sun, 25 May 2014 23:56:59 +0200
Subject: [PATCH] annotation

---
 sope-mime/NGImap4/NGImap4Client.h             |    2 +
 sope-mime/NGImap4/NGImap4Client.m             |   78 +++++++++++++++++++++++-
 sope-mime/NGImap4/NGImap4ResponseNormalizer.m |    7 +++
 sope-mime/NGImap4/NGImap4ResponseParser.m     |   81 +++++++++++++++++++++++--
 4 files changed, 162 insertions(+), 6 deletions(-)

diff --git a/sope-mime/NGImap4/NGImap4Client.h b/sope-mime/NGImap4/NGImap4Client.h
index 8442062..a5c3d17 100644
--- a/sope-mime/NGImap4/NGImap4Client.h
+++ b/sope-mime/NGImap4/NGImap4Client.h
@@ -136,6 +136,8 @@ typedef enum {
 - (NSDictionary *)select:(NSString *)_folder;
 - (NSDictionary *)unselect;
 - (NSDictionary *)status:(NSString *)_folder flags:(NSArray *)_flags;
+- (NSDictionary *)annotation:(NSString *)_folder entryName:(NSString *)_entry attributeName:(NSString *)_attribute;
+- (NSDictionary *)annotation:(NSString *)_folder entryName:(NSString *)_entry attributeName:(NSString *)_attribute  attributeValue:(NSString *)_value;
 - (NSDictionary *)rename:(NSString *)_folder to:(NSString *)_newName;
 - (NSDictionary *)delete:(NSString *)_folder;
 - (NSDictionary *)create:(NSString *)_name;
diff --git a/sope-mime/NGImap4/NGImap4Client.m b/sope-mime/NGImap4/NGImap4Client.m
index 02af32b..a614515 100644
--- a/sope-mime/NGImap4/NGImap4Client.m
+++ b/sope-mime/NGImap4/NGImap4Client.m
@@ -118,7 +118,7 @@ static int          ProfileImapEnabled = -1;
 static int          LogImapEnabled     = -1;
 static int          PreventExceptions  = -1;
 static BOOL         fetchDebug         = NO;
-static BOOL         ImapDebugEnabled   = NO;
+static BOOL         ImapDebugEnabled   = YES;
 static NSArray      *Imap4SystemFlags  = nil;
 
 static NSMutableDictionary *capabilities;
@@ -908,6 +908,79 @@ static NSMutableDictionary *namespaces;
   return [self->normer normalizeResponse:[self processCommand:@"unselect"]];
 }
 
+- (NSDictionary *)annotation:(NSString *)_folder entryName:(NSString *)_entry attributeName:(NSString *)_attribute {
+/*
+ result dict looks like the following  
+{FolderList = {INBOX = {"/comment" = {"value.priv" = "sogo_73c_192bd57b_d8"; }; }; "Other Users/sogo2" = {"/comment" = {"value.priv" = "sogo_c0c_192bd7dc_0"; }; }; Sent = {"/comment" = {"value.priv" = "sogo_73c_192bd57b_da"; }; }; Trash = {"/comment" = {"value.priv" = "sogo_73c_192bd57b_dc"; }; }; abczzll = {"/comment" = {"value.priv" = "sogo_73c_192bd57b_d9"; }; }; "abczzll/mmabcmm" = {"/comment" = {"value.priv" = "sogo_73c_192bd57b_de"; }; }; mf1renamedd = {"/comment" = {"value.priv" = "sogo_73c_192bd57b_dd"; }; }; tfu1 = {"/comment" = {"value.priv" = "sogo_73c_192bd57b_db"; }; }; zuk = {"/comment" = {"value.priv" = "sogo_73c_192bd57b_df"; }; }; }; RawResponse = "{FolderList = ({INBOX = {\"/comment\" = {\"value.priv\" = \"sogo_73c_192bd57b_d8\"; }; }; }, {Sent = {\"/comment\" = {\"value.priv\" = \"sogo_73c_192bd57b_da\"; }; }; }, {Trash = {\"/comment\" = {\"value.priv\" = \"sogo_73c_192bd57b_dc\"; }; }; }, {abczzll = {\"/comment\" = {\"value.priv\" = \"sogo_73c_192bd57b_d9\"; }; }; }, {\"abczzll/mmabcmm\" = {\"/comment\" = {\"value.priv\" = \"sogo_73c_192bd57b_de\"; }; }; }, {mf1renamedd = {\"/comment\" = {\"value.priv\" = \"sogo_73c_192bd57b_dd\"; }; }; }, {tfu1 = {\"/comment\" = {\"value.priv\" = \"sogo_73c_192bd57b_db\"; }; }; }, {zuk = {\"/comment\" = {\"value.priv\" = \"sogo_73c_192bd57b_df\"; }; }; }, {\"Other Users/sogo2\" = {\"/comment\" = {\"value.priv\" = \"sogo_c0c_192bd7dc_0\"; }; }; }); ResponseResult = {description = Completed; result = ok; tagId = 13; }; }"; expunge = (); result = 1; }
+
+ getannotation:
+
+ result = [client annotation: folderName entryName: @"/comment" attributeName: @"value.priv"];
+ result = [client annotation: folderName entryName: @"/comment" attributeName: @"value"];
+ result = [client annotation: folderName entryName: @"/*" attributeName: @"value"];
+ result = [client annotation: @"" entryName: @"/*" attributeName: @"value"];
+
+*/
+  NSString *cmd;
+  NGHashMap *_map;
+  NSDictionary        *obj;
+  NSMutableDictionary *result, *folderList;
+  
+  if (_folder == nil)
+    return nil;
+  if ((_entry == nil))
+    return nil;
+  if ((_attribute == nil))
+    return nil;
+  if ((_folder = [self _folder2ImapFolder:_folder]) == nil)
+    return nil;
+  
+  cmd     = [NSString stringWithFormat:@"getannotation \"%@\" \"%@\" \"%@\"",
+                      SaneFolderName(_folder), _entry, _attribute];
+  
+  result  = [NSMutableDictionary dictionaryWithCapacity:2];
+  _map = [self processCommand:cmd];
+
+  result = [self->normer normalizeResponse:_map];
+
+  NSEnumerator        *enumerator;
+  enumerator = [_map objectEnumeratorForKey:@"FolderList"];
+  folderList  = [NSMutableDictionary dictionaryWithCapacity:5];
+  while ((obj = [enumerator nextObject]) != nil) {
+
+    [folderList addEntriesFromDictionary:[NSDictionary dictionaryWithDictionary:obj]];
+  }
+
+  [result setObject: folderList forKey: @"FolderList" ];
+
+  return result;
+
+}
+
+- (NSDictionary *)annotation:(NSString *)_folder entryName:(NSString *)_entry attributeName:(NSString *)_attribute attributeValue:(NSString *)_value {
+  NSString *cmd;
+  NSMutableDictionary *result;
+  
+  if (_folder == nil)
+    return nil;
+  if ((_entry == nil))
+    return nil;
+  if ((_attribute == nil))
+    return nil;
+  if ((_value == nil))
+    return nil;
+  if ((_folder = [self _folder2ImapFolder:_folder]) == nil)
+    return nil;
+  
+  cmd     = [NSString stringWithFormat:@"setannotation \"%@\" \"%@\" (\"%@\" \"%@\")",
+                      SaneFolderName(_folder), _entry, _attribute, _value];
+  
+  result  = [NSMutableDictionary dictionaryWithCapacity:2];
+  result = [self->normer normalizeResponse:[self processCommand:cmd]];
+  return result;
+
+}
+
 - (NSDictionary *)status:(NSString *)_folder flags:(NSArray *)_flags {
   NSString *cmd;
   
@@ -918,7 +991,7 @@ static NSMutableDictionary *namespaces;
   if ((_folder = [self _folder2ImapFolder:_folder]) == nil)
     return nil;
   
-  cmd     = [NSString stringWithFormat:@"status \"%@\" (%@)",
+  cmd = [NSString stringWithFormat:@"status \"%@\" (%@)",
                       SaneFolderName(_folder), [_flags componentsJoinedByString:@" "]];
   return [self->normer normalizeStatusResponse:[self processCommand:cmd]];
 }
@@ -988,6 +1061,7 @@ static NSMutableDictionary *namespaces;
   return [_parts componentsJoinedByString:@" "];
 }
 
+
 - (NSDictionary *)fetchUids:(NSArray *)_uids parts:(NSArray *)_parts {
   /*
     eg: 'UID FETCH 1189,1325,1326 ([TODO])'
diff --git a/sope-mime/NGImap4/NGImap4ResponseNormalizer.m b/sope-mime/NGImap4/NGImap4ResponseNormalizer.m
index 687c3dc..8e0f0be 100644
--- a/sope-mime/NGImap4/NGImap4ResponseNormalizer.m
+++ b/sope-mime/NGImap4/NGImap4ResponseNormalizer.m
@@ -268,6 +268,8 @@ static int      LogImapEnabled = -1;
         [result setObject:o forKey:@"highestmodseq"];
       else if ((o = [obj objectForKey:@"UIDNEXT"]))
         [result setObject:o forKey:@"uidnext"];
+      else if ((o = [obj objectForKey:@"UIDVALIDITY"]))
+        [result setObject:o forKey:@"uidvalidity"];
     }
     else
       [self warnWithFormat:@"unexpected OK object: %@", obj];
@@ -293,6 +295,7 @@ static int      LogImapEnabled = -1;
   return result;
 }
 
+
 - (NSDictionary *)normalizeStatusResponse:(NGHashMap *)_map {
   /*
     filter for status response
@@ -318,6 +321,10 @@ static int      LogImapEnabled = -1;
   }
   if ((o = [obj  objectForKey:@"unseen"]) != nil)
     [result setObject:o forKey:@"unseen"];
+
+  // support x-guid (dovecot)
+  if ((o = [obj  objectForKey:@"x-guid"]) != nil)
+    [result setObject:o forKey:@"x-guid"];
   
   return result;
 }
diff --git a/sope-mime/NGImap4/NGImap4ResponseParser.m b/sope-mime/NGImap4/NGImap4ResponseParser.m
index f14c889..0fcfe98 100644
--- a/sope-mime/NGImap4/NGImap4ResponseParser.m
+++ b/sope-mime/NGImap4/NGImap4ResponseParser.m
@@ -11,7 +11,7 @@
   SOPE 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.
+/bin/bash: 58: command not found
 
   You should have received a copy of the GNU Lesser General Public
   License along with SOPE; see the file COPYING.  If not, write to the
@@ -48,6 +48,7 @@
 - (BOOL)_parseVanishedResponseIntoHashMap:(NGMutableHashMap *)result_;
 - (BOOL)_parseByeUntaggedResponseIntoHashMap:(NGMutableHashMap *)result_;
 - (BOOL)_parseACLResponseIntoHashMap:(NGMutableHashMap *)result_;
+- (BOOL)_parseAnnotationResponseIntoHashMap:(NGMutableHashMap *)result_;
 - (BOOL)_parseMyRightsResponseIntoHashMap:(NGMutableHashMap *)result_;
 - (BOOL)_parseListRightsResponseIntoHashMap:(NGMutableHashMap *)result_;
 
@@ -604,7 +605,10 @@ static void _parseUntaggedResponse(NGImap4ResponseParser *self,
   l0 = _la(self, 0);
   switch (l0) {
   case 'A':
-    if ([self _parseACLResponseIntoHashMap:result_])
+    l1 = _la(self, 1);
+    if (l1 == 'C' && [self _parseACLResponseIntoHashMap:result_])
+      return;
+    if (l1 == 'N' && [self _parseAnnotationResponseIntoHashMap:result_]) 
       return;
     break;
     
@@ -1407,7 +1411,7 @@ _purifyQuotedString(NSMutableString *quotedString) {
 - (BOOL)_parseStatusResponseIntoHashMap:(NGMutableHashMap *)result_ {
   NSString            *name  = nil;
   NSMutableDictionary *flags = nil;
-  NSDictionary *d;
+  NSDictionary *d, *f;
     
   if (!_matchesString(self, "STATUS "))
     return NO;
@@ -1437,8 +1441,12 @@ _purifyQuotedString(NSMutableString *quotedString) {
     if (_la(self, 0) == ' ')
       _consume(self, 1);
       
+    if ([[key lowercaseString] isEqualToString:@"x-guid"])
+    [flags setObject:value
+          forKey:[key lowercaseString]];
+    else
     [flags setObject:[NumClass numberWithInt:[value intValue]]
-	   forKey:[key lowercaseString]];
+          forKey:[key lowercaseString]];
   }
   _consumeIfMatch(self, ')');
   _parseUntil(self, '\n');
@@ -1451,6 +1459,71 @@ _purifyQuotedString(NSMutableString *quotedString) {
   return YES;
 }
 
+- (BOOL)_parseAnnotationResponseIntoHashMap:(NGMutableHashMap *)result_ {
+  NSString            *name  = nil;
+  NSString            *entry  = nil;
+  NSMutableDictionary *attributes = nil;
+  NSDictionary *d, *f;
+
+  if (!_matchesString(self, "ANNOTATION "))
+    return NO;
+
+  _consume(self, 11);
+
+  if (_la(self, 0) == '"') {
+    name = [self _parseQuotedString];
+    _consumeIfMatch(self, ' ');
+  }
+  else if (_la(self, 0) == '{') {
+    name = [self _parseQuotedStringOrNIL];
+    _consumeIfMatch(self, ' ');
+  }
+  else {
+    name = _parseUntil(self, ' ');
+  }
+
+  if (_la(self, 0) == '"') {
+    entry = [self _parseQuotedString];
+    _consumeIfMatch(self, ' ');
+  }
+  else if (_la(self, 0) == '{') {
+    entry = [self _parseQuotedStringOrNIL];
+    _consumeIfMatch(self, ' ');
+  }
+  else {
+    entry = _parseUntil(self, ' ');
+  }
+
+  _consumeIfMatch(self, '(');
+
+  attributes = [NSMutableDictionary dictionaryWithCapacity:2];
+  d = [NSMutableDictionary dictionaryWithCapacity:2];
+  f = [NSMutableDictionary dictionaryWithCapacity:2];
+
+  while (_la(self, 0) != ')') {
+    NSString *key   = [self _parseQuotedString];
+    _consumeIfMatch(self, ' ');
+    NSString *value = [self _parseQuotedString];
+
+    if (_la(self, 0) == ' ')
+      _consume(self, 1);
+
+    [attributes setObject:value
+           forKey:[key lowercaseString]];
+
+    [d setObject:[NSDictionary dictionaryWithDictionary:attributes]
+       forKey:entry];
+  }
+  _consumeIfMatch(self, ')');
+  _parseUntil(self, '\n');
+
+  [f setObject:d forKey:name];
+  [result_ addObject:f forKey:@"FolderList"];
+
+  return YES;
+}
+
+
 - (BOOL)_parseByeUntaggedResponseIntoHashMap:(NGMutableHashMap *)result_ {
   NSString *reason;
   
-- 
1.7.9.5

