//---------------------------------------------------------------------------------------
//	MessagePart.m created by erik on Mon 20-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: MessagePart.m,v 1.8 1998/09/28 12:28:35 erik Exp $
//---------------------------------------------------------------------------------------

#import "Utilities.h"
#import "Message.h"

static void getHeadersAndBody(NSData *buffer, NSDictionary **headerPtr, NSData **bodyPtr);



//---------------------------------------------------------------------------------------
    @implementation MessagePart
//---------------------------------------------------------------------------------------

+ (NSString *)tempAttachmentBasename;
{
#ifndef LITTLE_FOUNDATION
#	warning implementation missing
    return nil;
#else
    // NSLock missing. But then again, we're not multithreaded anyway...
    static int attachmentCount = 0;
    char buf[100]; // stringWithFormat is broken.

    sprintf(buf, "attachment%d", attachmentCount++);

    return [NSString stringWithCString:buf];
#endif
}


//---------------------------------------------------------------------------------------
//	Factory
//---------------------------------------------------------------------------------------

+ (id)messagePartWithData:(NSData *)transferRep;
{
	return [[[self alloc] initFromTransferRepresentation:transferRep] autorelease];
}


//---------------------------------------------------------------------------------------
//	Init
//---------------------------------------------------------------------------------------

- initWithContentType:(NSString *)ct subtype:(NSString *)cst parameters:(NSDictionary *)ctp
{
	TRACE_IN
    [super init];
    return self;
}


- initFromTransferRepresentation:(NSData *)transferRep
{
    NSData					*bodyData;
    NSString		  		*cteString, *string;
    MIMEContentType			*type;
    MIMEContentDisposition	*disposition;

	TRACE_IN
	[super init];

    getHeadersAndBody(transferRep, &headerFields, &bodyData);
	[headerFields retain];

    if((string = [self valueOfHeaderFieldNamed:@"MIME-Version"]) != nil)
        if([string floatValue] > 1.0)
            LOG(([NSString stringWithFormat:@"MIME Decoder: decoding version %@ as 1.0.", string]));
 
    if((cteString = [self valueOfHeaderFieldNamed:@"Content-Transfer-Encoding"]) != nil)
		cteString = [cteString stringByRemovingSurroundingWhitespace]; // i've seen this
	else
        cteString = MIME8BitContentTransferEncoding; // #?# default
    bodyData = [bodyData decodeContentWithTransferEncoding:cteString];

    if((string = [self valueOfHeaderFieldNamed:@"Content-Type"]) != nil)
        type = [[[MIMEContentType alloc] initWithString:string] autorelease];
	else
		type = [[[MIMEContentType alloc] initWithString:@"text/plain; charset=us-ascii"] autorelease];
    [self setValue:type ofHeaderFieldNamed:@"Content-Type"];

    if((string = [self valueOfHeaderFieldNamed:@"Content-Disposition"]) != nil)
        {
        disposition = [[[MIMEContentDisposition alloc] initWithString:string] autorelease];
        [self setValue:disposition ofHeaderFieldNamed:@"Content-Disposition"];
        }
		    
    if([type contentType] == MIMETextContentType)
        [self takeTextualContentsFromData:bodyData];
	else if([type contentType] == MIMEMultipartContentType)
        [self takeMultipartContentsFromData:bodyData];
    else 
		[self takeFileContentsFromData:bodyData];

    return self;
}


- (void)takeTextualContentsFromData:(NSData *)data;
{
    NSString		 *charset, *text;
    NSStringEncoding encoding;

	TRACE_IN
    if((charset = [[self contentTypeParameters] objectForKey:@"charset"]) == nil)
        encoding = NSASCIIStringEncoding;
    else if((encoding = [NSString stringEncodingForMIMEEncoding:charset]) == 0)
        encoding = NSASCIIStringEncoding; // hope that NSString does something reasonable
    text = [[[NSString alloc] initWithData:data encoding:encoding] autorelease];
    [self setContents:text];
}


- (void)takeFileContentsFromData:(NSData *)data;
{
	[self setContents:data];
}


- (void)takeMultipartContentsFromData:(NSData *)data;
{
    NSMutableArray	*newContents;
    NSString		*boundary;
    const char		*btext, *startPtr, *possibleEndPtr, *p, *pmin, *pmax, *q;
    unsigned int	blen;
    NSRange			subpartRange;
    BOOL   			done = NO;

	TRACE_IN
    newContents = [NSMutableArray array];

    if((boundary = [[self contentTypeParameters] objectForKey:@"boundary"]) == nil)
        [NSException raise:MIMEFormatException format:@"no boundary for multipart"];
    btext = [boundary cString];
    blen = strlen(btext);
    
    pmin = p = [data bytes];
    pmax = p + [data length];
    startPtr = possibleEndPtr = NULL;
    while(done == NO)
        {
        if(p > pmax - 5 - blen) // --boundary--\n
            [NSException raise:MIMEFormatException format:@"final boundary not found"];
        if((*p == '-') && (*(p+1) == '-') && (strncmp(p+2, btext, blen) == 0))
            {
            q = p + 2 + blen;
            if((*q == '-') && (*(q+1) == '-'))
                {
                done = YES;
                q += 2;
                }
            if((q = skipspace(q, pmax)) == NULL)  // might have been added
	            [NSException raise:MIMEFormatException format:@"final boundary not found"];
            if(iscrlf(*q) == NO)
                {
                LOG(@"ignoring junk after mime multipart boundary");
                if((q = skiptonewline(q, pmax)) == NULL)
		            [NSException raise:MIMEFormatException format:@"final boundary not found"];
                }

            if(startPtr != NULL)
                {
                subpartRange.location = startPtr - (const char *)[data bytes];
                subpartRange.length = possibleEndPtr - startPtr;
                [newContents addObject:[MessagePart messagePartWithData:[data subdataWithRange:subpartRange]]];
                }
            startPtr = p = skipnewline(q, pmax); // trailing crlf belongs to boundary
            }
        else
            {
            if((p = skiptonewline(p, pmax)) == NULL)
	            [NSException raise:MIMEFormatException format:@"final boundary not found"];
            possibleEndPtr = p;
            p = skipnewline(p, pmax);
            }
       }

    [self setContents:newContents];
}


//---------------------------------------------------------------------------------------
//	ACCESSOR METHODS
//---------------------------------------------------------------------------------------

- (NSData *)transferRepresentation;
{
	TRACE_IN
    return nil;
}


- (NSArray *)headerFieldNames;
{
	TRACE_IN
    return [headerFields allKeys];
}


- (id)valueOfHeaderFieldNamed:(NSString *)aFieldName;
{
	id			 object;
	NSString	 *fieldName;
	NSEnumerator *fieldNameEnum;
	
	TRACE_IN
	if((object = [headerFields objectForKey:aFieldName]) != nil)
		return object;

	fieldNameEnum = [headerFields keyEnumerator];
	while(((fieldName = [fieldNameEnum nextObject]) != nil) && (object == nil))
		{
		if([fieldName compare:aFieldName options:NSCaseInsensitiveSearch] == NSOrderedSame)
			object = [headerFields objectForKey:fieldName];
		}
	return object;
}


- (NSString *)stringValueOfHeaderFieldNamed:(NSString *)fieldName;
{
	id value;
	
	TRACE_IN
	value = [self valueOfHeaderFieldNamed:fieldName];
	if([value isKindOfClass:[NSString class]])
		return value;
	if([value isKindOfClass:[HeaderFieldBody class]])
		return [value stringRepresentation];
	return [value description];
}


- (void)setValue:(id)object ofHeaderFieldNamed:(NSString *)aFieldName;
{
	NSString	 *fieldName;
	NSEnumerator *fieldNameEnum;
	
	TRACE_IN
	if(([headerFields objectForKey:aFieldName]) == nil)
		{
		fieldNameEnum = [headerFields keyEnumerator];
		while((fieldName = [fieldNameEnum nextObject]) != nil)
			{
			if([fieldName compare:aFieldName options:NSCaseInsensitiveSearch] == NSOrderedSame)
				[headerFields removeObjectForKey:fieldName];
			}
		}
	[headerFields setObject:object forKey:aFieldName];
}


- (void)setContents:(id <NSObject>)object;
{
	TRACE_IN
    [contents autorelease];
    contents = [object retain];
}

- (id <NSObject>)contents;
{
	TRACE_IN
    return contents;
}


//---------------------------------------------------------------------------------------
// 	DERIVED ACCESSOR METHODS
//---------------------------------------------------------------------------------------

- (NSString *)contentType;		// returns uniqued string. (compare to constant with ==)
{
	TRACE_IN
    return [[self valueOfHeaderFieldNamed:@"Content-Type"] contentType];
}


- (NSString *)contentSubtype;
{
 	TRACE_IN
   return [[self valueOfHeaderFieldNamed:@"Content-Type"] contentSubtype];
}


- (NSDictionary *)contentTypeParameters;
{
	TRACE_IN
    return [[self valueOfHeaderFieldNamed:@"Content-Type"] parameters];
}


- (NSString *)contentDisposition; // returns uniqued string. (compare to constant with ==)
{
	TRACE_IN
    return [[self valueOfHeaderFieldNamed:@"Content-Disposition"] disposition];
}


- (NSDictionary *)contentDispositionParameters;
{
	TRACE_IN
    return [[self valueOfHeaderFieldNamed:@"Content-Disposition"] parameters];
}


- (NSString *)preferredFilename;
{
    NSString *filename;

	TRACE_IN
    if((filename = [[self contentDispositionParameters] objectForKey:@"filename"]) != nil)
        return [filename lastPathComponent];
    if((filename = [[self contentTypeParameters] objectForKey:@"name"]) != nil)
        return [filename lastPathComponent];
    return nil;
}


- (NSString *)temporaryFilename;
{	// use mailcap?
    NSString *extension, *type, *subtype;

	TRACE_IN
    type = [self contentType];
    subtype = [self contentSubtype];
    
    if((type == MIMEApplicationContentType) && ([subtype isEqualToString:@"postscript"]))
        extension =  @"ps";
    else if((type == MIMETextContentType) && ([subtype isEqualToString:@"plain"]))
        extension = @"text";
	else if((type == MIMEMessageContentType) && ([subtype isEqualToString:@"rfc822"]))
		extension = @"message";
    else if([subtype hasPrefix:@"x-"])
        extension = [subtype substringFromIndex:2];
    else
        extension = subtype;

#ifndef LITTLE_FOUNDATION
    return [[isa tempAttachmentBasename] stringByAppendingPathExtension:extension];
#else
	return [[[isa tempAttachmentBasename] stringByAppendingString:@"."] stringByAppendingString:extension];
#endif
}


- (NSString *)filename;
{
	NSString *filename;

	if((filename = [self preferredFilename]) == nil)
		filename = [self temporaryFilename];
	
	return filename;
}


//---------------------------------------------------------------------------------------
//	DERIVED ACCESSOR METHODS (R/W)
//---------------------------------------------------------------------------------------

- (void)setContentDescription:(NSString *)string;
{
 	TRACE_IN
    [self setValue:string ofHeaderFieldNamed:@"Content-Description"];
}

- (NSString *)contentDescription;
{
	TRACE_IN
    return [self valueOfHeaderFieldNamed:@"Content-Description"];
}


- (void)setContentID:(NSString *)string;
{
	TRACE_IN
    [self setValue:string ofHeaderFieldNamed:@"Content-ID"];
}

- (NSString *)contentID;
{
	TRACE_IN
    return [self valueOfHeaderFieldNamed:@"Content-ID"];
}



//---------------------------------------------------------------------------------------
//	NSObject Stuff
//---------------------------------------------------------------------------------------

- (void)dealloc
{
	TRACE_IN
    [headerFields release];
    [contents release];
    [super dealloc];
}


- (NSString *)description
{
    return [NSString stringWithFormat:@"%@ { type = %@, subtype = %@ }", [super description], [self contentType], [self contentSubtype]];
}


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



static void getHeadersAndBody(NSData *buffer, NSDictionary **headerPtr, NSData **bodyPtr)
{
    const char				*p, *pmax, *fnamePtr, *fbodyPtr, *eolPtr;
    NSMutableDictionary		*headerFields;
    NSMutableData			*fbodyData;
    NSString 				*name, *content;
    NSRange					bodyRange;

    headerFields = [NSMutableDictionary dictionary];
    name = nil;
    fnamePtr = p = [buffer bytes];
    pmax = p + [buffer length];
    fbodyPtr = NULL;
    fbodyData = nil;
    for(;p < pmax; p++)
        {
        if((*p == COLON) && (fbodyPtr == NULL))
            {
            fbodyPtr = p + 1;
            if((fbodyPtr < pmax) && (iswhitespace(*fbodyPtr)))
                fbodyPtr += 1;
            name = [NSString stringWithCString:fnamePtr length:(p - fnamePtr)];
            }
        else if(iscrlf(*p))
            {
            eolPtr = p;
            p = skipnewline(p, pmax);
            if((p < pmax) && iswhitespace(*p)) // folded header!
                {
                if(fbodyData == nil)
                    fbodyData = [NSMutableData dataWithBytes:fbodyPtr length:(eolPtr - fbodyPtr)];
                else
                    [fbodyData appendBytes:fbodyPtr length:(eolPtr - fbodyPtr)];
                fbodyPtr = p;
                }
            else
                {
                if(fbodyData == nil)
                    fbodyData = (id)[NSData dataWithBytes:fbodyPtr length:(eolPtr - fbodyPtr)];
                else
                    [fbodyData appendBytes:fbodyPtr length:(eolPtr - fbodyPtr)];
                content = [[[NSString alloc] initWithMIMEHeaderFieldData:fbodyData] autorelease];
                [headerFields setObject:content forKey:name];

                fbodyData = nil;
                fnamePtr = p;
                fbodyPtr = NULL;
                if((p < pmax) && iscrlf(*p))
                    break;
                }
            }
        }

    *headerPtr = headerFields;
    p = skipnewline(p, pmax);
    bodyRange = NSMakeRange(p - (char *)[buffer bytes], pmax - p);
    *bodyPtr = [buffer subdataWithRange:bodyRange];
}

