Recently, I’ve started working with IIS and have discovered that there is a lack of a (working) plugin for IIS 6.0 that introduces support for ~user style home directories, so I found a plugin called "mapper" that supposedly worked for IIS 3/4/5, and I tried recompiling it to work for IIS 6.0 — no dice, so the only solution was to make one which did work with IIS 6.0. This version is based upon a majority of the mapper code, however, some places have been improved for speed. Code size of the finished dll was reduced from 40kb of the original, to 10.5/11kb with the new version. Code is below (insert the standard stdafx.cpp etc).
1: #include "stdafx.h"
2: #include <windows.h>
3: #include <httpfilt.h>
4: #include <shlwapi.h>
5: #include <atlstr.h>
6: #pragma comment(lib, "shlwapi.lib")
7:
8: #define DEFAULT_BUFFER_SIZE 2048
9: #define MAX_BUF 2048
10: char szUsername[DEFAULT_BUFFER_SIZE];
11: char *pszUsername = szUsername;
12: const char szIndexFiles[8][13] = {"index.html","index.asp","index.aspx","default.htm","default.html","index.php","default.php","index.mspx"};
13: char pszDebugEnabled[1024];
14: char pszHomePath[1024];
15: DWORD g_dwAppendBufferLen = 0;
16:
17: bool DoInitialize(HMODULE hModule);
18: DWORD DoUrlRewriting(HTTP_FILTER_CONTEXT *pfc, HTTP_FILTER_URL_MAP *pMap);
19:
20: #ifdef _MANAGED
21: #pragma managed(push, off)
22: #endif
23:
24: #define DLC_TRACE(x) DlcReportEventA(EVENTLOG_INFORMATION_TYPE, x)
25:
26: void DlcReportEventA(WORD wType, LPCSTR pszMessage)
27: {
28: if (pszDebugEnabled == NULL || pszDebugEnabled != "True") {
29: return;
30: } // we don’t write anything.
31:
32: HANDLE hEventSource;
33: LPCTSTR lpszStrings[1];
34:
35: lpszStrings[0] = (LPCTSTR)pszMessage;
36:
37: /* Get a handle to use with ReportEvent(). */
38: hEventSource = RegisterEventSourceA(NULL, "IISHome");
39: if (hEventSource != NULL)
40: {
41: /* Write to event log. */
42: ReportEventA(hEventSource, wType, 0, 0, NULL, 1, 0, (LPCSTR*) &lpszStrings[0], NULL);
43: DeregisterEventSource(hEventSource);
44: }
45: }
46:
47: BOOL GetRegistryKey (char *lpszConfigItem, char *outBuffer)
48: {
49: char szRegistryResult[1024];
50: HKEY hKey;
51: BOOL bFoundItem = TRUE;
52: DWORD baseLen = 1024;
53: memset(szRegistryResult, 0, 1024);
54: char dbgInfo[DEFAULT_BUFFER_SIZE];
55: sprintf(dbgInfo, "DEBUG: Value Name: %s", lpszConfigItem);
56: DLC_TRACE(dbgInfo);
57:
58: if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\BrentP\\IISHome", 0, KEY_READ, &hKey) != ERROR_SUCCESS) {
59: bFoundItem = FALSE;
60: DLC_TRACE("DEBUG: RegOpenKeyEx(…) failed to open key.");
61: return bFoundItem;
62: }
63:
64: if (bFoundItem == TRUE) {
65: /* Get Registry Value */
66: DLC_TRACE("DEBUG: Key found - ready for RegQueryValueEx(…)");
67: if (RegQueryValueEx(hKey, lpszConfigItem, NULL, NULL, (LPBYTE)szRegistryResult, &baseLen) != ERROR_SUCCESS) {
68: bFoundItem = FALSE;
69: DLC_TRACE("DEBUG: RegQueryValueEx(…) failed. No Value found.");
70: } else {
71: DLC_TRACE("DEBUG: upto strcpy(outBuffer, szRegistryResult)");
72: strcpy(outBuffer, szRegistryResult);
73: }
74: }
75:
76: RegCloseKey(hKey);
77: DLC_TRACE("DEBUG: RegCloseKey(hKey)");
78:
79: return bFoundItem;
80: }
81:
82: // End Registry Stuff
83:
84: BOOL WINAPI __stdcall GetFilterVersion(HTTP_FILTER_VERSION *pVer)
85: {
86: /* Specify the types and order of notification */
87:
88: pVer->dwFlags = (SF_NOTIFY_ORDER_HIGH | SF_NOTIFY_NONSECURE_PORT |
89: SF_NOTIFY_SECURE_PORT | SF_NOTIFY_URL_MAP);
90:
91: pVer->dwFilterVersion = pVer->dwServerFilterVersion;
92: strcpy(pVer->lpszFilterDesc, (LPCSTR)"IISHome version 1.0");
93: DLC_TRACE("DEBUG: Started IISHome");
94:
95: // Now Init Registry Values
96: if (GetRegistryKey("HomePath", pszHomePath) == FALSE) {
97: strcpy(pszHomePath, "C:\\Users\\%s\\%s");
98: }
99: if (GetRegistryKey("DebugEnabled", pszDebugEnabled) == FALSE) {
100: strcpy(pszDebugEnabled, "False");
101: }
102:
103: char dbgInfo[DEFAULT_BUFFER_SIZE];
104: sprintf(dbgInfo, "DEBUG: HomePath: %s DebugEnabled: %s", pszHomePath, pszDebugEnabled);
105: DLC_TRACE(dbgInfo);
106:
107: return TRUE;
108: }
109:
110: BOOL FileExists(char *fileName) {
111: return GetFileAttributes(fileName) != INVALID_FILE_ATTRIBUTES;
112: }
113:
114: BOOL DirectoryExists(char *fileName) {
115: return GetFileAttributes(fileName) == FILE_ATTRIBUTE_DIRECTORY;
116: }
117:
118: DWORD DoUrlRewriting(HTTP_FILTER_CONTEXT *pfc, HTTP_FILTER_URL_MAP *pMap)
119: {
120: char *s;
121: //unsigned int i; //allow conversion between size_t and int.
122: size_t i; // size_t is unsigned int. converting to int might cause loss of data.
123: DWORD dwRet = SF_STATUS_REQ_NEXT_NOTIFICATION;
124: /* Heres what we do:
125: 1) Strip /~
126: 2) Strip remaining /….. to get username alone.
127: 3) Set Path to C:\Users\<username\
128: 4) If any of the index files exist, strcat it on.
129: 5) Now strcpy the physical path to the new path, and pass a return value.
130: */
131:
132: if (pfc == NULL || !pMap)
133: {
134: return dwRet;
135: }
136:
137: char szPhysicalPath[DEFAULT_BUFFER_SIZE];
138: DWORD cbBuf = DEFAULT_BUFFER_SIZE;
139:
140: if (!pszHomePath || pMap->pszURL[0] != ‘/’ || pMap->pszURL[1] != ‘~’) {
141: char dbgInfo[DEFAULT_BUFFER_SIZE];
142: sprintf(dbgInfo, "DEBUG: Could not match URL: %s", pMap->pszURL);
143: DLC_TRACE(dbgInfo);
144: return dwRet;
145: }
146:
147: // only match /~, not /bleh/~user.
148: char dbgInfo[DEFAULT_BUFFER_SIZE];
149: sprintf(dbgInfo, "DEBUG: URL Matched: %s", pMap->pszURL);
150: DLC_TRACE(dbgInfo);
151:
152: strcpy(szUsername, (pMap->pszURL)+2);
153: if (s = strchr(szUsername, ‘/’)) {
154: *s = (char)0; // replace it with a null character.
155: s++;
156: sprintf(szPhysicalPath, pszHomePath, szUsername, s);
157: char dbgInfo[DEFAULT_BUFFER_SIZE];
158: sprintf(dbgInfo, "DEBUG: Preliminary Physical Path: %s", szPhysicalPath);
159: DLC_TRACE(dbgInfo);
160: } else {
161: // we have nothing after the last /, so its http://server/~user/
162: sprintf(szPhysicalPath, pszHomePath, szUsername, "");
163: char dbgInfo[DEFAULT_BUFFER_SIZE];
164: sprintf(dbgInfo, "DEBUG: Preliminary Physical Path: %s", szPhysicalPath);
165: DLC_TRACE(dbgInfo);
166: }
167:
168: for (s = szPhysicalPath; *s; s++) {
169: if (*s == ‘/’) {
170: *s = ‘\\’;
171: } // replace / with a backslash
172: }
173:
174: DLC_TRACE("DEBUG: Replaced Slashes");
175:
176: // replace last character with a null value.
177: if (szPhysicalPath[i=strlen(szPhysicalPath)-1] == ‘\\‘) {
178: szPhysicalPath[i] = (char)0;
179: DLC_TRACE("DEBUG: Replaced Nulls");
180: }
181:
182: // FindFirstFile is inefficient, we use GetFileAttributes inside DirectoryExists.
183: if (szIndexFiles && DirectoryExists(szPhysicalPath)) {
184: DLC_TRACE("DEBUG: DirectoryExists(szPhysicalPath)=TRUE & szIndexFiles defined");
185: if (szPhysicalPath[i=strlen(szPhysicalPath)-1] != ‘\\’) {
186: strcat(szPhysicalPath, "\\"), i++;
187: }
188:
189: for (int j = 0; j < 9; j++) {
190: char szFName[2048];
191: strcpy(szFName, szPhysicalPath);
192: strcat(szFName, szIndexFiles[j]);
193:
194: if (FileExists(szFName)) {
195: strcpy(szPhysicalPath, szFName);
196: char dbgInfo[DEFAULT_BUFFER_SIZE];
197: sprintf(dbgInfo, "DEBUG: Found: %s", szFName);
198: DLC_TRACE(dbgInfo);
199: break;
200: }
201: }
202: }
203:
204: if( strlen(szPhysicalPath) < pMap->cbPathBuff) {
205: strcpy(pMap->pszPhysicalPath, szPhysicalPath);
206: char dbgInfo[DEFAULT_BUFFER_SIZE];
207: sprintf(dbgInfo, "DEBUG: Final Path: %s", pMap->pszPhysicalPath);
208: DLC_TRACE(dbgInfo);
209: } else {
210: DlcReportEventA(EVENTLOG_INFORMATION_TYPE, (LPCSTR)"DEBUG: Couldnt remap URL - Not enough Memory");
211: }
212:
213: return dwRet;
214: }
215:
216: DWORD WINAPI __stdcall HttpFilterProc(HTTP_FILTER_CONTEXT *pfc, DWORD NotificationType,
217: VOID *pvData)
218: {
219: DWORD status = SF_STATUS_REQ_NEXT_NOTIFICATION;
220: switch (NotificationType)
221: {
222:
223: case SF_NOTIFY_URL_MAP:
224: status = DoUrlRewriting(pfc, (HTTP_FILTER_URL_MAP*)pvData);
225: break;
226: }
227: return status;
228: }
229:
230: bool DoInitialize(HMODULE hModule)
231: {
232: return true;
233: }
234:
235: BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
236: {
237: switch (ul_reason_for_call)
238: {
239: case DLL_PROCESS_ATTACH:
240: {
241: if (!DoInitialize(hModule))
242: {
243: return FALSE;
244: }
245: break;
246: }
247: case DLL_PROCESS_DETACH:
248: {
249: g_dwAppendBufferLen = 0;
250: }
251: break;
252: }
253: return TRUE;
254: }
255: #ifdef _MANAGED
256: #pragma managed(pop)
257: #endif
Below is the registry file you’ll need to apply for it to work:
1: REGEDIT4
2:
3: [HKEY_LOCAL_MACHINE\SOFTWARE\BrentP\IISHome]
4: "HomePath"="C:\\Users\\%s\\%s"
5: "DebugEnabled"="False"
You’ll also need (because ISAPI requires some stuff to be exported) IISHome.def:
1: LIBRARY "IISHome"
2:
3: ;DESCRIPTION ‘IISHome Version 1.0′
4:
5: EXPORTS
6: HttpFilterProc
7: GetFilterVersion
Once you’ve shoved all that into a visual studio project, you should be able to compile it — providing you’ve linked in Advapi32.lib for registry access and set the project to use a Multibyte character set, and only to use standard windows libraries (this can be done in the VS Project configuration dialogs). Once you do this, the addon should compile with 0 warnings and errors, and then you can load it into IIS by right clicking on the website you want to load it in, then going to ISAPI Filters, then adding IISHome. You will see any errors generated by IISHome in the application section of the error log (eventvwr.msc in Start -> Run).
You WILL also need to install the runtimes for whatever version of visual studio you compiled, for example, VS2005 or VS2008. Enjoy.
This will probably be the last post for a while — again.