You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2265 lines
63 KiB
2265 lines
63 KiB
/*++
|
|
|
|
Copyright (c) 2000 Microsoft Corporation
|
|
|
|
Module Name :
|
|
cgi_handler.cxx
|
|
|
|
Abstract:
|
|
Handle CGI requests
|
|
|
|
Author:
|
|
Taylor Weiss (TaylorW) 27-Jan-2000
|
|
|
|
Environment:
|
|
Win32 - User Mode
|
|
|
|
Project:
|
|
ULW3.DLL
|
|
--*/
|
|
|
|
#include "precomp.hxx"
|
|
#include "cgi_handler.h"
|
|
|
|
//
|
|
// W3_CGI_HANDLER statics
|
|
//
|
|
|
|
BOOL W3_CGI_HANDLER::sm_fForwardServerEnvironmentBlock = FALSE;
|
|
WCHAR * W3_CGI_HANDLER::sm_pEnvString = NULL;
|
|
DWORD W3_CGI_HANDLER::sm_cchEnvLength = 0;
|
|
LIST_ENTRY W3_CGI_HANDLER::sm_CgiListHead;
|
|
CRITICAL_SECTION W3_CGI_HANDLER::sm_CgiListLock;
|
|
|
|
|
|
//
|
|
// Environment variable block used for CGI
|
|
//
|
|
LPSTR g_CGIServerVars[] =
|
|
{
|
|
{"ALL_HTTP"}, // Means insert all HTTP_ headers here
|
|
{"APP_POOL_ID"},
|
|
{"AUTH_TYPE"},
|
|
{"AUTH_PASSWORD"},
|
|
{"AUTH_USER"},
|
|
{"CERT_COOKIE"},
|
|
{"CERT_FLAGS"},
|
|
{"CERT_ISSUER"},
|
|
{"CERT_SERIALNUMBER"},
|
|
{"CERT_SUBJECT"},
|
|
{"CONTENT_LENGTH"},
|
|
{"CONTENT_TYPE"},
|
|
{"GATEWAY_INTERFACE"},
|
|
{"HTTPS"},
|
|
{"HTTPS_KEYSIZE"},
|
|
{"HTTPS_SECRETKEYSIZE"},
|
|
{"HTTPS_SERVER_ISSUER"},
|
|
{"HTTPS_SERVER_SUBJECT"},
|
|
{"INSTANCE_ID"},
|
|
{"LOCAL_ADDR"},
|
|
{"LOGON_USER"},
|
|
{"PATH_INFO"},
|
|
{"PATH_TRANSLATED"},
|
|
{"QUERY_STRING"},
|
|
{"REMOTE_ADDR"},
|
|
{"REMOTE_HOST"},
|
|
{"REMOTE_USER"},
|
|
{"REQUEST_METHOD"},
|
|
{"SCRIPT_NAME"},
|
|
{"SERVER_NAME"},
|
|
{"SERVER_PORT"},
|
|
{"SERVER_PORT_SECURE"},
|
|
{"SERVER_PROTOCOL"},
|
|
{"SERVER_SOFTWARE"},
|
|
{"UNMAPPED_REMOTE_USER"},
|
|
{NULL}
|
|
};
|
|
|
|
|
|
// static
|
|
HRESULT W3_CGI_HANDLER::Initialize()
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT, "W3_CGI_HANDLER::Initialize() called\n"));
|
|
|
|
//
|
|
// Read some CGI configuration from the registry
|
|
//
|
|
HRESULT hr;
|
|
|
|
if (!InitializeCriticalSectionAndSpinCount(&sm_CgiListLock, 10))
|
|
{
|
|
return HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
InitializeListHead(&sm_CgiListHead);
|
|
|
|
HKEY w3Params;
|
|
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
|
|
W3_PARAMETERS_KEY,
|
|
0,
|
|
KEY_READ,
|
|
&w3Params) == NO_ERROR)
|
|
{
|
|
DWORD dwType;
|
|
DWORD cbData = sizeof BOOL;
|
|
if ((RegQueryValueEx(w3Params,
|
|
L"ForwardServerEnvironmentBlock",
|
|
NULL,
|
|
&dwType,
|
|
(LPBYTE)&sm_fForwardServerEnvironmentBlock,
|
|
&cbData) != NO_ERROR) ||
|
|
(dwType != REG_DWORD))
|
|
{
|
|
sm_fForwardServerEnvironmentBlock = TRUE;
|
|
}
|
|
|
|
RegCloseKey(w3Params);
|
|
}
|
|
|
|
//
|
|
// Read the environment
|
|
//
|
|
if (sm_fForwardServerEnvironmentBlock)
|
|
{
|
|
WCHAR *EnvString = GetEnvironmentStrings();
|
|
if (EnvString == NULL)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
DBGPRINTF((DBG_CONTEXT, "GetEnvironmentStrings failed\n"));
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// Compute length of environment block (excluding block delimiter)
|
|
//
|
|
|
|
DWORD length;
|
|
sm_cchEnvLength = 0;
|
|
while ((length = (DWORD)wcslen(EnvString + sm_cchEnvLength)) != 0)
|
|
{
|
|
sm_cchEnvLength += length + 1;
|
|
}
|
|
|
|
//
|
|
// store it
|
|
//
|
|
if ((sm_pEnvString = new WCHAR[sm_cchEnvLength]) == NULL)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
|
|
FreeEnvironmentStrings(EnvString);
|
|
goto Exit;
|
|
}
|
|
|
|
memcpy(sm_pEnvString,
|
|
EnvString,
|
|
sm_cchEnvLength * sizeof(WCHAR));
|
|
|
|
FreeEnvironmentStrings(EnvString);
|
|
}
|
|
|
|
return S_OK;
|
|
|
|
Exit:
|
|
DBG_ASSERT(FAILED(hr));
|
|
|
|
//
|
|
// Cleanup partially created stuff
|
|
//
|
|
Terminate();
|
|
|
|
return hr;
|
|
}
|
|
|
|
W3_CGI_HANDLER::~W3_CGI_HANDLER()
|
|
{
|
|
//
|
|
// Close all open handles related to this request
|
|
//
|
|
EnterCriticalSection(&sm_CgiListLock);
|
|
RemoveEntryList(&m_CgiListEntry);
|
|
LeaveCriticalSection(&sm_CgiListLock);
|
|
|
|
if (m_hTimer)
|
|
{
|
|
if (!DeleteTimerQueueTimer(NULL,
|
|
m_hTimer,
|
|
INVALID_HANDLE_VALUE))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"DeleteTimerQueueTimer failed, %d\n",
|
|
GetLastError()));
|
|
}
|
|
m_hTimer = NULL;
|
|
}
|
|
|
|
if (m_hStdOut != INVALID_HANDLE_VALUE)
|
|
{
|
|
if (!CloseHandle(m_hStdOut))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"CloseHandle failed on StdOut, %d\n",
|
|
GetLastError()));
|
|
}
|
|
m_hStdOut = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if (m_hStdIn != INVALID_HANDLE_VALUE)
|
|
{
|
|
if (!CloseHandle(m_hStdIn))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"CloseHandle failed on StdIn, %d\n",
|
|
GetLastError()));
|
|
}
|
|
m_hStdIn = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if (m_hProcess)
|
|
{
|
|
//
|
|
// Just in case it is still running
|
|
//
|
|
TerminateProcess(m_hProcess, 0);
|
|
|
|
DBG_REQUIRE(CloseHandle(m_hProcess));
|
|
m_hProcess = NULL;
|
|
}
|
|
|
|
// perf ctr
|
|
QueryW3Context()->QuerySite()->DecCgiReqs();
|
|
|
|
if ( ETW_IS_TRACE_ON(ETW_LEVEL_CP) )
|
|
{
|
|
HTTP_REQUEST_ID RequestId = QueryW3Context()->QueryRequest()->QueryRequestId();
|
|
|
|
g_pEtwTracer->EtwTraceEvent( &CgiEventGuid,
|
|
ETW_TYPE_END,
|
|
&RequestId,
|
|
sizeof(HTTP_REQUEST_ID),
|
|
NULL,
|
|
0 );
|
|
}
|
|
}
|
|
|
|
VOID CALLBACK W3_CGI_HANDLER::OnPipeIoCompletion(
|
|
DWORD dwErrorCode,
|
|
DWORD dwNumberOfBytesTransfered,
|
|
LPOVERLAPPED lpOverlapped)
|
|
{
|
|
if (dwErrorCode && dwErrorCode != ERROR_BROKEN_PIPE)
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT, "Error %d on CGI_HANDLER::OnPipeIoCompletion\n", dwErrorCode));
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
BOOL fIsCgiError = TRUE;
|
|
|
|
W3_CGI_HANDLER *pHandler = CONTAINING_RECORD(lpOverlapped,
|
|
W3_CGI_HANDLER,
|
|
m_Overlapped);
|
|
|
|
if (dwErrorCode ||
|
|
(dwNumberOfBytesTransfered == 0))
|
|
{
|
|
if (pHandler->m_dwRequestState == CgiStateProcessingRequestEntity)
|
|
{
|
|
//
|
|
// If we could not write the request entity, for example because
|
|
// the CGI did not wait to read the entity, ignore the error and
|
|
// continue on to reading the output
|
|
//
|
|
|
|
//
|
|
// If this is an nph cgi, we do not parse header
|
|
//
|
|
if (pHandler->QueryIsNphCgi())
|
|
{
|
|
pHandler->m_dwRequestState = CgiStateProcessingResponseEntity;
|
|
}
|
|
else
|
|
{
|
|
pHandler->m_dwRequestState = CgiStateProcessingResponseHeaders;
|
|
}
|
|
pHandler->m_cbData = 0;
|
|
|
|
if (SUCCEEDED(hr = pHandler->CGIReadCGIOutput()))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
hr = HRESULT_FROM_WIN32(dwErrorCode);
|
|
}
|
|
|
|
fIsCgiError = TRUE;
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (pHandler->m_dwRequestState == CgiStateProcessingResponseHeaders)
|
|
{
|
|
//
|
|
// Copy the headers to the header buffer to be parsed when we have
|
|
// all the headers
|
|
//
|
|
if (!pHandler->m_bufResponseHeaders.Resize(
|
|
pHandler->m_cbData + dwNumberOfBytesTransfered + 1))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
memcpy((LPSTR)pHandler->m_bufResponseHeaders.QueryPtr() +
|
|
pHandler->m_cbData,
|
|
pHandler->m_DataBuffer,
|
|
dwNumberOfBytesTransfered);
|
|
|
|
pHandler->m_cbData += dwNumberOfBytesTransfered;
|
|
|
|
((LPSTR)pHandler->m_bufResponseHeaders.QueryPtr())[pHandler->m_cbData] = '\0';
|
|
}
|
|
else if (pHandler->m_dwRequestState == CgiStateProcessingResponseEntity)
|
|
{
|
|
pHandler->m_cbData = dwNumberOfBytesTransfered;
|
|
}
|
|
|
|
if (FAILED(hr = pHandler->CGIContinueOnPipeCompletion(&fIsCgiError)))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
return;
|
|
|
|
ErrorExit:
|
|
if (hr != HRESULT_FROM_WIN32(ERROR_BROKEN_PIPE))
|
|
{
|
|
pHandler->QueryW3Context()->SetErrorStatus(hr);
|
|
}
|
|
|
|
//
|
|
// If the error happened due to an CGI problem, mark it as 502
|
|
// appropriately
|
|
//
|
|
if (fIsCgiError)
|
|
{
|
|
if (pHandler->m_dwRequestState != CgiStateProcessingResponseEntity &&
|
|
pHandler->m_dwRequestState != CgiStateDoneWithRequest)
|
|
{
|
|
pHandler->QueryW3Context()->QueryResponse()->Clear();
|
|
|
|
DWORD dwExitCode;
|
|
if (GetExitCodeProcess(pHandler->m_hProcess,
|
|
&dwExitCode) &&
|
|
dwExitCode == CGI_PREMATURE_DEATH_CODE)
|
|
{
|
|
STACK_STRU( strUrl, 128);
|
|
STACK_STRU( strQuery, 128);
|
|
if (SUCCEEDED(pHandler->QueryW3Context()->QueryRequest()->GetUrl(&strUrl)) &&
|
|
SUCCEEDED(pHandler->QueryW3Context()->QueryRequest()->GetQueryString(&strQuery)))
|
|
{
|
|
LPCWSTR apsz[2];
|
|
apsz[0] = strUrl.QueryStr();
|
|
apsz[1] = strQuery.QueryStr();
|
|
|
|
g_pW3Server->LogEvent(W3_EVENT_KILLING_SCRIPT,
|
|
2,
|
|
apsz);
|
|
}
|
|
|
|
pHandler->QueryW3Context()->QueryResponse()->SetStatus(
|
|
HttpStatusBadGateway,
|
|
Http502Timeout);
|
|
}
|
|
else
|
|
{
|
|
pHandler->QueryW3Context()->QueryResponse()->SetStatus(
|
|
HttpStatusBadGateway,
|
|
Http502PrematureExit);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pHandler->QueryW3Context()->QueryResponse()->SetStatus(
|
|
HttpStatusBadRequest);
|
|
}
|
|
|
|
pHandler->m_dwRequestState = CgiStateDoneWithRequest;
|
|
|
|
POST_MAIN_COMPLETION( pHandler->QueryW3Context()->QueryMainContext() );
|
|
}
|
|
|
|
|
|
HRESULT
|
|
GeneratePipeNames(
|
|
STRU *pstrStdoutName,
|
|
STRU *pstrStdinName)
|
|
{
|
|
RPC_STATUS rpcStatus;
|
|
UUID pipeUuid;
|
|
LPWSTR pszPipeUuid = NULL;
|
|
HRESULT hr;
|
|
|
|
rpcStatus = UuidCreate(&pipeUuid);
|
|
if (rpcStatus != RPC_S_OK)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
rpcStatus = UuidToString(&pipeUuid, &pszPipeUuid);
|
|
if (rpcStatus != RPC_S_OK)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
if (FAILED(hr = pstrStdoutName->Copy(L"\\\\.\\pipe\\IISCgiStdout")) ||
|
|
FAILED(hr = pstrStdoutName->Append(pszPipeUuid)) ||
|
|
FAILED(hr = pstrStdinName->Copy(L"\\\\.\\pipe\\IISCgiStdin")) ||
|
|
FAILED(hr = pstrStdinName->Append(pszPipeUuid)))
|
|
{
|
|
RpcStringFree(&pszPipeUuid);
|
|
return hr;
|
|
}
|
|
|
|
RpcStringFree(&pszPipeUuid);
|
|
return hr;
|
|
}
|
|
|
|
|
|
// static
|
|
HRESULT W3_CGI_HANDLER::SetupChildPipes(
|
|
HANDLE *phStdOut,
|
|
HANDLE *phStdIn,
|
|
STARTUPINFO *pstartupinfo)
|
|
/*++
|
|
Synopsis
|
|
Setup the pipes to use for communicating with the child process
|
|
|
|
Arguments
|
|
phStdOut: this will be populated with the server side of CGIs stdout
|
|
phStdIn: this will be populated with the server side of CGIs stdin
|
|
pstartupinfo: this will be populated with the startinfo that can be
|
|
passed to a CreateProcess call
|
|
|
|
Return Value
|
|
HRESULT
|
|
--*/
|
|
{
|
|
STACK_STRU( strStdoutName, 128);
|
|
STACK_STRU( strStdinName, 128);
|
|
HRESULT hr = S_OK;
|
|
|
|
DBG_ASSERT(phStdOut != NULL);
|
|
DBG_ASSERT(phStdIn != NULL);
|
|
DBG_ASSERT(pstartupinfo != NULL);
|
|
|
|
SECURITY_ATTRIBUTES sa;
|
|
|
|
sa.nLength = sizeof(sa);
|
|
sa.lpSecurityDescriptor = NULL;
|
|
sa.bInheritHandle = TRUE;
|
|
|
|
pstartupinfo->hStdOutput = INVALID_HANDLE_VALUE;
|
|
pstartupinfo->hStdInput = INVALID_HANDLE_VALUE;
|
|
pstartupinfo->dwFlags = STARTF_USESTDHANDLES;
|
|
|
|
hr = GeneratePipeNames( &strStdoutName,
|
|
&strStdinName );
|
|
if (FAILED(hr))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
*phStdOut = CreateNamedPipe(strStdoutName.QueryStr(),
|
|
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE,
|
|
0,
|
|
1,
|
|
MAX_CGI_BUFFERING,
|
|
MAX_CGI_BUFFERING,
|
|
INFINITE,
|
|
NULL);
|
|
if (*phStdOut == INVALID_HANDLE_VALUE)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
goto ErrorExit;
|
|
}
|
|
if (!ThreadPoolBindIoCompletionCallback(*phStdOut,
|
|
OnPipeIoCompletion,
|
|
0))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT, "ThreadPoolBindIo failed\n"));
|
|
hr = E_FAIL;
|
|
goto ErrorExit;
|
|
}
|
|
pstartupinfo->hStdOutput = CreateFile(strStdoutName.QueryStr(),
|
|
GENERIC_WRITE,
|
|
0,
|
|
&sa,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_OVERLAPPED,
|
|
NULL);
|
|
if (pstartupinfo->hStdOutput == INVALID_HANDLE_VALUE)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
goto ErrorExit;
|
|
}
|
|
|
|
*phStdIn = CreateNamedPipe(strStdinName.QueryStr(),
|
|
PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE,
|
|
0,
|
|
1,
|
|
MAX_CGI_BUFFERING,
|
|
MAX_CGI_BUFFERING,
|
|
INFINITE,
|
|
NULL);
|
|
if (*phStdIn == INVALID_HANDLE_VALUE)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
goto ErrorExit;
|
|
}
|
|
if (!ThreadPoolBindIoCompletionCallback(*phStdIn,
|
|
OnPipeIoCompletion,
|
|
0))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT, "ThreadPoolBindIo failed\n"));
|
|
hr = E_FAIL;
|
|
goto ErrorExit;
|
|
}
|
|
pstartupinfo->hStdInput = CreateFile(strStdinName.QueryStr(),
|
|
GENERIC_READ,
|
|
0,
|
|
&sa,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_OVERLAPPED,
|
|
NULL);
|
|
if (pstartupinfo->hStdInput == INVALID_HANDLE_VALUE)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Stdout and Stderror will use the same pipe.
|
|
//
|
|
pstartupinfo->hStdError = pstartupinfo->hStdOutput;
|
|
|
|
return S_OK;
|
|
|
|
ErrorExit:
|
|
DBGPRINTF((DBG_CONTEXT, "SetupChildPipes Failed, hr %x\n", hr));
|
|
|
|
//
|
|
// Need to close these now so that other instances do not connect to it
|
|
//
|
|
if (pstartupinfo->hStdOutput != INVALID_HANDLE_VALUE)
|
|
{
|
|
DBG_REQUIRE(CloseHandle(pstartupinfo->hStdOutput));
|
|
pstartupinfo->hStdOutput = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if (pstartupinfo->hStdInput != INVALID_HANDLE_VALUE)
|
|
{
|
|
DBG_REQUIRE(CloseHandle(pstartupinfo->hStdInput));
|
|
pstartupinfo->hStdInput = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if (*phStdOut != INVALID_HANDLE_VALUE)
|
|
{
|
|
DBG_REQUIRE(CloseHandle(*phStdOut));
|
|
*phStdOut = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if (*phStdIn != INVALID_HANDLE_VALUE)
|
|
{
|
|
DBG_REQUIRE(CloseHandle(*phStdIn));
|
|
*phStdIn = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
BOOL IsCmdExe(const WCHAR *pchPath)
|
|
/*++
|
|
Tells whether the CGI call is for a cmd shell
|
|
--*/
|
|
{
|
|
//
|
|
// do case-insensitive search for cmd.exe (there is no function in
|
|
// msvcrt for this)
|
|
//
|
|
for (; *pchPath != L'\0'; pchPath++)
|
|
{
|
|
if ((pchPath[0] == L'c' || pchPath[0] == 'C') &&
|
|
(pchPath[1] == L'm' || pchPath[1] == 'M') &&
|
|
(pchPath[2] == L'd' || pchPath[2] == 'D') &&
|
|
pchPath[3] == L'.' &&
|
|
(pchPath[4] == L'e' || pchPath[4] == 'E') &&
|
|
(pchPath[5] == L'x' || pchPath[5] == 'X') &&
|
|
(pchPath[6] == L'e' || pchPath[6] == 'E'))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL IsNphCgi(const WCHAR *pszUrl)
|
|
/*++
|
|
Tells whether the URL is for an nph cgi script
|
|
--*/
|
|
{
|
|
LPWSTR pszLastSlash = wcsrchr(pszUrl, L'/');
|
|
if (pszLastSlash != NULL)
|
|
{
|
|
if (_wcsnicmp(pszLastSlash + 1, L"nph-", 4) == 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
HRESULT SetupCmdLine(W3_REQUEST *pRequest,
|
|
STRU *pstrCmdLine)
|
|
{
|
|
STACK_STRU (queryString, MAX_PATH);
|
|
HRESULT hr = S_OK;
|
|
if (FAILED(hr = pRequest->GetQueryString(&queryString)))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// If there is no QueryString OR if an unencoded "=" is found, don't
|
|
// append QueryString to the command line according to spec
|
|
//
|
|
|
|
if (queryString.QueryCCH() == 0 ||
|
|
wcschr(queryString.QueryStr(), L'='))
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
queryString.Unescape();
|
|
|
|
return pstrCmdLine->Append(queryString);
|
|
} // SetupCmdLine
|
|
|
|
|
|
HRESULT
|
|
W3_CGI_HANDLER::SetupChildEnv(OUT BUFFER *pBuff)
|
|
{
|
|
//
|
|
// Build the environment block for CGI
|
|
//
|
|
|
|
DWORD cchCurrentPos = 0;
|
|
|
|
//
|
|
// Copy the environment variables
|
|
//
|
|
|
|
if (sm_fForwardServerEnvironmentBlock)
|
|
{
|
|
if (!pBuff->Resize(sm_cchEnvLength * sizeof(WCHAR), 512))
|
|
{
|
|
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
|
|
}
|
|
|
|
memcpy(pBuff->QueryPtr(),
|
|
sm_pEnvString,
|
|
sm_cchEnvLength * sizeof(WCHAR));
|
|
|
|
cchCurrentPos = sm_cchEnvLength;
|
|
}
|
|
|
|
CHAR * pszName;
|
|
STACK_STRU (strVal, 512);
|
|
STACK_STRU (struName, 32);
|
|
for (DWORD i = 0; (pszName = g_CGIServerVars[i]) != NULL; i++)
|
|
{
|
|
HRESULT hr;
|
|
if (FAILED(hr = SERVER_VARIABLE_HASH::GetServerVariableW(
|
|
QueryW3Context(),
|
|
pszName,
|
|
&strVal)))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
DWORD cchName = (DWORD)strlen(pszName);
|
|
DWORD cchValue = strVal.QueryCCH();
|
|
|
|
//
|
|
// We need space for the terminating '\0' and the '='
|
|
//
|
|
|
|
DWORD cbNeeded = (cchName + cchValue + 1 + 1) * sizeof(WCHAR);
|
|
|
|
if (!pBuff->Resize(cchCurrentPos * sizeof(WCHAR) + cbNeeded,
|
|
512))
|
|
{
|
|
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
|
|
}
|
|
|
|
WCHAR * pchtmp = (WCHAR *)pBuff->QueryPtr();
|
|
if (strcmp(pszName, "ALL_HTTP") == 0)
|
|
{
|
|
//
|
|
// ALL_HTTP means we're adding all of the HTTP_ header
|
|
// fields which requires a little bit of special processing
|
|
//
|
|
|
|
memcpy(pchtmp + cchCurrentPos,
|
|
strVal.QueryStr(),
|
|
(cchValue + 1) * sizeof(WCHAR));
|
|
|
|
WCHAR * pszColonPosition = wcschr(pchtmp + cchCurrentPos, L':');
|
|
WCHAR * pszNewLinePosition;
|
|
|
|
//
|
|
// Convert the Name:Value\n entries to Name=Value\0 entries
|
|
// for the environment table
|
|
//
|
|
|
|
while (pszColonPosition != NULL)
|
|
{
|
|
*pszColonPosition = L'=';
|
|
|
|
pszNewLinePosition = wcschr(pszColonPosition + 1, L'\n');
|
|
|
|
DBG_ASSERT(pszNewLinePosition != NULL);
|
|
|
|
*pszNewLinePosition = L'\0';
|
|
|
|
pszColonPosition = wcschr(pszNewLinePosition + 1, L':');
|
|
}
|
|
|
|
cchCurrentPos += cchValue;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Normal ServerVariable, add it
|
|
//
|
|
if (FAILED(hr = struName.CopyA(pszName,
|
|
cchName)))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
memcpy(pchtmp + cchCurrentPos,
|
|
struName.QueryStr(),
|
|
cchName * sizeof(WCHAR));
|
|
|
|
*(pchtmp + cchCurrentPos + cchName) = L'=';
|
|
|
|
memcpy(pchtmp + cchCurrentPos + cchName + 1,
|
|
strVal.QueryStr(),
|
|
(cchValue + 1) * sizeof(WCHAR));
|
|
|
|
cchCurrentPos += cchName + cchValue + 1 + 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Add an extra null terminator to the environment list
|
|
//
|
|
|
|
if (!pBuff->Resize((cchCurrentPos + 1) * sizeof(WCHAR)))
|
|
{
|
|
return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
|
|
}
|
|
|
|
*((WCHAR *)pBuff->QueryPtr() + cchCurrentPos) = L'\0';
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
VOID CALLBACK W3_CGI_HANDLER::CGITerminateProcess(PVOID pContext,
|
|
BOOLEAN)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function is the callback called by the scheduler thread after the
|
|
specified timeout period has elapsed.
|
|
|
|
Arguments:
|
|
|
|
pContext - Handle of process to kill
|
|
|
|
--*/
|
|
{
|
|
W3_CGI_HANDLER *pHandler = reinterpret_cast<W3_CGI_HANDLER *>(pContext);
|
|
|
|
if (pHandler->m_hProcess &&
|
|
!TerminateProcess(pHandler->m_hProcess, CGI_PREMATURE_DEATH_CODE))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"CGITerminateProcess - TerminateProcess failed, error %d\n",
|
|
GetLastError()));
|
|
}
|
|
|
|
if (pHandler->m_hStdIn != INVALID_HANDLE_VALUE &&
|
|
!DisconnectNamedPipe(pHandler->m_hStdIn))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"CGITerminateProcess - DisconnectNamedPipe failed, error %d\n",
|
|
GetLastError()));
|
|
}
|
|
|
|
if (pHandler->m_hStdOut != INVALID_HANDLE_VALUE &&
|
|
!DisconnectNamedPipe(pHandler->m_hStdOut))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"CGITerminateProcess - DisconnectNamedPipe failed, error %d\n",
|
|
GetLastError()));
|
|
}
|
|
} // CGITerminateProcess
|
|
|
|
|
|
BOOL CheckForEndofHeaders(IN LPSTR pbuff,
|
|
IN int cbData,
|
|
OUT DWORD *pcbIndexStartOfData)
|
|
{
|
|
//
|
|
// If the response starts with a newline (\n, \r\n or \r\r\n), then,
|
|
// no headers present, it is all data
|
|
//
|
|
if (pbuff[0] == '\n')
|
|
{
|
|
*pcbIndexStartOfData = 1;
|
|
return TRUE;
|
|
}
|
|
if ((pbuff[0] == '\r') && (pbuff[1] == '\n'))
|
|
{
|
|
*pcbIndexStartOfData = 2;
|
|
return TRUE;
|
|
}
|
|
if ((pbuff[0] == '\r') && (pbuff[1] == '\r') && (pbuff[2] == '\n'))
|
|
{
|
|
*pcbIndexStartOfData = 3;
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Look for two consecutive newline, \n\r\r\n, \n\r\n or \n\n
|
|
// No problem with running beyond the end of buffer as the buffer is
|
|
// null terminated
|
|
//
|
|
int index;
|
|
for (index = 0; index < cbData - 1; index++)
|
|
{
|
|
if (pbuff[index] == '\n')
|
|
{
|
|
if (pbuff[index + 1] == '\n')
|
|
{
|
|
*pcbIndexStartOfData = index + 2;
|
|
return TRUE;
|
|
}
|
|
else if (pbuff[index + 1] == '\r')
|
|
{
|
|
if (pbuff[index + 2] == '\n')
|
|
{
|
|
*pcbIndexStartOfData = index + 3;
|
|
return TRUE;
|
|
}
|
|
else if ((pbuff[index + 2] == '\r') &&
|
|
(pbuff[index + 3] == '\n'))
|
|
{
|
|
*pcbIndexStartOfData = index + 4;
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
HRESULT
|
|
W3_CGI_HANDLER::ProcessCGIOutput()
|
|
/*++
|
|
Synopsis
|
|
This function parses the CGI output and seperates out the headers and
|
|
interprets them as appropriate
|
|
|
|
Return Value
|
|
HRESULT
|
|
--*/
|
|
{
|
|
W3_REQUEST *pRequest = QueryW3Context()->QueryRequest();
|
|
W3_RESPONSE *pResponse = QueryW3Context()->QueryResponse();
|
|
HRESULT hr = S_OK;
|
|
STACK_STRA (strReason, 32);
|
|
DWORD index = 0;
|
|
BOOL fCanKeepConn = FALSE;
|
|
|
|
//
|
|
// The end of CGI headers are marked by a blank line, check to see if
|
|
// we've hit that line.
|
|
//
|
|
LPSTR Headers = (LPSTR)m_bufResponseHeaders.QueryPtr();
|
|
DWORD cbIndexStartOfData;
|
|
if (!CheckForEndofHeaders(Headers,
|
|
m_cbData,
|
|
&cbIndexStartOfData))
|
|
{
|
|
return CGIReadCGIOutput();
|
|
}
|
|
|
|
m_dwRequestState = CgiStateProcessingResponseEntity;
|
|
//
|
|
// We've found the end of the headers, process them
|
|
//
|
|
// if request header contains:
|
|
//
|
|
// Location: xxxx - if starts with /, send doc, otherwise send
|
|
// redirect message
|
|
// URI: preferred synonym to Location:
|
|
// Status: nnn xxxx - Send as status code (HTTP/1.1 nnn xxxx)
|
|
//
|
|
|
|
//
|
|
// The first line in the response could (optionally) look like
|
|
// HTTP/n.n nnn xxxx\r\n
|
|
//
|
|
if (!strncmp(Headers, "HTTP/", 5))
|
|
{
|
|
USHORT statusCode;
|
|
LPSTR pszSpace;
|
|
LPSTR pszNewline;
|
|
if ((pszNewline = strchr(Headers, '\n')) == NULL)
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
index = (DWORD)DIFF(pszNewline - Headers) + 1;
|
|
if (pszNewline[-1] == '\r')
|
|
pszNewline--;
|
|
|
|
|
|
if (((pszSpace = strchr(Headers, ' ')) == NULL) ||
|
|
(pszSpace >= pszNewline))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
while (pszSpace[0] == ' ')
|
|
pszSpace++;
|
|
statusCode = (USHORT) atoi(pszSpace);
|
|
|
|
//
|
|
// UL only allows status codes upto 999, so reject others
|
|
//
|
|
if (statusCode > 999)
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (((pszSpace = strchr(pszSpace, ' ')) == NULL) ||
|
|
(pszSpace >= pszNewline))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
while (pszSpace[0] == ' ')
|
|
pszSpace++;
|
|
|
|
if (FAILED(hr = strReason.Copy(pszSpace,
|
|
(DWORD)DIFF(pszNewline - pszSpace))) ||
|
|
FAILED(hr = pResponse->SetStatus(statusCode, strReason)))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (pResponse->QueryStatusCode() == HttpStatusUnauthorized.statusCode)
|
|
{
|
|
pResponse->SetStatus(HttpStatusUnauthorized,
|
|
Http401Application);
|
|
}
|
|
}
|
|
|
|
while ((index + 3) < cbIndexStartOfData)
|
|
{
|
|
//
|
|
// Find the ':' in Header : Value\r\n
|
|
//
|
|
LPSTR pchColon = strchr(Headers + index, ':');
|
|
|
|
//
|
|
// Find the '\n' in Header : Value\r\n
|
|
//
|
|
LPSTR pchNewline = strchr(Headers + index, '\n');
|
|
|
|
//
|
|
// Take care of header continuation
|
|
//
|
|
while (pchNewline[1] == ' ' ||
|
|
pchNewline[1] == '\t')
|
|
{
|
|
pchNewline = strchr(pchNewline + 1, '\n');
|
|
}
|
|
|
|
if ((pchColon == NULL) ||
|
|
(pchColon >= pchNewline))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Skip over any spaces before the ':'
|
|
//
|
|
LPSTR pchEndofHeaderName;
|
|
for (pchEndofHeaderName = pchColon - 1;
|
|
(pchEndofHeaderName >= Headers + index) &&
|
|
(*pchEndofHeaderName == ' ');
|
|
pchEndofHeaderName--)
|
|
{}
|
|
|
|
//
|
|
// Copy the header name
|
|
//
|
|
STACK_STRA (strHeaderName, 32);
|
|
if (FAILED(hr = strHeaderName.Copy(Headers + index,
|
|
(DWORD)DIFF(pchEndofHeaderName - Headers) - index + 1)))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Skip over the ':' and any trailing spaces
|
|
//
|
|
for (index = (DWORD)DIFF(pchColon - Headers) + 1;
|
|
Headers[index] == ' ';
|
|
index++)
|
|
{}
|
|
|
|
//
|
|
// Skip over any spaces before the '\n'
|
|
//
|
|
LPSTR pchEndofHeaderValue;
|
|
for (pchEndofHeaderValue = pchNewline - 1;
|
|
(pchEndofHeaderValue >= Headers + index) &&
|
|
((*pchEndofHeaderValue == ' ') ||
|
|
(*pchEndofHeaderValue == '\r'));
|
|
pchEndofHeaderValue--)
|
|
{}
|
|
|
|
//
|
|
// Copy the header value
|
|
//
|
|
STACK_STRA (strHeaderValue, 32);
|
|
if (FAILED(hr = strHeaderValue.Copy(Headers + index,
|
|
(DWORD)DIFF(pchEndofHeaderValue - Headers) - index + 1)))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (!_stricmp("Status", strHeaderName.QueryStr()))
|
|
{
|
|
USHORT statusCode = (USHORT) atoi(strHeaderValue.QueryStr());
|
|
|
|
//
|
|
// UL only allows status codes upto 999, so reject others
|
|
//
|
|
if (statusCode > 999)
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
CHAR * pchReason = strchr(strHeaderValue.QueryStr(), ' ');
|
|
if (pchReason != NULL)
|
|
{
|
|
pchReason++;
|
|
if (FAILED(hr = strReason.Copy(pchReason)) ||
|
|
FAILED(hr = pResponse->SetStatus(statusCode, strReason)))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
if (pResponse->QueryStatusCode() == HttpStatusUnauthorized.statusCode)
|
|
{
|
|
pResponse->SetStatus(HttpStatusUnauthorized,
|
|
Http401Application);
|
|
}
|
|
}
|
|
}
|
|
else if (!_stricmp("Location", strHeaderName.QueryStr()) ||
|
|
!_stricmp("URI", strHeaderName.QueryStr()))
|
|
{
|
|
//
|
|
// The CGI script is redirecting us to another URL. If it
|
|
// begins with a '/', then get it, otherwise send a redirect
|
|
// message
|
|
//
|
|
|
|
m_dwRequestState = CgiStateDoneWithRequest;
|
|
m_fResponseRedirected = TRUE;
|
|
|
|
if (strHeaderValue.QueryStr()[0] == '/')
|
|
{
|
|
//
|
|
// Execute a child request
|
|
//
|
|
pResponse->Clear();
|
|
pResponse->SetStatus(HttpStatusOk);
|
|
|
|
if (FAILED(hr = pRequest->SetUrlA(strHeaderValue)) ||
|
|
FAILED(hr = QueryW3Context()->ExecuteChildRequest(
|
|
pRequest,
|
|
FALSE,
|
|
W3_FLAG_ASYNC )))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
else
|
|
{
|
|
HTTP_STATUS httpStatus;
|
|
pResponse->GetStatus(&httpStatus);
|
|
|
|
//
|
|
// Plain old redirect since this was not a "local" URL
|
|
// If the CGI had already set the status to some 3xx value,
|
|
// honor it
|
|
//
|
|
|
|
if (FAILED(hr = QueryW3Context()->SetupHttpRedirect(
|
|
strHeaderValue,
|
|
FALSE,
|
|
((httpStatus.statusCode / 100) == 3) ? httpStatus : HttpStatusRedirect)))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Remember the Content-Length the cgi specified
|
|
//
|
|
|
|
if (!_stricmp("Content-Length", strHeaderName.QueryStr()))
|
|
{
|
|
fCanKeepConn = TRUE;
|
|
m_bytesToSend = atoi(strHeaderValue.QueryStr());
|
|
}
|
|
else if (!_stricmp("Transfer-Encoding", strHeaderName.QueryStr()) &&
|
|
!_stricmp("chunked", strHeaderValue.QueryStr()))
|
|
{
|
|
fCanKeepConn = TRUE;
|
|
}
|
|
else if (!_stricmp("Connection", strHeaderName.QueryStr()) &&
|
|
!_stricmp("close", strHeaderValue.QueryStr()))
|
|
{
|
|
QueryW3Context()->SetDisconnect(TRUE);
|
|
}
|
|
|
|
if (strHeaderName.QueryCCH() > MAXUSHORT ||
|
|
strHeaderValue.QueryCCH() > MAXUSHORT)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
|
|
goto ErrorExit;
|
|
}
|
|
|
|
//
|
|
// Copy any other fields the script specified
|
|
//
|
|
hr = pResponse->SetHeader(strHeaderName.QueryStr(),
|
|
(USHORT)strHeaderName.QueryCCH(),
|
|
strHeaderValue.QueryStr(),
|
|
(USHORT)strHeaderValue.QueryCCH());
|
|
if (FAILED(hr))
|
|
{
|
|
goto ErrorExit;
|
|
}
|
|
}
|
|
|
|
index = (DWORD)DIFF(pchNewline - Headers) + 1;
|
|
} // while
|
|
|
|
if (m_fResponseRedirected)
|
|
{
|
|
return QueryW3Context()->SendResponse(W3_FLAG_ASYNC);
|
|
}
|
|
|
|
//
|
|
// We allow CGI to send its own entity if no custom-error is defined
|
|
//
|
|
if (pResponse->QueryStatusCode() >= 400)
|
|
{
|
|
BOOL fIsFileError;
|
|
STACK_STRU ( strError, 64 );
|
|
HTTP_SUB_ERROR subError;
|
|
pResponse->QuerySubError( &subError );
|
|
|
|
W3_METADATA *pMetaData = QueryW3Context()->QueryUrlContext()->QueryMetaData();
|
|
|
|
hr = pMetaData->FindCustomError( pResponse->QueryStatusCode(),
|
|
subError.mdSubError,
|
|
&fIsFileError,
|
|
&strError );
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
//
|
|
// Found a custom error, send it
|
|
//
|
|
m_dwRequestState = CgiStateDoneWithRequest;
|
|
return QueryW3Context()->SendResponse(W3_FLAG_ASYNC);
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the CGI did not say how much data was present, mark the
|
|
// connection for closing
|
|
//
|
|
if (!fCanKeepConn)
|
|
{
|
|
QueryW3Context()->SetDisconnect(TRUE);
|
|
}
|
|
|
|
//
|
|
// Now send any data trailing the headers
|
|
//
|
|
if (cbIndexStartOfData < m_cbData)
|
|
{
|
|
if (m_bytesToSend < m_cbData - cbIndexStartOfData)
|
|
{
|
|
m_cbData = m_bytesToSend + cbIndexStartOfData;
|
|
}
|
|
|
|
if (FAILED(hr = pResponse->AddMemoryChunkByReference(
|
|
Headers + cbIndexStartOfData,
|
|
m_cbData - cbIndexStartOfData)))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
m_bytesToSend -= m_cbData - cbIndexStartOfData;
|
|
if (m_bytesToSend == 0)
|
|
{
|
|
m_dwRequestState = CgiStateDoneWithRequest;
|
|
}
|
|
}
|
|
|
|
return QueryW3Context()->SendResponse(W3_FLAG_ASYNC
|
|
| W3_FLAG_MORE_DATA
|
|
| W3_FLAG_NO_CONTENT_LENGTH
|
|
| W3_FLAG_NO_ERROR_BODY);
|
|
|
|
ErrorExit:
|
|
m_dwRequestState = CgiStateProcessingResponseHeaders;
|
|
if (FAILED(hr))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
HRESULT W3_CGI_HANDLER::CGIReadRequestEntity(BOOL *pfIoPending)
|
|
/*++
|
|
Synopsis
|
|
|
|
This function reads the next chunk of request entity
|
|
|
|
Arguments
|
|
|
|
pfIoPending: On return indicates if there is an I/O pending
|
|
|
|
Return Value
|
|
HRESULT
|
|
--*/
|
|
{
|
|
if (m_bytesToReceive == 0)
|
|
{
|
|
*pfIoPending = FALSE;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT hr;
|
|
DWORD cbRead;
|
|
if (FAILED(hr = QueryW3Context()->ReceiveEntity(
|
|
W3_FLAG_ASYNC,
|
|
m_DataBuffer,
|
|
(DWORD)min(m_bytesToReceive, sizeof(m_DataBuffer)),
|
|
&cbRead)))
|
|
{
|
|
if (hr == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF))
|
|
{
|
|
*pfIoPending = FALSE;
|
|
return S_OK;
|
|
}
|
|
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"CGIReadEntity Error reading gateway data, hr %x\n",
|
|
hr));
|
|
|
|
return hr;
|
|
}
|
|
|
|
*pfIoPending = TRUE;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT W3_CGI_HANDLER::CGIWriteResponseEntity()
|
|
/*++
|
|
Synopsis
|
|
|
|
This function writes the next chunk of response entity
|
|
|
|
Arguments
|
|
|
|
None
|
|
|
|
Return Value
|
|
|
|
HRESULT
|
|
--*/
|
|
{
|
|
W3_RESPONSE *pResponse = QueryW3Context()->QueryResponse();
|
|
pResponse->Clear( TRUE );
|
|
|
|
//
|
|
// At this point, we got to be reading the entity
|
|
//
|
|
DBG_ASSERT(m_dwRequestState == CgiStateProcessingResponseEntity);
|
|
|
|
if (m_bytesToSend <= m_cbData)
|
|
{
|
|
m_cbData = m_bytesToSend;
|
|
m_bytesToSend = 0;
|
|
m_dwRequestState = CgiStateDoneWithRequest;
|
|
}
|
|
else
|
|
{
|
|
m_bytesToSend -= m_cbData;
|
|
}
|
|
|
|
HRESULT hr;
|
|
if (FAILED(hr = pResponse->AddMemoryChunkByReference(m_DataBuffer,
|
|
m_cbData)))
|
|
{
|
|
return hr;
|
|
}
|
|
|
|
return QueryW3Context()->SendEntity(W3_FLAG_ASYNC | W3_FLAG_MORE_DATA);
|
|
}
|
|
|
|
HRESULT W3_CGI_HANDLER::CGIReadCGIOutput()
|
|
/*++
|
|
Synopsis
|
|
|
|
This function Reads the next chunk of data from the CGI
|
|
|
|
Arguments
|
|
|
|
None
|
|
|
|
Return Value
|
|
S_OK if async I/O posted
|
|
Failure otherwise
|
|
--*/
|
|
{
|
|
if (!ReadFile(m_hStdOut,
|
|
m_DataBuffer,
|
|
MAX_CGI_BUFFERING,
|
|
NULL,
|
|
&m_Overlapped))
|
|
{
|
|
DWORD dwErr = GetLastError();
|
|
if (dwErr == ERROR_IO_PENDING)
|
|
return S_OK;
|
|
|
|
if (dwErr != ERROR_BROKEN_PIPE)
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"ReadFile from child stdout failed, error %d\n",
|
|
GetLastError()));
|
|
}
|
|
|
|
return HRESULT_FROM_WIN32(dwErr);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT W3_CGI_HANDLER::CGIWriteCGIInput()
|
|
/*++
|
|
Synopsis
|
|
|
|
This function
|
|
|
|
Arguments
|
|
|
|
None
|
|
|
|
Return Value
|
|
|
|
HRESULT
|
|
--*/
|
|
{
|
|
if (!WriteFile(m_hStdIn,
|
|
m_DataBuffer,
|
|
m_cbData,
|
|
NULL,
|
|
&m_Overlapped))
|
|
{
|
|
if (GetLastError() == ERROR_IO_PENDING)
|
|
return S_OK;
|
|
|
|
DBGPRINTF((DBG_CONTEXT, "WriteFile failed, error %d\n",
|
|
GetLastError()));
|
|
return HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT W3_CGI_HANDLER::CGIContinueOnPipeCompletion(
|
|
BOOL *pfIsCgiError)
|
|
/*++
|
|
Synopsis
|
|
|
|
This function continues on I/O completion to a pipe.
|
|
If the I/O completion was because of a write on the pipe, it reads
|
|
the next chunk of request entity, or if there is no more request entity,
|
|
reads the first chunk of CGI output.
|
|
If the I/O completion was because of a read on the pipe, it processes
|
|
that data by either adding it to the header buffer or sending it to the
|
|
client
|
|
|
|
Arguments
|
|
|
|
pfIsCgiError - Tells whether any error occurring inside this function
|
|
was the fault of the CGI or the client
|
|
|
|
Return Value
|
|
|
|
HRESULT
|
|
--*/
|
|
{
|
|
HRESULT hr;
|
|
if (m_dwRequestState == CgiStateProcessingRequestEntity)
|
|
{
|
|
BOOL fIoPending = FALSE;
|
|
if (FAILED(hr = CGIReadRequestEntity(&fIoPending)) ||
|
|
(fIoPending == TRUE))
|
|
{
|
|
*pfIsCgiError = FALSE;
|
|
return hr;
|
|
}
|
|
|
|
//
|
|
// There was no more request entity to read
|
|
//
|
|
DBG_ASSERT(fIoPending == FALSE);
|
|
|
|
//
|
|
// If this is an nph cgi, we do not parse header
|
|
//
|
|
if (QueryIsNphCgi())
|
|
{
|
|
m_dwRequestState = CgiStateProcessingResponseEntity;
|
|
}
|
|
else
|
|
{
|
|
m_dwRequestState = CgiStateProcessingResponseHeaders;
|
|
}
|
|
m_cbData = 0;
|
|
|
|
if (FAILED(hr = CGIReadCGIOutput()))
|
|
{
|
|
*pfIsCgiError = TRUE;
|
|
}
|
|
return hr;
|
|
}
|
|
else if (m_dwRequestState == CgiStateProcessingResponseHeaders)
|
|
{
|
|
if (FAILED(hr = ProcessCGIOutput()))
|
|
{
|
|
*pfIsCgiError = TRUE;
|
|
}
|
|
return hr;
|
|
}
|
|
else
|
|
{
|
|
DBG_ASSERT(m_dwRequestState == CgiStateProcessingResponseEntity);
|
|
|
|
if (FAILED(hr = CGIWriteResponseEntity()))
|
|
{
|
|
*pfIsCgiError = FALSE;
|
|
}
|
|
return hr;
|
|
}
|
|
}
|
|
|
|
HRESULT W3_CGI_HANDLER::CGIStartProcessing()
|
|
/*++
|
|
Synopsis
|
|
|
|
This function kicks off the CGI processing by reading request entity
|
|
if any or reading the first chunk of CGI output
|
|
|
|
Arguments
|
|
|
|
None
|
|
|
|
Return Value
|
|
|
|
HRESULT
|
|
--*/
|
|
{
|
|
DWORD cbAvailableAlready;
|
|
PVOID pbAvailableAlready;
|
|
|
|
m_dwRequestState = CgiStateProcessingRequestEntity;
|
|
|
|
//
|
|
// First we have to write any entity body to the program's stdin
|
|
//
|
|
// Start with the Entity Body already read
|
|
//
|
|
|
|
QueryW3Context()->QueryAlreadyAvailableEntity( &pbAvailableAlready,
|
|
&cbAvailableAlready );
|
|
|
|
m_bytesToReceive = QueryW3Context()->QueryRemainingEntityFromUl();
|
|
|
|
if ( cbAvailableAlready != 0 )
|
|
{
|
|
if (WriteFile(m_hStdIn,
|
|
pbAvailableAlready,
|
|
cbAvailableAlready,
|
|
NULL,
|
|
&m_Overlapped))
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
if (GetLastError() == ERROR_IO_PENDING)
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
DBGPRINTF((DBG_CONTEXT, "WriteFile failed, error %d\n",
|
|
GetLastError()));
|
|
|
|
//
|
|
// If we could not write the request entity, for example because
|
|
// the CGI did not wait to read the entity, ignore the error and
|
|
// continue on to reading the output
|
|
//
|
|
}
|
|
|
|
//
|
|
// Now continue with either reading the rest of the request entity
|
|
// or the CGI response
|
|
//
|
|
BOOL fIsCgiError;
|
|
return CGIContinueOnPipeCompletion(&fIsCgiError);
|
|
}
|
|
|
|
HRESULT W3_CGI_HANDLER::CGIContinueOnClientCompletion()
|
|
{
|
|
if (m_dwRequestState == CgiStateProcessingRequestEntity)
|
|
{
|
|
if (SUCCEEDED(CGIWriteCGIInput()))
|
|
{
|
|
return S_OK;
|
|
}
|
|
|
|
//
|
|
// If we could not write the request entity, for example because
|
|
// the CGI did not wait to read the entity, ignore the error and
|
|
// continue on to reading the output
|
|
//
|
|
|
|
//
|
|
// If this is an nph cgi, we do not parse header
|
|
//
|
|
if (QueryIsNphCgi())
|
|
{
|
|
m_dwRequestState = CgiStateProcessingResponseEntity;
|
|
}
|
|
else
|
|
{
|
|
m_dwRequestState = CgiStateProcessingResponseHeaders;
|
|
}
|
|
m_cbData = 0;
|
|
}
|
|
|
|
return CGIReadCGIOutput();
|
|
}
|
|
|
|
CONTEXT_STATUS W3_CGI_HANDLER::OnCompletion(DWORD cbCompletion,
|
|
DWORD dwCompletionStatus)
|
|
{
|
|
DBG_ASSERT(m_dwRequestState != CgiStateProcessingResponseHeaders);
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
if (dwCompletionStatus)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(dwCompletionStatus);
|
|
DBGPRINTF((DBG_CONTEXT, "Error %d on CGI_HANDLER::OnCompletion\n", dwCompletionStatus));
|
|
}
|
|
|
|
//
|
|
// Is this completion for the entity body preload? If so note the
|
|
// number of bytes and start handling the CGI request
|
|
//
|
|
|
|
if (!m_fEntityBodyPreloadComplete)
|
|
{
|
|
BOOL fComplete = FALSE;
|
|
|
|
//
|
|
// This completion is for entity body preload
|
|
//
|
|
|
|
W3_REQUEST *pRequest = QueryW3Context()->QueryRequest();
|
|
hr = pRequest->PreloadCompletion(QueryW3Context(),
|
|
cbCompletion,
|
|
dwCompletionStatus,
|
|
&fComplete);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (!fComplete)
|
|
{
|
|
return CONTEXT_STATUS_PENDING;
|
|
}
|
|
|
|
m_fEntityBodyPreloadComplete = TRUE;
|
|
|
|
//
|
|
// Finally we can call the CGI
|
|
//
|
|
|
|
return DoWork();
|
|
}
|
|
}
|
|
|
|
//
|
|
// UL can return EOF on the async completion, rather than on the
|
|
// original call (especially on chunked requests), treat them as no error
|
|
//
|
|
if (hr == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF))
|
|
{
|
|
hr = S_OK;
|
|
}
|
|
|
|
W3_RESPONSE *pResponse = QueryW3Context()->QueryResponse();
|
|
DBG_ASSERT(pResponse != NULL);
|
|
|
|
if (SUCCEEDED(hr) &&
|
|
m_dwRequestState != CgiStateDoneWithRequest)
|
|
{
|
|
if (m_dwRequestState == CgiStateProcessingRequestEntity)
|
|
{
|
|
m_bytesToReceive -= cbCompletion;
|
|
m_cbData = cbCompletion;
|
|
}
|
|
|
|
if (SUCCEEDED(hr = CGIContinueOnClientCompletion()))
|
|
{
|
|
return CONTEXT_STATUS_PENDING;
|
|
}
|
|
|
|
if (m_dwRequestState != CgiStateProcessingResponseEntity &&
|
|
m_dwRequestState != CgiStateDoneWithRequest)
|
|
{
|
|
pResponse->SetStatus(HttpStatusBadGateway,
|
|
Http502PrematureExit);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (hr == HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY))
|
|
{
|
|
pResponse->SetStatus(HttpStatusServerError);
|
|
}
|
|
else if ((m_dwRequestState == CgiStateProcessingRequestEntity) ||
|
|
!m_fEntityBodyPreloadComplete)
|
|
{
|
|
pResponse->SetStatus(HttpStatusBadRequest);
|
|
}
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
QueryW3Context()->SetErrorStatus(hr);
|
|
}
|
|
|
|
if (!m_fResponseRedirected &&
|
|
m_dwRequestState != CgiStateProcessingResponseEntity &&
|
|
m_dwRequestState != CgiStateDoneWithRequest)
|
|
{
|
|
//
|
|
// If we reached here, i.e. no response was sent, status should be
|
|
// 502 or 400 or 500
|
|
//
|
|
DBG_ASSERT(pResponse->QueryStatusCode() == HttpStatusBadGateway.statusCode ||
|
|
pResponse->QueryStatusCode() == HttpStatusBadRequest.statusCode ||
|
|
pResponse->QueryStatusCode() == HttpStatusServerError.statusCode );
|
|
|
|
QueryW3Context()->SendResponse(W3_FLAG_SYNC);
|
|
}
|
|
|
|
return CONTEXT_STATUS_CONTINUE;
|
|
}
|
|
|
|
CONTEXT_STATUS
|
|
W3_CGI_HANDLER::DoWork()
|
|
{
|
|
W3_CONTEXT *pW3Context = QueryW3Context();
|
|
DBG_ASSERT( pW3Context != NULL );
|
|
W3_RESPONSE *pResponse;
|
|
W3_REQUEST *pRequest;
|
|
URL_CONTEXT *pUrlContext;
|
|
W3_METADATA *pMetaData;
|
|
HRESULT hr = S_OK;
|
|
STACK_STRU( strSSICommandLine, 256 );
|
|
STRU *pstrPhysical;
|
|
HANDLE hImpersonationToken;
|
|
HANDLE hPrimaryToken;
|
|
DWORD dwFlags = DETACHED_PROCESS;
|
|
STACK_STRU( strCmdLine, 256);
|
|
BOOL fIsCmdExe;
|
|
WCHAR * pszWorkingDir;
|
|
STACK_BUFFER( buffEnv, MAX_CGI_BUFFERING);
|
|
STACK_STRU ( strApplicationName, 256);
|
|
WCHAR * pszCommandLine = NULL;
|
|
DWORD dwFileAttributes = 0;
|
|
BOOL fImageDisabled = FALSE;
|
|
BOOL fIsVrToken;
|
|
|
|
STARTUPINFO startupinfo;
|
|
|
|
pRequest = pW3Context->QueryRequest();
|
|
DBG_ASSERT( pRequest != NULL );
|
|
|
|
pResponse = pW3Context->QueryResponse();
|
|
DBG_ASSERT( pResponse != NULL );
|
|
|
|
ZeroMemory(&startupinfo, sizeof(startupinfo));
|
|
startupinfo.cb = sizeof(startupinfo);
|
|
startupinfo.hStdOutput = INVALID_HANDLE_VALUE;
|
|
startupinfo.hStdInput = INVALID_HANDLE_VALUE;
|
|
|
|
pResponse->SetStatus( HttpStatusOk );
|
|
|
|
if (!m_fEntityBodyPreloadComplete)
|
|
{
|
|
BOOL fComplete = FALSE;
|
|
|
|
hr = pRequest->PreloadEntityBody( pW3Context,
|
|
&fComplete );
|
|
if (FAILED(hr))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (!fComplete)
|
|
{
|
|
return CONTEXT_STATUS_PENDING;
|
|
}
|
|
|
|
m_fEntityBodyPreloadComplete = TRUE;
|
|
}
|
|
|
|
DBG_ASSERT( m_fEntityBodyPreloadComplete );
|
|
|
|
pUrlContext = pW3Context->QueryUrlContext();
|
|
DBG_ASSERT( pUrlContext != NULL );
|
|
|
|
pMetaData = pUrlContext->QueryMetaData();
|
|
DBG_ASSERT( pMetaData != NULL );
|
|
|
|
if (m_pszSSICommandLine == NULL)
|
|
{
|
|
pstrPhysical = pUrlContext->QueryPhysicalPath();
|
|
DBG_ASSERT(pstrPhysical != NULL);
|
|
}
|
|
else
|
|
{
|
|
hr = strSSICommandLine.CopyA(m_pszSSICommandLine);
|
|
if (FAILED(hr))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
pstrPhysical = &strSSICommandLine;
|
|
}
|
|
|
|
hImpersonationToken = pW3Context->QueryImpersonationToken( &fIsVrToken );
|
|
hPrimaryToken = pW3Context->QueryPrimaryToken();
|
|
|
|
if (QueryScriptMapEntry() != NULL &&
|
|
!QueryScriptMapEntry()->QueryIsStarScriptMap())
|
|
{
|
|
STRU *pstrExe = QueryScriptMapEntry()->QueryExecutable();
|
|
|
|
//
|
|
// Check to see if script mapped CGI is enabled.
|
|
//
|
|
|
|
if ( g_pW3Server->QueryIsCgiImageEnabled( pstrExe->QueryStr() ) == FALSE )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"CGI image disabled: %S.\r\n",
|
|
pstrExe->QueryStr() ));
|
|
|
|
hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
|
|
fImageDisabled = TRUE;
|
|
goto Exit;
|
|
}
|
|
|
|
if (wcschr(pstrPhysical->QueryStr(), '\"') != NULL)
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"Refusing request for CGI due to \" in path\n"));
|
|
|
|
hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
|
|
goto Exit;
|
|
}
|
|
|
|
fIsCmdExe = IsCmdExe(pstrExe->QueryStr());
|
|
|
|
STACK_STRU (strDecodedQueryString, MAX_PATH);
|
|
if (FAILED(hr = SetupCmdLine(pRequest, &strDecodedQueryString)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
STACK_BUFFER (bufCmdLine, MAX_PATH);
|
|
DWORD cchLen = pstrExe->QueryCCH() +
|
|
pstrPhysical->QueryCCH() +
|
|
strDecodedQueryString.QueryCCH();
|
|
if (!bufCmdLine.Resize(cchLen*sizeof(WCHAR) + sizeof(WCHAR)))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
goto Exit;
|
|
}
|
|
cchLen = _snwprintf((LPWSTR)bufCmdLine.QueryPtr(),
|
|
cchLen,
|
|
pstrExe->QueryStr(),
|
|
pstrPhysical->QueryStr(),
|
|
strDecodedQueryString.QueryStr());
|
|
|
|
if (FAILED(hr = strCmdLine.Copy((LPWSTR)bufCmdLine.QueryPtr(),
|
|
cchLen)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Check to see if non-script-mapped CGI is enabled
|
|
//
|
|
|
|
if ( g_pW3Server->QueryIsCgiImageEnabled( pstrPhysical->QueryStr() ) == FALSE )
|
|
{
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"CGI image disabled: %S.\r\n",
|
|
pstrPhysical->QueryStr() ));
|
|
|
|
hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND );
|
|
fImageDisabled = TRUE;
|
|
goto Exit;
|
|
}
|
|
|
|
fIsCmdExe = IsCmdExe(pstrPhysical->QueryStr());
|
|
|
|
if (FAILED(hr = strCmdLine.Copy(L"\"", 1)) ||
|
|
FAILED(hr = strCmdLine.Append(*pstrPhysical)) ||
|
|
FAILED(hr = strCmdLine.Append(L"\" ", 2)) ||
|
|
FAILED(hr = SetupCmdLine(pRequest, &strCmdLine)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
if (FAILED(hr = SetupChildEnv(&buffEnv)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
pszWorkingDir = pMetaData->QueryVrPath()->QueryStr();
|
|
|
|
//
|
|
// Check to see if we're spawning cmd.exe, if so, refuse the request if
|
|
// there are any special shell characters. Note we do the check here
|
|
// so that the command line has been fully unescaped
|
|
//
|
|
// Also, if invoking cmd.exe for a UNC script then don't set the
|
|
// working directory. Otherwise cmd.exe will complain about
|
|
// working dir on UNC being not supported, which will destroy the
|
|
// HTTP headers.
|
|
//
|
|
|
|
if (fIsCmdExe)
|
|
{
|
|
if (ISUNC(pstrPhysical->QueryStr()))
|
|
{
|
|
pszWorkingDir = NULL;
|
|
}
|
|
|
|
DWORD i;
|
|
|
|
//
|
|
// We'll either match one of the characters or the '\0'
|
|
//
|
|
|
|
i = (DWORD)wcscspn(strCmdLine.QueryStr(), L"&|(),;%<>");
|
|
|
|
if (strCmdLine.QueryStr()[i])
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"Refusing request for command shell due to special characters\n"));
|
|
|
|
hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER);
|
|
goto Exit;
|
|
}
|
|
|
|
//
|
|
// If this is a cmd.exe invocation, then ensure that the script
|
|
// does exist
|
|
//
|
|
|
|
hr = pW3Context->CheckPathInfoExists(NULL);
|
|
if (FAILED(hr))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now check if it is an nph cgi (if not already)
|
|
//
|
|
|
|
if (!m_fIsNphCgi)
|
|
{
|
|
m_fIsNphCgi = IsNphCgi( pUrlContext->QueryUrlInfo()->QueryProcessedUrl()->QueryStr() );
|
|
}
|
|
|
|
if (m_fIsNphCgi)
|
|
{
|
|
pW3Context->SetDisconnect(TRUE);
|
|
}
|
|
|
|
//
|
|
// We specify an unnamed desktop so a new windowstation will be
|
|
// created in the context of the calling user
|
|
//
|
|
startupinfo.lpDesktop = L"";
|
|
|
|
//
|
|
// Setup the pipes information
|
|
//
|
|
|
|
if (FAILED(hr = SetupChildPipes(&m_hStdOut,
|
|
&m_hStdIn,
|
|
&startupinfo)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
if (pMetaData->QueryCreateProcessNewConsole())
|
|
{
|
|
dwFlags = CREATE_NEW_CONSOLE;
|
|
}
|
|
|
|
//
|
|
// Depending what type of CGI this is (SSI command exec, Scriptmap,
|
|
// Explicit), the command line and application path are different
|
|
//
|
|
|
|
if (m_pszSSICommandLine != NULL )
|
|
{
|
|
pszCommandLine = strSSICommandLine.QueryStr();
|
|
}
|
|
else
|
|
{
|
|
if (QueryScriptMapEntry() == NULL )
|
|
{
|
|
if (FAILED(hr = MakePathCanonicalizationProof(pstrPhysical->QueryStr(),
|
|
&strApplicationName)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
pszCommandLine = strCmdLine.QueryStr();
|
|
}
|
|
|
|
if (!pMetaData->QueryCreateProcessAsUser())
|
|
{
|
|
//
|
|
// If we are not creating the process as user, make sure to fix the
|
|
// default ACL on the token
|
|
//
|
|
if ((fIsVrToken && !pW3Context->QueryVrToken()->QueryOOPToken()) ||
|
|
(!fIsVrToken && !pW3Context->QueryUserContext()->QueryIsCachedToken()) ||
|
|
(!fIsVrToken && !pW3Context->QueryUserContext()->QueryCachedToken()->QueryOOPToken()))
|
|
{
|
|
if (FAILED(hr = AddWpgToTokenDefaultDacl(hImpersonationToken)))
|
|
{
|
|
goto Exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Spawn the process and close the handles since we don't need them
|
|
//
|
|
|
|
if (!SetThreadToken(NULL, hImpersonationToken))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"SetThreadToken failed, error %d\n",
|
|
GetLastError()));
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
goto Exit;
|
|
}
|
|
|
|
PROCESS_INFORMATION processinfo;
|
|
|
|
BOOL fThreadsIncremented = FALSE;
|
|
if (QueryScriptMapEntry() == NULL)
|
|
{
|
|
if (ISUNC(pstrPhysical->QueryStr()))
|
|
{
|
|
ThreadPoolSetInfo( ThreadPoolIncMaxPoolThreads, 0 );
|
|
fThreadsIncremented = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ISUNC(pszCommandLine))
|
|
{
|
|
ThreadPoolSetInfo( ThreadPoolIncMaxPoolThreads, 0 );
|
|
fThreadsIncremented = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!pMetaData->QueryCreateProcessAsUser())
|
|
{
|
|
if (!CreateProcess(strApplicationName.QueryCCH() ? strApplicationName.QueryStr() : NULL,
|
|
pszCommandLine,
|
|
NULL, // Process security
|
|
NULL, // Thread security
|
|
TRUE, // Inherit handles
|
|
dwFlags | CREATE_UNICODE_ENVIRONMENT,
|
|
buffEnv.QueryPtr(),
|
|
pszWorkingDir,
|
|
&startupinfo,
|
|
&processinfo))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"CreateProcess failed, error %d\n",
|
|
GetLastError()));
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!CreateProcessAsUser(hPrimaryToken,
|
|
strApplicationName.QueryCCH() ? strApplicationName.QueryStr() : NULL,
|
|
pszCommandLine,
|
|
NULL, // Process security
|
|
NULL, // Thread security
|
|
TRUE, // Inherit handles
|
|
dwFlags | CREATE_UNICODE_ENVIRONMENT,
|
|
buffEnv.QueryPtr(),
|
|
pszWorkingDir,
|
|
&startupinfo,
|
|
&processinfo))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"CreateProcessAsUser failed, error %d\n",
|
|
GetLastError()));
|
|
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
}
|
|
|
|
if (fThreadsIncremented)
|
|
{
|
|
ThreadPoolSetInfo( ThreadPoolDecMaxPoolThreads, 0 );
|
|
}
|
|
|
|
DBG_REQUIRE(RevertToSelf());
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
goto Exit;
|
|
}
|
|
|
|
DBG_REQUIRE(CloseHandle(startupinfo.hStdInput));
|
|
startupinfo.hStdInput = INVALID_HANDLE_VALUE;
|
|
DBG_REQUIRE(CloseHandle(startupinfo.hStdOutput));
|
|
startupinfo.hStdOutput = INVALID_HANDLE_VALUE;
|
|
|
|
DBG_REQUIRE(CloseHandle(processinfo.hThread));
|
|
//
|
|
// Save the process handle in case we need to terminate it later on
|
|
//
|
|
m_hProcess = processinfo.hProcess;
|
|
|
|
//
|
|
// Schedule a callback to kill the process if it doesn't die
|
|
// in a timely manner
|
|
//
|
|
if (!CreateTimerQueueTimer(&m_hTimer,
|
|
NULL,
|
|
CGITerminateProcess,
|
|
this,
|
|
pMetaData->QueryScriptTimeout() * 1000,
|
|
0,
|
|
WT_EXECUTEONLYONCE))
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"CreateTimerQueueTimer failed, error %d\n",
|
|
GetLastError()));
|
|
}
|
|
|
|
if (SUCCEEDED(hr = CGIStartProcessing()))
|
|
{
|
|
return CONTEXT_STATUS_PENDING;
|
|
}
|
|
|
|
Exit:
|
|
if (startupinfo.hStdInput != INVALID_HANDLE_VALUE)
|
|
{
|
|
DBG_REQUIRE(CloseHandle(startupinfo.hStdInput));
|
|
startupinfo.hStdInput = INVALID_HANDLE_VALUE;
|
|
}
|
|
if (startupinfo.hStdOutput != INVALID_HANDLE_VALUE)
|
|
{
|
|
DBG_REQUIRE(CloseHandle(startupinfo.hStdOutput));
|
|
startupinfo.hStdOutput = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
switch (WIN32_FROM_HRESULT(hr))
|
|
{
|
|
case ERROR_FILE_NOT_FOUND:
|
|
case ERROR_PATH_NOT_FOUND:
|
|
case ERROR_INVALID_NAME:
|
|
|
|
if (fImageDisabled)
|
|
{
|
|
pResponse->SetStatus(HttpStatusNotFound,
|
|
Http404DeniedByPolicy);
|
|
}
|
|
else
|
|
{
|
|
pResponse->SetStatus(HttpStatusNotFound);
|
|
}
|
|
break;
|
|
|
|
case ERROR_ACCESS_DENIED:
|
|
case ERROR_ACCOUNT_DISABLED:
|
|
case ERROR_LOGON_FAILURE:
|
|
pResponse->SetStatus(HttpStatusUnauthorized,
|
|
Http401Resource);
|
|
break;
|
|
|
|
case ERROR_PRIVILEGE_NOT_HELD:
|
|
pResponse->SetStatus(HttpStatusForbidden,
|
|
Http403InsufficientPrivilegeForCgi);
|
|
break;
|
|
|
|
case ERROR_NOT_ENOUGH_MEMORY:
|
|
pResponse->SetStatus(HttpStatusServerError);
|
|
break;
|
|
|
|
default:
|
|
//
|
|
// If we were not able to preload the request entity, we will
|
|
// blame the client for it
|
|
//
|
|
if (!m_fEntityBodyPreloadComplete)
|
|
{
|
|
pResponse->SetStatus(HttpStatusBadRequest);
|
|
}
|
|
else
|
|
{
|
|
pResponse->SetStatus(HttpStatusServerError);
|
|
}
|
|
}
|
|
|
|
if ( fImageDisabled )
|
|
{
|
|
pW3Context->SetErrorStatus( ERROR_ACCESS_DISABLED_BY_POLICY );
|
|
}
|
|
else
|
|
{
|
|
pW3Context->SetErrorStatus(hr);
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we reached here, there was some error, response should not be 200
|
|
//
|
|
DBG_ASSERT(pResponse->QueryStatusCode() != HttpStatusOk.statusCode);
|
|
|
|
m_dwRequestState = CgiStateDoneWithRequest;
|
|
if (FAILED(hr = pW3Context->SendResponse(W3_FLAG_ASYNC)))
|
|
{
|
|
return CONTEXT_STATUS_CONTINUE;
|
|
}
|
|
|
|
return CONTEXT_STATUS_PENDING;
|
|
}
|
|
|
|
// static
|
|
VOID W3_CGI_HANDLER::KillAllCgis()
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT, "W3_CGI_HANDLER::KillAllCgis() called\n"));
|
|
|
|
//
|
|
// Kill all outstanding processes
|
|
//
|
|
|
|
EnterCriticalSection(&sm_CgiListLock);
|
|
|
|
for (LIST_ENTRY *pEntry = sm_CgiListHead.Flink;
|
|
pEntry != &sm_CgiListHead;
|
|
pEntry = pEntry->Flink)
|
|
{
|
|
W3_CGI_HANDLER *pCgi = CONTAINING_RECORD(pEntry,
|
|
W3_CGI_HANDLER,
|
|
m_CgiListEntry);
|
|
|
|
CGITerminateProcess(pCgi, 0);
|
|
}
|
|
|
|
LeaveCriticalSection(&sm_CgiListLock);
|
|
}
|
|
|
|
// static
|
|
VOID W3_CGI_HANDLER::Terminate()
|
|
{
|
|
DBGPRINTF((DBG_CONTEXT, "W3_CGI_HANDLER::Terminate() called\n"));
|
|
|
|
if (sm_pEnvString != NULL)
|
|
{
|
|
delete sm_pEnvString;
|
|
sm_pEnvString = NULL;
|
|
}
|
|
|
|
DeleteCriticalSection(&sm_CgiListLock);
|
|
}
|
|
|