// Copyright 1997-1999 Omni Development, Inc.  All rights reserved.
//
// This software may only be used and reproduced according to the
// terms in the file OmniSourceLicense.html, which should be
// distributed with this project and can also be found at
// http://www.omnigroup.com/DeveloperResources/OmniSourceLicense.html.

#import "OWHeaderDictionary.h"

#import <Foundation/Foundation.h>
#import <OmniBase/OmniBase.h>
#import <OmniFoundation/OmniFoundation.h>

#import "OWContentType.h"
#import "OWUnknownDataStreamProcessor.h"

RCS_ID("$Header: /Network/Source/CVS/OmniGroup/Frameworks/OWF/Pipelines.subproj/OWHeaderDictionary.m,v 1.9 1999/07/17 02:51:02 kc Exp $")

@interface OWHeaderDictionary (Private)
- (void)parseContentType;
@end

@implementation OWHeaderDictionary

static NSCharacterSet *ColonSet;
static NSCharacterSet *TSpecialsSet;
static NSMutableCharacterSet *TokenSet;
static NSCharacterSet *QuotedStringSet;
static NSString *ContentTypeHeaderKey = @"content-type";
static BOOL debugHeaderDictionary = NO;

+ (void)initialize;
{
    static BOOL initialized = NO;

    [super initialize];
    if (initialized)
	return;
    initialized = YES;

    ColonSet = [[NSCharacterSet characterSetWithCharactersInString:@":"]
		retain];

    // These are from the MIME standard, RFC 1521
    // http://www.oac.uci.edu/indiv/ehood/MIME/1521/04_Content-Type.html

    TSpecialsSet = [NSCharacterSet characterSetWithCharactersInString:@"()<>@,;:\\\"/[]?="];

    // This is a bit richer than the standard: I'm including non-ASCII.
    TokenSet = [[TSpecialsSet invertedSet] mutableCopy];
    [TokenSet removeCharactersInString:@" "];
    [TokenSet formIntersectionWithCharacterSet:[[NSCharacterSet controlCharacterSet] invertedSet]];
    [TokenSet addCharactersInString:@"/"];

    QuotedStringSet = [[[NSCharacterSet characterSetWithCharactersInString:@"\"\n"] invertedSet] retain];
}

+ (void)setDebug:(BOOL)debugMode;
{
    debugHeaderDictionary = debugMode;
}

- init;
{
    if (![super init])
	return nil;

    headerDictionary = [[OFMultiValueDictionary alloc] init];
    contentTypeLock = [[NSLock alloc] init];
    contentType = nil;
    contentTypeParameters = nil;

    return self;
}

- (void)dealloc;
{
    [headerDictionary release];
    [contentTypeLock release];
    [contentTypeParameters release];
    [super dealloc];
}

- (NSArray *)stringArrayForKey:(NSString *)aKey;
{
    return [headerDictionary arrayForKey:aKey];
}

- (NSString *)firstStringForKey:(NSString *)aKey;
{
    return [headerDictionary firstObjectForKey:aKey];
}

- (NSString *)lastStringForKey:(NSString *)aKey;
{
    return [headerDictionary lastObjectForKey:aKey];
}

- (void)addString:(NSString *)aString forKey:(NSString *)aKey;
{
    aKey = [aKey lowercaseString];
    if (contentType && [aKey isEqualToString:ContentTypeHeaderKey]) {
        [contentTypeLock lock];
        contentType = nil;
        [contentTypeLock unlock];
    }
    [headerDictionary addObject:aString forKey:aKey];
}

- (void)parseRFC822Header:(NSString *)aHeader;
{
    NSRange colonRange;
    NSString *key, *value;

    colonRange = [aHeader rangeOfCharacterFromSet:ColonSet];
    if (colonRange.length == 0)
	return;

    key = [aHeader substringToIndex:colonRange.location];
    value = [[aHeader substringFromIndex:NSMaxRange(colonRange)] stringByRemovingSurroundingWhitespace];
    [self addString:value forKey:key];
}

- (void)readRFC822HeadersFrom:(id)readLineSource;
{
    NSString *header = nil;

    do {
	NSString *newLine;

	newLine = [readLineSource readLine];
        if ([newLine isEqualToString:@"."])
            break;
	if (debugHeaderDictionary)
	    NSLog(@"%@", newLine);
	if ([newLine hasLeadingWhitespace])
	    header = [header stringByAppendingString:newLine];
	else {
	    if (header)
		[self parseRFC822Header:header];
	    header = newLine;
	}
    } while (header && [header length] > 0);	
}

- (void)readRFC822HeadersFromDataCursor:(OFDataCursor *)aCursor;
{
    [self readRFC822HeadersFrom:aCursor];
}

- (void)readRFC822HeadersFromCursor:(OWDataStreamCursor *)aCursor;
{
    [self readRFC822HeadersFrom:aCursor];
}

- (void)readRFC822HeadersFromSocketStream:(ONSocketStream *)aSocketStream;
{
    [self readRFC822HeadersFrom:aSocketStream];
}

- (OWContentType *)contentType;
{
    if (!contentType)
        [self parseContentType];
    return contentType;
}

- (OFMultiValueDictionary *)contentTypeParameters;
{
    if (!contentType)
	[self parseContentType];
    return contentTypeParameters;
}

- (OWContentType *)contentEncoding;
{
    NSString *headerString;
    OWContentType *contentEncoding;

    headerString = [self lastStringForKey:@"content-encoding"];
    if (!headerString || [headerString length] == 0)
	return nil;
    contentEncoding = [OWContentType contentTypeForString:[@"encoding/" stringByAppendingString:headerString]];
    return contentEncoding;
}

// Debugging

- (NSMutableDictionary *)debugDictionary;
{
    NSMutableDictionary *debugDictionary;

    debugDictionary = [super debugDictionary];

    if (headerDictionary)
	[debugDictionary setObject:headerDictionary forKey:@"headerDictionary"];

    return debugDictionary;
}

+ (NSString *)parseParameterizedHeader:(NSString *)aString intoDictionary:(OFMultiValueDictionary *)parameters valueChars:(NSCharacterSet *)validValues;
{
    NSScanner *scanner;
    NSCharacterSet *whitespaceAndNewlineCharacterSet;
    NSString *bareHeader;

    if (![aString containsString:@";"]) {
        return aString;
    }

    scanner = [NSScanner scannerWithString:aString];
    if (![scanner scanCharactersFromSet:validValues intoString:&bareHeader]) {
        return aString;
    }

    whitespaceAndNewlineCharacterSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];

    while ([scanner scanString:@";" intoString:NULL]) {
        NSString *attribute, *value;

        if (![scanner scanCharactersFromSet:TokenSet intoString:&attribute])
            break;
        if (![scanner scanString:@"=" intoString:NULL])
            break;

        if ([scanner scanString:@"\"" intoString:NULL]) {
            [scanner setCharactersToBeSkipped:nil];
            if (![scanner scanCharactersFromSet:QuotedStringSet intoString:&value])
                value = @"";
            while ([scanner scanString:@"\\" intoString:NULL]) {
                NSString *continuedValue;

                if ([scanner scanString:@"\"" intoString:NULL]) {
                    value = [value stringByAppendingString:@"\""];
                } else if ([scanner scanString:@"\\" intoString:NULL]) {
                    value = [value stringByAppendingString:@"\\"];
                } else if ([scanner scanString:@"\n" intoString:NULL]) {
                    value = [value stringByAppendingString:@"\n"];
                }
                if ([scanner scanCharactersFromSet:QuotedStringSet intoString:&continuedValue]) {
                    value = [value stringByAppendingString:continuedValue];
                }
            }
            if (![scanner scanString:@"\"" intoString:NULL])
                break;
            [scanner setCharactersToBeSkipped:whitespaceAndNewlineCharacterSet];
        } else {
            if (![scanner scanCharactersFromSet:validValues intoString:&value])
                break;
        }
        [parameters addObject:value forKey:[attribute lowercaseString]];
    }

    return bareHeader;
}

@end

@implementation OWHeaderDictionary (Private)

- (void)parseContentType;
{
    if (contentType)
        return;

    [contentTypeLock lock];
    if (!contentType) {
        NSString *headerString;

        headerString = [self lastStringForKey:ContentTypeHeaderKey];
        if (!headerString) {
            // No content type
            contentType = [OWUnknownDataStreamProcessor unknownContentType];
        } else if (![headerString containsString:@";"]) {
            // Simple content type
            contentType = [OWContentType contentTypeForString:headerString];
        } else {
            NSString *contentTypeString;

            // Content type with parameters
            if (!contentTypeParameters)
                contentTypeParameters = [[OFMultiValueDictionary alloc] init];
            contentTypeString = [isa parseParameterizedHeader:headerString intoDictionary:contentTypeParameters valueChars:TokenSet];
            contentType = [OWContentType contentTypeForString:contentTypeString];
        }
    }
    [contentTypeLock unlock];
}

@end
