From 6ddbfb64b92f49ca582668bd2e21b23b57b54fd7 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Fri, 24 Aug 2018 20:20:26 -0700 Subject: [PATCH] Improve configuration file handling logic (#1317) --- .../AspNetCore/AspNetCore.vcxproj | 4 +- .../AspNetCore/HandlerResolver.cpp | 29 ++++-- .../AspNetCore/HandlerResolver.h | 6 +- .../AspNetCore/PollingAppOfflineApplication.h | 2 +- .../AspNetCore/ShimOptions.cpp | 43 +++++++++ .../AspNetCore/ShimOptions.h | 65 +++++++++++++ .../AspNetCore/applicationinfo.h | 1 - .../AspNetCore/aspnetcore_shim_config.cpp | 80 ---------------- .../AspNetCore/aspnetcore_shim_config.h | 85 ----------------- .../CommonLib/CommonLib.vcxproj | 10 ++ .../CommonLib/ConfigurationLoadException.h | 20 ++++ .../CommonLib/ConfigurationSection.cpp | 57 +++++++++++ .../CommonLib/ConfigurationSection.h | 46 +++++++++ .../CommonLib/ConfigurationSource.cpp | 18 ++++ .../CommonLib/ConfigurationSource.h | 24 +++++ .../CommonLib/NonCopyable.h | 11 +++ .../CommonLib/StringHelpers.cpp | 5 + .../CommonLib/StringHelpers.h | 13 +++ .../WebConfigConfigurationSection.cpp | 81 ++++++++++++++++ .../CommonLib/WebConfigConfigurationSection.h | 25 +++++ .../WebConfigConfigurationSource.cpp | 20 ++++ .../CommonLib/WebConfigConfigurationSource.h | 23 +++++ .../CommonLib/application.h | 59 +++++++++++- .../CommonLib/aspnetcore_msg.mc | 2 +- src/AspNetCoreModuleV2/CommonLib/exceptions.h | 38 +++++++- src/AspNetCoreModuleV2/CommonLib/resources.h | 2 +- src/AspNetCoreModuleV2/IISLib/ahutil.h | 1 + .../InProcessOptions.cpp | 30 ++++++ .../InProcessOptions.h | 95 +++++++++++++++++++ .../InProcessRequestHandler.vcxproj | 2 + .../InProcessRequestHandler/dllmain.cpp | 20 +++- .../inprocessapplication.cpp | 48 ++++++---- .../inprocessapplication.h | 12 +-- .../managedexports.cpp | 10 +- .../AppOfflineTrackingApplication.h | 2 +- .../environmentvariablehash.h | 14 ++- .../Inprocess/EnvironmentVariableTests.cs | 4 +- .../Utilities/Helpers.cs | 6 ++ test/CommonLibTests/fakeclasses.h | 13 +-- .../inprocess_application_tests.cpp | 48 +++++++++- .../InProcess/StartupTests.cs | 42 ++++++++ 41 files changed, 877 insertions(+), 239 deletions(-) create mode 100644 src/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp create mode 100644 src/AspNetCoreModuleV2/AspNetCore/ShimOptions.h delete mode 100644 src/AspNetCoreModuleV2/AspNetCore/aspnetcore_shim_config.cpp delete mode 100644 src/AspNetCoreModuleV2/AspNetCore/aspnetcore_shim_config.h create mode 100644 src/AspNetCoreModuleV2/CommonLib/ConfigurationLoadException.h create mode 100644 src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.cpp create mode 100644 src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h create mode 100644 src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.cpp create mode 100644 src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.h create mode 100644 src/AspNetCoreModuleV2/CommonLib/NonCopyable.h create mode 100644 src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.cpp create mode 100644 src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.h create mode 100644 src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.cpp create mode 100644 src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.h create mode 100644 src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp create mode 100644 src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h diff --git a/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj b/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj index b809cdeadc..d700f7ed1b 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj +++ b/src/AspNetCoreModuleV2/AspNetCore/AspNetCore.vcxproj @@ -231,7 +231,7 @@ - + @@ -246,7 +246,7 @@ - + diff --git a/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp b/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp index 51b8cb611e..fbed8b09e9 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp @@ -12,6 +12,8 @@ #include "file_utility.h" #include "LoggingHelpers.h" #include "resources.h" +#include "ConfigurationLoadException.h" +#include "WebConfigConfigurationSource.h" const PCWSTR HandlerResolver::s_pwzAspnetcoreInProcessRequestHandlerName = L"aspnetcorev2_inprocess.dll"; const PCWSTR HandlerResolver::s_pwzAspnetcoreOutOfProcessRequestHandlerName = L"aspnetcorev2_outofprocess.dll"; @@ -25,7 +27,7 @@ HandlerResolver::HandlerResolver(HMODULE hModule, IHttpServer &pServer) } HRESULT -HandlerResolver::LoadRequestHandlerAssembly(IHttpApplication &pApplication, ASPNETCORE_SHIM_CONFIG& pConfiguration, std::unique_ptr& pApplicationFactory) +HandlerResolver::LoadRequestHandlerAssembly(IHttpApplication &pApplication, ShimOptions& pConfiguration, std::unique_ptr& pApplicationFactory) { HRESULT hr; PCWSTR pstrHandlerDllName; @@ -65,7 +67,7 @@ HandlerResolver::LoadRequestHandlerAssembly(IHttpApplication &pApplication, ASPN RETURN_IF_FAILED(LoggingHelpers::CreateLoggingProvider( pConfiguration.QueryStdoutLogEnabled(), !m_pServer.IsCommandLineLaunch(), - pConfiguration.QueryStdoutLogFile()->QueryStr(), + pConfiguration.QueryStdoutLogFile().c_str(), pApplication.GetApplicationPhysicalPath(), outputManager)); @@ -129,20 +131,20 @@ HandlerResolver::GetApplicationFactory(IHttpApplication &pApplication, std::uniq { try { - ASPNETCORE_SHIM_CONFIG pConfiguration; - RETURN_IF_FAILED(pConfiguration.Populate(&m_pServer, &pApplication)); + const WebConfigConfigurationSource configurationSource(m_pServer.GetAdminManager(), pApplication); + ShimOptions options(configurationSource); SRWExclusiveLock lock(m_requestHandlerLoadLock); if (m_loadedApplicationHostingModel != HOSTING_UNKNOWN) { // Mixed hosting models - if (m_loadedApplicationHostingModel != pConfiguration.QueryHostingModel()) + if (m_loadedApplicationHostingModel != options.QueryHostingModel()) { EventLog::Error( ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR, ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG, pApplication.GetApplicationId(), - pConfiguration.QueryHostingModel()); + options.QueryHostingModel()); return E_FAIL; } @@ -158,11 +160,20 @@ HandlerResolver::GetApplicationFactory(IHttpApplication &pApplication, std::uniq } } - m_loadedApplicationHostingModel = pConfiguration.QueryHostingModel(); + m_loadedApplicationHostingModel = options.QueryHostingModel(); m_loadedApplicationId = pApplication.GetApplicationId(); - RETURN_IF_FAILED(LoadRequestHandlerAssembly(pApplication, pConfiguration, pApplicationFactory)); + RETURN_IF_FAILED(LoadRequestHandlerAssembly(pApplication, options, pApplicationFactory)); } + catch(ConfigurationLoadException &ex) + { + EventLog::Error( + ASPNETCORE_CONFIGURATION_LOAD_ERROR, + ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG, + ex.get_message().c_str()); + + RETURN_HR(E_FAIL); + } CATCH_RETURN(); return S_OK; @@ -178,7 +189,7 @@ void HandlerResolver::ResetHostingModel() HRESULT HandlerResolver::FindNativeAssemblyFromGlobalLocation( - ASPNETCORE_SHIM_CONFIG& pConfiguration, + ShimOptions& pConfiguration, PCWSTR pstrHandlerDllName, std::wstring& handlerDllPath ) diff --git a/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h b/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h index 101b1ad2a0..be927693ab 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h +++ b/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h @@ -2,10 +2,10 @@ // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once -#include "aspnetcore_shim_config.h" #include #include +#include "ShimOptions.h" #include "hostfxroptions.h" #include "HandleWrapper.h" #include "ApplicationFactory.h" @@ -18,8 +18,8 @@ public: void ResetHostingModel(); private: - HRESULT LoadRequestHandlerAssembly(IHttpApplication &pApplication, ASPNETCORE_SHIM_CONFIG& pConfiguration, std::unique_ptr& pApplicationFactory); - HRESULT FindNativeAssemblyFromGlobalLocation(ASPNETCORE_SHIM_CONFIG& pConfiguration, PCWSTR libraryName, std::wstring& handlerDllPath); + HRESULT LoadRequestHandlerAssembly(IHttpApplication &pApplication, ShimOptions& pConfiguration, std::unique_ptr& pApplicationFactory); + HRESULT FindNativeAssemblyFromGlobalLocation(ShimOptions& pConfiguration, PCWSTR libraryName, std::wstring& handlerDllPath); HRESULT FindNativeAssemblyFromHostfxr(HOSTFXR_OPTIONS& hostfxrOptions, PCWSTR libraryName, std::wstring& handlerDllPath); HMODULE m_hModule; diff --git a/src/AspNetCoreModuleV2/AspNetCore/PollingAppOfflineApplication.h b/src/AspNetCoreModuleV2/AspNetCore/PollingAppOfflineApplication.h index e7ec367c17..193a63e639 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/PollingAppOfflineApplication.h +++ b/src/AspNetCoreModuleV2/AspNetCore/PollingAppOfflineApplication.h @@ -15,7 +15,7 @@ class PollingAppOfflineApplication: public APPLICATION { public: PollingAppOfflineApplication(IHttpApplication& pApplication, PollingAppOfflineApplicationMode mode) - : + : APPLICATION(pApplication), m_ulLastCheckTime(0), m_appOfflineLocation(GetAppOfflineLocation(pApplication)), m_fAppOfflineFound(false), diff --git a/src/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp b/src/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp new file mode 100644 index 0000000000..3ba1db7889 --- /dev/null +++ b/src/AspNetCoreModuleV2/AspNetCore/ShimOptions.cpp @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "ShimOptions.h" + +#include "StringHelpers.h" +#include "ConfigurationLoadException.h" + +#define CS_ASPNETCORE_HANDLER_VERSION L"handlerVersion" + +ShimOptions::ShimOptions(const ConfigurationSource &configurationSource) : + m_hostingModel(HOSTING_UNKNOWN), + m_fStdoutLogEnabled(false) +{ + auto const section = configurationSource.GetRequiredSection(CS_ASPNETCORE_SECTION); + auto hostingModel = section->GetString(CS_ASPNETCORE_HOSTING_MODEL).value_or(L""); + + if (hostingModel.empty() || equals_ignore_case(hostingModel, CS_ASPNETCORE_HOSTING_MODEL_OUTOFPROCESS)) + { + m_hostingModel = HOSTING_OUT_PROCESS; + } + else if (equals_ignore_case(hostingModel, CS_ASPNETCORE_HOSTING_MODEL_INPROCESS)) + { + m_hostingModel = HOSTING_IN_PROCESS; + } + else + { + throw ConfigurationLoadException(format( + L"Unknown hosting model '%s'. Please specify either hostingModel=\"inprocess\" " + "or hostingModel=\"outofprocess\" in the web.config file.", hostingModel.c_str())); + } + + if (m_hostingModel == HOSTING_OUT_PROCESS) + { + const auto handlerSettings = section->GetKeyValuePairs(CS_ASPNETCORE_HANDLER_SETTINGS); + m_strHandlerVersion = find_element(handlerSettings, CS_ASPNETCORE_HANDLER_VERSION).value_or(std::wstring()); + } + + m_strProcessPath = section->GetRequiredString(CS_ASPNETCORE_PROCESS_EXE_PATH); + m_strArguments = section->GetString(CS_ASPNETCORE_PROCESS_ARGUMENTS).value_or(CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT); + m_fStdoutLogEnabled = section->GetRequiredBool(CS_ASPNETCORE_STDOUT_LOG_ENABLED); + m_struStdoutLogFile = section->GetRequiredString(CS_ASPNETCORE_STDOUT_LOG_FILE); +} diff --git a/src/AspNetCoreModuleV2/AspNetCore/ShimOptions.h b/src/AspNetCoreModuleV2/AspNetCore/ShimOptions.h new file mode 100644 index 0000000000..f987e864ba --- /dev/null +++ b/src/AspNetCoreModuleV2/AspNetCore/ShimOptions.h @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include "ConfigurationSource.h" +#include "exceptions.h" + +enum APP_HOSTING_MODEL +{ + HOSTING_UNKNOWN = 0, + HOSTING_IN_PROCESS, + HOSTING_OUT_PROCESS +}; + +class ShimOptions: NonCopyable +{ +public: + const std::wstring& + QueryProcessPath() const + { + return m_strProcessPath; + } + + const std::wstring& + QueryArguments() const + { + return m_strArguments; + } + + APP_HOSTING_MODEL + QueryHostingModel() const + { + return m_hostingModel; + } + + const std::wstring& + QueryHandlerVersion() const + { + return m_strHandlerVersion; + } + + BOOL + QueryStdoutLogEnabled() const + { + return m_fStdoutLogEnabled; + } + + const std::wstring& + QueryStdoutLogFile() const + { + return m_struStdoutLogFile; + } + + ShimOptions(const ConfigurationSource &configurationSource); + +private: + std::wstring m_strArguments; + std::wstring m_strProcessPath; + APP_HOSTING_MODEL m_hostingModel; + std::wstring m_strHandlerVersion; + std::wstring m_struStdoutLogFile; + bool m_fStdoutLogEnabled; +}; diff --git a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h index 90f9fe8df9..94f2718e08 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h +++ b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.h @@ -4,7 +4,6 @@ #pragma once #include "hostfxroptions.h" -#include "aspnetcore_shim_config.h" #include "iapplication.h" #include "SRWSharedLock.h" #include "HandlerResolver.h" diff --git a/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_shim_config.cpp b/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_shim_config.cpp deleted file mode 100644 index f994fcc06d..0000000000 --- a/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_shim_config.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#include "aspnetcore_shim_config.h" - -#include "EventLog.h" -#include "config_utility.h" -#include "ahutil.h" - -HRESULT -ASPNETCORE_SHIM_CONFIG::Populate( - IHttpServer *pHttpServer, - IHttpApplication *pHttpApplication -) -{ - STACK_STRU(strHostingModel, 12); - CComPtr pAspNetCoreElement; - - IAppHostAdminManager *pAdminManager = pHttpServer->GetAdminManager(); - const CComBSTR bstrAspNetCoreSection = CS_ASPNETCORE_SECTION; - const CComBSTR applicationConfigPath = pHttpApplication->GetAppConfigPath(); - - RETURN_IF_FAILED(pAdminManager->GetAdminSection(bstrAspNetCoreSection, - applicationConfigPath, - &pAspNetCoreElement)); - - CComBSTR struProcessPath; - RETURN_IF_FAILED(GetElementStringProperty(pAspNetCoreElement, - CS_ASPNETCORE_PROCESS_EXE_PATH, - &struProcessPath)); - m_strProcessPath = struProcessPath; - - // Swallow this error for backward compatibility - // Use default behavior for empty string - GetElementStringProperty(pAspNetCoreElement, - CS_ASPNETCORE_HOSTING_MODEL, - &strHostingModel); - - if (strHostingModel.IsEmpty() || strHostingModel.Equals(L"outofprocess", TRUE)) - { - m_hostingModel = HOSTING_OUT_PROCESS; - } - else if (strHostingModel.Equals(L"inprocess", TRUE)) - { - m_hostingModel = HOSTING_IN_PROCESS; - } - else - { - // block unknown hosting value - EventLog::Error( - ASPNETCORE_EVENT_UNKNOWN_HOSTING_MODEL_ERROR, - ASPNETCORE_EVENT_UNKNOWN_HOSTING_MODEL_ERROR_MSG, - strHostingModel.QueryStr()); - RETURN_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - - CComBSTR struArguments; - RETURN_IF_FAILED(GetElementStringProperty(pAspNetCoreElement, - CS_ASPNETCORE_PROCESS_ARGUMENTS, - &struArguments)); - - m_strArguments = struArguments; - - if (m_hostingModel == HOSTING_OUT_PROCESS) - { - STRU struHandlerVersion; - RETURN_IF_FAILED(ConfigUtility::FindHandlerVersion(pAspNetCoreElement, struHandlerVersion)); - m_strHandlerVersion = struHandlerVersion.QueryStr(); - } - - - RETURN_IF_FAILED(GetElementBoolProperty(pAspNetCoreElement, - CS_ASPNETCORE_STDOUT_LOG_ENABLED, - &m_fStdoutLogEnabled)); - RETURN_IF_FAILED(GetElementStringProperty(pAspNetCoreElement, - CS_ASPNETCORE_STDOUT_LOG_FILE, - &m_struStdoutLogFile)); - - return S_OK; -} diff --git a/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_shim_config.h b/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_shim_config.h deleted file mode 100644 index c380433e72..0000000000 --- a/src/AspNetCoreModuleV2/AspNetCore/aspnetcore_shim_config.h +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for license information. - -#pragma once - -#include -#include -#include - -#define CS_ASPNETCORE_SECTION L"system.webServer/aspNetCore" -#define CS_ASPNETCORE_PROCESS_EXE_PATH L"processPath" -#define CS_ASPNETCORE_PROCESS_ARGUMENTS L"arguments" -#define CS_ASPNETCORE_HOSTING_MODEL L"hostingModel" -#define CS_ASPNETCORE_STDOUT_LOG_ENABLED L"stdoutLogEnabled" -#define CS_ASPNETCORE_STDOUT_LOG_FILE L"stdoutLogFile" - -enum APP_HOSTING_MODEL -{ - HOSTING_UNKNOWN = 0, - HOSTING_IN_PROCESS, - HOSTING_OUT_PROCESS -}; - -class ASPNETCORE_SHIM_CONFIG -{ -public: - virtual - ~ASPNETCORE_SHIM_CONFIG() = default; - - HRESULT - Populate( - IHttpServer *pHttpServer, - IHttpApplication *pHttpApplication - ); - - std::wstring& - QueryProcessPath() - { - return m_strProcessPath; - } - - std::wstring& - QueryArguments() - { - return m_strArguments; - } - - APP_HOSTING_MODEL - QueryHostingModel() - { - return m_hostingModel; - } - - std::wstring& - QueryHandlerVersion() - { - return m_strHandlerVersion; - } - - BOOL - QueryStdoutLogEnabled() - { - return m_fStdoutLogEnabled; - } - - STRU* - QueryStdoutLogFile() - { - return &m_struStdoutLogFile; - } - - ASPNETCORE_SHIM_CONFIG() : - m_hostingModel(HOSTING_UNKNOWN) - { - } - -private: - - std::wstring m_strArguments; - std::wstring m_strProcessPath; - APP_HOSTING_MODEL m_hostingModel; - std::wstring m_strHandlerVersion; - BOOL m_fStdoutLogEnabled; - STRU m_struStdoutLogFile; -}; diff --git a/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj index 84b7f360cc..ef9f1c1986 100644 --- a/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj +++ b/src/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj @@ -195,6 +195,9 @@ + + + @@ -211,6 +214,7 @@ + @@ -221,8 +225,12 @@ + + + + @@ -245,6 +253,8 @@ Create + + diff --git a/src/AspNetCoreModuleV2/CommonLib/ConfigurationLoadException.h b/src/AspNetCoreModuleV2/CommonLib/ConfigurationLoadException.h new file mode 100644 index 0000000000..0aade37061 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/ConfigurationLoadException.h @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include + +class ConfigurationLoadException: public std::runtime_error +{ + public: + ConfigurationLoadException(std::wstring msg) + : runtime_error("Configuration load exception has occured"), message(std::move(msg)) + { + } + + std::wstring get_message() const { return message; } + + private: + std::wstring message; +}; diff --git a/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.cpp b/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.cpp new file mode 100644 index 0000000000..77c3319dd1 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.cpp @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "ConfigurationSection.h" + +#include "StringHelpers.h" +#include "ConfigurationLoadException.h" + +std::wstring ConfigurationSection::GetRequiredString(const std::wstring& name) const +{ + auto result = GetString(name); + if (!result.has_value() || result.value().empty()) + { + ThrowRequiredException(name); + } + return result.value(); +} + +bool ConfigurationSection::GetRequiredBool(const std::wstring& name) const +{ + auto result = GetBool(name); + if (!result.has_value()) + { + ThrowRequiredException(name); + } + return result.value(); +} + +DWORD ConfigurationSection::GetRequiredTimespan(const std::wstring& name) const +{ + auto result = GetTimespan(name); + if (!result.has_value()) + { + ThrowRequiredException(name); + } + return result.value(); +} + +void ConfigurationSection::ThrowRequiredException(const std::wstring& name) +{ + throw ConfigurationLoadException(format(L"Attribute '%s' is required.", name.c_str())); +} + +std::optional find_element(const std::vector>& pairs, const std::wstring& name) +{ + const auto iter = std::find_if( + pairs.begin(), + pairs.end(), + [&](const std::pair& pair) { return equals_ignore_case(pair.first, name); }); + + if (iter == pairs.end()) + { + return std::nullopt; + } + + return std::make_optional(iter->second); +} diff --git a/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h b/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h new file mode 100644 index 0000000000..163f50f8e5 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/ConfigurationSection.h @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include +#include + +#include "NonCopyable.h" + +#define CS_ASPNETCORE_COLLECTION_ITEM_NAME L"name" +#define CS_ASPNETCORE_COLLECTION_ITEM_VALUE L"value" +#define CS_ASPNETCORE_ENVIRONMENT_VARIABLES L"environmentVariables" +#define CS_ASPNETCORE_STDOUT_LOG_FILE L"stdoutLogFile" +#define CS_ASPNETCORE_STDOUT_LOG_ENABLED L"stdoutLogEnabled" +#define CS_ASPNETCORE_PROCESS_EXE_PATH L"processPath" +#define CS_ASPNETCORE_PROCESS_ARGUMENTS L"arguments" +#define CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT L"" +#define CS_ASPNETCORE_HOSTING_MODEL_OUTOFPROCESS L"outofprocess" +#define CS_ASPNETCORE_HOSTING_MODEL_INPROCESS L"inprocess" +#define CS_ASPNETCORE_HOSTING_MODEL L"hostingModel" +#define CS_ASPNETCORE_HANDLER_SETTINGS L"handlerSettings" +#define CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE L"disableStartUpErrorPage" +#define CS_ENABLED L"enabled" + +class ConfigurationSection: NonCopyable +{ +public: + ConfigurationSection() = default; + virtual ~ConfigurationSection() = default; + virtual std::optional GetString(const std::wstring& name) const = 0; + virtual std::optional GetBool(const std::wstring& name) const = 0; + virtual std::optional GetTimespan(const std::wstring& name) const = 0; + + std::wstring GetRequiredString(const std::wstring& name) const; + bool GetRequiredBool(const std::wstring& name) const; + DWORD GetRequiredTimespan(const std::wstring& name) const; + + virtual std::vector> GetKeyValuePairs(const std::wstring& name) const = 0; + +protected: + static void ThrowRequiredException(const std::wstring& name); +}; + +std::optional find_element(const std::vector>& pairs, const std::wstring& name); diff --git a/src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.cpp b/src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.cpp new file mode 100644 index 0000000000..558d054659 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.cpp @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "ConfigurationSource.h" + +#include "StringHelpers.h" +#include "ConfigurationLoadException.h" + +std::shared_ptr ConfigurationSource::GetRequiredSection(const std::wstring& name) const +{ + auto section = GetSection(name); + if (!section) + { + throw ConfigurationLoadException(format(L"Unable to get required configuration section '%s'. Possible reason is web.config authoring error.", name.c_str())); + } + return section; +} + diff --git a/src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.h b/src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.h new file mode 100644 index 0000000000..53ab5a5218 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/ConfigurationSource.h @@ -0,0 +1,24 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include +#include +#include "NonCopyable.h" +#include "ConfigurationSection.h" + +#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" + +class ConfigurationSource: NonCopyable +{ +public: + ConfigurationSource() = default; + virtual ~ConfigurationSource() = default; + virtual std::shared_ptr GetSection(const std::wstring& name) const = 0; + std::shared_ptr GetRequiredSection(const std::wstring& name) const; +}; diff --git a/src/AspNetCoreModuleV2/CommonLib/NonCopyable.h b/src/AspNetCoreModuleV2/CommonLib/NonCopyable.h new file mode 100644 index 0000000000..8dcb282411 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/NonCopyable.h @@ -0,0 +1,11 @@ +// 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 NonCopyable { +public: + NonCopyable() = default; + NonCopyable(const NonCopyable&) = default; + NonCopyable& operator=(const NonCopyable&) = default; +}; diff --git a/src/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp b/src/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp index 8e574677cd..5e251f69ed 100644 --- a/src/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp @@ -13,3 +13,8 @@ bool ends_with(const std::wstring &source, const std::wstring &suffix, bool igno const auto offset = source.length() - suffix.length(); return CSTR_EQUAL == CompareStringOrdinal(source.c_str() + offset, static_cast(suffix.length()), suffix.c_str(), static_cast(suffix.length()), ignoreCase); } + +bool equals_ignore_case(const std::wstring& s1, const std::wstring& s2) +{ + return CSTR_EQUAL == CompareStringOrdinal(s1.c_str(), static_cast(s1.length()), s2.c_str(), static_cast(s2.length()), true); +} diff --git a/src/AspNetCoreModuleV2/CommonLib/StringHelpers.h b/src/AspNetCoreModuleV2/CommonLib/StringHelpers.h index 4999bec7a7..957f5bc3d6 100644 --- a/src/AspNetCoreModuleV2/CommonLib/StringHelpers.h +++ b/src/AspNetCoreModuleV2/CommonLib/StringHelpers.h @@ -8,6 +8,9 @@ [[nodiscard]] bool ends_with(const std::wstring &source, const std::wstring &suffix, bool ignoreCase = false); +[[nodiscard]] +bool equals_ignore_case(const std::wstring& s1, const std::wstring& s2); + template [[nodiscard]] std::wstring format(const std::wstring& format, Args ... args) @@ -18,3 +21,13 @@ std::wstring format(const std::wstring& format, Args ... args) return std::wstring(formattedBuffer.get(), formattedBuffer.get() + size - 1); } +template +[[nodiscard]] +std::string format(const std::string& format, Args ... args) +{ + const size_t size = snprintf(nullptr, 0, format.c_str(), args ...) + 1; // Extra char for '\0' + std::unique_ptr formattedBuffer(new char[size]); + snprintf(formattedBuffer.get(), size, format.c_str(), args ... ); + return std::string(formattedBuffer.get(), formattedBuffer.get() + size - 1); +} + diff --git a/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.cpp b/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.cpp new file mode 100644 index 0000000000..aa2495359b --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.cpp @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "WebConfigConfigurationSection.h" + +#include "exceptions.h" +#include "ahutil.h" + +std::optional WebConfigConfigurationSection::GetString(const std::wstring& name) const +{ + CComBSTR result; + if (FAILED_LOG(GetElementStringProperty(m_element, name.c_str(), &result.m_str))) + { + return std::nullopt; + } + + return std::make_optional(std::wstring(result)); +} + +std::optional WebConfigConfigurationSection::GetBool(const std::wstring& name) const +{ + bool result; + if (FAILED_LOG(GetElementBoolProperty(m_element, name.c_str(), &result))) + { + return std::nullopt; + } + + return std::make_optional(result); +} + +std::optional WebConfigConfigurationSection::GetTimespan(const std::wstring& name) const +{ + ULONGLONG result; + if (FAILED_LOG(GetElementRawTimeSpanProperty(m_element, name.c_str(), &result))) + { + return std::nullopt; + } + + return std::make_optional(static_cast(result / 10000ull)); +} + +std::vector> WebConfigConfigurationSection::GetKeyValuePairs(const std::wstring& name) const +{ + std::vector> pairs; + HRESULT findElementResult; + CComPtr element = nullptr; + CComPtr elementCollection = nullptr; + CComPtr collectionEntry = nullptr; + ENUM_INDEX index{}; + + if (FAILED_LOG(GetElementChildByName(m_element, name.c_str(), &element))) + { + return pairs; + } + + THROW_IF_FAILED(element->get_Collection(&elementCollection)); + THROW_IF_FAILED(findElementResult = FindFirstElement(elementCollection, &index, &collectionEntry)); + + while (findElementResult != S_FALSE) + { + CComBSTR strHandlerName; + if (LOG_IF_FAILED(GetElementStringProperty(collectionEntry, CS_ASPNETCORE_COLLECTION_ITEM_NAME, &strHandlerName.m_str))) + { + ThrowRequiredException(CS_ASPNETCORE_COLLECTION_ITEM_NAME); + } + + CComBSTR strHandlerValue; + if (LOG_IF_FAILED(GetElementStringProperty(collectionEntry, CS_ASPNETCORE_COLLECTION_ITEM_VALUE, &strHandlerValue.m_str))) + { + ThrowRequiredException(CS_ASPNETCORE_COLLECTION_ITEM_VALUE); + } + + pairs.emplace_back(strHandlerName, strHandlerValue); + + collectionEntry.Release(); + + THROW_IF_FAILED(findElementResult = FindNextElement(elementCollection, &index, &collectionEntry)); + } + + return pairs; +} diff --git a/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.h b/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.h new file mode 100644 index 0000000000..c2f87a9cfe --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSection.h @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include +#include "ConfigurationSection.h" + +class WebConfigConfigurationSection: public ConfigurationSection +{ +public: + WebConfigConfigurationSection(IAppHostElement* pElement) + : m_element(pElement) + { + } + + std::optional GetString(const std::wstring& name) const override; + std::optional GetBool(const std::wstring& name) const override; + std::optional GetTimespan(const std::wstring& name) const override; + std::vector> GetKeyValuePairs(const std::wstring& name) const override; + +private: + CComPtr m_element; +}; diff --git a/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.cpp b/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.cpp new file mode 100644 index 0000000000..c433913ec7 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.cpp @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "WebConfigConfigurationSource.h" + +#include "exceptions.h" +#include "WebConfigConfigurationSection.h" + +std::shared_ptr WebConfigConfigurationSource::GetSection(const std::wstring& name) const +{ + const CComBSTR bstrAspNetCoreSection = name.c_str(); + const CComBSTR applicationConfigPath = m_application.GetAppConfigPath(); + + IAppHostElement* sectionElement; + if (LOG_IF_FAILED(m_manager->GetAdminSection(bstrAspNetCoreSection, applicationConfigPath, §ionElement))) + { + return nullptr; + } + return std::make_unique(sectionElement); +} diff --git a/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.h b/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.h new file mode 100644 index 0000000000..ce8dbcdc50 --- /dev/null +++ b/src/AspNetCoreModuleV2/CommonLib/WebConfigConfigurationSource.h @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once +#include +#include "ConfigurationSection.h" +#include "ConfigurationSource.h" + +class WebConfigConfigurationSource: public ConfigurationSource +{ +public: + WebConfigConfigurationSource(IAppHostAdminManager *pAdminManager, IHttpApplication &pHttpApplication) + : m_manager(pAdminManager), + m_application(pHttpApplication) + { + } + + std::shared_ptr GetSection(const std::wstring& name) const override; + +private: + CComPtr m_manager; + IHttpApplication &m_application; +}; diff --git a/src/AspNetCoreModuleV2/CommonLib/application.h b/src/AspNetCoreModuleV2/CommonLib/application.h index 9c02484b1a..5e87e4ddf8 100644 --- a/src/AspNetCoreModuleV2/CommonLib/application.h +++ b/src/AspNetCoreModuleV2/CommonLib/application.h @@ -3,6 +3,7 @@ #pragma once +#include #include "iapplication.h" #include "ntassert.h" #include "SRWExclusiveLock.h" @@ -20,11 +21,15 @@ public: return m_fStopCalled ? APPLICATION_STATUS::RECYCLED : APPLICATION_STATUS::RUNNING; } - APPLICATION() + APPLICATION(const IHttpApplication& pHttpApplication) : m_fStopCalled(false), - m_cRefs(1) + m_cRefs(1), + m_applicationPhysicalPath(pHttpApplication.GetApplicationPhysicalPath()), + m_applicationConfigPath(pHttpApplication.GetAppConfigPath()), + m_applicationId(pHttpApplication.GetApplicationId()) { InitializeSRWLock(&m_stateLock); + m_applicationVirtualPath = ToVirtualPath(m_applicationConfigPath); } @@ -69,10 +74,58 @@ public: } } + const std::wstring& + QueryApplicationId() const + { + return m_applicationId; + } + + const std::wstring& + QueryApplicationPhysicalPath() const + { + return m_applicationPhysicalPath; + } + + const std::wstring& + QueryApplicationVirtualPath() const + { + return m_applicationVirtualPath; + } + + const std::wstring& + QueryConfigPath() const + { + return m_applicationConfigPath; + } + protected: - SRWLOCK m_stateLock; + SRWLOCK m_stateLock {}; bool m_fStopCalled; private: mutable LONG m_cRefs; + + std::wstring m_applicationPhysicalPath; + std::wstring m_applicationVirtualPath; + std::wstring m_applicationConfigPath; + std::wstring m_applicationId; + + static std::wstring ToVirtualPath(const std::wstring& configurationPath) + { + auto segments = 0; + auto position = configurationPath.find('/'); + // Skip first 4 segments of config path + while (segments != 3 && position != std::wstring::npos) + { + segments++; + position = configurationPath.find('/', position + 1); + } + + if (position != std::wstring::npos) + { + return configurationPath.substr(position); + } + + return L"/"; + } }; diff --git a/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc index f0e3d66de3..7289b85ce6 100644 --- a/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc +++ b/src/AspNetCoreModuleV2/CommonLib/aspnetcore_msg.mc @@ -190,7 +190,7 @@ Language=English . Messageid=1034 -SymbolicName=ASPNETCORE_EVENT_UNKNOWN_HOSTING_MODEL_ERROR +SymbolicName=ASPNETCORE_CONFIGURATION_LOAD_ERROR Language=English %1 . diff --git a/src/AspNetCoreModuleV2/CommonLib/exceptions.h b/src/AspNetCoreModuleV2/CommonLib/exceptions.h index 31873bc743..8d9a50ded3 100644 --- a/src/AspNetCoreModuleV2/CommonLib/exceptions.h +++ b/src/AspNetCoreModuleV2/CommonLib/exceptions.h @@ -6,6 +6,7 @@ #include #include "debugutil.h" +#include "StringHelpers.h" #define LOCATION_INFO_ENABLED TRUE @@ -26,10 +27,11 @@ #endif #define OBSERVE_CAUGHT_EXCEPTION() CaughtExceptionHResult(LOCATION_INFO); +#define RETURN_CAUGHT_EXCEPTION() return CaughtExceptionHResult(LOCATION_INFO); + #define RETURN_HR(hr) do { HRESULT __hrRet = hr; if (FAILED(__hrRet)) { LogHResultFailed(LOCATION_INFO, __hrRet); } return __hrRet; } while (0, 0) #define RETURN_LAST_ERROR() do { return LogLastError(LOCATION_INFO); } while (0, 0) #define RETURN_IF_FAILED(hr) do { HRESULT __hrRet = hr; if (FAILED(__hrRet)) { LogHResultFailed(LOCATION_INFO, __hrRet); return __hrRet; }} while (0, 0) -#define RETURN_CAUGHT_EXCEPTION() return CaughtExceptionHResult(LOCATION_INFO); #define RETURN_LAST_ERROR_IF(condition) do { if (condition) { return LogLastError(LOCATION_INFO); }} while (0, 0) #define RETURN_LAST_ERROR_IF_NULL(ptr) do { if ((ptr) == nullptr) { return LogLastError(LOCATION_INFO); }} while (0, 0) @@ -39,6 +41,11 @@ #define FINISHED_LAST_ERROR_IF(condition) do { if (condition) { hr = LogLastError(LOCATION_INFO); goto Finished; }} while (0, 0) #define FINISHED_LAST_ERROR_IF_NULL(ptr) do { if ((ptr) == nullptr) { hr = LogLastError(LOCATION_INFO); goto Finished; }} while (0, 0) +#define THROW_LAST_ERROR() do { ThrowResultException(LogLastError(LOCATION_INFO)); } while (0, 0) +#define THROW_IF_FAILED(hr) do { HRESULT __hrRet = hr; if (FAILED(__hrRet)) { ThrowResultException(LOCATION_INFO, __hrRet); }} while (0, 0) +#define THROW_LAST_ERROR_IF(condition) do { if (condition) { ThrowResultException(LogLastError(LOCATION_INFO)); }} while (0, 0) +#define THROW_LAST_ERROR_IF_NULL(ptr) do { if ((ptr) == nullptr) { ThrowResultException(LogLastError(LOCATION_INFO)); }} while (0, 0) + #define THROW_IF_NULL_ALLOC(ptr) Throw_IfNullAlloc(ptr) #define CATCH_RETURN() catch (...) { RETURN_CAUGHT_EXCEPTION(); } @@ -48,6 +55,22 @@ #define SUCCEEDED_LOG(hr) SUCCEEDED(LOG_IF_FAILED(hr)) #define FAILED_LOG(hr) FAILED(LOG_IF_FAILED(hr)) + +class ResultException: public std::runtime_error +{ +public: + explicit ResultException(HRESULT hr, LOCATION_ARGUMENTS_ONLY) : + runtime_error(format("HRESULT 0x%x returned at " LOCATION_FORMAT, hr, LOCATION_CALL_ONLY)), + m_hr(hr) + { + } + + HRESULT GetResult() const { return m_hr; } + +private: + HRESULT m_hr; +}; + __declspec(noinline) inline VOID ReportUntypedException(LOCATION_ARGUMENTS_ONLY) { DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, LOCATION_FORMAT "Unhandled non-standard exception", LOCATION_CALL_ONLY); @@ -75,7 +98,7 @@ __declspec(noinline) inline VOID ReportException(LOCATION_ARGUMENTS std::exception& exception) { - DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, "Exception : %s caught at" LOCATION_FORMAT, exception.what(), LOCATION_CALL_ONLY); + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, "Exception '%s' caught at " LOCATION_FORMAT, exception.what(), LOCATION_CALL_ONLY); } __declspec(noinline) inline HRESULT LogHResultFailed(LOCATION_ARGUMENTS HRESULT hr) @@ -97,10 +120,10 @@ __declspec(noinline) inline HRESULT CaughtExceptionHResult(LOCATION_ARGUMENTS_ON { return E_OUTOFMEMORY; } - catch (std::system_error& exception) + catch (ResultException& exception) { ReportException(LOCATION_CALL exception); - return exception.code().value(); + return exception.GetResult(); } catch (std::exception& exception) { @@ -114,6 +137,13 @@ __declspec(noinline) inline HRESULT CaughtExceptionHResult(LOCATION_ARGUMENTS_ON } } +[[noreturn]] + __declspec(noinline) inline void ThrowResultException(LOCATION_ARGUMENTS HRESULT hr) +{ + DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, "Throwing ResultException for HRESULT 0x%x at " LOCATION_FORMAT, hr, LOCATION_CALL_ONLY); + throw ResultException(hr, LOCATION_CALL_ONLY); +} + template auto Throw_IfNullAlloc(PointerT pointer) { if (pointer == nullptr) diff --git a/src/AspNetCoreModuleV2/CommonLib/resources.h b/src/AspNetCoreModuleV2/CommonLib/resources.h index b2ea6f5b85..c9cca2b8b6 100644 --- a/src/AspNetCoreModuleV2/CommonLib/resources.h +++ b/src/AspNetCoreModuleV2/CommonLib/resources.h @@ -28,7 +28,7 @@ #define ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG L"Application '%s' has shutdown." #define ASPNETCORE_EVENT_DUPLICATED_INPROCESS_APP_MSG L"Only one inprocess application is allowed per IIS application pool. Please assign the application '%s' to a different IIS application pool." #define ASPNETCORE_EVENT_MIXED_HOSTING_MODEL_ERROR_MSG L"Mixed hosting model is not supported. Application '%s' configured with different hostingModel value '%d' other than the one of running application(s)." -#define ASPNETCORE_EVENT_UNKNOWN_HOSTING_MODEL_ERROR_MSG L"Unknown hosting model '%s'. Please specify either hostingModel=\"inprocess\" or hostingModel=\"outofprocess\" in the web.config file." +#define ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG L"Configuration load error. %s" #define ASPNETCORE_EVENT_ADD_APPLICATION_ERROR_MSG L"Failed to start application '%s', ErrorCode '0x%x'." #define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x'. Last 4KB characters of captured stdout and stderr logs:\r\n%s" #define ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG L"Application '%s' with physical root '%s' hit unexpected managed background thread exit, ErrorCode = '0x%x'. Please check the stderr logs for more information." diff --git a/src/AspNetCoreModuleV2/IISLib/ahutil.h b/src/AspNetCoreModuleV2/IISLib/ahutil.h index 3632650aa4..d17dc7be30 100644 --- a/src/AspNetCoreModuleV2/IISLib/ahutil.h +++ b/src/AspNetCoreModuleV2/IISLib/ahutil.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. See License.txt in the project root for license information. #pragma once +#include "stringu.h" #include HRESULT diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp new file mode 100644 index 0000000000..6b26b3dcc9 --- /dev/null +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.cpp @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "InProcessOptions.h" + +InProcessOptions::InProcessOptions(const ConfigurationSource &configurationSource) : + m_fStdoutLogEnabled(false), + m_fWindowsAuthEnabled(false), + m_fBasicAuthEnabled(false), + m_fAnonymousAuthEnabled(false), + m_dwStartupTimeLimitInMS(INFINITE), + m_dwShutdownTimeLimitInMS(INFINITE) +{ + auto const aspNetCoreSection = configurationSource.GetRequiredSection(CS_ASPNETCORE_SECTION); + m_strArguments = aspNetCoreSection->GetString(CS_ASPNETCORE_PROCESS_ARGUMENTS).value_or(CS_ASPNETCORE_PROCESS_ARGUMENTS_DEFAULT); + m_strProcessPath = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_PROCESS_EXE_PATH); + m_fStdoutLogEnabled = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_STDOUT_LOG_ENABLED); + m_struStdoutLogFile = aspNetCoreSection->GetRequiredString(CS_ASPNETCORE_STDOUT_LOG_FILE); + m_fDisableStartUpErrorPage = aspNetCoreSection->GetRequiredBool(CS_ASPNETCORE_DISABLE_START_UP_ERROR_PAGE); + m_environmentVariables = aspNetCoreSection->GetKeyValuePairs(CS_ASPNETCORE_ENVIRONMENT_VARIABLES); + + const auto basicAuthSection = configurationSource.GetSection(CS_BASIC_AUTHENTICATION_SECTION); + m_fBasicAuthEnabled = basicAuthSection && basicAuthSection->GetBool(CS_ENABLED).value_or(false); + + const auto windowsAuthSection = configurationSource.GetSection(CS_WINDOWS_AUTHENTICATION_SECTION); + m_fWindowsAuthEnabled = windowsAuthSection && windowsAuthSection->GetBool(CS_ENABLED).value_or(false); + + const auto anonAuthSection = configurationSource.GetSection(CS_ANONYMOUS_AUTHENTICATION_SECTION); + m_fAnonymousAuthEnabled = anonAuthSection && anonAuthSection->GetBool(CS_ENABLED).value_or(false); +} diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h new file mode 100644 index 0000000000..c0509b3003 --- /dev/null +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessOptions.h @@ -0,0 +1,95 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#pragma once + +#include +#include "ConfigurationSource.h" + +class InProcessOptions: NonCopyable +{ +public: + const std::wstring& + QueryProcessPath() const + { + return m_strProcessPath; + } + + const std::wstring& + QueryArguments() const + { + return m_strArguments; + } + + bool + QueryStdoutLogEnabled() const + { + return m_fStdoutLogEnabled; + } + + const std::wstring& + QueryStdoutLogFile() const + { + return m_struStdoutLogFile; + } + + bool + QueryDisableStartUpErrorPage() const + { + return m_fDisableStartUpErrorPage; + } + + bool + QueryWindowsAuthEnabled() const + { + return m_fWindowsAuthEnabled; + } + + bool + QueryBasicAuthEnabled() const + { + return m_fBasicAuthEnabled; + } + + bool + QueryAnonymousAuthEnabled() const + { + return m_fAnonymousAuthEnabled; + } + + DWORD + QueryStartupTimeLimitInMS() const + { + return m_dwStartupTimeLimitInMS; + } + + DWORD + QueryShutdownTimeLimitInMS() const + { + return m_dwShutdownTimeLimitInMS; + } + + const std::vector>& + QueryEnvironmentVariables() const + { + return m_environmentVariables; + } + + InProcessOptions(const ConfigurationSource &configurationSource); + +private: + std::wstring m_strArguments; + std::wstring m_strProcessPath; + std::wstring m_struStdoutLogFile; + bool m_fStdoutLogEnabled; + bool m_fDisableStartUpErrorPage; + bool m_fWindowsAuthEnabled; + bool m_fBasicAuthEnabled; + bool m_fAnonymousAuthEnabled; + DWORD m_dwStartupTimeLimitInMS; + DWORD m_dwShutdownTimeLimitInMS; + std::vector> m_environmentVariables; + +protected: + InProcessOptions() = default; +}; diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj index 08e3b32bd3..f295280e4e 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/InProcessRequestHandler.vcxproj @@ -228,6 +228,7 @@ + @@ -238,6 +239,7 @@ + Create diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp index 2a0369206b..90779ce6d8 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/dllmain.cpp @@ -13,6 +13,10 @@ #include "resources.h" #include "exceptions.h" #include "ShuttingDownApplication.h" +#include "InProcessOptions.h" +#include "EventLog.h" +#include "WebConfigConfigurationSource.h" +#include "ConfigurationLoadException.h" DECLARE_DEBUG_PRINT_OBJECT("aspnetcorev2_inprocess.dll"); @@ -106,13 +110,12 @@ CreateApplication( return S_OK; } - REQUESTHANDLER_CONFIG *pConfig = nullptr; - RETURN_IF_FAILED(REQUESTHANDLER_CONFIG::CreateRequestHandlerConfig(pServer, pHttpApplication, &pConfig)); - std::unique_ptr pRequestHandlerConfig(pConfig); + const WebConfigConfigurationSource configurationSource(pServer->GetAdminManager(), *pHttpApplication); + auto pConfig = std::make_unique(configurationSource); BOOL disableStartupPage = pConfig->QueryDisableStartUpErrorPage(); - auto pApplication = std::make_unique(*pServer, *pHttpApplication, std::move(pRequestHandlerConfig), pParameters, nParameters); + auto pApplication = std::make_unique(*pServer, *pHttpApplication, std::move(pConfig), pParameters, nParameters); // never create two inprocess applications in one process g_fInProcessApplicationCreated = true; @@ -130,6 +133,15 @@ CreateApplication( *ppApplication = pApplication.release(); } } + catch(ConfigurationLoadException &ex) + { + EventLog::Error( + ASPNETCORE_CONFIGURATION_LOAD_ERROR, + ASPNETCORE_CONFIGURATION_LOAD_ERROR_MSG, + ex.get_message().c_str()); + + RETURN_HR(E_FAIL); + } CATCH_RETURN(); return S_OK; diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index 182ee53148..0a07ccc616 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -19,7 +19,7 @@ IN_PROCESS_APPLICATION* IN_PROCESS_APPLICATION::s_Application = NULL; IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION( IHttpServer& pHttpServer, IHttpApplication& pApplication, - std::unique_ptr pConfig, + std::unique_ptr pConfig, APPLICATION_PARAMETER *pParameters, DWORD nParameters) : InProcessApplicationBase(pHttpServer, pApplication), @@ -108,14 +108,14 @@ Finished: EventLog::Warn( ASPNETCORE_EVENT_GRACEFUL_SHUTDOWN_FAILURE, ASPNETCORE_EVENT_APP_SHUTDOWN_FAILURE_MSG, - m_pConfig->QueryConfigPath()->QueryStr()); + QueryConfigPath().c_str()); } else { EventLog::Info( ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL, ASPNETCORE_EVENT_APP_SHUTDOWN_SUCCESSFUL_MSG, - m_pConfig->QueryConfigPath()->QueryStr()); + QueryConfigPath().c_str()); } InProcessApplicationBase::StopInternal(fServerInitiated); @@ -212,7 +212,7 @@ IN_PROCESS_APPLICATION::SetCallbackHandles( EventLog::Info( ASPNETCORE_EVENT_INPROCESS_START_SUCCESS, ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG, - m_pConfig->QueryApplicationPhysicalPath()->QueryStr()); + QueryApplicationPhysicalPath().c_str()); SetEvent(m_pInitalizeEvent); m_fInitialized = TRUE; } @@ -334,8 +334,8 @@ Finished: EventLog::Error( ASPNETCORE_EVENT_LOAD_CLR_FALIURE, ASPNETCORE_EVENT_LOAD_CLR_FALIURE_MSG, - m_pConfig->QueryApplicationPath()->QueryStr(), - m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), + QueryApplicationId().c_str(), + QueryApplicationPhysicalPath().c_str(), hr); } DereferenceApplication(); @@ -365,11 +365,21 @@ IN_PROCESS_APPLICATION::SetEnvironementVariablesOnWorkerProcess( VOID ) { - HRESULT hr = S_OK; + auto variables = m_pConfig->QueryEnvironmentVariables(); + auto inputTable = std::unique_ptr(new ENVIRONMENT_VAR_HASH()); + RETURN_IF_FAILED(inputTable->Initialize(37 /*prime*/)); + // Copy environment variables to old style hash table + for (auto & variable : variables) + { + auto pNewEntry = std::unique_ptr(new ENVIRONMENT_VAR_ENTRY()); + RETURN_IF_FAILED(pNewEntry->Initialize((variable.first + L"=").c_str(), variable.second.c_str())); + RETURN_IF_FAILED(inputTable->InsertRecord(pNewEntry.get())); + } + ENVIRONMENT_VAR_HASH* pHashTable = NULL; std::unique_ptr table; - RETURN_IF_FAILED(hr = ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable( - m_pConfig->QueryEnvironmentVariables(), + RETURN_IF_FAILED(ENVIRONMENT_VAR_HELPERS::InitEnvironmentVariablesTable( + inputTable.get(), m_pConfig->QueryWindowsAuthEnabled(), m_pConfig->QueryBasicAuthEnabled(), m_pConfig->QueryAnonymousAuthEnabled(), @@ -377,11 +387,13 @@ IN_PROCESS_APPLICATION::SetEnvironementVariablesOnWorkerProcess( table.reset(pHashTable); + HRESULT hr = S_OK; table->Apply(ENVIRONMENT_VAR_HELPERS::AppendEnvironmentVariables, &hr); RETURN_IF_FAILED(hr); table->Apply(ENVIRONMENT_VAR_HELPERS::SetEnvironmentVariables, &hr); RETURN_IF_FAILED(hr); + return S_OK; } @@ -422,9 +434,9 @@ IN_PROCESS_APPLICATION::ExecuteApplication( FINISHED_IF_FAILED(hr = HOSTFXR_OPTIONS::Create( m_struExeLocation.QueryStr(), - m_pConfig->QueryProcessPath()->QueryStr(), - m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), - m_pConfig->QueryArguments()->QueryStr(), + m_pConfig->QueryProcessPath().c_str(), + QueryApplicationPhysicalPath().c_str(), + m_pConfig->QueryArguments().c_str(), hostFxrOptions )); hostFxrOptions->GetArguments(hostfxrArgc, hostfxrArgv); @@ -438,8 +450,8 @@ IN_PROCESS_APPLICATION::ExecuteApplication( FINISHED_IF_FAILED(hr = LoggingHelpers::CreateLoggingProvider( m_pConfig->QueryStdoutLogEnabled(), !m_pHttpServer.IsCommandLineLaunch(), - m_pConfig->QueryStdoutLogFile()->QueryStr(), - m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), + m_pConfig->QueryStdoutLogFile().c_str(), + QueryApplicationPhysicalPath().c_str(), m_pLoggerProvider)); LOG_IF_FAILED(m_pLoggerProvider->Start()); @@ -502,8 +514,8 @@ IN_PROCESS_APPLICATION::LogErrorsOnMainExit( EventLog::Error( ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT, ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_STDOUT_MSG, - m_pConfig->QueryApplicationPath()->QueryStr(), - m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), + QueryApplicationId().c_str(), + QueryApplicationPhysicalPath().c_str(), hr, struStdMsg.QueryStr()); } @@ -513,8 +525,8 @@ IN_PROCESS_APPLICATION::LogErrorsOnMainExit( EventLog::Error( ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT, ASPNETCORE_EVENT_INPROCESS_THREAD_EXIT_MSG, - m_pConfig->QueryApplicationPath()->QueryStr(), - m_pConfig->QueryApplicationPhysicalPath()->QueryStr(), + QueryApplicationId().c_str(), + QueryApplicationPhysicalPath().c_str(), hr); } } diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h index 1d5cfec4c9..7183ad86b3 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h @@ -4,8 +4,8 @@ #pragma once #include "InProcessApplicationBase.h" -#include "requesthandler_config.h" #include "IOutputManager.h" +#include "InProcessOptions.h" class IN_PROCESS_HANDLER; typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_REQUEST_HANDLER) (IN_PROCESS_HANDLER* pInProcessHandler, void* pvRequestHandlerContext); @@ -18,7 +18,7 @@ public: IN_PROCESS_APPLICATION( IHttpServer& pHttpServer, IHttpApplication& pApplication, - std::unique_ptr pConfig, + std::unique_ptr pConfig, APPLICATION_PARAMETER *pParameters, DWORD nParameters); @@ -97,10 +97,10 @@ public: return m_struExeLocation.QueryStr(); } - REQUESTHANDLER_CONFIG* - QueryConfig() + const InProcessOptions& + QueryConfig() const { - return m_pConfig.get(); + return *m_pConfig.get(); } bool @@ -146,7 +146,7 @@ private: volatile BOOL m_fShutdownCalledFromManaged; BOOL m_fInitialized; MANAGED_APPLICATION_STATUS m_status; - std::unique_ptr m_pConfig; + std::unique_ptr m_pConfig; static IN_PROCESS_APPLICATION* s_Application; diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp index af0035be11..d8d28e5a28 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/managedexports.cpp @@ -187,11 +187,11 @@ http_get_application_properties( auto pConfiguration = pInProcessApplication->QueryConfig(); pIISCofigurationData->pInProcessApplication = pInProcessApplication; - pIISCofigurationData->pwzFullApplicationPath = SysAllocString(pConfiguration->QueryApplicationPhysicalPath()->QueryStr()); - pIISCofigurationData->pwzVirtualApplicationPath = SysAllocString(pConfiguration->QueryApplicationVirtualPath()->QueryStr()); - pIISCofigurationData->fWindowsAuthEnabled = pConfiguration->QueryWindowsAuthEnabled(); - pIISCofigurationData->fBasicAuthEnabled = pConfiguration->QueryBasicAuthEnabled(); - pIISCofigurationData->fAnonymousAuthEnable = pConfiguration->QueryAnonymousAuthEnabled(); + pIISCofigurationData->pwzFullApplicationPath = SysAllocString(pInProcessApplication->QueryApplicationPhysicalPath().c_str()); + pIISCofigurationData->pwzVirtualApplicationPath = SysAllocString(pInProcessApplication->QueryApplicationVirtualPath().c_str()); + pIISCofigurationData->fWindowsAuthEnabled = pConfiguration.QueryWindowsAuthEnabled(); + pIISCofigurationData->fBasicAuthEnabled = pConfiguration.QueryBasicAuthEnabled(); + pIISCofigurationData->fAnonymousAuthEnable = pConfiguration.QueryAnonymousAuthEnabled(); return S_OK; } diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h index f2ce7a5c20..b504a730fd 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.h @@ -12,7 +12,7 @@ class AppOfflineTrackingApplication: public APPLICATION { public: AppOfflineTrackingApplication(const IHttpApplication& application) - : APPLICATION(), + : APPLICATION(application), m_applicationPath(application.GetApplicationPhysicalPath()), m_fileWatcher(nullptr), m_fAppOfflineProcessed(false) diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h b/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h index 98bcd5ada4..c5f63f6fde 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/environmentvariablehash.h @@ -33,13 +33,13 @@ public: { HRESULT hr = S_OK; if (FAILED(hr = _strName.Copy(pszName)) || - FAILED(hr = _strValue.Copy(pszValue))) + FAILED(hr = _strValue.Copy(pszValue))) { } - return hr; + return hr; } - VOID + VOID Reference() const { InterlockedIncrement(&_cRefs); @@ -139,3 +139,11 @@ struct ENVIRONMENT_VAR_HASH_DELETER delete hashTable; } }; + +struct ENVIRONMENT_VAR_ENTRY_DELETER +{ + void operator ()(ENVIRONMENT_VAR_ENTRY* entry) const + { + entry->Dereference(); + } +}; diff --git a/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs b/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs index 413aa4c35f..ae8fde39ed 100644 --- a/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs +++ b/test/Common.FunctionalTests/Inprocess/EnvironmentVariableTests.cs @@ -8,11 +8,11 @@ using Xunit; namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { [Collection(IISTestSiteCollection.Name)] - public class EnvironmentVariableTests + public class EnvironmentVariableTests: FixtureLoggedTest { private readonly IISTestSiteFixture _fixture; - public EnvironmentVariableTests(IISTestSiteFixture fixture) + public EnvironmentVariableTests(IISTestSiteFixture fixture): base(fixture) { _fixture = fixture; } diff --git a/test/Common.FunctionalTests/Utilities/Helpers.cs b/test/Common.FunctionalTests/Utilities/Helpers.cs index d0b1ebc109..34fb88082a 100644 --- a/test/Common.FunctionalTests/Utilities/Helpers.cs +++ b/test/Common.FunctionalTests/Utilities/Helpers.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Xml.Linq; @@ -133,5 +134,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests await verificationAction(); } } + + public static IEnumerable ToTheoryData(this Dictionary dictionary) + { + return dictionary.Keys.Select(k => new[] { k }); + } } } diff --git a/test/CommonLibTests/fakeclasses.h b/test/CommonLibTests/fakeclasses.h index ba792017c4..a6f4a3e111 100644 --- a/test/CommonLibTests/fakeclasses.h +++ b/test/CommonLibTests/fakeclasses.h @@ -5,6 +5,7 @@ #include "gtest/gtest.h" #include "gmock/gmock.h" +#include "InProcessOptions.h" class MockProperty : public IAppHostProperty { @@ -176,20 +177,14 @@ public: MOCK_METHOD0(GetModuleContextContainer, IHttpModuleContextContainer* ()); }; -class MockRequestHandlerConfig : public REQUESTHANDLER_CONFIG +class MockInProcessOptions : public InProcessOptions { public: static - MockRequestHandlerConfig* + MockInProcessOptions* CreateConfig() { - return new MockRequestHandlerConfig; - } - -private: - MockRequestHandlerConfig() - { - + return new MockInProcessOptions; } }; diff --git a/test/CommonLibTests/inprocess_application_tests.cpp b/test/CommonLibTests/inprocess_application_tests.cpp index 00c7b01611..06be5493bd 100644 --- a/test/CommonLibTests/inprocess_application_tests.cpp +++ b/test/CommonLibTests/inprocess_application_tests.cpp @@ -24,7 +24,13 @@ namespace InprocessTests ON_CALL(application, GetApplicationPhysicalPath()) .WillByDefault(testing::Return(L"Some path")); - auto requestHandlerConfig = std::unique_ptr(MockRequestHandlerConfig::CreateConfig()); + ON_CALL(application, GetAppConfigPath()) + .WillByDefault(testing::Return(L"")); + + ON_CALL(application, GetApplicationId()) + .WillByDefault(testing::Return(L"")); + + auto requestHandlerConfig = std::unique_ptr(MockInProcessOptions::CreateConfig()); std::wstring exePath(L"hello"); @@ -36,4 +42,44 @@ namespace InprocessTests ASSERT_STREQ(app->QueryExeLocation(), L"hello"); } + + TEST(InProcessTest, GeneratesVirtualPath) + { + MockHttpServer server; + NiceMock application; + + ON_CALL(application, GetApplicationPhysicalPath()) + .WillByDefault(testing::Return(L"Some path")); + + ON_CALL(application, GetAppConfigPath()) + .WillByDefault(testing::Return(L"SECTION1/SECTION2/SECTION3/SECTION4/SECTION5")); + + ON_CALL(application, GetApplicationId()) + .WillByDefault(testing::Return(L"")); + + auto requestHandlerConfig = std::unique_ptr(MockInProcessOptions::CreateConfig()); + IN_PROCESS_APPLICATION *app = new IN_PROCESS_APPLICATION(server, application, std::move(requestHandlerConfig), nullptr, 0); + + ASSERT_STREQ(app->QueryApplicationVirtualPath().c_str(), L"/SECTION5"); + } + + TEST(InProcessTest, GeneratesVirtualPathForDefaultApp) + { + MockHttpServer server; + NiceMock application; + + ON_CALL(application, GetApplicationPhysicalPath()) + .WillByDefault(testing::Return(L"Some path")); + + ON_CALL(application, GetAppConfigPath()) + .WillByDefault(testing::Return(L"SECTION1/SECTION2/SECTION3/SECTION4")); + + ON_CALL(application, GetApplicationId()) + .WillByDefault(testing::Return(L"")); + + auto requestHandlerConfig = std::unique_ptr(MockInProcessOptions::CreateConfig()); + IN_PROCESS_APPLICATION *app = new IN_PROCESS_APPLICATION(server, application, std::move(requestHandlerConfig), nullptr, 0); + + ASSERT_STREQ(app->QueryApplicationVirtualPath().c_str(), L"/"); + } } diff --git a/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs b/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs index 73bea61017..55d399ef03 100644 --- a/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs +++ b/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs @@ -2,11 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Xml.Linq; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; @@ -185,5 +187,45 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Unknown hosting model 'bogus'. Please specify either hostingModel=\"inprocess\" or hostingModel=\"outofprocess\" in the web.config file."); } + + + private static Dictionary)> InvalidConfigTransformations = InitInvalidConfigTransformations(); + public static IEnumerable InvalidConfigTransformationsScenarios => InvalidConfigTransformations.ToTheoryData(); + + [ConditionalTheory] + [MemberData(nameof(InvalidConfigTransformationsScenarios))] + public async Task StartsWithWebConfigVariationsPortable(string scenario) + { + var (expectedError, action) = InvalidConfigTransformations[scenario]; + var iisDeploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + iisDeploymentParameters.WebConfigActionList.Add((element, _) => action(element)); + var deploymentResult = await DeployAsync(iisDeploymentParameters); + var result = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); + Assert.Equal(HttpStatusCode.InternalServerError, result.StatusCode); + + StopServer(); + EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, "Configuration load error. " + expectedError); + } + + public static Dictionary)> InitInvalidConfigTransformations() + { + var dictionary = new Dictionary)>(); + dictionary.Add("Empty process path", + ( + "Attribute 'processPath' is required.", + element => element.Descendants("aspNetCore").Single().SetAttributeValue("processPath", "") + )); + dictionary.Add("Unknown hostingModel", + ( + "Unknown hosting model 'asdf'.", + element => element.Descendants("aspNetCore").Single().SetAttributeValue("hostingModel", "asdf") + )); + dictionary.Add("environmentVariables with add", + ( + "Unable to get required configuration section 'system.webServer/aspNetCore'. Possible reason is web.config authoring error.", + element => element.Descendants("aspNetCore").Single().GetOrAdd("environmentVariables").GetOrAdd("add") + )); + return dictionary; + } } }