diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/application.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/application.h new file mode 100644 index 0000000000..e0ef14ec9c --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/application.h @@ -0,0 +1,293 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +// +// The key used for hash-table lookups, consists of the port on which the http process is created. +// +class APPLICATION_KEY +{ +public: + + APPLICATION_KEY( + VOID + ) : INLINE_STRU_INIT(m_struKey) + { + } + + HRESULT + Initialize( + _In_ LPCWSTR pszKey + ) + { + return m_struKey.Copy(pszKey); + } + + BOOL + GetIsEqual( + const APPLICATION_KEY * key2 + ) const + { + return m_struKey.Equals(key2->m_struKey); + } + + DWORD CalcKeyHash() const + { + return Hash(m_struKey.QueryStr()); + } + +private: + + INLINE_STRU(m_struKey, 1024); +}; + +class APP_OFFLINE_HTM +{ +public: + APP_OFFLINE_HTM(LPCWSTR pszPath) : m_cRefs(1) + { + m_Path.Copy( pszPath ); + } + + VOID + ReferenceAppOfflineHtm() const + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceAppOfflineHtm() const + { + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + BOOL + Load( + VOID + ) + { + BOOL fResult = TRUE; + LARGE_INTEGER li = {0}; + CHAR *pszBuff = NULL; + HANDLE handle = INVALID_HANDLE_VALUE; + + handle = CreateFile( m_Path.QueryStr(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL ); + + if( handle == INVALID_HANDLE_VALUE ) + { + if ( GetLastError() == ERROR_FILE_NOT_FOUND ) + { + fResult = FALSE; + } + + // This Load() member function is supposed be called only when the change notification event of file creation or file modification happens. + // If file is currenlty locked exclusively by other processes, we might get INVALID_HANDLE_VALUE even though the file exists. In that case, we should return TRUE here. + goto Finished; + } + + if(!GetFileSizeEx( handle, &li )) + { + goto Finished; + } + + if( li.HighPart != 0 ) + { + // > 4gb file size not supported + // todo: log a warning at event log + goto Finished; + } + + DWORD bytesRead = 0; + + if(li.LowPart > 0) + { + pszBuff = new CHAR[ li.LowPart + 1 ]; + + if( ReadFile( handle, pszBuff, li.LowPart, &bytesRead, NULL ) ) + { + m_Contents.Copy( pszBuff, bytesRead ); + } + } + +Finished: + if( handle != INVALID_HANDLE_VALUE ) + { + CloseHandle(handle); + handle = INVALID_HANDLE_VALUE; + } + + if( pszBuff != NULL ) + { + delete[] pszBuff; + pszBuff = NULL; + } + + return fResult; + } + + mutable LONG m_cRefs; + STRA m_Contents; + STRU m_Path; +}; + +class APPLICATION_MANAGER; + +class APPLICATION +{ +public: + + APPLICATION() : m_pProcessManager(NULL), m_pApplicationManager(NULL), m_cRefs(1), + m_fAppOfflineFound(FALSE), m_pAppOfflineHtm(NULL), m_pFileWatcherEntry(NULL) + { + } + + APPLICATION_KEY * + QueryApplicationKey() + { + return &m_applicationKey; + } + + VOID + SetAppOfflineFound( + BOOL found + ) + { + m_fAppOfflineFound = found; + } + + BOOL + AppOfflineFound() + { + return m_fAppOfflineFound; + } + + HRESULT + GetProcess( + _In_ IHttpContext *context, + _In_ ASPNETCORE_CONFIG *pConfig, + _Out_ SERVER_PROCESS **ppServerProcess + ) + { + return m_pProcessManager->GetProcess( context, pConfig, ppServerProcess ); + } + + HRESULT + Recycle() + { + HRESULT hr = S_OK; + m_pProcessManager->ShutdownAllProcesses(); + return hr; + } + + VOID + ReferenceApplication() const + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceApplication() const + { + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + APP_OFFLINE_HTM* QueryAppOfflineHtm() + { + return m_pAppOfflineHtm; + } + + ~APPLICATION(); + + HRESULT + Initialize( + _In_ APPLICATION_MANAGER *pApplicationManager, + _In_ LPCWSTR pszApplication, + _In_ LPCWSTR pszPhysicalPath + ); + + VOID + UpdateAppOfflineFileHandle(); + + HRESULT + StartMonitoringAppOffline(); + +private: + + STRU m_strAppPhysicalPath; + mutable LONG m_cRefs; + APPLICATION_KEY m_applicationKey; + PROCESS_MANAGER* m_pProcessManager; + APPLICATION_MANAGER *m_pApplicationManager; + BOOL m_fAppOfflineFound; + APP_OFFLINE_HTM *m_pAppOfflineHtm; + FILE_WATCHER_ENTRY *m_pFileWatcherEntry; +}; + +class APPLICATION_HASH : + public HASH_TABLE +{ + +public: + + APPLICATION_HASH() + {} + + APPLICATION_KEY * + ExtractKey( + APPLICATION *pApplication + ) + { + return pApplication->QueryApplicationKey(); + } + + DWORD + CalcKeyHash( + APPLICATION_KEY *key + ) + { + return key->CalcKeyHash(); + } + + BOOL + EqualKeys( + APPLICATION_KEY *key1, + APPLICATION_KEY *key2 + ) + { + return key1->GetIsEqual(key2); + } + + VOID + ReferenceRecord( + APPLICATION *pApplication + ) + { + pApplication->ReferenceApplication(); + } + + VOID + DereferenceRecord( + APPLICATION *pApplication + ) + { + pApplication->DereferenceApplication(); + } + +private: + + APPLICATION_HASH(const APPLICATION_HASH &); + void operator=(const APPLICATION_HASH &); +}; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/applicationmanager.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/applicationmanager.h new file mode 100644 index 0000000000..75df270fc1 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/applicationmanager.h @@ -0,0 +1,158 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define DEFAULT_HASH_BUCKETS 293 + +class APPLICATION_MANAGER +{ +public: + + static + APPLICATION_MANAGER* + GetInstance( + VOID + ) + { + if( sm_pApplicationManager == NULL ) + { + sm_pApplicationManager = new APPLICATION_MANAGER(); + } + + return sm_pApplicationManager; + } + + static + VOID + Cleanup( + VOID + ) + { + if(sm_pApplicationManager != NULL) + { + delete sm_pApplicationManager; + sm_pApplicationManager = NULL; + } + } + + HRESULT + GetApplication( + _In_ IHttpContext* pContext, + _Out_ APPLICATION ** ppApplication + ); + + HRESULT + RecycleApplication( + _In_ LPCWSTR pszApplication + ); + + HRESULT + Get502ErrorPage( + _Out_ HTTP_DATA_CHUNK** ppErrorPage + ); + + ~APPLICATION_MANAGER() + { + if(m_pApplicationHash != NULL) + { + m_pApplicationHash->Clear(); + delete m_pApplicationHash; + m_pApplicationHash = NULL; + } + + if( m_pFileWatcher!= NULL ) + { + delete m_pFileWatcher; + m_pFileWatcher = NULL; + } + + if(m_pHttp502ErrorPage != NULL) + { + delete m_pHttp502ErrorPage; + m_pHttp502ErrorPage = NULL; + } + + } + + FILE_WATCHER* + GetFileWatcher() + { + return m_pFileWatcher; + } + + HRESULT Initialize() + { + HRESULT hr = S_OK; + + if(m_pApplicationHash == NULL) + { + m_pApplicationHash = new APPLICATION_HASH(); + if(m_pApplicationHash == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = m_pApplicationHash->Initialize(DEFAULT_HASH_BUCKETS); + if(FAILED(hr)) + { + goto Finished; + } + } + + if( m_pFileWatcher == NULL ) + { + m_pFileWatcher = new FILE_WATCHER; + if(m_pFileWatcher == NULL) + { + hr = HRESULT_FROM_WIN32( ERROR_NOT_ENOUGH_MEMORY ); + goto Finished; + } + + m_pFileWatcher->Create(); + } + + Finished: + return hr; + } + +private: + // + // we currently limit the size of m_pstrErrorInfo to 5000, be careful if you want to change its payload + // + APPLICATION_MANAGER() : m_pApplicationHash(NULL), m_pFileWatcher(NULL), m_pHttp502ErrorPage(NULL), m_pstrErrorInfo( + " \ + \ + \ + \ + IIS 502.5 Error \ +
\ +

HTTP Error 502.5 - Process Failure

\ +
\ +

Common causes of this issue:

\ +
  • The application process failed to start
  • \ +
  • The application process started but then stopped
  • \ +
  • The application process started but failed to listen on the configured port
\ +
\ +
\ +

Troubleshooting steps:

\ +
  • Check the system event log for error messages
  • \ +
  • Enable logging the application process' stdout messages
  • \ +
  • Attach a debugger to the application process and inspect
\ +

For more information visit: \ + https://go.microsoft.com/fwlink/?LinkID=808681

\ +
\ +
\ +
") + { + InitializeSRWLock(&m_srwLock); + } + + FILE_WATCHER *m_pFileWatcher; + APPLICATION_HASH *m_pApplicationHash; + static APPLICATION_MANAGER *sm_pApplicationManager; + SRWLOCK m_srwLock; + HTTP_DATA_CHUNK *m_pHttp502ErrorPage; + LPSTR m_pstrErrorInfo; +}; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/aspnetcore_msg.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/aspnetcore_msg.h new file mode 100644 index 0000000000..2dad31d686 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/aspnetcore_msg.h @@ -0,0 +1,127 @@ +/*++ + +Copyright (c) 2014 Microsoft Corporation + +Module Name: + +aspnetcore_msg.mc + +Abstract: + +Asp.Net Core Module localizable messages. + +--*/ + + +#ifndef _ASPNETCORE_MSG_H_ +#define _ASPNETCORE_MSG_H_ + +// +// Values are 32 bit values laid out as follows: +// +// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +// +---+-+-+-----------------------+-------------------------------+ +// |Sev|C|R| Facility | Code | +// +---+-+-+-----------------------+-------------------------------+ +// +// where +// +// Sev - is the severity code +// +// 00 - Success +// 01 - Informational +// 10 - Warning +// 11 - Error +// +// C - is the Customer code flag +// +// R - is a reserved bit +// +// Facility - is the facility code +// +// Code - is the facility's status code +// +// +// Define the facility codes +// + + +// +// Define the severity codes +// + + +// +// MessageId: ASPNETCORE_EVENT_PROCESS_START_ERROR +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_PROCESS_START_ERROR ((DWORD)0x000003E8L) + +// +// MessageId: ASPNETCORE_EVENT_PROCESS_START_SUCCESS +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_PROCESS_START_SUCCESS ((DWORD)0x000003E9L) + +// +// MessageId: ASPNETCORE_EVENT_PROCESS_CRASH +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_PROCESS_CRASH ((DWORD)0x000003EAL) + +// +// MessageId: ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED ((DWORD)0x000003EBL) + +// +// MessageId: ASPNETCORE_EVENT_CONFIG_ERROR +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_CONFIG_ERROR ((DWORD)0x000003ECL) + +// +// MessageId: ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE ((DWORD)0x000003EDL) + +// +// MessageId: ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST ((DWORD)0x000003EEL) + +// +// MessageId: ASPNETCORE_EVENT_RECYCLE_APPOFFLINE +// +// MessageText: +// +// %1 +// +#define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE ((DWORD)0x000003F4L) + +#endif // _ASPNETCORE_MODULE_MSG_H_ diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/aspnetcoreconfig.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/aspnetcoreconfig.h new file mode 100644 index 0000000000..95b4303cee --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/aspnetcoreconfig.h @@ -0,0 +1,207 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#define CS_ROOTWEB_CONFIG L"MACHINE/WEBROOT/APPHOST/" +#define CS_ROOTWEB_CONFIG_LEN _countof(CS_ROOTWEB_CONFIG)-1 +#define CS_ASPNETCORE_SECTION L"system.webServer/aspNetCore" +#define CS_WINDOWS_AUTHENTICATION_SECTION L"system.webServer/security/authentication/windowsAuthentication" +#define CS_BASIC_AUTHENTICATION_SECTION L"system.webServer/security/authentication/basicAuthentication" +#define CS_ANONYMOUS_AUTHENTICATION_SECTION L"system.webServer/security/authentication/anonymousAuthentication" +#define CS_AUTHENTICATION_ENABLED L"enabled" +#define CS_ASPNETCORE_PROCESS_EXE_PATH L"processPath" +#define CS_ASPNETCORE_PROCESS_ARGUMENTS L"arguments" +#define CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT L"startupTimeLimit" +#define CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT L"shutdownTimeLimit" +#define CS_ASPNETCORE_WINHTTP_REQUEST_TIMEOUT L"requestTimeout" +#define CS_ASPNETCORE_RAPID_FAILS_PER_MINUTE L"rapidFailsPerMinute" +#define CS_ASPNETCORE_STDOUT_LOG_ENABLED L"stdoutLogEnabled" +#define CS_ASPNETCORE_STDOUT_LOG_FILE L"stdoutLogFile" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLES L"environmentVariables" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLE L"environmentVariable" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLE_NAME L"name" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLE_VALUE L"value" +#define CS_ASPNETCORE_PROCESSES_PER_APPLICATION L"processesPerApplication" +#define CS_ASPNETCORE_FORWARD_WINDOWS_AUTH_TOKEN L"forwardWindowsAuthToken" +#define CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE L"disableStartUpErrorPage" +#define CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE L"recycleOnFileChange" +#define CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE_FILE L"file" +#define CS_ASPNETCORE_RECYCLE_ON_FILE_CHANGE_FILE_PATH L"path" + +#define MAX_RAPID_FAILS_PER_MINUTE 100 +#define MILLISECONDS_IN_ONE_SECOND 1000 +#define MIN_PORT 1025 +#define MAX_PORT 48000 + +#define HEX_TO_ASCII(c) ((CHAR)(((c) < 10) ? ((c) + '0') : ((c) + 'a' - 10))) + +extern HTTP_MODULE_ID g_pModuleId; +extern IHttpServer * g_pHttpServer; + +class ASPNETCORE_CONFIG : IHttpStoredContext +{ +public: + + virtual + ~ASPNETCORE_CONFIG(); + + VOID + CleanupStoredContext() + { + delete this; + } + + static + HRESULT + GetConfig( + _In_ IHttpContext *pHttpContext, + _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig + ); + + ENVIRONMENT_VAR_HASH* + QueryEnvironmentVariables( + VOID + ) + { + return m_pEnvironmentVariables; + } + + DWORD + QueryRapidFailsPerMinute( + VOID + ) + { + return m_dwRapidFailsPerMinute; + } + + DWORD + QueryStartupTimeLimitInMS( + VOID + ) + { + return m_dwStartupTimeLimitInMS; + } + + DWORD + QueryShutdownTimeLimitInMS( + VOID + ) + { + return m_dwShutdownTimeLimitInMS; + } + + DWORD + QueryProcessesPerApplication( + VOID + ) + { + return m_dwProcessesPerApplication; + } + + DWORD + QueryRequestTimeoutInMS( + VOID + ) + { + return m_dwRequestTimeoutInMS; + } + + STRU* + QueryArguments( + VOID + ) + { + return &m_struArguments; + } + + STRU* + QueryApplicationPath( + VOID + ) + { + return &m_struApplication; + } + + STRU* + QueryProcessPath( + VOID + ) + { + return &m_struProcessPath; + } + + BOOL + QueryStdoutLogEnabled() + { + return m_fStdoutLogEnabled; + } + + BOOL + QueryForwardWindowsAuthToken() + { + return m_fForwardWindowsAuthToken; + } + + BOOL + QueryWindowsAuthEnabled() + { + return m_fWindowsAuthEnabled; + } + + BOOL + QueryBasicAuthEnabled() + { + return m_fBasicAuthEnabled; + } + + BOOL + QueryAnonymousAuthEnabled() + { + return m_fAnonymousAuthEnabled; + } + + BOOL + QueryDisableStartUpErrorPage() + { + return m_fDisableStartUpErrorPage; + } + + STRU* + QueryStdoutLogFile() + { + return &m_struStdoutLogFile; + } + +private: + + // + // private constructor + // + ASPNETCORE_CONFIG(): + m_fStdoutLogEnabled( FALSE ), + m_pEnvironmentVariables( NULL ) + { + } + + HRESULT + Populate( + IHttpContext *pHttpContext + ); + + DWORD m_dwRequestTimeoutInMS; + DWORD m_dwStartupTimeLimitInMS; + DWORD m_dwShutdownTimeLimitInMS; + DWORD m_dwRapidFailsPerMinute; + DWORD m_dwProcessesPerApplication; + STRU m_struApplication; + STRU m_struArguments; + STRU m_struProcessPath; + STRU m_struStdoutLogFile; + BOOL m_fStdoutLogEnabled; + BOOL m_fForwardWindowsAuthToken; + BOOL m_fDisableStartUpErrorPage; + BOOL m_fWindowsAuthEnabled; + BOOL m_fBasicAuthEnabled; + BOOL m_fAnonymousAuthEnabled; + ENVIRONMENT_VAR_HASH* m_pEnvironmentVariables; +}; diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/debugutil.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/debugutil.h new file mode 100644 index 0000000000..7378462efb --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/debugutil.h @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define ASPNETCORE_DEBUG_FLAG_INFO 0x00000001 +#define ASPNETCORE_DEBUG_FLAG_WARNING 0x00000002 +#define ASPNETCORE_DEBUG_FLAG_ERROR 0x00000004 + +extern DWORD g_dwAspNetCoreDebugFlags; + +static +BOOL +IfDebug( + DWORD dwFlag + ) +{ + return ( dwFlag & g_dwAspNetCoreDebugFlags ); +} + +static +VOID +DebugPrint( + DWORD dwFlag, + LPCSTR szString + ) +{ + STACK_STRA (strOutput, 256); + HRESULT hr = S_OK; + + if ( IfDebug( dwFlag ) ) + { + hr = strOutput.SafeSnprintf( + "[aspnetcore.dll] %s\r\n", + szString ); + + if (FAILED (hr)) + { + goto Finished; + } + + OutputDebugStringA( strOutput.QueryStr() ); + } + +Finished: + + return; +} + +static +VOID +DebugPrintf( +DWORD dwFlag, +LPCSTR szFormat, +... +) +{ + STACK_STRA (strCooked,256); + + va_list args; + HRESULT hr = S_OK; + + if ( IfDebug( dwFlag ) ) + { + va_start( args, szFormat ); + + hr = strCooked.SafeVsnprintf(szFormat, args ); + + va_end( args ); + + if (FAILED (hr)) + { + goto Finished; + } + + DebugPrint( dwFlag, strCooked.QueryStr() ); + } + +Finished: + return; +} + diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/environmentvariablehash.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/environmentvariablehash.h new file mode 100644 index 0000000000..062090ac17 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/environmentvariablehash.h @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +// +// The key used for hash-table lookups, consists of the port on which the http process is created. +// + +class ENVIRONMENT_VAR_ENTRY +{ +public: + ENVIRONMENT_VAR_ENTRY(): + _cRefs(1) + { + } + + HRESULT + Initialize( + PCWSTR pszName, + PCWSTR pszValue + ) + { + HRESULT hr = S_OK; + if (FAILED(hr = _strName.Copy(pszName)) || + FAILED(hr = _strValue.Copy(pszValue))) + { + } + return hr; + } + + VOID + Reference() const + { + InterlockedIncrement(&_cRefs); + } + + VOID + Dereference() const + { + if (InterlockedDecrement(&_cRefs) == 0) + { + delete this; + } + } + + PWSTR const + QueryName() + { + return _strName.QueryStr(); + } + + PWSTR const + QueryValue() + { + return _strValue.QueryStr(); + } + +private: + ~ENVIRONMENT_VAR_ENTRY() + { + } + + STRU _strName; + STRU _strValue; + mutable LONG _cRefs; +}; + +class ENVIRONMENT_VAR_HASH : public HASH_TABLE +{ +public: + ENVIRONMENT_VAR_HASH() + {} + + PWSTR + ExtractKey( + ENVIRONMENT_VAR_ENTRY * pEntry + ) + { + return pEntry->QueryName(); + } + + DWORD + CalcKeyHash( + PWSTR pszName + ) + { + return HashStringNoCase(pszName); + } + + BOOL + EqualKeys( + PWSTR pszName1, + PWSTR pszName2 + ) + { + return (_wcsicmp(pszName1, pszName2) == 0); + } + + VOID + ReferenceRecord( + ENVIRONMENT_VAR_ENTRY * pEntry + ) + { + pEntry->Reference(); + } + + VOID + DereferenceRecord( + ENVIRONMENT_VAR_ENTRY * pEntry + ) + { + pEntry->Dereference(); + } + + static + VOID + CopyToMultiSz( + ENVIRONMENT_VAR_ENTRY * pEntry, + PVOID pvData + ) + { + STRU strTemp; + MULTISZ *pMultiSz = static_cast(pvData); + DBG_ASSERT(pMultiSz); + DBG_ASSERT(pEntry); + strTemp.Copy(pEntry->QueryName()); + strTemp.Append(pEntry->QueryValue()); + pMultiSz->Append(strTemp.QueryStr()); + } + + static + VOID + CopyToTable( + ENVIRONMENT_VAR_ENTRY * pEntry, + PVOID pvData + ) + { + // best effort copy, ignore the failure + ENVIRONMENT_VAR_ENTRY * pNewEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pNewEntry != NULL) + { + pNewEntry->Initialize(pEntry->QueryName(), pEntry->QueryValue()); + ENVIRONMENT_VAR_HASH *pHash = static_cast(pvData); + DBG_ASSERT(pHash); + pHash->InsertRecord(pNewEntry); + // Need to dereference as InsertRecord references it now + pNewEntry->Dereference(); + } + } + +private: + ENVIRONMENT_VAR_HASH(const ENVIRONMENT_VAR_HASH &); + void operator=(const ENVIRONMENT_VAR_HASH &); +}; diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/filewatcher.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/filewatcher.h new file mode 100644 index 0000000000..16d3942a2f --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/filewatcher.h @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define FILE_WATCHER_SHUTDOWN_KEY (ULONG_PTR)(-1) +#define FILE_WATCHER_ENTRY_BUFFER_SIZE 4096 +#ifndef CONTAINING_RECORD +// +// Calculate the address of the base of the structure given its type, and an +// address of a field within the structure. +// + +#define CONTAINING_RECORD(address, type, field) \ + ((type *)((PCHAR)(address)-(ULONG_PTR)(&((type *)0)->field))) + +#endif // !CONTAINING_RECORD +#define FILE_NOTIFY_VALID_MASK 0x00000fff +#define FILE_WATCHER_ENTRY_SIGNATURE ((DWORD) 'FWES') +#define FILE_WATCHER_ENTRY_SIGNATURE_FREE ((DWORD) 'sewf') + +class APPLICATION; + +class FILE_WATCHER{ +public: + + FILE_WATCHER(); + + ~FILE_WATCHER(); + + HRESULT Create(); + + HANDLE + QueryCompletionPort( + VOID + ) const + { + return m_hCompletionPort; + } + + static + DWORD + WINAPI ChangeNotificationThread(LPVOID); + + static + void + WINAPI FileWatcherCompletionRoutine + ( + DWORD dwCompletionStatus, + DWORD cbCompletion, + OVERLAPPED * pOverlapped + ); + +private: + HANDLE m_hCompletionPort; + HANDLE m_hChangeNotificationThread; +}; + +class FILE_WATCHER_ENTRY +{ +public: + FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor); + + OVERLAPPED _overlapped; + + HRESULT + Create( + _In_ PCWSTR pszDirectoryToMonitor, + _In_ PCWSTR pszFileNameToMonitor, + _In_ APPLICATION* pApplication, + _In_ HANDLE hImpersonationToken + ); + + VOID + ReferenceFileWatcherEntry() const + { + InterlockedIncrement(&_cRefs); + } + + VOID + DereferenceFileWatcherEntry() const + { + if (InterlockedDecrement(&_cRefs) == 0) + { + delete this; + } + } + + BOOL + QueryIsValid() const + { + return _fIsValid; + } + + VOID + MarkEntryInValid() + { + _fIsValid = FALSE; + } + + HRESULT Monitor(); + + VOID StopMonitor(); + + HRESULT + HandleChangeCompletion( + _In_ DWORD dwCompletionStatus, + _In_ DWORD cbCompletion + ); + +private: + virtual ~FILE_WATCHER_ENTRY(); + + DWORD _dwSignature; + BUFFER _buffDirectoryChanges; + HANDLE _hImpersonationToken; + HANDLE _hDirectory; + FILE_WATCHER* _pFileMonitor; + APPLICATION* _pApplication; + STRU _strFileName; + STRU _strDirectoryName; + LONG _lStopMonitorCalled; + mutable LONG _cRefs; + BOOL _fIsValid; + SRWLOCK _srwLock; +}; diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/forwarderconnection.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/forwarderconnection.h new file mode 100644 index 0000000000..a3f5dfdabe --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/forwarderconnection.h @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +// +// The key used for hash-table lookups, consists of the port on which the http process is created. +// +class FORWARDER_CONNECTION_KEY +{ +public: + + FORWARDER_CONNECTION_KEY( + VOID + ) + { + } + + HRESULT + Initialize( + _In_ DWORD dwPort + ) + { + m_dwPort = dwPort; + return S_OK; + } + + BOOL + GetIsEqual( + const FORWARDER_CONNECTION_KEY * key2 + ) const + { + return m_dwPort == key2->m_dwPort; + } + + DWORD CalcKeyHash() const + { + // TODO: Review hash distribution. + return Hash(m_dwPort); + } + +private: + + DWORD m_dwPort; +}; + +class FORWARDER_CONNECTION +{ +public: + + FORWARDER_CONNECTION( + VOID + ); + + HRESULT + Initialize( + DWORD dwPort + ); + + HINTERNET + QueryHandle() const + { + return m_hConnection; + } + + VOID + ReferenceForwarderConnection() const + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceForwarderConnection() const + { + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + FORWARDER_CONNECTION_KEY * + QueryConnectionKey() + { + return &m_ConnectionKey; + } + +private: + + ~FORWARDER_CONNECTION() + { + if (m_hConnection != NULL) + { + WinHttpCloseHandle(m_hConnection); + m_hConnection = NULL; + } + } + + mutable LONG m_cRefs; + FORWARDER_CONNECTION_KEY m_ConnectionKey; + HINTERNET m_hConnection; +}; + +class FORWARDER_CONNECTION_HASH : + public HASH_TABLE +{ + +public: + + FORWARDER_CONNECTION_HASH() + {} + + FORWARDER_CONNECTION_KEY * + ExtractKey( + FORWARDER_CONNECTION *pConnection + ) + { + return pConnection->QueryConnectionKey(); + } + + DWORD + CalcKeyHash( + FORWARDER_CONNECTION_KEY *key + ) + { + return key->CalcKeyHash(); + } + + BOOL + EqualKeys( + FORWARDER_CONNECTION_KEY *key1, + FORWARDER_CONNECTION_KEY *key2 + ) + { + return key1->GetIsEqual(key2); + } + + VOID + ReferenceRecord( + FORWARDER_CONNECTION *pConnection + ) + { + pConnection->ReferenceForwarderConnection(); + } + + VOID + DereferenceRecord( + FORWARDER_CONNECTION *pConnection + ) + { + pConnection->DereferenceForwarderConnection(); + } + +private: + + FORWARDER_CONNECTION_HASH(const FORWARDER_CONNECTION_HASH &); + void operator=(const FORWARDER_CONNECTION_HASH &); +}; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/forwardinghandler.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/forwardinghandler.h new file mode 100644 index 0000000000..10caa3bd84 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/forwardinghandler.h @@ -0,0 +1,479 @@ +#pragma once + +/*++ + +Copyright (c) 2013 Microsoft Corporation + +Module Name: + + forwardinghandler.h + +Abstract: + + Handler for handling URLs from out-of-box. + +--*/ + +#include "forwarderconnection.h" +#include "protocolconfig.h" +#include "serverprocess.h" +#include "application.h" +#include "tracelog.h" +#include "websockethandler.h" + +#define ASPNETCORE_DEBUG_STRU_BUFFER_SIZE 100 +#define ASPNETCORE_DEBUG_STRU_ARRAY_SIZE 100 + +enum FORWARDING_REQUEST_STATUS +{ + FORWARDER_START, + FORWARDER_SENDING_REQUEST, + FORWARDER_RECEIVING_RESPONSE, + FORWARDER_RECEIVED_WEBSOCKET_RESPONSE, + FORWARDER_RESET_CONNECTION, + FORWARDER_DONE +}; + +extern HTTP_MODULE_ID g_pModuleId; +extern IHttpServer * g_pHttpServer; +extern BOOL g_fAsyncDisconnectAvailable; +extern PCWSTR g_pszModuleName; +extern HMODULE g_hModule; +extern HMODULE g_hWinHttpModule; +extern DWORD g_dwTlsIndex; +extern DWORD g_OptionalWinHttpFlags; + +#ifdef DEBUG +extern STRA g_strLogs[ASPNETCORE_DEBUG_STRU_ARRAY_SIZE]; +extern DWORD g_dwLogCounter; +#endif // DEBUG + +enum MULTI_PART_POSITION +{ + MULTI_PART_IN_BOUNDARY, + MULTI_PART_IN_HEADER, + MULTI_PART_IN_CHUNK, + MULTI_PART_IN_CHUNK_END +}; + +class ASYNC_DISCONNECT_CONTEXT; + +#define FORWARDING_HANDLER_SIGNATURE ((DWORD)'FHLR') +#define FORWARDING_HANDLER_SIGNATURE_FREE ((DWORD)'fhlr') + +class FORWARDING_HANDLER +{ +public: + + FORWARDING_HANDLER( + __in IHttpContext * pW3Context + ); + + static void * operator new(size_t size); + + static void operator delete(void * pMemory); + + VOID + ReferenceForwardingHandler( + VOID + ) const; + + VOID + DereferenceForwardingHandler( + VOID + ) const; + + REQUEST_NOTIFICATION_STATUS + OnExecuteRequestHandler(); + + REQUEST_NOTIFICATION_STATUS + OnAsyncCompletion( + DWORD cbCompletion, + HRESULT hrCompletionStatus + ); + + IHttpTraceContext * + QueryTraceContext() + { + return m_pW3Context->GetTraceContext(); + } + + IHttpContext * + QueryHttpContext( + VOID + ) + { + return m_pW3Context; + } + + static + VOID + CALLBACK + OnWinHttpCompletion( + HINTERNET hRequest, + DWORD_PTR dwContext, + DWORD dwInternetStatus, + LPVOID lpvStatusInformation, + DWORD dwStatusInformationLength + ) + { + + FORWARDING_HANDLER * pThis = static_cast(reinterpret_cast(dwContext)); + if (pThis == NULL) + { + //error happened, nothing can be done here + return; + } + DBG_ASSERT(pThis->m_Signature == FORWARDING_HANDLER_SIGNATURE); + pThis->OnWinHttpCompletionInternal(hRequest, + dwInternetStatus, + lpvStatusInformation, + dwStatusInformationLength); + } + + static + HRESULT + StaticInitialize( + BOOL fEnableReferenceCountTracing + ); + + static + VOID + StaticTerminate(); + + static + PCWSTR + QueryErrorFormat() + { + return sm_strErrorFormat.QueryStr(); + } + + static + HANDLE + QueryEventLog() + { + return sm_hEventLog; + } + + VOID + TerminateRequest( + BOOL fClientInitiated + ); + + static HINTERNET sm_hSession; + + HRESULT + SetStatusAndHeaders( + PCSTR pszHeaders, + DWORD cchHeaders + ); + + HRESULT + OnSharedRequestEntity( + ULONGLONG ulOffset, + LPCBYTE pvBuffer, + DWORD cbBuffer + ); + + VOID + SetStatus( + FORWARDING_REQUEST_STATUS status + ) + { + m_RequestStatus = status; + } + + virtual + ~FORWARDING_HANDLER( + VOID + ); + +private: + + // + // Begin OnMapRequestHandler phases. + // + + HRESULT + CreateWinHttpRequest( + __in const IHttpRequest * pRequest, + __in const PROTOCOL_CONFIG * pProtocol, + __in HINTERNET hConnect, + __inout STRU * pstrUrl, + ASPNETCORE_CONFIG* pAspNetCoreConfig, + SERVER_PROCESS* pServerProcess + ); + + // + // End OnMapRequestHandler phases. + // + + VOID + RemoveRequest(); + + HRESULT + GetHeaders( + const PROTOCOL_CONFIG * pProtocol, + PCWSTR * ppszHeaders, + DWORD * pcchHeaders, + ASPNETCORE_CONFIG* pAspNetCoreConfig, + SERVER_PROCESS* pServerProcess + ); + + HRESULT + DoReverseRewrite( + __in IHttpResponse *pResponse + ); + + BYTE * + GetNewResponseBuffer( + DWORD dwBufferSize + ); + + VOID + FreeResponseBuffers(); + + VOID + OnWinHttpCompletionInternal( + HINTERNET hRequest, + DWORD dwInternetStatus, + LPVOID lpvStatusInformation, + DWORD dwStatusInformationLength + ); + + HRESULT + OnWinHttpCompletionSendRequestOrWriteComplete( + HINTERNET hRequest, + DWORD dwInternetStatus, + __out BOOL * pfClientError, + __out BOOL * pfAnotherCompletionExpected + ); + + HRESULT + OnWinHttpCompletionStatusHeadersAvailable( + HINTERNET hRequest, + __out BOOL * pfAnotherCompletionExpected + ); + + HRESULT + OnWinHttpCompletionStatusDataAvailable( + HINTERNET hRequest, + DWORD dwBytes, + __out BOOL * pfAnotherCompletionExpected + ); + + HRESULT + OnWinHttpCompletionStatusReadComplete( + __in IHttpResponse * pResponse, + DWORD dwStatusInformationLength, + __out BOOL * pfAnotherCompletionExpected + ); + + HRESULT + OnSendingRequest( + DWORD cbCompletion, + HRESULT hrCompletionStatus, + __out BOOL * pfClientError + ); + + HRESULT + OnReceivingResponse(); + + HRESULT + OnWebSocketWinHttpSendComplete( + HINTERNET hRequest, + LPVOID pvStatus, + DWORD hrCompletion, + DWORD cbCompletion, + BOOL * pfAnotherCompletionExpected + ); + + HRESULT + OnWebSocketWinHttpReceiveComplete( + HINTERNET hRequest, + LPVOID pvStatus, + DWORD hrCompletion, + DWORD cbCompletion, + BOOL * pfAnotherCompletionExpected + ); + + HRESULT + OnWebSocketIisSendComplete( + DWORD hrCompletion, + DWORD cbCompletion + ); + + HRESULT + OnWebSocketIisReceiveComplete( + DWORD hrCompletion, + DWORD cbCompletion + ); + + HRESULT + DoIisWebSocketReceive( + VOID + ); + + VOID + TerminateWebsocket( + VOID + ); + + DWORD m_Signature; + mutable LONG m_cRefs; + + IHttpContext * m_pW3Context; + + // + // WinHTTP request handle is protected using a read-write lock. + // + SRWLOCK m_RequestLock; + HINTERNET m_hRequest; + + APP_OFFLINE_HTM *m_pAppOfflineHtm; + APPLICATION *m_pApplication; + + BOOL m_fResponseHeadersReceivedAndSet; + volatile BOOL m_fClientDisconnected; + // + // A safety guard flag indicating no more IIS PostCompletion is allowed + // + volatile BOOL m_fFinishRequest; + // + // A safety guard flag to prevent from unexpect callback which may signal IIS pipeline + // more than once with non-pending status + // + volatile BOOL m_fDoneAsyncCompletion; + volatile BOOL m_fHasError; + // + // WinHttp may hit AV under race if handle got closed more than once simultaneously + // Use two bool variables to guard + // + volatile BOOL m_fHttpHandleInClose; + volatile BOOL m_fWebSocketHandleInClose; + // + // Record the number of winhttp handles in use + // release IIS pipeline only after all handles got closed + // + volatile LONG m_dwHandlers; + + BOOL m_fDoReverseRewriteHeaders; + BOOL m_fServerResetConn; + DWORD m_msStartTime; + DWORD m_BytesToReceive; + DWORD m_BytesToSend; + + BYTE * m_pEntityBuffer; + DWORD m_cchLastSend; + + static const SIZE_T INLINE_ENTITY_BUFFERS = 8; + DWORD m_cEntityBuffers; + BUFFER_T m_buffEntityBuffers; + + DWORD m_cBytesBuffered; + DWORD m_cMinBufferLimit; + + PCSTR m_pszOriginalHostHeader; + + volatile FORWARDING_REQUEST_STATUS m_RequestStatus; + + ASYNC_DISCONNECT_CONTEXT * m_pDisconnect; + + PCWSTR m_pszHeaders; + DWORD m_cchHeaders; + + BOOL m_fWebSocketEnabled; + + STRU m_strFullUri; + + ULONGLONG m_cContentLength; + + WEBSOCKET_HANDLER * m_pWebSocket; + + static PROTOCOL_CONFIG sm_ProtocolConfig; + + static STRU sm_strErrorFormat; + + static HANDLE sm_hEventLog; + + static ALLOC_CACHE_HANDLER * sm_pAlloc; + + // + // Reference cout tracing for debugging purposes. + // + static TRACE_LOG * sm_pTraceLog; +}; + +class ASYNC_DISCONNECT_CONTEXT : public IHttpConnectionStoredContext +{ + public: + ASYNC_DISCONNECT_CONTEXT() + { + m_pHandler = NULL; + } + + VOID + CleanupStoredContext() + { + DBG_ASSERT(m_pHandler == NULL); + delete this; + } + + VOID + NotifyDisconnect() + { + FORWARDING_HANDLER *pInitialValue = (FORWARDING_HANDLER*) + InterlockedExchangePointer((PVOID*) &m_pHandler, NULL); + + if (pInitialValue != NULL) + { + pInitialValue->TerminateRequest(TRUE); + pInitialValue->DereferenceForwardingHandler(); + } + } + + VOID + SetHandler( + FORWARDING_HANDLER *pHandler + ) + { + // + // Take a reference on the forwarding handler. + // This reference will be released on either of two conditions: + // + // 1. When the request processing ends, in which case a ResetHandler() + // is called. + // + // 2. When a disconnect notification arrives. + // + // We need to make sure that only one of them ends up dereferencing + // the object. + // + + DBG_ASSERT (pHandler != NULL); + DBG_ASSERT (m_pHandler == NULL); + + pHandler->ReferenceForwardingHandler(); + InterlockedExchangePointer((PVOID*)&m_pHandler, pHandler); + } + + VOID + ResetHandler( + VOID + ) + { + FORWARDING_HANDLER *pInitialValue = (FORWARDING_HANDLER*) + InterlockedExchangePointer( (PVOID*)&m_pHandler, NULL); + + if (pInitialValue != NULL) + { + pInitialValue->DereferenceForwardingHandler(); + } + } + + private: + ~ASYNC_DISCONNECT_CONTEXT() + {} + + FORWARDING_HANDLER * m_pHandler; +}; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/fx_ver.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/fx_ver.h new file mode 100644 index 0000000000..f485ba5a6e --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/fx_ver.h @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef __FX_VER_H__ +#define __FX_VER_H__ +#include + +// Note: This is not SemVer (esp., in comparing pre-release part, fx_ver_t does not +// compare multiple dot separated identifiers individually.) ex: 1.0.0-beta.2 vs. 1.0.0-beta.11 +struct fx_ver_t +{ + fx_ver_t(int major, int minor, int patch); + fx_ver_t(int major, int minor, int patch, const std::wstring& pre); + fx_ver_t(int major, int minor, int patch, const std::wstring& pre, const std::wstring& build); + + int get_major() const { return m_major; } + int get_minor() const { return m_minor; } + int get_patch() const { return m_patch; } + + void set_major(int m) { m_major = m; } + void set_minor(int m) { m_minor = m; } + void set_patch(int p) { m_patch = p; } + + bool is_prerelease() const { return !m_pre.empty(); } + + std::wstring as_str() const; + std::wstring prerelease_glob() const; + std::wstring patch_glob() const; + + bool operator ==(const fx_ver_t& b) const; + bool operator !=(const fx_ver_t& b) const; + bool operator <(const fx_ver_t& b) const; + bool operator >(const fx_ver_t& b) const; + bool operator <=(const fx_ver_t& b) const; + bool operator >=(const fx_ver_t& b) const; + + static bool parse(const std::wstring& ver, fx_ver_t* fx_ver, bool parse_only_production = false); + +private: + int m_major; + int m_minor; + int m_patch; + std::wstring m_pre; + std::wstring m_build; + + static int compare(const fx_ver_t&a, const fx_ver_t& b); +}; + +#endif // __FX_VER_H__ \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/globalmodule.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/globalmodule.h new file mode 100644 index 0000000000..aca1857051 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/globalmodule.h @@ -0,0 +1,37 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class ASPNET_CORE_GLOBAL_MODULE : public CGlobalModule +{ + +public: + + ASPNET_CORE_GLOBAL_MODULE( + APPLICATION_MANAGER* pApplicationManager + ); + + ~ASPNET_CORE_GLOBAL_MODULE() + { + } + + VOID Terminate() + { + // Remove the class from memory. + delete this; + } + + GLOBAL_NOTIFICATION_STATUS + OnGlobalStopListening( + _In_ IGlobalStopListeningProvider * pProvider + ); + + GLOBAL_NOTIFICATION_STATUS + OnGlobalConfigurationChange( + _In_ IGlobalConfigurationChangeProvider * pProvider + ); + +private: + APPLICATION_MANAGER * m_pApplicationManager; +}; diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/path.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/path.h new file mode 100644 index 0000000000..05545acfd5 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/path.h @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class PATH +{ +public: + + static + HRESULT + SplitUrl( + PCWSTR pszDestinationUrl, + BOOL *pfSecure, + STRU *pstrDestination, + STRU *pstrUrl + ); + + static + HRESULT + UnEscapeUrl( + PCWSTR pszUrl, + DWORD cchUrl, + bool fCopyQuery, + STRA * pstrResult + ); + + static + HRESULT + UnEscapeUrl( + PCWSTR pszUrl, + DWORD cchUrl, + STRU * pstrResult + ); + + static HRESULT + EscapeAbsPath( + IHttpRequest * pRequest, + STRU * strEscapedUrl + ); + + static + bool + IsValidAttributeNameChar( + WCHAR ch + ); + + static + bool + IsValidQueryStringName( + PCWSTR pszName + ); + + static + bool + IsValidHeaderName( + PCWSTR pszName + ); + + static + bool + FindInMultiString( + PCWSTR pszMultiString, + PCWSTR pszStringToFind + ); + + static + HRESULT + IsPathUnc( + __in LPCWSTR pszPath, + __out BOOL * pfIsUnc + ); + + static + HRESULT + ConvertPathToFullPath( + _In_ LPCWSTR pszPath, + _In_ LPCWSTR pszRootPath, + _Out_ STRU* pStrFullPath + ); + +private: + + PATH() {} + ~PATH() {} + + static + CHAR + ToHexDigit( + UINT nDigit + ) + { + return static_cast(nDigit > 9 ? nDigit - 10 + 'A' : nDigit + '0'); + } +}; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/processmanager.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/processmanager.h new file mode 100644 index 0000000000..b91e8e6bfb --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/processmanager.h @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define ONE_MINUTE_IN_MILLISECONDS 60000 + +class PROCESS_MANAGER +{ +public: + + virtual + ~PROCESS_MANAGER(); + + VOID + ReferenceProcessManager() const + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceProcessManager() const + { + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + HRESULT + GetProcess( + _In_ IHttpContext *context, + _In_ ASPNETCORE_CONFIG *pConfig, + _Out_ SERVER_PROCESS **ppServerProcess + ); + + HANDLE + QueryNULHandle() + { + return m_hNULHandle; + } + + HRESULT + Initialize( + VOID + ); + + VOID + SendShutdownSignal() + { + AcquireSRWLockExclusive( &m_srwLock ); + + for(DWORD i = 0; i < m_dwProcessesPerApplication; ++i ) + { + if( m_ppServerProcessList != NULL && + m_ppServerProcessList[i] != NULL ) + { + m_ppServerProcessList[i]->SendSignal(); + m_ppServerProcessList[i]->DereferenceServerProcess(); + m_ppServerProcessList[i] = NULL; + } + } + + ReleaseSRWLockExclusive( &m_srwLock ); + } + + VOID + ShutdownProcess( + SERVER_PROCESS* pServerProcess + ) + { + AcquireSRWLockExclusive( &m_srwLock ); + + ShutdownProcessNoLock( pServerProcess ); + + ReleaseSRWLockExclusive( &m_srwLock ); + } + + VOID + ShutdownAllProcesses( + ) + { + AcquireSRWLockExclusive( &m_srwLock ); + + ShutdownAllProcessesNoLock(); + + ReleaseSRWLockExclusive( &m_srwLock ); + } + + VOID + IncrementRapidFailCount( + VOID + ) + { + InterlockedIncrement(&m_cRapidFailCount); + } + + PROCESS_MANAGER() : + m_ppServerProcessList( NULL ), + m_hNULHandle( NULL ), + m_cRapidFailCount( 0 ), + m_dwProcessesPerApplication( 1 ), + m_dwRouteToProcessIndex( 0 ), + m_fServerProcessListReady(FALSE), + m_cRefs( 1 ) + { + InitializeSRWLock( &m_srwLock ); + } + +private: + + BOOL + RapidFailsPerMinuteExceeded( + LONG dwRapidFailsPerMinute + ) + { + DWORD dwCurrentTickCount = GetTickCount(); + + if( (dwCurrentTickCount - m_dwRapidFailTickStart) + >= ONE_MINUTE_IN_MILLISECONDS ) + { + // + // reset counters every minute. + // + + InterlockedExchange(&m_cRapidFailCount, 0); + m_dwRapidFailTickStart = dwCurrentTickCount; + } + + return m_cRapidFailCount > dwRapidFailsPerMinute; + } + + VOID + ShutdownProcessNoLock( + SERVER_PROCESS* pServerProcess + ) + { + for(DWORD i = 0; i < m_dwProcessesPerApplication; ++i ) + { + if( m_ppServerProcessList != NULL && + m_ppServerProcessList[i] != NULL && + m_ppServerProcessList[i]->GetPort() == pServerProcess->GetPort() ) + { + // shutdown pServerProcess if not already shutdown. + m_ppServerProcessList[i]->StopProcess(); + m_ppServerProcessList[i]->DereferenceServerProcess(); + m_ppServerProcessList[i] = NULL; + } + } + } + + VOID + ShutdownAllProcessesNoLock( + VOID + ) + { + for(DWORD i = 0; i < m_dwProcessesPerApplication; ++i ) + { + if( m_ppServerProcessList != NULL && + m_ppServerProcessList[i] != NULL ) + { + // shutdown pServerProcess if not already shutdown. + m_ppServerProcessList[i]->SendSignal(); + m_ppServerProcessList[i]->DereferenceServerProcess(); + m_ppServerProcessList[i] = NULL; + } + } + } + + volatile LONG m_cRapidFailCount; + DWORD m_dwRapidFailTickStart; + DWORD m_dwProcessesPerApplication; + volatile DWORD m_dwRouteToProcessIndex; + + SRWLOCK m_srwLock; + SERVER_PROCESS **m_ppServerProcessList; + + // + // m_hNULHandle is used to redirect stdout/stderr to NUL. + // If Createprocess is called to launch a batch file for example, + // it tries to write to the console buffer by default. It fails to + // start if the console buffer is owned by the parent process i.e + // in our case w3wp.exe. So we have to redirect the stdout/stderr + // of the child process to NUL or to a file (anything other than + // the console buffer of the parent process). + // + + HANDLE m_hNULHandle; + mutable LONG m_cRefs; + + volatile static BOOL sm_fWSAStartupDone; + volatile BOOL m_fServerProcessListReady; +}; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/protocolconfig.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/protocolconfig.h new file mode 100644 index 0000000000..d9d730c544 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/protocolconfig.h @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include "aspnetcoreconfig.h" + +class PROTOCOL_CONFIG +{ + public: + + PROTOCOL_CONFIG() + { + } + + HRESULT + Initialize(); + + VOID + OverrideConfig( + ASPNETCORE_CONFIG *pAspNetCoreConfig + ); + + BOOL + QueryDoKeepAlive() const + { + return m_fKeepAlive; + } + + DWORD + QueryTimeout() const + { + return m_msTimeout; + } + + BOOL + QueryPreserveHostHeader() const + { + return m_fPreserveHostHeader; + } + + BOOL + QueryReverseRewriteHeaders() const + { + return m_fReverseRewriteHeaders; + } + + const STRA * + QueryXForwardedForName() const + { + return &m_strXForwardedForName; + } + + BOOL + QueryIncludePortInXForwardedFor() const + { + return m_fIncludePortInXForwardedFor; + } + + DWORD + QueryMinResponseBuffer() const + { + return m_dwMinResponseBuffer; + } + + DWORD + QueryResponseBufferLimit() const + { + return m_dwResponseBufferLimit; + } + + DWORD + QueryMaxResponseHeaderSize() const + { + return m_dwMaxResponseHeaderSize; + } + + const STRA* + QuerySslHeaderName() const + { + return &m_strSslHeaderName; + } + + const STRA * + QueryClientCertName() const + { + return &m_strClientCertName; + } + + private: + + BOOL m_fKeepAlive; + BOOL m_fPreserveHostHeader; + BOOL m_fReverseRewriteHeaders; + BOOL m_fIncludePortInXForwardedFor; + + DWORD m_msTimeout; + DWORD m_dwMinResponseBuffer; + DWORD m_dwResponseBufferLimit; + DWORD m_dwMaxResponseHeaderSize; + + STRA m_strXForwardedForName; + STRA m_strSslHeaderName; + STRA m_strClientCertName; +}; diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/proxymodule.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/proxymodule.h new file mode 100644 index 0000000000..7e5f30a8eb --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/proxymodule.h @@ -0,0 +1,64 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +extern HTTP_MODULE_ID g_pModuleId; +extern IHttpServer *g_pHttpServer; +extern HMODULE g_hAspnetCoreRH; + +class ASPNET_CORE_PROXY_MODULE : public CHttpModule +{ + public: + + ASPNET_CORE_PROXY_MODULE(); + + ~ASPNET_CORE_PROXY_MODULE(); + + void * operator new(size_t size, IModuleAllocator * pPlacement) + { + return pPlacement->AllocateMemory(static_cast(size)); + } + + VOID + operator delete( + void * + ) + {} + + __override + REQUEST_NOTIFICATION_STATUS + OnExecuteRequestHandler( + IHttpContext * pHttpContext, + IHttpEventProvider * pProvider + ); + + __override + REQUEST_NOTIFICATION_STATUS + OnAsyncCompletion( + IHttpContext * pHttpContext, + DWORD dwNotification, + BOOL fPostNotification, + IHttpEventProvider * pProvider, + IHttpCompletionInfo * pCompletionInfo + ); + + private: + + APPLICATION_INFO *m_pApplicationInfo; + APPLICATION *m_pApplication; + REQUEST_HANDLER *m_pHandler; +}; + +class ASPNET_CORE_PROXY_MODULE_FACTORY : public IHttpModuleFactory +{ + public: + HRESULT + GetHttpModule( + CHttpModule ** ppModule, + IModuleAllocator * pAllocator + ); + + VOID + Terminate(); +}; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/resource.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/resource.h new file mode 100644 index 0000000000..31bfcfe8bf --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/resource.h @@ -0,0 +1,17 @@ +#pragma once + +#define IDS_INVALID_PROPERTY 1000 +#define IDS_SERVER_ERROR 1001 + +#define ASPNETCORE_EVENT_MSG_BUFFER_SIZE 256 +#define ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG L"Application '%s' started process '%d' successfully and is listening on port '%d'." +#define ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED_MSG L"Maximum rapid fail count per minute of '%d' exceeded." +#define ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG L"Application '%s' failed to parse processPath and arguments due to internal error, ErrorCode = '0x%x'." +#define ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s'but failed to get its status, ErrorCode = '0x%x'." +#define ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG L"Application '%s' with physical root '%s' failed to start process with commandline '%s', ErrorCode = '0x%x : %x." +#define ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s' but failed to listen on the given port '%d'" +#define ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG L"Application '%s' with physical root '%s' created process with commandline '%s' but either crashed or did not reponse or did not listen on the given port '%d', ErrorCode = '0x%x'" +#define ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG L"Warning: Could not create stdoutLogFile %s, ErrorCode = %d." +#define ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG L"Failed to gracefully shutdown process '%d'." +#define ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG L"Sent shutdown HTTP message to process '%d' and received http status '%d'." +#define ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG L"App_offline file '%s' was detected." diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/responseheaderhash.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/responseheaderhash.h new file mode 100644 index 0000000000..7ef127366b --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/responseheaderhash.h @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +// +// *_HEADER_HASH maps strings to UlHeader* values +// + +#define UNKNOWN_INDEX (0xFFFFFFFF) + +struct HEADER_RECORD +{ + PCSTR _pszName; + ULONG _ulHeaderIndex; +}; + +class RESPONSE_HEADER_HASH: public HASH_TABLE +{ +public: + RESPONSE_HEADER_HASH() + {} + + VOID + ReferenceRecord( + HEADER_RECORD * + ) + {} + + VOID + DereferenceRecord( + HEADER_RECORD * + ) + {} + + PCSTR + ExtractKey( + HEADER_RECORD * pRecord + ) + { + return pRecord->_pszName; + } + + DWORD + CalcKeyHash( + PCSTR key + ) + { + return HashStringNoCase(key); + } + + BOOL + EqualKeys( + PCSTR key1, + PCSTR key2 + ) + { + return (_stricmp(key1, key2) == 0); + } + + HRESULT + Initialize( + VOID + ); + + VOID + Terminate( + VOID + ); + + DWORD + GetIndex( + PCSTR pszName + ) + { + HEADER_RECORD * pRecord = NULL; + + FindKey(pszName, &pRecord); + if (pRecord != NULL) + { + return pRecord->_ulHeaderIndex; + } + + return UNKNOWN_INDEX; + } + + static + PCSTR + GetString( + ULONG ulIndex + ) + { + if (ulIndex < HttpHeaderResponseMaximum) + { + DBG_ASSERT(sm_rgHeaders[ulIndex]._ulHeaderIndex == ulIndex); + return sm_rgHeaders[ulIndex]._pszName; + } + + return NULL; + } + +private: + + static HEADER_RECORD sm_rgHeaders[]; + + RESPONSE_HEADER_HASH(const RESPONSE_HEADER_HASH &); + void operator=(const RESPONSE_HEADER_HASH &); +}; + +extern RESPONSE_HEADER_HASH * g_pResponseHeaderHash; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/serverprocess.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/serverprocess.h new file mode 100644 index 0000000000..4d6bf6c145 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/serverprocess.h @@ -0,0 +1,325 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#define MIN_PORT 1025 +#define MAX_PORT 48000 +#define MAX_RETRY 10 +#define MAX_ACTIVE_CHILD_PROCESSES 16 +#define LOCALHOST "127.0.0.1" +#define ASPNETCORE_PORT_STR L"ASPNETCORE_PORT" +#define ASPNETCORE_PORT_ENV_STR L"ASPNETCORE_PORT=" +#define ASPNETCORE_APP_PATH_ENV_STR L"ASPNETCORE_APPL_PATH=" +#define ASPNETCORE_APP_TOKEN_ENV_STR L"ASPNETCORE_TOKEN=" +#define ASPNETCORE_APP_PATH_ENV_STR L"ASPNETCORE_APPL_PATH=" +#define HOSTING_STARTUP_ASSEMBLIES_ENV_STR L"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES" +#define HOSTING_STARTUP_ASSEMBLIES_NAME L"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=" +#define HOSTING_STARTUP_ASSEMBLIES_VALUE L"Microsoft.AspNetCore.Server.IISIntegration" +#define ASPNETCORE_IIS_AUTH_ENV_STR L"ASPNETCORE_IIS_HTTPAUTH=" +#define ASPNETCORE_IIS_AUTH_WINDOWS L"windows;" +#define ASPNETCORE_IIS_AUTH_BASIC L"basic;" +#define ASPNETCORE_IIS_AUTH_ANONYMOUS L"anonymous;" +#define ASPNETCORE_IIS_AUTH_NONE L"none" + +class PROCESS_MANAGER; +class FORWARDER_CONNECTION; + +class SERVER_PROCESS +{ +public: + SERVER_PROCESS(); + + HRESULT + Initialize( + _In_ PROCESS_MANAGER *pProcessManager, + _In_ STRU *pszProcessExePath, + _In_ STRU *pszArguments, + _In_ DWORD dwStartupTimeLimitInMS, + _In_ DWORD dwShtudownTimeLimitInMS, + _In_ BOOL fWindowsAuthEnabled, + _In_ BOOL fBasicAuthEnabled, + _In_ BOOL fAnonymousAuthEnabled, + _In_ ENVIRONMENT_VAR_HASH* pEnvironmentVariables, + _In_ BOOL fStdoutLogEnabled, + _In_ STRU *pstruStdoutLogFile + ); + + + HRESULT + StartProcess( + _In_ IHttpContext *context + ); + + HRESULT + SetWindowsAuthToken( + _In_ HANDLE hToken, + _Out_ LPHANDLE pTargeTokenHandle + ); + + BOOL + IsReady( + VOID + ) + { + return m_fReady; + } + + VOID + StopProcess( + VOID + ); + + DWORD + GetPort() + { + return m_dwPort; + } + + VOID + ReferenceServerProcess( + VOID + ) + { + InterlockedIncrement(&m_cRefs); + } + + VOID + DereferenceServerProcess( + VOID + ) + { + _ASSERT(m_cRefs != 0 ); + + if (InterlockedDecrement(&m_cRefs) == 0) + { + delete this; + } + } + + virtual + ~SERVER_PROCESS(); + + HRESULT + HandleProcessExit( + VOID + ); + + FORWARDER_CONNECTION* + QueryWinHttpConnection( + VOID + ) + { + return m_pForwarderConnection; + } + + static + VOID + CALLBACK + TimerCallback( + _In_ PTP_CALLBACK_INSTANCE Instance, + _In_ PVOID Context, + _In_ PTP_TIMER Timer + ); + + LPCWSTR + QueryPortStr() + { + return m_struPort.QueryStr(); + } + + LPCWSTR + QueryFullLogPath() + { + return m_struFullLogFile.QueryStr(); + } + + LPCSTR + QueryGuid() + { + return m_straGuid.QueryStr(); + } + + DWORD + QueryProcessGroupId() + { + return m_dwProcessId; + } + + VOID + SendSignal( + VOID + ); + +private: + + BOOL + IsDebuggerIsAttached( + VOID + ); + + HRESULT + StopAllProcessesInJobObject( + VOID + ); + + HRESULT + SetupStdHandles( + _In_ IHttpContext *context, + _In_ LPSTARTUPINFOW pStartupInfo + ); + + HRESULT + CheckIfServerIsUp( + _In_ DWORD dwPort, + _Out_ DWORD * pdwProcessId, + _Out_ BOOL * pfReady + ); + + HRESULT + RegisterProcessWait( + _In_ PHANDLE phWaitHandle, + _In_ HANDLE hProcessToWaitOn + ); + + HRESULT + GetChildProcessHandles( + ); + + HRESULT + SetupListenPort( + ENVIRONMENT_VAR_HASH *pEnvironmentVarTable + ); + + HRESULT + SetupAppPath( + IHttpContext* pContext, + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable + ); + + HRESULT + SetupAppToken( + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable + ); + + HRESULT + InitEnvironmentVariablesTable( + ENVIRONMENT_VAR_HASH** pEnvironmentVarTable + ); + + HRESULT + OutputEnvironmentVariables( + MULTISZ* pmszOutput, + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable + ); + + HRESULT + SetupCommandLine( + STRU* pstrCommandLine + ); + + HRESULT + PostStartCheck( + const STRU* const pStruCommandline, + STRU* pStruErrorMessage + ); + + HRESULT + GetRandomPort( + DWORD* pdwPickedPort, + DWORD dwExcludedPort + ); + + DWORD + GetNumberOfDigits( + _In_ DWORD dwNumber + ) + { + DWORD digits = 0; + + if( dwNumber == 0 ) + { + digits = 1; + goto Finished; + } + + while( dwNumber > 0) + { + dwNumber = dwNumber / 10; + digits ++; + } + Finished: + return digits; + } + + static + VOID + SendShutDownSignal( + LPVOID lpParam + ); + + VOID + SendShutDownSignalInternal( + VOID + ); + + HRESULT + SendShutdownHttpMessage( + VOID + ); + + VOID + TerminateBackendProcess( + VOID + ); + + FORWARDER_CONNECTION *m_pForwarderConnection; + BOOL m_fStdoutLogEnabled; + BOOL m_fWindowsAuthEnabled; + BOOL m_fBasicAuthEnabled; + BOOL m_fAnonymousAuthEnabled; + + STTIMER m_Timer; + SOCKET m_socket; + + STRU m_struLogFile; + STRU m_struFullLogFile; + STRU m_ProcessPath; + STRU m_Arguments; + STRU m_struAppPath; + STRU m_struAppFullPath; + STRU m_struPort; + STRU m_pszRootApplicationPath; + volatile LONG m_lStopping; + volatile BOOL m_fReady; + mutable LONG m_cRefs; + + DWORD m_dwPort; + DWORD m_dwStartupTimeLimitInMS; + DWORD m_dwShutdownTimeLimitInMS; + DWORD m_cChildProcess; + DWORD m_dwChildProcessIds[MAX_ACTIVE_CHILD_PROCESSES]; + DWORD m_dwProcessId; + DWORD m_dwListeningProcessId; + + STRA m_straGuid; + + HANDLE m_hJobObject; + HANDLE m_hStdoutHandle; + // + // m_hProcessHandle is the handle to process this object creates. + // + HANDLE m_hProcessHandle; + HANDLE m_hListeningProcessHandle; + HANDLE m_hProcessWaitHandle; + HANDLE m_hShutdownHandle; + // + // m_hChildProcessHandle is the handle to process created by + // m_hProcessHandle process if it does. + // + HANDLE m_hChildProcessHandles[MAX_ACTIVE_CHILD_PROCESSES]; + HANDLE m_hChildProcessWaitHandles[MAX_ACTIVE_CHILD_PROCESSES]; + + PROCESS_MANAGER *m_pProcessManager; + ENVIRONMENT_VAR_HASH *m_pEnvironmentVarTable ; +}; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/websockethandler.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/websockethandler.h new file mode 100644 index 0000000000..aadfcb6884 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/websockethandler.h @@ -0,0 +1,221 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +class FORWARDING_HANDLER; + +class WEBSOCKET_HANDLER +{ +public: + WEBSOCKET_HANDLER(); + + virtual + ~WEBSOCKET_HANDLER(); + + static + HRESULT + StaticInitialize( + BOOL fEnableReferenceTraceLogging + ); + + static + VOID + StaticTerminate( + VOID + ); + + VOID + Terminate( + VOID + ); + + VOID + TerminateRequest( + VOID + ) + { + Cleanup(ServerStateUnavailable); + } + + HRESULT + ProcessRequest( + FORWARDING_HANDLER *pHandler, + IHttpContext * pHttpContext, + HINTERNET hRequest, + BOOL* fHandleCreated + ); + + REQUEST_NOTIFICATION_STATUS + OnAsyncCompletion( + VOID + ); + + HRESULT + OnWinHttpSendComplete( + WINHTTP_WEB_SOCKET_STATUS * pCompletionStatus + ); + + HRESULT + OnWinHttpShutdownComplete( + VOID + ); + + HRESULT + OnWinHttpReceiveComplete( + WINHTTP_WEB_SOCKET_STATUS * pCompletionStatus + ); + + HRESULT + OnWinHttpIoError( + WINHTTP_WEB_SOCKET_ASYNC_RESULT *pCompletionStatus + ); + +private: + enum CleanupReason + { + CleanupReasonUnknown = 0, + IdleTimeout = 1, + ConnectFailed = 2, + ClientDisconnect = 3, + ServerDisconnect = 4, + ServerStateUnavailable = 5 + }; + + WEBSOCKET_HANDLER(const WEBSOCKET_HANDLER &); + void operator=(const WEBSOCKET_HANDLER &); + + VOID + InsertRequest( + VOID + ); + + VOID + RemoveRequest( + VOID + ); + + static + VOID + WINAPI + OnReadIoCompletion( + HRESULT hrError, + VOID * pvCompletionContext, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ); + + static + VOID + WINAPI + OnWriteIoCompletion( + HRESULT hrError, + VOID * pvCompletionContext, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ); + + VOID + Cleanup( + CleanupReason reason + ); + + HRESULT + DoIisWebSocketReceive( + VOID + ); + + HRESULT + DoWinHttpWebSocketReceive( + VOID + ); + + HRESULT + DoIisWebSocketSend( + DWORD cbData, + WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType + ); + + HRESULT + DoWinHttpWebSocketSend( + DWORD cbData, + WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType + ); + + HRESULT + OnIisSendComplete( + HRESULT hrError, + DWORD cbIO + ); + + HRESULT + OnIisReceiveComplete( + HRESULT hrError, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ); + + VOID + IncrementOutstandingIo( + VOID + ); + + VOID + DecrementOutstandingIo( + VOID + ); + + VOID + IndicateCompletionToIIS( + VOID + ); + +private: + static const + DWORD RECEIVE_BUFFER_SIZE = 4*1024; + + LIST_ENTRY _listEntry; + + IHttpContext3 * _pHttpContext; + + IWebSocketContext * _pWebSocketContext; + + FORWARDING_HANDLER *_pHandler; + + HINTERNET _hWebSocketRequest; + + BYTE _WinHttpReceiveBuffer[RECEIVE_BUFFER_SIZE]; + + BYTE _IisReceiveBuffer[RECEIVE_BUFFER_SIZE]; + + CRITICAL_SECTION _RequestLock; + + LONG _dwOutstandingIo; + + volatile + BOOL _fHandleClosed; + + volatile + BOOL _fCleanupInProgress; + + volatile + BOOL _fIndicateCompletionToIis; + + volatile + BOOL _fReceivedCloseMsg; + + static + LIST_ENTRY sm_RequestsListHead; + + static + SRWLOCK sm_RequestsListLock; + + static + TRACE_LOG * sm_pTraceLog; +}; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/Inc/winhttphelper.h b/src/AspNetCoreModuleV1/AspNetCore/Inc/winhttphelper.h new file mode 100644 index 0000000000..b301a76cf2 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/Inc/winhttphelper.h @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +typedef +HINTERNET +(WINAPI * PFN_WINHTTP_WEBSOCKET_COMPLETE_UPGRADE)( + _In_ HINTERNET hRequest, + _In_opt_ DWORD_PTR pContext +); + + +typedef +DWORD +(WINAPI * PFN_WINHTTP_WEBSOCKET_SEND)( + _In_ HINTERNET hWebSocket, + _In_ WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType, + _In_reads_opt_(dwBufferLength) PVOID pvBuffer, + _In_ DWORD dwBufferLength +); + +typedef +DWORD +(WINAPI * PFN_WINHTTP_WEBSOCKET_RECEIVE)( + _In_ HINTERNET hWebSocket, + _Out_writes_bytes_to_(dwBufferLength, *pdwBytesRead) PVOID pvBuffer, + _In_ DWORD dwBufferLength, + _Out_range_(0, dwBufferLength) DWORD *pdwBytesRead, + _Out_ WINHTTP_WEB_SOCKET_BUFFER_TYPE *peBufferType +); + +typedef +DWORD +(WINAPI * PFN_WINHTTP_WEBSOCKET_SHUTDOWN)( + _In_ HINTERNET hWebSocket, + _In_ USHORT usStatus, + _In_reads_bytes_opt_(dwReasonLength) PVOID pvReason, + _In_range_(0, WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH) DWORD dwReasonLength +); + +typedef +DWORD +(WINAPI * PFN_WINHTTP_WEBSOCKET_QUERY_CLOSE_STATUS)( + _In_ HINTERNET hWebSocket, + _Out_ USHORT *pusStatus, + _Out_writes_bytes_to_opt_(dwReasonLength, *pdwReasonLengthConsumed) PVOID pvReason, + _In_range_(0, WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH) DWORD dwReasonLength, + _Out_range_(0, WINHTTP_WEB_SOCKET_MAX_CLOSE_REASON_LENGTH) DWORD *pdwReasonLengthConsumed +); + +class WINHTTP_HELPER +{ +public: + static + HRESULT + StaticInitialize(); + + static + VOID + GetFlagsFromBufferType( + __in WINHTTP_WEB_SOCKET_BUFFER_TYPE BufferType, + __out BOOL * pfUtf8Encoded, + __out BOOL * pfFinalFragment, + __out BOOL * pfClose + ); + + static + VOID + GetBufferTypeFromFlags( + __in BOOL fUtf8Encoded, + __in BOOL fFinalFragment, + __in BOOL fClose, + __out WINHTTP_WEB_SOCKET_BUFFER_TYPE* pBufferType + ); + + static + PFN_WINHTTP_WEBSOCKET_COMPLETE_UPGRADE sm_pfnWinHttpWebSocketCompleteUpgrade; + + static + PFN_WINHTTP_WEBSOCKET_SEND sm_pfnWinHttpWebSocketSend; + + static + PFN_WINHTTP_WEBSOCKET_RECEIVE sm_pfnWinHttpWebSocketReceive; + + static + PFN_WINHTTP_WEBSOCKET_SHUTDOWN sm_pfnWinHttpWebSocketShutdown; + + static + PFN_WINHTTP_WEBSOCKET_QUERY_CLOSE_STATUS sm_pfnWinHttpWebSocketQueryCloseStatus; +}; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_msg.rc b/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_msg.rc new file mode 100644 index 0000000000..0abcb0fa2c --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_msg.rc @@ -0,0 +1,2 @@ +LANGUAGE 0x9,0x1 +1 11 "MSG00001.bin" diff --git a/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_schema.xml b/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_schema.xml index d65be07195..c1590816b7 100644 --- a/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_schema.xml +++ b/src/AspNetCoreModuleV1/AspNetCore/aspnetcore_schema.xml @@ -11,37 +11,29 @@ --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/aspnetcoremodule.rc b/src/AspNetCoreModuleV1/AspNetCore/aspnetcoremodule.rc deleted file mode 100644 index 9d0dfa7f83..0000000000 --- a/src/AspNetCoreModuleV1/AspNetCore/aspnetcoremodule.rc +++ /dev/null @@ -1,120 +0,0 @@ -// Microsoft Visual C++ generated resource script. -// -#include -#include "version.h" -#include "..\CommonLib\Aspnetcore_msg.rc" -#include "..\CommonLib\resources.h" -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US - -#define FileDescription "IIS AspNetCore Module. Commit: " CommitHash - -///////////////////////////////////////////////////////////////////////////// -// -// 11 -// - -//1 11 -//BEGIN -// 0x0001, 0x0000, 0x03e8, 0x0000, 0x03ed, 0x0000, 0x0010, 0x0000, 0x0010, -// 0x0001, 0x0025, 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, -// 0x0025, 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, -// 0x0031, 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, -// 0x000d, 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, 0x000d, -// 0x000a, 0x0000, 0x0000, 0x0010, 0x0001, 0x0025, 0x0031, 0x000d, 0x000a, -// 0x0000, 0x0000 -//END - - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "..\CommonLib\resources.h\0" -END - -2 TEXTINCLUDE -BEGIN - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO - FILEVERSION FileVersion - PRODUCTVERSION ProductVersion - FILEFLAGSMASK 0x3fL -#ifdef _DEBUG - FILEFLAGS 0x1L -#else - FILEFLAGS 0x0L -#endif - FILEOS 0x40004L - FILETYPE 0x2L - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "CompanyName", "Microsoft Corporation" - VALUE "FileDescription", FileDescription - VALUE "FileVersion", FileVersionStr - VALUE "InternalName", "aspnetcore" - VALUE "LegalCopyright", "Copyright (C) Microsoft Corporation" - VALUE "OriginalFilename", "aspnetcore.dll" - VALUE "ProductName", "ASP.NET Core Module" - VALUE "ProductVersion", ProductVersionStr - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END - - -///////////////////////////////////////////////////////////////////////////// -// -// String Table -// - -STRINGTABLE -BEGIN - IDS_INVALID_PROPERTY "Property name '%s' in system.webServer/aspNetCore section has invalid value '%s' which does not conform to the prescribed format" - IDS_SERVER_ERROR "There was a connection error while trying to route the request." -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/application.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/application.cxx new file mode 100644 index 0000000000..f80435cfbb --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/application.cxx @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +APPLICATION::~APPLICATION() +{ + if (m_pAppOfflineHtm != NULL) + { + m_pAppOfflineHtm->DereferenceAppOfflineHtm(); + m_pAppOfflineHtm = NULL; + } + + if (m_pFileWatcherEntry != NULL) + { + // Mark the entry as invalid, + // StopMonitor will close the file handle and trigger a FCN + // the entry will delete itself when processing this FCN + m_pFileWatcherEntry->MarkEntryInValid(); + m_pFileWatcherEntry->StopMonitor(); + m_pFileWatcherEntry = NULL; + } + + if (m_pProcessManager != NULL) + { + m_pProcessManager->ShutdownAllProcesses(); + m_pProcessManager->DereferenceProcessManager(); + m_pProcessManager = NULL; + } +} + +HRESULT +APPLICATION::Initialize( + _In_ APPLICATION_MANAGER* pApplicationManager, + _In_ LPCWSTR pszApplication, + _In_ LPCWSTR pszPhysicalPath +) +{ + HRESULT hr = S_OK; + + DBG_ASSERT(pszPhysicalPath != NULL); + DBG_ASSERT(pApplicationManager != NULL); + DBG_ASSERT(pszPhysicalPath != NULL); + m_strAppPhysicalPath.Copy(pszPhysicalPath); + + m_pApplicationManager = pApplicationManager; + + hr = m_applicationKey.Initialize(pszApplication); + if (FAILED(hr)) + { + goto Finished; + } + + if (m_pProcessManager == NULL) + { + m_pProcessManager = new PROCESS_MANAGER; + if (m_pProcessManager == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = m_pProcessManager->Initialize(); + if (FAILED(hr)) + { + goto Finished; + } + } + + if (m_pFileWatcherEntry == NULL) + { + m_pFileWatcherEntry = new FILE_WATCHER_ENTRY(pApplicationManager->GetFileWatcher()); + if (m_pFileWatcherEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + } + + UpdateAppOfflineFileHandle(); + +Finished: + + if (FAILED(hr)) + { + if (m_pFileWatcherEntry != NULL) + { + m_pFileWatcherEntry->DereferenceFileWatcherEntry(); + m_pFileWatcherEntry = NULL; + } + + if (m_pProcessManager != NULL) + { + m_pProcessManager->DereferenceProcessManager(); + m_pProcessManager = NULL; + } + } + + return hr; +} + +HRESULT +APPLICATION::StartMonitoringAppOffline() +{ + HRESULT hr = S_OK; + + hr = m_pFileWatcherEntry->Create(m_strAppPhysicalPath.QueryStr(), L"app_offline.htm", this, NULL); + + return hr; +} + +VOID +APPLICATION::UpdateAppOfflineFileHandle() +{ + STRU strFilePath; + PATH::ConvertPathToFullPath(L".\\app_offline.htm", m_strAppPhysicalPath.QueryStr(), &strFilePath); + APP_OFFLINE_HTM *pOldAppOfflineHtm = NULL; + APP_OFFLINE_HTM *pNewAppOfflineHtm = NULL; + + if (INVALID_FILE_ATTRIBUTES == GetFileAttributes(strFilePath.QueryStr()) && GetLastError() == ERROR_FILE_NOT_FOUND) + { + m_fAppOfflineFound = FALSE; + } + else + { + m_fAppOfflineFound = TRUE; + + // + // send shutdown signal + // + + // The reason why we send the shutdown signal before loading the new app_offline file is because we want to make some delay + // before reading the appoffline.htm so that the file change can be done on time. + if (m_pProcessManager != NULL) + { + m_pProcessManager->SendShutdownSignal(); + } + + pNewAppOfflineHtm = new APP_OFFLINE_HTM(strFilePath.QueryStr()); + + if ( pNewAppOfflineHtm != NULL ) + { + if (pNewAppOfflineHtm->Load()) + { + // + // loaded the new app_offline.htm + // + pOldAppOfflineHtm = (APP_OFFLINE_HTM *)InterlockedExchangePointer((VOID**)&m_pAppOfflineHtm, pNewAppOfflineHtm); + + if (pOldAppOfflineHtm != NULL) + { + pOldAppOfflineHtm->DereferenceAppOfflineHtm(); + pOldAppOfflineHtm = NULL; + } + } + else + { + // ignored the new app_offline file because the file does not exist. + pNewAppOfflineHtm->DereferenceAppOfflineHtm(); + pNewAppOfflineHtm = NULL; + } + } + } +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/applicationmanager.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/applicationmanager.cxx index 5b3e2ad93a..41f20013b7 100644 --- a/src/AspNetCoreModuleV1/AspNetCore/src/applicationmanager.cxx +++ b/src/AspNetCoreModuleV1/AspNetCore/src/applicationmanager.cxx @@ -1,81 +1,41 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. #include "precomp.hxx" -// The application manager is a singleton across ANCM. APPLICATION_MANAGER* APPLICATION_MANAGER::sm_pApplicationManager = NULL; -// -// Retrieves the application info from the application manager -// Will create the application info if it isn't initalized -// HRESULT -APPLICATION_MANAGER::GetOrCreateApplicationInfo( - _In_ IHttpServer* pServer, - _In_ ASPNETCORE_CONFIG* pConfig, - _Out_ APPLICATION_INFO ** ppApplicationInfo +APPLICATION_MANAGER::GetApplication( + _In_ IHttpContext* pContext, + _Out_ APPLICATION ** ppApplication ) { - HRESULT hr = S_OK; - APPLICATION_INFO *pApplicationInfo = NULL; - APPLICATION_INFO_KEY key; - BOOL fExclusiveLock = FALSE; - BOOL fMixedHostingModelError = FALSE; - BOOL fDuplicatedInProcessApp = FALSE; - PCWSTR pszApplicationId = NULL; + HRESULT hr = S_OK; + APPLICATION *pApplication = NULL; + APPLICATION_KEY key; + BOOL fExclusiveLock = FALSE; + PCWSTR pszApplicationId = NULL; - DBG_ASSERT(pServer); - DBG_ASSERT(pConfig); - DBG_ASSERT(ppApplicationInfo); + *ppApplication = NULL; + + DBG_ASSERT(pContext != NULL); + DBG_ASSERT(pContext->GetApplication() != NULL); + pszApplicationId = pContext->GetApplication()->GetApplicationId(); - *ppApplicationInfo = NULL; - - // The configuration path is unique for each application and is used for the - // key in the applicationInfoHash. - pszApplicationId = pConfig->QueryConfigPath()->QueryStr(); hr = key.Initialize(pszApplicationId); if (FAILED(hr)) { goto Finished; } - // When accessing the m_pApplicationInfoHash, we need to acquire the application manager - // lock to avoid races on setting state. - AcquireSRWLockShared(&m_srwLock); - if (g_fInShutdown) + m_pApplicationHash->FindKey(&key, ppApplication); + + if (*ppApplication == NULL) { - ReleaseSRWLockShared(&m_srwLock); - hr = HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS); - goto Finished; - } - m_pApplicationInfoHash->FindKey(&key, ppApplicationInfo); - ReleaseSRWLockShared(&m_srwLock); - if (*ppApplicationInfo == NULL) - { - // Check which hosting model we want to support - switch (pConfig->QueryHostingModel()) - { - case HOSTING_IN_PROCESS: - if (m_pApplicationInfoHash->Count() > 0) - { - // Only one inprocess app is allowed per IIS worker process - fDuplicatedInProcessApp = TRUE; - hr = HRESULT_FROM_WIN32(ERROR_APP_INIT_FAILURE); - goto Finished; - } - break; - - case HOSTING_OUT_PROCESS: - break; - - default: - hr = E_UNEXPECTED; - goto Finished; - } - pApplicationInfo = new APPLICATION_INFO(pServer); - if (pApplicationInfo == NULL) + pApplication = new APPLICATION(); + if (pApplication == NULL) { hr = E_OUTOFMEMORY; goto Finished; @@ -83,353 +43,136 @@ APPLICATION_MANAGER::GetOrCreateApplicationInfo( AcquireSRWLockExclusive(&m_srwLock); fExclusiveLock = TRUE; - if (g_fInShutdown) - { - // Already in shuting down. No need to create the application - hr = HRESULT_FROM_WIN32(ERROR_SERVER_SHUTDOWN_IN_PROGRESS); - goto Finished; - } - m_pApplicationInfoHash->FindKey(&key, ppApplicationInfo); + m_pApplicationHash->FindKey(&key, ppApplication); - if (*ppApplicationInfo != NULL) + if (*ppApplication != NULL) { // someone else created the application - delete pApplicationInfo; - pApplicationInfo = NULL; + delete pApplication; + pApplication = NULL; goto Finished; } - // hosting model check. We do not allow mixed scenario for now - // could be changed in the future - if (m_hostingModel != HOSTING_UNKNOWN) - { - if (m_hostingModel != pConfig->QueryHostingModel()) - { - // hosting model does not match, error out - fMixedHostingModelError = TRUE; - hr = HRESULT_FROM_WIN32(ERROR_APP_INIT_FAILURE); - goto Finished; - } - } - - hr = pApplicationInfo->Initialize(pConfig, m_pFileWatcher); + hr = pApplication->Initialize(this, pszApplicationId, pContext->GetApplication()->GetApplicationPhysicalPath()); if (FAILED(hr)) { goto Finished; } - hr = m_pApplicationInfoHash->InsertRecord( pApplicationInfo ); + hr = m_pApplicationHash->InsertRecord( pApplication ); + if (FAILED(hr)) { goto Finished; } - - // - // first application will decide which hosting model allowed by this process - // - if (m_hostingModel == HOSTING_UNKNOWN) - { - m_hostingModel = pConfig->QueryHostingModel(); - } - - *ppApplicationInfo = pApplicationInfo; - pApplicationInfo->StartMonitoringAppOffline(); - - // Release the locko after starting to monitor for app offline to avoid races with it being dropped. ReleaseSRWLockExclusive(&m_srwLock); - fExclusiveLock = FALSE; - pApplicationInfo = NULL; + + pApplication->StartMonitoringAppOffline(); + + *ppApplication = pApplication; + pApplication = NULL; } Finished: + if (fExclusiveLock == TRUE) + ReleaseSRWLockExclusive(&m_srwLock); + + if (FAILED(hr)) + { + if (pApplication != NULL) + { + pApplication->DereferenceApplication(); + pApplication = NULL; + } + } + + return hr; +} + + +HRESULT +APPLICATION_MANAGER::RecycleApplication( + _In_ LPCWSTR pszApplication +) +{ + HRESULT hr = S_OK; + APPLICATION_KEY key; + + hr = key.Initialize(pszApplication); + if (FAILED(hr)) + { + goto Finished; + } + AcquireSRWLockExclusive(&m_srwLock); + m_pApplicationHash->DeleteKey(&key); + ReleaseSRWLockExclusive(&m_srwLock); + +Finished: + + return hr; +} + +HRESULT +APPLICATION_MANAGER::Get502ErrorPage( + _Out_ HTTP_DATA_CHUNK** ppErrorPage +) +{ + HRESULT hr = S_OK; + BOOL fExclusiveLock = FALSE; + HTTP_DATA_CHUNK *pHttp502ErrorPage = NULL; + + DBG_ASSERT(ppErrorPage != NULL); + + //on-demand create the error page + if (m_pHttp502ErrorPage != NULL) + { + *ppErrorPage = m_pHttp502ErrorPage; + } + else + { + AcquireSRWLockExclusive(&m_srwLock); + fExclusiveLock = TRUE; + if (m_pHttp502ErrorPage != NULL) + { + *ppErrorPage = m_pHttp502ErrorPage; + } + else + { + size_t maxsize = 5000; + pHttp502ErrorPage = new HTTP_DATA_CHUNK(); + if (pHttp502ErrorPage == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + goto Finished; + } + pHttp502ErrorPage->DataChunkType = HttpDataChunkFromMemory; + pHttp502ErrorPage->FromMemory.pBuffer = (PVOID)m_pstrErrorInfo; + + pHttp502ErrorPage->FromMemory.BufferLength = (ULONG)strnlen(m_pstrErrorInfo, maxsize); //(ULONG)(wcslen(m_pstrErrorInfo)); // *sizeof(WCHAR); + if(m_pHttp502ErrorPage != NULL) + { + delete m_pHttp502ErrorPage; + } + m_pHttp502ErrorPage = pHttp502ErrorPage; + *ppErrorPage = m_pHttp502ErrorPage; + } + } + +Finished: if (fExclusiveLock) { ReleaseSRWLockExclusive(&m_srwLock); } - if (pApplicationInfo != NULL) - { - pApplicationInfo->DereferenceApplicationInfo(); - pApplicationInfo = NULL; - } - if (FAILED(hr)) { - if (fDuplicatedInProcessApp) + if (pHttp502ErrorPage != NULL) { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP, - ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG, - pszApplicationId); - } - else if (fMixedHostingModelError) - { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR, - ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG, - pszApplicationId, - pConfig->QueryHostingModel()); - } - else - { - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_ADD_APPLICATION_ERROR, - ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG, - pszApplicationId, - hr); + delete pHttp502ErrorPage; } } return hr; } - - -// -// If the application's configuration was changed, -// append the configuration path to the config change context. -// -BOOL -APPLICATION_MANAGER::FindConfigChangedApplication( - _In_ APPLICATION_INFO * pEntry, - _In_ PVOID pvContext) -{ - DBG_ASSERT(pEntry); - DBG_ASSERT(pvContext); - - // Config Change context contains the original config path that changed - // and a multiStr containing - CONFIG_CHANGE_CONTEXT* pContext = static_cast(pvContext); - STRU* pstruConfigPath = pEntry->QueryConfig()->QueryConfigPath(); - - // check if the application path contains our app/subapp by seeing if the config path - // starts with the notification path. - BOOL fChanged = pstruConfigPath->StartsWith(pContext->pstrPath, true); - if (fChanged) - { - DWORD dwLen = (DWORD)wcslen(pContext->pstrPath); - WCHAR wChar = pstruConfigPath->QueryStr()[dwLen]; - - // We need to check that the last character of the config path - // is either a null terminator or a slash. - // This checks the case where the config path was - // MACHINE/WEBROOT/site and your site path is MACHINE/WEBROOT/siteTest - if (wChar != L'\0' && wChar != L'/') - { - // not current app or sub app - fChanged = FALSE; - } - else - { - pContext->MultiSz.Append(*pstruConfigPath); - } - } - return fChanged; -} - -// -// Finds any applications affected by a configuration change and calls Recycle on them -// InProcess: Triggers g_httpServer->RecycleProcess() and keep the application inside of the manager. -// This will cause a shutdown event to occur through the global stop listening event. -// OutOfProcess: Removes all applications in the application manager and calls Recycle, which will call Shutdown, -// on each application. -// -HRESULT -APPLICATION_MANAGER::RecycleApplicationFromManager( - _In_ LPCWSTR pszApplicationId -) -{ - HRESULT hr = S_OK; - APPLICATION_INFO_KEY key; - DWORD dwPreviousCounter = 0; - APPLICATION_INFO_HASH* table = NULL; - CONFIG_CHANGE_CONTEXT context; - BOOL fKeepTable = FALSE; - - if (g_fInShutdown) - { - // We are already shutting down, ignore this event as a global configuration change event - // can occur after global stop listening for some reason. - return hr; - } - - AcquireSRWLockExclusive(&m_srwLock); - if (g_fInShutdown) - { - ReleaseSRWLockExclusive(&m_srwLock); - return hr; - } - - hr = key.Initialize(pszApplicationId); - if (FAILED(hr)) - { - goto Finished; - } - // Make a shallow copy of existing hashtable as we may need to remove nodes - // This will be used for finding differences in which applications are affected by a config change. - table = new APPLICATION_INFO_HASH(); - - if (table == NULL) - { - hr = E_OUTOFMEMORY; - goto Finished; - } - - // few application expected, small bucket size for hash table - if (FAILED(hr = table->Initialize(17 /*prime*/))) - { - goto Finished; - } - - context.pstrPath = pszApplicationId; - - // Keep track of the preview count of applications to know whether there are applications to delete - dwPreviousCounter = m_pApplicationInfoHash->Count(); - - // We don't want to hold the application manager lock for long time as it will block all incoming requests - // Don't call application shutdown inside the lock - m_pApplicationInfoHash->Apply(APPLICATION_INFO_HASH::ReferenceCopyToTable, static_cast(table)); - DBG_ASSERT(dwPreviousCounter == table->Count()); - - // Removed the applications which are impacted by the configurtion change - m_pApplicationInfoHash->DeleteIf(FindConfigChangedApplication, (PVOID)&context); - - if (dwPreviousCounter != m_pApplicationInfoHash->Count()) - { - if (m_hostingModel == HOSTING_IN_PROCESS) - { - // When we are inprocess, we need to keep the application the - // application manager that is being deleted. This is because we will always need to recycle the worker - // process and any requests that hit this worker process must be rejected (while out of process can - // start a new dotnet process). We will immediately call Recycle after this call. - DBG_ASSERT(m_pApplicationInfoHash->Count() == 0); - delete m_pApplicationInfoHash; - - // We don't want to delete the table as m_pApplicationInfoHash = table - fKeepTable = TRUE; - m_pApplicationInfoHash = table; - } - } - - if (m_pApplicationInfoHash->Count() == 0) - { - m_hostingModel = HOSTING_UNKNOWN; - } - - ReleaseSRWLockExclusive(&m_srwLock); - - // If we receive a request at this point. - // OutOfProcess: we will create a new application with new configuration - // InProcess: the request would have to be rejected, as we are about to call g_HttpServer->RecycleProcess - // on the worker proocess - if (!context.MultiSz.IsEmpty()) - { - PCWSTR path = context.MultiSz.First(); - // Iterate through each of the paths that were shut down, - // calling RecycleApplication on each of them. - while (path != NULL) - { - APPLICATION_INFO* pRecord; - - // Application got recycled. Log an event - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_INFORMATION_TYPE, - ASPNETCORE_EVENT_RECYCLE_CONFIGURATION, - ASPNETCORE_EVENT_RECYCLE_CONFIGURATION_MSG, - path); - - hr = key.Initialize(path); - if (FAILED(hr)) - { - goto Finished; - } - - table->FindKey(&key, &pRecord); - DBG_ASSERT(pRecord != NULL); - - // RecycleApplication is called on a separate thread. - pRecord->RecycleApplication(); - pRecord->DereferenceApplicationInfo(); - path = context.MultiSz.Next(path); - } - } - -Finished: - if (table != NULL && !fKeepTable) - { - table->Clear(); - delete table; - } - - if (FAILED(hr)) - { - // Failed to recycle an application. Log an event - UTILITY::LogEventF(g_hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_RECYCLE_APP_FAILURE, - ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG, - pszApplicationId); - // Need to recycle the process as we cannot recycle the application - if (!g_fRecycleProcessCalled) - { - g_fRecycleProcessCalled = TRUE; - g_pHttpServer->RecycleProcess(L"AspNetCore Recycle Process on Demand Due Application Recycle Error"); - } - } - - return hr; -} - -// -// Shutsdown all applications in the application hashtable -// Only called by OnGlobalStopListening. -// -VOID -APPLICATION_MANAGER::ShutDown() -{ - // We are guaranteed to only have one outstanding OnGlobalStopListening event at a time - // However, it is possible to receive multiple OnGlobalStopListening events - // Protect against this by checking if we already shut down. - g_fInShutdown = TRUE; - if (m_pApplicationInfoHash != NULL) - { - if (m_pFileWatcher != NULL) - { - delete m_pFileWatcher; - m_pFileWatcher = NULL; - } - - DBG_ASSERT(m_pApplicationInfoHash); - // During shutdown we lock until we delete the application - AcquireSRWLockExclusive(&m_srwLock); - - // Call shutdown on each application in the application manager - m_pApplicationInfoHash->Apply(ShutdownApplication, NULL); - m_pApplicationInfoHash->Clear(); - delete m_pApplicationInfoHash; - m_pApplicationInfoHash = NULL; - - ReleaseSRWLockExclusive(&m_srwLock); - } -} - -// -// Calls shutdown on each application. ApplicationManager's lock is held for duration of -// each shutdown call, guaranteeing another application cannot be created. -// -// static -VOID -APPLICATION_MANAGER::ShutdownApplication( - _In_ APPLICATION_INFO * pEntry, - _In_ PVOID pvContext -) -{ - UNREFERENCED_PARAMETER(pvContext); - DBG_ASSERT(pEntry != NULL); - - pEntry->ShutDownApplication(); -} diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/aspnetcoreconfig.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/aspnetcoreconfig.cxx new file mode 100644 index 0000000000..d8bcd8d777 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/aspnetcoreconfig.cxx @@ -0,0 +1,416 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +ASPNETCORE_CONFIG::~ASPNETCORE_CONFIG() +{ + // + // the destructor will be called once IIS decides to recycle the module context (i.e., application) + // + if (!m_struApplication.IsEmpty()) + { + APPLICATION_MANAGER::GetInstance()->RecycleApplication(m_struApplication.QueryStr()); + } + if(m_pEnvironmentVariables != NULL) + { + m_pEnvironmentVariables->Clear(); + delete m_pEnvironmentVariables; + m_pEnvironmentVariables = NULL; + } +} + +HRESULT +ASPNETCORE_CONFIG::GetConfig( + _In_ IHttpContext *pHttpContext, + _Out_ ASPNETCORE_CONFIG **ppAspNetCoreConfig +) +{ + HRESULT hr = S_OK; + IHttpApplication *pHttpApplication = pHttpContext->GetApplication(); + ASPNETCORE_CONFIG *pAspNetCoreConfig = NULL; + + if (ppAspNetCoreConfig == NULL) + { + hr = E_INVALIDARG; + goto Finished; + } + + *ppAspNetCoreConfig = NULL; + + // potential bug if user sepcific config at virtual dir level + pAspNetCoreConfig = (ASPNETCORE_CONFIG*) + pHttpApplication->GetModuleContextContainer()->GetModuleContext(g_pModuleId); + + if (pAspNetCoreConfig != NULL) + { + *ppAspNetCoreConfig = pAspNetCoreConfig; + pAspNetCoreConfig = NULL; + goto Finished; + } + + pAspNetCoreConfig = new ASPNETCORE_CONFIG; + if (pAspNetCoreConfig == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = pAspNetCoreConfig->Populate(pHttpContext); + if (FAILED(hr)) + { + goto Finished; + } + + hr = pHttpApplication->GetModuleContextContainer()-> + SetModuleContext(pAspNetCoreConfig, g_pModuleId); + if (FAILED(hr)) + { + if (hr == HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)) + { + delete pAspNetCoreConfig; + + pAspNetCoreConfig = (ASPNETCORE_CONFIG*)pHttpApplication-> + GetModuleContextContainer()-> + GetModuleContext(g_pModuleId); + + _ASSERT(pAspNetCoreConfig != NULL); + + hr = S_OK; + } + else + { + goto Finished; + } + } + else + { + // set appliction info here instead of inside Populate() + // as the destructor will delete the backend process + hr = pAspNetCoreConfig->QueryApplicationPath()->Copy(pHttpApplication->GetApplicationId()); + if (FAILED(hr)) + { + goto Finished; + } + } + + *ppAspNetCoreConfig = pAspNetCoreConfig; + pAspNetCoreConfig = NULL; + +Finished: + + if (pAspNetCoreConfig != NULL) + { + delete pAspNetCoreConfig; + pAspNetCoreConfig = NULL; + } + + return hr; +} + +HRESULT +ASPNETCORE_CONFIG::Populate( + IHttpContext *pHttpContext +) +{ + HRESULT hr = S_OK; + STACK_STRU(strSiteConfigPath, 256); + STRU strEnvName; + STRU strEnvValue; + STRU strExpandedEnvValue; + IAppHostAdminManager *pAdminManager = NULL; + IAppHostElement *pAspNetCoreElement = NULL; + IAppHostElement *pWindowsAuthenticationElement = NULL; + IAppHostElement *pBasicAuthenticationElement = NULL; + IAppHostElement *pAnonymousAuthenticationElement = NULL; + IAppHostElement *pEnvVarList = NULL; + IAppHostElement *pEnvVar = NULL; + IAppHostElementCollection *pEnvVarCollection = NULL; + ULONGLONG ullRawTimeSpan = 0; + ENUM_INDEX index; + ENVIRONMENT_VAR_ENTRY* pEntry = NULL; + + m_pEnvironmentVariables = new ENVIRONMENT_VAR_HASH(); + if (m_pEnvironmentVariables == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + if (FAILED(hr = m_pEnvironmentVariables->Initialize(37 /*prime*/))) + { + delete m_pEnvironmentVariables; + m_pEnvironmentVariables = NULL; + goto Finished; + } + + pAdminManager = g_pHttpServer->GetAdminManager(); + + hr = strSiteConfigPath.Copy(pHttpContext->GetApplication()->GetAppConfigPath()); + if (FAILED(hr)) + { + goto Finished; + } + + hr = pAdminManager->GetAdminSection(CS_WINDOWS_AUTHENTICATION_SECTION, + strSiteConfigPath.QueryStr(), + &pWindowsAuthenticationElement); + if (FAILED(hr)) + { + // assume the corresponding authen was not enabled + // as the section may get deleted by user in some HWC case + // ToDo: log a warning to event log + m_fWindowsAuthEnabled = FALSE; + } + else + { + hr = GetElementBoolProperty(pWindowsAuthenticationElement, + CS_AUTHENTICATION_ENABLED, + &m_fWindowsAuthEnabled); + if (FAILED(hr)) + { + goto Finished; + } + } + + hr = pAdminManager->GetAdminSection(CS_BASIC_AUTHENTICATION_SECTION, + strSiteConfigPath.QueryStr(), + &pBasicAuthenticationElement); + if (FAILED(hr)) + { + m_fBasicAuthEnabled = FALSE; + } + else + { + hr = GetElementBoolProperty(pBasicAuthenticationElement, + CS_AUTHENTICATION_ENABLED, + &m_fBasicAuthEnabled); + if (FAILED(hr)) + { + goto Finished; + } + } + + hr = pAdminManager->GetAdminSection(CS_ANONYMOUS_AUTHENTICATION_SECTION, + strSiteConfigPath.QueryStr(), + &pAnonymousAuthenticationElement); + if (FAILED(hr)) + { + m_fAnonymousAuthEnabled = FALSE; + } + else + { + hr = GetElementBoolProperty(pAnonymousAuthenticationElement, + CS_AUTHENTICATION_ENABLED, + &m_fAnonymousAuthEnabled); + if (FAILED(hr)) + { + goto Finished; + } + } + + hr = pAdminManager->GetAdminSection(CS_ASPNETCORE_SECTION, + strSiteConfigPath.QueryStr(), + &pAspNetCoreElement); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_EXE_PATH, + &m_struProcessPath); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_ARGUMENTS, + &m_struArguments); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementDWORDProperty(pAspNetCoreElement, + CS_ASPNETCORE_RAPID_FAILS_PER_MINUTE, + &m_dwRapidFailsPerMinute); + if (FAILED(hr)) + { + goto Finished; + } + + // + // rapidFailsPerMinute cannot be greater than 100. + // + if (m_dwRapidFailsPerMinute > MAX_RAPID_FAILS_PER_MINUTE) + { + m_dwRapidFailsPerMinute = MAX_RAPID_FAILS_PER_MINUTE; + } + + hr = GetElementDWORDProperty(pAspNetCoreElement, + CS_ASPNETCORE_PROCESSES_PER_APPLICATION, + &m_dwProcessesPerApplication); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementDWORDProperty( + pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_STARTUP_TIME_LIMIT, + &m_dwStartupTimeLimitInMS + ); + if (FAILED(hr)) + { + goto Finished; + } + + m_dwStartupTimeLimitInMS *= MILLISECONDS_IN_ONE_SECOND; + + hr = GetElementDWORDProperty( + pAspNetCoreElement, + CS_ASPNETCORE_PROCESS_SHUTDOWN_TIME_LIMIT, + &m_dwShutdownTimeLimitInMS + ); + if (FAILED(hr)) + { + goto Finished; + } + m_dwShutdownTimeLimitInMS *= MILLISECONDS_IN_ONE_SECOND; + + hr = GetElementBoolProperty(pAspNetCoreElement, + CS_ASPNETCORE_FORWARD_WINDOWS_AUTH_TOKEN, + &m_fForwardWindowsAuthToken); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementBoolProperty(pAspNetCoreElement, + CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE, + &m_fDisableStartUpErrorPage); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementRawTimeSpanProperty( + pAspNetCoreElement, + CS_ASPNETCORE_WINHTTP_REQUEST_TIMEOUT, + &ullRawTimeSpan + ); + if (FAILED(hr)) + { + goto Finished; + } + + m_dwRequestTimeoutInMS = (DWORD)TIMESPAN_IN_MILLISECONDS(ullRawTimeSpan); + + hr = GetElementBoolProperty(pAspNetCoreElement, + CS_ASPNETCORE_STDOUT_LOG_ENABLED, + &m_fStdoutLogEnabled); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementStringProperty(pAspNetCoreElement, + CS_ASPNETCORE_STDOUT_LOG_FILE, + &m_struStdoutLogFile); + if (FAILED(hr)) + { + goto Finished; + } + + hr = GetElementChildByName(pAspNetCoreElement, + CS_ASPNETCORE_ENVIRONMENT_VARIABLES, + &pEnvVarList); + if (FAILED(hr)) + { + goto Finished; + } + + hr = pEnvVarList->get_Collection(&pEnvVarCollection); + if (FAILED(hr)) + { + goto Finished; + } + + for (hr = FindFirstElement(pEnvVarCollection, &index, &pEnvVar); + SUCCEEDED(hr); + hr = FindNextElement(pEnvVarCollection, &index, &pEnvVar)) + { + if (hr == S_FALSE) + { + hr = S_OK; + break; + } + + if (FAILED(hr = GetElementStringProperty(pEnvVar, + CS_ASPNETCORE_ENVIRONMENT_VARIABLE_NAME, + &strEnvName)) || + FAILED(hr = GetElementStringProperty(pEnvVar, + CS_ASPNETCORE_ENVIRONMENT_VARIABLE_VALUE, + &strEnvValue)) || + FAILED(hr = strEnvName.Append(L"=")) || + FAILED(hr = STRU::ExpandEnvironmentVariables(strEnvValue.QueryStr(), &strExpandedEnvValue))) + { + goto Finished; + } + + pEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (FAILED(hr = pEntry->Initialize(strEnvName.QueryStr(), strExpandedEnvValue.QueryStr())) || + FAILED(hr = m_pEnvironmentVariables->InsertRecord(pEntry))) + { + goto Finished; + } + strEnvName.Reset(); + strEnvValue.Reset(); + strExpandedEnvValue.Reset(); + pEnvVar->Release(); + pEnvVar = NULL; + pEntry->Dereference(); + pEntry = NULL; + } + +Finished: + + if (pAspNetCoreElement != NULL) + { + pAspNetCoreElement->Release(); + pAspNetCoreElement = NULL; + } + + if (pEnvVarList != NULL) + { + pEnvVarList->Release(); + pEnvVarList = NULL; + } + + if (pEnvVar != NULL) + { + pEnvVar->Release(); + pEnvVar = NULL; + } + + if (pEnvVarCollection != NULL) + { + pEnvVarCollection->Release(); + pEnvVarCollection = NULL; + } + + if (pEntry != NULL) + { + pEntry->Dereference(); + pEntry = NULL; + } + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/filewatcher.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/filewatcher.cxx new file mode 100644 index 0000000000..086ed8774f --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/filewatcher.cxx @@ -0,0 +1,481 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +FILE_WATCHER::FILE_WATCHER() : + m_hCompletionPort(NULL), + m_hChangeNotificationThread(NULL) +{ +} + +FILE_WATCHER::~FILE_WATCHER() +{ + if (m_hChangeNotificationThread != NULL) + { + CloseHandle(m_hChangeNotificationThread); + m_hChangeNotificationThread = NULL; + } +} + +HRESULT +FILE_WATCHER::Create( + VOID +) +{ + HRESULT hr = S_OK; + + m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, + NULL, + 0, + 0); + + if (m_hCompletionPort == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + m_hChangeNotificationThread = CreateThread(NULL, + 0, + ChangeNotificationThread, + this, + 0, + NULL); + + if (m_hChangeNotificationThread == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + + CloseHandle(m_hCompletionPort); + m_hCompletionPort = NULL; + + goto Finished; + } + +Finished: + return hr; +} + +DWORD +WINAPI +FILE_WATCHER::ChangeNotificationThread( + LPVOID pvArg +) +/*++ + +Routine Description: + +IO completion thread + +Arguments: + +None + +Return Value: + +Win32 error + +--*/ +{ + FILE_WATCHER * pFileMonitor; + BOOL fSuccess = FALSE; + DWORD cbCompletion = 0; + OVERLAPPED * pOverlapped = NULL; + DWORD dwErrorStatus; + ULONG_PTR completionKey; + + pFileMonitor = (FILE_WATCHER*)pvArg; + DBG_ASSERT(pFileMonitor != NULL); + + while (TRUE) + { + fSuccess = GetQueuedCompletionStatus( + pFileMonitor->m_hCompletionPort, + &cbCompletion, + &completionKey, + &pOverlapped, + INFINITE); + + DBG_ASSERT(fSuccess); + DebugPrint(1, "FILE_WATCHER::ChangeNotificationThread"); + dwErrorStatus = fSuccess ? ERROR_SUCCESS : GetLastError(); + + if (completionKey == FILE_WATCHER_SHUTDOWN_KEY) + { + continue; + } + + DBG_ASSERT(pOverlapped != NULL); + if (pOverlapped != NULL) + { + FileWatcherCompletionRoutine( + dwErrorStatus, + cbCompletion, + pOverlapped); + } + pOverlapped = NULL; + cbCompletion = 0; + } +} + +VOID +WINAPI +FILE_WATCHER::FileWatcherCompletionRoutine( + DWORD dwCompletionStatus, + DWORD cbCompletion, + OVERLAPPED * pOverlapped +) +/*++ + +Routine Description: + +Called when ReadDirectoryChangesW() completes + +Arguments: + +dwCompletionStatus - Error of completion +cbCompletion - Bytes of completion +pOverlapped - State of completion + +Return Value: + +None + +--*/ +{ + FILE_WATCHER_ENTRY * pMonitorEntry; + pMonitorEntry = CONTAINING_RECORD(pOverlapped, FILE_WATCHER_ENTRY, _overlapped); + pMonitorEntry->DereferenceFileWatcherEntry(); + DBG_ASSERT(pMonitorEntry != NULL); + + pMonitorEntry->HandleChangeCompletion(dwCompletionStatus, cbCompletion); + + if (pMonitorEntry->QueryIsValid()) + { + // + // Continue monitoring + // + pMonitorEntry->Monitor(); + } + else + { + // + // Marked by application distructor + // Deference the entry to delete it + // + pMonitorEntry->DereferenceFileWatcherEntry(); + } +} + + +FILE_WATCHER_ENTRY::FILE_WATCHER_ENTRY(FILE_WATCHER * pFileMonitor) : + _pFileMonitor(pFileMonitor), + _hDirectory(INVALID_HANDLE_VALUE), + _hImpersonationToken(NULL), + _pApplication(NULL), + _lStopMonitorCalled(0), + _cRefs(1), + _fIsValid(TRUE) +{ + _dwSignature = FILE_WATCHER_ENTRY_SIGNATURE; + InitializeSRWLock(&_srwLock); +} + +FILE_WATCHER_ENTRY::~FILE_WATCHER_ENTRY() +{ + _dwSignature = FILE_WATCHER_ENTRY_SIGNATURE_FREE; + + if (_hDirectory != INVALID_HANDLE_VALUE) + { + CloseHandle(_hDirectory); + _hDirectory = INVALID_HANDLE_VALUE; + } + + if (_hImpersonationToken != NULL) + { + CloseHandle(_hImpersonationToken); + _hImpersonationToken = NULL; + } +} + +#pragma warning(disable:4100) + +HRESULT +FILE_WATCHER_ENTRY::HandleChangeCompletion( + _In_ DWORD dwCompletionStatus, + _In_ DWORD cbCompletion +) +/*++ + +Routine Description: + +Handle change notification (see if any of associated config files +need to be flushed) + +Arguments: + +dwCompletionStatus - Completion status +cbCompletion - Bytes of completion + +Return Value: + +HRESULT + +--*/ +{ + HRESULT hr = S_OK; + FILE_NOTIFY_INFORMATION * pNotificationInfo; + BOOL fFileChanged = FALSE; + STRU strEventMsg; + + AcquireSRWLockExclusive(&_srwLock); + if (!_fIsValid) + { + goto Finished; + } + + // When directory handle is closed then HandleChangeCompletion + // happens with cbCompletion = 0 and dwCompletionStatus = 0 + // From documentation it is not clear if that combination + // of return values is specific to closing handles or whether + // it could also mean an error condition. Hence we will maintain + // explicit flag that will help us determine if entry + // is being shutdown (StopMonitor() called) + // + if (_lStopMonitorCalled) + { + goto Finished; + } + + // + // There could be a FCN overflow + // Let assume the file got changed instead of checking files + // Othersie we have to cache the file info + // + if (cbCompletion == 0) + { + fFileChanged = TRUE; + } + else + { + pNotificationInfo = (FILE_NOTIFY_INFORMATION*)_buffDirectoryChanges.QueryPtr(); + DBG_ASSERT(pNotificationInfo != NULL); + + while (pNotificationInfo != NULL) + { + // + // check whether the monitored file got changed + // + if (_wcsnicmp(pNotificationInfo->FileName, + _strFileName.QueryStr(), + pNotificationInfo->FileNameLength/sizeof(WCHAR)) == 0) + { + fFileChanged = TRUE; + break; + } + // + // Advance to next notification + // + if (pNotificationInfo->NextEntryOffset == 0) + { + pNotificationInfo = NULL; + } + else + { + pNotificationInfo = (FILE_NOTIFY_INFORMATION*) + ((PBYTE)pNotificationInfo + + pNotificationInfo->NextEntryOffset); + } + } + } + + if (fFileChanged) + { + LPCWSTR apsz[1]; + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG, + _strFileName.QueryStr()))) + { + apsz[0] = strEventMsg.QueryStr(); + + // + // not checking return code because if ReportEvent + // fails, we cannot do anything. + // + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_INFORMATION_TYPE, + 0, + ASPNETCORE_EVENT_RECYCLE_APPOFFLINE, + NULL, + 1, + 0, + apsz, + NULL); + } + } + // + // so far we only monitoring app_offline + // + _pApplication->UpdateAppOfflineFileHandle(); + } + +Finished: + ReleaseSRWLockExclusive(&_srwLock); + return hr; +} + +#pragma warning( error : 4100 ) + +HRESULT +FILE_WATCHER_ENTRY::Monitor(VOID) +{ + HRESULT hr = S_OK; + DWORD cbRead; + + AcquireSRWLockExclusive(&_srwLock); + ReferenceFileWatcherEntry(); + ZeroMemory(&_overlapped, sizeof(_overlapped)); + + if(!ReadDirectoryChangesW(_hDirectory, + _buffDirectoryChanges.QueryPtr(), + _buffDirectoryChanges.QuerySize(), + FALSE, // Watching sub dirs. Set to False now as only monitoring app_offline + FILE_NOTIFY_VALID_MASK & ~FILE_NOTIFY_CHANGE_LAST_ACCESS, + &cbRead, + &_overlapped, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + + ReleaseSRWLockExclusive(&_srwLock); + return hr; +} + +VOID +FILE_WATCHER_ENTRY::StopMonitor(VOID) +{ + // + // Flag that monitoring is being stopped so that + // we know that HandleChangeCompletion() call + // can be ignored + // + InterlockedExchange(&_lStopMonitorCalled, 1); + + AcquireSRWLockExclusive(&_srwLock); + + if (_hDirectory != INVALID_HANDLE_VALUE) + { + CloseHandle(_hDirectory); + _hDirectory = INVALID_HANDLE_VALUE; + } + + ReleaseSRWLockExclusive(&_srwLock); +} + +HRESULT +FILE_WATCHER_ENTRY::Create( + _In_ PCWSTR pszDirectoryToMonitor, + _In_ PCWSTR pszFileNameToMonitor, + _In_ APPLICATION* pApplication, + _In_ HANDLE hImpersonationToken +) +{ + HRESULT hr = S_OK; + BOOL fRet = FALSE; + + if (pszDirectoryToMonitor == NULL || + pszFileNameToMonitor == NULL || + pApplication == NULL) + { + DBG_ASSERT(FALSE); + hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + goto Finished; + } + + // + //remember the application + // + _pApplication = pApplication; + + if (FAILED(hr = _strFileName.Copy(pszFileNameToMonitor))) + { + goto Finished; + } + + if (FAILED(hr = _strDirectoryName.Copy(pszDirectoryToMonitor))) + { + goto Finished; + } + + // + // Resize change buffer to something "reasonable" + // + if (!_buffDirectoryChanges.Resize(FILE_WATCHER_ENTRY_BUFFER_SIZE)) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + goto Finished; + } + + if (hImpersonationToken != NULL) + { + fRet = DuplicateHandle(GetCurrentProcess(), + hImpersonationToken, + GetCurrentProcess(), + &_hImpersonationToken, + 0, + FALSE, + DUPLICATE_SAME_ACCESS); + + if (!fRet) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + } + else + { + if (_hImpersonationToken != NULL) + { + CloseHandle(_hImpersonationToken); + _hImpersonationToken = NULL; + } + } + + _hDirectory = CreateFileW( + _strDirectoryName.QueryStr(), + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + NULL); + + if (_hDirectory == INVALID_HANDLE_VALUE) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (CreateIoCompletionPort( + _hDirectory, + _pFileMonitor->QueryCompletionPort(), + NULL, + 0) == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // + // Start monitoring + // + hr = Monitor(); + +Finished: + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/forwarderconnection.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/forwarderconnection.cxx new file mode 100644 index 0000000000..ed1e29aadd --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/forwarderconnection.cxx @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +FORWARDER_CONNECTION::FORWARDER_CONNECTION( + VOID +) : m_cRefs (1), + m_hConnection (NULL) +{ +} + +HRESULT +FORWARDER_CONNECTION::Initialize( + DWORD dwPort +) +{ + HRESULT hr = S_OK; + + hr = m_ConnectionKey.Initialize( dwPort ); + if ( FAILED( hr ) ) + { + goto Finished; + } + + m_hConnection = WinHttpConnect(FORWARDING_HANDLER::sm_hSession, + L"127.0.0.1", + (USHORT) dwPort, + 0); + if (m_hConnection == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // + // Since WinHttp will not emit WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING + // when closing WebSocket handle on Win8. Register callback at Connect level as a workaround + // + if (WinHttpSetStatusCallback(m_hConnection, + FORWARDING_HANDLER::OnWinHttpCompletion, + WINHTTP_CALLBACK_FLAG_HANDLES, + NULL) == WINHTTP_INVALID_STATUS_CALLBACK) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + +Finished: + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/forwardinghandler.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/forwardinghandler.cxx new file mode 100644 index 0000000000..f02a318f41 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/forwardinghandler.cxx @@ -0,0 +1,3073 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" +#include + +// Just to be aware of the FORWARDING_HANDLER object size. +C_ASSERT(sizeof(FORWARDING_HANDLER) <= 632); + +#define DEF_MAX_FORWARDS 32 +#define HEX_TO_ASCII(c) ((CHAR)(((c) < 10) ? ((c) + '0') : ((c) + 'a' - 10))) + +#define BUFFER_SIZE (8192UL) +#define ENTITY_BUFFER_SIZE (6 + BUFFER_SIZE + 2) +#define STR_ANCM_CHILDREQUEST "ANCM_WasCreateProcessFailure" + +HINTERNET FORWARDING_HANDLER::sm_hSession = NULL; +STRU FORWARDING_HANDLER::sm_strErrorFormat; +HANDLE FORWARDING_HANDLER::sm_hEventLog = NULL; +ALLOC_CACHE_HANDLER * FORWARDING_HANDLER::sm_pAlloc = NULL; +TRACE_LOG * FORWARDING_HANDLER::sm_pTraceLog = NULL; +PROTOCOL_CONFIG FORWARDING_HANDLER::sm_ProtocolConfig; + +FORWARDING_HANDLER::FORWARDING_HANDLER( + __in IHttpContext * pW3Context +) : m_Signature(FORWARDING_HANDLER_SIGNATURE), +m_cRefs(1), +m_dwHandlers(1), +m_pW3Context(pW3Context), +m_hRequest(NULL), +m_fResponseHeadersReceivedAndSet(FALSE), +m_fDoReverseRewriteHeaders(FALSE), +m_msStartTime(0), +m_BytesToReceive(0), +m_BytesToSend(0), +m_pEntityBuffer(NULL), +m_cchLastSend(0), +m_cEntityBuffers(0), +m_cBytesBuffered(0), +m_cMinBufferLimit(0), +m_pszOriginalHostHeader(NULL), +m_RequestStatus(FORWARDER_START), +m_pDisconnect(NULL), +m_pszHeaders(NULL), +m_cchHeaders(0), +m_fWebSocketEnabled(FALSE), +m_cContentLength(0), +m_pWebSocket(NULL), +m_pApplication(NULL), +m_pAppOfflineHtm(NULL), +m_fFinishRequest(FALSE), +m_fClientDisconnected(FALSE), +m_fHasError(FALSE), +m_fServerResetConn(FALSE), +m_fDoneAsyncCompletion(FALSE), +m_fHttpHandleInClose(FALSE), +m_fWebSocketHandleInClose(FALSE) +{ + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER --%p\n", this); + + InitializeSRWLock(&m_RequestLock); +} + +FORWARDING_HANDLER::~FORWARDING_HANDLER( + VOID +) +{ + // + // Destructor has started. + // + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "~FORWARDING_HANDLER --%p\n", this); + + m_Signature = FORWARDING_HANDLER_SIGNATURE_FREE; + + // + // Disconnect notification cleanup would happen first, before + // the FORWARDING_HANDLER instance got removed from m_pSharedhandler list. + // The m_pServer cleanup would happen afterwards, since there may be a + // call pending from SHARED_HANDLER to FORWARDING_HANDLER::SetStatusAndHeaders() + // + DBG_ASSERT(m_pDisconnect == NULL); + + FreeResponseBuffers(); + + if (m_hRequest != NULL) + { + // m_hRequest should have already been closed and set to NULL + // if not, we cannot close it as it may callback and cause AV + // let's do our best job here + WinHttpSetStatusCallback(m_hRequest, + NULL, // Callback Function + WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, + NULL); + WinHttpCloseHandle(m_hRequest); + m_hRequest = NULL; + } + + if(m_pApplication != NULL) + { + m_pApplication->DereferenceApplication(); + m_pApplication = NULL; + } + + if(m_pAppOfflineHtm != NULL) + { + m_pAppOfflineHtm->DereferenceAppOfflineHtm(); + m_pAppOfflineHtm = NULL; + } + + m_pW3Context = NULL; +} + +// static +void * FORWARDING_HANDLER::operator new(size_t) +{ + DBG_ASSERT(sm_pAlloc != NULL); + if (sm_pAlloc == NULL) + { + return NULL; + } + return sm_pAlloc->Alloc(); +} + +// static +void FORWARDING_HANDLER::operator delete(void * pMemory) +{ + DBG_ASSERT(sm_pAlloc != NULL); + if (sm_pAlloc != NULL) + { + sm_pAlloc->Free(pMemory); + } +} + +VOID +FORWARDING_HANDLER::ReferenceForwardingHandler( + VOID +) const +{ + LONG cRefs = InterlockedIncrement(&m_cRefs); + if (sm_pTraceLog != NULL) + { + WriteRefTraceLog(sm_pTraceLog, + cRefs, + this); + } +} + +VOID +FORWARDING_HANDLER::DereferenceForwardingHandler( + VOID +) const +{ + DBG_ASSERT(m_cRefs != 0); + + LONG cRefs = InterlockedDecrement(&m_cRefs); + if (cRefs == 0) + { + delete this; + } + + if (sm_pTraceLog != NULL) + { + WriteRefTraceLog(sm_pTraceLog, + cRefs, + this); + } +} + +HRESULT +FORWARDING_HANDLER::SetStatusAndHeaders( + PCSTR pszHeaders, + DWORD +) +{ + HRESULT hr; + IHttpResponse * pResponse = m_pW3Context->GetResponse(); + IHttpRequest * pRequest = m_pW3Context->GetRequest(); + STACK_STRA(strHeaderName, 128); + STACK_STRA(strHeaderValue, 2048); + DWORD index = 0; + PSTR pchNewline; + PCSTR pchEndofHeaderValue; + BOOL fServerHeaderPresent = FALSE; + + _ASSERT(pszHeaders != NULL); + + // + // The first line is the status line + // + PSTR pchStatus = const_cast(strchr(pszHeaders, ' ')); + if (pchStatus == NULL) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + while (*pchStatus == ' ') + { + pchStatus++; + } + USHORT uStatus = static_cast(atoi(pchStatus)); + + if (m_fWebSocketEnabled && uStatus != 101) + { + // + // Expected 101 response. + // + + m_fWebSocketEnabled = FALSE; + } + + pchStatus = strchr(pchStatus, ' '); + if (pchStatus == NULL) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + while (*pchStatus == ' ') + { + pchStatus++; + } + if (*pchStatus == '\r' || *pchStatus == '\n') + { + pchStatus--; + } + + pchNewline = strchr(pchStatus, '\n'); + if (pchNewline == NULL) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + + if (uStatus != 200) + { + // + // Skip over any spaces before the '\n' + // + for (pchEndofHeaderValue = pchNewline - 1; + (pchEndofHeaderValue > pchStatus) && + ((*pchEndofHeaderValue == ' ') || + (*pchEndofHeaderValue == '\r')); + pchEndofHeaderValue--) + {} + + // + // Copy the status description + // + if (FAILED(hr = strHeaderValue.Copy( + pchStatus, + (DWORD)(pchEndofHeaderValue - pchStatus) + 1)) || + FAILED(hr = pResponse->SetStatus(uStatus, + strHeaderValue.QueryStr(), + 0, + S_OK, + NULL, + TRUE))) + { + return hr; + } + } + + for (index = static_cast(pchNewline - pszHeaders) + 1; + pszHeaders[index] != '\r' && pszHeaders[index] != '\n' && pszHeaders[index] != '\0'; + index = static_cast(pchNewline - pszHeaders) + 1) + { + // + // Find the ':' in Header : Value\r\n + // + PCSTR pchColon = strchr(pszHeaders + index, ':'); + + // + // Find the '\n' in Header : Value\r\n + // + pchNewline = const_cast(strchr(pszHeaders + index, '\n')); + + if (pchNewline == NULL) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + + // + // Take care of header continuation + // + while (pchNewline[1] == ' ' || + pchNewline[1] == '\t') + { + pchNewline = strchr(pchNewline + 1, '\n'); + } + + DBG_ASSERT( + (pchColon != NULL) && (pchColon < pchNewline)); + if ((pchColon == NULL) || (pchColon >= pchNewline)) + { + return HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + } + + // + // Skip over any spaces before the ':' + // + PCSTR pchEndofHeaderName; + for (pchEndofHeaderName = pchColon - 1; + (pchEndofHeaderName >= pszHeaders + index) && + (*pchEndofHeaderName == ' '); + pchEndofHeaderName--) + {} + + pchEndofHeaderName++; + + // + // Copy the header name + // + if (FAILED(hr = strHeaderName.Copy( + pszHeaders + index, + (DWORD)(pchEndofHeaderName - pszHeaders) - index))) + { + return hr; + } + + // + // Skip over the ':' and any trailing spaces + // + for (index = static_cast(pchColon - pszHeaders) + 1; + pszHeaders[index] == ' '; + index++) + {} + + + // + // Skip over any spaces before the '\n' + // + for (pchEndofHeaderValue = pchNewline - 1; + (pchEndofHeaderValue >= pszHeaders + index) && + ((*pchEndofHeaderValue == ' ') || + (*pchEndofHeaderValue == '\r')); + pchEndofHeaderValue--) + {} + + pchEndofHeaderValue++; + + // + // Copy the header value + // + if (pchEndofHeaderValue == pszHeaders + index) + { + strHeaderValue.Reset(); + } + else if (FAILED(hr = strHeaderValue.Copy( + pszHeaders + index, + (DWORD)(pchEndofHeaderValue - pszHeaders) - index))) + { + return hr; + } + + // + // Do not pass the transfer-encoding:chunked, Connection, Date or + // Server headers along + // + DWORD headerIndex = g_pResponseHeaderHash->GetIndex(strHeaderName.QueryStr()); + if (headerIndex == UNKNOWN_INDEX) + { + hr = pResponse->SetHeader(strHeaderName.QueryStr(), + strHeaderValue.QueryStr(), + static_cast(strHeaderValue.QueryCCH()), + FALSE); // fReplace + } + else + { + switch (headerIndex) + { + case HttpHeaderTransferEncoding: + if (!strHeaderValue.Equals("chunked", TRUE)) + { + break; + } + __fallthrough; + case HttpHeaderConnection: + case HttpHeaderDate: + continue; + + case HttpHeaderServer: + fServerHeaderPresent = TRUE; + break; + + case HttpHeaderContentLength: + if (pRequest->GetRawHttpRequest()->Verb != HttpVerbHEAD) + { + m_cContentLength = _atoi64(strHeaderValue.QueryStr()); + } + break; + } + + hr = pResponse->SetHeader(static_cast(headerIndex), + strHeaderValue.QueryStr(), + static_cast(strHeaderValue.QueryCCH()), + TRUE); // fReplace + } + if (FAILED(hr)) + { + return hr; + } + } + + // + // Explicitly remove the Server header if the back-end didn't set one. + // + + if (!fServerHeaderPresent) + { + pResponse->DeleteHeader("Server"); + } + + if (m_fDoReverseRewriteHeaders) + { + hr = DoReverseRewrite(pResponse); + if (FAILED(hr)) + { + return hr; + } + } + + m_fResponseHeadersReceivedAndSet = TRUE; + + return S_OK; +} + +HRESULT +FORWARDING_HANDLER::DoReverseRewrite( + __in IHttpResponse *pResponse +) +{ + DBG_ASSERT(pResponse == m_pW3Context->GetResponse()); + BOOL fSecure = (m_pW3Context->GetRequest()->GetRawHttpRequest()->pSslInfo != NULL); + STRA strTemp; + PCSTR pszHeader; + PCSTR pszStartHost; + PCSTR pszEndHost; + HTTP_RESPONSE_HEADERS *pHeaders; + HRESULT hr; + + // + // Content-Location and Location are easy, one known header in + // http[s]://host/url format + // + pszHeader = pResponse->GetHeader(HttpHeaderContentLocation); + if (pszHeader != NULL) + { + if (_strnicmp(pszHeader, "http://", 7) == 0) + { + pszStartHost = pszHeader + 7; + } + else if (_strnicmp(pszHeader, "https://", 8) == 0) + { + pszStartHost = pszHeader + 8; + } + else + { + goto Location; + } + + pszEndHost = strchr(pszStartHost, '/'); + + if (FAILED(hr = strTemp.Copy(fSecure ? "https://" : "http://")) || + FAILED(hr = strTemp.Append(m_pszOriginalHostHeader))) + { + return hr; + } + if (pszEndHost != NULL && + FAILED(hr = strTemp.Append(pszEndHost))) + { + return hr; + } + if (FAILED(hr = pResponse->SetHeader(HttpHeaderContentLocation, + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) + { + return hr; + } + } + +Location: + + pszHeader = pResponse->GetHeader(HttpHeaderLocation); + if (pszHeader != NULL) + { + if (_strnicmp(pszHeader, "http://", 7) == 0) + { + pszStartHost = pszHeader + 7; + } + else if (_strnicmp(pszHeader, "https://", 8) == 0) + { + pszStartHost = pszHeader + 8; + } + else + { + goto SetCookie; + } + + pszEndHost = strchr(pszStartHost, '/'); + + if (FAILED(hr = strTemp.Copy(fSecure ? "https://" : "http://")) || + FAILED(hr = strTemp.Append(m_pszOriginalHostHeader))) + { + return hr; + } + if (pszEndHost != NULL && + FAILED(hr = strTemp.Append(pszEndHost))) + { + return hr; + } + if (FAILED(hr = pResponse->SetHeader(HttpHeaderLocation, + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) + { + return hr; + } + } + +SetCookie: + + // + // Set-Cookie is different - possibly multiple unknown headers with + // syntax name=value ; ... ; Domain=.host ; ... + // + pHeaders = &pResponse->GetRawHttpResponse()->Headers; + for (DWORD i = 0; iUnknownHeaderCount; i++) + { + if (_stricmp(pHeaders->pUnknownHeaders[i].pName, "Set-Cookie") != 0) + { + continue; + } + + pszHeader = pHeaders->pUnknownHeaders[i].pRawValue; + pszStartHost = strchr(pszHeader, ';'); + while (pszStartHost != NULL) + { + pszStartHost++; + while (IsSpace(*pszStartHost)) + { + pszStartHost++; + } + + if (_strnicmp(pszStartHost, "Domain", 6) != 0) + { + pszStartHost = strchr(pszStartHost, ';'); + continue; + } + pszStartHost += 6; + + while (IsSpace(*pszStartHost)) + { + pszStartHost++; + } + if (*pszStartHost != '=') + { + break; + } + pszStartHost++; + while (IsSpace(*pszStartHost)) + { + pszStartHost++; + } + if (*pszStartHost == '.') + { + pszStartHost++; + } + pszEndHost = pszStartHost; + while (!IsSpace(*pszEndHost) && + *pszEndHost != ';' && + *pszEndHost != '\0') + { + pszEndHost++; + } + + if (FAILED(hr = strTemp.Copy(pszHeader, static_cast(pszStartHost - pszHeader))) || + FAILED(hr = strTemp.Append(m_pszOriginalHostHeader)) || + FAILED(hr = strTemp.Append(pszEndHost))) + { + return hr; + } + + pszHeader = (PCSTR)m_pW3Context->AllocateRequestMemory(strTemp.QueryCCH() + 1); + if (pszHeader == NULL) + { + return E_OUTOFMEMORY; + } + StringCchCopyA(const_cast(pszHeader), strTemp.QueryCCH() + 1, strTemp.QueryStr()); + pHeaders->pUnknownHeaders[i].pRawValue = pszHeader; + pHeaders->pUnknownHeaders[i].RawValueLength = static_cast(strTemp.QueryCCH()); + + break; + } + } + + return S_OK; +} + +HRESULT +FORWARDING_HANDLER::GetHeaders( + const PROTOCOL_CONFIG * pProtocol, + PCWSTR * ppszHeaders, + DWORD * pcchHeaders, + ASPNETCORE_CONFIG* pAspNetCoreConfig, + SERVER_PROCESS* pServerProcess +) +{ + + HRESULT hr = S_OK; + PCSTR pszCurrentHeader; + PCSTR ppHeadersToBeRemoved; + PCSTR pszFinalHeader; + USHORT cchCurrentHeader; + DWORD cchFinalHeader; + BOOL fSecure = FALSE; // dummy. Used in SplitUrl. Value will not be used + // as ANCM always use http protocol to communicate with backend + STRU struDestination; + STRU struUrl; + STACK_STRA(strTemp, 64); + HTTP_REQUEST_HEADERS *pHeaders; + IHttpRequest *pRequest = m_pW3Context->GetRequest(); + MULTISZA mszMsAspNetCoreHeaders; + + // + // We historically set the host section in request url to the new host header + // this is wrong but Kestrel has dependency on it. + // should change it in the future + // + if (!pProtocol->QueryPreserveHostHeader()) + { + if (FAILED(hr = PATH::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, + &fSecure, + &struDestination, + &struUrl)) || + FAILED(hr = strTemp.CopyW(struDestination.QueryStr())) || + FAILED(hr = pRequest->SetHeader(HttpHeaderHost, + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) // fReplace + { + return hr; + } + } + // + // Strip all headers starting with MS-ASPNETCORE. + // These headers are generated by the asp.net core module and + // passed to the process it creates. + // + + pHeaders = &m_pW3Context->GetRequest()->GetRawHttpRequest()->Headers; + for (DWORD i = 0; iUnknownHeaderCount; i++) + { + if (_strnicmp(pHeaders->pUnknownHeaders[i].pName, "MS-ASPNETCORE", 13) == 0) + { + mszMsAspNetCoreHeaders.Append(pHeaders->pUnknownHeaders[i].pName, (DWORD)pHeaders->pUnknownHeaders[i].NameLength); + } + } + + ppHeadersToBeRemoved = mszMsAspNetCoreHeaders.First(); + + // + // iterate the list of headers to be removed and delete them from the request. + // + + while (ppHeadersToBeRemoved != NULL) + { + m_pW3Context->GetRequest()->DeleteHeader(ppHeadersToBeRemoved); + ppHeadersToBeRemoved = mszMsAspNetCoreHeaders.Next(ppHeadersToBeRemoved); + } + + if (pServerProcess->QueryGuid() != NULL) + { + hr = m_pW3Context->GetRequest()->SetHeader("MS-ASPNETCORE-TOKEN", + pServerProcess->QueryGuid(), + (USHORT)strlen(pServerProcess->QueryGuid()), + TRUE); + if (FAILED(hr)) + { + return hr; + } + } + + if (pAspNetCoreConfig->QueryForwardWindowsAuthToken() && + (_wcsicmp(m_pW3Context->GetUser()->GetAuthenticationType(), L"negotiate") == 0 || + _wcsicmp(m_pW3Context->GetUser()->GetAuthenticationType(), L"ntlm") == 0)) + { + if (m_pW3Context->GetUser()->GetPrimaryToken() != NULL && + m_pW3Context->GetUser()->GetPrimaryToken() != INVALID_HANDLE_VALUE) + { + HANDLE hTargetTokenHandle = NULL; + hr = pServerProcess->SetWindowsAuthToken(m_pW3Context->GetUser()->GetPrimaryToken(), + &hTargetTokenHandle); + if (FAILED(hr)) + { + return hr; + } + + // + // set request header with target token value + // + CHAR pszHandleStr[16] = { 0 }; + if (_ui64toa_s((UINT64)hTargetTokenHandle, pszHandleStr, 16, 16) != 0) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + return hr; + } + + hr = m_pW3Context->GetRequest()->SetHeader("MS-ASPNETCORE-WINAUTHTOKEN", + pszHandleStr, + (USHORT)strlen(pszHandleStr), + TRUE); + if (FAILED(hr)) + { + return hr; + } + } + } + + if (!pProtocol->QueryXForwardedForName()->IsEmpty()) + { + strTemp.Reset(); + + pszCurrentHeader = pRequest->GetHeader(pProtocol->QueryXForwardedForName()->QueryStr(), &cchCurrentHeader); + if (pszCurrentHeader != NULL) + { + if (FAILED(hr = strTemp.Copy(pszCurrentHeader, cchCurrentHeader)) || + FAILED(hr = strTemp.Append(", ", 2))) + { + return hr; + } + } + + if (FAILED(hr = m_pW3Context->GetServerVariable("REMOTE_ADDR", + &pszFinalHeader, + &cchFinalHeader))) + { + return hr; + } + + if (pRequest->GetRawHttpRequest()->Address.pRemoteAddress->sa_family == AF_INET6) + { + if (FAILED(hr = strTemp.Append("[", 1)) || + FAILED(hr = strTemp.Append(pszFinalHeader, cchFinalHeader)) || + FAILED(hr = strTemp.Append("]", 1))) + { + return hr; + } + } + else + { + if (FAILED(hr = strTemp.Append(pszFinalHeader, cchFinalHeader))) + { + return hr; + } + } + + if (pProtocol->QueryIncludePortInXForwardedFor()) + { + if (FAILED(hr = m_pW3Context->GetServerVariable("REMOTE_PORT", + &pszFinalHeader, + &cchFinalHeader))) + { + return hr; + } + + if (FAILED(hr = strTemp.Append(":", 1)) || + FAILED(hr = strTemp.Append(pszFinalHeader, cchFinalHeader))) + { + return hr; + } + } + + if (FAILED(hr = pRequest->SetHeader(pProtocol->QueryXForwardedForName()->QueryStr(), + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) // fReplace + { + return hr; + } + } + + if (!pProtocol->QuerySslHeaderName()->IsEmpty()) + { + const HTTP_SSL_INFO *pSslInfo = pRequest->GetRawHttpRequest()->pSslInfo; + LPSTR pszScheme = "http"; + if (pSslInfo != NULL) + { + pszScheme = "https"; + } + + strTemp.Reset(); + + pszCurrentHeader = pRequest->GetHeader(pProtocol->QuerySslHeaderName()->QueryStr(), &cchCurrentHeader); + if (pszCurrentHeader != NULL) + { + if (FAILED(hr = strTemp.Copy(pszCurrentHeader, cchCurrentHeader)) || + FAILED(hr = strTemp.Append(", ", 2))) + { + return hr; + } + } + + if (FAILED(hr = strTemp.Append(pszScheme))) + { + return hr; + } + + if (FAILED(pRequest->SetHeader(pProtocol->QuerySslHeaderName()->QueryStr(), + strTemp.QueryStr(), + (USHORT)strTemp.QueryCCH(), + TRUE))) + { + return hr; + } + } + + if (!pProtocol->QueryClientCertName()->IsEmpty()) + { + if (pRequest->GetRawHttpRequest()->pSslInfo == NULL || + pRequest->GetRawHttpRequest()->pSslInfo->pClientCertInfo == NULL) + { + pRequest->DeleteHeader(pProtocol->QueryClientCertName()->QueryStr()); + } + else + { + // Resize the buffer large enough to hold the encoded certificate info + if (FAILED(hr = strTemp.Resize( + 1 + (pRequest->GetRawHttpRequest()->pSslInfo->pClientCertInfo->CertEncodedSize + 2) / 3 * 4))) + { + return hr; + } + + Base64Encode( + pRequest->GetRawHttpRequest()->pSslInfo->pClientCertInfo->pCertEncoded, + pRequest->GetRawHttpRequest()->pSslInfo->pClientCertInfo->CertEncodedSize, + strTemp.QueryStr(), + strTemp.QuerySize(), + NULL); + strTemp.SyncWithBuffer(); + + if (FAILED(hr = pRequest->SetHeader( + pProtocol->QueryClientCertName()->QueryStr(), + strTemp.QueryStr(), + static_cast(strTemp.QueryCCH()), + TRUE))) // fReplace + { + return hr; + } + } + } + + // + // Remove the connection header + // + if (!m_fWebSocketEnabled) + { + pRequest->DeleteHeader(HttpHeaderConnection); + } + + // + // Get all the headers to send to the client + // + hr = m_pW3Context->GetServerVariable("ALL_RAW", + ppszHeaders, + pcchHeaders); + if (FAILED(hr)) + { + return hr; + } + + return S_OK; +} + +HRESULT +FORWARDING_HANDLER::CreateWinHttpRequest( + __in const IHttpRequest * pRequest, + __in const PROTOCOL_CONFIG * pProtocol, + __in HINTERNET hConnect, + __inout STRU * pstrUrl, + ASPNETCORE_CONFIG* pAspNetCoreConfig, + SERVER_PROCESS* pServerProcess +) +{ + HRESULT hr = S_OK; + PCWSTR pszVersion = NULL; + PCSTR pszVerb; + STACK_STRU(strVerb, 32); + + // + // Create the request handle for this request (leave some fields blank, + // we will fill them when sending the request) + // + pszVerb = pRequest->GetHttpMethod(); + if (FAILED(hr = strVerb.CopyA(pszVerb))) + { + goto Finished; + } + + //pszVersion = pProtocol->QueryVersion(); + if (pszVersion == NULL) + { + DWORD cchUnused; + hr = m_pW3Context->GetServerVariable( + "HTTP_VERSION", + &pszVersion, + &cchUnused); + if (FAILED(hr)) + { + goto Finished; + } + } + + m_hRequest = WinHttpOpenRequest(hConnect, + strVerb.QueryStr(), + pstrUrl->QueryStr(), + pszVersion, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_ESCAPE_DISABLE_QUERY + | g_OptionalWinHttpFlags); + if (m_hRequest == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (!WinHttpSetTimeouts(m_hRequest, + pProtocol->QueryTimeout(), + pProtocol->QueryTimeout(), + pProtocol->QueryTimeout(), + pProtocol->QueryTimeout())) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + DWORD dwResponseBufferLimit = pProtocol->QueryResponseBufferLimit(); + if (!WinHttpSetOption(m_hRequest, + WINHTTP_OPTION_MAX_RESPONSE_DRAIN_SIZE, + &dwResponseBufferLimit, + sizeof(dwResponseBufferLimit))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + DWORD dwMaxHeaderSize = pProtocol->QueryMaxResponseHeaderSize(); + if (!WinHttpSetOption(m_hRequest, + WINHTTP_OPTION_MAX_RESPONSE_HEADER_SIZE, + &dwMaxHeaderSize, + sizeof(dwMaxHeaderSize))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + DWORD dwOption = WINHTTP_DISABLE_COOKIES; + + dwOption |= WINHTTP_DISABLE_AUTHENTICATION; + + if (!pProtocol->QueryDoKeepAlive()) + { + dwOption |= WINHTTP_DISABLE_KEEP_ALIVE; + } + if (!WinHttpSetOption(m_hRequest, + WINHTTP_OPTION_DISABLE_FEATURE, + &dwOption, + sizeof(dwOption))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (WinHttpSetStatusCallback(m_hRequest, + FORWARDING_HANDLER::OnWinHttpCompletion, + (WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | + WINHTTP_CALLBACK_FLAG_HANDLES | + WINHTTP_CALLBACK_STATUS_SENDING_REQUEST), + NULL) == WINHTTP_INVALID_STATUS_CALLBACK) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + hr = GetHeaders(pProtocol, + &m_pszHeaders, + &m_cchHeaders, + pAspNetCoreConfig, + pServerProcess); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + + return hr; +} + +REQUEST_NOTIFICATION_STATUS +FORWARDING_HANDLER::OnExecuteRequestHandler( + VOID +) +{ + REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_CONTINUE; + HRESULT hr = S_OK; + ASPNETCORE_CONFIG *pAspNetCoreConfig = NULL; + FORWARDER_CONNECTION *pConnection = NULL; + STACK_STRU(strDestination, 32); + STACK_STRU(strUrl, 2048); + STACK_STRU(struEscapedUrl, 2048); + STACK_STRU(strDescription, 128); + HINTERNET hConnect = NULL; + IHttpRequest *pRequest = m_pW3Context->GetRequest(); + IHttpResponse *pResponse = m_pW3Context->GetResponse(); + PROTOCOL_CONFIG *pProtocol = &sm_ProtocolConfig; + APPLICATION_MANAGER *pApplicationManager = NULL; + SERVER_PROCESS *pServerProcess = NULL; + USHORT cchHostName = 0; + BOOL fSecure = FALSE; + BOOL fProcessStartFailure = FALSE; + HTTP_DATA_CHUNK *pDataChunk = NULL; + IHttpConnection *pClientConnection = NULL; + + DBG_ASSERT(m_RequestStatus == FORWARDER_START); + + // + // Take a reference so that object does not go away as a result of + // async completion. + // + ReferenceForwardingHandler(); + + m_pszOriginalHostHeader = pRequest->GetHeader(HttpHeaderHost, &cchHostName); + + // read per site aspNetCore configuration. + hr = ASPNETCORE_CONFIG::GetConfig(m_pW3Context, &pAspNetCoreConfig); + if (FAILED(hr)) + { + // configuration error. + goto Failure; + } + + // override Protocol related config from aspNetCore config + pProtocol->OverrideConfig(pAspNetCoreConfig); + + // + // parse original url + // + if (FAILED(hr = PATH::SplitUrl(pRequest->GetRawHttpRequest()->CookedUrl.pFullUrl, + &fSecure, + &strDestination, + &strUrl))) + { + goto Failure; + } + + if (FAILED(hr = PATH::EscapeAbsPath(pRequest, &struEscapedUrl))) + { + goto Failure; + } + + m_fDoReverseRewriteHeaders = pProtocol->QueryReverseRewriteHeaders(); + + m_cMinBufferLimit = pProtocol->QueryMinResponseBuffer(); + + pClientConnection = m_pW3Context->GetConnection(); + if (pClientConnection == NULL || + !pClientConnection->IsConnected()) + { + hr = HRESULT_FROM_WIN32(WSAECONNRESET); + goto Failure; + } + + // + // Find the application that is supposed to service this request. + // + pApplicationManager = APPLICATION_MANAGER::GetInstance(); + if (pApplicationManager == NULL) + { + hr = E_OUTOFMEMORY; + goto Failure; + } + + hr = pApplicationManager->GetApplication(m_pW3Context, + &m_pApplication); + if (FAILED(hr)) + { + goto Failure; + } + + m_pAppOfflineHtm = m_pApplication->QueryAppOfflineHtm(); + if (m_pAppOfflineHtm != NULL) + { + m_pAppOfflineHtm->ReferenceAppOfflineHtm(); + } + + if (m_pApplication->AppOfflineFound() && m_pAppOfflineHtm != NULL) + { + HTTP_DATA_CHUNK DataChunk; + + DataChunk.DataChunkType = HttpDataChunkFromMemory; + DataChunk.FromMemory.pBuffer = (PVOID)m_pAppOfflineHtm->m_Contents.QueryStr(); + DataChunk.FromMemory.BufferLength = m_pAppOfflineHtm->m_Contents.QueryCB(); + + pResponse->SetStatus(503, "Service Unavailable", 0, hr, NULL, TRUE); + pResponse->SetHeader("Content-Type", + "text/html", + (USHORT)strlen("text/html"), + FALSE + ); // no need to check return hresult + + pResponse->WriteEntityChunkByReference(&DataChunk); + goto Finished; + } + + hr = m_pApplication->GetProcess(m_pW3Context, + pAspNetCoreConfig, + &pServerProcess); + if (FAILED(hr)) + { + fProcessStartFailure = TRUE; + goto Failure; + } + + if (pServerProcess == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); + goto Failure; + } + + if (pServerProcess->QueryWinHttpConnection() == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE); + goto Failure; + } + + hConnect = pServerProcess->QueryWinHttpConnection()->QueryHandle(); + + // + // Mark request as websocket if upgrade header is present. + // + + if (g_fWebSocketSupported) + { + USHORT cchHeader = 0; + PCSTR pszWebSocketHeader = pRequest->GetHeader("Upgrade", &cchHeader); + + if (cchHeader == 9 && _stricmp(pszWebSocketHeader, "websocket") == 0) + { + m_fWebSocketEnabled = TRUE; + } + } + + hr = CreateWinHttpRequest(pRequest, + pProtocol, + hConnect, + &struEscapedUrl, + pAspNetCoreConfig, + pServerProcess); + + if (FAILED(hr)) + { + goto Failure; + } + + // + // Register for connection disconnect notification with http.sys. + // + if (g_fAsyncDisconnectAvailable) + { + m_pDisconnect = static_cast( + pClientConnection->GetModuleContextContainer()-> + GetConnectionModuleContext(g_pModuleId)); + if (m_pDisconnect == NULL) + { + m_pDisconnect = new ASYNC_DISCONNECT_CONTEXT; + if (m_pDisconnect == NULL) + { + hr = E_OUTOFMEMORY; + goto Failure; + } + + hr = pClientConnection->GetModuleContextContainer()-> + SetConnectionModuleContext(m_pDisconnect, + g_pModuleId); + DBG_ASSERT(hr != HRESULT_FROM_WIN32(ERROR_ALREADY_ASSIGNED)); + if (FAILED(hr)) + { + m_pDisconnect->CleanupStoredContext(); + m_pDisconnect = NULL; + goto Failure; + } + } + + // + // Issue: There is a window of opportunity to miss on the disconnect + // notification if it happens before the SetHandler() call is made. + // It is suboptimal for performance, but should functionally be OK. + // + + m_pDisconnect->SetHandler(this); + } + + if (m_hRequest == NULL) + { + hr = HRESULT_FROM_WIN32(WSAECONNRESET); + goto Failure; + } + + // + // Begins normal request handling. Send request to server. + // + + m_RequestStatus = FORWARDER_SENDING_REQUEST; + + // + // Calculate the bytes to receive from the content length. + // + + DWORD cbContentLength = 0; + PCSTR pszContentLength = pRequest->GetHeader(HttpHeaderContentLength); + if (pszContentLength != NULL) + { + cbContentLength = m_BytesToReceive = atol(pszContentLength); + if (m_BytesToReceive == INFINITE) + { + hr = HRESULT_FROM_WIN32(WSAECONNRESET); + goto Failure; + } + } + else if (pRequest->GetHeader(HttpHeaderTransferEncoding) != NULL) + { + m_BytesToReceive = INFINITE; + } + + if (m_fWebSocketEnabled) + { + // + // Set the upgrade flag for a websocket request. + // + + if (!WinHttpSetOption(m_hRequest, + WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, + NULL, + 0)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + } + + m_cchLastSend = m_cchHeaders; + + if (!WinHttpSendRequest(m_hRequest, + m_pszHeaders, + m_cchHeaders, + NULL, + 0, + cbContentLength, + reinterpret_cast(static_cast(this)))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::OnExecuteRequestHandler, Send request failed"); + goto Failure; + } + + // + // Async WinHTTP operation is in progress. Release this thread meanwhile, + // OnWinHttpCompletion method should resume the work by posting an IIS completion. + // + retVal = RQ_NOTIFICATION_PENDING; + goto Finished; + +Failure: + + // + // Reset status for consistency. + // + m_RequestStatus = FORWARDER_DONE; + m_fHasError = TRUE; + pResponse->DisableKernelCache(); + pResponse->GetRawHttpResponse()->EntityChunkCount = 0; + // + // Finish the request on failure. + // + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + + if (hr == HRESULT_FROM_WIN32(WSAECONNRESET)) + { + pResponse->SetStatus(400, "Bad Request", 0, hr); + goto Finished; + } + else if (fProcessStartFailure && !pAspNetCoreConfig->QueryDisableStartUpErrorPage()) + { + pResponse->SetStatus(502, "Bad Gateway", 5, hr, NULL, TRUE); + pResponse->SetHeader("Content-Type", + "text/html", + (USHORT)strlen("text/html"), + FALSE + ); + + if (SUCCEEDED(pApplicationManager->Get502ErrorPage(&pDataChunk))) + { + pResponse->WriteEntityChunkByReference(pDataChunk); + goto Finished; + } + } + + // + // default error behavior + // + pResponse->SetStatus(502, "Bad Gateway", 3, hr); + + if (hr > HRESULT_FROM_WIN32(WINHTTP_ERROR_BASE) && + hr <= HRESULT_FROM_WIN32(WINHTTP_ERROR_LAST)) + { +#pragma prefast (suppress : __WARNING_FUNCTION_NEEDS_REVIEW, "Function and parameters reviewed.") + FormatMessage( + FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, + g_hWinHttpModule, + HRESULT_CODE(hr), + 0, + strDescription.QueryStr(), + strDescription.QuerySizeCCH(), + NULL); + } + else + { + LoadString(g_hModule, + IDS_SERVER_ERROR, + strDescription.QueryStr(), + strDescription.QuerySizeCCH()); + } + + strDescription.SyncWithBuffer(); + if (strDescription.QueryCCH() != 0) + { + pResponse->SetErrorDescription( + strDescription.QueryStr(), + strDescription.QueryCCH(), + FALSE); + } + +Finished: + + if (pConnection != NULL) + { + pConnection->DereferenceForwarderConnection(); + pConnection = NULL; + } + + if (pServerProcess != NULL) + { + pServerProcess->DereferenceServerProcess(); + pServerProcess = NULL; + } + + if (retVal != RQ_NOTIFICATION_PENDING) + { + RemoveRequest(); + } + + DereferenceForwardingHandler(); + // + // Do not use this object after dereferencing it, it may be gone. + // + + return retVal; +} + +VOID +FORWARDING_HANDLER::RemoveRequest() +{ + ASYNC_DISCONNECT_CONTEXT * pDisconnect; + pDisconnect = (ASYNC_DISCONNECT_CONTEXT *) InterlockedExchangePointer((PVOID*)&m_pDisconnect, NULL); + if (pDisconnect != NULL) + { + pDisconnect->ResetHandler(); + pDisconnect = NULL; + } +} + +REQUEST_NOTIFICATION_STATUS +FORWARDING_HANDLER::OnAsyncCompletion( + DWORD cbCompletion, + HRESULT hrCompletionStatus +) +/*++ + +Routine Description: + +Handle the completion from IIS and continue the execution +of this request based on the current state. + +Arguments: + +cbCompletion - Number of bytes associated with this completion +dwCompletionStatus - the win32 status associated with this completion + +Return Value: + +REQUEST_NOTIFICATION_STATUS + +--*/ +{ + HRESULT hr = S_OK; + REQUEST_NOTIFICATION_STATUS retVal = RQ_NOTIFICATION_PENDING; + BOOL fLocked = FALSE; + BOOL fClientError = FALSE; + BOOL fClosed = FALSE; + BOOL fWebsocketUpgraded = FALSE; + +#ifdef DEBUG + FORWARDING_REQUEST_STATUS dwLocalStatus = m_RequestStatus; +#endif // DEBUG + + if (sm_pTraceLog != NULL) + { + WriteRefTraceLogEx(sm_pTraceLog, + m_cRefs, + this, + "FORWARDING_HANDLER::OnAsyncCompletion Enter", + reinterpret_cast(static_cast(cbCompletion)), + reinterpret_cast(static_cast(hrCompletionStatus))); + } + + DBG_ASSERT(m_pW3Context != NULL); + __analysis_assume(m_pW3Context != NULL); + + // + // Take a reference so that object does not go away as a result of + // async completion. + // + ReferenceForwardingHandler(); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::OnAsyncCompletion"); + + // + // OnAsyncCompletion can be called on a Winhttp io completion thread. + // Hence we need to check the TLS before we acquire the shared lock. + // + if (TlsGetValue(g_dwTlsIndex) != this) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + AcquireSRWLockExclusive(&m_RequestLock); + TlsSetValue(g_dwTlsIndex, this); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + + fLocked = TRUE; + } + + if (m_fClientDisconnected && (m_RequestStatus != FORWARDER_DONE) ) + { + hr = ERROR_CONNECTION_ABORTED; + goto Failure; + } + + if (m_RequestStatus == FORWARDER_RECEIVED_WEBSOCKET_RESPONSE) + { + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::OnAsyncCompletion, Send completed for 101 response"); + // + // This should be the write completion of the 101 response. + // + m_pWebSocket = new WEBSOCKET_HANDLER(); + if (m_pWebSocket == NULL) + { + hr = E_OUTOFMEMORY; + goto Failure; + } + + hr = m_pWebSocket->ProcessRequest(this, m_pW3Context, m_hRequest, &fWebsocketUpgraded); + if (fWebsocketUpgraded) + { + // WinHttp WebSocket handle has been created, bump the counter so that remember to close it + // and prevent from premature postcomplation and unexpected callback from winhttp + InterlockedIncrement(&m_dwHandlers); + } + + if (FAILED(hr)) + { + // This failure could happen when client disconnect happens or backend server fails + // after websocket upgrade + goto Failure; + } + + // + // WebSocket upgrade is successful. Close the WinHttpRequest Handle + // + m_fHttpHandleInClose = TRUE; + fClosed = WinHttpCloseHandle(m_hRequest); + DBG_ASSERT(fClosed); + m_hRequest = NULL; + + if (!fClosed) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + retVal = RQ_NOTIFICATION_PENDING; + goto Finished; + } + + // + // Begins normal completion handling. There is already a shared acquired + // for protecting the WinHTTP request handle from being closed. + // + switch (m_RequestStatus) + { + case FORWARDER_RECEIVING_RESPONSE: + + // + // This is a completion of a write (send) to http.sys, abort in case of + // failure, if there is more data available from WinHTTP, read it + // or else ask if there is more. + // + if (FAILED(hrCompletionStatus)) + { + hr = hrCompletionStatus; + fClientError = TRUE; + goto Failure; + } + + hr = OnReceivingResponse(); + if (FAILED(hr)) + { + goto Failure; + } + break; + + case FORWARDER_SENDING_REQUEST: + + hr = OnSendingRequest(cbCompletion, + hrCompletionStatus, + &fClientError); + if (FAILED(hr)) + { + goto Failure; + } + break; + + default: + DBG_ASSERT(m_RequestStatus == FORWARDER_DONE); + if (m_hRequest == NULL && m_pWebSocket == NULL) + { + // Request must have been done + if (!m_fFinishRequest) + { + goto Failure; + } + + if (m_fHasError) + { + retVal = RQ_NOTIFICATION_FINISH_REQUEST; + } + else + { + retVal = RQ_NOTIFICATION_CONTINUE; + } + } + goto Finished; + } + + // + // Either OnReceivingResponse or OnSendingRequest initiated an + // async WinHTTP operation, release this thread meanwhile, + // OnWinHttpCompletion method should resume the work by posting an IIS completion. + // + retVal = RQ_NOTIFICATION_PENDING; + goto Finished; + +Failure: + + // + // Reset status for consistency. + // + m_RequestStatus = FORWARDER_DONE; + if (!m_fHasError) + { + m_fHasError = TRUE; + + // + // Do the right thing based on where the error originated from. + // + IHttpResponse *pResponse = m_pW3Context->GetResponse(); + pResponse->DisableKernelCache(); + pResponse->GetRawHttpResponse()->EntityChunkCount = 0; + + if (fClientError || m_fClientDisconnected) + { + if (!m_fResponseHeadersReceivedAndSet) + { + pResponse->SetStatus(400, "Bad Request", 0, HRESULT_FROM_WIN32(WSAECONNRESET)); + } + else + { + // + // Response headers from origin server were + // already received and set for the current response. + // Honor the response status. + // + } + } + else + { + STACK_STRU(strDescription, 128); + + pResponse->SetStatus(502, "Bad Gateway", 3, hr); + + if (hr > HRESULT_FROM_WIN32(WINHTTP_ERROR_BASE) && + hr <= HRESULT_FROM_WIN32(WINHTTP_ERROR_LAST)) + { +#pragma prefast (suppress : __WARNING_FUNCTION_NEEDS_REVIEW, "Function and parameters reviewed.") + FormatMessage( + FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, + g_hWinHttpModule, + HRESULT_CODE(hr), + 0, + strDescription.QueryStr(), + strDescription.QuerySizeCCH(), + NULL); + } + else + { + LoadString(g_hModule, + IDS_SERVER_ERROR, + strDescription.QueryStr(), + strDescription.QuerySizeCCH()); + } + (VOID)strDescription.SyncWithBuffer(); + +#ifdef DEBUG + // adding more info to error descrption to help us diagnose the issue + STACK_STRA(strMoreData, 128); + sprintf_s(strMoreData.QueryStr(), 100, "OnAsyncCompletion --%p--%p--%d--%d--%d--%d\n", this, m_pW3Context, GetCurrentThreadId(), (UINT)dwLocalStatus, m_fServerResetConn, m_fClientDisconnected); + strMoreData.SyncWithBuffer(); + strDescription.AppendA(strMoreData.QueryStr()); +#endif // DEBUG + + if (strDescription.QueryCCH() != 0) + { + pResponse->SetErrorDescription( + strDescription.QueryStr(), + strDescription.QueryCCH(), + FALSE); + } + + if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) + { + if (!m_fServerResetConn) + { + RemoveRequest(); + pResponse->ResetConnection(); + m_fServerResetConn = TRUE; + } + } + } + } + + if (m_pWebSocket != NULL && !m_fWebSocketHandleInClose) + { + m_fWebSocketHandleInClose = TRUE; + m_pWebSocket->TerminateRequest(); + } + + if (m_hRequest != NULL && !m_fHttpHandleInClose) + { + m_fHttpHandleInClose = TRUE; + WinHttpCloseHandle(m_hRequest); + m_hRequest = NULL; + } + +Finished: + + if (fLocked) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockExclusive(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } + + if (retVal != RQ_NOTIFICATION_PENDING) + { +#ifdef DEBUG + DWORD counter = InterlockedIncrement(&g_dwLogCounter) % ASPNETCORE_DEBUG_STRU_ARRAY_SIZE; + g_strLogs[counter].Reset(); + sprintf_s(g_strLogs[counter].QueryStr(), ASPNETCORE_DEBUG_STRU_BUFFER_SIZE, + "OnAyncCompletion--%p--%d--%d--%d--%d\n", this, GetCurrentThreadId(), (UINT)dwLocalStatus, retVal, m_fDoneAsyncCompletion); + g_strLogs[counter].SyncWithBuffer(); +#endif // DEBUG + + DBG_ASSERT(m_dwHandlers == 0); + RemoveRequest(); + + // This is just a safety guard to prevent from returning non pending status no more once + // which should never happen + if (!m_fDoneAsyncCompletion) + { + m_fDoneAsyncCompletion = TRUE; + } + else + { + retVal = RQ_NOTIFICATION_PENDING; + } + } + + DereferenceForwardingHandler(); + // + // Do not use this object after dereferencing it, it may be gone. + // + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::OnAsyncCompletion Done %d", retVal); + return retVal; +} + +HRESULT +FORWARDING_HANDLER::OnSendingRequest( + DWORD cbCompletion, + HRESULT hrCompletionStatus, + __out BOOL * pfClientError +) +{ + HRESULT hr = S_OK; + + // + // This is a completion for a read from http.sys, abort in case + // of failure, if we read anything write it out over WinHTTP, + // but we have already reached EOF, now read the response + // + if (hrCompletionStatus == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF)) + { + DBG_ASSERT(m_BytesToReceive == 0 || m_BytesToReceive == INFINITE); + if (m_BytesToReceive == INFINITE) + { + m_BytesToReceive = 0; + m_cchLastSend = 5; // "0\r\n\r\n" + + // + // WinHttpWriteData can operate asynchronously. + // + // Take reference so that object does not go away as a result of + // async completion. + // + if (!WinHttpWriteData(m_hRequest, + "0\r\n\r\n", + 5, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + } + else + { + m_RequestStatus = FORWARDER_RECEIVING_RESPONSE; + + // + // WinHttpReceiveResponse can operate asynchronously. + // + // Take reference so that object does not go away as a result of + // async completion. + // + if (!WinHttpReceiveResponse(m_hRequest, NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + } + } + else if (SUCCEEDED(hrCompletionStatus)) + { + DWORD cbOffset; + + if (m_BytesToReceive != INFINITE) + { + m_BytesToReceive -= cbCompletion; + cbOffset = 6; + } + else + { + // + // For chunk-encoded requests, need to re-chunk the + // entity body + // + // Add the CRLF just before and after the chunk data + // + m_pEntityBuffer[4] = '\r'; + m_pEntityBuffer[5] = '\n'; + + m_pEntityBuffer[cbCompletion + 6] = '\r'; + m_pEntityBuffer[cbCompletion + 7] = '\n'; + + if (cbCompletion < 0x10) + { + cbOffset = 3; + m_pEntityBuffer[3] = HEX_TO_ASCII(cbCompletion); + cbCompletion += 5; + } + else if (cbCompletion < 0x100) + { + cbOffset = 2; + m_pEntityBuffer[2] = HEX_TO_ASCII(cbCompletion >> 4); + m_pEntityBuffer[3] = HEX_TO_ASCII(cbCompletion & 0xf); + cbCompletion += 6; + } + else if (cbCompletion < 0x1000) + { + cbOffset = 1; + m_pEntityBuffer[1] = HEX_TO_ASCII(cbCompletion >> 8); + m_pEntityBuffer[2] = HEX_TO_ASCII((cbCompletion >> 4) & 0xf); + m_pEntityBuffer[3] = HEX_TO_ASCII(cbCompletion & 0xf); + cbCompletion += 7; + } + else + { + DBG_ASSERT(cbCompletion < 0x10000); + + cbOffset = 0; + m_pEntityBuffer[0] = HEX_TO_ASCII(cbCompletion >> 12); + m_pEntityBuffer[1] = HEX_TO_ASCII((cbCompletion >> 8) & 0xf); + m_pEntityBuffer[2] = HEX_TO_ASCII((cbCompletion >> 4) & 0xf); + m_pEntityBuffer[3] = HEX_TO_ASCII(cbCompletion & 0xf); + cbCompletion += 8; + } + } + m_cchLastSend = cbCompletion; + + // + // WinHttpWriteData can operate asynchronously. + // + // Take reference so that object does not go away as a result of + // async completion. + // + if (!WinHttpWriteData(m_hRequest, + m_pEntityBuffer + cbOffset, + cbCompletion, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + } + else + { + hr = hrCompletionStatus; + *pfClientError = TRUE; + goto Failure; + } + +Failure: + + return hr; +} + +HRESULT +FORWARDING_HANDLER::OnReceivingResponse( +) +{ + HRESULT hr = S_OK; + + if (m_cBytesBuffered >= m_cMinBufferLimit) + { + FreeResponseBuffers(); + } + + if (m_BytesToSend == 0) + { + // + // If response buffering is enabled, try to read large chunks + // at a time - also treat very small buffering limit as no + // buffering + // + m_BytesToSend = min(m_cMinBufferLimit, BUFFER_SIZE); + if (m_BytesToSend < BUFFER_SIZE / 2) + { + // + // Disable buffering. + // + m_BytesToSend = 0; + } + } + + if (m_BytesToSend == 0) + { + // + // No buffering enabled. + // + // WinHttpQueryDataAvailable can operate asynchronously. + // + if (!WinHttpQueryDataAvailable(m_hRequest, NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + } + else + { + // + // Buffering enabled. + // + + if (m_pEntityBuffer == NULL) + { + m_pEntityBuffer = GetNewResponseBuffer(min(m_BytesToSend, BUFFER_SIZE)); + if (m_pEntityBuffer == NULL) + { + hr = E_OUTOFMEMORY; + goto Failure; + } + } + + // + // WinHttpReadData can operate asynchronously. + // + if (!WinHttpReadData(m_hRequest, + m_pEntityBuffer, + min(m_BytesToSend, BUFFER_SIZE), + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + } + +Failure: + + return hr; +} + +VOID +FORWARDING_HANDLER::OnWinHttpCompletionInternal( + HINTERNET hRequest, + DWORD dwInternetStatus, + LPVOID lpvStatusInformation, + DWORD dwStatusInformationLength +) +/*++ + +Routine Description: + +Completion call associated with a WinHTTP operation + +Arguments: + +hRequest - The winhttp request handle associated with this completion +dwInternetStatus - enum specifying what the completion is for +lpvStatusInformation - completion specific information +dwStatusInformationLength - length of the above information + +Return Value: + +None + +--*/ +{ + HRESULT hr = S_OK; + BOOL fExclusiveLock = FALSE; + BOOL fSharedLock = FALSE; + BOOL fDoPostCompletion = FALSE; + BOOL fClientError = FALSE; + BOOL fAnotherCompletionExpected = FALSE; + DWORD dwHandlers = 1; // defaullt for http handler + + DBG_ASSERT(m_pW3Context != NULL); + __analysis_assume(m_pW3Context != NULL); + IHttpResponse * pResponse = m_pW3Context->GetResponse(); + BOOL fEndRequest = FALSE; + + // Reference the request handler to prevent it from being released prematurely + ReferenceForwardingHandler(); + + UNREFERENCED_PARAMETER(dwStatusInformationLength); + + if (sm_pTraceLog != NULL) + { + WriteRefTraceLogEx(sm_pTraceLog, + m_cRefs, + this, + "FORWARDING_HANDLER::OnWinHttpCompletionInternal Enter", + reinterpret_cast(static_cast(dwInternetStatus)), + NULL); + } + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::OnWinHttpCompletionInternal %x --%p", + dwInternetStatus, this); + + // + // Exclusive lock on the winhttp handle to protect from a client disconnect/ + // server stop closing the handle while we are using it. + // + // WinHttp can call async completion on the same thread/stack, so + // we have to account for that and not try to take the lock again, + // otherwise, we could end up in a deadlock. + // + + fEndRequest = (dwInternetStatus == WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING); + + if (TlsGetValue(g_dwTlsIndex) != this) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + if (m_RequestStatus != FORWARDER_RECEIVED_WEBSOCKET_RESPONSE) + { + // Webscoket has already been guarded by critical section + // Only require exclisive lock for non-websocket scenario which has duplex channel + // Otherwise, there will be a deadlock + AcquireSRWLockExclusive(&m_RequestLock); + TlsSetValue(g_dwTlsIndex, this); + fExclusiveLock = TRUE; + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + } + else + { + AcquireSRWLockShared(&m_RequestLock); + TlsSetValue(g_dwTlsIndex, this); + fSharedLock = TRUE; + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + } + } + + if (fEndRequest) + { + dwHandlers = InterlockedDecrement(&m_dwHandlers); + } + + if (m_fFinishRequest) + { + // Request was done by another thread, skip + goto Finished; + } + + if(m_fClientDisconnected && (m_RequestStatus != FORWARDER_DONE)) + { + hr = ERROR_CONNECTION_ABORTED; + goto Failure; + } + + if (m_RequestStatus == FORWARDER_RECEIVED_WEBSOCKET_RESPONSE) + { + fAnotherCompletionExpected = TRUE; + + if(m_pWebSocket == NULL) + { + goto Finished; + } + + switch (dwInternetStatus) + { + case WINHTTP_CALLBACK_STATUS_SHUTDOWN_COMPLETE: + m_pWebSocket->OnWinHttpShutdownComplete(); + break; + + case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE: + m_pWebSocket->OnWinHttpSendComplete( + (WINHTTP_WEB_SOCKET_STATUS*)lpvStatusInformation + ); + break; + + case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: + m_pWebSocket->OnWinHttpReceiveComplete( + (WINHTTP_WEB_SOCKET_STATUS*)lpvStatusInformation + ); + break; + + case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: + m_pWebSocket->OnWinHttpIoError( + (WINHTTP_WEB_SOCKET_ASYNC_RESULT*)lpvStatusInformation + ); + break; + } + goto Finished; + } + + switch (dwInternetStatus) + { + case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: + case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE: + hr = OnWinHttpCompletionSendRequestOrWriteComplete(hRequest, + dwInternetStatus, + &fClientError, + &fAnotherCompletionExpected); + break; + + case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: + hr = OnWinHttpCompletionStatusHeadersAvailable(hRequest, + &fAnotherCompletionExpected); + break; + + case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: + hr = OnWinHttpCompletionStatusDataAvailable(hRequest, + *reinterpret_cast(lpvStatusInformation), // dwBytes + &fAnotherCompletionExpected); + break; + + case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: + hr = OnWinHttpCompletionStatusReadComplete(pResponse, + dwStatusInformationLength, + &fAnotherCompletionExpected); + break; + + case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: + hr = HRESULT_FROM_WIN32(static_cast(lpvStatusInformation)->dwError); + break; + + case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST: + // + // This is a notification, not a completion. This notifiation happens + // during the Send Request operation. + // + fAnotherCompletionExpected = TRUE; + break; + + case WINHTTP_CALLBACK_STATUS_REQUEST_SENT: + // + // Need to ignore this event. We get it as a side-effect of registering + // for WINHTTP_CALLBACK_STATUS_SENDING_REQUEST (which we actually need). + // + hr = S_OK; + fAnotherCompletionExpected = TRUE; + break; + + case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING: + + fAnotherCompletionExpected = FALSE; + + if (m_RequestStatus != FORWARDER_DONE) + { + hr = ERROR_CONNECTION_ABORTED; + fClientError = m_fClientDisconnected; + goto Failure; + } + break; + + case WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED: + hr = ERROR_CONNECTION_ABORTED; + break; + + default: + // + // E_UNEXPECTED is rarely used, if seen means that this condition may been occurred. + // + DBG_ASSERT(FALSE); + hr = E_UNEXPECTED; + if (sm_pTraceLog != NULL) + { + WriteRefTraceLogEx(sm_pTraceLog, + m_cRefs, + this, + "FORWARDING_HANDLER::OnWinHttpCompletionInternal Unexpected WinHTTP Status", + reinterpret_cast(static_cast(dwInternetStatus)), + NULL); + } + break; + } + + // + // Handle failure code for switch statement above. + // + if (FAILED(hr)) + { + goto Failure; + } + + // + // WinHTTP completion handled successfully. + // + goto Finished; + +Failure: + m_RequestStatus = FORWARDER_DONE; + if(!m_fHasError) + { + m_fHasError = TRUE; + pResponse->DisableKernelCache(); + pResponse->GetRawHttpResponse()->EntityChunkCount = 0; + + if (fClientError || m_fClientDisconnected) + { + if (!m_fResponseHeadersReceivedAndSet) + { + pResponse->SetStatus(400, "Bad Request", 0, HRESULT_FROM_WIN32(WSAECONNRESET)); + } + else + { + // + // Response headers from origin server were + // already received and set for the current response. + // Honor the response status. + // + } + } + else + { + STACK_STRU(strDescription, 128); + pResponse->SetStatus(502, "Bad Gateway", 3, hr); + + if (hr > HRESULT_FROM_WIN32(WINHTTP_ERROR_BASE) && + hr <= HRESULT_FROM_WIN32(WINHTTP_ERROR_LAST)) + { +#pragma prefast (suppress : __WARNING_FUNCTION_NEEDS_REVIEW, "Function and parameters reviewed.") + FormatMessage( + FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, + g_hWinHttpModule, + HRESULT_CODE(hr), + 0, + strDescription.QueryStr(), + strDescription.QuerySizeCCH(), + NULL); + } + else + { + LoadString(g_hModule, + IDS_SERVER_ERROR, + strDescription.QueryStr(), + strDescription.QuerySizeCCH()); + } + strDescription.SyncWithBuffer(); + +#ifdef DEBUG + // addingmore data to error description to help us diaganose the issue + STACK_STRA(strMoreData, 128); + sprintf_s(strMoreData.QueryStr(), 100, "OnWinHttpCompletionInternal --%p--%d--%d--%d\n", this, GetCurrentThreadId(), dwInternetStatus, m_fServerResetConn); + strMoreData.SyncWithBuffer(); + strDescription.AppendA(strMoreData.QueryStr()); +#endif // DEBUG + + if (strDescription.QueryCCH() != 0) + { + pResponse->SetErrorDescription( + strDescription.QueryStr(), + strDescription.QueryCCH(), + FALSE); + } + + if (hr == HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE)) + { + if (!m_fServerResetConn) + { + RemoveRequest(); + pResponse->ResetConnection(); + m_fServerResetConn = TRUE; + } + } + } + } + +Finished: + // + // Since we use TLS to guard WinHttp operation, call PostCompletion instead of + // IndicateCompletion to allow cleaning up the TLS before thread reuse. + // Never post after the request has been finished for whatever reason + // + // Only postCompletion after all WinHttp handle got closed, + // i.e., received WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING callback for all handles + // So that no further WinHttp callback will be called + // Never post completion again after that + // Otherwise, there will be a AV as the request already passed IIS pipeline + // + if (fEndRequest && !m_fFinishRequest && dwHandlers ==0) + { + // + // Happy path + // +#ifdef DEBUG + DWORD counter = InterlockedIncrement(&g_dwLogCounter) % ASPNETCORE_DEBUG_STRU_ARRAY_SIZE; + sprintf_s(g_strLogs[counter].QueryStr(), ASPNETCORE_DEBUG_STRU_BUFFER_SIZE, + "PostCompletion 0 --%p--%p--%d\n", this, m_pW3Context, GetCurrentThreadId()); + g_strLogs[counter].SyncWithBuffer(); +#endif // DEBUG + + // Marked the request is finished, no more PostCompletion is allowed + RemoveRequest(); + m_fFinishRequest = TRUE; + fDoPostCompletion = TRUE; + if (m_pWebSocket != NULL) + { + delete(m_pWebSocket); + m_pWebSocket = NULL; + } + } + + else if (m_RequestStatus == FORWARDER_DONE) + { + // + // Error path + // + RemoveRequest(); + if (m_hRequest != NULL && !m_fHttpHandleInClose) + { + m_fHttpHandleInClose = TRUE; + WinHttpCloseHandle(m_hRequest); + m_hRequest = NULL; + } + + if (m_pWebSocket != NULL && !m_fWebSocketHandleInClose) + { + m_fWebSocketHandleInClose = TRUE; + m_pWebSocket->TerminateRequest(); + } + + if (fEndRequest) + { + fDoPostCompletion = (dwHandlers == 0 && !m_fFinishRequest); + if (fDoPostCompletion) + { +#ifdef DEBUG + DWORD counter = InterlockedIncrement(&g_dwLogCounter) % ASPNETCORE_DEBUG_STRU_ARRAY_SIZE; + sprintf_s(g_strLogs[counter].QueryStr(), ASPNETCORE_DEBUG_STRU_BUFFER_SIZE, + "PostCompletion 1 --%p--%p--%d\n", this, m_pW3Context, GetCurrentThreadId()); + g_strLogs[counter].SyncWithBuffer(); +#endif // DEBUG + // Marked the request is finished, no more PostCompletion is allowed + m_fFinishRequest = TRUE; + } + } + } + else if (!fAnotherCompletionExpected) + { + // + // Regular async IO operation + // + fDoPostCompletion = !m_fFinishRequest; +#ifdef DEBUG + if (fDoPostCompletion) + { + DWORD counter = InterlockedIncrement(&g_dwLogCounter) % ASPNETCORE_DEBUG_STRU_ARRAY_SIZE; + sprintf_s(g_strLogs[counter].QueryStr(), ASPNETCORE_DEBUG_STRU_BUFFER_SIZE, + "PostCompletion 2 --%p--%p--%d\n", this, m_pW3Context, GetCurrentThreadId()); + g_strLogs[counter].SyncWithBuffer(); + } +#endif // DEBUG + } + + // + // No code should access IIS m_pW3Context after posting the completion. + // + if (fDoPostCompletion) + { + m_pW3Context->PostCompletion(0); + } + + if (fExclusiveLock) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockExclusive(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } + else if (fSharedLock) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockShared(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } + + DereferenceForwardingHandler(); + +} + +HRESULT +FORWARDING_HANDLER::OnWinHttpCompletionSendRequestOrWriteComplete( + HINTERNET hRequest, + DWORD, + __out BOOL * pfClientError, + __out BOOL * pfAnotherCompletionExpected +) +{ + HRESULT hr = S_OK; + IHttpRequest * pRequest = m_pW3Context->GetRequest(); + + // + // completion for sending the initial request or request entity to + // winhttp, get more request entity if available, else start receiving + // the response + // + if (m_BytesToReceive > 0) + { + if (m_pEntityBuffer == NULL) + { + m_pEntityBuffer = GetNewResponseBuffer( + ENTITY_BUFFER_SIZE); + if (m_pEntityBuffer == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + } + + if (sm_pTraceLog != NULL) + { + WriteRefTraceLogEx(sm_pTraceLog, + m_cRefs, + this, + "Calling ReadEntityBody", + NULL, + NULL); + } + hr = pRequest->ReadEntityBody( + m_pEntityBuffer + 6, + min(m_BytesToReceive, BUFFER_SIZE), + TRUE, // fAsync + NULL, // pcbBytesReceived + NULL); // pfCompletionPending + if (hr == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF)) + { + DBG_ASSERT(m_BytesToReceive == 0 || + m_BytesToReceive == INFINITE); + + // + // ERROR_HANDLE_EOF is not an error. + // + hr = S_OK; + + if (m_BytesToReceive == INFINITE) + { + m_BytesToReceive = 0; + m_cchLastSend = 5; + + if (!WinHttpWriteData(m_hRequest, + "0\r\n\r\n", + 5, + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + *pfAnotherCompletionExpected = TRUE; + + goto Finished; + } + } + else if (FAILED(hr)) + { + *pfClientError = TRUE; + goto Finished; + } + else + { + // + // ReadEntityBody will post a completion to IIS. + // + *pfAnotherCompletionExpected = TRUE; + + goto Finished; + } + } + + m_RequestStatus = FORWARDER_RECEIVING_RESPONSE; + + if (!WinHttpReceiveResponse(hRequest, NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + *pfAnotherCompletionExpected = TRUE; + +Finished: + + return hr; +} + +HRESULT +FORWARDING_HANDLER::OnWinHttpCompletionStatusHeadersAvailable( + HINTERNET hRequest, + __out BOOL * pfAnotherCompletionExpected +) +{ + HRESULT hr = S_OK; + STACK_BUFFER(bufHeaderBuffer, 2048); + STACK_STRA(strHeaders, 2048); + DWORD dwHeaderSize = bufHeaderBuffer.QuerySize(); + + UNREFERENCED_PARAMETER(pfAnotherCompletionExpected); + + // + // Headers are available, read the status line and headers and pass + // them on to the client + // + // WinHttpQueryHeaders operates synchronously, + // no need for taking reference. + // + dwHeaderSize = bufHeaderBuffer.QuerySize(); + if (!WinHttpQueryHeaders(hRequest, + WINHTTP_QUERY_RAW_HEADERS_CRLF, + WINHTTP_HEADER_NAME_BY_INDEX, + bufHeaderBuffer.QueryPtr(), + &dwHeaderSize, + WINHTTP_NO_HEADER_INDEX)) + { + if (!bufHeaderBuffer.Resize(dwHeaderSize)) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + // + // WinHttpQueryHeaders operates synchronously, + // no need for taking reference. + // + if (!WinHttpQueryHeaders(hRequest, + WINHTTP_QUERY_RAW_HEADERS_CRLF, + WINHTTP_HEADER_NAME_BY_INDEX, + bufHeaderBuffer.QueryPtr(), + &dwHeaderSize, + WINHTTP_NO_HEADER_INDEX)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + } + + if (FAILED(hr = strHeaders.CopyW( + reinterpret_cast(bufHeaderBuffer.QueryPtr())))) + { + goto Finished; + } + + // Issue: The reason we add trailing \r\n is to eliminate issues that have been observed + // in some configurations where status and headers would not have final \r\n nor \r\n\r\n + // (last header was null terminated).That caused crash within header parsing code that expected valid + // format. Parsing code was fized to return ERROR_INVALID_PARAMETER, but we still should make + // Example of a status+header string that was causing problems (note the missing \r\n at the end) + // HTTP/1.1 302 Moved Permanently\r\n....\r\nLocation:http://site\0 + // + + if (!strHeaders.IsEmpty() && strHeaders.QueryStr()[strHeaders.QueryCCH() - 1] != '\n') + { + hr = strHeaders.Append("\r\n"); + if (FAILED(hr)) + { + goto Finished; + } + } + + if (FAILED(hr = SetStatusAndHeaders( + strHeaders.QueryStr(), + strHeaders.QueryCCH()))) + { + goto Finished; + } + + FreeResponseBuffers(); + + // + // If the request was websocket, and response was 101, + // trigger a flush, so that IIS's websocket module + // can get a chance to initialize and complete the handshake. + // + + if (m_fWebSocketEnabled) + { + + hr = m_pW3Context->GetResponse()->Flush( + TRUE, + TRUE, + NULL, + NULL); + + if (FAILED(hr)) + { + *pfAnotherCompletionExpected = FALSE; + } + else + { + m_RequestStatus = FORWARDER_RECEIVED_WEBSOCKET_RESPONSE; + *pfAnotherCompletionExpected = TRUE; + } + } + +Finished: + + return hr; +} + +HRESULT +FORWARDING_HANDLER::OnWinHttpCompletionStatusDataAvailable( + HINTERNET hRequest, + DWORD dwBytes, + __out BOOL * pfAnotherCompletionExpected +) +{ + HRESULT hr = S_OK; + + // + // Response data is available from winhttp, read it + // + if (dwBytes == 0) + { + if (m_cContentLength != 0) + { + hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE); + goto Finished; + } + + m_RequestStatus = FORWARDER_DONE; + + goto Finished; + } + + m_BytesToSend = dwBytes; + if (m_cContentLength != 0) + { + m_cContentLength -= dwBytes; + } + + m_pEntityBuffer = GetNewResponseBuffer( + min(m_BytesToSend, BUFFER_SIZE)); + if (m_pEntityBuffer == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (!WinHttpReadData(hRequest, + m_pEntityBuffer, + min(m_BytesToSend, BUFFER_SIZE), + NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + *pfAnotherCompletionExpected = TRUE; + +Finished: + + return hr; +} + +HRESULT +FORWARDING_HANDLER::OnWinHttpCompletionStatusReadComplete( + __in IHttpResponse * pResponse, + DWORD dwStatusInformationLength, + __out BOOL * pfAnotherCompletionExpected +) +{ + HRESULT hr = S_OK; + + // + // Response data has been read from winhttp, send it to the client + // + m_BytesToSend -= dwStatusInformationLength; + + if (m_cMinBufferLimit >= BUFFER_SIZE / 2) + { + if (m_cContentLength != 0) + { + m_cContentLength -= dwStatusInformationLength; + } + + // + // If we were not using WinHttpQueryDataAvailable and winhttp + // did not fill our buffer, we must have reached the end of the + // response + // + if (dwStatusInformationLength == 0 || + m_BytesToSend != 0) + { + if (m_cContentLength != 0) + { + hr = HRESULT_FROM_WIN32(ERROR_WINHTTP_INVALID_SERVER_RESPONSE); + goto Finished; + } + + m_RequestStatus = FORWARDER_DONE; + } + } + else + { + DBG_ASSERT(dwStatusInformationLength != 0); + } + + if (dwStatusInformationLength == 0) + { + goto Finished; + } + else + { + m_cBytesBuffered += dwStatusInformationLength; + + HTTP_DATA_CHUNK Chunk; + Chunk.DataChunkType = HttpDataChunkFromMemory; + Chunk.FromMemory.pBuffer = m_pEntityBuffer; + Chunk.FromMemory.BufferLength = dwStatusInformationLength; + if (FAILED(hr = pResponse->WriteEntityChunkByReference(&Chunk))) + { + goto Finished; + } + } + + if (m_cBytesBuffered >= m_cMinBufferLimit) + { + // + // Always post a completion to resume the WinHTTP data pump. + // + hr = pResponse->Flush(TRUE, // fAsync + TRUE, // fMoreData + NULL); // pcbSent + if (FAILED(hr)) + { + goto Finished; + } + *pfAnotherCompletionExpected = TRUE; + } + else + { + *pfAnotherCompletionExpected = FALSE; + } + +Finished: + + return hr; +} + +// static +HRESULT +FORWARDING_HANDLER::StaticInitialize( + BOOL fEnableReferenceCountTracing +) +/*++ + +Routine Description: + +Global initialization routine for FORWARDING_HANDLERs + +Arguments: + +fEnableReferenceCountTracing - True if ref count tracing should be use. + +Return Value: + +HRESULT + +--*/ +{ + HRESULT hr = S_OK; + + sm_pAlloc = new ALLOC_CACHE_HANDLER; + if (sm_pAlloc == NULL) + { + hr = E_OUTOFMEMORY; + goto Failure; + } + + hr = sm_pAlloc->Initialize(sizeof(FORWARDING_HANDLER), + 128); // nThreshold + if (FAILED(hr)) + { + goto Failure; + } + + // + // Open the session handle, specify random user-agent that will be + // overwritten by the client + // + sm_hSession = WinHttpOpen(L"", + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + WINHTTP_FLAG_ASYNC); + if (sm_hSession == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + + // + // Don't set non-blocking callbacks WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS, + // as we will call WinHttpQueryDataAvailable to get response on the same thread + // that we received callback from Winhttp on completing sending/forwarding the request + // + + // + // Setup the callback function + // + if (WinHttpSetStatusCallback(sm_hSession, + FORWARDING_HANDLER::OnWinHttpCompletion, + (WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS | + WINHTTP_CALLBACK_STATUS_SENDING_REQUEST), + NULL) == WINHTTP_INVALID_STATUS_CALLBACK) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + + // + // Make sure we see the redirects (rather than winhttp doing it + // automatically) + // + DWORD dwRedirectOption = WINHTTP_OPTION_REDIRECT_POLICY_NEVER; + if (!WinHttpSetOption(sm_hSession, + WINHTTP_OPTION_REDIRECT_POLICY, + &dwRedirectOption, + sizeof(dwRedirectOption))) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + + // Initialize Application Manager + APPLICATION_MANAGER *pApplicationManager = APPLICATION_MANAGER::GetInstance(); + if (pApplicationManager == NULL) + { + hr = E_OUTOFMEMORY; + goto Failure; + } + + hr = pApplicationManager->Initialize(); + if (FAILED(hr)) + { + goto Failure; + } + + // Initialize PROTOCOL_CONFIG + sm_ProtocolConfig.Initialize(); + + if (FAILED(hr = sm_strErrorFormat.Resize(256))) + { + goto Failure; + } + + if (LoadString(g_hModule, + IDS_INVALID_PROPERTY, + sm_strErrorFormat.QueryStr(), + sm_strErrorFormat.QuerySizeCCH()) == 0) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + sm_strErrorFormat.SyncWithBuffer(); + + // If RegisterEventSource failed, we cannot do any thing about it + // No need to check whether the returned handle is valid + + if (g_pHttpServer->IsCommandLineLaunch()) + { + sm_hEventLog = RegisterEventSource(NULL, ASPNETCORE_IISEXPRESS_EVENT_PROVIDER); + } + else + { + sm_hEventLog = RegisterEventSource(NULL, ASPNETCORE_EVENT_PROVIDER); + } + + g_dwTlsIndex = TlsAlloc(); + if (g_dwTlsIndex == TLS_OUT_OF_INDEXES) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Failure; + } + + if (fEnableReferenceCountTracing) + { + sm_pTraceLog = CreateRefTraceLog(10000, 0); + } + + return S_OK; + +Failure: + + StaticTerminate(); + + return hr; +} + +// static +VOID +FORWARDING_HANDLER::StaticTerminate( + VOID +) +/*++ + +Routine Description: + +Global termination routine for FORWARDING_HANDLERs + +Arguments: + +None + +Return Value: + +None + +--*/ +{ + // + // Delete all the statics + // + + APPLICATION_MANAGER::Cleanup(); + + // + // wait for all server processes to go away + // for a max of 10 seconds. + // + + DWORD tickCount = GetTickCount(); + + while (g_dwActiveServerProcesses > 0) + { + if ((GetTickCount() - tickCount) > 10000) + { + break; + } + Sleep(250); + } + + if (sm_hSession != NULL) + { + WinHttpCloseHandle(sm_hSession); + sm_hSession = NULL; + } + + if (sm_hEventLog != NULL) + { + DeregisterEventSource(sm_hEventLog); + sm_hEventLog = NULL; + } + + if (g_dwTlsIndex != TLS_OUT_OF_INDEXES) + { + DBG_REQUIRE(TlsFree(g_dwTlsIndex)); + g_dwTlsIndex = TLS_OUT_OF_INDEXES; + } + + sm_strErrorFormat.Reset(); + + if (sm_pTraceLog != NULL) + { + DestroyRefTraceLog(sm_pTraceLog); + sm_pTraceLog = NULL; + } + + if (sm_pAlloc != NULL) + { + delete sm_pAlloc; + sm_pAlloc = NULL; + } +} + +VOID +CopyMultiSzToOutput( + IGlobalRSCAQueryProvider * pProvider, + PCWSTR pszList, + DWORD * pcbData +) +{ + PBYTE pvData; + DWORD cbData = 0; + PCWSTR pszListCopy = pszList; + while (*pszList != L'\0') + { + cbData += (static_cast(wcslen(pszList)) + 1) * sizeof(WCHAR); + pszList += wcslen(pszList) + 1; + } + cbData += sizeof(WCHAR); + if (FAILED(pProvider->GetOutputBuffer(cbData, + &pvData))) + { + return; + } + memcpy(pvData, + pszListCopy, + cbData); + *pcbData = cbData; +} + +struct AFFINITY_LOOKUP_CONTEXT +{ + DWORD timeout; + PCWSTR pszServer; + BUFFER * pHostNames; + DWORD cbData; +}; + +struct CACHE_CONTEXT +{ + PCSTR pszHostName; + IGlobalRSCAQueryProvider *pProvider; + __field_bcount_part(cbBuffer, cbData) + PBYTE pvData; + DWORD cbData; + DWORD cbBuffer; +}; + +VOID +FORWARDING_HANDLER::TerminateRequest( + BOOL fClientInitiated +) +{ + BOOL fAcquiredLock = FALSE; + if (TlsGetValue(g_dwTlsIndex) != this) + { + AcquireSRWLockExclusive(&m_RequestLock); + TlsSetValue(g_dwTlsIndex, this); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + fAcquiredLock = TRUE; + } + + // Only set the disconnect flag as the disconnect happens, request most likely is in OnAsyncCompletion + // If we close the handle here, most likely WinHttp callback happens on the same threads. + // We will have two OnAyncCompletion calls and may have race on PostCompletion + // Need do more investigation + if (!m_fHttpHandleInClose) + { + m_fClientDisconnected = fClientInitiated; + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "FORWARDING_HANDLER::TerminateRequest"); + RemoveRequest(); + + if (m_RequestStatus == FORWARDER_RECEIVED_WEBSOCKET_RESPONSE) + { + // + // websocket client is gone, cannot finish closing handshake gracefully + // have to terminate the request + // + if (m_pWebSocket != NULL) + { + m_pWebSocket->TerminateRequest(); + } + } + } + if (fAcquiredLock) + { + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == this); + TlsSetValue(g_dwTlsIndex, NULL); + ReleaseSRWLockExclusive(&m_RequestLock); + DBG_ASSERT(TlsGetValue(g_dwTlsIndex) == NULL); + } +} + +BYTE * +FORWARDING_HANDLER::GetNewResponseBuffer( + DWORD dwBufferSize +) +{ + DWORD dwNeededSize = (m_cEntityBuffers + 1) * sizeof(BYTE *); + if (dwNeededSize > m_buffEntityBuffers.QuerySize() && + !m_buffEntityBuffers.Resize( + max(dwNeededSize, m_buffEntityBuffers.QuerySize() * 2))) + { + return NULL; + } + + BYTE *pBuffer = (BYTE *)HeapAlloc(GetProcessHeap(), + 0, // dwFlags + dwBufferSize); + if (pBuffer == NULL) + { + return NULL; + } + + m_buffEntityBuffers.QueryPtr()[m_cEntityBuffers] = pBuffer; + m_cEntityBuffers++; + + return pBuffer; +} + +VOID +FORWARDING_HANDLER::FreeResponseBuffers() +{ + BYTE **pBuffers = m_buffEntityBuffers.QueryPtr(); + for (DWORD i = 0; i + +#ifdef DEBUG +DECLARE_DEBUG_PRINTS_OBJECT(); +DECLARE_DEBUG_VARIABLE(); +DECLARE_PLATFORM_TYPE(); +#endif // DEBUG + +HTTP_MODULE_ID g_pModuleId = NULL; +IHttpServer * g_pHttpServer = NULL; +BOOL g_fAsyncDisconnectAvailable = FALSE; +BOOL g_fWinHttpNonBlockingCallbackAvailable = FALSE; +PCWSTR g_pszModuleName = NULL; +HINSTANCE g_hModule; +HINSTANCE g_hWinHttpModule; +BOOL g_fWebSocketSupported = FALSE; +DWORD g_dwTlsIndex = TLS_OUT_OF_INDEXES; +BOOL g_fEnableReferenceCountTracing = FALSE; +DWORD g_dwAspNetCoreDebugFlags = 0; +BOOL g_fNsiApiNotSupported = FALSE; +DWORD g_dwActiveServerProcesses = 0; +DWORD g_OptionalWinHttpFlags = 0; //specify additional WinHTTP options when using WinHttpOpenRequest API. +DWORD g_dwDebugFlags = 0; +PCSTR g_szDebugLabel = "ASPNET_CORE_MODULE"; + +#ifdef DEBUG +STRA g_strLogs[ASPNETCORE_DEBUG_STRU_ARRAY_SIZE]; +DWORD g_dwLogCounter = 0; +#endif // DEBUG + + +BOOL WINAPI DllMain( + HMODULE hModule, + DWORD dwReason, + LPVOID + ) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + g_hModule = hModule; + DisableThreadLibraryCalls(hModule); + break; + default: + break; + } + + return TRUE; +} + +VOID +LoadGlobalConfiguration( +VOID +) +{ + HKEY hKey; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module\\Parameters", + 0, + KEY_READ, + &hKey) == NO_ERROR) + { + DWORD dwType; + DWORD dwData; + DWORD cbData; + + cbData = sizeof(dwData); + if ((RegQueryValueEx(hKey, + L"OptionalWinHttpFlags", + NULL, + &dwType, + (LPBYTE)&dwData, + &cbData) == NO_ERROR) && + (dwType == REG_DWORD)) + { + g_OptionalWinHttpFlags = dwData; + } + + cbData = sizeof(dwData); + if ((RegQueryValueEx(hKey, + L"EnableReferenceCountTracing", + NULL, + &dwType, + (LPBYTE)&dwData, + &cbData) == NO_ERROR) && + (dwType == REG_DWORD) && (dwData == 1 || dwData == 0)) + { + g_fEnableReferenceCountTracing = !!dwData; + } + + cbData = sizeof(dwData); + if ((RegQueryValueEx(hKey, + L"DebugFlags", + NULL, + &dwType, + (LPBYTE)&dwData, + &cbData) == NO_ERROR) && + (dwType == REG_DWORD)) + { + g_dwAspNetCoreDebugFlags = dwData; + } + + RegCloseKey(hKey); + } + + DWORD dwSize = 0; + DWORD dwResult = GetExtendedTcpTable(NULL, + &dwSize, + FALSE, + AF_INET, + TCP_TABLE_OWNER_PID_LISTENER, + 0); + if (dwResult != NO_ERROR && dwResult != ERROR_INSUFFICIENT_BUFFER) + { + g_fNsiApiNotSupported = TRUE; + } +} + +HRESULT +__stdcall +RegisterModule( +DWORD dwServerVersion, +IHttpModuleRegistrationInfo * pModuleInfo, +IHttpServer * pHttpServer +) +/*++ + +Routine description: + +Function called by IIS immediately after loading the module, used to let +IIS know what notifications the module is interested in + +Arguments: + +dwServerVersion - IIS version the module is being loaded on +pModuleInfo - info regarding this module +pHttpServer - callback functions which can be used by the module at +any point + +Return value: + +HRESULT + +--*/ +{ + HRESULT hr = S_OK; + CProxyModuleFactory * pFactory = NULL; + +#ifdef DEBUG + CREATE_DEBUG_PRINT_OBJECT("Asp.Net Core Module"); + g_dwDebugFlags = DEBUG_FLAGS_ANY; +#endif // DEBUG + + LoadGlobalConfiguration(); + + // + // 7.0 is 0,7 + // + if (dwServerVersion > MAKELONG(0, 7)) + { + g_fAsyncDisconnectAvailable = TRUE; + } + + // + // 8.0 is 0,8 + // + if (dwServerVersion >= MAKELONG(0, 8)) + { + // IISOOB:36641 Enable back WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS for Win8. + // g_fWinHttpNonBlockingCallbackAvailable = TRUE; + g_fWebSocketSupported = TRUE; + } + + hr = WINHTTP_HELPER::StaticInitialize(); + if (FAILED(hr)) + { + if (hr == HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND)) + { + g_fWebSocketSupported = FALSE; + } + else + { + goto Finished; + } + } + + g_pModuleId = pModuleInfo->GetId(); + g_pszModuleName = pModuleInfo->GetName(); + g_pHttpServer = pHttpServer; + +#ifdef DEBUG + for (int i = 0; i < ASPNETCORE_DEBUG_STRU_ARRAY_SIZE; i++) + { + g_strLogs[i].Resize(ASPNETCORE_DEBUG_STRU_BUFFER_SIZE + 1); + } +#endif // DEBUG + // + // WinHTTP does not create enough threads, ask it to create more. + // Starting in Windows 7, this setting is ignored because WinHTTP + // uses a thread pool. + // + SYSTEM_INFO si; + GetSystemInfo(&si); + DWORD dwThreadCount = (si.dwNumberOfProcessors * 3 + 1) / 2; + WinHttpSetOption(NULL, + WINHTTP_OPTION_WORKER_THREAD_COUNT, + &dwThreadCount, + sizeof(dwThreadCount)); + + // + // Create the factory before any static initialization. + // The CProxyModuleFactory::Terminate method will clean any + // static object initialized. + // + pFactory = new CProxyModuleFactory; + if (pFactory == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = pModuleInfo->SetRequestNotifications( + pFactory, + RQ_EXECUTE_REQUEST_HANDLER, + 0); + if (FAILED(hr)) + { + goto Finished; + } + + pFactory = NULL; + g_pResponseHeaderHash = new RESPONSE_HEADER_HASH; + if (g_pResponseHeaderHash == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = g_pResponseHeaderHash->Initialize(); + if (FAILED(hr)) + { + goto Finished; + } + + hr = ALLOC_CACHE_HANDLER::StaticInitialize(); + if (FAILED(hr)) + { + goto Finished; + } + + hr = FORWARDING_HANDLER::StaticInitialize(g_fEnableReferenceCountTracing); + if (FAILED(hr)) + { + goto Finished; + } + + hr = WEBSOCKET_HANDLER::StaticInitialize(g_fEnableReferenceCountTracing); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + + if (pFactory != NULL) + { + pFactory->Terminate(); + pFactory = NULL; + } + + return hr; +} + diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/path.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/path.cxx new file mode 100644 index 0000000000..cde0dd5312 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/path.cxx @@ -0,0 +1,442 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +// static +HRESULT +PATH::SplitUrl( + PCWSTR pszDestinationUrl, + BOOL *pfSecure, + STRU *pstrDestination, + STRU *pstrUrl +) +/*++ + +Routine Description: + + Split the URL specified for forwarding into its specific components + The format of the URL looks like + http[s]://destination[:port]/path + when port is omitted, the default port for that specific protocol is used + when host is omitted, it gets the same value as the destination + +Arguments: + + pszDestinationUrl - the url to be split up + pfSecure - SSL to be used in forwarding? + pstrDestination - destination + pDestinationPort - port + pstrUrl - URL + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr; + + // + // First determine if the target is secure + // + if (_wcsnicmp(pszDestinationUrl, L"http://", 7) == 0) + { + *pfSecure = FALSE; + pszDestinationUrl += 7; + } + else if (_wcsnicmp(pszDestinationUrl, L"https://", 8) == 0) + { + *pfSecure = TRUE; + pszDestinationUrl += 8; + } + else + { + return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + + if (*pszDestinationUrl == L'\0') + { + return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); + } + + // + // Find the 3rd slash corresponding to the url + // + LPCWSTR pszSlash = wcschr(pszDestinationUrl, L'/'); + if (pszSlash == NULL) + { + if (FAILED(hr = pstrUrl->Copy(L"/", 1)) || + FAILED(hr = pstrDestination->Copy(pszDestinationUrl))) + { + return hr; + } + } + else + { + if (FAILED(hr = pstrUrl->Copy(pszSlash)) || + FAILED(hr = pstrDestination->Copy(pszDestinationUrl, + (DWORD)(pszSlash - pszDestinationUrl)))) + { + return hr; + } + } + + return S_OK; +} + +// Change a hexadecimal digit to its numerical equivalent +#define TOHEX( ch ) \ + ((ch) > L'9' ? \ + (ch) >= L'a' ? \ + (ch) - L'a' + 10 : \ + (ch) - L'A' + 10 \ + : (ch) - L'0') + +// static +HRESULT +PATH::UnEscapeUrl( + PCWSTR pszUrl, + DWORD cchUrl, + bool fCopyQuery, + STRA * pstrResult +) +{ + HRESULT hr; + CHAR pch[2]; + pch[1] = '\0'; + DWORD cchStart = 0; + DWORD index = 0; + + while (index < cchUrl && + (fCopyQuery || pszUrl[index] != L'?')) + { + switch (pszUrl[index]) + { + case L'%': + if (iswxdigit(pszUrl[index+1]) && iswxdigit(pszUrl[index+2])) + { + if (index > cchStart && + FAILED(hr = pstrResult->AppendW(pszUrl + cchStart, + index - cchStart))) + { + return hr; + } + cchStart = index+3; + + pch[0] = static_cast(TOHEX(pszUrl[index+1]) * 16 + + TOHEX(pszUrl[index+2])); + if (FAILED(hr = pstrResult->Append(pch, 1))) + { + return hr; + } + index += 3; + break; + } + + __fallthrough; + default: + index++; + } + } + + if (index > cchStart) + { + return pstrResult->AppendW(pszUrl + cchStart, + index - cchStart); + } + + return S_OK; +} + +// static +HRESULT +PATH::UnEscapeUrl( + PCWSTR pszUrl, + DWORD cchUrl, + STRU * pstrResult +) +{ + HRESULT hr; + WCHAR pch[2]; + pch[1] = L'\0'; + DWORD cchStart = 0; + DWORD index = 0; + bool fInQuery = FALSE; + + while (index < cchUrl) + { + switch (pszUrl[index]) + { + case L'%': + if (iswxdigit(pszUrl[index+1]) && iswxdigit(pszUrl[index+2])) + { + if (index > cchStart && + FAILED(hr = pstrResult->Append(pszUrl + cchStart, + index - cchStart))) + { + return hr; + } + cchStart = index+3; + + pch[0] = static_cast(TOHEX(pszUrl[index+1]) * 16 + + TOHEX(pszUrl[index+2])); + if (FAILED(hr = pstrResult->Append(pch, 1))) + { + return hr; + } + index += 3; + if (pch[0] == L'?') + { + fInQuery = TRUE; + } + break; + } + + index++; + break; + + case L'/': + if (fInQuery) + { + if (index > cchStart && + FAILED(hr = pstrResult->Append(pszUrl + cchStart, + index - cchStart))) + { + return hr; + } + cchStart = index+1; + + if (FAILED(hr = pstrResult->Append(L"\\", 1))) + { + return hr; + } + index += 1; + break; + } + + __fallthrough; + default: + index++; + } + } + + if (index > cchStart) + { + return pstrResult->Append(pszUrl + cchStart, + index - cchStart); + } + + return S_OK; +} + +HRESULT +PATH::EscapeAbsPath( + IHttpRequest * pRequest, + STRU * strEscapedUrl +) +{ + HRESULT hr = S_OK; + STRU strAbsPath; + LPCWSTR pszAbsPath = NULL; + LPCWSTR pszFindStr = NULL; + + hr = strAbsPath.Copy( pRequest->GetRawHttpRequest()->CookedUrl.pAbsPath, + pRequest->GetRawHttpRequest()->CookedUrl.AbsPathLength / sizeof(WCHAR) ); + if(FAILED(hr)) + { + goto Finished; + } + + pszAbsPath = strAbsPath.QueryStr(); + pszFindStr = wcschr(pszAbsPath, L'?'); + + while(pszFindStr != NULL) + { + strEscapedUrl->Append( pszAbsPath, pszFindStr - pszAbsPath); + strEscapedUrl->Append(L"%3F"); + pszAbsPath = pszFindStr + 1; + pszFindStr = wcschr(pszAbsPath, L'?'); + } + + strEscapedUrl->Append(pszAbsPath); + strEscapedUrl->Append(pRequest->GetRawHttpRequest()->CookedUrl.pQueryString, + pRequest->GetRawHttpRequest()->CookedUrl.QueryStringLength / sizeof(WCHAR)); + +Finished: + return hr; +} + +// static +bool +PATH::IsValidAttributeNameChar( + WCHAR ch +) +{ + // + // Values based on ASP.NET rendering for cookie names. RFC 2965 is not clear + // what the non-special characters are. + // + return ch == L'\t' || (ch > 31 && ch < 127); +} + +// static +bool +PATH::FindInMultiString( + PCWSTR pszMultiString, + PCWSTR pszStringToFind +) +{ + while (*pszMultiString != L'\0') + { + if (wcscmp(pszMultiString, pszStringToFind) == 0) + { + return TRUE; + } + pszMultiString += wcslen(pszMultiString) + 1; + } + + return FALSE; +} + +// static +bool +PATH::IsValidQueryStringName( + PCWSTR pszName +) +{ + while (*pszName != L'\0') + { + WCHAR c = *pszName; + if (c != L'-' && c != L'_' && c != L'+' && + c != L'.' && c != L'*' && c != L'$' && c != L'%' && c != L',' && + !iswalnum(c)) + { + return FALSE; + } + pszName++; + } + + return TRUE; +} + +// static +bool +PATH::IsValidHeaderName( + PCWSTR pszName +) +{ + while (*pszName != L'\0') + { + WCHAR c = *pszName; + if (c != L'-' && c != L'_' && c != L'+' && + c != L'.' && c != L'*' && c != L'$' && c != L'%' + && !iswalnum(c)) + { + return FALSE; + } + pszName++; + } + + return TRUE; +} + +HRESULT +PATH::IsPathUnc( + __in LPCWSTR pszPath, + __out BOOL * pfIsUnc +) +{ + HRESULT hr = S_OK; + STRU strTempPath; + + if ( pszPath == NULL || pfIsUnc == NULL ) + { + hr = E_INVALIDARG; + goto Finished; + } + + hr = MakePathCanonicalizationProof( (LPWSTR) pszPath, &strTempPath ); + if ( FAILED(hr) ) + { + goto Finished; + } + + // + // MakePathCanonicalizationProof will map \\?\UNC, \\.\UNC and \\ to \\?\UNC + // + (*pfIsUnc) = ( _wcsnicmp( strTempPath.QueryStr(), L"\\\\?\\UNC\\", 8 /* sizeof \\?\UNC\ */) == 0 ); + +Finished: + + return hr; +} + +HRESULT +PATH::ConvertPathToFullPath( + _In_ LPCWSTR pszPath, + _In_ LPCWSTR pszRootPath, + _Out_ STRU* pStruFullPath +) +{ + HRESULT hr = S_OK; + STRU strFileFullPath; + LPWSTR pszFullPath = NULL; + + // if relative path, prefix with root path and then convert to absolute path. + if( pszPath[0] == L'.' ) + { + hr = strFileFullPath.Copy(pszRootPath); + if(FAILED(hr)) + { + goto Finished; + } + + if(!strFileFullPath.EndsWith(L"\\")) + { + hr = strFileFullPath.Append(L"\\"); + if(FAILED(hr)) + { + goto Finished; + } + } + } + + hr = strFileFullPath.Append( pszPath ); + if(FAILED(hr)) + { + goto Finished; + } + + pszFullPath = new WCHAR[ strFileFullPath.QueryCCH() + 1]; + if( pszFullPath == NULL ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if(_wfullpath( pszFullPath, + strFileFullPath.QueryStr(), + strFileFullPath.QueryCCH() + 1 ) == NULL ) + { + hr = HRESULT_FROM_WIN32( ERROR_INVALID_PARAMETER ); + goto Finished; + } + + // convert to canonical path + hr = MakePathCanonicalizationProof( pszFullPath, pStruFullPath ); + if(FAILED(hr)) + { + goto Finished; + } + +Finished: + + if( pszFullPath != NULL ) + { + delete[] pszFullPath; + pszFullPath = NULL; + } + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/precomp.hxx b/src/AspNetCoreModuleV1/AspNetCore/src/precomp.hxx new file mode 100644 index 0000000000..6cccc90ab7 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/precomp.hxx @@ -0,0 +1,147 @@ +#pragma once + +/*++ + +Copyright (c) 2013 Microsoft Corporation + +Module Name: + + precomp.hxx + +Abstract: + + Precompiled header. + +--*/ + +// +// System related headers +// + +#define _WIN32_WINNT 0x0600 +#define _WINSOCKAPI_ +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +// +// Option available starting Windows 8. +// 111 is the value in SDK on May 15, 2012. +// +#ifndef WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS +#define WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS 111 +#endif + +#define ASPNETCORE_EVENT_PROVIDER L"IIS AspNetCore Module" +#define ASPNETCORE_IISEXPRESS_EVENT_PROVIDER L"IIS Express AspNetCore Module" + +#define TIMESPAN_IN_MILLISECONDS(x) ((x)/((LONGLONG)(10000))) +#define TIMESPAN_IN_SECONDS(x) ((TIMESPAN_IN_MILLISECONDS(x))/((LONGLONG)(1000))) +#define TIMESPAN_IN_MINUTES(x) ((TIMESPAN_IN_SECONDS(x))/((LONGLONG)(60))) + +#ifdef max +#undef max +template inline T max(T a, T b) +{ + return a > b ? a : b; +} +#endif + +#ifdef min +#undef min +template inline T min(T a, T b) +{ + return a < b ? a : b; +} +#endif + +inline bool IsSpace(char ch) +{ + switch(ch) + { + case 32: // ' ' + case 9: // '\t' + case 10: // '\n' + case 13: // '\r' + case 11: // '\v' + case 12: // '\f' + return true; + default: + return false; + } +} + +#include +#include +#include +#include +#include + +#include +#include "ahutil.h" +#include "multisz.hxx" +#include "multisza.hxx" +#include "sttimer.h" +#include +#include +#include +#include +#include +#include + +#include "filewatcher.h" +#include "environmentvariablehash.h" +#include "aspnetcore_msg.h" +#include "aspnetcoreconfig.h" +#include "serverprocess.h" +#include "processmanager.h" +#include "application.h" +#include "applicationmanager.h" +#include "resource.h" +#include "path.h" +#include "debugutil.h" +#include "protocolconfig.h" +#include "responseheaderhash.h" +#include "forwarderconnection.h" +#include "winhttphelper.h" +#include "websockethandler.h" +#include "forwardinghandler.h" +#include "proxymodule.h" + +FORCEINLINE +DWORD +WIN32_FROM_HRESULT( + HRESULT hr +) +{ + if ((FAILED(hr)) && + (HRESULT_FACILITY(hr) == FACILITY_WIN32)) + { + return HRESULT_CODE(hr); + } + return hr; +} + +FORCEINLINE +HRESULT +HRESULT_FROM_GETLASTERROR() +{ + return ( GetLastError() != NO_ERROR ) + ? HRESULT_FROM_WIN32( GetLastError() ) + : E_FAIL; +} + +extern BOOL g_fAsyncDisconnectAvailable; +extern PVOID g_pModuleId; +extern BOOL g_fWebSocketSupported; +extern BOOL g_fEnableReferenceCountTracing; +extern DWORD g_dwActiveServerProcesses; \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/processmanager.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/processmanager.cxx new file mode 100644 index 0000000000..5029a52670 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/processmanager.cxx @@ -0,0 +1,294 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +volatile BOOL PROCESS_MANAGER::sm_fWSAStartupDone = FALSE; + +HRESULT +PROCESS_MANAGER::Initialize( + VOID +) +{ + HRESULT hr = S_OK; + WSADATA wsaData; + int result; + BOOL fLocked = FALSE; + + if( !sm_fWSAStartupDone ) + { + AcquireSRWLockExclusive( &m_srwLock ); + fLocked = TRUE; + + if( !sm_fWSAStartupDone ) + { + if( (result = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0 ) + { + hr = HRESULT_FROM_WIN32( result ); + goto Finished; + } + sm_fWSAStartupDone = TRUE; + } + + ReleaseSRWLockExclusive( &m_srwLock ); + fLocked = FALSE; + } + + m_dwRapidFailTickStart = GetTickCount(); + + if( m_hNULHandle == NULL ) + { + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + m_hNULHandle = CreateFileW( L"NUL", + FILE_WRITE_DATA, + FILE_SHARE_READ, + &saAttr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL ); + if( m_hNULHandle == INVALID_HANDLE_VALUE ) + { + hr = HRESULT_FROM_GETLASTERROR(); + goto Finished; + } + } + +Finished: + + if(fLocked) + { + ReleaseSRWLockExclusive( &m_srwLock ); + } + + return hr; +} + +PROCESS_MANAGER::~PROCESS_MANAGER() +{ + AcquireSRWLockExclusive(&m_srwLock); + + if( m_ppServerProcessList != NULL ) + { + for( DWORD i = 0; i < m_dwProcessesPerApplication; ++i ) + { + if( m_ppServerProcessList[i] != NULL ) + { + m_ppServerProcessList[i]->DereferenceServerProcess(); + m_ppServerProcessList[i] = NULL; + } + } + + delete[] m_ppServerProcessList; + m_ppServerProcessList = NULL; + } + + if( m_hNULHandle != NULL ) + { + CloseHandle( m_hNULHandle ); + m_hNULHandle = NULL; + } + + if( sm_fWSAStartupDone ) + { + WSACleanup(); + sm_fWSAStartupDone = FALSE; + } + + ReleaseSRWLockExclusive(&m_srwLock); +} + +HRESULT +PROCESS_MANAGER::GetProcess( + _In_ IHttpContext *context, + _In_ ASPNETCORE_CONFIG *pConfig, + _Out_ SERVER_PROCESS **ppServerProcess +) +{ + HRESULT hr = S_OK; + BOOL fSharedLock = FALSE; + BOOL fExclusiveLock = FALSE; + PCWSTR apsz[1]; + STACK_STRU( strEventMsg, 256 ); + DWORD dwProcessIndex = 0; + SERVER_PROCESS **ppSelectedServerProcess = NULL; + + if (!m_fServerProcessListReady) + { + AcquireSRWLockExclusive( &m_srwLock ); + fExclusiveLock = TRUE; + + if (!m_fServerProcessListReady) + { + m_dwProcessesPerApplication = pConfig->QueryProcessesPerApplication(); + m_ppServerProcessList = new SERVER_PROCESS*[m_dwProcessesPerApplication]; + if(m_ppServerProcessList == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + for(DWORD i=0;iIsReady() ) + { + m_ppServerProcessList[dwProcessIndex]->ReferenceServerProcess(); + *ppServerProcess = m_ppServerProcessList[dwProcessIndex]; + goto Finished; + } + + ReleaseSRWLockShared( &m_srwLock ); + fSharedLock = FALSE; + // should make the lock per process so that we can start processes simultaneously ? + + if(m_ppServerProcessList[dwProcessIndex] == NULL || !m_ppServerProcessList[dwProcessIndex]->IsReady()) + { + AcquireSRWLockExclusive( &m_srwLock ); + fExclusiveLock = TRUE; + + if( m_ppServerProcessList[dwProcessIndex] != NULL ) + { + if( !m_ppServerProcessList[dwProcessIndex]->IsReady() ) + { + // + // terminate existing process that is not ready + // before creating new one. + // + + ShutdownProcessNoLock( m_ppServerProcessList[dwProcessIndex] ); + } + else + { + // server is already up and ready to serve requests. + m_ppServerProcessList[dwProcessIndex]->ReferenceServerProcess(); + *ppServerProcess = m_ppServerProcessList[dwProcessIndex]; + goto Finished; + } + } + + if( RapidFailsPerMinuteExceeded(pConfig->QueryRapidFailsPerMinute()) ) + { + // + // rapid fails per minute exceeded, do not create new process. + // + + if( SUCCEEDED( strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED_MSG, + pConfig->QueryRapidFailsPerMinute() ) ) ) + { + apsz[0] = strEventMsg.QueryStr(); + + // + // not checking return code because if ReportEvent + // fails, we cannot do anything. + // + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_INFORMATION_TYPE, + 0, + ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED, + NULL, + 1, + 0, + apsz, + NULL); + } + } + + hr = HRESULT_FROM_WIN32(ERROR_SERVER_DISABLED); + goto Finished; + } + + if( m_ppServerProcessList[dwProcessIndex] == NULL ) + { + m_ppServerProcessList[dwProcessIndex] = new SERVER_PROCESS(); + if( m_ppServerProcessList[dwProcessIndex] == NULL ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = m_ppServerProcessList[dwProcessIndex]->Initialize( + this, + pConfig->QueryProcessPath(), + pConfig->QueryArguments(), + pConfig->QueryStartupTimeLimitInMS(), + pConfig->QueryShutdownTimeLimitInMS(), + pConfig->QueryWindowsAuthEnabled(), + pConfig->QueryBasicAuthEnabled(), + pConfig->QueryAnonymousAuthEnabled(), + pConfig->QueryEnvironmentVariables(), + pConfig->QueryStdoutLogEnabled(), + pConfig->QueryStdoutLogFile() + ); + if( FAILED( hr ) ) + { + goto Finished; + } + + hr = m_ppServerProcessList[dwProcessIndex]->StartProcess(context); + if( FAILED( hr ) ) + { + goto Finished; + } + } + + if( !m_ppServerProcessList[dwProcessIndex]->IsReady() ) + { + hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); + goto Finished; + } + + m_ppServerProcessList[dwProcessIndex]->ReferenceServerProcess(); + *ppServerProcess = m_ppServerProcessList[dwProcessIndex]; + } + +Finished: + + if( FAILED(hr) ) + { + if(m_ppServerProcessList[dwProcessIndex] != NULL ) + { + m_ppServerProcessList[dwProcessIndex]->DereferenceServerProcess(); + m_ppServerProcessList[dwProcessIndex] = NULL; + } + } + + if( fSharedLock ) + { + ReleaseSRWLockShared( &m_srwLock ); + fSharedLock = FALSE; + } + + if( fExclusiveLock ) + { + ReleaseSRWLockExclusive( &m_srwLock ); + fExclusiveLock = FALSE; + } + + return hr; +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/protocolconfig.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/protocolconfig.cxx new file mode 100644 index 0000000000..83e2648925 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/protocolconfig.cxx @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +HRESULT +PROTOCOL_CONFIG::Initialize() +{ + HRESULT hr; + STRU strTemp; + + m_fKeepAlive = TRUE; + m_msTimeout = 120000; + m_fPreserveHostHeader = TRUE; + m_fReverseRewriteHeaders = FALSE; + + if (FAILED(hr = m_strXForwardedForName.CopyW(L"X-Forwarded-For"))) + { + goto Finished; + } + + if (FAILED(hr = m_strSslHeaderName.CopyW(L"X-Forwarded-Proto"))) + { + goto Finished; + } + + if (FAILED(hr = m_strClientCertName.CopyW(L"MS-ASPNETCORE-CLIENTCERT"))) + { + goto Finished; + } + + m_fIncludePortInXForwardedFor = TRUE; + m_dwMinResponseBuffer = 0; // no response buffering + m_dwResponseBufferLimit = 4096*1024; + m_dwMaxResponseHeaderSize = 65536; + +Finished: + + return hr; +} + +VOID +PROTOCOL_CONFIG::OverrideConfig( + ASPNETCORE_CONFIG *pAspNetCoreConfig +) +{ + m_msTimeout = pAspNetCoreConfig->QueryRequestTimeoutInMS(); +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/proxymodule.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/proxymodule.cxx new file mode 100644 index 0000000000..d4add071be --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/proxymodule.cxx @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + + +__override +HRESULT +CProxyModuleFactory::GetHttpModule( + CHttpModule ** ppModule, + IModuleAllocator * pAllocator +) +{ + CProxyModule *pModule = new (pAllocator) CProxyModule(); + if (pModule == NULL) + { + return E_OUTOFMEMORY; + } + + *ppModule = pModule; + return S_OK; +} + +__override +VOID +CProxyModuleFactory::Terminate( + VOID +) +/*++ + +Routine description: + + Function called by IIS for global (non-request-specific) notifications + +Arguments: + + None. + +Return value: + + None + +--*/ +{ + FORWARDING_HANDLER::StaticTerminate(); + + WEBSOCKET_HANDLER::StaticTerminate(); + + if (g_pResponseHeaderHash != NULL) + { + g_pResponseHeaderHash->Clear(); + delete g_pResponseHeaderHash; + g_pResponseHeaderHash = NULL; + } + + ALLOC_CACHE_HANDLER::StaticTerminate(); + + delete this; +} + +CProxyModule::CProxyModule( +) : m_pHandler(NULL) +{ +} + +CProxyModule::~CProxyModule() +{ + if (m_pHandler != NULL) + { + // + // This will be called when the main notification is cleaned up + // i.e., the request is done with IIS pipeline + // + m_pHandler->DereferenceForwardingHandler(); + m_pHandler = NULL; + } +} + +__override +REQUEST_NOTIFICATION_STATUS +CProxyModule::OnExecuteRequestHandler( + IHttpContext * pHttpContext, + IHttpEventProvider * +) +{ + m_pHandler = new FORWARDING_HANDLER(pHttpContext); + if (m_pHandler == NULL) + { + pHttpContext->GetResponse()->SetStatus(500, "Internal Server Error", 0, E_OUTOFMEMORY); + return RQ_NOTIFICATION_FINISH_REQUEST; + } + + return m_pHandler->OnExecuteRequestHandler(); +} + +__override +REQUEST_NOTIFICATION_STATUS +CProxyModule::OnAsyncCompletion( + IHttpContext *, + DWORD dwNotification, + BOOL fPostNotification, + IHttpEventProvider *, + IHttpCompletionInfo * pCompletionInfo +) +{ + UNREFERENCED_PARAMETER(dwNotification); + UNREFERENCED_PARAMETER(fPostNotification); + DBG_ASSERT(dwNotification == RQ_EXECUTE_REQUEST_HANDLER); + DBG_ASSERT(fPostNotification == FALSE); + + return m_pHandler->OnAsyncCompletion( + pCompletionInfo->GetCompletionBytes(), + pCompletionInfo->GetCompletionStatus()); +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/responseheaderhash.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/responseheaderhash.cxx new file mode 100644 index 0000000000..161095042c --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/responseheaderhash.cxx @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +RESPONSE_HEADER_HASH * g_pResponseHeaderHash = NULL; + +HEADER_RECORD RESPONSE_HEADER_HASH::sm_rgHeaders[] = +{ + { "Cache-Control", HttpHeaderCacheControl }, + { "Connection", HttpHeaderConnection }, + { "Date", HttpHeaderDate }, + { "Keep-Alive", HttpHeaderKeepAlive }, + { "Pragma", HttpHeaderPragma }, + { "Trailer", HttpHeaderTrailer }, + { "Transfer-Encoding", HttpHeaderTransferEncoding }, + { "Upgrade", HttpHeaderUpgrade }, + { "Via", HttpHeaderVia }, + { "Warning", HttpHeaderWarning }, + { "Allow", HttpHeaderAllow }, + { "Content-Length", HttpHeaderContentLength }, + { "Content-Type", HttpHeaderContentType }, + { "Content-Encoding", HttpHeaderContentEncoding }, + { "Content-Language", HttpHeaderContentLanguage }, + { "Content-Location", HttpHeaderContentLocation }, + { "Content-MD5", HttpHeaderContentMd5 }, + { "Content-Range", HttpHeaderContentRange }, + { "Expires", HttpHeaderExpires }, + { "Last-Modified", HttpHeaderLastModified }, + { "Accept-Ranges", HttpHeaderAcceptRanges }, + { "Age", HttpHeaderAge }, + { "ETag", HttpHeaderEtag }, + { "Location", HttpHeaderLocation }, + { "Proxy-Authenticate", HttpHeaderProxyAuthenticate }, + { "Retry-After", HttpHeaderRetryAfter }, + { "Server", HttpHeaderServer }, + // Set it to something which cannot be a header name, in effect + // making Server an unknown header. w:w is used to avoid collision with Keep-Alive. + { "w:w\r\n", HttpHeaderServer }, + // Set it to something which cannot be a header name, in effect + // making Set-Cookie an unknown header + { "y:y\r\n", HttpHeaderSetCookie }, + { "Vary", HttpHeaderVary }, + // Set it to something which cannot be a header name, in effect + // making WWW-Authenticate an unknown header + { "z:z\r\n", HttpHeaderWwwAuthenticate } + +}; + +HRESULT +RESPONSE_HEADER_HASH::Initialize( + VOID +) +/*++ + +Routine Description: + + Initialize global header hash table + +Arguments: + + None + +Return Value: + + HRESULT + +--*/ +{ + HRESULT hr; + + // + // 31 response headers. + // Make sure to update the number of buckets it new headers + // are added. Test it to avoid collisions. + // + C_ASSERT(_countof(sm_rgHeaders) == 31); + + // + // 79 buckets will have less collisions for the 31 response headers. + // Known collisions are "Age" colliding with "Expire" and "Location" + // colliding with both "Expire" and "Age". + // + hr = HASH_TABLE::Initialize(79); + if (FAILED(hr)) + { + return hr; + } + + for ( DWORD Index = 0; Index < _countof(sm_rgHeaders); ++Index ) + { + if (FAILED(hr = InsertRecord(&sm_rgHeaders[Index]))) + { + return hr; + } + } + + return S_OK; +} + diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx new file mode 100644 index 0000000000..bc724aa639 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/serverprocess.cxx @@ -0,0 +1,2350 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" +#include +#include + +extern BOOL g_fNsiApiNotSupported; + +#define STARTUP_TIME_LIMIT_INCREMENT_IN_MILLISECONDS 5000 + +HRESULT +SERVER_PROCESS::Initialize( + PROCESS_MANAGER *pProcessManager, + STRU *pszProcessExePath, + STRU *pszArguments, + DWORD dwStartupTimeLimitInMS, + DWORD dwShtudownTimeLimitInMS, + BOOL fWindowsAuthEnabled, + BOOL fBasicAuthEnabled, + BOOL fAnonymousAuthEnabled, + ENVIRONMENT_VAR_HASH *pEnvironmentVariables, + BOOL fStdoutLogEnabled, + STRU *pstruStdoutLogFile +) +{ + HRESULT hr = S_OK; + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = { 0 }; + + m_pProcessManager = pProcessManager; + m_dwStartupTimeLimitInMS = dwStartupTimeLimitInMS; + m_dwShutdownTimeLimitInMS = dwShtudownTimeLimitInMS; + m_fStdoutLogEnabled = fStdoutLogEnabled; + m_fWindowsAuthEnabled = fWindowsAuthEnabled; + m_fBasicAuthEnabled = fBasicAuthEnabled; + m_fAnonymousAuthEnabled = fAnonymousAuthEnabled; + m_pProcessManager->ReferenceProcessManager(); + + if (FAILED (hr = m_ProcessPath.Copy(*pszProcessExePath)) || + FAILED (hr = m_struLogFile.Copy(*pstruStdoutLogFile))|| + FAILED (hr = m_Arguments.Copy(*pszArguments))) + { + goto Finished; + } + + if (m_hJobObject == NULL) + { + m_hJobObject = CreateJobObject(NULL, // LPSECURITY_ATTRIBUTES + NULL); // LPCTSTR lpName +#pragma warning( disable : 4312) + // 0xdeadbeef is used by Antares + if (m_hJobObject == NULL || m_hJobObject == (HANDLE)0xdeadbeef) + { + m_hJobObject = NULL; + // ignore job object creation error. + } +#pragma warning( error : 4312) + if (m_hJobObject != NULL) + { + jobInfo.BasicLimitInformation.LimitFlags = + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + + if (!SetInformationJobObject(m_hJobObject, + JobObjectExtendedLimitInformation, + &jobInfo, + sizeof jobInfo)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + } + + m_pEnvironmentVarTable = pEnvironmentVariables; + } + +Finished: + return hr; +} + +HRESULT +SERVER_PROCESS::GetRandomPort +( + DWORD* pdwPickedPort, + DWORD dwExcludedPort = 0 +) +{ + HRESULT hr = S_OK; + BOOL fPortInUse = FALSE; + DWORD dwActualProcessId = 0; + + if (g_fNsiApiNotSupported) + { + // + // the default value for optional parameter dwExcludedPort is 0 which is reserved + // a random number between MIN_PORT and MAX_PORT + // + while ((*pdwPickedPort = (rand() % (MAX_PORT - MIN_PORT)) + MIN_PORT + 1) == dwExcludedPort); + } + else + { + DWORD cRetry = 0; + do + { + // + // ignore dwActualProcessId because here we are + // determing whether the randomly generated port is + // in use by any other process. + // + while ((*pdwPickedPort = (rand() % (MAX_PORT - MIN_PORT)) + MIN_PORT + 1) == dwExcludedPort); + hr = CheckIfServerIsUp(*pdwPickedPort, &dwActualProcessId, &fPortInUse); + } while (fPortInUse && ++cRetry < MAX_RETRY); + + if (cRetry >= MAX_RETRY) + { + hr = HRESULT_FROM_WIN32(ERROR_PORT_NOT_SET); + } + } + + return hr; +} + +HRESULT +SERVER_PROCESS::SetupListenPort( + ENVIRONMENT_VAR_HASH *pEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + ENVIRONMENT_VAR_ENTRY *pEntry = NULL; + pEnvironmentVarTable->FindKey(ASPNETCORE_PORT_ENV_STR, &pEntry); + if (pEntry != NULL) + { + pEntry->Dereference(); + if (pEntry->QueryValue() != NULL || pEntry->QueryValue()[0] != L'\0') + { + m_dwPort = (DWORD)_wtoi(pEntry->QueryValue()); + if(m_dwPort >MAX_PORT || m_dwPort < MIN_PORT) + { + hr = E_INVALIDARG; + goto Finished; + // need add log for this one + } + hr = m_struPort.Copy(pEntry->QueryValue()); + goto Finished; + } + else + { + // + // user set the env variable but did not give value, let's set it up + // + pEnvironmentVarTable->DeleteKey(ASPNETCORE_PORT_ENV_STR); + } + } + + WCHAR buffer[15]; + if (FAILED(hr = GetRandomPort(&m_dwPort))) + { + goto Finished; + } + + if (swprintf_s(buffer, 15, L"%d", m_dwPort) <= 0) + { + hr = E_INVALIDARG; + goto Finished; + } + + pEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (FAILED(hr = pEntry->Initialize(ASPNETCORE_PORT_ENV_STR, buffer)) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry)) || + FAILED(hr = m_struPort.Copy(buffer))) + { + goto Finished; + } + +Finished: + if (pEntry != NULL) + { + pEntry->Dereference(); + pEntry = NULL; + } + return hr; +} + +HRESULT +SERVER_PROCESS::SetupAppPath( + IHttpContext* pContext, + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + DWORD dwCounter = 0; + DWORD dwPosition = 0; + WCHAR* pszPath = NULL; + ENVIRONMENT_VAR_ENTRY* pEntry = NULL; + + pEnvironmentVarTable->FindKey(ASPNETCORE_APP_PATH_ENV_STR, &pEntry); + if (pEntry != NULL) + { + // user should not set this environment variable in configuration + pEnvironmentVarTable->DeleteKey(ASPNETCORE_APP_PATH_ENV_STR); + pEntry->Dereference(); + pEntry = NULL; + } + + if (m_struAppPath.IsEmpty()) + { + if (FAILED(hr = m_pszRootApplicationPath.Copy(pContext->GetApplication()->GetApplicationPhysicalPath())) || + FAILED(hr = m_struAppFullPath.Copy(pContext->GetApplication()->GetAppConfigPath()))) + { + goto Finished; + } + } + + // let's find the app path. IIS does not support nested sites + // we can seek for the fourth '/' if it exits + // MACHINE/WEBROOT/APPHOST//. + pszPath = m_struAppFullPath.QueryStr(); + while (pszPath[dwPosition] != NULL) + { + if (pszPath[dwPosition] == '/') + { + dwCounter++; + if (dwCounter == 4) + break; + } + dwPosition++; + } + + if (dwCounter == 4) + { + hr = m_struAppPath.Copy(pszPath + dwPosition); + } + else + { + hr = m_struAppPath.Copy(L"/"); + } + + if (FAILED(hr)) + { + goto Finished; + } + + pEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + if (FAILED (hr = pEntry->Initialize(ASPNETCORE_APP_PATH_ENV_STR, m_struAppPath.QueryStr())) || + FAILED (hr = pEnvironmentVarTable->InsertRecord(pEntry))) + { + goto Finished; + } + +Finished: + if (pEntry!= NULL) + { + pEntry->Dereference(); + pEntry = NULL; + } + return hr; +} + +HRESULT +SERVER_PROCESS::SetupAppToken( + ENVIRONMENT_VAR_HASH *pEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + UUID logUuid; + PSTR pszLogUuid = NULL; + BOOL fRpcStringAllocd = FALSE; + RPC_STATUS rpcStatus; + STRU strAppToken; + ENVIRONMENT_VAR_ENTRY* pEntry = NULL; + + pEnvironmentVarTable->FindKey(ASPNETCORE_APP_TOKEN_ENV_STR, &pEntry); + if (pEntry != NULL) + { + // user sets the environment variable + m_straGuid.Reset(); + hr = m_straGuid.CopyW(pEntry->QueryValue()); + pEntry->Dereference(); + pEntry = NULL; + goto Finished; + } + else + { + if (m_straGuid.IsEmpty()) + { + // the GUID has not been set yet + rpcStatus = UuidCreate(&logUuid); + if (rpcStatus != RPC_S_OK) + { + hr = rpcStatus; + goto Finished; + } + + rpcStatus = UuidToStringA(&logUuid, (BYTE **)&pszLogUuid); + if (rpcStatus != RPC_S_OK) + { + hr = rpcStatus; + goto Finished; + } + + fRpcStringAllocd = TRUE; + + if (FAILED (hr = m_straGuid.Copy(pszLogUuid))) + { + goto Finished; + } + } + + pEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (FAILED(strAppToken.CopyA(m_straGuid.QueryStr())) || + FAILED(hr = pEntry->Initialize(ASPNETCORE_APP_TOKEN_ENV_STR, strAppToken.QueryStr())) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pEntry))) + { + goto Finished; + } + } + +Finished: + + if (fRpcStringAllocd) + { + RpcStringFreeA((BYTE **)&pszLogUuid); + pszLogUuid = NULL; + } + if (pEntry != NULL) + { + pEntry->Dereference(); + pEntry = NULL; + } + return hr; +} + + +HRESULT +SERVER_PROCESS::InitEnvironmentVariablesTable( + ENVIRONMENT_VAR_HASH** ppEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + BOOL fFound = FALSE; + DWORD dwResult, dwError; + STRU strIisAuthEnvValue; + STACK_STRU(strStartupAssemblyEnv, 1024); + ENVIRONMENT_VAR_ENTRY* pHostingEntry = NULL; + ENVIRONMENT_VAR_ENTRY* pIISAuthEntry = NULL; + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable = NULL; + + pEnvironmentVarTable = new ENVIRONMENT_VAR_HASH(); + if (pEnvironmentVarTable == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + // + // few environment variables expected, small bucket size for hash table + // + if (FAILED(hr = pEnvironmentVarTable->Initialize(37 /*prime*/))) + { + goto Finished; + } + + // copy the envirable hash table (from configuration) to a temp one as we may need to remove elements + m_pEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HASH::CopyToTable, pEnvironmentVarTable); + if (pEnvironmentVarTable->Count() != m_pEnvironmentVarTable->Count()) + { + // hash table copy failed + hr = E_UNEXPECTED; + goto Finished; + } + + pEnvironmentVarTable->FindKey(ASPNETCORE_IIS_AUTH_ENV_STR, &pIISAuthEntry); + if (pIISAuthEntry != NULL) + { + // user defined ASPNETCORE_IIS_HTTPAUTH in configuration, wipe it off + pIISAuthEntry->Dereference(); + pEnvironmentVarTable->DeleteKey(ASPNETCORE_IIS_AUTH_ENV_STR); + } + + if (m_fWindowsAuthEnabled) + { + strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_WINDOWS); + } + + if (m_fBasicAuthEnabled) + { + strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_BASIC); + } + + if (m_fAnonymousAuthEnabled) + { + strIisAuthEnvValue.Append(ASPNETCORE_IIS_AUTH_ANONYMOUS); + } + + if (strIisAuthEnvValue.IsEmpty()) + { + strIisAuthEnvValue.Copy(ASPNETCORE_IIS_AUTH_NONE); + } + + pIISAuthEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pIISAuthEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + if (FAILED(hr = pIISAuthEntry->Initialize(ASPNETCORE_IIS_AUTH_ENV_STR, strIisAuthEnvValue.QueryStr())) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pIISAuthEntry))) + { + goto Finished; + } + + + pEnvironmentVarTable->FindKey(HOSTING_STARTUP_ASSEMBLIES_NAME, &pHostingEntry); + if (pHostingEntry !=NULL ) + { + // user defined ASPNETCORE_HOSTINGSTARTUPASSEMBLIES in configuration + // the value will be used in OutputEnvironmentVariables. Do nothing here + pHostingEntry->Dereference(); + pHostingEntry = NULL; + goto Skipped; + } + + //check whether ASPNETCORE_HOSTINGSTARTUPASSEMBLIES is defined in system + dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, + strStartupAssemblyEnv.QueryStr(), + strStartupAssemblyEnv.QuerySizeCCH()); + if (dwResult == 0) + { + dwError = GetLastError(); + + // Windows API (e.g., CreateProcess) allows variable with empty string value + // in such case dwResult will be 0 and dwError will also be 0 + // As UI and CMD does not allow empty value, ignore this environment var + if (dwError != ERROR_ENVVAR_NOT_FOUND && dwError != ERROR_SUCCESS) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + } + else if (dwResult > strStartupAssemblyEnv.QuerySizeCCH()) + { + // have to increase the buffer and try get environment var again + strStartupAssemblyEnv.Reset(); + strStartupAssemblyEnv.Resize(dwResult + (DWORD)wcslen(HOSTING_STARTUP_ASSEMBLIES_VALUE) +1); + dwResult = GetEnvironmentVariable(HOSTING_STARTUP_ASSEMBLIES_ENV_STR, + strStartupAssemblyEnv.QueryStr(), + strStartupAssemblyEnv.QuerySizeCCH()); + if (strStartupAssemblyEnv.IsEmpty()) + { + hr = E_UNEXPECTED; + goto Finished; + } + fFound = TRUE; + } + else + { + fFound = TRUE; + } + + strStartupAssemblyEnv.SyncWithBuffer(); + if (fFound) + { + strStartupAssemblyEnv.Append(L";"); + } + strStartupAssemblyEnv.Append(HOSTING_STARTUP_ASSEMBLIES_VALUE); + + // the environment variable was not defined, create it and add to hashtable + pHostingEntry = new ENVIRONMENT_VAR_ENTRY(); + if (pHostingEntry == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + if (FAILED(hr = pHostingEntry->Initialize(HOSTING_STARTUP_ASSEMBLIES_NAME, strStartupAssemblyEnv.QueryStr())) || + FAILED(hr = pEnvironmentVarTable->InsertRecord(pHostingEntry))) + { + goto Finished; + } + +Skipped: + *ppEnvironmentVarTable = pEnvironmentVarTable; + pEnvironmentVarTable = NULL; + +Finished: + if (pHostingEntry != NULL) + { + pHostingEntry->Dereference(); + pHostingEntry = NULL; + } + + if (pIISAuthEntry != NULL) + { + pIISAuthEntry->Dereference(); + pIISAuthEntry = NULL; + } + + if (pEnvironmentVarTable != NULL) + { + pEnvironmentVarTable->Clear(); + delete pEnvironmentVarTable; + pEnvironmentVarTable = NULL; + } + return hr; +} + +HRESULT +SERVER_PROCESS::OutputEnvironmentVariables +( + MULTISZ* pmszOutput, + ENVIRONMENT_VAR_HASH* pEnvironmentVarTable +) +{ + HRESULT hr = S_OK; + LPWSTR pszEnvironmentVariables = NULL; + LPWSTR pszCurrentVariable = NULL; + LPWSTR pszNextVariable = NULL; + LPWSTR pszEqualChar = NULL; + STRU strEnvVar; + ENVIRONMENT_VAR_ENTRY* pEntry = NULL; + + DBG_ASSERT(pmszOutput); + DBG_ASSERT(pEnvironmentVarTable); // We added some startup variables + DBG_ASSERT(pEnvironmentVarTable->Count() >0); + + pszEnvironmentVariables = GetEnvironmentStringsW(); + if (pszEnvironmentVariables == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_ENVIRONMENT); + goto Finished; + } + pszCurrentVariable = pszEnvironmentVariables; + while (*pszCurrentVariable != L'\0') + { + pszNextVariable = pszCurrentVariable + wcslen(pszCurrentVariable) + 1; + pszEqualChar = wcschr(pszCurrentVariable, L'='); + if (pszEqualChar != NULL) + { + if (FAILED(hr = strEnvVar.Copy(pszCurrentVariable, (DWORD)(pszEqualChar - pszCurrentVariable) + 1))) + { + goto Finished; + } + pEnvironmentVarTable->FindKey(strEnvVar.QueryStr(), &pEntry); + if (pEntry != NULL) + { + // same env variable is defined in configuration, use it + if (FAILED(hr = strEnvVar.Append(pEntry->QueryValue()))) + { + goto Finished; + } + pmszOutput->Append(strEnvVar); //should we check the returned bool + // remove the record from hash table as we already output it + pEntry->Dereference(); + pEnvironmentVarTable->DeleteKey(pEntry->QueryName()); + strEnvVar.Reset(); + pEntry = NULL; + } + else + { + pmszOutput->Append(pszCurrentVariable); + } + } + else + { + // env varaible is not well formated + hr = HRESULT_FROM_WIN32(ERROR_INVALID_ENVIRONMENT); + goto Finished; + } + // move to next env variable + pszCurrentVariable = pszNextVariable; + } + // append the remaining env variable in hash table + pEnvironmentVarTable->Apply(ENVIRONMENT_VAR_HASH::CopyToMultiSz, pmszOutput); + +Finished: + if (pszEnvironmentVariables != NULL) + { + FreeEnvironmentStringsW(pszEnvironmentVariables); + pszEnvironmentVariables = NULL; + } + return hr; +} + +HRESULT +SERVER_PROCESS::SetupCommandLine( + STRU* pstrCommandLine +) +{ + HRESULT hr = S_OK; + LPWSTR pszPath = NULL; + LPWSTR pszFullPath = NULL; + STRU strRelativePath; + DWORD dwBufferSize = 0; + FILE *file = NULL; + + DBG_ASSERT(pstrCommandLine); + + pszPath = m_ProcessPath.QueryStr(); + + if ((wcsstr(pszPath, L":") == NULL) && (wcsstr(pszPath, L"%") == NULL)) + { + // let's check whether it is a relative path + if (FAILED(hr = strRelativePath.Copy(m_pszRootApplicationPath.QueryStr())) || + FAILED(hr = strRelativePath.Append(L"\\")) || + FAILED(hr = strRelativePath.Append(pszPath))) + { + goto Finished; + } + + dwBufferSize = strRelativePath.QueryCCH() + 1; + pszFullPath = new WCHAR[dwBufferSize]; + if (pszFullPath == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + if (_wfullpath(pszFullPath, + strRelativePath.QueryStr(), + dwBufferSize) == NULL) + { + hr = HRESULT_FROM_WIN32(ERROR_INVALID_PARAMETER); + goto Finished; + } + + if ((file = _wfsopen(pszFullPath, L"r", _SH_DENYNO)) != NULL) + { + fclose(file); + pszPath = pszFullPath; + } + } + if (FAILED(hr = pstrCommandLine->Copy(pszPath)) || + FAILED(hr = pstrCommandLine->Append(L" ")) || + FAILED(hr = pstrCommandLine->Append(m_Arguments.QueryStr()))) + { + goto Finished; + } + +Finished: + if (pszFullPath != NULL) + { + delete pszFullPath; + } + return hr; +} + + +HRESULT +SERVER_PROCESS::PostStartCheck( + const STRU* const pStruCommandline, + STRU* pStruErrorMessage) +{ + HRESULT hr = S_OK; + + BOOL fReady = FALSE; + BOOL fProcessMatch = FALSE; + BOOL fDebuggerAttached = FALSE; + DWORD dwTickCount = 0; + DWORD dwTimeDifference = 0; + DWORD dwActualProcessId = 0; + INT iChildProcessIndex = -1; + + if (CheckRemoteDebuggerPresent(m_hProcessHandle, &fDebuggerAttached) == 0) + { + // some error occurred - assume debugger is not attached; + fDebuggerAttached = FALSE; + } + + dwTickCount = GetTickCount(); + do + { + DWORD processStatus; + if (GetExitCodeProcess(m_hProcessHandle, &processStatus)) + { + // make sure the process is still running + if (processStatus != STILL_ACTIVE) + { + hr = E_FAIL; + pStruErrorMessage->SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + pStruCommandline->QueryStr(), + hr, + processStatus); + goto Finished; + } + } + // + // dwActualProcessId will be set only when NsiAPI(GetExtendedTcpTable) is supported + // + hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); + fDebuggerAttached = IsDebuggerIsAttached(); + + if (!fReady) + { + Sleep(250); + } + + dwTimeDifference = (GetTickCount() - dwTickCount); + } while (fReady == FALSE && + ((dwTimeDifference < m_dwStartupTimeLimitInMS) || fDebuggerAttached)); + + // register call back with the created process + if (FAILED(hr = RegisterProcessWait(&m_hProcessWaitHandle, m_hProcessHandle))) + { + goto Finished; + } + + // + // check if debugger is attached after startupTimeout. + // + if (!fDebuggerAttached && + CheckRemoteDebuggerPresent(m_hProcessHandle, &fDebuggerAttached) == 0) + { + // some error occurred - assume debugger is not attached; + fDebuggerAttached = FALSE; + } + if (!g_fNsiApiNotSupported) + { + // + // NsiAPI(GetExtendedTcpTable) is supported. we should check whether processIds matche + // + if (dwActualProcessId == m_dwProcessId) + { + m_dwListeningProcessId = m_dwProcessId; + fProcessMatch = TRUE; + } + + if (!fProcessMatch) + { + // could be the scenario that backend creates child process + if (FAILED(hr = GetChildProcessHandles())) + { + goto Finished; + } + + for (DWORD i = 0; i < m_cChildProcess; ++i) + { + // a child process listen on the assigned port + if (dwActualProcessId == m_dwChildProcessIds[i]) + { + m_dwListeningProcessId = m_dwChildProcessIds[i]; + fProcessMatch = TRUE; + + if (m_hChildProcessHandles[i] != NULL) + { + if (fDebuggerAttached == FALSE && + CheckRemoteDebuggerPresent(m_hChildProcessHandles[i], &fDebuggerAttached) == 0) + { + // some error occurred - assume debugger is not attached; + fDebuggerAttached = FALSE; + } + + if (FAILED(hr = RegisterProcessWait(&m_hChildProcessWaitHandles[i], + m_hChildProcessHandles[i]))) + { + goto Finished; + } + iChildProcessIndex = i; + } + break; + } + } + } + + if(!fProcessMatch) + { + // + // process that we created is not listening + // on the port we specified. + // + fReady = FALSE; + pStruErrorMessage->SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_WRONGPORT_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + pStruCommandline->QueryStr(), + m_dwPort, + hr); + hr = HRESULT_FROM_WIN32(ERROR_CREATE_FAILED); + goto Finished; + } + } + + if (!fReady) + { + // + // hr is already set by CheckIfServerIsUp + // + if (dwTimeDifference >= m_dwStartupTimeLimitInMS) + { + hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); + pStruErrorMessage->SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + pStruCommandline->QueryStr(), + m_dwPort, + hr); + } + goto Finished; + } + + if (iChildProcessIndex >= 0) + { + // + // final check to make sure child process listening on HTTP is still UP + // This is needed because, the child process might have crashed/exited between + // the previous call to checkIfServerIsUp and RegisterProcessWait + // and we would not know about it. + // + + hr = CheckIfServerIsUp(m_dwPort, &dwActualProcessId, &fReady); + + if ((FAILED(hr) || fReady == FALSE)) + { + pStruErrorMessage->SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_NOTREADY_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath, + pStruCommandline->QueryStr(), + m_dwPort, + hr); + goto Finished; + } + } + + // + // ready to mark the server process ready but before this, + // create and initialize the FORWARDER_CONNECTION + // + if (m_pForwarderConnection != NULL) + { + m_pForwarderConnection->DereferenceForwarderConnection(); + m_pForwarderConnection = NULL; + } + + if (m_pForwarderConnection == NULL) + { + m_pForwarderConnection = new FORWARDER_CONNECTION(); + if (m_pForwarderConnection == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + hr = m_pForwarderConnection->Initialize(m_dwPort); + if (FAILED(hr)) + { + goto Finished; + } + } + + if (!g_fNsiApiNotSupported) + { + m_hListeningProcessHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + m_dwListeningProcessId); + } + + // + // mark server process as Ready + // + m_fReady = TRUE; + +Finished: + return hr; +} + +HRESULT +SERVER_PROCESS::StartProcess( + IHttpContext *context +) +{ + HRESULT hr = S_OK; + PROCESS_INFORMATION processInformation = {0}; + STARTUPINFOW startupInfo = {0}; + BOOL fDonePrepareCommandLine = FALSE; + DWORD dwCreationFlags = 0; + + STACK_STRU( strEventMsg, 256); + STRU strFullProcessPath; + STRU struRelativePath; + STRU struApplicationId; + STRU struCommandLine; + + LPCWSTR apsz[1]; + + MULTISZ mszNewEnvironment; + ENVIRONMENT_VAR_HASH *pHashTable = NULL; + + GetStartupInfoW(&startupInfo); + + // + // setup stdout and stderr handles to our stdout handle only if + // the handle is valid. + // + SetupStdHandles(context, &startupInfo); + + if (FAILED(hr = InitEnvironmentVariablesTable(&pHashTable))) + { + goto Finished; + } + + // + // setup the the port that the backend process will listen on + // + if (FAILED (hr= SetupListenPort(pHashTable))) + { + goto Finished; + } + + // + // get app path + // + if (FAILED(hr = SetupAppPath(context, pHashTable))) + { + goto Finished; + } + + // + // generate new guid for each process + // + if (FAILED(hr = SetupAppToken(pHashTable))) + { + goto Finished; + } + + // + // setup environment variables for new process + // + if (FAILED(hr = OutputEnvironmentVariables(&mszNewEnvironment, pHashTable))) + { + goto Finished; + } + + // + // generate process command line. + // + if (FAILED(hr = SetupCommandLine(&struCommandLine))) + { + goto Finished; + } + + fDonePrepareCommandLine = TRUE; + + dwCreationFlags = CREATE_NO_WINDOW | + CREATE_UNICODE_ENVIRONMENT | + CREATE_SUSPENDED | + CREATE_NEW_PROCESS_GROUP; + + if (!CreateProcessW( + NULL, // applicationName + struCommandLine.QueryStr(), + NULL, // processAttr + NULL, // threadAttr + TRUE, // inheritHandles + dwCreationFlags, + mszNewEnvironment.QueryStr(), + m_pszRootApplicationPath.QueryStr(), // currentDir + &startupInfo, + &processInformation) ) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + // don't the check return code as we already in error report + strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath.QueryStr(), + struCommandLine.QueryStr(), + hr, + 0); + goto Finished; + } + + m_hProcessHandle = processInformation.hProcess; + m_dwProcessId = processInformation.dwProcessId; + + if (m_hJobObject != NULL) + { + if (!AssignProcessToJobObject(m_hJobObject, m_hProcessHandle)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + if (hr != HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)) + { + goto Finished; + } + } + } + + if (ResumeThread( processInformation.hThread ) == -1) + { + hr = HRESULT_FROM_WIN32( GetLastError() ); + goto Finished; + } + + // + // need to make sure the server is up and listening on the port specified. + // + if (FAILED(hr = PostStartCheck(&struCommandLine, &strEventMsg))) + { + goto Finished; + } + + + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG, + m_struAppFullPath.QueryStr(), + m_dwProcessId, + m_dwPort))) + { + apsz[0] = strEventMsg.QueryStr(); + + // + // not checking return code because if ReportEvent + // fails, we cannot do anything. + // + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_INFORMATION_TYPE, + 0, + ASPNETCORE_EVENT_PROCESS_START_SUCCESS, + NULL, + 1, + 0, + apsz, + NULL); + } + } + +Finished: + if (processInformation.hThread != NULL) + { + CloseHandle(processInformation.hThread); + processInformation.hThread = NULL; + } + + if (pHashTable != NULL) + { + pHashTable->Clear(); + delete pHashTable; + pHashTable = NULL; + } + + if ( FAILED(hr) ) + { + if (strEventMsg.IsEmpty()) + { + if (!fDonePrepareCommandLine) + strEventMsg.SafeSnwprintf( + m_struAppFullPath.QueryStr(), + ASPNETCORE_EVENT_PROCESS_START_INTERNAL_ERROR_MSG, + hr); + else + strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_PROCESS_START_POSTCREATE_ERROR_MSG, + m_struAppFullPath.QueryStr(), + m_pszRootApplicationPath, + struCommandLine.QueryStr(), + hr); + } + + apsz[0] = strEventMsg.QueryStr(); + + // not checking return code because if ReportEvent + // fails, we cannot do anything. + // + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_ERROR_TYPE, + 0, + ASPNETCORE_EVENT_PROCESS_START_ERROR, + NULL, + 1, + 0, + apsz, + NULL); + } + } + + if ( FAILED( hr ) || m_fReady == FALSE) + { + if (m_hStdoutHandle != NULL) + { + if ( m_hStdoutHandle != INVALID_HANDLE_VALUE ) + { + CloseHandle( m_hStdoutHandle ); + } + m_hStdoutHandle = NULL; + } + + if ( m_fStdoutLogEnabled ) + { + m_Timer.CancelTimer(); + } + + if (m_hListeningProcessHandle != NULL) + { + if( m_hListeningProcessHandle != INVALID_HANDLE_VALUE ) + { + CloseHandle( m_hListeningProcessHandle ); + } + m_hListeningProcessHandle = NULL; + } + + if ( m_hProcessWaitHandle != NULL ) + { + UnregisterWait( m_hProcessWaitHandle ); + m_hProcessWaitHandle = NULL; + } + + StopProcess(); + + StopAllProcessesInJobObject(); + } + return hr; +} + +HRESULT +SERVER_PROCESS::SetWindowsAuthToken( + HANDLE hToken, + LPHANDLE pTargetTokenHandle +) +{ + HRESULT hr = S_OK; + + if ( m_hListeningProcessHandle != NULL && m_hListeningProcessHandle != INVALID_HANDLE_VALUE ) + { + if (!DuplicateHandle( GetCurrentProcess(), + hToken, + m_hListeningProcessHandle, + pTargetTokenHandle, + 0, + FALSE, + DUPLICATE_SAME_ACCESS )) + { + hr = HRESULT_FROM_GETLASTERROR(); + goto Finished; + } + } + +Finished: + + return hr; +} + +HRESULT +SERVER_PROCESS::SetupStdHandles( + IHttpContext *context, + LPSTARTUPINFOW pStartupInfo +) +{ + SECURITY_ATTRIBUTES saAttr = {0}; + HRESULT hr = S_OK; + SYSTEMTIME systemTime; + STRU struLogFileName; + BOOL fStdoutLoggingFailed = FALSE; + STRU strEventMsg; + LPCWSTR apsz[1]; + STRU struAbsLogFilePath; + + DBG_ASSERT(pStartupInfo); + + if ( m_fStdoutLogEnabled ) + { + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + if (m_hStdoutHandle != NULL) + { + if(!CloseHandle( m_hStdoutHandle )) + { + hr = HRESULT_FROM_GETLASTERROR(); + goto Finished; + } + + m_hStdoutHandle = NULL; + } + + hr = PATH::ConvertPathToFullPath( m_struLogFile.QueryStr(), + context->GetApplication()->GetApplicationPhysicalPath(), + &struAbsLogFilePath ); + if (FAILED(hr)) + { + goto Finished; + } + + GetSystemTime(&systemTime); + hr = struLogFileName.SafeSnwprintf( L"%s_%d_%d%d%d%d%d%d.log", + struAbsLogFilePath.QueryStr(), + GetCurrentProcessId(), + systemTime.wYear, + systemTime.wMonth, + systemTime.wDay, + systemTime.wHour, + systemTime.wMinute, + systemTime.wSecond ); + if (FAILED(hr)) + { + goto Finished; + } + + m_hStdoutHandle = CreateFileW( struLogFileName.QueryStr(), + FILE_WRITE_DATA, + FILE_SHARE_READ, + &saAttr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL ); + if ( m_hStdoutHandle == INVALID_HANDLE_VALUE ) + { + fStdoutLoggingFailed = TRUE; + m_hStdoutHandle = NULL; + + if( SUCCEEDED( strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_INVALID_STDOUT_LOG_FILE_MSG, + struLogFileName.QueryStr(), + HRESULT_FROM_GETLASTERROR() ) ) ) + { + apsz[0] = strEventMsg.QueryStr(); + + // + // not checking return code because if ReportEvent + // fails, we cannot do anything. + // + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_WARNING_TYPE, + 0, + ASPNETCORE_EVENT_CONFIG_ERROR, + NULL, + 1, + 0, + apsz, + NULL); + } + } + } + + if( !fStdoutLoggingFailed ) + { + pStartupInfo->dwFlags = STARTF_USESTDHANDLES; + pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; + pStartupInfo->hStdError = m_hStdoutHandle; + pStartupInfo->hStdOutput = m_hStdoutHandle; + + m_struFullLogFile.Copy( struLogFileName ); + + // start timer to open and close handles regularly. + m_Timer.InitializeTimer(SERVER_PROCESS::TimerCallback, this, 3000, 3000); + } + } + + if( (!m_fStdoutLogEnabled || fStdoutLoggingFailed) && + m_pProcessManager->QueryNULHandle() != NULL && + m_pProcessManager->QueryNULHandle() != INVALID_HANDLE_VALUE ) + { + pStartupInfo->dwFlags = STARTF_USESTDHANDLES; + pStartupInfo->hStdInput = INVALID_HANDLE_VALUE; + pStartupInfo->hStdError = m_pProcessManager->QueryNULHandle(); + pStartupInfo->hStdOutput = m_pProcessManager->QueryNULHandle(); + } + +Finished: + + return hr; +} + +VOID +CALLBACK +SERVER_PROCESS::TimerCallback( + IN PTP_CALLBACK_INSTANCE Instance, + IN PVOID Context, + IN PTP_TIMER Timer +) +{ + Instance; + Timer; + SERVER_PROCESS* pServerProcess = (SERVER_PROCESS*) Context; + HANDLE hStdoutHandle = NULL; + SECURITY_ATTRIBUTES saAttr = {0}; + HRESULT hr = S_OK; + + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + hStdoutHandle = CreateFileW( pServerProcess->QueryFullLogPath(), + FILE_READ_DATA, + FILE_SHARE_WRITE, + &saAttr, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL ); + if( hStdoutHandle == INVALID_HANDLE_VALUE ) + { + hr = HRESULT_FROM_GETLASTERROR(); + } + + CloseHandle( hStdoutHandle ); +} + +HRESULT +SERVER_PROCESS::CheckIfServerIsUp( + _In_ DWORD dwPort, + _Out_ DWORD * pdwProcessId, + _Out_ BOOL * pfReady +) +{ + HRESULT hr = S_OK; + DWORD dwResult = 0; + MIB_TCPTABLE_OWNER_PID *pTCPInfo = NULL; + MIB_TCPROW_OWNER_PID *pOwner = NULL; + DWORD dwSize = 0; + int iResult = 0; + SOCKADDR_IN sockAddr; + SOCKET socketCheck = INVALID_SOCKET; + + DBG_ASSERT(pfReady); + DBG_ASSERT(pdwProcessId); + + *pfReady = FALSE; + // + // it's OK for us to return processID 0 in case we cannot detect the real one + // + *pdwProcessId = 0; + + if (!g_fNsiApiNotSupported) + { + dwResult = GetExtendedTcpTable(NULL, + &dwSize, + FALSE, + AF_INET, + TCP_TABLE_OWNER_PID_LISTENER, + 0); + + if (dwResult != NO_ERROR && dwResult != ERROR_INSUFFICIENT_BUFFER) + { + hr = HRESULT_FROM_WIN32(dwResult); + goto Finished; + } + + pTCPInfo = (MIB_TCPTABLE_OWNER_PID*)HeapAlloc(GetProcessHeap(), 0, dwSize); + if (pTCPInfo == NULL) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + dwResult = GetExtendedTcpTable(pTCPInfo, + &dwSize, + FALSE, + AF_INET, + TCP_TABLE_OWNER_PID_LISTENER, + 0); + if (dwResult != NO_ERROR) + { + hr = HRESULT_FROM_WIN32(dwResult); + goto Finished; + } + + // iterate pTcpInfo struct to find PID/PORT entry + for (DWORD dwLoop = 0; dwLoop < pTCPInfo->dwNumEntries; dwLoop++) + { + pOwner = &pTCPInfo->table[dwLoop]; + if (ntohs((USHORT)pOwner->dwLocalPort) == dwPort) + { + *pdwProcessId = pOwner->dwOwningPid; + *pfReady = TRUE; + break; + } + } + } + else + { + // + // We have to open socket to ping the service + // + socketCheck = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (socketCheck == INVALID_SOCKET) + { + hr = HRESULT_FROM_WIN32(WSAGetLastError()); + goto Finished; + } + + sockAddr.sin_family = AF_INET; + if (!inet_pton(AF_INET, LOCALHOST, &(sockAddr.sin_addr))) + { + hr = HRESULT_FROM_WIN32(WSAGetLastError()); + goto Finished; + } + + //sockAddr.sin_addr.s_addr = inet_addr( LOCALHOST ); + sockAddr.sin_port = htons((u_short)dwPort); + + // + // Connect to server. + // + iResult = connect(socketCheck, (SOCKADDR *)&sockAddr, sizeof(sockAddr)); + if (iResult == SOCKET_ERROR) + { + hr = HRESULT_FROM_WIN32(WSAGetLastError()); + goto Finished; + } + + *pfReady = TRUE; + } + +Finished: + + if (socketCheck != INVALID_SOCKET) + { + iResult = closesocket(socketCheck); + if (iResult == SOCKET_ERROR) + { + hr = HRESULT_FROM_WIN32(WSAGetLastError()); + } + socketCheck = INVALID_SOCKET; + } + + if( pTCPInfo != NULL ) + { + HeapFree( GetProcessHeap(), 0, pTCPInfo ); + pTCPInfo = NULL; + } + + return hr; +} + +// send signal to the process to let it gracefully shutdown +// if the process cannot shutdown within given time, terminate it +VOID +SERVER_PROCESS::SendSignal( + VOID +) +{ + HRESULT hr = S_OK; + HANDLE hThread = NULL; + + ReferenceServerProcess(); + + m_hShutdownHandle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, m_dwProcessId); + + if (m_hShutdownHandle == NULL) + { + // since we cannot open the process. let's terminate the process + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + hThread = CreateThread( + NULL, // default security attributes + 0, // default stack size + (LPTHREAD_START_ROUTINE)SendShutDownSignal, + this, // thread function arguments + 0, // default creation flags + NULL); // receive thread identifier + + if (hThread == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (WaitForSingleObject(m_hShutdownHandle, m_dwShutdownTimeLimitInMS) != WAIT_OBJECT_0) + { + hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); + goto Finished; + } + + +Finished: + if (hThread != NULL) + { + // if the send shutdown message thread is still running, terminate it + DWORD dwThreadStatus = 0; + if (GetExitCodeThread(hThread, &dwThreadStatus)!= 0 && dwThreadStatus == STILL_ACTIVE) + { + TerminateThread(hThread, STATUS_CONTROL_C_EXIT); + } + CloseHandle(hThread); + hThread = NULL; + } + + if (FAILED(hr)) + { + TerminateBackendProcess(); + } + + if (m_hShutdownHandle != NULL && m_hShutdownHandle != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hShutdownHandle); + m_hShutdownHandle = NULL; + } + + DereferenceServerProcess(); +} + +// +// StopProcess is only called if process crashes OR if the process +// creation failed and calling this counts towards RapidFailCounts. +// + +VOID +SERVER_PROCESS::StopProcess( + VOID +) +{ + m_fReady = FALSE; + + m_pProcessManager->IncrementRapidFailCount(); + + for(INT i=0;iNumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || + processList->NumberOfProcessIdsInList == 0 ) ); + + if( dwError == ERROR_MORE_DATA ) + { + hr = E_OUTOFMEMORY; + // some error + goto Finished; + } + + if( processList == NULL || + ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || + processList->NumberOfProcessIdsInList == 0 ) ) + { + hr = HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED); + // some error + goto Finished; + } + + if( processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES ) + { + hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); + goto Finished; + } + + for( DWORD i=0; iNumberOfProcessIdsInList; i++ ) + { + dwPid = (DWORD)processList->ProcessIdList[i]; + if( dwPid != dwWorkerProcessPid ) + { + HANDLE hProcess = OpenProcess( + PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + dwPid + ); + + BOOL returnValue = CheckRemoteDebuggerPresent( hProcess, &fDebuggerPresent ); + if (hProcess != NULL) + { + CloseHandle(hProcess); + hProcess = NULL; + } + + if( ! returnValue ) + { + goto Finished; + } + + if( fDebuggerPresent ) + { + break; + } + } + } + +Finished: + + if( processList != NULL ) + { + HeapFree(GetProcessHeap(), 0, processList); + } + + return fDebuggerPresent; +} + +HRESULT +SERVER_PROCESS::GetChildProcessHandles( + VOID +) +{ + HRESULT hr = S_OK; + PJOBOBJECT_BASIC_PROCESS_ID_LIST processList = NULL; + DWORD dwPid = 0; + DWORD dwWorkerProcessPid = 0; + DWORD cbNumBytes = 1024; + DWORD dwRetries = 0; + DWORD dwError = NO_ERROR; + + dwWorkerProcessPid = GetCurrentProcessId(); + + do + { + dwError = NO_ERROR; + + if( processList != NULL ) + { + HeapFree(GetProcessHeap(), 0, processList); + processList = NULL; + + // resize + cbNumBytes = cbNumBytes * 2; + } + + processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( + GetProcessHeap(), + 0, + cbNumBytes + ); + if( processList == NULL ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + RtlZeroMemory( processList, cbNumBytes ); + + if( !QueryInformationJobObject( + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, + NULL) ) + { + dwError = GetLastError(); + if( dwError != ERROR_MORE_DATA ) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + } + + } while( dwRetries++ < 5 && + processList != NULL && + ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ); + + if( dwError == ERROR_MORE_DATA ) + { + hr = E_OUTOFMEMORY; + // some error + goto Finished; + } + + if( processList == NULL || ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ) + { + hr = HRESULT_FROM_WIN32(ERROR_PROCESS_ABORTED); + // some error + goto Finished; + } + + if( processList->NumberOfProcessIdsInList > MAX_ACTIVE_CHILD_PROCESSES ) + { + hr = HRESULT_FROM_WIN32( ERROR_CREATE_FAILED ); + goto Finished; + } + + for( DWORD i=0; iNumberOfProcessIdsInList; i++ ) + { + dwPid = (DWORD)processList->ProcessIdList[i]; + if( dwPid != m_dwProcessId && + dwPid != dwWorkerProcessPid ) + { + m_hChildProcessHandles[m_cChildProcess] = OpenProcess( + PROCESS_QUERY_INFORMATION | SYNCHRONIZE | PROCESS_TERMINATE | PROCESS_DUP_HANDLE, + FALSE, + dwPid + ); + m_dwChildProcessIds[m_cChildProcess] = dwPid; + m_cChildProcess ++; + } + } + +Finished: + + if( processList != NULL ) + { + HeapFree(GetProcessHeap(), 0, processList); + } + + return hr; +} + +HRESULT +SERVER_PROCESS::StopAllProcessesInJobObject( + VOID +) +{ + HRESULT hr = S_OK; + PJOBOBJECT_BASIC_PROCESS_ID_LIST processList = NULL; + HANDLE hProcess = NULL; + DWORD dwWorkerProcessPid = 0; + DWORD cbNumBytes = 1024; + DWORD dwRetries = 0; + + dwWorkerProcessPid = GetCurrentProcessId(); + + do + { + if( processList != NULL ) + { + HeapFree(GetProcessHeap(), 0, processList); + processList = NULL; + + // resize + cbNumBytes = cbNumBytes * 2; + } + + processList = (PJOBOBJECT_BASIC_PROCESS_ID_LIST) HeapAlloc( + GetProcessHeap(), + 0, + cbNumBytes + ); + if( processList == NULL ) + { + hr = E_OUTOFMEMORY; + goto Finished; + } + + RtlZeroMemory( processList, cbNumBytes ); + + if( !QueryInformationJobObject( + m_hJobObject, + JobObjectBasicProcessIdList, + processList, + cbNumBytes, + NULL) ) + { + DWORD dwError = GetLastError(); + if( dwError != ERROR_MORE_DATA ) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + } + + } while( dwRetries++ < 5 && + processList != NULL && + ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ); + + if( processList == NULL || ( processList->NumberOfAssignedProcesses > processList->NumberOfProcessIdsInList || processList->NumberOfProcessIdsInList == 0 ) ) + { + hr = HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + // some error + goto Finished; + } + + for( DWORD i=0; iNumberOfProcessIdsInList; i++ ) + { + if( dwWorkerProcessPid != (DWORD)processList->ProcessIdList[i] ) + { + hProcess = OpenProcess( PROCESS_TERMINATE, + FALSE, + (DWORD)processList->ProcessIdList[i] ); + if( hProcess != NULL ) + { + if( !TerminateProcess(hProcess, 1) ) + { + hr = HRESULT_FROM_GETLASTERROR(); + } + else + { + WaitForSingleObject( hProcess, INFINITE ); + } + + if( hProcess != NULL ) + { + CloseHandle( hProcess ); + hProcess = NULL; + } + } + } + } + +Finished: + + if( processList != NULL ) + { + HeapFree(GetProcessHeap(), 0, processList); + } + + return hr; +} + +SERVER_PROCESS::SERVER_PROCESS() : + m_cRefs( 1 ), + m_hProcessHandle( NULL ), + m_hProcessWaitHandle( NULL ), + m_dwProcessId( 0 ), + m_cChildProcess( 0 ), + m_fReady( FALSE ), + m_lStopping( 0L ), + m_hStdoutHandle( NULL ), + m_fStdoutLogEnabled( FALSE ), + m_hJobObject( NULL ), + m_pForwarderConnection( NULL ), + m_dwListeningProcessId( 0 ), + m_hListeningProcessHandle( NULL ), + m_hShutdownHandle( NULL ) +{ + InterlockedIncrement(&g_dwActiveServerProcesses); + srand(GetTickCount()); + + for(INT i=0;iDereferenceProcessManager(); + m_pProcessManager = NULL; + } + + if(m_pForwarderConnection != NULL) + { + m_pForwarderConnection->DereferenceForwarderConnection(); + m_pForwarderConnection = NULL; + } + + m_pEnvironmentVarTable = NULL; + // no need to free m_pEnvironmentVarTable, as it references to + // the same hash table hold by configuration. + // the hashtable memory will be freed once onfiguration got recycled + + InterlockedDecrement(&g_dwActiveServerProcesses); +} + +VOID +ProcessHandleCallback( + _In_ PVOID pContext, + _In_ BOOL +) +{ + SERVER_PROCESS *pServerProcess = (SERVER_PROCESS*) pContext; + pServerProcess->HandleProcessExit(); +} + +HRESULT +SERVER_PROCESS::RegisterProcessWait( + PHANDLE phWaitHandle, + HANDLE hProcessToWaitOn +) +{ + HRESULT hr = S_OK; + NTSTATUS status = 0; + + _ASSERT( phWaitHandle != NULL && *phWaitHandle == NULL ); + + *phWaitHandle = NULL; + + // wait thread will dereference. + ReferenceServerProcess(); + + status = RegisterWaitForSingleObject( + phWaitHandle, + hProcessToWaitOn, + (WAITORTIMERCALLBACK)&ProcessHandleCallback, + this, + INFINITE, + WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD + ); + + if( status < 0 ) + { + hr = HRESULT_FROM_NT( status ); + goto Finished; + } + +Finished: + + if( FAILED( hr ) ) + { + *phWaitHandle = NULL; + DereferenceServerProcess(); + } + + return hr; +} + +HRESULT +SERVER_PROCESS::HandleProcessExit() +{ + HRESULT hr = S_OK; + BOOL fReady = FALSE; + DWORD dwProcessId = 0; + if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) + { + CheckIfServerIsUp(m_dwPort, &dwProcessId, &fReady); + + if (!fReady) + { + m_pProcessManager->ShutdownProcess(this); + } + + DereferenceServerProcess(); + } + + return hr; +} + +HRESULT +SERVER_PROCESS::SendShutdownHttpMessage() +{ + HRESULT hr = S_OK; + HINTERNET hSession = NULL, + hConnect = NULL, + hRequest = NULL; + + STACK_STRU(strHeaders, 256); + STRU strAppToken; + STRU strUrl; + DWORD dwStatusCode = 0; + DWORD dwSize = sizeof(dwStatusCode); + + LPCWSTR apsz[1]; + STACK_STRU(strEventMsg, 256); + + hSession = WinHttpOpen(L"", + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + + if (hSession == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + hConnect = WinHttpConnect(hSession, + L"127.0.0.1", + (USHORT)m_dwPort, + 0); + + if (hConnect == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + if (m_struAppPath.QueryCCH() > 1) + { + // app path size is 1 means site root, i.e., "/" + // we don't want to add duplicated '/' to the request url + // otherwise the request will fail + strUrl.Copy(m_struAppPath); + } + strUrl.Append(L"/iisintegration"); + + hRequest = WinHttpOpenRequest(hConnect, + L"POST", + strUrl.QueryStr(), + NULL, + WINHTTP_NO_REFERER, + NULL, + 0); + + if (hRequest == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // set timeout + if (!WinHttpSetTimeouts(hRequest, + m_dwShutdownTimeLimitInMS, // dwResolveTimeout + m_dwShutdownTimeLimitInMS, // dwConnectTimeout + m_dwShutdownTimeLimitInMS, // dwSendTimeout + m_dwShutdownTimeLimitInMS)) // dwReceiveTimeout + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + // set up the shutdown headers + if (FAILED(hr = strHeaders.Append(L"MS-ASPNETCORE-EVENT:shutdown \r\n")) || + FAILED(hr = strAppToken.Append(L"MS-ASPNETCORE-TOKEN:")) || + FAILED(hr = strAppToken.AppendA(m_straGuid.QueryStr())) || + FAILED(hr = strHeaders.Append(strAppToken.QueryStr()))) + { + goto Finished; + } + + if (!WinHttpSendRequest(hRequest, + strHeaders.QueryStr(), // pwszHeaders + strHeaders.QueryCCH(), // dwHeadersLength + WINHTTP_NO_REQUEST_DATA, + 0, // dwOptionalLength + 0, // dwTotalLength + 0)) // dwContext + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (!WinHttpReceiveResponse(hRequest , NULL)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (!WinHttpQueryHeaders(hRequest, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &dwStatusCode, + &dwSize, + WINHTTP_NO_HEADER_INDEX)) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + if (dwStatusCode != 202) + { + // not expected http status + hr = E_FAIL; + } + + // log + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST_MSG, + m_dwProcessId, + dwStatusCode))) + { + apsz[0] = strEventMsg.QueryStr(); + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_INFORMATION_TYPE, + 0, + ASPNETCORE_EVENT_SENT_SHUTDOWN_HTTP_REQUEST, + NULL, + 1, + 0, + apsz, + NULL); + } + } + +Finished: + if (hRequest) + { + WinHttpCloseHandle(hRequest); + hRequest = NULL; + } + if (hConnect) + { + WinHttpCloseHandle(hConnect); + hConnect = NULL; + } + if (hSession) + { + WinHttpCloseHandle(hSession); + hSession = NULL; + } + return hr; +} + +//static +VOID +SERVER_PROCESS::SendShutDownSignal( + LPVOID lpParam +) +{ + SERVER_PROCESS* pThis = static_cast(lpParam); + DBG_ASSERT(pThis); + pThis->SendShutDownSignalInternal(); +} + +// +// send shutdown message first, if fail then send +// ctrl-c to the backend process to let it gracefully shutdown +// +VOID +SERVER_PROCESS::SendShutDownSignalInternal( + VOID +) +{ + ReferenceServerProcess(); + + if (FAILED(SendShutdownHttpMessage())) + { + // + // failed to send shutdown http message + // try send ctrl signal + // + HWND hCurrentConsole = NULL; + BOOL fFreeConsole = FALSE; + hCurrentConsole = GetConsoleWindow(); + if (hCurrentConsole) + { + // free current console first, as we may have one, e.g., hostedwebcore case + fFreeConsole = FreeConsole(); + } + + if (AttachConsole(m_dwProcessId)) + { + // call ctrl-break instead of ctrl-c as child process may ignore ctrl-c + if (!GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, m_dwProcessId)) + { + // failed to send the ctrl signal. terminate the backend process immediately instead of waiting for timeout + TerminateBackendProcess(); + } + FreeConsole(); + + if (fFreeConsole) + { + // IISExpress and hostedwebcore w3wp run as background process + // have to attach console back to ensure post app_offline scenario still works + AttachConsole(ATTACH_PARENT_PROCESS); + } + } + else + { + // terminate the backend process immediately instead of waiting for timeout + TerminateBackendProcess(); + } + } + + DereferenceServerProcess(); +} + +VOID +SERVER_PROCESS::TerminateBackendProcess( + VOID +) +{ + LPCWSTR apsz[1]; + STACK_STRU(strEventMsg, 256); + + if (InterlockedCompareExchange(&m_lStopping, 1L, 0L) == 0L) + { + // backend process will be terminated, remove the waitcallback + if (m_hProcessWaitHandle != NULL) + { + UnregisterWait(m_hProcessWaitHandle); + m_hProcessWaitHandle = NULL; + } + + // cannot gracefully shutdown or timeout, terminate the process + if (m_hProcessHandle != NULL && m_hProcessHandle != INVALID_HANDLE_VALUE) + { + TerminateProcess(m_hProcessHandle, 0); + m_hProcessHandle = NULL; + } + + // as we skipped process exit callback (ProcessHandleCallback), + // need to dereference the object otherwise memory leak + DereferenceServerProcess(); + + // log a warning for ungraceful shutdown + if (SUCCEEDED(strEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE_MSG, + m_dwProcessId))) + { + apsz[0] = strEventMsg.QueryStr(); + if (FORWARDING_HANDLER::QueryEventLog() != NULL) + { + ReportEventW(FORWARDING_HANDLER::QueryEventLog(), + EVENTLOG_WARNING_TYPE, + 0, + ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE, + NULL, + 1, + 0, + apsz, + NULL); + } + } + } +} \ No newline at end of file diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/websockethandler.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/websockethandler.cxx new file mode 100644 index 0000000000..7adc14c915 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/websockethandler.cxx @@ -0,0 +1,1169 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +/*++ + +Abstract: + + Main Handler for websocket requests. + + Initiates websocket connection to backend. + Uses WinHttp API's for backend connections, + and IIS Websocket API's for sending/receiving + websocket traffic. + + Transfers data between the two IO endpoints. + +----------------- +Read Loop Design +----------------- +When a read IO completes successfully on any endpoints, Asp.Net Core Module doesn't +immediately issue the next read. The next read is initiated only after +the read data is sent to the other endpoint. As soon as this send completes, +we initiate the next IO. It should be noted that the send complete merely +indicates the API completion from HTTP, and not necessarily over the network. + +This prevents the need for data buffering at the Asp.Net Core Module level. + +--*/ + +#include "precomp.hxx" + +SRWLOCK WEBSOCKET_HANDLER::sm_RequestsListLock; + +LIST_ENTRY WEBSOCKET_HANDLER::sm_RequestsListHead; + +TRACE_LOG * WEBSOCKET_HANDLER::sm_pTraceLog; + +WEBSOCKET_HANDLER::WEBSOCKET_HANDLER(): + _pHttpContext ( NULL ), + _pWebSocketContext ( NULL ), + _hWebSocketRequest( NULL ), + _pHandler ( NULL ), + _dwOutstandingIo ( 0 ), + _fCleanupInProgress ( FALSE ), + _fIndicateCompletionToIis ( FALSE ), + _fHandleClosed( FALSE ), + _fReceivedCloseMsg ( FALSE ) +{ + DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::WEBSOCKET_HANDLER"); + + InitializeCriticalSectionAndSpinCount(&_RequestLock, 1000); + + InsertRequest(); +} + +WEBSOCKET_HANDLER::~WEBSOCKET_HANDLER() +{ + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::~WEBSOCKET_HANDLER"); + RemoveRequest(); + DeleteCriticalSection(&_RequestLock); +} + +VOID +WEBSOCKET_HANDLER::Terminate( + VOID + ) +{ + DebugPrintf (ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::Terminate"); + if (!_fHandleClosed) + { + EnterCriticalSection(&_RequestLock); + _fCleanupInProgress = TRUE; + if (_pHttpContext != NULL) + { + _pHttpContext->CancelIo(); + _pHttpContext = NULL; + } + + if (_hWebSocketRequest != NULL) + { + WinHttpCloseHandle(_hWebSocketRequest); + _hWebSocketRequest = NULL; + } + + _fHandleClosed = TRUE; + LeaveCriticalSection(&_RequestLock); + } +} + +//static +HRESULT +WEBSOCKET_HANDLER::StaticInitialize( + BOOL fEnableReferenceCountTracing + ) +/*++ + + Routine Description: + + Initialize structures required for idle connection cleanup. + +--*/ +{ + if (!g_fWebSocketSupported) + { + return S_OK; + } + + if (fEnableReferenceCountTracing) + { + // + // If tracing is enabled, keep track of all websocket requests + // for debugging purposes. + // + + InitializeListHead (&sm_RequestsListHead); + sm_pTraceLog = CreateRefTraceLog( 10000, 0 ); + } + + return S_OK; +} + +//static +VOID +WEBSOCKET_HANDLER::StaticTerminate( + VOID + ) +{ + if (!g_fWebSocketSupported) + { + return; + } + + if (sm_pTraceLog) + { + DestroyRefTraceLog(sm_pTraceLog); + sm_pTraceLog = NULL; + } +} + +VOID +WEBSOCKET_HANDLER::InsertRequest( + VOID + ) +{ + if (g_fEnableReferenceCountTracing) + { + AcquireSRWLockExclusive(&sm_RequestsListLock); + + InsertTailList(&sm_RequestsListHead, &_listEntry); + + ReleaseSRWLockExclusive( &sm_RequestsListLock); + } +} + +//static +VOID +WEBSOCKET_HANDLER::RemoveRequest( + VOID + ) +{ + if (g_fEnableReferenceCountTracing) + { + AcquireSRWLockExclusive(&sm_RequestsListLock); + + RemoveEntryList(&_listEntry); + + ReleaseSRWLockExclusive( &sm_RequestsListLock); + } +} + +VOID +WEBSOCKET_HANDLER::IncrementOutstandingIo( + VOID + ) +{ + LONG dwOutstandingIo = InterlockedIncrement(&_dwOutstandingIo); + + if (sm_pTraceLog) + { + WriteRefTraceLog(sm_pTraceLog, dwOutstandingIo, this); + } +} + +VOID +WEBSOCKET_HANDLER::DecrementOutstandingIo( + VOID + ) +/*++ + Routine Description: + Decrements outstanding IO count. + + This indicates completion to IIS if all outstanding IO + has been completed, and a Cleanup was triggered for this + connection (denoted by _fIndicateCompletionToIis). + +--*/ +{ + LONG dwOutstandingIo = InterlockedDecrement (&_dwOutstandingIo); + + if (sm_pTraceLog) + { + WriteRefTraceLog(sm_pTraceLog, dwOutstandingIo, this); + } + + if (dwOutstandingIo == 0 && _fIndicateCompletionToIis) + { + IndicateCompletionToIIS(); + } +} + +VOID +WEBSOCKET_HANDLER::IndicateCompletionToIIS( + VOID + ) +/*++ + Routine Description: + Indicates completion to IIS. + + This returns a Pending Status, so that forwarding handler has a chance + to do book keeping when request is finally done. + +--*/ +{ + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::IndicateCompletionToIIS called %d", _dwOutstandingIo); + // + // close Websocket handle. This will triger a WinHttp callback + // on handle close, then let IIS pipeline continue. + // Make sure no pending IO as there is no IIS websocket cancelation, + // any unexpected callback will lead to AV. Revisit it once CanelOutGoingIO works + // + if (_hWebSocketRequest != NULL && _dwOutstandingIo == 0) + { + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::IndicateCompletionToIIS"); + + _pHandler->SetStatus(FORWARDER_DONE); + _fHandleClosed = TRUE; + WinHttpCloseHandle(_hWebSocketRequest); + _hWebSocketRequest = NULL; + } +} + +HRESULT +WEBSOCKET_HANDLER::ProcessRequest( + FORWARDING_HANDLER *pHandler, + IHttpContext *pHttpContext, + HINTERNET hRequest, + BOOL* fHandleCreated +) +/*++ + +Routine Description: + + Entry point to WebSocket Handler: + + This routine is called after the 101 response was successfully sent to + the client. + This routine get's a websocket handle to winhttp, + websocket handle to IIS's websocket context, and initiates IO + in these two endpoints. + + +--*/ +{ + HRESULT hr = S_OK; + //DWORD dwBuffSize = RECEIVE_BUFFER_SIZE; + + _pHandler = pHandler; + + EnterCriticalSection(&_RequestLock); + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::ProcessRequest"); + + // + // Cache the points to IHttpContext3 + // + + hr = HttpGetExtendedInterface(g_pHttpServer, + pHttpContext, + &_pHttpContext); + if (FAILED (hr)) + { + goto Finished; + } + + // + // Get pointer to IWebSocketContext for IIS websocket IO. + // + + _pWebSocketContext = (IWebSocketContext *) _pHttpContext-> + GetNamedContextContainer()->GetNamedContext(IIS_WEBSOCKET); + if ( _pWebSocketContext == NULL ) + { + hr = HRESULT_FROM_WIN32( ERROR_FILE_NOT_FOUND ); + goto Finished; + } + + // + // Get Handle to Winhttp's websocket context. + // + + _hWebSocketRequest = WINHTTP_HELPER::sm_pfnWinHttpWebSocketCompleteUpgrade( + hRequest, + (DWORD_PTR) pHandler); + + if (_hWebSocketRequest == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + *fHandleCreated = TRUE; + // + // Resize the send & receive buffers to be more conservative (and avoid DoS attacks). + // NOTE: The two WinHTTP options below were added for WinBlue, so we can't + // rely on their existence. + // + + //if (!WinHttpSetOption(_hWebSocketRequest, + // WINHTTP_OPTION_WEB_SOCKET_RECEIVE_BUFFER_SIZE, + // &dwBuffSize, + // sizeof(dwBuffSize))) + //{ + // DWORD dwRet = GetLastError(); + // if ( dwRet != ERROR_WINHTTP_INVALID_OPTION ) + // { + // hr = HRESULT_FROM_WIN32(dwRet); + // goto Finished; + // } + //} + + //if (!WinHttpSetOption(_hWebSocketRequest, + // WINHTTP_OPTION_WEB_SOCKET_SEND_BUFFER_SIZE, + // &dwBuffSize, + // sizeof(dwBuffSize))) + //{ + // DWORD dwRet = GetLastError(); + // if ( dwRet != ERROR_WINHTTP_INVALID_OPTION ) + // { + // hr = HRESULT_FROM_WIN32(dwRet); + // goto Finished; + // } + //} + + // + // Initiate Read on IIS + // + + hr = DoIisWebSocketReceive(); + if (FAILED(hr)) + { + goto Finished; + } + + // + // Initiate Read on WinHttp + // + + hr = DoWinHttpWebSocketReceive(); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + LeaveCriticalSection(&_RequestLock); + + if (FAILED (hr)) + { + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "Process Request Failed with HR=%08x", hr); + } + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::DoIisWebSocketReceive( + VOID +) +/*++ + +Routine Description: + + Initiates a websocket receive on the IIS Websocket Context. + + +--*/ +{ + HRESULT hr = S_OK; + + DWORD dwBufferSize = RECEIVE_BUFFER_SIZE; + BOOL fUtf8Encoded; + BOOL fFinalFragment; + BOOL fClose; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoIisWebSocketReceive"); + + IncrementOutstandingIo(); + + hr = _pWebSocketContext->ReadFragment( + &_IisReceiveBuffer, + &dwBufferSize, + TRUE, + &fUtf8Encoded, + &fFinalFragment, + &fClose, + OnReadIoCompletion, + this, + NULL); + if (FAILED(hr)) + { + DecrementOutstandingIo(); + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::DoIisWebSocketSend failed with %08x", hr); + + } + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive( + VOID +) +/*++ + +Routine Description: + + Initiates a websocket receive on WinHttp + + +--*/ +{ + HRESULT hr = S_OK; + DWORD dwError = NO_ERROR; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive"); + + IncrementOutstandingIo(); + + dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketReceive( + _hWebSocketRequest, + &_WinHttpReceiveBuffer, + RECEIVE_BUFFER_SIZE, + NULL, + NULL); + + if (dwError != NO_ERROR) + { + DecrementOutstandingIo(); + + hr = HRESULT_FROM_WIN32(dwError); + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::DoWinHttpWebSocketReceive failed with %08x", hr); + + } + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::DoIisWebSocketSend( + DWORD cbData, + WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType +) +/*++ + +Routine Description: + + Initiates a websocket send on IIS + +--*/ +{ + HRESULT hr = S_OK; + + BOOL fUtf8Encoded = FALSE; + BOOL fFinalFragment = FALSE; + BOOL fClose = FALSE; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoIisWebSocketSend %d", eBufferType); + + if (eBufferType == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) + { + // + // Query Close Status from WinHttp + // + + DWORD dwError = NO_ERROR; + USHORT uStatus; + DWORD dwReceived = 0; + STACK_STRU(strCloseReason, 128); + + dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketQueryCloseStatus( + _hWebSocketRequest, + &uStatus, + &_WinHttpReceiveBuffer, + RECEIVE_BUFFER_SIZE, + &dwReceived); + + if (dwError != NO_ERROR) + { + hr = HRESULT_FROM_WIN32(dwError); + goto Finished; + } + + // + // Convert close reason to WCHAR + // + + hr = strCloseReason.CopyA((PCSTR)&_WinHttpReceiveBuffer, + dwReceived); + if (FAILED(hr)) + { + goto Finished; + } + + IncrementOutstandingIo(); + + // + // Backend end may start close hand shake first + // Need to inidcate no more receive should be called on WinHttp connection + // + _fReceivedCloseMsg = TRUE; + _fIndicateCompletionToIis = TRUE; + + // + // Send close to IIS. + // + + hr = _pWebSocketContext->SendConnectionClose( + TRUE, + uStatus, + uStatus == 1005 ? NULL : strCloseReason.QueryStr(), + OnWriteIoCompletion, + this, + NULL); + } + else + { + // + // Get equivalant flags for IIS API from buffer type. + // + + WINHTTP_HELPER::GetFlagsFromBufferType(eBufferType, + &fUtf8Encoded, + &fFinalFragment, + &fClose); + + IncrementOutstandingIo(); + + // + // Do the Send. + // + + hr = _pWebSocketContext->WriteFragment( + &_WinHttpReceiveBuffer, + &cbData, + TRUE, + fUtf8Encoded, + fFinalFragment, + OnWriteIoCompletion, + this, + NULL); + + } + + if (FAILED(hr)) + { + DecrementOutstandingIo(); + } + +Finished: + if (FAILED(hr)) + { + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::DoIisWebSocketSend failed with %08x", hr); + } + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::DoWinHttpWebSocketSend( + DWORD cbData, + WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType +) +/*++ + +Routine Description: + + Initiates a websocket send on WinHttp + +--*/ +{ + DWORD dwError = NO_ERROR; + HRESULT hr = S_OK; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoWinHttpWebSocketSend, %d", eBufferType); + + if (eBufferType == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) + { + USHORT uStatus; + LPCWSTR pszReason; + STACK_STRA(strCloseReason, 128); + + // + // Get Close status from IIS. + // + + hr = _pWebSocketContext->GetCloseStatus(&uStatus, + &pszReason); + + if (FAILED(hr)) + { + goto Finished; + } + + // + // Convert status to UTF8 + // + + hr = strCloseReason.CopyWToUTF8Unescaped(pszReason); + if (FAILED(hr)) + { + goto Finished; + } + + IncrementOutstandingIo(); + + // + // Send Close. + // + + dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketShutdown( + _hWebSocketRequest, + uStatus, + strCloseReason.QueryCCH() == 0 ? NULL : (PVOID) strCloseReason.QueryStr(), + strCloseReason.QueryCCH()); + + if (dwError == ERROR_IO_PENDING) + { + // + // Call will complete asynchronously, return. + // ignore error. + // + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoWinhttpWebSocketSend IO_PENDING"); + + dwError = NO_ERROR; + } + else + { + if (dwError == NO_ERROR) + { + // + // Call completed synchronously. + // + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::DoWinhttpWebSocketSend Shutdown successful."); + } + } + } + else + { + IncrementOutstandingIo(); + + dwError = WINHTTP_HELPER::sm_pfnWinHttpWebSocketSend( + _hWebSocketRequest, + eBufferType, + cbData == 0 ? NULL : &_IisReceiveBuffer, + cbData + ); + } + + if (dwError != NO_ERROR) + { + hr = HRESULT_FROM_WIN32(dwError); + DecrementOutstandingIo(); + goto Finished; + } + +Finished: + if (FAILED(hr)) + { + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::DoWinHttpWebSocketSend failed with %08x", hr); + } + + return hr; +} + +//static +VOID +WINAPI +WEBSOCKET_HANDLER::OnReadIoCompletion( + HRESULT hrError, + VOID * pvCompletionContext, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ) +/*++ + + Routine Description: + + Completion routine for Read's from IIS pipeline. + +--*/ +{ + WEBSOCKET_HANDLER * pHandler = (WEBSOCKET_HANDLER *) + pvCompletionContext; + + pHandler->OnIisReceiveComplete( + hrError, + cbIO, + fUTF8Encoded, + fFinalFragment, + fClose + ); +} + +//static +VOID +WINAPI +WEBSOCKET_HANDLER::OnWriteIoCompletion( + HRESULT hrError, + VOID * pvCompletionContext, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ) +/*++ + Routine Description: + + Completion routine for Write's from IIS pipeline. + +--*/ +{ + WEBSOCKET_HANDLER * pHandler = (WEBSOCKET_HANDLER *) + pvCompletionContext; + + UNREFERENCED_PARAMETER(fUTF8Encoded); + UNREFERENCED_PARAMETER(fFinalFragment); + UNREFERENCED_PARAMETER(fClose); + + pHandler->OnIisSendComplete( + hrError, + cbIO + ); +} + + +HRESULT +WEBSOCKET_HANDLER::OnWinHttpSendComplete( + WINHTTP_WEB_SOCKET_STATUS * + ) +/*++ + +Routine Description: + Completion callback executed when a send to backend + server completes. + + If the send was successful, issue the next read + on the client's endpoint. + +++*/ +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + CleanupReason cleanupReason = CleanupReasonUnknown; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnWinHttpSendComplete"); + + if (_fCleanupInProgress) + { + goto Finished; + } + + EnterCriticalSection (&_RequestLock); + + fLocked = TRUE; + + if (_fCleanupInProgress) + { + goto Finished; + } + // + // Data was successfully sent to backend. + // Initiate next receive from IIS. + // + + hr = DoIisWebSocketReceive(); + if (FAILED(hr)) + { + goto Finished; + } + +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + + if (FAILED (hr)) + { + Cleanup (cleanupReason); + + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnWinsockSendComplete failed with HR=%08x", hr); + } + + // + // The handler object can be gone after this call. + // do not reference it after this statement. + // + + DecrementOutstandingIo(); + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::OnWinHttpShutdownComplete( + VOID + ) +{ + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnWinHttpShutdownComplete --%p", _pHandler); + + DecrementOutstandingIo(); + + return S_OK; +} + +HRESULT +WEBSOCKET_HANDLER::OnWinHttpIoError( + WINHTTP_WEB_SOCKET_ASYNC_RESULT *pCompletionStatus +) +{ + HRESULT hr = HRESULT_FROM_WIN32(pCompletionStatus->AsyncResult.dwError); + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnWinHttpIoError HR = %08x, Operation = %d", + hr, pCompletionStatus->AsyncResult.dwResult); + + Cleanup(ServerDisconnect); + + DecrementOutstandingIo(); + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::OnWinHttpReceiveComplete( + WINHTTP_WEB_SOCKET_STATUS * pCompletionStatus + ) +/*++ + +Routine Description: + + Completion callback executed when a receive completes + on the backend server winhttp endpoint. + + Issue send on the Client(IIS) if the receive was + successful. + + If the receive completed with zero bytes, that + indicates that the server has disconnected the connection. + Issue cleanup for the websocket handler. +--*/ +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + CleanupReason cleanupReason = CleanupReasonUnknown; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnWinHttpReceiveComplete, %d", _fCleanupInProgress); + + if (_fCleanupInProgress) + { + goto Finished; + } + + EnterCriticalSection(&_RequestLock); + + fLocked = TRUE; + if (_fCleanupInProgress) + { + goto Finished; + } + hr = DoIisWebSocketSend( + pCompletionStatus->dwBytesTransferred, + pCompletionStatus->eBufferType + ); + + if (FAILED (hr)) + { + cleanupReason = ClientDisconnect; + goto Finished; + } + +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + if (FAILED (hr)) + { + Cleanup (cleanupReason); + + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnWinsockReceiveComplete failed with HR=%08x", hr); + } + + // + // The handler object can be gone after this call. + // do not reference it after this statement. + // + + DecrementOutstandingIo(); + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::OnIisSendComplete( + HRESULT hrCompletion, + DWORD cbIo + ) +/*++ +Routine Description: + + Completion callback executed when a send + completes from the client. + + If send was successful,issue read on the + server endpoint, to continue the readloop. + +--*/ +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + CleanupReason cleanupReason = CleanupReasonUnknown; + + UNREFERENCED_PARAMETER(cbIo); + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "WEBSOCKET_HANDLER::OnIisSendComplete"); + + if (FAILED(hrCompletion)) + { + hr = hrCompletion; + cleanupReason = ClientDisconnect; + goto Finished; + } + + if (_fCleanupInProgress) + { + goto Finished; + } + EnterCriticalSection(&_RequestLock); + fLocked = TRUE; + if (_fCleanupInProgress) + { + goto Finished; + } + + // + // Only call read if no close hand shake was received from backend + // + //if (!_fReceivedCloseMsg) + //{ + // + // Write Completed, initiate next read from backend server. + // + hr = DoWinHttpWebSocketReceive(); + if (FAILED(hr)) + { + cleanupReason = ServerDisconnect; + goto Finished; + } + //} + +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + if (FAILED (hr)) + { + Cleanup (cleanupReason); + + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnIisSendComplete failed with HR=%08x", hr); + } + + // + // The handler object can be gone after this call. + // do not reference it after this statement. + // + + DecrementOutstandingIo(); + + return hr; +} + +HRESULT +WEBSOCKET_HANDLER::OnIisReceiveComplete( + HRESULT hrCompletion, + DWORD cbIO, + BOOL fUTF8Encoded, + BOOL fFinalFragment, + BOOL fClose + ) +/*++ +Routine Description: + + Completion routine executed when a receive completes + from the client (IIS endpoint). + + If the receive was successful, initiate a send on + the backend server (winhttp) endpoint. + + If the receive failed, initiate cleanup. + +--*/ +{ + HRESULT hr = S_OK; + BOOL fLocked = FALSE; + CleanupReason cleanupReason = CleanupReasonUnknown; + WINHTTP_WEB_SOCKET_BUFFER_TYPE BufferType; + + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::OnIisReceiveComplete"); + + if (FAILED(hrCompletion)) + { + cleanupReason = ClientDisconnect; + hr = hrCompletion; + goto Finished; + } + + if (_fCleanupInProgress) + { + goto Finished; + } + + EnterCriticalSection(&_RequestLock); + fLocked = TRUE; + + if (_fCleanupInProgress) + { + goto Finished; + } + // + // Get Buffer Type from flags. + // + + WINHTTP_HELPER::GetBufferTypeFromFlags(fUTF8Encoded, + fFinalFragment, + fClose, + &BufferType); + + // + // Initiate Send. + // + + hr = DoWinHttpWebSocketSend(cbIO, BufferType); + if (FAILED (hr)) + { + cleanupReason = ServerDisconnect; + goto Finished; + } + +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + if (FAILED (hr)) + { + Cleanup (cleanupReason); + + DebugPrintf (ASPNETCORE_DEBUG_FLAG_ERROR, + "WEBSOCKET_HANDLER::OnIisReceiveComplete failed with HR=%08x", hr); + } + + // + // The handler object can be gone after this call. + // do not reference it after this statement. + // + + DecrementOutstandingIo(); + + return hr; +} + +VOID +WEBSOCKET_HANDLER::Cleanup( + CleanupReason reason +) +/*++ + +Routine Description: + + Cleanup function for the websocket handler. + + Initiates cancelIo on the two IO endpoints: + IIS, WinHttp client. + +Arguments: + CleanupReason +--*/ +{ + BOOL fLocked = FALSE; + DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, + "WEBSOCKET_HANDLER::Cleanup Initiated with reason %d", reason); + + if (_fCleanupInProgress) + { + goto Finished; + } + + EnterCriticalSection(&_RequestLock); + fLocked = TRUE; + + if (_fCleanupInProgress) + { + goto Finished; + } + + _fCleanupInProgress = TRUE; + + _fIndicateCompletionToIis = TRUE; + // + // We need cancel IO for fast error handling + // Reivist the code once CanelOutstandingIO api is available + // + /*if (_pWebSocketContext != NULL) + { + _pWebSocketContext->CancelOutstandingIO(); + }*/ + _pHttpContext->CancelIo(); + + // + // Don't close the handle here, + // as it trigger a WinHttp callback and let IIS pipeline continue + // Handle should be closed only in IndicateCompletionToIIS + IndicateCompletionToIIS(); +Finished: + if (fLocked) + { + LeaveCriticalSection(&_RequestLock); + } + +} diff --git a/src/AspNetCoreModuleV1/AspNetCore/src/winhttphelper.cxx b/src/AspNetCoreModuleV1/AspNetCore/src/winhttphelper.cxx new file mode 100644 index 0000000000..8407c45830 --- /dev/null +++ b/src/AspNetCoreModuleV1/AspNetCore/src/winhttphelper.cxx @@ -0,0 +1,176 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "precomp.hxx" + +PFN_WINHTTP_WEBSOCKET_COMPLETE_UPGRADE +WINHTTP_HELPER::sm_pfnWinHttpWebSocketCompleteUpgrade; + +PFN_WINHTTP_WEBSOCKET_SEND +WINHTTP_HELPER::sm_pfnWinHttpWebSocketSend; + +PFN_WINHTTP_WEBSOCKET_RECEIVE +WINHTTP_HELPER::sm_pfnWinHttpWebSocketReceive; + +PFN_WINHTTP_WEBSOCKET_SHUTDOWN +WINHTTP_HELPER::sm_pfnWinHttpWebSocketShutdown; + +PFN_WINHTTP_WEBSOCKET_QUERY_CLOSE_STATUS +WINHTTP_HELPER::sm_pfnWinHttpWebSocketQueryCloseStatus; + +//static +HRESULT +WINHTTP_HELPER::StaticInitialize( + VOID +) +{ + HRESULT hr = S_OK; + + if (!g_fWebSocketSupported) + { + return S_OK; + } + + // + // Initialize the function pointers for WinHttp Websocket API's. + // + + HMODULE hWinHttp = GetModuleHandleA("winhttp.dll"); + if (hWinHttp == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + sm_pfnWinHttpWebSocketCompleteUpgrade = (PFN_WINHTTP_WEBSOCKET_COMPLETE_UPGRADE) + GetProcAddress(hWinHttp, "WinHttpWebSocketCompleteUpgrade"); + if (sm_pfnWinHttpWebSocketCompleteUpgrade == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + sm_pfnWinHttpWebSocketQueryCloseStatus = (PFN_WINHTTP_WEBSOCKET_QUERY_CLOSE_STATUS) + GetProcAddress(hWinHttp, "WinHttpWebSocketQueryCloseStatus"); + if (sm_pfnWinHttpWebSocketQueryCloseStatus == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + sm_pfnWinHttpWebSocketReceive = (PFN_WINHTTP_WEBSOCKET_RECEIVE) + GetProcAddress(hWinHttp, "WinHttpWebSocketReceive"); + if (sm_pfnWinHttpWebSocketReceive == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + sm_pfnWinHttpWebSocketSend = (PFN_WINHTTP_WEBSOCKET_SEND) + GetProcAddress(hWinHttp, "WinHttpWebSocketSend"); + if (sm_pfnWinHttpWebSocketSend == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + + sm_pfnWinHttpWebSocketShutdown = (PFN_WINHTTP_WEBSOCKET_SHUTDOWN) + GetProcAddress(hWinHttp, "WinHttpWebSocketShutdown"); + if (sm_pfnWinHttpWebSocketShutdown == NULL) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + +Finished: + return hr; +} + + +//static +VOID +WINHTTP_HELPER::GetFlagsFromBufferType( + __in WINHTTP_WEB_SOCKET_BUFFER_TYPE BufferType, + __out BOOL * pfUtf8Encoded, + __out BOOL * pfFinalFragment, + __out BOOL * pfClose +) +{ + switch (BufferType) + { + case WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE: + *pfUtf8Encoded = FALSE; + *pfFinalFragment = TRUE; + *pfClose = FALSE; + + break; + + case WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE: + *pfUtf8Encoded = FALSE; + *pfFinalFragment = FALSE; + *pfClose = FALSE; + + break; + + case WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE: + *pfUtf8Encoded = TRUE; + *pfFinalFragment = TRUE; + *pfClose = FALSE; + + break; + + case WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE: + *pfUtf8Encoded = TRUE; + *pfFinalFragment = FALSE; + *pfClose = FALSE; + + break; + + case WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE: + *pfUtf8Encoded = FALSE; + *pfFinalFragment = FALSE; + *pfClose = TRUE; + + break; + } +} + +//static +VOID +WINHTTP_HELPER::GetBufferTypeFromFlags( + __in BOOL fUtf8Encoded, + __in BOOL fFinalFragment, + __in BOOL fClose, + __out WINHTTP_WEB_SOCKET_BUFFER_TYPE* pBufferType +) +{ + if (fClose) + { + *pBufferType = WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE; + } + else + if (fUtf8Encoded) + { + if (fFinalFragment) + { + *pBufferType = WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE; + } + else + { + *pBufferType = WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE; + } + } + else + { + if (fFinalFragment) + { + *pBufferType = WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE; + } + else + { + *pBufferType = WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE; + } + } + + return; +} \ No newline at end of file