/*
  Copyright (C) 2000-2004 SKYRIX Software AG

  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 "NGImap4ResponseParser.h"
#include "NGImap4Support.h"
#include "imCommon.h"

// TODO(hh): code is now prepared for last-exception, but currently it just
//           raises and may leak the exception object

@interface NGImap4ResponseParser(ParsingPrivates)
- (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_;
- (NSDictionary *)_parseBodyContent;
@end

@implementation NGImap4ResponseParser

#define __la(__SELF__, __LACNT) \
  ((__SELF__->la == NULL) \
    ? [__SELF__->buffer la:__LACNT]\
    : __SELF__->la(__SELF__->buffer, @selector(la:), __LACNT))

static __inline__ int _la(NGImap4ResponseParser *self, unsigned _laCnt) {
  char c = __la(self, _laCnt);
  
  if (c == '\r')
    return _la(self, _laCnt + 1);
  
  return c;
}

static NSDictionary *_parseBody(NGImap4ResponseParser *self);
static NSString *_parseBodyString(NGImap4ResponseParser *self,
                                  BOOL _convertString);
static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self,
                                        BOOL _convertString,
                                        BOOL _decode);
static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self);
static NSArray *_parseAddressStructure(NGImap4ResponseParser *self);
static NSArray *_parseParenthesizedAddressList(NGImap4ResponseParser *self);
static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self);
static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self);
static int _parseTaggedResponse(NGImap4ResponseParser *self,
                                NGMutableHashMap *result_);
static void _parseUntaggedResponse(NGImap4ResponseParser *self,
                                   NGMutableHashMap *result_);
static BOOL _parseByeUntaggedResponse(NGImap4ResponseParser *self,
                                      NGMutableHashMap *result_);
static NSArray *_parseFlagArray(NGImap4ResponseParser *self);
static BOOL _parseFlagsUntaggedResponse(NGImap4ResponseParser *self,
                                        NGMutableHashMap *result_);
static BOOL _parseOkUntaggedResponse(NGImap4ResponseParser *self,
                                     NGMutableHashMap *result_);
static BOOL _parseBadUntaggedResponse(NGImap4ResponseParser *self,
                                      NGMutableHashMap *result_);
static BOOL _parseNoUntaggedResponse(NGImap4ResponseParser *self,
                                     NGMutableHashMap *result_);
static BOOL _parseThreadResponse(NGImap4ResponseParser *self,
                                 NGMutableHashMap *result_);
static BOOL _parseStatusResponse(NGImap4ResponseParser *self,
                                 NGMutableHashMap *result_);
static BOOL _parseListOrLSubResponse(NGImap4ResponseParser *self,
                                     NGMutableHashMap *result_);
static BOOL _parseCapabilityResponse(NGImap4ResponseParser *self,
                                     NGMutableHashMap *result_);
static BOOL _parseSearchResponse(NGImap4ResponseParser *self,
                                 NGMutableHashMap *result_);
static NSArray *_parseThread(NGImap4ResponseParser *self);
static BOOL _parseSortResponse(NGImap4ResponseParser *self,
                               NGMutableHashMap *result_);
static NSNumber *_parseUnsigned(NGImap4ResponseParser *self);
static NSString *_parseUntil(NGImap4ResponseParser *self, char _c);
static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2);
static __inline__ void _match(NGImap4ResponseParser *self, char _match);
static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt);
static void _parseContinuationResponse(NGImap4ResponseParser *self,
                                       NGMutableHashMap *result_);
static NSData *_parseData(NGImap4ResponseParser *self);

static void _parseSieveRespone(NGImap4ResponseParser *self,
                               NGMutableHashMap *result_);
static BOOL _parseGreetingsSieveResponse(NGImap4ResponseParser *self,
                                         NGMutableHashMap *result_);
static BOOL _parseDataSieveResponse(NGImap4ResponseParser *self,
                                    NGMutableHashMap *result_);
static BOOL _parseOkSieveResponse(NGImap4ResponseParser *self,
                                  NGMutableHashMap *result_);
static BOOL _parseNoSieveResponse(NGImap4ResponseParser *self,
                                  NGMutableHashMap *result_);
static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self);
static NSString *_parseStringSieveResponse(NGImap4ResponseParser *self);

static BOOL _parseQuotaResponse(NGImap4ResponseParser *self,
                                NGMutableHashMap *result_);
static BOOL _parseQuotaRootResponse(NGImap4ResponseParser *self,
                                    NGMutableHashMap *result_);

static unsigned int     LaSize              = 4097;
static unsigned         UseMemoryMappedData = 0;
static unsigned         Imap4MMDataBoundary = 0;
static BOOL             debugOn             = NO;
static BOOL             debugDataOn         = NO;
static NSStringEncoding encoding;
static Class            StrClass = Nil;
static Class            NumClass      = Nil;
static NSStringEncoding defCStringEncoding;
static NSNumber         *YesNum = nil;
static NSNumber         *NoNum  = nil;

+ (void)initialize {
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  static BOOL didInit = NO;
  if (didInit) return;
  didInit = YES;

  encoding = [NGMimePartParser defaultHeaderFieldEncoding];
  defCStringEncoding = [NSString defaultCStringEncoding];
  
  debugOn             = [ud boolForKey:@"ImapDebugEnabled"];
  UseMemoryMappedData = [ud boolForKey:@"NoMemoryMappedDataForImapBlobs"]?0:1;
  Imap4MMDataBoundary = [ud integerForKey:@"Imap4MMDataBoundary"];
  
  if (Imap4MMDataBoundary < 10)
    /* Note: this should be larger than a usual header size! */
    Imap4MMDataBoundary = 2 * LaSize;
  
  StrClass = [NSString class];
  NumClass      = [NSNumber class];
  YesNum        = [[NumClass numberWithBool:YES] retain];
  NoNum         = [[NumClass numberWithBool:NO]  retain];
}

+ (id)parserWithStream:(id<NGActiveSocket>)_stream {
  NGImap4ResponseParser *parser;

  parser = [NGImap4ResponseParser alloc]; /* seperate line to keep gcc happy */
  return [[parser initWithStream:_stream] autorelease];
}

- (id)initWithStream:(id<NGActiveSocket>)_stream {
  // designated initializer
  if (_stream == nil) {
    [self logWithFormat:@"%s: got no stream ...", __PRETTY_FUNCTION__];
    [self release];
    return nil;
  }
  
  if ((self = [super init])) {
    id s;
    
    s = [(NGBufferedStream *)[NGBufferedStream alloc] initWithSource:_stream];
    self->buffer = [NGByteBuffer alloc];
    self->buffer = [self->buffer initWithSource:s la:LaSize];
    [s release];
    
    if ([self->buffer respondsToSelector:@selector(methodForSelector:)])
      self->la = (int(*)(id, SEL, unsigned))
        [self->buffer methodForSelector:@selector(la:)];
    
    self->debug = debugOn;
  }
  return self;
}

- (id)init {
  [self release];
  [NSException raise:@"InvalidUseOfMethodException"
	       format:
		 @"calling -init on the NGImap4ResponseParser is not allowed"];
  return nil;
}

- (void)dealloc {
  [self->buffer release];
  if (self->debug)
    [self->serverResponseDebug release];
  [super dealloc];
}

/* exception handling */

- (void)setLastException:(NSException *)_exc {
  // TODO: support last exception
  [_exc raise];
}

/*
** Parse Sieve Responses
*/

- (NGHashMap *)parseSieveResponse {
  NGMutableHashMap *result;

  if (self->debug) {
    if (self->serverResponseDebug != nil)
      [self->serverResponseDebug release];
    self->serverResponseDebug = [[NSMutableString alloc] initWithCapacity:512];
  }
  result = [NGMutableHashMap hashMapWithCapacity:64];

  if (_la(self,0) == -1) {
    [self setLastException:[self->buffer lastException]];
    return nil;
  }
  
  _parseSieveRespone(self, result);
  return result;
}

- (NGHashMap *)parseResponseForTagId:(int)_tag exception:(NSException **)ex_ {
  /* parse a response from the server, _tag!=-1 parse until tagged response */
  // TODO: is NGHashMap really necessary here?
  BOOL             endOfCommand;
  NGMutableHashMap *result;
  
  if (ex_) *ex_ = nil;
  
  if (self->debug) {
    [self->serverResponseDebug release]; self->serverResponseDebug = nil;
    self->serverResponseDebug = [[NSMutableString alloc] initWithCapacity:512];
  }
  
  result = [NGMutableHashMap hashMapWithCapacity:64];
  
  if (_la(self, 0) == -1) {
    [self logWithFormat:@"%s: catched: %@", __PRETTY_FUNCTION__,
            [self->buffer lastException]];
    
    if (ex_) {
      *ex_ = [self->buffer lastException];
      return nil;
    }
    else {
      [self setLastException:[self->buffer lastException]];
      return nil;
    }
  }
  for (endOfCommand = NO; !endOfCommand; ) {
    unsigned char l0;
    
    l0 = _la(self, 0);
    
    if (l0 == '*') { /* those starting with '* ' */
      _parseUntaggedResponse(self, result);
      if ([result objectForKey:@"bye"]) {
        endOfCommand = YES;
      }
      else {
        if (_tag == -1) {
          if ([result objectForKey:@"ok"] != nil)
            endOfCommand = YES;
        }
      }
    }
    else if (l0 == '+') { /* starting with a '+'? */
      _parseContinuationResponse(self, result);
      endOfCommand = YES;
    }
    else if (isdigit(l0)) {
      /* those starting with a number '24 ', eg '24 OK Completed' */
      endOfCommand = (_parseTaggedResponse(self, result) == _tag);
    }
  }
  return result;
}
- (NGHashMap *)parseResponseForTagId:(int)_tag {
  // DEPRECATED
  NSException *e = nil;
  NGHashMap   *hm;

  hm = [self parseResponseForTagId:_tag exception:&e];
  if (e) {
    [self setLastException:e];
    return nil;
  }
  return hm;
}

static void _parseSieveRespone(NGImap4ResponseParser *self,
                               NGMutableHashMap *result_)
{
  if (_parseGreetingsSieveResponse(self, result_)) 
    return;
  if (_parseDataSieveResponse(self, result_))    // la: 1
    return;
  if (_parseOkSieveResponse(self, result_))     // la: 2
    return; 
  if (_parseNoSieveResponse(self, result_))     // la: 2
    return;
}

static NSData *_parseData(NGImap4ResponseParser *self) {
  // TODO: split up method
  NSData   *result;
  unsigned size;
  NSNumber *n;

  if (_la(self, 0) != '{')
    return nil;
  
  if (debugDataOn) [self logWithFormat:@"parse data ..."];

  /* got header */
  result = nil;  
    
  _consume(self, 1);
  if ((n = _parseUnsigned(self)) == nil) {
      NSException *e;

      e = [[NGImap4ParserException alloc] 
	    initWithFormat:@"expect a number between {}"];
      [self setLastException:[e autorelease]];
      return nil;
  }
  if (debugDataOn) [self logWithFormat:@"  parse data: %@", n];
  _match(self, '}');
  _match(self, '\n');

  size = [n intValue];
  
  if (UseMemoryMappedData && (size > Imap4MMDataBoundary)) {
      static NSProcessInfo *Pi = nil;
      NGFileStream *stream;
      
      unsigned char buf[LaSize + 2];
      unsigned char tmpBuf[LaSize + 2];
      unsigned      wasRead = 0;
      NSString      *path;
      signed char   lastChar; // must be signed
      
      if (debugDataOn) [self logWithFormat:@"  using memory mapped data  ..."];
      
      if (Pi == nil)
        Pi = [[NSProcessInfo processInfo] retain];

      path   = [Pi temporaryFileName];
      stream = [NGFileStream alloc]; /* extra line to keep gcc happy */
      stream = [stream initWithPath:path];

      if (![stream openInMode:NGFileWriteOnly]) {
	NSException *e;

	e = [[NGImap4ParserException alloc]
	      initWithFormat:@"Could not open temporary file %@", path];
        [self setLastException:[e autorelease]];
	return nil;
      }
      
      lastChar = -1;
      while (wasRead < size) {
        unsigned readCnt, bufCnt, tmpSize, cnt, tmpBufCnt;

        bufCnt = 0;
        
        if (lastChar != -1) {
          buf[bufCnt++] = lastChar;
          lastChar = -1;
        }
        
        [self->buffer la:(size - wasRead <  LaSize) 
             ? (size - wasRead)
             : LaSize];
        
        readCnt = [self->buffer readBytes:buf+bufCnt count:size - wasRead];
        
        wasRead+=readCnt;
        bufCnt +=readCnt;

        tmpSize   = bufCnt - 1;
        cnt       = 0;
        tmpBufCnt = 0;
        
        while (cnt < tmpSize) {
          if ((buf[cnt] == '\r') && (buf[cnt+1] == '\n')) {
            cnt++;
          }
          tmpBuf[tmpBufCnt++] = buf[cnt++];
        }
        if (cnt < bufCnt) {
          lastChar = buf[cnt];
        }
        [stream writeBytes:tmpBuf count:tmpBufCnt];
      }
      if (lastChar != -1)
        [stream writeBytes:&lastChar count:1];
      
      [stream close];
      [stream release]; stream = nil;
      result = [NSData dataWithContentsOfMappedFile:path];
      [[NSFileManager defaultManager] removeFileAtPath:path handler:nil];

      return result;
  }

  if (size == 0) {
    [self logWithFormat:@"ERROR(%s): got content size '0'!", 
            __PRETTY_FUNCTION__];
    return nil;
  }
  else {
    unsigned char *buf = NULL;
    unsigned      wasRead   = 0;
    unsigned char *tmpBuf;
    unsigned      cnt, tmpBufCnt, tmpSize;
          
    buf = calloc(size + 10, sizeof(char));
    
    while (wasRead < size) {
      [self->buffer la:(size - wasRead <  LaSize) ? (size - wasRead) : LaSize];
            
      wasRead += [self->buffer readBytes:(buf + wasRead) 
		               count:(size - wasRead)];
    }
    
    /* normalize response  \r\n -> \n */
	
    tmpBuf    = calloc(size + 10, sizeof(char));
    cnt       = 0;
    tmpBufCnt = 0;
    tmpSize   = size == 0 ? 0 : size - 1;
    while (tmpBufCnt < tmpSize && cnt < size) {
      if ((buf[cnt] == '\r') && (buf[cnt + 1] == '\n'))
	cnt++; /* skip \r */
      
      tmpBuf[tmpBufCnt] = buf[cnt];
      tmpBufCnt++;
      cnt++;
    }
    if (cnt < size) {
      tmpBuf[tmpBufCnt] = buf[cnt];
      tmpBufCnt++;
      cnt++;
    }
    
    result = [NSData dataWithBytesNoCopy:tmpBuf length:tmpBufCnt];
    
    if (buf != NULL) free(buf); buf = NULL;
    return result;
  }
}

static int _parseTaggedResponse(NGImap4ResponseParser *self,
                                NGMutableHashMap *result_) 
{
  NSDictionary *d;
  NSNumber *tag  = nil;
  NSString *res  = nil;
  NSString *desc = nil;
  NSString *flag = nil;
  
  if ((tag  = _parseUnsigned(self)) == nil) {
    NSException *e;
    
    if (self->debug) {
      e = [[NGImap4ParserException alloc]
	    initWithFormat:@"expect a number at begin of tagged response <%@>",
	    self->serverResponseDebug];
    }
    else {
      e = [[NGImap4ParserException alloc]
	    initWithFormat:@"expect a number at begin of tagged response"];
    }
    e = [e autorelease];
    [self setLastException:e];
    return -1;
  }
  
  _match(self, ' ');
  res  = [_parseUntil(self, ' ') lowercaseString];
  if (_la(self, 0) == '[') { /* Found flag like [READ-ONLY] */
    _consume(self, 1);
    flag = _parseUntil(self, ']');
  }
  desc = _parseUntil(self, '\n');
  /*
    ATTENTION: if no flag was set, flag == nil, in this case all key-value 
               pairs after flag will be ignored
  */
  d = [[NSDictionary alloc] initWithObjectsAndKeys:
			      tag,  @"tagId",
			      res,  @"result",
			      desc, @"description",
			      flag, @"flag", nil];
  [result_ addObject:d forKey:@"ResponseResult"];
  [d release];
  return [tag intValue];
}

static void _parseUntaggedResponse(NGImap4ResponseParser *self,
                                   NGMutableHashMap *result_) 
{
  // TODO: is it really required by IMAP4 that responses are uppercase?
  // TODO: apparently this code *breaks* with lowercase detection on!
  unsigned char l0, l1 = 0;
  _match(self, '*');
  _match(self, ' ');
  
  l0 = _la(self, 0);
  switch (l0) {
  case 'B':
    l1 = _la(self, 1);
    if (l1 == 'A' && _parseBadUntaggedResponse(self, result_))    // la: 3
      return;
    if (l1 == 'Y' && _parseByeUntaggedResponse(self, result_))    // la: 3
      return;
    break;

  case 'C':
    if (_parseCapabilityResponse(self, result_))         // la: 10
      return;
    break;

  case 'F':
    if (_parseFlagsUntaggedResponse(self, result_))  // la: 5
      return;
    break;

  case 'L':
    if (_parseListOrLSubResponse(self, result_))     // la: 4
      return;
    break;

  case 'N':
    if (_parseNoUntaggedResponse(self, result_))     // la: 2
      return;
    break;

  case 'O':
    if (_parseOkUntaggedResponse(self, result_))     // la: 2
      /* eg "* OK Completed" */
      return;
    break;

  case 'R':
    break;

  case 'S':
    switch (_la(self, 1)) {
    case 'O': // SORT
      if (_parseSortResponse(self, result_))         // la: 4
	return;
      break;
    case 'E': // SEARCH
      if (_parseSearchResponse(self, result_))         // la: 5
	return;
      break;
    case 'T': // STATUS
      if (_parseStatusResponse(self, result_))         // la: 6
	/* eg "* STATUS INBOX (MESSAGES 0 RECENT 0 UNSEEN 0)" */
	return;
      break;
    }
    break;

  case 'T':
    if (_parseThreadResponse(self, result_))         // la: 6
      return;
    break;
    
  case 'Q':
    if (_parseQuotaResponse(self, result_))          // la: 6
      return;
    if (_parseQuotaRootResponse(self, result_))         // la: 10
      return;
    break;

  case '0': case '1': case '2': case '3': case '4':
  case '5': case '6': case '7': case '8': case '9':
    if ([self _parseNumberUntaggedResponse:result_]) // la: 5
      /* eg "* 928 FETCH ..." */
      return;
    break;
  }
  
  // TODO: what if none matches?
  [self logWithFormat:@"%s: no matching tag specifier?", __PRETTY_FUNCTION__];
}

static void _parseContinuationResponse(NGImap4ResponseParser *self,
                                       NGMutableHashMap *result_) {
  _match(self, '+');
  _match(self, ' ');

  [result_ addObject:YesNum forKey:@"ContinuationResponse"];
  [result_ addObject:_parseUntil(self, '\n') forKey:@"description"];
}

static BOOL _parseListOrLSubResponse(NGImap4ResponseParser *self,
                                     NGMutableHashMap *result_) {
  if (((_la(self, 0) == 'L')
       && (_la(self, 1) == 'I')
       && (_la(self, 2) == 'S')
       && (_la(self, 3) == 'T')
       && (_la(self, 4) == ' ')) ||
      ((_la(self, 0) == 'L')
       && (_la(self, 1) == 'S')
       && (_la(self, 2) == 'U')
       && (_la(self, 3) == 'B')
       && (_la(self, 4) == ' '))) {
    NSArray  *flags = nil;
    NSString *delim = nil;
    NSString *name  = nil;
    NSDictionary *d;
    
    _consume(self, 5);

    flags = _parseFlagArray(self);

    _match(self, ' ');

    if (_la(self, 0) == '"') {
      _match(self, '"');
      delim = _parseUntil(self, '"');
      _match(self, ' ');
    }
    else {
      _parseUntil(self, ' ');
      delim = nil;
    }
    if (_la(self, 0) == '"') {
      _consume(self, 1);
      name = _parseUntil(self, '"');
      _parseUntil(self, '\n');
    }
    else {
      name = _parseUntil(self, '\n');
    }

    d = [[NSDictionary alloc] initWithObjectsAndKeys:
				name, @"folderName",
			        flags, @"flags",
			        delim, @"delimiter", nil];
    [result_ addObject:d forKey:@"list"];
    [d release];
    return YES;
  }
  return NO;
}

static BOOL _parseCapabilityResponse(NGImap4ResponseParser *self,
                                     NGMutableHashMap *result_) {
  if ((_la(self, 0)     == 'C')
      && (_la(self, 1)  == 'A')
      && (_la(self, 2)  == 'P')
      && (_la(self, 3)  == 'A')
      && (_la(self, 4)  == 'B')
      && (_la(self, 5)  == 'I')
      && (_la(self, 6)  == 'L')
      && (_la(self, 7)  == 'I')
      && (_la(self, 8)  == 'T')
      && (_la(self, 9)  == 'Y')
      && (_la(self, 10) == ' ')) {
    NSString *caps;

    caps = _parseUntil(self, '\n');
    {
      NSEnumerator   *enumerator;
      id             obj;
      NSMutableArray *array;
      NSArray        *tmp;

      array = [[NSMutableArray alloc] initWithCapacity:16];

      enumerator = [[caps componentsSeparatedByString:@" "] objectEnumerator];
      while ((obj = [enumerator nextObject])) {
        [array addObject:[obj lowercaseString]];
      }
      tmp = [array shallowCopy];
      [result_ addObject:tmp forKey:@"capability"];

      [array release]; array = nil;
      [tmp   release]; tmp   = nil;
    }
    return YES;
  }
  return NO;
}

static BOOL _parseSearchResponse(NGImap4ResponseParser *self,
                                 NGMutableHashMap *result_) {
  if ((_la(self, 0) == 'S')
      && (_la(self, 1) == 'E')
      && (_la(self, 2) == 'A')
      && (_la(self, 3) == 'R')
      && (_la(self, 4) == 'C')
      && (_la(self, 5) == 'H')) {

    NSMutableArray *msn = nil;

    _consume(self, 6);

    msn = [NSMutableArray arrayWithCapacity:128];

    while (_la(self, 0) == ' ') {
      _consume(self, 1);
      [msn addObject:_parseUnsigned(self)];
    }
    _parseUntil(self, '\n');
    [result_ addObject:msn forKey:@"search"];
    return YES;
  }
  return NO;
}

static BOOL _parseSortResponse(NGImap4ResponseParser *self,
                                 NGMutableHashMap *result_) {
  if ((_la(self, 0) == 'S')
      && (_la(self, 1) == 'O')
      && (_la(self, 2) == 'R')
      && (_la(self, 3) == 'T')) {

    NSMutableArray *msn = nil;

    _consume(self, 4);

    msn = [NSMutableArray arrayWithCapacity:128];

    while (_la(self, 0) == ' ') {
      _consume(self, 1);
      [msn addObject:_parseUnsigned(self)];
    }
    _parseUntil(self, '\n');
    [result_ addObject:msn forKey:@"sort"];
    return YES;
  }
  return NO;
}

static BOOL _parseQuotaResponse(NGImap4ResponseParser *self,
                                NGMutableHashMap *result_) {
  if ((_la(self, 0) == 'Q')
      && (_la(self, 1) == 'U')
      && (_la(self, 2) == 'O')
      && (_la(self, 3) == 'T')
      && (_la(self, 4) == 'A')
      && (_la(self, 5) == ' ')) {

    NSString            *qRoot;
    NSMutableDictionary *parse;
    NSMutableDictionary *quota;

    _consume(self, 6);

    quota = [result_ objectForKey:@"quota"];

    if (!quota) {
      quota = [NSMutableDictionary dictionaryWithCapacity:2];
      [result_ setObject:quota forKey:@"quota"];
    }
    
    parse = [NSMutableDictionary dictionaryWithCapacity:3];
    qRoot = _parseUntil2(self, ' ', '\n');

    if (_la(self, 0) == ' ') {
      _consume(self, 1);

      if (_la(self, 0) == '(') {
        _consume(self,1);
        if (_la(self, 0) == ')') { /* empty quota response */
          _consume(self,1);
        }
        else {
          NSString *key;

          key = _parseUntil(self, ' ');
          key = [key lowercaseString];
          if ([key isEqualToString:@"storage"]) {
            NSString *used, *max;

            used = _parseUntil(self, ' ');
            max  = _parseUntil(self, ')');

            [parse setObject:used forKey:@"usedSpace"];
            [parse setObject:max  forKey:@"maxQuota"];
          }
          else {
            NSString *v;

            v = _parseUntil(self, ')');

            [parse setObject:v forKey:@"resource"];
          }
        }
      }
      [quota setObject:parse forKey:qRoot];
    }
    _parseUntil(self, '\n');
    
    return YES;
  }
  return NO;
}

static BOOL _parseQuotaRootResponse(NGImap4ResponseParser *self,
                                    NGMutableHashMap *result_)
{
  if ((_la(self, 0) == 'Q')
      && (_la(self, 1) == 'U')
      && (_la(self, 2) == 'O')
      && (_la(self, 3) == 'T')
      && (_la(self, 4) == 'A')
      && (_la(self, 5) == 'R')
      && (_la(self, 6) == 'O')
      && (_la(self, 7) == 'O')
      && (_la(self, 8) == 'T')
      && (_la(self, 9) == ' ')) {
    NSString *folderName, *folderRoot;
    NSMutableDictionary *dict;

    _consume(self, 10);

    dict = [result_ objectForKey:@"quotaRoot"];

    if (!dict) {
      dict = [NSMutableDictionary dictionaryWithCapacity:2];
      [result_ setObject:dict forKey:@"quotaRoot"];
    }
    if (_la(self, 0) == '"') {
      _consume(self , 1);
      folderName = _parseUntil(self, '"');
    }
    else {
      folderName = _parseUntil2(self, '\n', ' ');
    }
    if (_la(self, 0) == ' ') {
      _consume(self, 1);
      folderRoot = _parseUntil(self, '\n');
    }
    else {
      _consume(self, 1);
      folderRoot = nil;
    }
    if ([folderName length] && [folderRoot length]) {
      [dict setObject:folderRoot forKey:folderName];
    }
    return YES;
  }
  return NO;
}

static NSArray *_parseThread(NGImap4ResponseParser *self) {
  NSMutableArray *array;
  NSNumber       *msg;
    
  array = [NSMutableArray arrayWithCapacity:64];

  if (_la(self, 0) == '(') {
    _consume(self, 1);
  }
  while (1) {
    if (_la(self, 0) == '(') {
      id a;
      a = _parseThread(self);
      [array addObject:a];
    }
    else if ((msg = _parseUnsigned(self))) {
      [array addObject:msg];
    }
    else {
      return nil;
    }
    if (_la(self, 0) == ')')
      break;
    else if (_la(self, 0) == ' ')
      _consume(self, 1);
  }
  _match(self, ')');
  return array;
}


static BOOL _parseThreadResponse(NGImap4ResponseParser *self,
                                 NGMutableHashMap *result_) {
  if ((_la(self, 0) == 'T')
      && (_la(self, 1) == 'H')
      && (_la(self, 2) == 'R')
      && (_la(self, 3) == 'E')
      && (_la(self, 4) == 'A')
      && (_la(self, 5) == 'D')) {

    NSMutableArray *msn;

    _consume(self, 6);

    if (_la(self, 0) == ' ') {
      _consume(self, 1);
    }
    else {
      [result_ addObject:[NSArray array] forKey:@"thread"];
      return YES;
    }
    msn = [NSMutableArray arrayWithCapacity:64];
    while ((_la(self, 0) == '(')) {
      NSArray *array;
      if ((array = _parseThread(self)))
        [msn addObject:array];
    }
    _parseUntil(self, '\n');
    [result_ addObject:msn forKey:@"thread"];
    return YES;
  }
  return NO;
}

static BOOL _parseStatusResponse(NGImap4ResponseParser *self,
                                 NGMutableHashMap *result_) {
  if ((_la(self, 0) == 'S')
      && (_la(self, 1) == 'T')
      && (_la(self, 2) == 'A')
      && (_la(self, 3) == 'T')
      && (_la(self, 4) == 'U')
      && (_la(self, 5) == 'S')       
      && (_la(self, 6) == ' ')) {
    NSString            *name  = nil;
    NSMutableDictionary *flags = nil;
    NSDictionary *d;
    
    _consume(self, 7);

    if (_la(self, 0) == '"') {
      _consume(self, 1);
      name = _parseUntil(self, '"');
      _match(self, ' ');
    }
    else {
      name = _parseUntil(self, ' ');
    }
    _match(self, '(');
    flags = [NSMutableDictionary dictionaryWithCapacity:8];
    
    while (_la(self, 0) != ')') {
      NSString *key   = _parseUntil(self, ' ');
      id       value = _parseUntil2(self, ' ', ')');

      if (_la(self, 0) == ' ')
        _consume(self, 1);
      
      [flags setObject:[NumClass numberWithInt:[value intValue]]
             forKey:[key lowercaseString]];
    }
    _match(self, ')');
    _parseUntil(self, '\n');

    d = [[NSDictionary alloc] initWithObjectsAndKeys:
				name, @"folderName",
			        flags, @"flags", nil];
    [result_ addObject:d forKey:@"status"];
    [d release];
    return YES;
  }
  return NO;
}

static BOOL _parseByeUntaggedResponse(NGImap4ResponseParser *self,
                                      NGMutableHashMap *result_) {
  if ((_la(self, 0) == 'B')
      && (_la(self, 1) == 'Y')
      && (_la(self, 2) == 'E')
      && (_la(self, 3) == ' ')) {
    NSString *reason = nil;

    _consume(self, 4);
    reason = _parseUntil(self, '\n');
    [result_ addObject:reason forKey:@"bye"];
    return YES;
  }
  return NO;
}

- (BOOL)_parseNumberUntaggedResponse:(NGMutableHashMap *)result_ {
  NSNumber *number = nil;
  NSString *key    = nil;

  if ((number = _parseUnsigned(self)) == nil)
    return NO;
  
  _match(self, ' ');
  
  if ((_la(self, 0) == 'F')
      && (_la(self, 1) == 'E')
      && (_la(self, 2) == 'T')
      && (_la(self, 3) == 'C')
      && (_la(self, 4) == 'H')
      && (_la(self, 5) == ' ')) { /* got a fetch response (fetch request) */
    /* eg: "FETCH (FLAGS (\Seen) UID 5 RFC822.HEADER {2903}" */
    NSMutableDictionary *fetch = nil;

    fetch = [[NSMutableDictionary alloc] initWithCapacity:10];
    
    _consume(self, 6);
    _match(self, '(');
    while (_la(self, 0) != ')') {
      NSString *key = nil;
      
      key = [_parseUntil(self, ' ') lowercaseString];
      if ([key hasPrefix:@"body["]) {
        NSDictionary *content;
        
        if ((content = [self _parseBodyContent]) != nil)
          [fetch setObject:content forKey:key];
        else
          [self logWithFormat:@"ERROR: got no body content for key: '%@'",key];
      }
      else if ([key isEqualToString:@"body"]) {
        [fetch setObject:_parseBody(self) forKey:key];
      }
      else if ([key isEqualToString:@"flags"]) {
        [fetch setObject:_parseFlagArray(self) forKey:key];
      }
      else if ([key isEqualToString:@"uid"]) {
        [fetch setObject:_parseUnsigned(self) forKey:key];
      }
      else if ([key isEqualToString:@"rfc822.size"]) {
        [fetch setObject:_parseUnsigned(self) forKey:key];
      }
      else if ([key hasPrefix:@"rfc822"]) {
        NSData *data;

        if (_la(self, 0) == '"') {
          NSString *str;
          _consume(self,1);

          str = _parseUntil(self, '"');
          data = [str dataUsingEncoding:defCStringEncoding];
        }
        else 
          data = _parseData(self);

        if (data != nil) [fetch setObject:data forKey:key];
      }
      else {
	NSException *e;

	e = [[NGImap4ParserException alloc] initWithFormat:
					      @"unsupported fetch %@", key];
        [self setLastException:[e autorelease]];
	return NO;
      }
      if (_la(self, 0) == ' ')
        _consume(self, 1);
    }
    if (fetch) {
      [fetch setObject:number forKey:@"msn"];
      [result_ addObject:fetch forKey:@"fetch"];
      _consume(self, 1); /* consume ')' */
      _match(self, '\n');
    }
    else { /* no correct fetch line */
      _parseUntil(self, '\n');
    }

    [fetch release]; fetch = nil;

    return YES;
  }

  { /* got a number request from select like  exists or recent */
    key = _parseUntil(self, '\n');
    [result_ addObject:number forKey:[key lowercaseString]];
  }
  return YES;
}

static BOOL _parseGreetingsSieveResponse(NGImap4ResponseParser *self,
                                         NGMutableHashMap *result_) 
{
  BOOL isOK;
  
  while (!(isOK = _parseOkSieveResponse(self, result_))) {
    NSString *key, *value;

    if (!(key = _parseStringSieveResponse(self))) {
      break;
    }
    if (_la(self, 0) == ' ') {
      _consume(self, 1);

      if (!(value = _parseStringSieveResponse(self))) {
        break;
      }
    }
    else {
      value = @"";
    }
    _parseUntil(self, '\n');
    [result_ addObject:value forKey:[key lowercaseString]];
  }
  return isOK;
}

static BOOL _parseDataSieveResponse(NGImap4ResponseParser *self,
				    NGMutableHashMap *result_) 
{
  NSString *str;
  NSData   *data;

  if ((data = _parseData(self)) == nil)
    return NO;
  
  str = [[StrClass alloc] initWithData:data encoding:defCStringEncoding];
  [result_ setObject:str forKey:@"data"];
  [str release]; str = nil;
  return YES;
}

static BOOL _parseOkSieveResponse(NGImap4ResponseParser *self,
                                  NGMutableHashMap *result_) 
{
  if (!((_la(self, 0) == 'O') && (_la(self, 1) == 'K')))
    return NO;
    
  _consume(self, 2);

  if (_la(self, 0) == ' ') {
      NSString *reason;

      if ((reason = _parseContentSieveResponse(self)))
        [result_ addObject:reason forKey:@"reason"];
  }
  _parseUntil(self, '\n');

  [result_ addObject:YesNum forKey:@"ok"];
  return YES;
}

static BOOL _parseNoSieveResponse(NGImap4ResponseParser *self,
                                  NGMutableHashMap *result_) 
{
  NSString *data;
  
  if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' ')))
    return NO;
    
  _consume(self, 3);

  data = _parseContentSieveResponse(self);
    
  [result_ addObject:NoNum forKey:@"ok"];
  if (data) [result_ addObject:data forKey:@"reason"];
  return YES;
}

static NSString *_parseContentSieveResponse(NGImap4ResponseParser *self) {
  NSString *str;
  NSData *data;
  
  if ((str = _parseStringSieveResponse(self)))
    return str;
  
  if ((data = _parseData(self)) == nil)
    return nil;
  
  return [[[StrClass alloc] initWithData:data encoding:defCStringEncoding]
                          autorelease];
}

static NSString *_parseStringSieveResponse(NGImap4ResponseParser *self) {
  if (_la(self, 0) != '"')
    return nil;
  
  _consume(self, 1);
  return _parseUntil(self, '"');
}

static NSString *_parseBodyDecodeString(NGImap4ResponseParser *self,
                                        BOOL _convertString,
                                        BOOL _decode)
{

  NSString *str;
  
  if (_la(self, 0) == '"') {
    _consume(self, 1);
    str = _parseUntil(self, '"');
  }
  else if (_la(self, 0) == '{') {
    NSData *data;
    
    if (debugDataOn) [self logWithFormat:@"parse body decode string"];
    data = _parseData(self);

    if (_decode)
      data = [data decodeQuotedPrintableValueOfMIMEHeaderField:nil];
    
    return [[[StrClass alloc] initWithData:data encoding:encoding]
                            autorelease];
  }
  else {
    str = _parseUntil2(self, ' ', ')');
  }
  if (_convertString) {
    if ([[str lowercaseString] isEqualToString:@"nil"])
      str = @"";
  }
  if (_decode) {
    id  d;

    d = [str dataUsingEncoding:defCStringEncoding];
    d = [d decodeQuotedPrintableValueOfMIMEHeaderField:nil];

    if ([d isKindOfClass:StrClass])
      str = d;
    else {
      str = [[[StrClass alloc] initWithData:d encoding:encoding]
                             autorelease];
    }
  }
  return str;
}


static NSString *_parseBodyString(NGImap4ResponseParser *self,
                                  BOOL _convertString)
{
  return _parseBodyDecodeString(self, _convertString, NO);
}

static NSDictionary *_parseBodyParameterList(NGImap4ResponseParser *self)
{
  NSMutableDictionary *list;

  if (_la(self, 0) == '(') {
    _consume(self, 1);

    list = [NSMutableDictionary dictionaryWithCapacity:4];
  
    while (_la(self,0) != ')') {
      NSString *key, *value;

      if (_la(self, 0) == ' ')
        _consume(self, 1);
      
      key = _parseBodyString(self, YES);
      _match(self, ' ');
      value = _parseBodyDecodeString(self, YES, YES);

      [list setObject:value forKey:[key lowercaseString]];
    }
    _match(self, ')');
  }
  else {
    NSString *str;
    str = _parseBodyString(self, YES);

    if ([str length] > 0) {
      NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str);
    }
    list = (id)[NSDictionary dictionary];
  }
  return list;
}

static NSArray *_parseAddressStructure(NGImap4ResponseParser *self) {
  NSString *personalName, *sourceRoute, *mailboxName, *hostName;
  
  _match(self, '(');
  personalName = _parseBodyString(self, YES);
  _match(self, ' ');
  sourceRoute = _parseBodyString(self, YES);
  _match(self, ' ');
  mailboxName = _parseBodyString(self, YES);
  _match(self, ' ');
  hostName = _parseBodyString(self, YES);
  _match(self, ')');
  return [NSDictionary dictionaryWithObjectsAndKeys:
                       personalName, @"personalName",
                       sourceRoute,  @"sourceRoute",
                       mailboxName,  @"mailboxName",
                       hostName,     @"hostName", nil];
}

static NSArray *_parseParenthesizedAddressList(NGImap4ResponseParser *self) {
  NSMutableArray *result;
  result = [NSMutableArray arrayWithCapacity:8];

  if (_la(self, 0) == '(') {
    _consume(self, 1);
    while (_la(self, 0) != ')') {
      [result addObject:_parseAddressStructure(self)];
    }
    _consume(self, 1);
  }
  else {
    NSString *str;
    str = _parseBodyString(self, YES);

    if ([str length] > 0) {
      NSLog(@"%s: got unexpected string %@", __PRETTY_FUNCTION__, str);
    }
    result = (id)[NSArray array];
  }
  return result;
}

static NSDictionary *_parseSingleBody(NGImap4ResponseParser *self) {
  NSString            *type, *subtype, *bodyId, *description,
                      *encoding, *bodysize;
  NSDictionary        *parameterList;
  NSMutableDictionary *dict;

  type = [_parseBodyString(self, YES) lowercaseString];
  _match(self, ' ');
  subtype = _parseBodyString(self, YES);
  _match(self, ' ');
  parameterList = _parseBodyParameterList(self);
  _match(self, ' ');
  bodyId = _parseBodyString(self, YES);
  _match(self, ' ');
  description = _parseBodyString(self, YES);
  _match(self, ' ');
  encoding = _parseBodyString(self, YES);
  _match(self, ' ');
  bodysize = _parseBodyString(self, YES);

  dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                              type, @"type",
                              subtype, @"subtype",
                              parameterList, @"parameterList",
                              bodyId,        @"bodyId",
                              description,   @"description",
                              encoding,      @"encoding",
                              bodysize,      @"size", nil];
  
  if ([type isEqualToString:@"text"]) {
    _match(self, ' ');
    [dict setObject:_parseBodyString(self, YES) forKey:@"lines"];
  }
  else if ([type isEqualToString:@"message"]) {
    if (_la(self, 0) != ')') {
      _match(self, ' ');
      _match(self, '(');
      [dict setObject:_parseBodyString(self, YES) forKey:@"date"];
      _match(self, ' ');
      [dict setObject:_parseBodyString(self, YES) forKey:@"subject"];
      _match(self, ' ');
      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"from"];
      _match(self, ' ');
      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"sender"];
      _match(self, ' ');
      [dict setObject:_parseParenthesizedAddressList(self)
            forKey:@"reply-to"];
      _match(self, ' ');
      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"to"];
      _match(self, ' ');
      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"cc"];
      _match(self, ' ');
      [dict setObject:_parseParenthesizedAddressList(self) forKey:@"bcc"];
      _match(self, ' ');
      [dict setObject:_parseBodyString(self, YES) forKey:@"in-reply-to"];
      _match(self, ' ');
      [dict setObject:_parseBodyString(self, YES) forKey:@"messageId"];
      _match(self, ')');
      _match(self, ' ');
      [dict setObject:_parseBody(self) forKey:@"body"];
      _match(self, ' ');
      [dict setObject:_parseBodyString(self, YES) forKey:@"bodyLines"];
    }
  }
  return dict;
}

static NSDictionary *_parseMultipartBody(NGImap4ResponseParser *self) {
  NSMutableArray *parts;
  NSString       *kind;

  parts = [NSMutableArray arrayWithCapacity:4];

  while (_la(self, 0) == '(') {
    [parts addObject:_parseBody(self)];
  }
  _match(self, ' ');
  kind = _parseBodyString(self, YES);
  return [NSDictionary dictionaryWithObjectsAndKeys:
                       parts,        @"parts",
                       @"multipart", @"type",
                       kind        , @"subtype", nil];
}

static NSDictionary *_parseBody(NGImap4ResponseParser *self) {
  NSDictionary *result;

  _match(self, '(');

  if (_la(self, 0) == '(') {
    result = _parseMultipartBody(self);
  }
  else {
    result = _parseSingleBody(self);
  }
  if (_la(self,0) != ')') {
    NSString *str;

    str = _parseUntil(self, ')');
    NSLog(@"%s: got noparsed content %@", __PRETTY_FUNCTION__,
          str);
  }
  else 
    _consume(self, 1);

  return result;
}

- (NSDictionary *)_parseBodyContent {
  NSData *data;
  
  if (_la(self, 0) == '"') {
    NSString *str;
    _consume(self,1);
    
    str = _parseUntil(self, '"');
    data = [str dataUsingEncoding:defCStringEncoding];
  }
  else 
    data = _parseData(self);
  
  if (data == nil) {
    [self logWithFormat:@"ERROR(%s): got no data.", __PRETTY_FUNCTION__];
    return nil;
  }
  return [NSDictionary dictionaryWithObject:data forKey:@"data"];
}


static NSArray *_parseFlagArray(NGImap4ResponseParser *self) {
  NSString *flags;
  
  _match(self, '(');
  
  flags = _parseUntil(self, ')');
  if ([flags length] == 0) {
    static NSArray *emptyArray = nil;
    if (emptyArray == nil) emptyArray = [[NSArray alloc] init];
    return emptyArray;
  }
  else
    return [[flags lowercaseString] componentsSeparatedByString:@" "];
}

static BOOL _parseFlagsUntaggedResponse(NGImap4ResponseParser *self,
                                        NGMutableHashMap *result_) {
  if ((_la(self, 0) == 'F')
      && (_la(self, 1) == 'L')
      && (_la(self, 2) == 'A')
      && (_la(self, 3) == 'G')
      && (_la(self, 4) == 'S')      
      && (_la(self, 5) == ' ')) {
    _consume(self, 6);
    [result_ addObject:_parseFlagArray(self) forKey:@"flags"];
    _match(self, '\n');
    return YES;
  }
  return NO;
}

static BOOL _parseBadUntaggedResponse(NGImap4ResponseParser *self,
                                     NGMutableHashMap *result_) 
{
  if (!((_la(self, 0)=='B') && (_la(self, 1)=='A') && (_la(self, 2)=='D')      
	&& (_la(self, 3) == ' ')))
    return NO;

  _consume(self, 4);
  [result_ addObject:_parseUntil(self, '\n') forKey:@"bad"];
  return YES;
}

static BOOL _parseNoOrOkArguments(NGImap4ResponseParser *self,
                                  NGMutableHashMap *result_, NSString *_key) 
{
  NSString *obj;

  obj = nil;
  
  if (_la(self, 0) == '[') {
    NSString *key;
      
    _consume(self, 1);
    key = _parseUntil2(self, ']', ' ');

    /* possible kinds of untagged OK responses are either
     * OK [ALERT] System shutdown in 10 minutes
     or               
     * OK [UNSEEN 14]
     or
     * OK [PERMANENTFLAGS (\Answered \Flagged \Draft \Deleted \Seen \*)]
     */
    if (_la(self, 0) == ']') {

      _consume(self, 1);
      if (_la(self, 0) == ' ') {
        id value;
        
        _consume(self, 1);
        value = _parseUntil(self, '\n');
        if ([value length] > 0) {
          obj = [[NSDictionary alloc] 
		  initWithObjects:&value forKeys:&key count:1];
	}
        else
          obj = [key retain];
      }
      else {
        obj = [key retain];
        _parseUntil(self, '\n');
      }
    }
    else { /* _la(self, 0) should be ' ' */
      id value;

      value = nil;
      
      _consume(self, 1);
      if (_la(self, 0) == '(') {
        value = _parseFlagArray(self);
        _consume(self, 1); /* consume ']' */
      }
      else {
        value = _parseUntil(self, ']');
      }
      {
        id tmp;

        tmp = _parseUntil(self, '\n');
        
        obj = [[NSDictionary alloc] initWithObjectsAndKeys:
                            value, key,
                            tmp,   @"comment", nil];
      }
    }
  }
  else
    obj = [_parseUntil(self, '\n') retain];

  [result_ addObject:obj forKey:_key];
  [obj release];
  return YES;
}


static BOOL _parseNoUntaggedResponse(NGImap4ResponseParser *self,
                                     NGMutableHashMap *result_) 
{
  if (!((_la(self, 0)=='N') && (_la(self, 1)=='O') && (_la(self, 2)==' ')))
    return NO;

  _consume(self, 3);
  return _parseNoOrOkArguments(self, result_, @"no");
}

static BOOL _parseOkUntaggedResponse(NGImap4ResponseParser *self,
                                     NGMutableHashMap *result_) 
{
  if (!((_la(self, 0)=='O') && (_la(self, 1)=='K') && (_la(self, 2)==' ')))
    return NO;
  
  _consume(self, 3);
  return _parseNoOrOkArguments(self, result_, @"ok");
}

static NSNumber *_parseUnsigned(NGImap4ResponseParser *self) {
  unsigned      n;
  unsigned char c;
  BOOL     isNumber;

  isNumber = NO;  
  n        = 0;  
  c        = _la(self, 0);
  
  while ((c >= '0') && (c <= '9')) {
    _consume(self, 1);
    isNumber = YES;
    n        = 10 * n + (c - 48);
    c        = _la(self, 0);
  }
  if (!isNumber)
    return nil;
  
  return [NumClass numberWithUnsignedInt:n];
}

static NSString *_parseUntil(NGImap4ResponseParser *self, char _c) {
  /*
    _parseUntil(self, char) consume the stop char
    normalize \r\n constructions
  */
  // TODO: optimize!
  char            buf[1024], c;
  NSMutableString *str;
  unsigned        cnt;

  cnt = 0;
  str = nil;  
  while ((c = _la(self, 0)) != _c) {
    buf[cnt] = c;
    _consume(self, 1);
    cnt++;
    if (cnt == 1024) {
      if (str == nil)
        str = (NSMutableString *)
          [NSMutableString stringWithCString:buf length:1024];
      else {
        NSString *s;
        
        s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024];
        [str appendString:s];
        [s release];
      }
      cnt = 0;
    }
  }
  _consume(self,1); /* consume known stop char */
  if (_c == '\n' && cnt > 0) {
    if (buf[cnt-1] == '\r')
      cnt--;
  }
  
  if (str == nil)
    return [StrClass stringWithCString:buf length:cnt];
  else {
    NSString *s, *s2;
    
    s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt];
    s2 = [str stringByAppendingString:s];
    [s release];
    return s2;
  }
}

static NSString *_parseUntil2(NGImap4ResponseParser *self, char _c1, char _c2){
  /* _parseUntil2(self, char, char) doesn`t consume the stop-chars */
  char            buf[1024], c;
  NSMutableString *str;
  unsigned        cnt;

  cnt = 0;
  c   = _la(self, 0);
  str = nil;
  
  while ((c != _c1) && (c != _c2)) {
    buf[cnt] = c;
    _consume(self, 1);
    cnt++;
    if (cnt == 1024) {
      if (str == nil)
        str = (NSMutableString *)
                 [NSMutableString stringWithCString:buf length:1024];
      else {
        NSString *s;
        s = [(NSString *)[StrClass alloc] initWithCString:buf length:1024];
        [str appendString:s];
        [s release];
      }
      
      cnt = 0;
    }
    c = _la(self, 0);    
  }

  if (str == nil)
    return [StrClass stringWithCString:buf length:cnt];
  
  {
    NSString *s, *s2;
    
    s = [(NSString *)[StrClass alloc] initWithCString:buf length:cnt];
    s2 = [str stringByAppendingString:s];
    [s release];
    return s2;
  }
}


static __inline__ void _match(NGImap4ResponseParser *self, char _match) {
  if (_la(self,0) == _match) {
    _consume(self, 1);
    return;
  }
  {
    NSException *e;
    if (self->debug) {
      e = [[NGImap4ParserException alloc]
                                     initWithFormat:@"unexpected char <%c> "
                                     @"expected <%c> <%@>",
                                     _la(self, 0), _match,
                                     self->serverResponseDebug];
    }
    else {
      e = [[NGImap4ParserException alloc]
                                     initWithFormat:@"unexpected char <%c> "
                                     @"expected <%c>",
                                     _la(self, 0), _match];
    }
    e = [e autorelease];
    [self setLastException:e];
  }
}

static __inline__ void _consume(NGImap4ResponseParser *self, unsigned _cnt) {
  /* Normalize end of line */

  if (_cnt == 0)
    return;
  
  _cnt +=  (__la(self, _cnt - 1) == '\r') ? 1 : 0;
  
  if (self->debug) {
    unsigned cnt;
    
    for (cnt = 0; cnt < _cnt; cnt++) {
      NSString *s;
      unichar c = _la(self, cnt);
      
      if (c == '\r')
	continue;
        
      s = [[StrClass alloc] initWithCharacters:&c length:1];
      [self->serverResponseDebug appendString:s];
      [s release];
      
      if (c == '\n') {
          if ([self->serverResponseDebug cStringLength] > 2) {
            fprintf(stderr, "S[%p]: %s", self,
                    [self->serverResponseDebug cString]);
          }
          [self->serverResponseDebug release];
          self->serverResponseDebug = 
            [[NSMutableString alloc] initWithCapacity:512];
      }
    }
  }
  [self->buffer consume:_cnt];
}

@end /* NGImap4ResponseParser */
