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

  This file is part of OGo

  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.
*/
// $Id$

#include "NGMimeMessageParser.h"
#include "NGMimeMessage.h"
#include "common.h"



@interface NGMimeMessageParserDelegate : NSObject
@end

@implementation NGMimeMessageParserDelegate

static int   UseFoundationStringEncodingForMimeHeader = -1;
static Class NGMimeMessageParserClass                 = NULL; 

+ (void)initialize {
  if (UseFoundationStringEncodingForMimeHeader == -1) {
    UseFoundationStringEncodingForMimeHeader
      = [[NSUserDefaults standardUserDefaults]
                         boolForKey:@"UseFoundationStringEncodingForMimeHeader"]
      ? 1 : 0;
  }
  if (NGMimeMessageParserClass == NULL) {
    NGMimeMessageParserClass = [NGMimeMessageParser class];
  }
}

- (id)parser:(id)_parser parseHeaderField:(NSString *)_field data:(NSData *)_data
{
  id v = nil;

  if ([_parser isKindOfClass:NGMimeMessageParserClass] == NO) {
    NGMimeMessageParser *parser = nil;

    parser = [[NGMimeMessageParserClass alloc] init];
    v = [parser valueOfHeaderField:_field data:_data];
    [parser release]; parser = nil;
  }
  return v;
}

- (id<NGMimeBodyParser>)parser:(NGMimePartParser *)_parser
  bodyParserForPart:(id<NGMimePart>)_part
{
  id                   ctype;
  NGMimeType           *contentType;

  ctype = [_part contentType];
  
  contentType = ([ctype isKindOfClass:[NGMimeType class]])
    ? ctype
    : [NGMimeType mimeType:[ctype stringValue]];
  
  if ([[contentType type] isEqualToString:@"message"] &&
      [[contentType subType] isEqualToString:@"rfc822"]) {
    return [[[NGMimeRfc822BodyParser alloc] init] autorelease];
  }
  return nil;
}


@end /* NGMimeMessageParserDelegate */

@implementation NGMimeMessageParser

static Class NSStringClass = Nil;

+ (int)version {
  return 3;
}
+ (void)initialize {
  NSAssert2([super version] == 3,
            @"invalid superclass (%@) version %i !",
            NSStringFromClass([self superclass]), [super version]);
  if (NSStringClass == Nil)
    NSStringClass = [NSString class];
}

- (id)init {
  if ((self = [super init])) {
    [self setDelegate:[NGMimeMessageParserDelegate new]];
  }
  return self;
}

/* factory */

- (id<NGMimePart>)producePartWithHeader:(NGHashMap *)_header {
  return [NGMimeMessage messageWithHeader:_header];
}

/* header field specifics */

- (id)valueOfHeaderField:(NSString *)_name data:(id)_data {
  // check data for 8-bit headerfields (RFC 2047 (MIME PART III))
  
  /* check whether we got passed a string ... */
  if ([_data isKindOfClass:NSStringClass]) {
    NSLog(@"%s: WARNING unexpected class for headerfield %@ (value %@)",
          __PRETTY_FUNCTION__, _name, _data);
    return [super valueOfHeaderField:_name data:_data];
  }
  _data = [_data decodeQuotedPrintableValueOfMIMEHeaderField:_name];
  return [super valueOfHeaderField:_name data:_data];
}

@end /* NGMimeMessageParser */

@implementation NSData(MimeQPHeaderFieldDecoding)

- (id)decodeQuotedPrintableValueOfMIMEHeaderField:(NSString *)_name {
  // check data for 8-bit headerfields (RFC 2047 (MIME PART III))
  static Class NGMimeTypeClass = Nil;
  enum {
    NGMimeMessageParser_quoted_start   = 1,
    NGMimeMessageParser_quoted_charSet = 2,
    NGMimeMessageParser_quoted_qpData  = 3,
    NGMimeMessageParser_quoted_end     = 4
  } status = NGMimeMessageParser_quoted_start;
  unsigned int        length;
  const unsigned char *bytes, *firstEq;
  BOOL foundQP = NO;

  if (NSStringClass   == Nil) NSStringClass   = [NSString class];
  if (NGMimeTypeClass == Nil) NGMimeTypeClass = [NGMimeType class];
  
  length = [self length];
  
  /* check whether the string is long enough to be quoted etc */
  if (length <= 6)
    return self;
  
  /* check whether the string contains QP tokens ... */
  bytes = [self bytes];
  
  if ((firstEq = memchr(bytes, '=', length)) == NULL)
    return self;
  
  /* process data ... (quoting etc) */
  {
    unichar       *buffer;
    unsigned int  bufLen, maxBufLen;
    NSString      *charset;
    BOOL          appendLC;
    int           cnt, tmp;
    unsigned char encoding;
    
    buffer = calloc(sizeof(unichar), length + 13);
    
    maxBufLen             = length + 3;
    buffer[maxBufLen - 1] = '\0';
    bufLen                = 0;
    
    encoding = 0;
    tmp      = -1;
    appendLC = YES;      
    charset  = nil;
    status   = NGMimeMessageParser_quoted_start;

    /* copy data up to first '=' sign */
    if ((cnt = (firstEq - bytes)) > 0) {
      for (; bufLen < cnt; bufLen++) 
        buffer[bufLen] = bytes[bufLen];
    }
    
    for (; cnt < (length-1); cnt++) {
      appendLC = YES;      
      
      if (status == NGMimeMessageParser_quoted_start) {
        if ((bytes[cnt] == '=') && (bytes[cnt + 1] == '?')) { // found begin
          cnt++;
          status = NGMimeMessageParser_quoted_charSet;
        }
        else { // other char
          if (bytes[cnt + 1] != '=') {
            buffer[bufLen++] = bytes[cnt];
            buffer[bufLen++] = bytes[cnt+1];
            cnt++;
            if (cnt >= length - 1)
              appendLC = NO;
          }
          else {
            buffer[bufLen++] = bytes[cnt];
          }
        }
      }
      else if (status == NGMimeMessageParser_quoted_charSet) {
        if (tmp == -1)
          tmp = cnt;
	
        if (bytes[cnt] == '?') {
          charset = 
	    [NSStringClass stringWithCString:(bytes + tmp) length:cnt - tmp];
          tmp = -1;
	  
          if ((length - cnt) > 2) { 
	    // set encoding (eg 'q' for quoted printable)
            cnt++; // skip '?'
            encoding = bytes[cnt];
            cnt++; // skip encoding
            status = NGMimeMessageParser_quoted_qpData;
          }
          else { // unexpected end
            NSLog(@"WARNING: unexpected end of header");
            appendLC = NO;
            break;
          }
        }
      }
      else if (status == NGMimeMessageParser_quoted_qpData) {
        if (tmp == -1)
          tmp = cnt;
	
        if ((bytes[cnt] == '?') && (bytes[cnt + 1] == '=')) {
          NSData           *tmpData;
          NSString         *tmpStr;
	  unsigned int     tmpLen;
	  
          tmpData = _rfc2047Decoding(encoding, bytes + tmp, cnt - tmp);
	  foundQP = YES;

	  /* 
	     create a temporary string for charset conversion ... 
	     Note: the headerfield is currently held in ISO Latin 1
	  */
          tmpStr = nil;
          
          if (!UseFoundationStringEncodingForMimeHeader) {
            tmpStr = [NSStringClass stringWithData:tmpData
                                    usingEncodingNamed:charset];
          }
          if (tmpStr == nil) {
            NSStringEncoding enc;
            
            enc    = [NGMimeTypeClass stringEncodingForCharset:charset];
            tmpStr = [[[NSStringClass alloc] initWithData:tmpData encoding:enc]
                                      autorelease];
          }
	  tmpLen = [tmpStr length];
	  
	  if ((tmpLen + bufLen) < maxBufLen) {
	    [tmpStr getCharacters:(buffer + bufLen)];
	    bufLen += tmpLen;
	  }
	  else {
	    NSLog(@"ERROR[%s]: quoted data to large --> ignored %@",
		  __PRETTY_FUNCTION__, tmpStr);
	  }
          tmp = -1;
          cnt++;
          appendLC = YES;
          status   = NGMimeMessageParser_quoted_start;
        }
      }
    }
    if (appendLC == YES) {
      if (cnt < length) {
        buffer[bufLen] = bytes[cnt];
        bufLen++;
      }
    }
    buffer[bufLen] = '\0';
    {
      id data;

      data = nil;
      
      if (buffer && foundQP) {
        data = [[[NSString alloc] initWithCharacters:buffer length:bufLen]
                           autorelease];
        if (data == nil) {
          NSLog(@"%s: got no string for buffer '%s', length '%i' !", 
                __PRETTY_FUNCTION__,
                buffer, bufLen);
        }
      }
      if (!data) {
        data = self;
      }
      free(buffer); buffer = NULL;
      return data;
    }
  }
  return self;
}

@end /* NSData(MimeQPHeaderFieldDecoding) */

@implementation NGMimeRfc822BodyParser

+ (int)version {
  return 2;
}
+ (void)initialize {
  NSAssert2([super version] == 2,
            @"invalid superclass (%@) version %i !",
            NSStringFromClass([self superclass]), [super version]);
}

- (id)parseBodyOfPart:(id<NGMimePart>)_part data:(NSData *)_data
  delegate:(id)_d
{
  id<NGMimePart> body;
  id             parser; // NGMimeMessageParser

  parser = [[NGMimeMessageParser alloc] init];
  body = [parser parsePartFromData:_data];
  [parser release]; parser = nil;
  
  return body;
}

@end /* NGMimeRfc822BodyParser */
