//---------------------------------------------------------------------------------------
//	NSData+MIME.m created by erik on Sun 12-Jan-1997
//	This code is part of the Alexandra Newsreader Project. For copyright details see
//	GNU public license version 2 or above. No warranties implied. Use at own risk.
//	More information can be found at <http://www.object-factory.com/Alexandra>.
//	@(#)$Id: NSData+MIME.m,v 1.7 1998/09/28 12:28:38 erik Exp $
//---------------------------------------------------------------------------------------

#import "Utilities.h"
#import "NSCharacterSet+MIME.h"
#import "NSData+MIME.h"
#import "NSString+MIME.h"


//---------------------------------------------------------------------------------------
//	Mother's little helpers
//---------------------------------------------------------------------------------------

typedef unsigned char byte;

static char basishex[] =
   "0123456789ABCDEF";

static byte indexhex[128] = {
    99,99,99,99, 99,99,99,99, 99,99,99,99, 99,99,99,99,
    99,99,99,99, 99,99,99,99, 99,99,99,99, 99,99,99,99,
    99,99,99,99, 99,99,99,99, 99,99,99,99, 99,99,99,99,
     0, 1, 2, 3,  4, 5, 6, 7,  8, 9,99,99, 99,99,99,99,
    99,10,11,12, 13,14,15,99, 99,99,99,99, 99,99,99,99,
    99,99,99,99, 99,99,99,99, 99,99,99,99, 99,99,99,99,
    99,10,11,12, 13,14,15,99, 99,99,99,99, 99,99,99,99,
    99,99,99,99, 99,99,99,99, 99,99,99,99, 99,99,99,99
};

static __inline__ void encode2bytehex(unsigned int value, char *buffer)
{
    buffer[0] = basishex[value/16];
    buffer[1] = basishex[value%16];
}


static __inline__ unsigned int decode2bytehex(const char *p)
{
    return indexhex[(int)*p] * 16 + indexhex[(int)*(p+1)];
}


static byte basis64[] =
   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

static byte index64[128] = {
    99,99,99,99, 99,99,99,99, 99,99,99,99, 99,99,99,99,
    99,99,99,99, 99,99,99,99, 99,99,99,99, 99,99,99,99,
    99,99,99,99, 99,99,99,99, 99,99,99,62, 99,99,99,63,
    52,53,54,55, 56,57,58,59, 60,61,99,99, 99,99,99,99,
    99, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
    15,16,17,18, 19,20,21,22, 23,24,25,99, 99,99,99,99,
    99,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
    41,42,43,44, 45,46,47,48, 49,50,51,99, 99,99,99,99
};


static __inline__ byte char64(byte c)
{
    return (c > 127) ? 99 : index64[(int)c];
}


static __inline__ byte invchar64(byte b)
{
    return basis64[(int)b];
}


//---------------------------------------------------------------------------------------
//      constants
//---------------------------------------------------------------------------------------

NSString *MIME7BitContentTransferEncoding = @"7bit";
NSString *MIME8BitContentTransferEncoding = @"8bit";
NSString *MIMEBinaryContentTransferEncoding = @"binary";
NSString *MIMEQuotedPrintableContentTransferEncoding = @"quoted-printable";
NSString *MIMEBase64ContentTransferEncoding = @"base64";


//---------------------------------------------------------------------------------------
   @implementation NSData(MIMEExtensions_Alexandra)
//---------------------------------------------------------------------------------------

- (NSData *)decodeContentWithTransferEncoding:(NSString *)encodingName
{
    encodingName = [encodingName lowercaseString];
    if([encodingName isEqualToString:MIME7BitContentTransferEncoding])
        return self;
    if([encodingName isEqualToString:MIME8BitContentTransferEncoding])
        return self;
    if([encodingName isEqualToString:MIMEBinaryContentTransferEncoding])
        return self;
    if([encodingName isEqualToString:MIMEQuotedPrintableContentTransferEncoding])
        return [self decodeQuotedPrintable];
    if([encodingName isEqualToString:MIMEBase64ContentTransferEncoding])
        return [self decodeBase64];
    [NSException raise:NSInvalidArgumentException format:@"unknown content transfer encoding; found '%@'", encodingName];
    return nil;
}


//---------------------------------------------------------------------------------------
//	DECODING SCHEMES
//---------------------------------------------------------------------------------------

- (NSData *)decodeQuotedPrintable;
{
    NSMutableData 	*decodedData;
    const char          *source, *endOfSource;
    char                        *dest;

    source = [self bytes];
    endOfSource = source + [self length];
    decodedData = [NSMutableData dataWithLength:[self length]];
    dest = [decodedData mutableBytes];

    while(source < endOfSource)
        {
        if(*source == EQUALS)
            {
            source += 1;
            if(iscrlf(*source) || iswhitespace(*source))
                {
                while(iswhitespace(*source))
                    source += 1;
                if(*source == CR)       // this is not exactly according
                    source += 1;        // to rfc1521 but it does decode
                if(*source == LF)       // it properly while offering
                    source += 1;        // some robustness...
                }
            else
                {
                if(isxdigit(*source) && isxdigit(*(source+1)))
                    {
                    *dest++ = decode2bytehex(source);
                    source += 2;
                    }
                }
            }
        else
            {
            *dest ++ = *source++;
            }
        }
    [decodedData setLength:(unsigned int)((void *)dest - [decodedData mutableBytes])];

    return decodedData;
}


- (NSData *)decodeQuotedPrintable1522;
{
    NSMutableData 	*decodedData;
    const char          *source, *endOfSource;
    char                        *dest;

    source = [self bytes];
    endOfSource = source + [self length];
    decodedData = [NSMutableData dataWithLength:[self length]];
    dest = [decodedData mutableBytes];

    while(source < endOfSource)
        {
        unsigned char c = *source++;
        if((c == EQUALS) && isxdigit(*(source)) && isxdigit(*(source+1)))
            {
            c = decode2bytehex(source);
            source += 2;
            }
        else if(c == UNDERSCORE)
            {
            c = SPACE;
            }
        *dest++ = c;
        }
    [decodedData setLength:(unsigned int)((void *)dest - [decodedData mutableBytes])];

    return decodedData;
}


- (NSData *)decodeBase64;
{
    NSMutableData 	*decodedData;
    const byte          *source, *endOfSource;
    byte                        *dest, groupv[4], c, b;
    int                         groupc = 0;
    BOOL                        groupHadPadding = NO;

    source = [self bytes];
    endOfSource = source + [self length];
    decodedData = [NSMutableData dataWithLength:[self length]];
    dest = [decodedData mutableBytes];

    while(source < endOfSource)
        {
        c = *source++;
        if((b = char64(c)) != 99)
            {
            groupv[groupc++] = b;
            }
        else if(c == EQUALS)
            {
            if(groupc > 1)
                {
                groupv[groupc++] = 127;
                groupHadPadding = YES;
                }
            }
        if(groupc == 4)
            {
            groupc = 0;
            *dest++ = (groupv[0]<<2) | ((groupv[1]&0x30)>>4);
            if(groupv[2] != 127)
                {
                *dest++ = ((groupv[1]&0xF) << 4) | ((groupv[2]&0x3C) >> 2);
                if(groupv[3] != 127)
                    *dest++ = ((groupv[2]&0x03) << 6) | groupv[3];
                }
            if(groupHadPadding)
                break;
            }
        }
    if(groupc != 0)
        LOG(@"MIME Decoder: premature end of base64 encoded data");

    [decodedData setLength:(unsigned int)((void *)dest - [decodedData mutableBytes])];

    return decodedData;
}



//---------------------------------------------------------------------------------------
//	ENCODING SCHEMES
//---------------------------------------------------------------------------------------


- (NSData *)encodeQuotedPrintable;
{
    [NSException raise:NSInternalInconsistencyException format:@"NSData+Mime: -encodeQuotedPrintable not implemented yet."];
    return nil;
}


- (NSData *)encodeQuotedPrintable1522;
{
    return [self encodeQuotedPrintable1522MustEscapeCharactersInString:nil];
}


- (NSData *)encodeQuotedPrintable1522MustEscapeCharactersInString:(NSString *)escChars;
{
    NSMutableCharacterSet *tempCharacterSet;
    NSCharacterSet		  *literalChars;
    NSMutableData		  *buffer;
    unsigned int		  length;
    const byte		   	  *source, *chunkStart, *endOfSource;
    char				  escValue[3] = "=00", underscore = '_';
    
    if(escChars != nil)
        {
        tempCharacterSet = [[NSCharacterSet MIME1522DefaultLiteralCharacterSet] mutableCopy];
        [tempCharacterSet removeCharactersInString:escChars];
        literalChars = [[tempCharacterSet copy] autorelease];
        [tempCharacterSet release];
        }
    else
        {
        literalChars = [NSCharacterSet MIME1522DefaultLiteralCharacterSet];
        }

    length = [self length];
    buffer = [[[NSMutableData alloc] initWithCapacity:length + length / 10] autorelease];

    chunkStart = source = [self bytes];
    endOfSource = source + length;
    while(source < endOfSource)
        {
        if([literalChars characterIsMember:(unichar)(*source)] == NO)
            {
            if((length = (source - chunkStart)) > 0)
                [buffer appendBytes:chunkStart length:length];
            if(*source == SPACE)
                {
                [buffer appendBytes:&underscore length:1];
                }
            else
                {
                encode2bytehex(*source, &escValue[1]);
                [buffer appendBytes:escValue length:3];
                }
            chunkStart = source + 1;
            }
        source += 1;
        }
    [buffer appendBytes:chunkStart length:(source - chunkStart)];

    return buffer;
}


- (NSData *)encodeBase64;
{
    return [self encodeBase64WithLineLength:76 andNewlineAtEnd:YES];
}


- (NSData *)encodeBase64WithLineLength:(unsigned int)lineLength andNewlineAtEnd:(BOOL)endWithNL;
{
    NSMutableData 	*encodedData;
    const byte		*source, *endOfSource;
    byte			*dest, groupv[4], b;
    int				i, bytec = 0, groupc = 0;
    unsigned int	numgroups, groupsPerLine, dataLength;

    source = [self bytes];
    endOfSource = source + [self length];

    numgroups = udivroundup([self length], 3);
    groupsPerLine = lineLength / 4;
    if(groupsPerLine == 0)
        [NSException raise:NSInvalidArgumentException format:@"line length must be > 3"];
    if((lineLength % 4) != 0)
        LOG(@"MIME Encoder: rounded down line length for base64 encoding.");
    dataLength = numgroups * 4;
    if(lineLength > 0)
        dataLength += udivroundup(numgroups, groupsPerLine) * 2;

    encodedData = [NSMutableData dataWithLength:dataLength];
    dest = [encodedData mutableBytes];

    while(source < endOfSource)
        {
        b = *source++;
        switch(bytec++)
            {
        case 0:
            groupv[0]  = (b & 0xFC) >> 2;
            groupv[1]  = (b & 0x03) << 4;
            break;
        case 1:
            groupv[1] |= (b & 0xF0) >> 4;
            groupv[2]  = (b & 0x0F) << 2;
            break;
        case 2:
            groupv[2] |= (b & 0xC0) >> 6;
            groupv[3]  = (b & 0x3F);
            break;
            }
        if((bytec == 3) || (source == endOfSource))
            {
            for(i = 0; i < bytec + 1; i++)
                *dest++ = invchar64(groupv[i]);
            for(; i < 4; i++)
                *dest++ = EQUALS;
            bytec = 0;
            groupc += 1;
            if(((groupc % groupsPerLine) == 0) || ((source == endOfSource) && (endWithNL == YES)))
                {
                *dest++ = CR;
                *dest++ = LF;
                }
            }
        }

    [encodedData setLength:(unsigned int)((void *)dest - [encodedData mutableBytes])];

    return encodedData;
}




//---------------------------------------------------------------------------------------
    @end
//---------------------------------------------------------------------------------------


#if 0
static void appendchars(NSMutableString *buffer, const char *p, unsigned int l)
{
    NSString *s = [[NSString alloc] initWithData:[NSData dataWithBytes:p length:l] encoding:NSISOLatin1StringEncoding];
    [buffer appendString:s];
    [s release];
}
#endif
