//---------------------------------------------------------------------------------------
//	NSString+MIME.m created by erik on Wed 08-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: NSString+MIME.m,v 1.7 1998/10/22 14:27:24 erik Exp $
//---------------------------------------------------------------------------------------

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


//---------------------------------------------------------------------------------------
//	Constants
//---------------------------------------------------------------------------------------

NSString *MIMEFormatException = @"MIMEFormatException";

NSString *MIMEAsciiStringEncoding = @"us-ascii";
NSString *MIMELatin1StringEncoding = @"iso-8859-1";
NSString *MIMELatin2StringEncoding = @"iso-8859-2";
NSString *MIME2022JPStringEncoding = @"iso-2022";


//---------------------------------------------------------------------------------------
    @implementation NSString(MIMEExtensions_Alexandra)
//---------------------------------------------------------------------------------------

+ (NSString *)stringWithData:(NSData *)data MIMEEncoding:(NSString *)encodingName
{
    return [[[NSString alloc] initWithData:data MIMEEncoding:encodingName] autorelease];
}


+ (NSString *)stringWithBytes:(const void *)buffer length:(unsigned int)length MIMEEncoding:(NSString *)encodingName;
{
    return [self stringWithData:[NSData dataWithBytes:buffer length:length] MIMEEncoding:encodingName];
}


//---------------------------------------------------------------------------------------
//	Converting to/from byte representations
//---------------------------------------------------------------------------------------

- initWithData:(NSData *)buffer MIMEEncoding:(NSString *)encodingName;
{
    NSStringEncoding encoding;

    encodingName = [encodingName lowercaseString];
    if((encoding = [NSString stringEncodingForMIMEEncoding:encodingName]) == 0)
        encoding = NSASCIIStringEncoding; // hope that NSString does something reasonable
    return [self initWithData:buffer encoding:encoding];
}


- (NSData *)dataUsingMIMEEncoding:(NSString *)encodingName
{
    NSStringEncoding encoding;

    encodingName = [encodingName lowercaseString];
    if((encoding = [NSString stringEncodingForMIMEEncoding:encodingName]) == 0)
        return nil;
    return [self dataUsingEncoding:encoding];
}


//---------------------------------------------------------------------------------------
//	NSStringEncoding vs. MIME Encoding
//---------------------------------------------------------------------------------------

- (NSString *)recommendedMIMEEncoding;
{
    if([self canBeConvertedToEncoding:NSASCIIStringEncoding])
        return MIMEAsciiStringEncoding;
    if([self canBeConvertedToEncoding:NSISOLatin1StringEncoding])
        return MIMELatin1StringEncoding;
#ifndef LITTLE_FOUNDATION
    if([self canBeConvertedToEncoding:NSISOLatin2StringEncoding])
        return MIMELatin2StringEncoding;
    if([self canBeConvertedToEncoding:NSISO2022JPStringEncoding])
        return MIME2022JPStringEncoding;
#endif
    return nil;
}



+ (NSStringEncoding)stringEncodingForMIMEEncoding:(NSString *)encoding;
{
    static NSMutableDictionary	*table = nil;

    if(table == nil)
        {
        table = [NSMutableDictionary dictionary];
        [table setObject:[NSNumber numberWithUnsignedInt:NSASCIIStringEncoding] forKey:MIMEAsciiStringEncoding];
        [table setObject:[NSNumber numberWithUnsignedInt:NSISOLatin1StringEncoding] forKey:MIMELatin1StringEncoding];
#ifndef LITTLE_FOUNDATION
        [table setObject:[NSNumber numberWithUnsignedInt:NSISOLatin2StringEncoding] forKey:MIMELatin2StringEncoding];
        [table setObject:[NSNumber numberWithUnsignedInt:NSISO2022JPStringEncoding] forKey:MIME2022JPStringEncoding];
#endif
        table = [table copy];
        }
    return [[table objectForKey:[encoding lowercaseString]] unsignedIntValue];
}


//---------------------------------------------------------------------------------------
//	RFC1522 Encoding/Decoding
//---------------------------------------------------------------------------------------

- initWithMIMEHeaderFieldData:(NSData *)value;
{
    NSMutableString	*dest;
    const char		*source, *endOfSource, *vstart, *buffer;
    unsigned int	length;

    dest = [[NSMutableString alloc] init];
    buffer = [value bytes];
    length = [value length];
    vstart = buffer;
    endOfSource = (const char *)buffer + length;
    for(source = buffer; source < endOfSource; source++)
        {
        if((*source == EQUALS) && (source < endOfSource - 1) &&(*(source + 1) == QMARK))
            {
            const char		 *p, *q, *pmax, *qmarkp[4];
            char			 transferEncoding;
            int				 qmarks = 0, len;
            NSStringEncoding stringEncoding;
            NSString 		 *charset, *text, *tmp;
            NSData	 		 *textBuffer = nil;

            pmax = source + umin(endOfSource - (char *)source, 76);
            for(p=source+1; p<pmax-1 && qmarks<4; p++)
                {
                if(*p == QMARK)
                    {
                    if(*(p - 1) == QMARK)
                        break;
                    qmarkp[qmarks] = p;
                    qmarks += 1;
                    }
                else if(*p == SPACE)
                    break;
                }
            if((qmarks < 4) || (*(qmarkp[3] + 1) != EQUALS))
                {
                tmp = [NSString stringWithFormat:@"MIME Decoder: '=?' didn't introducde RFC1522 word in '%@'", [NSString stringWithCString:buffer length:length]];
                LOG(tmp);
                continue;
                }

            q = qmarkp[2] + 1; len = qmarkp[3] - q;
            transferEncoding = tolower(*(qmarkp[1]+1));
            if(transferEncoding == 'q')
                textBuffer = [[NSData dataWithBytes:(void *)q length:len] decodeQuotedPrintable1522];
            else if(transferEncoding == 'b')
                textBuffer = [[NSData dataWithBytes:(void *)q length:len] decodeBase64];
            if(textBuffer != nil)
                {
                q = qmarkp[0] + 1; len = qmarkp[1] - q;
                charset = [[NSString stringWithCString:q length:len] lowercaseString];
                if((stringEncoding = [NSString stringEncodingForMIMEEncoding:charset]) == 0)
                    stringEncoding = NSASCIIStringEncoding;
                text = [[NSString alloc] initWithData:textBuffer encoding:stringEncoding];
                if(vstart != source)
                    [dest appendString:[NSString stringWithBytes:vstart length:source - vstart MIMEEncoding:MIMELatin1StringEncoding]];
                [dest appendString:text];
		[text release];

                vstart = qmarkp[3] + 2;
                source = vstart - 1; // will be incremented by loop!
                }
            }
        }
    if(vstart != source)
	[dest appendString:[NSString stringWithBytes:vstart length:source - vstart
		  MIMEEncoding:MIMELatin1StringEncoding]];
    self = [self initWithString:dest];
    [dest release];
    return self;
}    



- (NSData *)dataForMIMEHeaderField;
{
    NSCharacterSet	*spaceCharacterSet;
    NSMutableData	*buffer;
    NSScanner		*scanner;
    NSMutableString	*text;
    NSString		*currentEncoding, *nextEncoding, *word, *spaces;

    spaceCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@" "];
    buffer = [NSMutableData data];
    scanner = [NSScanner scannerWithString:self];
    [scanner setCharactersToBeSkipped:nil];

    text = [NSMutableString string];
    currentEncoding = nil;
    do
        {
        if([scanner scanCharactersFromSet:spaceCharacterSet intoString:&spaces] == NO)
            spaces = @"";
        if([scanner scanUpToCharactersFromSet:spaceCharacterSet intoString:&word] == NO)
            word = nil;

        nextEncoding = [word recommendedMIMEEncoding];
        if((nextEncoding != currentEncoding) || ([text length] + [word length] + 7 + [currentEncoding length] > 75) || (word == nil))
            {
            if([text length] > 0)
                {
                if(currentEncoding != MIMEAsciiStringEncoding)
                    [buffer appendData:[text dataByWrappingInHeaderFieldWord]];
                else
                    [buffer appendData:[text dataUsingEncoding:NSASCIIStringEncoding]];
                }
            [buffer appendData:[spaces dataUsingEncoding:NSASCIIStringEncoding]];
            currentEncoding = nextEncoding;
            text = [[word mutableCopy] autorelease];
            }
        else
            {
            [text appendString:spaces];
            [text appendString:word];
            }
        }
    while([text length] > 0);
    
    return buffer;
}


- (NSData *)dataByWrappingInHeaderFieldWord;
{
    NSMutableData	*buffer;
    NSData			*stringData, *b64TransferData, *qpTransferData, *transferData;
    NSString		*characterSetName;
    unsigned int	b64Length, qpLength;

    characterSetName = [self recommendedMIMEEncoding];
    stringData = [self dataUsingMIMEEncoding:characterSetName];        
    b64TransferData = [stringData encodeBase64WithLineLength:UINT_MAX-3 andNewlineAtEnd:NO];
    qpTransferData = [stringData encodeQuotedPrintable1522];
    b64Length = [b64TransferData length]; qpLength = [qpTransferData length];
    if((qpLength < 6) || (qpLength < [stringData length] * 3/2) || (qpLength <= b64Length))
       transferData = qpTransferData;
    else
       transferData = b64TransferData;

    if([transferData length] + 7 + [characterSetName length] > 75)
        [NSException raise:NSInvalidArgumentException format:@"Encoding of this string results in a MIME word which exceeds the maximum length of 75 characters. Try to split it into multiple words and make sure that idividual components are separated by whitespaces."];

    buffer = [NSMutableData data];
    [buffer appendData:[[NSString stringWithFormat:@"=?%@?%@?", characterSetName, (transferData == qpTransferData) ? @"Q" : @"B"] dataUsingEncoding:NSASCIIStringEncoding]];
    [buffer appendData:transferData];
    [buffer appendData:[@"?=" dataUsingEncoding:NSASCIIStringEncoding]];

    return buffer;
}


- (NSData *)dataForHeaderFieldBody;
{
	return [self dataUsingMIMEEncoding:MIMEAsciiStringEncoding];
}


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

