
/*
 *
 * @APPLE_LICENSE_HEADER_START@
 *
 * Copyright (c) 1999-2001 Apple Computer, Inc.  All Rights Reserved. The
 * contents of this file constitute Original Code as defined in and are
 * subject to the Apple Public Source License Version 1.2 (the 'License').
 * You may not use this file except in compliance with the License.  Please
 * obtain a copy of the License at http://www.apple.com/publicsource and
 * read it before using this file.
 *
 * This Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.  Please
 * see the License for the specific language governing rights and
 * limitations under the License.
 *
 *
 * @APPLE_LICENSE_HEADER_END@
 *
 */

#include "QueryParamList.h"

#include "StringParser.h"
#include "OSMemory.h"

#include <string.h>
#include <stdlib.h>

QueryParamList::QueryParamList( StrPtrLen* querySPL )
{
	// ctor from StrPtrLen
	fNameValueQueryParamlist = NEW PLDoubleLinkedList<QueryParamListElement>;

	this->BulidList( querySPL );	
}


QueryParamList::QueryParamList( char* queryString )
{
	// ctor from char*
	StrPtrLen		querySPL( queryString );

	fNameValueQueryParamlist = NEW PLDoubleLinkedList<QueryParamListElement>;
		
	this->BulidList( &querySPL );
}


void QueryParamList::BulidList( StrPtrLen* querySPL )
{
	// parse the string and build the name/value list from the tokens.
	// the string is a 'form' encoded query string ( see rfc - 1808 )
	
	StringParser	queryParser( querySPL );
	
	while  ( queryParser.GetDataRemaining() > 0 )
	{
		StrPtrLen		theCGIParamName;
		StrPtrLen		theCGIParamValue;
		
		queryParser.ConsumeUntil(&theCGIParamName, '=');		// leaves "=..." in cgiParser, puts item keywd in theCGIParamName
		
		if ( queryParser.GetDataRemaining() > 1  )
		{
			queryParser.ConsumeLength(&theCGIParamValue, 1 );	// the '='
		
			queryParser.ConsumeUntil(&theCGIParamValue, '&');	// our value will end by here...
			
			AddNameValuePairToList( theCGIParamName.GetAsCString(), theCGIParamValue.GetAsCString() );
			
			queryParser.ConsumeLength(&theCGIParamValue, 1 );	// the '='
			
		}
	}
}


static void  PrintNameAndValue( PLDoubleLinkedListNode<QueryParamListElement> *node,  void *userData )
{
	// used by QueryParamList::PrintAll
	QueryParamListElement*	nvPair = node->fElement;
	
	printf( "qpl: %s, name %s, val %s\n", (char*)userData, nvPair->mName, nvPair->mValue );
}


void QueryParamList::PrintAll( char *idString )
{
	// print name and value of each item in the list, print each pair preceded by "idString"
	fNameValueQueryParamlist->ForEach( PrintNameAndValue, idString );
}


static bool  CompareStrToName( PLDoubleLinkedListNode<QueryParamListElement> *node,  void *userData )
{
	/*
		make a case insenstitive comparison between "node" name and the userData
		
		used by QueryParamList::DoFindCGIValueForParam
	*/
	
	QueryParamListElement*	nvPair = node->fElement;
	StrPtrLen				name( nvPair->mName );
	
	if ( name.EqualIgnoreCase( (char*)userData, strlen( (char*)userData ) ) == true )
		return true;
	
	return false;
}


const char *QueryParamList::DoFindCGIValueForParam( char *name )
{
	/*
		return the first value where the paramter name matches "name"
		use case insenstitive comparison
	
	*/
	PLDoubleLinkedListNode<QueryParamListElement>*	node;

	node = fNameValueQueryParamlist->ForEachUntil( CompareStrToName, name );
	
	if ( node != NULL )
	{	
		QueryParamListElement*	nvPair = (QueryParamListElement*)node->fElement;
		
		return	nvPair->mValue;
	}
	
	return NULL;
	
}
	
	
void QueryParamList::AddNameValuePairToList( char* name, char* value  )
{
	// add the name/value pair to the by creating the holder struct
	// then adding that as the element in the linked list
	
	PLDoubleLinkedListNode<QueryParamListElement>*		nvNode;
	QueryParamListElement*		nvPair;
	
	this->DecodeArg( name );
	this->DecodeArg( value );
	
	nvPair = NEW  QueryParamListElement( name, value );
	
	
	// create a node to hold the pair
	nvNode = NEW PLDoubleLinkedListNode<QueryParamListElement> ( nvPair );
	
	// add it to the list
	fNameValueQueryParamlist->AddNode( nvNode );
}



void QueryParamList::DecodeArg( char *ioCodedPtr )
{
	// perform In-Place  &hex and + to space decoding of the parameter
	//  on input, ioCodedPtr mau contain encoded text, on exit ioCodedPtr will be plain text
	// on % decoding errors, the 
	
	if ( !ioCodedPtr ) 
		return;

	char*	destPtr;
	char*	curChar;
	short	lineState = kLastWasText;
	char	hexBuff[32];

	destPtr = curChar = ioCodedPtr;
	
	while( *curChar )
	{
		switch( lineState )
		{
			case kRcvHexDigitOne:
				if ( IsHex( *curChar ) )
				{	
					hexBuff[3] = *curChar;
					hexBuff[4] = 0;
					
					*destPtr++ = (char)::strtoul( hexBuff, NULL, 0 );
				}
				else
				{	// not a valid encoding
					*destPtr++ = '%';			// put back the pct sign
					*destPtr++ = hexBuff[2];	// put back the first digit too.
					*destPtr++ = *curChar;		// and this one!
					
				}
				lineState = kLastWasText;
				break;

			case kLastWasText:
				if ( *curChar == '%' )
					lineState = kPctEscape;
				else
				{
					if (  *curChar == '+' )
						*destPtr++ = ' ';
					else
						*destPtr++ = *curChar;
				}
				break;

			case kPctEscape:
				if ( *curChar == '%' )
				{
					*destPtr++ = '%';
					lineState = kLastWasText;
				}
				else
				{
					if ( IsHex( *curChar ) )
					{	
						hexBuff[0] = '0';
						hexBuff[1] = 'x';
						hexBuff[2] = *curChar;
						lineState = kRcvHexDigitOne;
					}
					else
					{	
						*destPtr++ = '%';		// put back the pct sign
						*destPtr++ = *curChar;	// and this one!
						lineState = kLastWasText;
					}
				}
				break;
				
					
		}	
		
		curChar++;
	}

	*destPtr = *curChar;
}

Bool16 QueryParamList::IsHex( char c )
{
	// return true if char c is a valid hexidecimal digit
	// false otherwise.
	
	if ( c >= '0' && c <= '9' )
		return true;

	if ( c >= 'A' && c <= 'F' )
		return true;

	if ( c >= 'a' && c <= 'f' )
		return true;

	return false;
}


