]> err.no Git - sope/commitdiff
improved Sieve client
authorhelge <helge@e4a50df8-12e2-0310-a44c-efbce7f8a7e3>
Sun, 28 Nov 2004 17:58:43 +0000 (17:58 +0000)
committerhelge <helge@e4a50df8-12e2-0310-a44c-efbce7f8a7e3>
Sun, 28 Nov 2004 17:58:43 +0000 (17:58 +0000)
git-svn-id: http://svn.opengroupware.org/SOPE/trunk@418 e4a50df8-12e2-0310-a44c-efbce7f8a7e3

sope-mime/ChangeLog
sope-mime/NGImap4/ChangeLog
sope-mime/NGImap4/NGImap4ResponseParser.h
sope-mime/NGImap4/NGSieveClient.h
sope-mime/NGImap4/NGSieveClient.m
sope-mime/Version
sope-mime/samples/GNUmakefile
sope-mime/samples/ImapTool.h
sope-mime/samples/Mime2XmlTool.h
sope-mime/samples/sievetool.m [new file with mode: 0644]

index a1c8175a8878a081cf890783635205862d1fa16b..375acd26ca3513597c1e346547d41740246315df 100644 (file)
@@ -1,3 +1,7 @@
+2004-11-28  Helge Hess  <helge.hess@skyrix.com>
+
+       * NGImap4: improvements in the Sieve client (v4.5.201)
+
 2004-11-19  Helge Hess  <helge.hess@skyrix.com>
 
        * NGImap4: minor code cleanups (v4.5.200)
index 141136db151f8f18fee11a518733f58e065f3be6..502f05d74ca8a8d048ea79408b26c1494a903a3e 100644 (file)
@@ -1,3 +1,13 @@
+2004-11-28  Helge Hess  <helge.hess@skyrix.com>
+
+       * NGSieveClient.m: can init using a URL, prepared some parsing methods,
+         open connection on demand when login:password: is called, added
+         support for -listScripts and -getScript:
+
+       * NGSieveClient.m: added a buffered stream for raw IO, added proper
+         error handling in some methods, added support for 'NSData commands',
+         properly convert commands to UTF-8
+
 2004-11-19  Helge Hess  <helge.hess@skyrix.com>
 
        * NGSieveClient.m, NGImap4Client.m: minor code cleanups
index f716852faaba3aa71106816285a90eeadb536c33..75c426f58563fd6743db64c83861cd6ba6341354 100644 (file)
@@ -1,7 +1,7 @@
 /*
   Copyright (C) 2000-2004 SKYRIX Software AG
 
-  This file is part of OGo
+  This file is part of OpenGroupware.org.
 
   OGo 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
@@ -18,7 +18,6 @@
   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
   02111-1307, USA.
 */
-// $Id$
 
 #ifndef __OGo_NGImap4_NGImap4ResponseParser_H__
 #define __OGo_NGImap4_NGImap4ResponseParser_H__
index 2545ce5e145040ad36b7a1ba7567855cb94971f4..25192d7ae9da8e0896a1b1cbb204b7ee08456c36 100644 (file)
 #include <NGImap4/NGImap4Support.h>
 #include <NGImap4/NGImap4ResponseParser.h>
 
-@class NSMutableArray, NSString, NSNumber, NSDictionary, NSArray;
-@class NGSieveResponseParser, EOQualifier, NGHashMap;
+/*
+  NGSieveClient
+  
+  This implements a client for server stored Sieve scripts as supported by
+  the Cyrus IMAP server.
+*/
+
+@class NSMutableArray, NSString, NSNumber, NSDictionary, NSArray, NSException;
+@class EOQualifier;
+@class NGHashMap;
+@class NGBufferedStream;
 
 typedef enum {
   UnConnected_NGSieveState = 1,
@@ -40,10 +49,11 @@ typedef enum {
 @interface NGSieveClient : NSObject
 {
 @protected
-  id<NGActiveSocket>       socket;
-  id<NGExtendedTextStream> text;
-  id<NGSocketAddress>      address;
-  NGImap4ResponseParser    *parser;
+  id<NGActiveSocket>    socket;
+  NGBufferedStream      *io;
+  id<NGSocketAddress>   address;
+  NGImap4ResponseParser *parser;
+  NSException           *lastException;
 
   BOOL     isLogin;
 
@@ -53,9 +63,11 @@ typedef enum {
   BOOL debug;
 }
 
++ (id)clientWithURL:(id)_url;
 + (id)clientWithAddress:(id<NGSocketAddress>)_address;
 + (id)clientWithHost:(id)_host;
 
+- (id)initWithURL:(id)_url;
 - (id)initWithHost:(id)_host;
 - (id)initWithAddress:(id<NGSocketAddress>)_address;
 
@@ -64,6 +76,11 @@ typedef enum {
 - (id<NGActiveSocket>)socket;
 - (id<NGSocketAddress>)address;
 
+/* exceptions */
+
+- (NSException *)lastException;
+- (void)resetLastException;
+
 /* connection */
 
 - (NSDictionary *)openConnection;
@@ -76,11 +93,11 @@ typedef enum {
 - (NSDictionary *)login:(NSString *)_login password:(NSString *)_passwd;
 - (NSDictionary *)logout;
 
-- (NSDictionary *)getScript:(NSString *)_scriptName;
+- (NSString *)getScript:(NSString *)_scriptName;
 - (NSDictionary *)putScript:(NSString *)_name script:(NSString *)_script;
 - (NSDictionary *)setActiveScript:(NSString *)_name;
 - (NSDictionary *)deleteScript:(NSString *)_script;
-- (NSDictionary *)listScript:(NSString *)_script;
+- (NSDictionary *)listScripts;
 
 /* equality */
 
index 4d4a8d4387514a37394382a898d2f16ba2006df1..d3478d6165f2087fee15e1dc7b7fcf6b5288b41b 100644 (file)
 
 @interface NGSieveClient(Private)
 
-- (NGHashMap *)processCommand:(NSString *)_command;
-- (NGHashMap *)processCommand:(NSString *)_command logText:(NSString *)_txt;
+- (NGHashMap *)processCommand:(id)_command;
+- (NGHashMap *)processCommand:(id)_command logText:(id)_txt;
 
-- (void)sendCommand:(NSString *)_command;
-- (void)sendCommand:(NSString *)_command logText:(NSString *)_txt;
+- (NSException *)sendCommand:(id)_command;
+- (NSException *)sendCommand:(id)_command logText:(id)_txt;
+- (NSException *)sendCommand:(id)_command logText:(id)_txt attempts:(int)_c;
 
 - (NSMutableDictionary *)normalizeResponse:(NGHashMap *)_map;
 - (NSMutableDictionary *)normalizeOpenConnectionResponse:(NGHashMap *)_map;
 - (NSDictionary *)login;
 
+/* parsing */
+
+- (NSString *)readStringToCRLF;
+- (NSString *)readString;
+
 @end
 
 /*
@@ -85,15 +91,48 @@ static BOOL     debugImap4         = NO;
   NoNumber  = [[NSNumber numberWithBool:NO] retain];
 }
 
++ (id)clientWithURL:(id)_url {
+  return [[[self alloc] initWithURL:_url] autorelease];
+}
+
 + (id)clientWithAddress:(id<NGSocketAddress>)_address {
-  return
-    [[(NGSieveClient *)[self alloc] initWithAddress:_address] autorelease];
+  NGSieveClient *client;
+  
+  client = [self alloc];
+  return [[client initWithAddress:_address] autorelease];
 }
 
 + (id)clientWithHost:(id)_host {
   return [[[self alloc] initWithHost:_host] autorelease];
 }
 
+- (id)initWithNSURL:(NSURL *)_url {
+  NGInternetSocketAddress *a;
+  int port;
+  
+  if ((port = [[_url port] intValue]) == 0)
+    port = defaultSievePort;
+  
+  a = [NGInternetSocketAddress addressWithPort:port 
+                              onHost:[_url host]];
+  if ((self = [self initWithAddress:a])) {
+    self->login    = [[_url user]     copy];
+    self->password = [[_url password] copy];
+  }
+  return self;
+}
+- (id)initWithURL:(id)_url {
+  if (_url == nil) {
+    [self release];
+    return nil;
+  }
+  
+  if (![_url isKindOfClass:[NSURL class]])
+    _url = [NSURL URLWithString:[_url stringValue]];
+  
+  return [self initWithNSURL:_url];
+}
+
 - (id)initWithHost:(id)_host {
   NGInternetSocketAddress *a;
   
@@ -104,14 +143,15 @@ static BOOL     debugImap4         = NO;
 - (id)initWithAddress:(id<NGSocketAddress>)_address { // di
   if ((self = [super init])) {
     self->address = [_address retain];
-    self->debug = debugImap4;
+    self->debug   = debugImap4;
   }
   return self;
 }
 
 - (void)dealloc {
-  [self->text     release];
+  [self->lastException release];
   [self->address  release];
+  [self->io       release];
   [self->socket   release];
   [self->parser   release];
   [self->login    release];
@@ -145,9 +185,26 @@ static BOOL     debugImap4         = NO;
   return self->address;
 }
 
+/* exceptions */
+
+- (void)setLastException:(NSException *)_ex {
+  ASSIGN(self->lastException, _ex);
+}
+- (NSException *)lastException {
+  return self->lastException;
+}
+- (void)resetLastException {
+  [self->lastException release];
+  self->lastException = nil;
+}
+
 /* connection */
 
-/* Opens a connection to given Host. */
+- (void)resetStreams {
+  [self->socket release]; self->socket = nil;
+  [self->io     release]; self->io     = nil;
+  [self->parser release]; self->parser = nil;
+}
 
 - (NSDictionary *)openConnection {
   struct timeval tv;
@@ -157,15 +214,17 @@ static BOOL     debugImap4         = NO;
     gettimeofday(&tv, NULL);
     ti =  (double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0);
   }
-
-  [self->socket release]; self->socket = nil;
-  [self->parser release]; self->parser = nil;
-  [self->text   release]; self->text   = nil;
+  
+  [self resetStreams];
   
   self->socket =
     [[NGActiveSocket socketConnectedToAddress:self->address] retain];
-  self->text   = 
-    [(NGCTextStream *)[NGCTextStream alloc] initWithSource:self->socket];
+  if (self->socket == nil) {
+    [self logWithFormat:@"ERROR: could not connect: %@", self->address];
+    return nil;
+  }
+  
+  self->io     = [[NGBufferedStream alloc] initWithSource:self->socket];
   self->parser = [[NGImap4ResponseParser alloc] initWithStream:self->socket];
 
   /* receive greeting from server without tag-id */
@@ -180,30 +239,26 @@ static BOOL     debugImap4         = NO;
                [self->parser parseSieveResponse]];
 }
 
-/*"
-** Check whether stream is already open (could be closed because server-timeout)
-"*/
-
 - (NSNumber *)isConnected {
+  // TODO: why does this return a number?!
+  /* 
+     Check whether stream is already open (could be closed because
+     server-timeout
+  */
   return [NSNumber numberWithBool:
                      (self->socket == nil)
                      ? NO : [(NGActiveSocket *)self->socket isAlive]];
 }
 
-/*"
-** Close a consisting connection.
-"*/
-
 - (void)closeConnection {
   [self->socket close];
   [self->socket release]; self->socket = nil;
-  [self->text   release]; self->text   = nil; 
   [self->parser release]; self->parser = nil;
 }
 
-/* login with plaintext password authenticating */
-
 - (NSDictionary *)login:(NSString *)_login password:(NSString *)_passwd {
+  /* login with plaintext password authenticating */
+  
   if ((_login == nil) || (_passwd == nil))
     return nil;
   
@@ -226,13 +281,21 @@ static BOOL     debugImap4         = NO;
   NSData    *auth;
   char      *buf;
   int       bufLen, logLen;
-
-
+  
+  if (![self->socket isConnected]) {
+    id con;
+    
+    if ((con = [self openConnection]) == nil)
+      return nil;
+    if (![[con objectForKey:@"result"] boolValue])
+      return con;
+  }
+  
   logLen = [self->login cStringLength];
   bufLen = (logLen * 2) + [self->password cStringLength] +2;
   
-  buf = calloc(sizeof(char), bufLen);
-
+  buf = calloc(bufLen + 2, sizeof(char));
+  
   sprintf(buf, "%s %s %s", 
           [self->login cString], [self->login cString],
           [self->password cString]);
@@ -245,7 +308,7 @@ static BOOL     debugImap4         = NO;
   
   if (LOG_PASSWORD == 1) {
     NSString *s;
-
+    
     s = [NSString stringWithFormat:@"AUTHENTICATE \"PLAIN\" {%d+}\r\n%s",
                   [auth length], [auth bytes]];
     map = [self processCommand:s];
@@ -258,7 +321,7 @@ static BOOL     debugImap4         = NO;
     map = [self processCommand:s
                 logText:@"AUTHENTICATE \"PLAIN\" {%d+}\r\nLOGIN:PASSWORD\r\n"];
   }
-  return [self normalizeResponse:map];
+  return map ? [self normalizeResponse:map] : nil;
 }
 
 /* logout from the connected host and close the connection */
@@ -271,10 +334,46 @@ static BOOL     debugImap4         = NO;
   return [self normalizeResponse:map];
 }
 
-- (NSDictionary *)getScript:(NSString *)_scriptName {
-  // TODO: implement
-  [self notImplemented:_cmd];
-  return nil;
+- (NSString *)getScript:(NSString *)_scriptName {
+  NSException *ex;
+  NSString *script, *s;
+  
+  s = [@"GETSCRIPT \"" stringByAppendingString:_scriptName];
+  s = [s stringByAppendingString:@"\""];
+  ex = [self sendCommand:s logText:s attempts:3];
+  if (ex != nil) {
+    [self logWithFormat:@"ERROR: could not get script: %@", ex];
+    [self setLastException:ex];
+    return nil;
+  }
+  
+  /* read script string */
+  
+  if ((script = [[self readString] autorelease]) == nil)
+    return nil;
+  
+  /* read response code */
+  
+  if ((s = [self readStringToCRLF]) == nil) {
+    [self logWithFormat:@"ERROR: could not parse status line."];
+    return nil;
+  }
+  if ([s length] == 0) { // remainder of previous string
+    [s release];
+    if ((s = [self readStringToCRLF]) == nil) {
+      [self logWithFormat:@"ERROR: could not parse status line."];
+      return nil;
+    }
+  }
+  
+  if (![s hasPrefix:@"OK"]) {
+    [self logWithFormat:@"ERROR: status line reports: '%@'", s];
+    [s release];
+    return nil;
+  }
+  [s release];
+  
+  return script;
 }
 
 - (BOOL)isValidScriptName:(NSString *)_name {
@@ -324,24 +423,79 @@ static BOOL     debugImap4         = NO;
     NSLog(@"%s: missing script-name", __PRETTY_FUNCTION__);
     return nil;
   }
-
+  
   s = [NSString stringWithFormat:@"DELETESCRIPT \"%@\"\r\n", _name];
   map = [self processCommand:s];
   return [self normalizeResponse:map];
 }
-- (NSDictionary *)listScript:(NSString *)_script {
-  [self notImplemented:_cmd];
-  return nil;
+
+- (NSDictionary *)listScripts {
+  NSMutableDictionary *md;
+  NSException *ex;
+  NSString *line;
+  
+  ex = [self sendCommand:@"LISTSCRIPTS" logText:@"LISTSCRIPTS" attempts:3];
+  if (ex != nil) {
+    [self logWithFormat:@"ERROR: could not list scripts: %@", ex];
+    [self setLastException:ex];
+    return nil;
+  }
+  
+  /* read response */
+  
+  md = [NSMutableDictionary dictionaryWithCapacity:16];
+  while ((line = [self readStringToCRLF]) != nil) {
+    if ([line hasPrefix:@"OK"])
+      break;
+    
+    if ([line hasPrefix:@"NO"]) {
+      md = nil;
+      break;
+    }
+    
+    if ([line hasPrefix:@"{"]) {
+      [self logWithFormat:@"unsupported list response line: '%@'", line];
+    }
+    else if ([line hasPrefix:@"\""]) {
+      NSString *s;
+      NSRange  r;
+      BOOL     isActive;
+      
+      s = [line substringFromIndex:1];
+      r = [s rangeOfString:@"\""];
+      
+      if (r.length == 0) {
+       [self logWithFormat:@"missing closing quote in line: '%@'", line];
+       [line release]; line = nil;
+       continue;
+      }
+      
+      s = [s substringToIndex:r.location];
+      isActive = [line rangeOfString:@"ACTIVE"].length == 0 ? NO : YES;
+      
+      [md setObject:isActive ? @"ACTIVE" : @"" forKey:s];
+    }
+    else {
+      [self logWithFormat:@"unexpected list response line (%d): '%@'", 
+           [line length], line];
+    }
+    
+    [line release]; line = nil;
+  }
+  
+  [line release]; line = nil;
+  
+  return md;
 }
 
 
 /*
-** Filter for all responses
-**       result  : NSNumber (response result)
-**       exists  : NSNumber (number of exists mails in selectet folder
-**       recent  : NSNumber (number of recent mails in selectet folder
-**       expunge : NSArray  (message sequence number of expunged mails
-                             in selectet folder)
+  Filter for all responses
+       result  : NSNumber (response result)
+       exists  : NSNumber (number of exists mails in selectet folder
+       recent  : NSNumber (number of recent mails in selectet folder
+       expunge : NSArray  (message sequence number of expunged mails
+                           in selectet folder)
 */
 
 - (NSMutableDictionary *)normalizeResponse:(NGHashMap *)_map {
@@ -355,14 +509,12 @@ static BOOL     debugImap4         = NO;
                               forKeys:keys count:2];
 }
 
-/*
-** filter for open connection
-*/
 
 - (NSDictionary *)normalizeOpenConnectionResponse:(NGHashMap *)_map {
+  /* filter for open connection */
   NSMutableDictionary *result;
   NSString *tmp;
-
+  
   result = [self normalizeResponse:_map];
   
   if (![[[_map objectEnumeratorForKey:@"ok"] nextObject] boolValue])
@@ -375,18 +527,6 @@ static BOOL     debugImap4         = NO;
   return result;
 }
 
-
-/*
-** filter for list
-**       list : NSDictionary (folder name as key and flags as value)
-*/
-
-- (NSString *)description {
-  return [NSString stringWithFormat:@"<Imap4Client[0x%08X]: socket=%@>",
-                     (unsigned)self, [self socket]];
-}
-
 /* Private Methods */
 
 - (BOOL)handleProcessException:(NSException *)_exception
@@ -424,13 +564,13 @@ static BOOL     debugImap4         = NO;
   [self logWithFormat:@"reconnect ..."];
 }
 
-- (NGHashMap *)processCommand:(NSString *)_command logText:(NSString *)_txt {
+- (NGHashMap *)processCommand:(id)_command logText:(id)_txt {
   NGHashMap *map          = nil;
   BOOL      repeatCommand = NO;
   int       repeatCnt     = 0;
   struct timeval tv;
   double         ti = 0.0;
-
+  
   if (ProfileImapEnabled) {
     gettimeofday(&tv, NULL);
     ti =  (double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0);
@@ -447,8 +587,14 @@ static BOOL     debugImap4         = NO;
     }
     
     NS_DURING {
-      [self sendCommand:_command logText:_txt];
-      map = [self->parser parseSieveResponse];
+      NSException *ex;
+      
+      if ((ex = [self sendCommand:_command logText:_txt]) != nil) {
+       repeatCommand = [self handleProcessException:ex 
+                             repetitionCount:repeatCnt];
+      }
+      else
+       map = [self->parser parseSieveResponse];
     }
     NS_HANDLER {
       repeatCommand = [self handleProcessException:localException
@@ -468,26 +614,233 @@ static BOOL     debugImap4         = NO;
   return map;
 }
 
-- (NGHashMap *)processCommand:(NSString *)_command {
+- (NGHashMap *)processCommand:(id)_command {
   return [self processCommand:_command logText:_command];
 }
 
-- (void)sendCommand:(NSString *)_command logText:(NSString *)_txt {
-  // TODO: should accept 'NSData' commands
+- (NSException *)sendCommand:(id)_command logText:(id)_txt {
   NSString *command = nil;
   
-  command = _command;
+  if ((command = _command) == nil) /* missing command */
+    return nil; // TODO: return exception?
+  
+  /* log */
+
+  if (self->debug) {
+    if ([_txt isKindOfClass:[NSData class]]) {
+      fprintf(stderr, "C: ");
+      fwrite([_txt bytes], [_txt length], 1, stderr);
+      fputc('\n', stderr);
+    }
+    else
+      fprintf(stderr, "C: %s\n", [_txt cString]);
+  }
+
+  /* write */
+  
+  if (![_command isKindOfClass:[NSData class]])
+    _command = [command dataUsingEncoding:NSUTF8StringEncoding];
+  
+  if (![self->io safeWriteData:_command])
+    return [self->io lastException];
+  if (![self->io writeBytes:"\r\n" count:2])
+    return [self->io lastException];
+  if (![self->io flush])
+    return [self->io lastException];
+  
+  return nil;
+}
+
+- (NSException *)sendCommand:(id)_command {
+  return [self sendCommand:_command logText:_command];
+}
+
+- (NSException *)sendCommand:(id)_command logText:(id)_txt attempts:(int)_c {
+  NSException *ex;
+  BOOL tryAgain;
+  int  repeatCnt;
+  
+  for (tryAgain = YES, repeatCnt = 0, ex = nil; tryAgain; repeatCnt++) {
+    if (repeatCnt > 0) {
+      if (repeatCnt > 1) /* one repeat goes without delay */
+        [self waitPriorReconnectWithRepetitionCount:repeatCnt];
+      [self reconnect];
+      tryAgain = NO;
+    }
+    
+    NS_DURING
+      ex = [self sendCommand:_command logText:_txt];
+    NS_HANDLER
+      ex = [localException retain];
+    NS_ENDHANDLER;
+    
+    if (ex == nil) /* everything is fine */
+      break;
+    
+    if (repeatCnt > _c) /* reached max attempts */
+      break;
+    
+    /* try again for certain exceptions */
+    tryAgain = [self handleProcessException:ex repetitionCount:repeatCnt];
+  }
+  
+  return ex;
+}
+
+/* low level */
+
+- (int)readByte {
+  unsigned char c;
+  
+  if (![self->io readBytes:&c count:1]) {
+    [self setLastException:[self->io lastException]];
+    return -1;
+  }
+  return c;
+}
+
+- (NSString *)readLiteral {
+  /* 
+     Assumes 1st char is consumed, returns a retained string.
+     
+     Parses: "{" number [ "+" ] "}" CRLF *OCTET
+  */
+  unsigned char countBuf[16];
+  int      i;
+  unsigned byteCount;
+  unsigned char *octets;
+  
+  /* read count */
   
-  if (self->debug)
-    fprintf(stderr, "C: %s\n", [_txt cString]);
+  for (i = 0; i < 14; i++) {
+    int c;
+    
+    if ((c = [self readByte]) == -1)
+      return nil;
+    if (c == '}')
+      break;
+    
+    countBuf[i] = c;
+  }
+  countBuf[i] = '\0';
+  byteCount = i > 0 ? atoi(countBuf) : 0;
+  
+  /* read CRLF */
+  
+  i = [self readByte];
+  if (i != '\n') {
+    if (i == '\r' && i != -1)
+      i = [self readByte];
+    if (i == -1)
+      return nil;
+  }
+  
+  /* read octet */
+  
+  if (byteCount == 0)
+    return @"";
+  
+  octets = malloc(byteCount + 4);
+  if (![self->io safeReadBytes:octets count:byteCount]) {
+    [self setLastException:[self->io lastException]];
+    return nil;
+  }
+  octets[byteCount] = '\0';
   
-  [self->text writeString:command];
-  [self->text writeString:@"\r\n"];
-  [self->text flush];
+  return [[NSString alloc] initWithUTF8String:octets];
 }
 
-- (void)sendCommand:(NSString *)_command {
-  [self sendCommand:_command logText:_command];
+- (NSString *)readQuoted {
+  /* 
+     assumes 1st char is consumed, returns a retained string
+
+     Note: quoted strings are limited to 1KB!
+  */
+  unsigned char buf[1032];
+  int i, c;
+  
+  i = 0;
+  do {
+    c      = [self readByte];
+    buf[i] = c;
+    i++;
+  }
+  while ((c != -1) && (c != '"'));
+  buf[i] = '\0';
+  
+  if (c == -1)
+    return nil;
+  
+  return [[NSString alloc] initWithUTF8String:buf];
+}
+
+- (NSString *)readStringToCRLF {
+  unsigned char buf[1032];
+  int i, c;
+  
+  i = 0;
+  do {
+    c = [self readByte];
+    if (c == '\n' || c == '\r')
+      break;
+    
+    buf[i] = c;
+    i++;
+  }
+  while ((c != -1) && (c != '\r') && (c != '\n') && (i < 1024));
+  buf[i] = '\0';
+  
+  if (c == -1)
+    return nil;
+  
+  /* consume CRLF */
+  if (c == '\r') {
+    if ((c = [self readByte]) != '\n') {
+      if (c == -1)
+       return nil;
+      [self logWithFormat:@"WARNING(%s): expected LF after CR, got: '%c'",
+             __PRETTY_FUNCTION__, c];
+      return nil;
+    }
+  }
+  
+  return [[NSString alloc] initWithUTF8String:buf];
+}
+
+- (NSString *)readString {
+  /* Note: returns a retained string */
+  int c1;
+  
+  if ((c1 = [self readByte]) == -1)
+    return nil;
+  
+  if (c1 == '"')
+    return [self readQuoted];
+  if (c1 == '{')
+    return [self readLiteral];
+  
+  return [self readStringToCRLF];
+}
+
+- (NSString *)readSieveName {
+  return [self readString];
+}
+
+/* description */
+
+- (NSString *)description {
+  NSMutableString *ms;
+
+  ms = [NSMutableString stringWithCapacity:128];
+  [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
+  
+  if (self->socket != nil)
+    [ms appendFormat:@" socket=%@", [self socket]];
+  else
+    [ms appendFormat:@" address=%@", self->address];
+
+  [ms appendString:@">"];
+  return ms;
 }
 
 @end /* NGSieveClient */
index 31c4d5bd3c7b3e5ecce7c574883487527bfa382d..51cb919c9f92986353d14695041a5f73ff656975 100644 (file)
@@ -2,6 +2,6 @@
 
 MAJOR_VERSION:=4
 MINOR_VERSION:=5
-SUBMINOR_VERSION:=200
+SUBMINOR_VERSION:=201
 
 # v4.2.149 requires libNGStreams v4.2.34
index 8eeceeb2c5f0eafa5b9810a1148deb1481f5921e..08a12f41ba58f8e650961a817376be6ea6fd2d60 100644 (file)
@@ -8,7 +8,8 @@ TOOL_NAME = \
        imapls          \
        test_qpdecode   \
        imapquota       \
-       imap_tool
+       imap_tool       \
+       sievetool
 
 imapquota_OBJC_FILES     = ImapQuotaTool.m ImapTool.m imapquota.m
 imapget_OBJC_FILES       = ImapTool.m imapget.m
@@ -16,6 +17,7 @@ imap_tool_OBJC_FILES     = imap_tool.m
 mime2xml_OBJC_FILES      = Mime2XmlTool.m mime2xml.m
 imapls_OBJC_FILES        = ImapTool.m ImapListTool.m imapls.m
 test_qpdecode_OBJC_FILES = test_qpdecode.m
+sievetool_OBJC_FILES    = sievetool.m
 
 -include GNUmakefile.preamble
 include $(GNUSTEP_MAKEFILES)/tool.make
index 0181b3a94ed1024882c050c3ecd130f76c8529e1..6fef303119a22ef13d7bd9eadacdd7b33798d0e7 100644 (file)
@@ -1,7 +1,7 @@
 /*
-  Copyright (C) 2000-2003 SKYRIX Software AG
+  Copyright (C) 2000-2004 SKYRIX Software AG
 
-  This file is part of OGo
+  This file is part of OpenGroupware.org.
 
   OGo 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
 {
   NGImap4FileManager *fileManager;
 }
+
+/* operations */
+
 - (void)flush;
+
+/* accessors */
+
 - (NGImap4FileManager *)fileManager;
 
 @end
index 30c8d11df936e338dd4f9c90faa9a1a21e9355ef..d5a169b0443ba1e814c8d0f176ba5c0aae401bc2 100644 (file)
@@ -1,7 +1,7 @@
 /*
-  Copyright (C) 2000-2003 SKYRIX Software AG
+  Copyright (C) 2000-2004 SKYRIX Software AG
 
-  This file is part of OGo
+  This file is part of OpenGroupware.org.
 
   OGo 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
@@ -18,7 +18,6 @@
   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
   02111-1307, USA.
 */
-// $Id$
 
 #import <Foundation/NSObject.h>
 
diff --git a/sope-mime/samples/sievetool.m b/sope-mime/samples/sievetool.m
new file mode 100644 (file)
index 0000000..1fde51f
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+  Copyright (C) 2004 Helge Hess
+
+  This file is part of OpenGroupware.org.
+
+  OGo 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.
+
+  OGo 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 OGo; see the file COPYING.  If not, write to the
+  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+  02111-1307, USA.
+*/
+
+#include <NGImap4/NGSieveClient.h>
+#include "common.h"
+
+static NSURL *getDefaultsURL(void)  {
+  NSUserDefaults *ud;
+  NSString *pwd, *user, *host;
+  NSString *url;
+
+  ud = [NSUserDefaults standardUserDefaults];
+  
+  if ((url = [ud stringForKey:@"url"]) != nil)
+    return [NSURL URLWithString:url];
+  
+  user = [ud stringForKey:@"user"];
+  pwd  = [ud stringForKey:@"password"];
+  host = [ud stringForKey:@"host"];
+  
+  url = [@"http://" stringByAppendingString:user];
+  url = [url stringByAppendingString:@":"];
+  url = [url stringByAppendingString:pwd];
+  url = [url stringByAppendingString:@"@"];
+  url = [url stringByAppendingString:host];
+  url = [url stringByAppendingString:@":2000/"];
+  return [NSURL URLWithString:url];
+}
+
+static int test(NSArray *args) {
+  NSUserDefaults *ud;
+  NGSieveClient  *client;
+  NSURL *url;
+  id res;
+
+  ud = [NSUserDefaults standardUserDefaults];
+  
+  url = getDefaultsURL();
+  NSLog(@"check URL: %@", url);
+  
+  client = [[NGSieveClient alloc] initWithURL:url];
+  NSLog(@"  client: %@", client);
+  
+  if ((res = [client login:[url user] password:[url password]]) == nil) {
+    NSLog(@"could not login %@: %@", [url user], client);
+    return 1;
+  }
+  
+  NSLog(@"  login %@: %@", [url user], res);
+  NSLog(@"  client: %@", client);
+  
+  res = [client listScripts];
+  NSLog(@"  list: %@", res);
+  
+  res = [client getScript:@"ogo"];
+  NSLog(@"  get 'ogo': %@", res);
+  
+  return 0;
+}
+
+int main(int argc, char **argv, char **env) {
+  NSAutoreleasePool *pool;
+  int res;
+  
+  pool = [NSAutoreleasePool new];
+#if LIB_FOUNDATION_LIBRARY  
+  [NSProcessInfo initializeWithArguments:argv count:argc environment:env];
+#endif
+  
+  res = test([[NSProcessInfo processInfo] argumentsWithoutDefaults]);
+  
+  [pool release];
+  exit(0);
+  /* static linking */
+  [NGExtensions class];
+  return 0;
+}