diff --git a/CLAUDE.md b/CLAUDE.md index f6de64e..de2a789 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -70,6 +70,7 @@ Report lands at `_build\windows-x86-64-coverage\coverage-report\index.html`. - **`src/`** — the main application (`CMakeLists.txt` defines target `ModernCppApp`, project `ModernCppApp`). Sources are glob-collected (`*.cpp`/`*.h` in the directory), so **new files under `src/` are picked up automatically at the next CMake configure** — no need to edit `src/CMakeLists.txt` when adding a file there. - `src/controller/`, `src/model/`, `src/view/` are currently empty placeholder directories (each holds only a `.gitignore`), suggesting an intended MVC split that hasn't been built out yet. - `src/utils/` is a separate static library target, `ModernCppUtils` (project `ModernCppUtils`), also glob-built and linked into both the app and the test binary. New utility files go here and are picked up the same way. + - `src/module/` is the IIS native module itself: target `IISNativeModule`, a `SHARED` library (DLL) that exports `RegisterModule` via `iis_native_module.def`. `iis::HttpModule` (`http_module.h/.cpp`) overrides every overridable `CHttpModule` method with skeleton bodies returning `RQ_NOTIFICATION_CONTINUE`; `iis::HttpModuleFactory` creates instances; `register_module.cpp` currently subscribes to no notifications (`SetRequestNotifications(factory, 0, 0)`) — add `RQ_*` flags there as methods gain real implementations. - **`test/`** — GTest-based tests, target/project `ModernCppTest`. Globs both `test/*.cpp` and `test/utils/*.cpp`, and links `ModernCppUtils`. `test/main.cpp` is a standard GTest `main()` running `RUN_ALL_TESTS()`. Mirror `src/utils/` layout under `test/utils/` for new utility tests (see `test/utils/smoke_test.cpp` as the existing example — one GTest `TEST` per behavior, wrapped in the same namespace as the code under test). - Test includes reference library headers as `` because `test/CMakeLists.txt` adds `test/../src` (i.e. `src/`) to the include path — so app/test code both include utils headers via the `utils/` prefix, not relative paths. - Because both `src/` and `src/utils/` (and `test/`) use `file(GLOB ...)`, CMake must be **re-run** (re-configure, not just rebuild) after adding or removing source files for them to be picked up — GLOB results are cached at configure time. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 75f03d3..836a9b4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 4.2) project(ModernCppApp C CXX) +add_subdirectory(module) add_subdirectory(utils) file(GLOB SRC_FILES diff --git a/src/module/CMakeLists.txt b/src/module/CMakeLists.txt new file mode 100644 index 0000000..f2f3629 --- /dev/null +++ b/src/module/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 4.2) + +project(IISNativeModule C CXX) + +file(GLOB MODULE_FILES + "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/*.h" +) + +# The .def file exports RegisterModule, the entry point IIS looks up +# with GetProcAddress when loading the module. +add_library(${PROJECT_NAME} SHARED + ${MODULE_FILES} + "${CMAKE_CURRENT_SOURCE_DIR}/iis_native_module.def" +) diff --git a/src/module/http_module.cpp b/src/module/http_module.cpp new file mode 100644 index 0000000..900de7e --- /dev/null +++ b/src/module/http_module.cpp @@ -0,0 +1,188 @@ +#include "http_module.h" + +namespace iis { + +auto HttpModule::OnBeginRequest( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnPostBeginRequest( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnAuthenticateRequest( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IAuthenticationProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnPostAuthenticateRequest( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnAuthorizeRequest( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnPostAuthorizeRequest( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnResolveRequestCache( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnPostResolveRequestCache( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnMapRequestHandler( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IMapHandlerProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnPostMapRequestHandler( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnAcquireRequestState( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnPostAcquireRequestState( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnPreExecuteRequestHandler( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnPostPreExecuteRequestHandler( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnExecuteRequestHandler( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnPostExecuteRequestHandler( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnReleaseRequestState( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnPostReleaseRequestState( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnUpdateRequestCache( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnPostUpdateRequestCache( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnLogRequest( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnPostLogRequest( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnEndRequest( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnPostEndRequest( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IHttpEventProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnSendResponse( + _In_ IHttpContext * /*pHttpContext*/, + _In_ ISendResponseProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnMapPath( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IMapPathProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnReadEntity( + _In_ IHttpContext * /*pHttpContext*/, + _In_ IReadEntityProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnCustomRequestNotification( + _In_ IHttpContext * /*pHttpContext*/, + _In_ ICustomNotificationProvider * /*pProvider*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::OnAsyncCompletion( + _In_ IHttpContext * /*pHttpContext*/, + _In_ DWORD /*dwNotification*/, + _In_ BOOL /*fPostNotification*/, + _In_ IHttpEventProvider * /*pProvider*/, + _In_ IHttpCompletionInfo * /*pCompletionInfo*/) -> REQUEST_NOTIFICATION_STATUS { + return RQ_NOTIFICATION_CONTINUE; +} + +auto HttpModule::Dispose() -> VOID { + // Matches the base-class behavior: IIS releases module instances + // through Dispose(), so an empty body here would leak them. + delete this; +} + +} diff --git a/src/module/http_module.h b/src/module/http_module.h new file mode 100644 index 0000000..58fae09 --- /dev/null +++ b/src/module/http_module.h @@ -0,0 +1,153 @@ +#ifndef HTTP_MODULE_H_ +#define HTTP_MODULE_H_ + +#define _WINSOCKAPI_ +#include +#include +#include + +namespace iis { + // Request-level HTTP module skeleton: overrides every overridable + // method of CHttpModule, with empty bodies to be filled in later. + class HttpModule : public CHttpModule { + public: + // RQ_BEGIN_REQUEST + auto OnBeginRequest( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + auto OnPostBeginRequest( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_AUTHENTICATE_REQUEST + auto OnAuthenticateRequest( + _In_ IHttpContext * pHttpContext, + _In_ IAuthenticationProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + auto OnPostAuthenticateRequest( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_AUTHORIZE_REQUEST + auto OnAuthorizeRequest( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + auto OnPostAuthorizeRequest( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_RESOLVE_REQUEST_CACHE + auto OnResolveRequestCache( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + auto OnPostResolveRequestCache( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_MAP_REQUEST_HANDLER + auto OnMapRequestHandler( + _In_ IHttpContext * pHttpContext, + _In_ IMapHandlerProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + auto OnPostMapRequestHandler( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_ACQUIRE_REQUEST_STATE + auto OnAcquireRequestState( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + auto OnPostAcquireRequestState( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_PRE_EXECUTE_REQUEST_HANDLER + auto OnPreExecuteRequestHandler( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + auto OnPostPreExecuteRequestHandler( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_EXECUTE_REQUEST_HANDLER + auto OnExecuteRequestHandler( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + auto OnPostExecuteRequestHandler( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_RELEASE_REQUEST_STATE + auto OnReleaseRequestState( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + auto OnPostReleaseRequestState( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_UPDATE_REQUEST_CACHE + auto OnUpdateRequestCache( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + auto OnPostUpdateRequestCache( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_LOG_REQUEST + auto OnLogRequest( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + auto OnPostLogRequest( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_END_REQUEST + auto OnEndRequest( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + auto OnPostEndRequest( + _In_ IHttpContext * pHttpContext, + _In_ IHttpEventProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_SEND_RESPONSE + auto OnSendResponse( + _In_ IHttpContext * pHttpContext, + _In_ ISendResponseProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_MAP_PATH + auto OnMapPath( + _In_ IHttpContext * pHttpContext, + _In_ IMapPathProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_READ_ENTITY + auto OnReadEntity( + _In_ IHttpContext * pHttpContext, + _In_ IReadEntityProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // RQ_CUSTOM_NOTIFICATION + auto OnCustomRequestNotification( + _In_ IHttpContext * pHttpContext, + _In_ ICustomNotificationProvider * pProvider) -> REQUEST_NOTIFICATION_STATUS override; + + // Completion + auto OnAsyncCompletion( + _In_ IHttpContext * pHttpContext, + _In_ DWORD dwNotification, + _In_ BOOL fPostNotification, + _In_ IHttpEventProvider * pProvider, + _In_ IHttpCompletionInfo * pCompletionInfo) -> REQUEST_NOTIFICATION_STATUS override; + + auto Dispose() -> VOID override; + }; +} +#endif // HTTP_MODULE_H_ diff --git a/src/module/http_module_factory.cpp b/src/module/http_module_factory.cpp new file mode 100644 index 0000000..5f9e861 --- /dev/null +++ b/src/module/http_module_factory.cpp @@ -0,0 +1,24 @@ +#include "http_module_factory.h" + +#include + +#include "http_module.h" + +namespace iis { + +auto HttpModuleFactory::GetHttpModule( + _Outptr_ CHttpModule ** ppModule, + _In_ IModuleAllocator * /*pAllocator*/) -> HRESULT { + auto * pModule = new (std::nothrow) HttpModule(); + if (pModule == nullptr) { + return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + } + *ppModule = pModule; + return S_OK; +} + +auto HttpModuleFactory::Terminate() -> VOID { + delete this; +} + +} diff --git a/src/module/http_module_factory.h b/src/module/http_module_factory.h new file mode 100644 index 0000000..8327fab --- /dev/null +++ b/src/module/http_module_factory.h @@ -0,0 +1,21 @@ +#ifndef HTTP_MODULE_FACTORY_H_ +#define HTTP_MODULE_FACTORY_H_ + +#define _WINSOCKAPI_ +#include +#include +#include + +namespace iis { + // Creates an HttpModule instance for each request IIS dispatches + // to this module. + class HttpModuleFactory : public IHttpModuleFactory { + public: + auto GetHttpModule( + _Outptr_ CHttpModule ** ppModule, + _In_ IModuleAllocator * pAllocator) -> HRESULT override; + + auto Terminate() -> VOID override; + }; +} +#endif // HTTP_MODULE_FACTORY_H_ diff --git a/src/module/iis_native_module.def b/src/module/iis_native_module.def new file mode 100644 index 0000000..44e4c25 --- /dev/null +++ b/src/module/iis_native_module.def @@ -0,0 +1,3 @@ +LIBRARY "IISNativeModule" +EXPORTS + RegisterModule diff --git a/src/module/register_module.cpp b/src/module/register_module.cpp new file mode 100644 index 0000000..d12c81c --- /dev/null +++ b/src/module/register_module.cpp @@ -0,0 +1,23 @@ +#define _WINSOCKAPI_ +#include +#include +#include + +#include + +#include "http_module_factory.h" + +// Entry point IIS calls when it loads the module DLL; exported via +// iis_native_module.def. +HRESULT WINAPI RegisterModule( + DWORD /*dwServerVersion*/, + IHttpModuleRegistrationInfo * pModuleInfo, + IHttpServer * /*pGlobalInfo*/) { + auto * pFactory = new (std::nothrow) iis::HttpModuleFactory(); + if (pFactory == nullptr) { + return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY); + } + // Skeleton: no request notifications subscribed yet. Add RQ_* flags + // here as the module methods gain real implementations. + return pModuleInfo->SetRequestNotifications(pFactory, 0, 0); +}