2025-11-28 00:35:46 +09:00

667 lines
19 KiB
C++

//*****************************************************************************
//
// Microsoft Windows Media
// Copyright (C) Microsoft Corporation. All rights reserved.
//
// FileName: ProxyPluginImpl.cpp
//
// Abstract:
//
//*****************************************************************************
#include "stdafx.h"
#include <limits.h>
#include "WMSBasicPlugin.h"
#include "nserror.h"
#include "WMSContextNames.h"
#include "ProxyPlugin.h"
#include "ProxyPluginImpl.h"
// Since the plugin supports protocol rollver, we have a hard-coded list of the
// protocols which are supported. The last entry signifies the end of the array.
LPWSTR g_ProxyProtocols[] = { L"HTTP", L"RTSP", NULL };
/////////////////////////////////////////////////////////////////////////////
//
// [CProxyPlugin]
//
/////////////////////////////////////////////////////////////////////////////
CProxyPlugin::CProxyPlugin()
{
m_pICacheProxyServer = NULL;
}
/////////////////////////////////////////////////////////////////////////////
//
// [~CProxyPlugin]
//
/////////////////////////////////////////////////////////////////////////////
CProxyPlugin::~CProxyPlugin()
{
}
/////////////////////////////////////////////////////////////////////////////
//
// [InitializePlugin]
//
/////////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE
CProxyPlugin::InitializePlugin(
IWMSContext *pServerContext,
IWMSNamedValues *pNamedValues,
IWMSClassObject *pClassFactory
)
{
HRESULT hr = S_OK;
if( NULL == pNamedValues )
{
return ( E_INVALIDARG );
}
// A pointer to the IWMSCacheProxyServer is located in the server context
// passed into the plugin when it is initialized. The plugin will need to
// query for this value and store it.
hr = pServerContext->GetAndQueryIUnknownValue(
WMS_SERVER_CACHE_MANAGER,
WMS_SERVER_CACHE_MANAGER_ID,
IID_IWMSCacheProxyServer,
( IUnknown ** ) &m_pICacheProxyServer,
0 );
if( ( FAILED( hr ) ) || ( NULL == m_pICacheProxyServer ) )
{
hr = FAILED( hr ) ? hr : E_FAIL;
goto abort;
}
abort:
return( hr );
}
/////////////////////////////////////////////////////////////////////////////
//
// [EnablePlugin]
//
/////////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE
CProxyPlugin::EnablePlugin( long *pdwFlags, long *pdwHeartbeatPeriod )
{
if( ( NULL == pdwFlags ) || ( NULL == pdwHeartbeatPeriod ) )
{
return( E_INVALIDARG );
}
*pdwFlags = 0;
*pdwHeartbeatPeriod = 0;
return( S_OK );
}
/////////////////////////////////////////////////////////////////////////////
//
// [DisablePlugin]
//
/////////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE
CProxyPlugin::DisablePlugin()
{
return( S_OK );
}
/////////////////////////////////////////////////////////////////////////////
//
// [ShutdownPlugin]
//
/////////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE
CProxyPlugin::ShutdownPlugin()
{
if( NULL != m_pICacheProxyServer )
{
m_pICacheProxyServer->Release();
m_pICacheProxyServer = NULL;
}
return( S_OK );
}
/////////////////////////////////////////////////////////////////////////////
//
// [GetCustomAdminInterface]
//
/////////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE
CProxyPlugin::GetCustomAdminInterface( IDispatch **ppValue )
{
if( NULL == ppValue )
{
return( E_INVALIDARG );
}
*ppValue = NULL;
return( S_OK );
}
/////////////////////////////////////////////////////////////////////////////
//
// [OnHeartbeat]
//
/////////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE
CProxyPlugin::OnHeartbeat()
{
return( S_OK );
}
/////////////////////////////////////////////////////////////////////////////
//
// [QueryCache]
//
/////////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE
CProxyPlugin::QueryCache( BSTR bstrOriginUrl,
IWMSContext *pUserContext,
IWMSCommandContext *pCommandContext,
IWMSContext *pPresentationContext,
long QueryType,
IWMSCacheProxyCallback *pCallback,
VARIANT varContext
)
{
HRESULT hr = S_OK;
COpState *pOpState = NULL;
LPWSTR pszHostName = NULL;
if( ( NULL == bstrOriginUrl) ||
( NULL == pCommandContext ) ||
( NULL == pPresentationContext ) ||
( NULL == pCallback ) )
{
return( E_INVALIDARG );
}
// Since this plugin just supports proxy, we always return (in the callback)
// WMS_CACHE_QUERY_MISS. If this plugin were also to do caching, this is
// where the cache database would be queried. If a hit is made, then the
// expiration of the cache entry will be checked, and if it expired a
// freshness check would need to be done. A freshness check is done
// through CompareContentInformation, an async call. Rather than waiting
// for the response, the plugin should return from this method and
// call the callback (OnQueryCache) in OnCompareContentInformation.
//
// If OnCompareContentInformation reports the cached entry is up-to-date,
// a hit could be reported. Otherwise a miss will need to be reported,
// and the plugin will need to wait till QueryCacheMissPolicy.
hr = pCallback->OnQueryCache( hr,
WMS_CACHE_QUERY_MISS,
NULL,
NULL,
NULL,
varContext );
return( S_OK );
}
/////////////////////////////////////////////////////////////////////////////
//
// [QueryCacheMissPolicy]
//
/////////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE
CProxyPlugin::QueryCacheMissPolicy( BSTR bstrOriginUrl,
IWMSContext *pUserContext,
IWMSCommandContext *pCommandContext,
IWMSContext *pPresentationContext,
IUnknown *pCachePluginContext,
long QueryType,
IWMSCacheProxyCallback *pCallback,
VARIANT varContext
)
{
VARIANT varContext2;
COpState *pOpState = NULL;
HRESULT hr = S_OK;
BOOL fRet;
if( ( NULL == bstrOriginUrl) ||
( NULL == pCommandContext ) ||
( NULL == pPresentationContext ) ||
( NULL == pCallback ) )
{
return( E_INVALIDARG );
}
if( QueryType & WMS_CACHE_QUERY_REVERSE_PROXY )
{
hr = pCallback->OnQueryCacheMissPolicy( S_OK,
WMS_CACHE_QUERY_MISS_SKIP,
NULL,
NULL,
NULL,
varContext );
return( hr );
}
if( ( QueryType & WMS_CACHE_QUERY_CACHE_EVENT ) ||
( QueryType & WMS_CACHE_QUERY_GET_CONTENT_INFO ) )
{
hr = pCallback->OnQueryCacheMissPolicy( S_OK,
WMS_CACHE_QUERY_MISS_FORWARD_REQUEST,
NULL,
NULL,
NULL,
varContext );
return( hr );
}
pOpState = new COpState;
if( NULL == pOpState )
{
return( E_OUTOFMEMORY );
}
pOpState->m_Op = COpState::OP_QUERY_CACHE_MISS_POLICY;
pOpState->m_bstrOriginUrl = bstrOriginUrl;
pOpState->m_pPresentationContext = pPresentationContext;
pOpState->m_pCacheProxyCallback = pCallback;
pOpState->m_varContext = varContext;
pOpState->m_pPresentationContext->AddRef();
pOpState->m_pCacheProxyCallback->AddRef();
if( NULL == pOpState->m_bstrOriginUrl.m_str )
{
pOpState->Release();
return( E_OUTOFMEMORY );
}
fRet = ParseUrl( bstrOriginUrl,
&pOpState->m_bstrProtocol,
&pOpState->m_bstrHost,
&pOpState->m_bstrPath,
&pOpState->m_wPort );
if( !fRet )
{
pOpState->Release();
return( E_FAIL );
}
// Initiate protocol rollover by calling OnGetContentInformation with an
// appropriate HRESULT
VariantInit( &varContext2 );
V_VT( &varContext2 ) = VT_UNKNOWN;
V_UNKNOWN( &varContext2 ) = (IUnknown *) pOpState;
pOpState->m_dwProtocolIndex = -1;
OnGetContentInformation( NS_E_CONNECTION_FAILURE, NULL, varContext2 );
return( S_OK );
}
/////////////////////////////////////////////////////////////////////////////
//
// [OnGetContentInformation]
//
// IWMSCacheProxyServerCallback
//
/////////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE
CProxyPlugin::OnGetContentInformation(
long lHr,
IWMSContext *pContentInfo,
VARIANT varContext
)
{
HRESULT returnHr = (HRESULT) lHr;
HRESULT hr = S_OK;
COpState *pOpState = NULL;
WMS_CACHE_QUERY_MISS_RESPONSE CacheMissPolicy = WMS_CACHE_QUERY_MISS_SKIP;
IWMSDataContainerVersion *pContentVersion = NULL;
CComBSTR bstrUrl;
BOOL fDownload = FALSE;
long lContentType = 0;
DWORD dwCacheFlags;
WCHAR szPort[ 20 ];
if( ( VT_UNKNOWN != V_VT( &varContext ) ) ||
( NULL == V_UNKNOWN( &varContext ) ) )
{
return( E_INVALIDARG );
}
pOpState = ( COpState *) V_UNKNOWN( &varContext );
if( FAILED( returnHr ) )
{
if( NS_E_CONNECTION_FAILURE == returnHr )
{
// Do protocol rollover
pOpState->m_dwProtocolIndex++;
if( NULL == g_ProxyProtocols[ pOpState->m_dwProtocolIndex ] )
{
// we have tried all the protocols and failed
hr = E_NOINTERFACE;
goto abort;
}
bstrUrl = g_ProxyProtocols[ pOpState->m_dwProtocolIndex ];
bstrUrl.Append( L"://" );
bstrUrl.Append( pOpState->m_bstrHost );
// if we we are using the same protocol requested by the client, then we should
// also use the port specified by the client (if one was specified)
if( ( 0 != pOpState->m_wPort ) &&
( 0 == _wcsicmp( g_ProxyProtocols[ pOpState->m_dwProtocolIndex ], pOpState->m_bstrProtocol ) ) )
{
bstrUrl.Append( L":" );
_itow_s( pOpState->m_wPort, szPort,sizeof(szPort)/sizeof(WCHAR), 10 );
bstrUrl.Append( szPort );
}
bstrUrl.Append( L"/" );
bstrUrl.Append( pOpState->m_bstrPath );
hr = m_pICacheProxyServer->GetContentInformation(
bstrUrl,
pOpState->m_pPresentationContext,
this,
NULL,
(IWMSCacheProxyServerCallback *) this,
varContext
);
if( FAILED( hr ) )
{
goto abort;
}
return( S_OK );
}
else if( E_ACCESSDENIED == returnHr )
{
// the origin server requires authentication to provide information about this content.
// since we don't have the credentials, we can either proxy this stream on-demand (in
// which case the player will be prompted for the credentials) or simply fail this request.
// let's opt for proxying the stream.
CacheMissPolicy = WMS_CACHE_QUERY_MISS_PLAY_ON_DEMAND;
}
else
{
hr = returnHr;
goto abort;
}
}
else
{
hr = pContentInfo->GetLongValue( WMS_CACHE_CONTENT_INFORMATION_CONTENT_TYPE,
WMS_CACHE_CONTENT_INFORMATION_CONTENT_TYPE_ID,
(long *) &lContentType,
0 );
if( FAILED( hr ) )
{
goto abort;
}
if( WMS_CACHE_CONTENT_TYPE_BROADCAST & lContentType )
{
hr = pContentInfo->GetAndQueryIUnknownValue( WMS_CACHE_CONTENT_INFORMATION_DATA_CONTAINER_VERSION,
WMS_CACHE_CONTENT_INFORMATION_DATA_CONTAINER_VERSION_ID,
IID_IWMSDataContainerVersion,
(IUnknown **) &pContentVersion,
0 );
if( ( FAILED( hr ) ) || ( NULL == pContentVersion ) )
{
hr = FAILED( hr ) ? hr : E_UNEXPECTED;
goto abort;
}
hr = pContentVersion->GetCacheFlags( (long *) &dwCacheFlags );
if( FAILED( hr ) )
{
goto abort;
}
if( dwCacheFlags & WMS_DATA_CONTAINER_VERSION_ALLOW_STREAM_SPLITTING )
{
CacheMissPolicy = WMS_CACHE_QUERY_MISS_PLAY_BROADCAST;
}
else
{
CacheMissPolicy = WMS_CACHE_QUERY_MISS_PLAY_ON_DEMAND;
}
}
else // It is an on-demand publishing point
{
CacheMissPolicy = WMS_CACHE_QUERY_MISS_PLAY_ON_DEMAND;
}
}
hr = S_OK;
bstrUrl = g_ProxyProtocols[ pOpState->m_dwProtocolIndex ];
bstrUrl.Append( L"://" );
bstrUrl.Append( pOpState->m_bstrHost );
// if we we are using the same protocol requested by the client, then we should
// also use the port specified by the client (if one was specified)
if( ( 0 != pOpState->m_wPort ) &&
( 0 == _wcsicmp( g_ProxyProtocols[ pOpState->m_dwProtocolIndex ], pOpState->m_bstrProtocol ) ) )
{
bstrUrl.Append( L":" );
_itow_s( pOpState->m_wPort, szPort,sizeof(szPort)/sizeof(WCHAR), 10 );
bstrUrl.Append( szPort );
}
bstrUrl.Append( L"/" );
bstrUrl.Append( pOpState->m_bstrPath );
abort:
hr = pOpState->m_pCacheProxyCallback->OnQueryCacheMissPolicy( hr,
CacheMissPolicy,
bstrUrl,
NULL,
pContentInfo,
pOpState->m_varContext );
if( FAILED( hr ) )
{
hr = S_OK;
}
if( NULL != pOpState )
{
pOpState->Release();
}
if( NULL != pContentVersion )
{
pContentVersion->Release();
}
return( S_OK );
}
/////////////////////////////////////////////////////////////////////////////
//
//
//
/////////////////////////////////////////////////////////////////////////////
BOOL
CProxyPlugin::ParseUrl( BSTR bstrUrl,
BSTR *pbstrProtocol,
BSTR *pbstrHost,
BSTR *pbstrPath,
WORD *pwPort
)
{
LPWSTR pszSchemeless;
LPWSTR pszProtocol = L"";
LPWSTR pszHost = L"";
LPWSTR pszPath = L"";
LPWSTR pszUrlCopy = NULL;
LPWSTR pszHostEnd = NULL;;
LPWSTR psz = NULL;;
BOOL fRet = TRUE;
if( ( NULL == bstrUrl ) ||
( NULL == pbstrProtocol ) ||
( NULL == pbstrHost ) ||
( NULL == pbstrPath ) ||
( NULL == pwPort ) )
{
return( FALSE );
}
*pbstrProtocol = NULL;
*pbstrHost = NULL;
*pbstrPath = NULL;
pszUrlCopy = new WCHAR[ wcslen( bstrUrl ) + 1 ];
if( NULL == pszUrlCopy )
{
fRet = FALSE;
goto done;
}
wcscpy_s( pszUrlCopy,wcslen( bstrUrl ) + 1, bstrUrl );
// skip the protocol
pszSchemeless = wcsstr( pszUrlCopy, L"://" );
if( NULL != pszSchemeless )
{
pszProtocol = pszUrlCopy;
*pszSchemeless = L'\0'; // null terminate the protocol name
pszSchemeless += 3;
}
else
{
pszSchemeless = pszUrlCopy;
// Remove any leading '/'.
while ( L'/' == *pszSchemeless )
{
pszSchemeless++;
}
}
pszHost = pszSchemeless;
// find the beginning of the path
psz = wcschr( pszSchemeless, L'/' );
if( NULL != psz )
{
*psz = L'\0'; // null terminate the host name
pszPath = psz + 1;
pszHostEnd = psz;
}
else
{
// there is no path, only the host name
pszHostEnd = pszSchemeless + wcslen( pszSchemeless );
}
// see if a port number was specified at the end of the host name
for( psz = pszHostEnd; psz > pszUrlCopy; psz-- )
{
if( L':' == *psz )
{
*psz = L'\0'; // null terminate the host name
*pwPort = ( WORD ) _wtoi( psz + 1 );
break;
}
}
*pbstrProtocol = SysAllocString( pszProtocol );
if( NULL == *pbstrProtocol )
{
fRet = FALSE;
goto done;
}
*pbstrHost = SysAllocString( pszHost );
if( NULL == *pbstrHost )
{
fRet = FALSE;
goto done;
}
*pbstrPath = SysAllocString( pszPath );
if( NULL == *pbstrPath )
{
fRet = FALSE;
goto done;
}
done:
if( NULL != pszUrlCopy )
{
delete [] pszUrlCopy;
}
if( !fRet )
{
if( NULL != *pbstrProtocol )
{
SysFreeString( *pbstrProtocol );
}
if( NULL != *pbstrHost )
{
SysFreeString( *pbstrHost );
}
if( NULL != *pbstrPath )
{
SysFreeString( *pbstrPath );
}
}
return( fRet );
}
/////////////////////////////////////////////////////////////////////////////
//
// [OnCacheClientClose]
//
/////////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE
CProxyPlugin::OnCacheClientClose(
HRESULT resultHr,
IWMSContext *pUserContext,
IWMSContext *pPresentationContext
)
{
return( S_OK );
} // OnCacheClientClose