/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: CHXClientContext.cpp,v 1.16.2.4 2004/07/09 01:49:47 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

#include "CHXClientContext.h"
#include "CHXClientBuffer.h"
#include "CHXClientDebug.h"
#include "IHXClientPlayer.h"

#include "enter_hx_headers.h"
#include "hxwintyp.h"
#include "hxwin.h"   // IID_IHXSiteSupplier
#include "hxerror.h" // IID_IHXErrorSink
#include "hxfiles.h" // IHXRequest
#include "ihxpckts.h"
#include "hxcore.h"

#include "hxsmartptr.h"
HX_SMART_POINTER_INLINE( SPIHXBuffer, IHXBuffer );
HX_SMART_POINTER_INLINE( SPIHXStreamSource, IHXStreamSource );
HX_SMART_POINTER_INLINE( SPIHXPlayer, IHXPlayer );
HX_SMART_POINTER_INLINE( SPIHXPlayer2, IHXPlayer2 );
HX_SMART_POINTER_INLINE( SPIHXRequest, IHXRequest );
#include "exit_hx_headers.h"

#include "hlxclib/string.h"
#include "hlxclib/ctype.h"

#define SERVER_AUTHENTICATE_HEADER "WWW-Authenticate"
#define PROXY_AUTHENTICATE_HEADER  "Proxy-Authenticate"
#define SERVER_HEADER "_server"
#define REALM_HEADER  "realm"

CHXClientContext::~CHXClientContext( void )
{
}

CHXClientContext::CHXClientContext( IHXClientPlayer* pClientPlayer, IUnknown* pIUnkSiteSupplier, IUnknown* pIUnkErrorSink, void* userInfo, const HXClientCallbacks* pClientCallbacks )
	: m_UserInfo( userInfo )
	, m_pClientPlayer( pClientPlayer )
	, m_pIUnkSiteSupplier( pIUnkSiteSupplier )
	, m_pIUnkErrorSink( pIUnkErrorSink )
	, m_pClientCallbacks( pClientCallbacks )
	, m_hasRequestedUpgrade( false )
{
	CHXASSERT( m_pClientPlayer ); // Need a valid client player.
}

// XXXSEH: Fill out QI.
BEGIN_INTERFACE_LIST_NOCREATE( CHXClientContext )
    INTERFACE_LIST_ENTRY_MEMBER( IID_IHXSiteSupplier, m_pIUnkSiteSupplier )
    INTERFACE_LIST_ENTRY_MEMBER( IID_IHXErrorSink, m_pIUnkErrorSink )
    INTERFACE_LIST_ENTRY_SIMPLE( IHXHyperNavigate )
    INTERFACE_LIST_ENTRY_SIMPLE( IHXHyperNavigate2 )
    INTERFACE_LIST_ENTRY_SIMPLE( IHXHyperNavigateWithContext )
    //INTERFACE_LIST_ENTRY_SIMPLE( IHXClientRequestSink )
    INTERFACE_LIST_ENTRY_SIMPLE( IHXUpgradeHandler )
    INTERFACE_LIST_ENTRY_SIMPLE( IHXStatusMessage )
    INTERFACE_LIST_ENTRY_SIMPLE( IHXAuthenticationManager )
    INTERFACE_LIST_ENTRY_SIMPLE( IHXAuthenticationManager2 )
END_INTERFACE_LIST

STDMETHODIMP
CHXClientContext::GoToURL( const char* pURL,
						   const char* pTarget )
{
	return Execute( pURL, pTarget, NULL, NULL, NULL ); // XXXSEH: Does pTarget map to pTargetInstance?
}

static char*
CopyStringWithinBoundsTrimmingWhitespace( const char* pStringStart, const char* pStringEnd )
{
	const char* pStringToCopy = pStringStart;
	while ( ( pStringToCopy < pStringEnd ) && isspace( *pStringToCopy ) )
	{
		pStringToCopy++;
	}
	if ( pStringToCopy >= pStringEnd ) return NULL;
	
	const char* pStringToCopyEnd = pStringEnd;
	while ( ( pStringToCopyEnd > pStringToCopy ) && isspace( pStringToCopyEnd[ -1 ] ) )
	{
		pStringToCopyEnd--;
	}
	if ( pStringToCopy >= pStringToCopyEnd ) return NULL;
	
	unsigned long copyStringLength = pStringToCopyEnd - pStringToCopy;
	char* pCopiedString = new char[ copyStringLength + 1 ];
	if ( !pCopiedString ) return NULL;
	
	memcpy( pCopiedString, pStringToCopy, copyStringLength );
	pCopiedString[ copyStringLength ] = '\0';
	
	return pCopiedString;
}

bool
CHXClientContext::HandleURLCommand( const char* pCommand, const char* pTargetInstance )
{
	bool outResult = false; // We assume these are Helix commands.
	
	const char* beginArgumentsDelimiter = strstr( pCommand, "(" );
	if ( beginArgumentsDelimiter )
	{
		unsigned long sizeOfCommand = beginArgumentsDelimiter - pCommand;
		
		// See http://service.real.com/help/library/guides/production8/realpgd.htm Topic 7 Extending SMIL.
		const char* const kHURLCommand = "openwindow";
		if ( ( sizeOfCommand >= strlen( kHURLCommand ) ) &&
			 ( 0 == strncasecmp( pCommand, kHURLCommand, strlen( kHURLCommand ) ) ) )
		{
			const char* pArguments = beginArgumentsDelimiter + 1;
			const char* endArgumentsDelimiter = strstr( pArguments, ")" );
			CHXASSERT( endArgumentsDelimiter );
			if ( endArgumentsDelimiter )
			{
				const char* firstArgumentDelimiter  = strstr( pArguments, "," );
				const char* secondArgumentDelimiter = ( NULL != firstArgumentDelimiter ) ? strstr( ( firstArgumentDelimiter + 1 ), "," ) : NULL;
			
				char* pOpenWindowName = ( NULL != firstArgumentDelimiter ) ? CopyStringWithinBoundsTrimmingWhitespace( pArguments, firstArgumentDelimiter ) : NULL;
				char* pOpenWindowURL = CopyStringWithinBoundsTrimmingWhitespace( ( ( NULL != firstArgumentDelimiter  ) ? ( firstArgumentDelimiter + 1 ) : pArguments ),
																				 ( ( NULL != secondArgumentDelimiter ) ? secondArgumentDelimiter : endArgumentsDelimiter ) );
				char* pPlayModeNameValuePair = ( NULL != secondArgumentDelimiter ) ? CopyStringWithinBoundsTrimmingWhitespace( ( secondArgumentDelimiter + 1 ), endArgumentsDelimiter ) : NULL;

				if ( pOpenWindowName &&
					 ( ( 0 == strcasecmp( pOpenWindowName, "_self" ) ) ||
					   ( 0 == strcasecmp( pOpenWindowName, "_current" ) ) ) )
				{
					m_pClientPlayer->OpenURL( pOpenWindowURL, NULL );
					m_pClientPlayer->Play();
					outResult = true;
				}
				else
				{
					bool isPlayerURL = pTargetInstance && ( 0 == strcasecmp( pTargetInstance, "_player" ) );
					
					// XXXSEH: Should we filter out _new and _blank in pOpenWindowName and just send through NULL instead?
					outResult = m_pClientCallbacks->GoToURL ? m_pClientCallbacks->GoToURL( m_UserInfo, pOpenWindowURL, pOpenWindowName, isPlayerURL ) : false;
				}
				delete [] pPlayModeNameValuePair; // XXXSEH: This isn't currently supported. I'm hopeful we can handle these within hxclientkit.
				delete [] pOpenWindowURL;
				delete [] pOpenWindowName;
			}
		}
	}
	return outResult;
}

bool
CHXClientContext::CallGoToURLCallback( const char* pURL, const char* pTargetInstance )
{
	if ( pURL && *pURL )
	{		
		const char* protocolDelimiter = strstr( pURL, ":" );
		if ( protocolDelimiter )
		{
			// Helix needs to handle these directly.
			const char* const kSupportedProtocols[] = { "rtsp", "pnm", NULL };
			
			unsigned long sizeOfProtocol = protocolDelimiter - pURL;
			int index = 0;
			while ( kSupportedProtocols[ index ] )
			{
				if ( ( sizeOfProtocol == strlen( kSupportedProtocols[ index ] ) ) &&
					 ( 0 == strncmp( pURL, kSupportedProtocols[ index ], sizeOfProtocol ) ) )
				{
					return false;
				}
				++index;
			}
			// Check for command URL's.
			const char* const kCommandURL = "command";
			if ( ( sizeOfProtocol == strlen( kCommandURL ) ) &&
				 ( 0 == strncmp( pURL, kCommandURL, sizeOfProtocol ) ) )
			{
				const char* pCommand = pURL + strlen( kCommandURL ) + 1;
				return HandleURLCommand( pCommand, pTargetInstance );
			}
		}
		// Content intended for Helix.
		if ( pTargetInstance && ( 0 == strcasecmp( pTargetInstance, "_player" ) ) ) return false;

		return m_pClientCallbacks->GoToURL ? m_pClientCallbacks->GoToURL( m_UserInfo, pURL, pTargetInstance, false ) : false;
	}
	return false;
}

STDMETHODIMP
CHXClientContext::Execute( const char* pURL,
						   const char* pTargetInstance,
						   const char* pTargetApplication,
						   const char* pTargetRegion,
						   IHXValues*  pParams )
{
	const char* const kContextPaneTarget = "_rpcontextwin";
	SPIHXBuffer spBuffer;
	if ( ( pTargetInstance && *pTargetInstance &&
		   ( 0 == strcasecmp( kContextPaneTarget, pTargetInstance ) ) ) ||
		 ( pParams && SUCCEEDED( pParams->GetPropertyCString( "sendTo", *spBuffer.AsInOutParam() ) ) &&
		   ( 0 == strcasecmp( kContextPaneTarget, ( const char* ) spBuffer->GetBuffer() ) ) ) )
	{
		// ?rptarget=<kContextPaneTarget>&rpcontextwidth=#####&rpcontextheight=#####
		const INT32 kQueryStringMaxLength = 1 + ( 8 + 1 + strlen( kContextPaneTarget ) ) + 1 + ( 14 + 1 + 5 ) + 1 + ( 15 + 1 + 5 );
		UINT32 origURLLength = strlen( pURL );
		char* pURLWithQuery = new char[ origURLLength + kQueryStringMaxLength + 1 ];
		if ( pURLWithQuery )
		{
			strcpy( pURLWithQuery, pURL );
			INT32 copyIndex = origURLLength;
			char concatChar = ( !strstr( pURLWithQuery, "?" ) ) ? '?' : '&';
			sprintf( &pURLWithQuery[ copyIndex ], "%c%s%s", concatChar, "rptarget=", kContextPaneTarget );
			copyIndex += strlen( &pURLWithQuery[ copyIndex ] );
			if ( pParams )
			{
				if ( SUCCEEDED( pParams->GetPropertyCString( "width", *spBuffer.AsInOutParam() ) ) )
				{
					sprintf( &pURLWithQuery[ copyIndex ], "&%s%s", "rpcontextwidth=", ( const char* ) spBuffer->GetBuffer() );
					copyIndex += strlen( &pURLWithQuery[ copyIndex ] );
				}
				if ( SUCCEEDED( pParams->GetPropertyCString( "height", *spBuffer.AsInOutParam() ) ) )
				{
					sprintf( &pURLWithQuery[ copyIndex ], "&%s%s", "rpcontextheight=", ( const char* ) spBuffer->GetBuffer() );
					copyIndex += strlen( &pURLWithQuery[ copyIndex ] );
				}
			}
			pURLWithQuery[ copyIndex ] = '\0';
			bool handled = CallGoToURLCallback( pURLWithQuery, NULL );
			delete [] pURLWithQuery;
			return handled ? HXR_OK : HXR_NOTIMPL;
		}
	}
	bool handled = CallGoToURLCallback( pURL, pTargetInstance );
	return handled ? HXR_OK : HXR_NOTIMPL;
}

STDMETHODIMP
CHXClientContext::ExecuteWithContext( const char* pURL,
									  const char* pTargetInstance,
									  const char* pTargetApplication,
									  const char* pTargetRegion,
									  IHXValues*  pParams,
									  IUnknown*   pContext )
{
	return Execute( pURL, pTargetInstance, pTargetApplication, pTargetRegion, pParams );
}

/* STDMETHODIMP
CHXClientContext::OnNewRequest( IHXRequest* pNewRequest )
{
	if ( !pNewRequest ) return HXR_INVALID_PARAMETER;
	
	const char* pURL = NULL;
	pNewRequest->GetURL( pURL );
	
	return HXR_OK;
} */

STDMETHODIMP
CHXClientContext::RequestUpgrade( IHXUpgradeCollection* pComponents, HXBOOL bBlocking )
{
	m_hasRequestedUpgrade = true;
	
	if ( !pComponents ) return HXR_INVALID_PARAMETER;
	if ( !m_pClientCallbacks->RequestUpgrade ) return HXR_FAIL;

	UINT32 numOfComponents = pComponents->GetCount();
	if ( numOfComponents <= 0 ) return HXR_INVALID_PARAMETER;
	
	char** componentNames = new char*[ numOfComponents ];
	if ( !componentNames ) return HXR_OUTOFMEMORY;
	
	HX_RESULT outResult = HXR_CANCELLED;
	
	char* pURL = NULL;
	SPIHXPlayer spIPlayer;
	m_pClientPlayer->GetHXPlayer( spIPlayer.AsInOutParam() );
	UINT16 sourceCount = spIPlayer.IsValid() ? spIPlayer->GetSourceCount() : 0;
	if ( sourceCount > 0 )
	{
		// Get the last source's URL. This avoids getting the .ram URL when the first URL in the .ram triggers the Upgrade request.
		SPIUnknown spUnkSource;
		spIPlayer->GetSource( ( sourceCount - 1 ), *spUnkSource.AsInOutParam() );
		SPIHXStreamSource spStreamSource = spUnkSource.Ptr();
		if ( spStreamSource.IsValid() )
		{
			const char* pURLToCopy = spStreamSource->GetURL();
			
			// Does this URL have a valid protocol? SDP files, for example, return "helix-sdp:<file contents>" in GetURL().
			const char* pProtocolDelims = NULL;
			if ( ( NULL != ( pProtocolDelims = strstr( pURLToCopy, "://" ) ) ) &&
				 ( ( pProtocolDelims - pURLToCopy ) <= 5 ) ) // Maximum protocol length based on "https://"
			{
				pURL = new char[ strlen( pURLToCopy ) + 1 ];
				if ( pURL )
				{
					strcpy( pURL, pURLToCopy );
				}
			}
		}
	}
	if ( !pURL )
	{
		SPIHXPlayer2 spIPlayer2 = spIPlayer.Ptr();
		if ( spIPlayer2.IsValid() )
		{
			SPIHXRequest spIRequest;
			if ( SUCCEEDED( spIPlayer2->GetRequest( *spIRequest.AsInOutParam() ) ) && spIRequest.IsValid() )
			{
				const char* pURLToCopy = NULL;
				if ( SUCCEEDED( spIRequest->GetURL( pURLToCopy ) ) && pURLToCopy && *pURLToCopy )
				{
					pURL = new char[ strlen( pURLToCopy ) + 1 ];
					if ( pURL )
					{
						strcpy( pURL, pURLToCopy );
					}
				}
			}
		}
	}
	// XXXSEH: Do we need to add in StripOffPrefixes() support as found in upgrdlib/cupgradelogicshell.cpp?
	HXUpgradeType upgradeType;
	UINT32 majorVersion, minorVersion;
	UINT32 index;
	UINT32 numOfComponentsToUpgrade = 0;	
	for ( index = 0; index < numOfComponents; ++index )
	{
		SPIHXBuffer spPluginId = new CHXClientBuffer;
		pComponents->GetAt( index, upgradeType, spPluginId.Ptr(), majorVersion, minorVersion );
		UINT32 sizeOfComponentName = spPluginId->GetSize();
		if ( ( sizeOfComponentName > 0 ) && ( NULL != spPluginId->GetBuffer() ) )
		{
			componentNames[ index ] = new char[ sizeOfComponentName ];
			if ( !componentNames[ index ] )
			{
				// outResult = HXR_OUTOFMEMORY; XXXSEH: Don't know that we need to pass this info. on?
				goto bail;
			}
			memcpy( componentNames[ index ], spPluginId->GetBuffer(), sizeOfComponentName );
			++numOfComponentsToUpgrade;
		}
	}
	if ( ( numOfComponentsToUpgrade > 0 ) &&
		 m_pClientCallbacks->RequestUpgrade( m_UserInfo, pURL, numOfComponentsToUpgrade, ( const char** ) componentNames, ( ( 0 != bBlocking ) ? true : false ) ) )
	{
		outResult = HXR_OK;
	}
	
bail:

	delete [] pURL;
	
	for ( index = 0; index < numOfComponentsToUpgrade; ++index )
	{
		delete [] componentNames[ index ];
	}
	delete [] componentNames;
	
	return outResult;
}

STDMETHODIMP
CHXClientContext::HasComponents( IHXUpgradeCollection* pComponents )
{
	if ( !pComponents ) return HXR_INVALID_PARAMETER;
	if ( !m_pClientCallbacks->HasComponent ) return HXR_FAIL;

	UINT32 numOfComponents = pComponents->GetCount();
	if ( numOfComponents <= 0 ) return HXR_INVALID_PARAMETER;
	
	HXUpgradeType upgradeType;
	UINT32 majorVersion, minorVersion;
	UINT32 index = numOfComponents;
	do
	{
		--index;
		
		SPIHXBuffer spPluginId = new CHXClientBuffer;
		pComponents->GetAt( index, upgradeType, spPluginId.Ptr(), majorVersion, minorVersion );
		if ( ( NULL == spPluginId->GetBuffer() ) ||
			 m_pClientCallbacks->HasComponent( m_UserInfo, ( const char* ) spPluginId->GetBuffer() ) )
		{
			pComponents->Remove( index );
		}
	}
	while( index > 0 );
	
	numOfComponents = pComponents->GetCount();
	return ( numOfComponents > 0 ) ? HXR_FAIL : HXR_OK;
}

STDMETHODIMP
CHXClientContext::SetStatus( const char* pText )
{
	if ( m_pClientCallbacks->OnStatusChanged )
	{
		m_pClientCallbacks->OnStatusChanged( m_UserInfo, pText );
	}
	return HXR_OK; // What does the return value mean?
}

STDMETHODIMP
CHXClientContext::HandleAuthenticationRequest( IHXAuthenticationManagerResponse* pResponse )
{
	return HandleAuthenticationRequest2( pResponse, NULL );
}

STDMETHODIMP
CHXClientContext::HandleAuthenticationRequest2( IHXAuthenticationManagerResponse* pResponse, IHXValues* pHeader )
{
	if ( !pResponse ) return HXR_INVALID_PARAMETER;
	if ( !m_pClientCallbacks->RequestAuthentication ) return pResponse->AuthenticationRequestDone( HXR_NOT_AUTHORIZED, NULL, NULL );
	
	bool isProxyServer = false;
	SPIHXBuffer spServer, spRealm;
	if ( pHeader )
	{
		SPIHXBuffer spBuffer;
		isProxyServer = ( FAILED( pHeader->GetPropertyCString( SERVER_AUTHENTICATE_HEADER, *spBuffer.AsInOutParam() ) ) &&
						  SUCCEEDED( pHeader->GetPropertyCString( PROXY_AUTHENTICATE_HEADER, *spBuffer.AsInOutParam() ) ) );
		
		( void ) pHeader->GetPropertyCString( SERVER_HEADER, *spServer.AsInOutParam() );
		( void ) pHeader->GetPropertyCString( REALM_HEADER, *spRealm.AsInOutParam() );
	}
	const char* pServer = spServer.IsValid() ? ( char* ) spServer->GetBuffer() : NULL;
	const char* pRealm  = spRealm.IsValid()  ? ( char* ) spRealm->GetBuffer()  : NULL;
	
	m_spPendingAuthManagerResponse = pResponse;
	if ( m_pClientCallbacks->RequestAuthentication( m_UserInfo, pServer, pRealm, isProxyServer ) )
	{
		return HXR_OK;
	}
	else
	{
		m_spPendingAuthManagerResponse.Clear();
		return pResponse->AuthenticationRequestDone( HXR_NOT_AUTHORIZED, NULL, NULL );
	}
}

HX_RESULT
CHXClientContext::Authenticate( bool shouldValidateUser, const char* pUsername, const char* pPassword )
{
	if ( !m_spPendingAuthManagerResponse.IsValid() ) return HXR_UNEXPECTED;
	
	SPIHXAuthenticationManagerResponse spResponse = m_spPendingAuthManagerResponse.Ptr();
	m_spPendingAuthManagerResponse.Clear();
	
	return shouldValidateUser ?
		   spResponse->AuthenticationRequestDone( HXR_OK, pUsername, pPassword ) :
		   spResponse->AuthenticationRequestDone( HXR_NOT_AUTHORIZED, NULL, NULL );
}
