Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add CookieMunger

  • Loading branch information...
commit 8c906ce43592c838121e523a70a121f2ce1c8ef9 1 parent 5337a77
@georgevreilly authored
View
853 CookieMunger/ckymunge.cpp
@@ -0,0 +1,853 @@
+// CkyMunge: ISAPI filter for ASP session state for cookieless browsers.
+// An inglorious hack that munges URLs embedded in outgoing ASP pages,
+// embedding the ASPSESSIONID cookie in them. Also useful as an example of
+// an ISAPI filter that does something non-trivial with rawdata.
+
+
+#include "CkyPch.h"
+
+#include "debug.h"
+#include "isapiflt.h"
+#include "utils.h"
+#include "notify.h"
+#include "filter.h"
+#include "globals.h"
+#include "keyword.h"
+
+
+#define MAJOR_VERSION 1
+#define MINOR_VERSION 1
+
+// the munging mode. This will be read from the registry upon initialization
+int g_mungeMode = MungeMode_Off;
+
+// the session ID size is either 16 or 24 chars (depends on the server version)
+long g_SessionIDSize=-1;
+
+// the "cookie extra" is the string appended after "ASPSESSIONID" and before
+// "=". It is actually the process ID (with some extra mangling)
+static volatile long g_fCookieExtraSet = 0;
+static CRITICAL_SECTION g_csCookieExtra;
+CHAR g_szCookieExtra[ COOKIE_NAME_EXTRA_SIZE + 1 ];
+
+
+static LPCTSTR szRegKey = "Software\\Microsoft\\CkyMunge";
+static LPCTSTR szRegValue = "MungeMode";
+
+void SetSessionIDSize( HTTP_FILTER_CONTEXT* );
+void SetCookieExtra( LPCSTR );
+bool IsValidCookieExtra( LPCSTR );
+
+//
+// Optional entry/exit point for all DLLs
+//
+
+extern "C"
+BOOL
+WINAPI
+DllMain(
+ HINSTANCE /*hInstance*/,
+ DWORD dwReason,
+ LPVOID /*lpReserved*/)
+{
+ if (dwReason == DLL_PROCESS_ATTACH)
+ {
+ DEBUG_INIT();
+ TRACE("%s starting up\n", EVENT_MODULE);
+
+ // get the munge mode from the registry
+ HKEY hKey;
+ DWORD dwDisposition;
+ if ( ::RegCreateKeyEx(
+ HKEY_LOCAL_MACHINE,
+ szRegKey,
+ 0,
+ REG_NONE,
+ 0,
+ KEY_ALL_ACCESS,
+ NULL,
+ &hKey,
+ &dwDisposition ) != ERROR_SUCCESS )
+ {
+ TRACE( "Couldn't create/open key in registry\n" );
+ return FALSE;
+ }
+
+ DWORD dwType;
+ DWORD dwValue;
+ DWORD dwBufferSize = sizeof( dwValue );
+
+ if ( ::RegQueryValueEx(
+ hKey,
+ szRegValue,
+ NULL,
+ &dwType,
+ reinterpret_cast<BYTE*>(&dwValue),
+ &dwBufferSize ) != ERROR_SUCCESS )
+ {
+ TRACE( "No munge mode set, defaulting to smart mode\n" );
+ dwValue = MungeMode_Smart;
+
+ ::RegSetValueEx(
+ hKey,
+ szRegValue,
+ 0,
+ REG_DWORD,
+ reinterpret_cast<BYTE*>(&dwValue),
+ sizeof( DWORD ) );
+ }
+
+ g_mungeMode = static_cast<int>( dwValue );
+ TRACE("MungeMode = %d\n", g_mungeMode);
+
+ if (! InitUtils() || ! InitKeywords())
+ return FALSE;
+
+ g_szCookieExtra[0]='\0';
+ ::InitializeCriticalSection( &g_csCookieExtra );
+
+ }
+ else if (dwReason == DLL_PROCESS_DETACH)
+ {
+ ::DeleteCriticalSection( &g_csCookieExtra );
+
+ if (! TerminateUtils() || ! TerminateKeywords())
+ return FALSE;
+ TRACE("%s shutting down\n", EVENT_MODULE);
+ DEBUG_TERM();
+ }
+
+ return TRUE; // ok
+}
+
+
+
+//
+// Required initialization entrypoint for all ISAPI filters
+//
+
+BOOL
+WINAPI
+GetFilterVersion(
+ HTTP_FILTER_VERSION* pVer)
+{
+ EventReport("", "", EVENTLOG_INFORMATION_TYPE, CMFI_LOADED);
+
+ pVer->dwFilterVersion = HTTP_FILTER_REVISION;
+
+ // Specify the types and order of notification
+ pVer->dwFlags = ((SF_NOTIFY_SECURE_PORT | SF_NOTIFY_NONSECURE_PORT)
+ | SF_NOTIFY_ORDER_MEDIUM
+ | SF_NOTIFY_PREPROC_HEADERS
+ | SF_NOTIFY_URL_MAP
+ | SF_NOTIFY_SEND_RAW_DATA
+ | SF_NOTIFY_END_OF_REQUEST
+ );
+
+ // Set the filter description
+ wsprintf(pVer->lpszFilterDesc,
+ "Active Server Pages ISAPI filter for munging ASPSESSIONID "
+ "cookies, v%d.%02d",
+ MAJOR_VERSION, MINOR_VERSION);
+ TRACE("%s\n", pVer->lpszFilterDesc);
+
+ return TRUE;
+}
+
+
+
+//
+// Required dispatch entrypoint for all ISAPI filters
+//
+
+DWORD
+WINAPI
+HttpFilterProc(
+ HTTP_FILTER_CONTEXT* pfc,
+ DWORD dwNotificationType,
+ VOID* pvData)
+{
+ // first verify the session ID size
+ if ( g_SessionIDSize == -1 )
+ {
+ SetSessionIDSize( pfc );
+ }
+
+ if ( g_mungeMode == MungeMode_Off )
+ {
+ // just get out as quick as possible
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+ }
+
+ CNotification* pNotify = CNotification::Get(pfc);
+ if ( pNotify != NULL )
+ {
+ if ( pNotify->MungingOff() )
+ {
+ // we must've figured out that the browser is
+ // accepting cookies.
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+ }
+ }
+
+ switch (dwNotificationType)
+ {
+ case SF_NOTIFY_READ_RAW_DATA:
+ return OnReadRawData(pfc, (PHTTP_FILTER_RAW_DATA) pvData);
+
+ case SF_NOTIFY_PREPROC_HEADERS:
+ return OnPreprocHeaders(pfc, (PHTTP_FILTER_PREPROC_HEADERS) pvData);
+
+ case SF_NOTIFY_URL_MAP:
+ return OnUrlMap(pfc, (PHTTP_FILTER_URL_MAP) pvData);
+
+ case SF_NOTIFY_AUTHENTICATION:
+ return OnAuthentication(pfc, (PHTTP_FILTER_AUTHENT) pvData);
+
+ case SF_NOTIFY_ACCESS_DENIED:
+ return OnAccessDenied(pfc, (PHTTP_FILTER_ACCESS_DENIED) pvData);
+
+ case SF_NOTIFY_SEND_RAW_DATA:
+ return OnSendRawData(pfc, (PHTTP_FILTER_RAW_DATA) pvData);
+
+ case SF_NOTIFY_END_OF_REQUEST:
+ return OnEndOfRequest(pfc);
+
+ case SF_NOTIFY_LOG:
+ return OnLog(pfc, (PHTTP_FILTER_LOG) pvData);
+
+ case SF_NOTIFY_END_OF_NET_SESSION:
+ return OnEndOfNetSession(pfc);
+
+ default:
+ TRACE("Unknown notification: %x, context: %p, data: %p\n",
+ dwNotificationType, pfc, pvData);
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+ }
+}
+
+
+
+//
+// Read raw data from the client (browser)
+//
+
+DWORD
+OnReadRawData(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_RAW_DATA pRawData)
+{
+ TRACE("OnReadRawData(%p, %p)\n", pfc, pRawData);
+
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+}
+
+
+
+//
+// Preprocess the headers of the client's request before the server handles
+// the request
+//
+
+DWORD
+OnPreprocHeaders(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_PREPROC_HEADERS pHeaders)
+{
+ TRACE("OnPreprocHeaders(%p)\n", pfc, pHeaders);
+
+ CHAR szUrl[1024*5];
+ DWORD cbUrl = sizeof szUrl;
+
+ // Get the URL for this request
+ if (! pHeaders->GetHeader(pfc, "url", szUrl, &cbUrl))
+ {
+ TRACE("GetHeader(\"url\") failed\n");
+ EventReport("OnPreprocHeaders", "url",
+ EVENTLOG_ERROR_TYPE, CMFE_GETHEADER);
+ return SF_STATUS_REQ_ERROR;
+ }
+
+ CNotification* pNotify = NULL;
+ CHAR szSessionID[ MAX_SESSION_ID_SIZE + 1 ];
+ *szSessionID = '\0';
+
+ // Does the URL contain an embedded Session ID, such as
+ // /foo/bar.asp-ASP=PVZQGHUMEAYAHMFV ?
+
+ if (DecodeURL(szUrl, szSessionID))
+ {
+ pNotify = CNotification::SetSessionID(pfc, szSessionID);
+
+ // Set the URL to one without the Session ID
+ if (!pHeaders->SetHeader(pfc, "url", szUrl))
+ {
+ TRACE("Failed to set Url header!\n", szUrl);
+ EventReport("OnPreprocHeaders", szUrl,
+ EVENTLOG_ERROR_TYPE, CMFE_SETHEADER);
+ return SF_STATUS_REQ_ERROR;
+ }
+ }
+
+
+ // Look for a "Cookie:" header
+
+ CHAR szCookie[1024*4];
+ DWORD cbCookie = sizeof szCookie;
+ BOOL fCookie = pHeaders->GetHeader(pfc, "Cookie:", szCookie, &cbCookie);
+
+ if (fCookie && cbCookie > 0)
+ {
+ TRACE("Cookie: %s\n", szCookie);
+ // if the Cookie header includes ASPSESSIONID=<whatever>, use that
+ pNotify = CNotification::SetSessionID(pfc, szCookie);
+
+ // got a header with a cookie, so don't munge anymore
+ if ( pNotify )
+ {
+ TRACE( "Cookies accepted: stop munging\n" );
+ pNotify->m_fTestCookies = false;
+ }
+ }
+ else if (pNotify != NULL && *szSessionID != '\0')
+ {
+ // No cookie header, so we synthesize an ASPSESSIONID cookie header
+ // from the Session ID embedded in the URL
+ CHAR sz[ SZ_SESSION_ID_COOKIE_NAME_SIZE + MAX_SESSION_ID_SIZE
+ + 1 + COOKIE_NAME_EXTRA_SIZE + 1];
+ wsprintf(sz, "%s%s=%s", SZ_SESSION_ID_COOKIE_NAME,
+ g_szCookieExtra, szSessionID);
+ TRACE("About to AddHeader(\"%s\")\n", sz);
+
+ if (!pHeaders->AddHeader(pfc, "Cookie:", sz))
+ {
+ TRACE("Failed to AddHeader(\"Cookie:\", %s)\n", sz);
+ EventReport("OnPreprocHeaders", szUrl,
+ EVENTLOG_ERROR_TYPE, CMFE_ADDHEADER);
+ return SF_STATUS_REQ_ERROR;
+ }
+
+ // if we were testing cookies, we now know that the browser isn't
+ // sending any to us, so start munging
+ if ( pNotify->m_fTestCookies )
+ {
+ TRACE( "Cookies not accepted: continue munging\n" );
+ pNotify->m_fTestCookies = false;
+ pNotify->m_fEatCookies = true;
+ }
+ }
+
+ // Kill the "Connection: Keep-alive" header, so that browser will
+ // terminate session properly. If it's present, the server
+ // will send back a "Connection: Keep-Alive" header in response to
+ // requests for .htm pages (though not for .asp pages). The
+ // browser will think that there's more data to come, when
+ // there's not, and it will show an hourglass cursor and eventually
+ // put up an error messagebox.
+
+ BOOL fKeepAlive = pHeaders->SetHeader(pfc, "Connection:", "");
+
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+}
+
+
+
+//
+// We have mapped the URL to the corresponding physical file
+//
+
+DWORD
+OnUrlMap(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_URL_MAP pMapInfo)
+{
+ TRACE("OnUrlMap(%p, %p, %s)\n", pfc, pMapInfo, pMapInfo->pszURL);
+
+ // Can we safely ignore this URL based on its MIME type (e.g., image/gif)?
+ if (IsIgnorableUrl(pMapInfo->pszURL))
+ {
+ CNotification::Destroy(pfc);
+ TRACE("Ignoring <%s>\n", pMapInfo->pszURL);
+ }
+ else
+ {
+ CNotification* pNotify = CNotification::Get(pfc);
+
+ if (pNotify == NULL)
+ pNotify = CNotification::Create(pfc, NULL);
+
+ pNotify->m_nState = HN_SEEN_URL;
+ }
+
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+}
+
+
+
+//
+// Authenticating the user
+//
+
+DWORD
+OnAuthentication(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_AUTHENT pAuthent)
+{
+ TRACE("OnAuthentication(%p, %p)\n", pfc, pAuthent);
+
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+}
+
+
+
+//
+// Authentication failed
+//
+
+DWORD
+OnAccessDenied(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_ACCESS_DENIED pAccess)
+{
+ TRACE("OnAccessDenied(%p, %p)\n", pfc, pAccess);
+
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+}
+
+
+
+//
+// Do the hard work of munging the data. Note that we may be in one of
+// three interesting states: HN_SEEN_URL (initially), HN_IN_HEADER (looking
+// at the outgoing HTTP headers), and HN_IN_BODY (in the body of the
+// response). If the browser has cached this URL already (not the case
+// with .ASP pages, but typically the case with ordinary HTML pages or
+// images), no body is sent and we never move into the HN_IN_BODY state.
+//
+// The data will be sent in one or more packets, and we may need to buffer
+// portions of those packets, as tokens may be split across two or more
+// packets. The code assumes that an individual header will not be split
+// across packets.
+//
+
+DWORD
+OnSendRawData(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_RAW_DATA pRawData)
+{
+ TRACE("OnSendRawData(%p, %p)\n", pfc, pRawData);
+
+ CNotification* pNotify = CNotification::Get(pfc);
+
+ if (pNotify == NULL || pNotify->m_nState == HN_UNDEFINED)
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+
+ if ( pNotify->MungingOff() )
+ {
+ // either munging has been turned off, or we detected that
+ // munging isn't necessary
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+ }
+
+ LPSTR pszData = (LPSTR) pRawData->pvInData;
+ int iStart = 0; // offset of the beginning of the body data
+
+ // first time in OnSendRawData?
+ if (pNotify->m_nState == HN_SEEN_URL)
+ {
+ // Assume Content-Type header is in first packet
+ LPCSTR pszContentType = FindHeaderValue("Content-Type:", "text/html",
+ pRawData, 0);
+
+ if (pszContentType == NULL)
+ {
+ pNotify->m_nState = HN_UNDEFINED; // not HTML; ignore
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+ }
+ else
+ {
+ pNotify->m_nState = HN_IN_HEADER;
+ pNotify->m_ct = CT_TEXT_HTML;
+ }
+ }
+
+ if (pNotify->m_nState == HN_IN_HEADER)
+ {
+ static const char szSetCookie[] = "Set-Cookie:";
+ LPSTR pszCookie = FindString(szSetCookie, pRawData, 0);
+
+ // multiple Set-Cookie headers may be present in the header
+ while (pszCookie != NULL)
+ {
+ pNotify->m_nState = HN_IN_HEADER;
+
+ // Header lines are supposed to be terminated by "\r\n"
+ LPSTR pszEoln = strchr(pszCookie, '\r');
+
+ if (pszEoln != NULL)
+ {
+ *pszEoln = '\0';
+ TRACE("%s\n", pszCookie);
+
+ // ASP only sends the ASPSESSIONID cookie if a session ID
+ // hasn't already been picked, which happens either when
+ // Session_OnStart is executed (if it and global.asa are
+ // present) or when the Session object is first modified
+ // by user code.
+ LPCSTR szCookieName;
+ if ( ( szCookieName =
+ strstr(pszCookie, SZ_SESSION_ID_COOKIE_NAME) ) != NULL)
+ {
+ // need to figure out what's tacked onto the cookie name.
+ if ( !g_fCookieExtraSet )
+ {
+ SetCookieExtra( szCookieName );
+ }
+
+ // we know this cookie contains ASPSESSIONID, but is it
+ // ours? (the cookie extra parts must match)
+ if ( strstr( szCookieName, g_szCookieExtra ) != NULL )
+ {
+ VERIFY(CNotification::SetSessionID(pfc, pszCookie)
+ == pNotify);
+ TRACE("Update: %s\n", pNotify->SessionID());
+
+ *pszEoln = '\r'; // restore
+
+ // Eat outgoing "Set-Cookie: ASPSESSIONIDXXXXXXXX=..."
+ // header? Benign for cookie-less browsers; will keep
+ // cookie-warning browsers quiet.
+ if (pNotify->m_fEatCookies)
+ {
+ TRACE("Deleting cookie\n");
+ DeleteLine(szSetCookie, pRawData, pszCookie);
+ }
+ }
+ }
+ else
+ {
+ *pszEoln = '\r'; // restore
+ }
+
+ pszCookie =
+ FindString(szSetCookie, pRawData,
+ pszEoln - static_cast<LPCSTR>(pRawData->pvInData));
+ }
+ else
+ {
+ pszCookie = NULL; // terminate loop
+ }
+ }
+
+ // If a Content-Length header is present, we need to destroy it
+ // because there is no way we can guess a priori how much longer
+ // the data will become. If we don't destroy it, the browser will
+ // believe the header and become very confused.
+
+ static const char szContentLength[] = "Content-Length:";
+ LPSTR pszCL = FindString(szContentLength, pRawData, 0);
+
+ if (pszCL != NULL)
+ {
+ char szFmt[ARRAYSIZE(szContentLength) + 10];
+ sprintf(szFmt, "%s %%u", szContentLength);
+ sscanf(pszCL, szFmt, &pNotify->m_cchContentLength);
+ TRACE("%s is %u\n", szContentLength, pNotify->m_cchContentLength);
+ DeleteLine(szContentLength, pRawData, pszCL);
+ }
+
+ // Is the end-of-headers marker present?
+ LPCSTR pszEndHeaderBlock = FindString("\n\r\n", pRawData, 0);
+
+ if (pszEndHeaderBlock != NULL)
+ {
+ pNotify->m_nState = HN_IN_BODY;
+ iStart = pszEndHeaderBlock + 3 - pszData;
+ }
+ }
+
+ // We're in the body. Let's do some real work.
+
+ if (pNotify->m_nState == HN_IN_BODY)
+ {
+ LPSTR pszBuf = pszData;
+ int iEnd;
+
+ // Have we got a partial line from the last packet? If so, it
+ // means that the last character in the buffer on that packet was
+ // not a token boundary character, such as '\n' or '>'. Prepend
+ // that data to the current batch.
+
+ if (pNotify->m_pbPartialToken != NULL)
+ {
+ ASSERT(iStart == 0);
+ ASSERT(pNotify->m_cbPartialToken > 0);
+
+ pNotify->AppendToken(pfc, pszBuf, pRawData->cbInData);
+
+ pRawData->pvInData = pszBuf = (LPSTR) pNotify->m_pbPartialToken;
+ pRawData->cbInData = pRawData->cbInBuffer
+ = pNotify->m_cbPartialToken;
+
+ iEnd = g_trie.EndOfBuffer(pRawData, iStart);
+
+ if (iEnd < 0)
+ {
+ // Don't let IIS send any data on this pass
+ pRawData->pvInData = NULL;
+ pRawData->cbInData = pRawData->cbInBuffer = 0;
+
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+ }
+ else
+ {
+ // Have a complete token
+ pNotify->m_pbPartialToken = pNotify->m_pbTokenBuffer = NULL;
+ pNotify->m_cbPartialToken = pNotify->m_cbTokenBuffer = 0;
+ }
+ }
+
+ ASSERT(pNotify->m_pbPartialToken == NULL
+ && pNotify->m_cbPartialToken == 0);
+
+ // Is the last token in the block incomplete?
+ iEnd = g_trie.EndOfBuffer(pRawData, iStart);
+
+ if (iEnd != pRawData->cbInData)
+ {
+ LPSTR pszBoln = (iEnd < 0) ? pszBuf + iStart : pszBuf + iEnd ;
+
+ TRACE("Partial Token: ");
+ pNotify->AppendToken(pfc, pszBoln,
+ (pszBuf + pRawData->cbInData) - pszBoln);
+ pRawData->cbInData -= pNotify->m_cbPartialToken;
+ }
+
+ pNotify->m_cchContentLength -= pRawData->cbInData;
+
+ // Filter whatever is left
+ const int nExtra = Filter(pfc, pRawData, pszBuf,
+ pRawData->cbInData, iStart,
+ pNotify->m_szSessionID);
+ }
+
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+}
+
+
+
+//
+// The transaction is over. Pump out any remaining data before the
+// connection closes.
+//
+
+DWORD
+OnEndOfRequest(
+ PHTTP_FILTER_CONTEXT pfc)
+{
+ TRACE("OnEndOfRequest(%p)\n", pfc);
+
+ CNotification* pNotify = CNotification::Get(pfc);
+
+ if (pNotify != NULL && pNotify->m_pbPartialToken != NULL)
+ {
+ // append a '\n', which is guaranteed to be a token boundary char
+ pNotify->m_pbPartialToken[pNotify->m_cbPartialToken] = '\n';
+#ifdef _DEBUG
+ pNotify->m_pbPartialToken[pNotify->m_cbPartialToken + 1] = '\0';
+#endif
+ LPBYTE pbTemp = pNotify->m_pbPartialToken;
+ DWORD cb = pNotify->m_cbPartialToken + 1;
+
+ // Note: WriteClient ends up calling OnSendRawData. Destroy the
+ // partial token before it's called.
+ pNotify->m_pbPartialToken = NULL;
+ pNotify->m_cbPartialToken = 0;
+
+ if (!pfc->WriteClient(pfc, pbTemp, &cb, 0))
+ TRACE("WriteClient failed, err %x.\n", GetLastError());
+ }
+
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+}
+
+
+
+//
+// Log the details of the transaction
+//
+
+DWORD
+OnLog(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_LOG pLog)
+{
+ TRACE("OnLog(%p, %p)\n", pfc, pLog);
+
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+}
+
+
+
+//
+// The HTTP session (transaction) is over and the connection has been closed.
+//
+
+DWORD
+OnEndOfNetSession(
+ PHTTP_FILTER_CONTEXT pfc)
+{
+ TRACE("OnEndOfNetSession(%p)\n", pfc);
+
+ CNotification::Destroy(pfc);
+
+ return SF_STATUS_REQ_NEXT_NOTIFICATION;
+}
+
+
+
+void
+SetSessionIDSize(
+ PHTTP_FILTER_CONTEXT pfc )
+{
+ static const char szVersion4B2[] = "Microsoft-IIS/4.0 Beta 2";
+ static const char szVersion4[] = "Microsoft-IIS/4.0";
+ const long version3Size = 16;
+ const long version4Size = MAX_SESSION_ID_SIZE;
+ const long version4B2Size = 16;
+
+ long size = version3Size;
+
+ DWORD dwBufferSize = 0;
+ pfc->GetServerVariable( pfc, "SERVER_SOFTWARE", NULL, &dwBufferSize );
+ if ( ::GetLastError() == ERROR_INSUFFICIENT_BUFFER )
+ {
+ LPSTR str = (LPSTR)_alloca( ++dwBufferSize );
+ if (pfc->GetServerVariable(pfc, "SERVER_SOFTWARE", str, &dwBufferSize))
+ {
+ TRACE( "Server Software: %s\n", str );
+ if ( strcmp( szVersion4B2, str ) == 0 )
+ {
+ TRACE( "Using version 4 beta 2 session ID size (%d bytes)\n",
+ version4B2Size );
+ size = version4B2Size;
+ }
+ else if ( strncmp( szVersion4, str, strlen( szVersion4 ) ) == 0 )
+ {
+ TRACE( "Using version 4 session ID size (%d bytes)\n",
+ version4Size );
+ size = version4Size;
+ }
+ else
+ {
+ TRACE( "Using version 3 session ID size (%d bytes)\n",
+ version3Size );
+ }
+ }
+ else
+ {
+ TRACE( "Failed to get server variable, error: %d\n",
+ ::GetLastError() );
+ }
+ }
+ else
+ {
+ TRACE( "Failed to get server variable(SERVER_SOFTWARE), error: %d\n",
+ ::GetLastError() );
+ }
+ ::InterlockedExchange( &g_SessionIDSize, size );
+}
+
+
+
+void
+SetCookieExtra(
+ LPCSTR szCookieName )
+{
+ ::EnterCriticalSection( &g_csCookieExtra );
+ // need to check again in case the cookie extra was set
+ // while we were waiting on the critical section
+ if ( !g_fCookieExtraSet )
+ {
+ szCookieName += SZ_SESSION_ID_COOKIE_NAME_SIZE;
+ if ( *szCookieName != '=' )
+ {
+ CHAR szExtra[ COOKIE_NAME_EXTRA_SIZE + 1 ];
+ strncpy( szExtra, szCookieName, COOKIE_NAME_EXTRA_SIZE );
+ szExtra[ COOKIE_NAME_EXTRA_SIZE ] = 0;
+
+ if ( IsValidCookieExtra( szExtra ) )
+ {
+ // copy the cookie name extra
+ strcpy( g_szCookieExtra, szExtra );
+ TRACE("SetCookieExtra(%s)\n", g_szCookieExtra);
+ ::InterlockedExchange( (long*)&g_fCookieExtraSet, 1 );
+ }
+ else
+ {
+ TRACE( "Cookie extra validation failed\n" );
+ }
+ }
+ }
+ ::LeaveCriticalSection( &g_csCookieExtra );
+}
+
+
+
+// Check to see if this `extra part' is a valid value for this server.
+// The extra part is derived from the process ID and then randomized
+// slightly. So we can tell if the extra is reasonable based on the
+// process ID.
+bool
+IsValidCookieExtra(
+ LPCSTR szExtra )
+{
+ bool rc = true;
+
+ CHAR szProcessID[ COOKIE_NAME_EXTRA_SIZE + 1 ];
+
+ // Process ID
+ wsprintf(szProcessID, "%08X", GetCurrentProcessId());
+
+ // check based on how we know the process ID is munged to the cookie extra
+ static const char *pszDigitsToLetters[2] = {"GHIJKLMNOP","QRSTUVWXYZ"};
+
+ for (int i = 0; i < COOKIE_NAME_EXTRA_SIZE; i++)
+ {
+ char cp = szProcessID[i];
+ char ce = szExtra[i];
+ if ( ( cp >= '0' ) && ( cp <= '9' ) )
+ {
+ int ndx = cp - '0';
+ if ( ( pszDigitsToLetters[0][ndx] == ce )
+ || ( pszDigitsToLetters[1][ndx] == ce ) )
+ {
+ // okay, keep checking
+ }
+ else
+ {
+ // no good
+ rc = false;
+ i = COOKIE_NAME_EXTRA_SIZE;
+ }
+ }
+ else
+ {
+ if ( cp == ce )
+ {
+ // okay, keep checking
+ }
+ else
+ {
+ // no good
+ rc = false;
+ i = COOKIE_NAME_EXTRA_SIZE;
+ }
+ }
+ }
+#ifdef _DEBUG
+ if (!rc)
+ TRACE("`%s' is not a valid extra\n", szExtra);
+#endif
+ return rc;
+}
View
6 CookieMunger/ckymunge.def
@@ -0,0 +1,6 @@
+LIBRARY CkyMunge
+
+EXPORTS
+ DllMain
+ HttpFilterProc
+ GetFilterVersion
View
177 CookieMunger/ckymunge.dsp
@@ -0,0 +1,177 @@
+# Microsoft Developer Studio Project File - Name="CkyMunge" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 5.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102
+
+CFG=CkyMunge - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "CkyMunge.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "CkyMunge.mak" CFG="CkyMunge - Win32 Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "CkyMunge - Win32 Release" (based on\
+ "Win32 (x86) Dynamic-Link Library")
+!MESSAGE "CkyMunge - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library")
+!MESSAGE
+
+# Begin Project
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+MTL=midl.exe
+RSC=rc.exe
+
+!IF "$(CFG)" == "CkyMunge - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "Release"
+# PROP Intermediate_Dir "Release"
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D _WIN32_WINNT=0x400 /YX /FD /c
+# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32
+# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o NUL /win32
+# ADD BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386
+
+!ELSEIF "$(CFG)" == "CkyMunge - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "Debug"
+# PROP Intermediate_Dir "Debug"
+# PROP Ignore_Export_Lib 0
+# PROP Target_Dir ""
+# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c
+# ADD CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "_DEBUG" /D _WIN32_WINNT=0x400 /D "WIN32" /D "_WINDOWS" /Yc"ckypch.h" /FD /c
+# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32
+# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o NUL /win32
+# ADD BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept
+
+!ENDIF
+
+# Begin Target
+
+# Name "CkyMunge - Win32 Release"
+# Name "CkyMunge - Win32 Debug"
+# Begin Source File
+
+SOURCE=.\CkyMunge.cpp
+# ADD CPP /Yc"CkyPch.h"
+# End Source File
+# Begin Source File
+
+SOURCE=.\CkyMunge.def
+# End Source File
+# Begin Source File
+
+SOURCE=.\Debug.cpp
+# ADD CPP /Yu"CkyPch.h"
+# End Source File
+# Begin Source File
+
+SOURCE=.\Filter.cpp
+# ADD CPP /Yu"CkyPch.h"
+# End Source File
+# Begin Source File
+
+SOURCE=.\Keyword.cpp
+# ADD CPP /Yu"CkyPch.h"
+# End Source File
+# Begin Source File
+
+SOURCE=.\Messages.mc
+
+!IF "$(CFG)" == "CkyMunge - Win32 Release"
+
+# Begin Custom Build
+InputPath=.\Messages.mc
+InputName=Messages
+
+BuildCmds= \
+ mc $(InputName).mc
+
+"$(InputName).rc" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"
+ $(BuildCmds)
+
+"$(InputName).h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"
+ $(BuildCmds)
+# End Custom Build
+
+!ELSEIF "$(CFG)" == "CkyMunge - Win32 Debug"
+
+# Begin Custom Build
+InputPath=.\Messages.mc
+InputName=Messages
+
+BuildCmds= \
+ mc $(InputName).mc
+
+"$(InputName).rc" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"
+ $(BuildCmds)
+
+"$(InputName).h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"
+ $(BuildCmds)
+# End Custom Build
+
+!ENDIF
+
+# End Source File
+# Begin Source File
+
+SOURCE=.\Messages.rc
+# PROP Exclude_From_Build 1
+# End Source File
+# Begin Source File
+
+SOURCE=.\Notify.cpp
+# ADD CPP /Yu"CkyPch.h"
+# End Source File
+# Begin Source File
+
+SOURCE=.\Token.cpp
+# ADD CPP /Yu"CkyPch.h"
+# End Source File
+# Begin Source File
+
+SOURCE=.\Utils.cpp
+# ADD CPP /Yu"CkyPch.h"
+# End Source File
+# Begin Source File
+
+SOURCE=.\version.rc
+# End Source File
+# End Target
+# End Project
View
29 CookieMunger/ckymunge.dsw
@@ -0,0 +1,29 @@
+Microsoft Developer Studio Workspace File, Format Version 5.00
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
+
+###############################################################################
+
+Project: "CkyMunge"=.\CkyMunge.dsp - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+}}}
+
+###############################################################################
+
+Global:
+
+Package=<5>
+{{{
+}}}
+
+Package=<3>
+{{{
+}}}
+
+###############################################################################
+
View
38 CookieMunger/ckypch.h
@@ -0,0 +1,38 @@
+// force strict type checking
+#define STRICT
+
+// disable rarely-used sections of Windows
+#define WIN32_LEAN_AND_MEAN
+#define NOMCX
+#define NOIME
+#define NOSOUND
+#define NOCOMM
+#define NOKANJI
+#define NORPC
+#define NOPROXYSTUB
+#define NOIMAGE
+#define NOTAPE
+
+// Needed by AspAssertHandler in debug.cpp
+// #define _WIN32_WINNT 0x400
+
+#include <windows.h>
+#include <httpfilt.h>
+
+#include <crtdbg.h>
+#include <tchar.h>
+#include <limits.h>
+#include <malloc.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+// disable warning messages about truncating extremly long identifiers
+#pragma warning (disable : 4786)
+#include <string>
+#include <set>
+#include <map>
+#include <vector>
+#include <stack>
+
+
View
156 CookieMunger/debug.cpp
@@ -0,0 +1,156 @@
+#include "CkyPch.h"
+#include "debug.h"
+
+
+#ifdef _DEBUG
+
+void __cdecl
+Trace(
+ LPCTSTR ptszFormat,
+ ...)
+{
+ TCHAR tszBuff[2048];
+ va_list args;
+
+ va_start(args, ptszFormat);
+ _vsntprintf(tszBuff, 2048, ptszFormat, args);
+ // _vsntprintf doesn't always NUL-terminate the buffer
+ tszBuff[2048-1] = TEXT('\0');
+ va_end(args);
+
+ OutputDebugString(tszBuff);
+}
+
+
+#define ACTIVE_SERVER_PAGES
+
+# if defined(_MSC_VER) && (_MSC_VER >= 1000)
+
+
+# ifdef ACTIVE_SERVER_PAGES
+
+// The default assertion mechanism set up by Visual C++ 4 will not
+// work with Active Server Pages because it's running inside a service
+// and there is no desktop to interact with.
+
+// Note: for this to work properly, #define _WIN32_WINNT 0x400 before
+// including <winuser.h> or MB_SERVICE_NOTIFICATION won't be #define'd.
+
+int
+AspAssertHandler(
+ int nReportType,
+ char* pszErrorText,
+ int* pnReturn)
+{
+ const char szInfo[] = " (Press ABORT to terminate IIS,"
+ " RETRY to debug this failure,"
+ " or IGNORE to continue.)";
+ char* pszMessageTitle = NULL;
+
+ // These flags enable message boxes to show up on the user's console
+ switch (nReportType)
+ {
+ case _CRT_WARN:
+ pszMessageTitle = "Warning";
+ break;
+ case _CRT_ERROR:
+ pszMessageTitle = "Fatal Error";
+ break;
+ case _CRT_ASSERT:
+ pszMessageTitle = "Assertion Failed";
+ break;
+ }
+
+ char* pszMessageText =
+ static_cast<char*>(_alloca(strlen(pszErrorText) + strlen(szInfo) + 1));
+
+ strcpy(pszMessageText, pszErrorText);
+ strcat(pszMessageText, szInfo);
+
+ const int n = MessageBoxA(NULL, pszMessageText, pszMessageTitle,
+ (MB_SERVICE_NOTIFICATION | MB_TOPMOST
+ | MB_ABORTRETRYIGNORE | MB_ICONEXCLAMATION));
+
+ if (n == IDABORT)
+ {
+ exit(1);
+ }
+ else if (n == IDRETRY)
+ {
+ *pnReturn = 1; // tell _CrtDbgReport to start the debugger
+ return TRUE; // tell _CrtDbgReport to run
+ }
+
+ *pnReturn = 0; // nothing for _CrtDbgReport to do
+
+ return FALSE;
+}
+
+# endif // ACTIVE_SERVER_PAGES
+# endif // _MSC_VER >= 1000
+
+
+
+void
+DebugInit()
+{
+# if defined(_MSC_VER) && (_MSC_VER >= 1000)
+# ifdef ACTIVE_SERVER_PAGES
+ // If we end up in _CrtDbgReport, don't put up a message box
+ _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG);
+ _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);
+ _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG);
+
+ // Use AspAssertHandler to put up a message box instead
+ _CrtSetReportHook(AspAssertHandler);
+# endif // ACTIVE_SERVER_PAGES
+
+ // Enable debug heap allocations & check for memory leaks at program exit
+ // The memory leak check will not be performed if inetinfo.exe is
+ // run directly under a debugger, only if it is run as a service.
+ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF
+ | _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG));
+# endif // _MSC_VER >= 1000
+}
+
+
+
+void
+DebugTerm()
+{
+# if defined(_MSC_VER) && (_MSC_VER >= 1000)
+# ifdef ACTIVE_SERVER_PAGES
+ // Turn off AspAssertHandler, so that we don't get numerous message boxes
+ // if there are memory leaks on shutdown
+ _CrtSetReportHook(NULL);
+# endif // ACTIVE_SERVER_PAGES
+# endif // _MSC_VER >= 1000
+}
+
+#endif //_DEBUG
+
+
+
+BOOL
+IsValidString(
+ LPCTSTR ptsz,
+ int nLength /* =-1 */)
+{
+ if (ptsz == NULL)
+ return FALSE;
+
+ return !IsBadStringPtr(ptsz, nLength);
+}
+
+
+
+BOOL
+IsValidAddress(
+ LPCVOID pv,
+ UINT nBytes,
+ BOOL fReadWrite /* =TRUE */)
+{
+ return (pv != NULL
+ && !IsBadReadPtr(pv, nBytes)
+ && (!fReadWrite || !IsBadWritePtr((LPVOID) pv, nBytes)));
+}
View
115 CookieMunger/debug.h
@@ -0,0 +1,115 @@
+/*
+ * Some simple debugging macros that look and behave a lot like their
+ * namesakes in MFC. These macros should work in both C and C++ and
+ * do something useful with almost any Win32 compiler.
+ *
+ * George V. Reilly <georgere@microsoft.com>
+ */
+
+#ifndef __DEBUG_H__
+#define __DEBUG_H__
+
+#include <tchar.h>
+
+#ifdef _DEBUG
+
+# if defined(_MSC_VER) && (_MSC_VER >= 1000)
+ /* Use the new debugging tools in Visual C++ 4.x */
+# include <crtdbg.h>
+ /* _ASSERTE will give a more meaningful message, but the string takes
+ * space. Use _ASSERT if this is an issue. */
+# define ASSERT(f) _ASSERTE(f)
+# else
+# include <assert.h>
+# define ASSERT(f) assert(f)
+# endif
+
+# define VERIFY(f) ASSERT(f)
+# define DEBUG_ONLY(f) (f)
+# define TRACE Trace
+# define TRACE0(psz) Trace(_T("%s"), _T(psz))
+# define TRACE1(psz, p1) Trace(_T(psz), p1)
+# define TRACE2(psz, p1, p2) Trace(_T(psz), p1, p2)
+# define TRACE3(psz, p1, p2, p3) Trace(_T(psz), p1, p2, p3)
+# define ASSERT_VALID(pObj) \
+ do {ASSERT((pObj) != NULL); (pObj)->AssertValid();} while (0)
+# define DUMP(pObj) \
+ do {ASSERT((pObj) != NULL); (pObj)->Dump();} while (0)
+# define DEBUG_INIT() DebugInit()
+# define DEBUG_TERM() DebugTerm()
+
+#else /* !_DEBUG */
+
+ /* These macros should all compile away to nothing */
+# define ASSERT(f) ((void)0)
+# define VERIFY(f) ((void)(f))
+# define DEBUG_ONLY(f) ((void)0)
+# define TRACE 1 ? (void)0 : Trace
+# define TRACE0(psz)
+# define TRACE1(psz, p1)
+# define TRACE2(psz, p1, p2)
+# define TRACE3(psz, p1, p2, p3)
+# define ASSERT_VALID(pObj) ((void)0)
+# define DUMP(pObj) ((void)0)
+# define DEBUG_INIT() ((void)0)
+# define DEBUG_TERM() ((void)0)
+
+#endif /* !_DEBUG */
+
+
+#define ASSERT_POINTER(p, type) \
+ ASSERT(((p) != NULL) && IsValidAddress((p), sizeof(type), FALSE))
+
+#define ASSERT_NULL_OR_POINTER(p, type) \
+ ASSERT(((p) == NULL) || IsValidAddress((p), sizeof(type), FALSE))
+
+
+/* Declarations for non-Windows apps */
+
+#ifndef _WINDEF_
+typedef void* LPVOID;
+typedef const void* LPCVOID;
+typedef unsigned int UINT;
+typedef int BOOL;
+typedef const char* LPCTSTR;
+#endif /* _WINDEF_ */
+
+#ifndef TRUE
+# define FALSE 0
+# define TRUE 1
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+
+/* Low-level sanity checks for memory blocks */
+BOOL IsValidAddress(LPCVOID pv, UINT nBytes, BOOL fReadWrite = TRUE);
+BOOL IsValidString(LPCTSTR ptsz, int nLength = -1);
+
+#else /* !__cplusplus */
+
+/* Low-level sanity checks for memory blocks */
+BOOL IsValidAddress(LPCVOID pv, UINT nBytes, BOOL fReadWrite);
+BOOL IsValidString(LPCTSTR ptsz, int nLength);
+
+#endif /* !__cplusplus */
+
+/* in debug version, writes trace messages to debug stream */
+void __cdecl
+Trace(
+ LPCTSTR pszFormat,
+ ...);
+
+/* should be called from main(), WinMain(), or DllMain() */
+void
+DebugInit();
+
+void
+DebugTerm();
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __DEBUG_H__ */
View
119 CookieMunger/filter.cpp
@@ -0,0 +1,119 @@
+#include "CkyPch.h"
+
+#include "filter.h"
+#include "keyword.h"
+#include "utils.h"
+#include "globals.h"
+
+
+
+//
+// First pass through the data: count how many extra bytes we're going to
+// need to allocate to hold the modified data, if any.
+//
+
+int
+CountExtraBytes(
+ LPCTSTR ptszStart,
+ UINT cch)
+{
+ ASSERT(ptszStart != NULL);
+
+ LPCTSTR ptszEnd = ptszStart + cch;
+ CStateStack ss;
+ int cb = 0;
+
+ for (LPCTSTR ptsz = ptszStart; ptsz < ptszEnd; )
+ {
+ int cLen;
+ const CToken* ptok = g_trie.Search(ptsz, &cLen, ptszEnd - ptsz);
+
+ if (ptok == NULL)
+ ++ptsz;
+ else
+ {
+ cb += ptok->CountBytes(ss, ptsz, ptszEnd - ptsz);
+ ptsz += ptok->m_str.length();
+ }
+ }
+
+ return cb;
+}
+
+
+
+//
+// Second pass: munge the data
+//
+
+void
+DoFilter(
+ LPCTSTR ptszStart,
+ UINT cch,
+ LPCTSTR ptszSessionID,
+ LPTSTR ptszOutBuf)
+{
+ ASSERT(ptszStart != NULL && cch > 0);
+ ASSERT(ptszSessionID != NULL && _tcslen(ptszSessionID) > 0);
+ ASSERT(ptszOutBuf != NULL);
+
+ LPCTSTR ptszEnd = ptszStart + cch;
+ // const int cchUrl = _tcslen(ptszSessionID);
+ // const int cchUrlNameValue = _tcslen(s_szUrlNameValue);
+ CStateStack ss(ptszSessionID);
+
+ for (LPCTSTR ptsz = ptszStart; ptsz < ptszEnd; )
+ {
+ int cLen;
+ const CToken* ptok = g_trie.Search(ptsz, &cLen, ptszEnd - ptsz);
+
+ if (ptok == NULL)
+ {
+ // TRACE("%c", *ptsz);
+ *ptszOutBuf++ = *ptsz++;
+ }
+ else
+ {
+ // DoFilter is supposed to copy itself, if appropriate,
+ // and adjust ptsz and ptszOutBuf
+ ptok->DoFilter(ss, ptsz, ptszEnd - ptsz, ptszOutBuf);
+ }
+ }
+}
+
+
+
+int
+Filter(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_RAW_DATA pRawData,
+ LPCSTR pszStart,
+ UINT cch,
+ int iStart,
+ LPCSTR pszSessionID)
+{
+ ASSERT(pszSessionID != NULL);
+
+ // If empty SessionID (typically happens on plain HTML pages), nothing
+ // useful can be done
+ if (strlen(pszSessionID) == 0)
+ return 0;
+
+ const int nExtra = CountExtraBytes(pszStart + iStart, cch - iStart);
+
+ if (nExtra > 0)
+ {
+ TRACE("Filtering `%s', found %d extra bytes\n", pszSessionID, nExtra);
+ const int nNewSize = nExtra + cch;
+ TRACE("FilterNew: ");
+ LPSTR pchNew = (LPSTR) AllocMem(pfc, nNewSize);
+
+ memcpy(pchNew, pszStart, iStart);
+ DoFilter(pszStart + iStart, cch - iStart,
+ pszSessionID, pchNew + iStart);
+ pRawData->pvInData = pchNew;
+ pRawData->cbInData = pRawData->cbInBuffer = nNewSize;
+ }
+
+ return nExtra;
+}
View
13 CookieMunger/filter.h
@@ -0,0 +1,13 @@
+#ifndef __FILTER_H__
+#define __FILTER_H__
+
+int
+Filter(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_RAW_DATA pRawData,
+ LPCSTR pszStart,
+ UINT cch,
+ int iStart,
+ LPCSTR pszSessionID);
+
+#endif // __FILTER_H__
View
21 CookieMunger/globals.h
@@ -0,0 +1,21 @@
+#ifndef __GLOBALS_H__
+#define __GLOBALS_H__
+
+#include "token.h"
+
+#ifndef EXTERN
+# define EXTERN extern
+# define INITVAL(x)
+#else
+# define INITVAL(x) = (x)
+#endif
+
+EXTERN CTokenTrie g_trie;
+
+#define SESSION_ID_PREFIX "-ASP=" // "@ASP:"
+#define SESSION_ID_PREFIX_SIZE 5 // 5
+#define SESSION_ID_SUFFIX "" // ":SESS"
+#define SESSION_ID_SUFFIX_SIZE 0 // 5
+#define COOKIE_NAME_EXTRA_SIZE 8
+
+#endif // __GLOBALS_H__
View
69 CookieMunger/isapiflt.h
@@ -0,0 +1,69 @@
+#ifndef __ISAPIFLT_H__
+#define __ISAPIFLT_H__
+
+#include <httpfilt.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+// This notification was added in IIS 3.0
+
+#ifndef SF_NOTIFY_END_OF_REQUEST
+# define SF_NOTIFY_END_OF_REQUEST 0x00000080
+#endif
+
+
+///////////////////////////////////////////////////////////////////////
+// ISAPI Filter Notification handlers
+
+DWORD
+OnReadRawData(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_RAW_DATA pRawData);
+
+DWORD
+OnPreprocHeaders(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_PREPROC_HEADERS pHeaders);
+
+DWORD
+OnUrlMap(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_URL_MAP pMapInfo);
+
+DWORD
+OnAuthentication(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_AUTHENT pAuthent);
+
+DWORD
+OnAccessDenied(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_ACCESS_DENIED pAccess);
+
+DWORD
+OnSendRawData(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_RAW_DATA pRawData);
+
+DWORD
+OnEndOfRequest(
+ PHTTP_FILTER_CONTEXT pfc);
+
+DWORD
+OnLog(
+ PHTTP_FILTER_CONTEXT pfc,
+ PHTTP_FILTER_LOG pLog);
+
+DWORD
+OnEndOfNetSession(
+ PHTTP_FILTER_CONTEXT pfc);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // __ISAPIFLT_H__
View
414 CookieMunger/keyword.cpp
@@ -0,0 +1,414 @@
+#include "CkyPch.h"
+
+#include "keyword.h"
+#include "filter.h"
+#include "utils.h"
+
+#undef EXTERN
+#define EXTERN
+#include "globals.h"
+
+
+// Build table of keywords
+
+typedef struct {
+ LPCTSTR ptszKwd;
+ CToken::BOUNDARY bndPrefix;
+ CToken::BOUNDARY bndSuffix;
+} SKeywordData;
+
+
+#define KEYWORD(KWD, tszKwd, bndStart, bndEnd, DoFilterDecl) \
+ \
+ class CKwd##KWD : public CToken \
+ { \
+ public: \
+ CKwd##KWD( \
+ LPCTSTR ptsz, \
+ BOUNDARY bndPrefix, \
+ BOUNDARY bndSuffix) \
+ : CToken(ptsz, bndPrefix, bndSuffix) \
+ {} \
+ \
+ virtual UINT \
+ CountBytes( \
+ CStateStack& rss, \
+ LPCTSTR ptszData, \
+ UINT cchData) const; \
+ \
+ DoFilterDecl \
+ }; \
+ \
+ static const SKeywordData kd##KWD = \
+ {tszKwd, CToken::bndStart, CToken::bndEnd};
+
+
+#define DO_FILTER_DECL \
+ virtual UINT \
+ DoFilter( \
+ CStateStack& rss, \
+ LPCTSTR& rptszData, \
+ UINT cchData, \
+ LPTSTR& rptszOutBuf) const; \
+
+#define NO_DO_FILTER_DECL /*none*/
+
+
+KEYWORD(StartTag, _T("<"), IRRELEVANT, IRRELEVANT, NO_DO_FILTER_DECL);
+KEYWORD(Anchor, _T("<a"), IRRELEVANT, WHITESPACE, NO_DO_FILTER_DECL);
+KEYWORD(Area, _T("<area"), IRRELEVANT, WHITESPACE, NO_DO_FILTER_DECL);
+KEYWORD(EndTag, _T(">"), IRRELEVANT, IRRELEVANT, NO_DO_FILTER_DECL);
+KEYWORD(HRef, _T("href="), WHITESPACE, IRRELEVANT, DO_FILTER_DECL);
+KEYWORD(BegCmnt, _T("<--!"), IRRELEVANT, WHITESPACE, NO_DO_FILTER_DECL);
+KEYWORD(EndCmnt, _T("-->"), WHITESPACE, IRRELEVANT, NO_DO_FILTER_DECL);
+KEYWORD(BegCmnt2, _T("<comment>"), IRRELEVANT, WHITESPACE, NO_DO_FILTER_DECL);
+KEYWORD(EndCmnt2, _T("</comment>"), WHITESPACE, IRRELEVANT, NO_DO_FILTER_DECL);
+
+
+
+#define KEYWORD_INIT(KWD) \
+ g_trie.AddToken(new CKwd##KWD(kd##KWD.ptszKwd, \
+ kd##KWD.bndPrefix, \
+ kd##KWD.bndSuffix))
+
+
+BOOL
+InitKeywords()
+{
+ KEYWORD_INIT(StartTag);
+ KEYWORD_INIT(Anchor);
+ KEYWORD_INIT(Area);
+ KEYWORD_INIT(EndTag);
+ KEYWORD_INIT(HRef);
+ KEYWORD_INIT(BegCmnt);
+ KEYWORD_INIT(EndCmnt);
+ KEYWORD_INIT(BegCmnt2);
+ KEYWORD_INIT(EndCmnt2);
+
+ DUMP(&g_trie);
+
+ return TRUE;
+}
+
+
+
+BOOL
+TerminateKeywords()
+{
+ g_trie.Flush();
+ return TRUE;
+}
+
+
+
+//----------------------------------------------------------------
+// "<" starting a tag
+
+UINT
+CKwdStartTag::CountBytes(
+ CStateStack& rss,
+ LPCTSTR ptszData,
+ UINT cchData) const
+{
+ rss.m_fInTag = TRUE;
+ return 0;
+}
+
+
+
+//----------------------------------------------------------------
+// "<a"
+
+UINT
+CKwdAnchor::CountBytes(
+ CStateStack& rss,
+ LPCTSTR ptszData,
+ UINT cchData) const
+{
+ if (!rss.m_fInComment)
+ rss.push(ANCHOR);
+
+ return 0;
+}
+
+
+
+//----------------------------------------------------------------
+// "<area" (occurs inside <map> ... </map>)
+
+UINT
+CKwdArea::CountBytes(
+ CStateStack& rss,
+ LPCTSTR ptszData,
+ UINT cchData) const
+{
+ if (!rss.m_fInComment)
+ rss.push(AREA);
+
+ return 0;
+}
+
+
+
+//----------------------------------------------------------------
+// ">" closing "<a [href=...] ...>" or "<area [href=...] ...>"
+
+UINT
+CKwdEndTag::CountBytes(
+ CStateStack& rss,
+ LPCTSTR ptszData,
+ UINT cchData) const
+{
+ if (!rss.m_fInComment && !rss.m_fInComment2)
+ {
+ PARSESTATE ps = rss.top();
+
+ if (ps == HREF)
+ {
+ ps = rss.top();
+ rss.pop();
+ }
+
+ if (ps == ANCHOR || ps == AREA)
+ {
+ ps = rss.top();
+ rss.pop();
+ }
+ }
+
+ rss.m_fInTag = FALSE;
+ return 0;
+}
+
+
+
+//----------------------------------------------------------------
+// "href="
+
+UINT
+CKwdHRef::CountBytes(
+ CStateStack& rss,
+ LPCTSTR ptszData,
+ UINT cchData) const
+{
+ PARSESTATE ps = rss.top();
+
+ if (!rss.m_fInComment && !rss.m_fInComment2)
+ if (ps == ANCHOR || ps == AREA)
+ rss.push(HREF);
+
+ const TCHAR* const ptszEnd = ptszData + cchData;
+
+ ptszData += m_str.length();
+
+ const TCHAR tchDelim = *ptszData;
+
+ if (tchDelim == _T('"'))
+ ++ptszData;
+
+ int cLen;
+ const URLTYPE ut = UrlType(ptszData, ptszEnd, cLen);
+
+ if (ut != UT_HTTP && ut != UT_HTTPS && ut != UT_NONE)
+ return 0;
+ else
+ ptszData += cLen;
+
+ // Is it an absolute URL? E.g., http://server/foo/...
+ // Don't want to mangle URLs that belong to some other
+ // server, as it won't know what to do with them
+ if (ptszData[0] == '/' && ptszData[1] == '/')
+ return 0;
+
+ // Is it a server-relative absolute URL? E.g., http:/foo/...
+ // Session IDs are scoped by application (virtual root) in ASP 1.0
+ if (ptszData[0] == '/')
+ return 0;
+
+ TRACE("href = %x\n",
+ (SESSION_ID_PREFIX_SIZE
+ + g_SessionIDSize
+ + SESSION_ID_SUFFIX_SIZE));
+
+ return (SESSION_ID_PREFIX_SIZE
+ + g_SessionIDSize
+ + SESSION_ID_SUFFIX_SIZE);
+}
+
+
+
+UINT
+CKwdHRef::DoFilter(
+ CStateStack& rss,
+ LPCTSTR& rptszData,
+ UINT cchData,
+ LPTSTR& rptszOutBuf) const
+{
+ UINT cb = CountBytes(rss, rptszData, cchData);
+
+ if (cb != 0)
+ {
+ const TCHAR* const ptszEnd = rptszData + cchData;
+
+ LPCTSTR ptsz = rptszData + m_str.length();
+
+ const TCHAR tchDelim = *ptsz;
+
+ if (tchDelim == _T('"'))
+ ++ptsz;
+
+ int cLen;
+ const URLTYPE ut = UrlType(ptsz, ptszEnd, cLen);
+
+ if (ut != UT_HTTP && ut != UT_HTTPS && ut != UT_NONE)
+ {
+ ASSERT(FALSE);
+ return 0;
+ }
+ else
+ ptsz += cLen;
+
+ // Is it an absolute URL? E.g., http://server/foo/...
+ if (ptsz[0] == '/' && ptsz[1] == '/')
+ {
+ ASSERT(FALSE);
+ return 0;
+ }
+
+ while (*ptsz != _T('?')
+ && *ptsz != _T(' ')
+ && *ptsz != _T('>')
+ && *ptsz != _T('"'))
+ {
+ // TRACE("%c", *ptsz);
+ ++ptsz;
+ }
+
+ memcpy(rptszOutBuf, rptszData, ptsz - rptszData);
+ rptszOutBuf += ptsz - rptszData;
+
+ strcpy(rptszOutBuf, SESSION_ID_PREFIX);
+ rptszOutBuf += SESSION_ID_PREFIX_SIZE;
+
+ strcpy(rptszOutBuf, rss.m_szSessionID);
+ rptszOutBuf += g_SessionIDSize;
+
+ strcpy(rptszOutBuf, SESSION_ID_SUFFIX);
+ rptszOutBuf += SESSION_ID_SUFFIX_SIZE;
+
+ // TRACE("%s%s%s",
+ // SESSION_ID_PREFIX, rss.m_szSessionID, SESSION_ID_SUFFIX);
+
+ rptszData = ptsz;
+ }
+ else
+ {
+ // we must skip over the keyword
+ rptszData += m_str.length();
+ memcpy( rptszOutBuf, m_str.c_str(), m_str.length() );
+ rptszOutBuf += m_str.length();
+ }
+
+ return 0; // we have taken care of updating rptszOutBuf
+}
+
+
+
+//----------------------------------------------------------------
+// "<--! "
+
+UINT
+CKwdBegCmnt::CountBytes(
+ CStateStack& rss,
+ LPCTSTR ptszData,
+ UINT cchData) const
+{
+ rss.push(COMMENT);
+ rss.m_fInComment = TRUE;
+
+ return 0;
+}
+
+
+
+//----------------------------------------------------------------
+// " -->"
+
+UINT
+CKwdEndCmnt::CountBytes(
+ CStateStack& rss,
+ LPCTSTR ptszData,
+ UINT cchData) const
+{
+ PARSESTATE ps = rss.top();
+
+ if (ps == COMMENT)
+ {
+ ASSERT(rss.m_fInComment);
+ ps = rss.top();
+ rss.pop();
+ }
+ else if (rss.m_fInComment)
+ {
+ do
+ {
+ ps = rss.top();
+ rss.pop();
+ } while (ps != COMMENT && ps != INVALID);
+ }
+
+ rss.m_fInComment = FALSE;
+
+ return 0;
+}
+
+
+
+
+//----------------------------------------------------------------
+// "<comment": an IE- and Mosaic-specific tag
+
+UINT
+CKwdBegCmnt2::CountBytes(
+ CStateStack& rss,
+ LPCTSTR ptszData,
+ UINT cchData) const
+{
+ rss.push(COMMENT2);
+ rss.m_fInComment2 = TRUE;
+
+ return 0;
+}
+
+
+
+//----------------------------------------------------------------
+// " </comment"
+
+UINT
+CKwdEndCmnt2::CountBytes(
+ CStateStack& rss,
+ LPCTSTR ptszData,
+ UINT cchData) const
+{
+ PARSESTATE ps = rss.top();
+
+ if (ps == COMMENT2)
+ {
+ ASSERT(rss.m_fInComment2);
+ ps = rss.top();
+ rss.pop();
+ }
+ else if (rss.m_fInComment2)
+ {
+ do
+ {
+ ps = rss.top();
+ rss.pop();
+ } while (ps != COMMENT2 && ps != INVALID);
+ }
+
+ rss.m_fInComment2 = FALSE;
+
+ return 0;
+}
View
10 CookieMunger/keyword.h
@@ -0,0 +1,10 @@
+#ifndef __KEYWORD_H__
+#define __KEYWORD_H__
+
+BOOL
+InitKeywords();
+
+BOOL
+TerminateKeywords();
+
+#endif // __KEYWORD_H__
View
88 CookieMunger/makefile
@@ -0,0 +1,88 @@
+Proj = CkyMunge
+
+!ifdef PROCESSOR_ARCHITECTURE
+# We're on Windows NT
+CPU = $(PROCESSOR_ARCHITECTURE)
+! if "$(CPU)" == "x86"
+CPU = i386
+! endif
+!else # !PROCESSOR_ARCHITECTURE
+# We're on Windows 95
+CPU = i386
+!endif # !PROCESSOR_ARCHITECTURE
+
+# Build a retail version by default
+
+!ifndef DEBUG
+NODEBUG = 1
+!endif
+
+
+!include <Win32.mak>
+
+
+cflags = $(cflags) -GX # enable exceptions for STL
+
+
+!ifdef NODEBUG
+cflags = $(cflags) -DNDEBUG
+msvcrtlib = msvcprt.lib msvcrt.lib
+!else
+cflags = $(cflags) -DDEBUG -D_DEBUG
+msvcrtlib = msvcprtd.lib msvcrtd.lib
+!endif
+
+all: $(Proj).Dll
+
+OBJ = CkyMunge.obj Debug.obj Filter.obj Keyword.obj Notify.obj \
+ Token.obj Utils.obj
+
+Messages.rc Messages.h: Messages.mc
+ mc Messages.mc
+
+Messages.res: Messages.rc
+ $(rc) Messages.rc
+
+$(Proj).Obj: $(Proj).cpp Messages.h
+ $(cc) $(cdebug) $(cflags) $(cvarsdll) $(Proj).cpp
+
+Debug.Obj: Debug.cpp
+ $(cc) $(cdebug) $(cflags) $(cvarsdll) Debug.cpp
+
+Filter.Obj: Filter.cpp
+ $(cc) $(cdebug) $(cflags) $(cvarsdll) Filter.cpp
+
+Keyword.Obj: Keyword.cpp
+ $(cc) $(cdebug) $(cflags) $(cvarsdll) Keyword.cpp
+
+Notify.Obj: Notify.cpp
+ $(cc) $(cdebug) $(cflags) $(cvarsdll) Notify.cpp
+
+Token.Obj: Token.cpp
+ $(cc) $(cdebug) $(cflags) $(cvarsdll) Token.cpp
+
+Utils.Obj: Utils.cpp
+ $(cc) $(cdebug) $(cflags) $(cvarsdll) Utils.cpp
+
+$(Proj).Dll: $(OBJ) Messages.res $(Proj).Exp
+ $(link) $(linkdebug) $(dlllflags) -base:0x1C080000 -out:$(Proj).dll \
+ $(OBJ) Messages.res $(Proj).Exp $(msvcrtlib) $(guilibsdll)
+
+$(Proj).Exp: $(Proj).Obj
+ $(implib) -machine:$(CPU) -nologo -Def:$(Proj).Def -Out:$(Proj).Lib \
+ $(Proj).Obj
+
+
+Clean:
+ del *.obj
+
+Cleanall:
+ del *.obj
+ del *.exp
+ del *.dll
+ del *.lib
+ del messages.rc
+ del messages.res
+ del messages.h
+ del msg000*.bin
+
View
55 CookieMunger/messages.mc
@@ -0,0 +1,55 @@
+;/*
+; Copyright (c) 1997 Microsoft Corporation
+;
+; Module Name:
+;
+; Messages.mc
+;
+; Abstract:
+;
+; error messages for the Active Server Pages Cookie Munge ISAPI Filter
+;*/
+;
+
+
+SeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS
+ Information=0x1:STATUS_SEVERITY_INFORMATION
+ Warning=0x2:STATUS_SEVERITY_WARNING
+ Error=0x3:STATUS_SEVERITY_ERROR
+ )
+
+MessageId=0x4000
+Severity=Information
+SymbolicName=CMFI_LOADED
+Language=English
+[GetFilterVersion] Loaded Active Server Pages Cookie Munge Filter.
+.
+
+MessageId=0x4001
+Severity=Error
+SymbolicName=CMFE_GETHEADER
+Language=English
+[%1] GetHeader for %2 failed.
+.
+
+MessageId=0x4002
+Severity=Error
+SymbolicName=CMFE_SETHEADER
+Language=English
+[%1] SetHeader for %2 failed.
+.
+
+MessageId=0x4003
+Severity=Error
+SymbolicName=CMFE_GETSERVERVAR
+Language=English
+[%1] GetServerVariable for %2 failed.
+.
+
+MessageId=0x4004
+Severity=Error
+SymbolicName=CMFE_ADDHEADER
+Language=English
+[%1] AddHeader for %2 failed.
+.
+
View
209 CookieMunger/notify.cpp
@@ -0,0 +1,209 @@
+#include "CkyPch.h"
+#include "debug.h"
+#include "utils.h"
+#include "notify.h"
+
+
+//
+// ctor
+//
+
+CNotification::CNotification(
+ LPCSTR pszCookie)
+ : m_nState(HN_UNDEFINED),
+ m_ct(CT_UNDEFINED),
+ m_pszUrl(NULL),
+ m_pbPartialToken(NULL),
+ m_cbPartialToken(0),
+ m_pbTokenBuffer(NULL),
+ m_cbTokenBuffer(0),
+ m_cchContentLength(UINT_MAX), // not specified for .ASPs
+ m_fTestCookies(false)
+{
+ // set the eat cookies and test cookies based on the munge mode.
+ // Here's how the flags will affect output of this session
+
+ // if EatCookies is true, cookies will be stripped and URLs will
+ // be munged with the cookie
+ // if TestCookies is true URLs will be munged
+ switch ( g_mungeMode )
+ {
+ case MungeMode_Off:
+ {
+ m_fEatCookies = false;
+ } break;
+
+ case MungeMode_On:
+ {
+ m_fEatCookies = true;
+ } break;
+
+ // always default to smart
+ default:
+ {
+ m_fEatCookies = false;
+ m_fTestCookies = true;
+ } break;
+ }
+
+ *m_szSessionID = '\0';
+
+ if (pszCookie != NULL)
+ {
+ if (!Cookie2SessionID(pszCookie, m_szSessionID))
+ {
+ TRACE("CNotification(%s): ", pszCookie);
+ CopySessionID(pszCookie, m_szSessionID);
+ }
+ }
+
+ TRACE("CNotification(%s)\n", (*m_szSessionID ? m_szSessionID : "<none>"));
+}
+
+
+
+//
+// dtor
+//
+
+CNotification::~CNotification()
+{
+ TRACE("~CNotification(%s)\n", m_szSessionID);
+}
+
+
+
+//
+// Create a CNotification for a Filter Context
+//
+
+CNotification*
+CNotification::Create(
+ PHTTP_FILTER_CONTEXT pfc,
+ LPCSTR pszCookie)
+{
+ ASSERT(pfc->pFilterContext == NULL);
+
+ TRACE("Notify: ");
+
+ // placement new, using ISAPI's fast allocator
+ LPBYTE pbNotify =
+ static_cast<LPBYTE>(AllocMem(pfc, sizeof(CNotification)));
+ CNotification* pNotify = new (pbNotify) CNotification(pszCookie);
+
+ pfc->pFilterContext = static_cast<PVOID>(pNotify);
+
+ return pNotify;
+}
+
+
+
+//
+// Cleanup
+//
+
+void
+CNotification::Destroy(
+ PHTTP_FILTER_CONTEXT pfc)
+{
+ CNotification* pNotify = Get(pfc);
+
+ if (pNotify != NULL)
+ pNotify->~CNotification(); // placement destruction
+
+ pfc->pFilterContext = NULL;
+}
+
+
+
+//
+// Set the filter context's session ID, creating a CNotification if necessary
+//
+
+CNotification*
+CNotification::SetSessionID(
+ PHTTP_FILTER_CONTEXT pfc,
+ LPCSTR pszCookie)
+{
+ CNotification* pNotify = Get(pfc);
+
+ if (pNotify != NULL)
+ {
+ if (!Cookie2SessionID(pszCookie, pNotify->m_szSessionID))
+ {
+ TRACE("SetSessionID(%s): ", pszCookie);
+ CopySessionID(pszCookie, pNotify->m_szSessionID);
+ }
+ }
+ else
+ pNotify = Create(pfc, pszCookie);
+
+ return pNotify;
+}
+
+
+
+//
+// Sometimes the data in OnSendRawData ends in a partial token. We need
+// to buffer that partial token for the next call to OnSendRawData. In
+// some cases, it may take several successive calls to OnSendRawData to
+// accumulate a complete token. This routine builds the partial token,
+// taking care of memory (re)allocation. There's always SPARE_BYTES bytes
+// unused at the end that callers (esp. OnEndOfRequest) can write into.
+//
+
+void
+CNotification::AppendToken(
+ PHTTP_FILTER_CONTEXT pfc,
+ LPCSTR pszNewData,
+ int cchNewData)
+{
+ ASSERT(pszNewData != NULL && cchNewData > 0);
+
+ // if there's room, append new data to the currently allocated buffer
+ if (m_cbPartialToken + cchNewData <= m_cbTokenBuffer - SPARE_BYTES)
+ {
+ m_pbPartialToken = m_pbTokenBuffer;
+ memcpy(m_pbPartialToken + m_cbPartialToken, pszNewData, cchNewData);
+ m_cbPartialToken += cchNewData;
+#ifdef _DEBUG
+ m_pbPartialToken[m_cbPartialToken] = '\0';
+#endif
+ return;
+ }
+
+ // We want to allocate some extra space so that we have room to
+ // grow for a while before needing to allocate more space. If we
+ // allocated only exactly enough, we'd see O(n^2) memory usage in
+ // degenerate cases because AllocMem doesn't return memory to its
+ // pool until the transaction is over.
+ DWORD cb2 = max(1024, 2 * (cchNewData + m_cbTokenBuffer) + SPARE_BYTES);
+
+ if (cb2 > 10000)
+ cb2 = 3 * (cchNewData + m_cbTokenBuffer) / 2 + SPARE_BYTES;
+
+ m_cbTokenBuffer = cb2;
+ LPBYTE pb = (LPBYTE) AllocMem(pfc, m_cbTokenBuffer);
+ LPBYTE pb2 = pb;
+
+ // Already have a partial token buffer? Copy contents, if so.
+ if (m_cbPartialToken > 0)
+ {
+ ASSERT(m_pbPartialToken != NULL
+ && m_pbPartialToken == m_pbTokenBuffer);
+ memcpy(pb, m_pbPartialToken, m_cbPartialToken);
+ pb2 += m_cbPartialToken;
+ }
+ else
+ ASSERT(m_pbPartialToken == NULL);
+
+ memcpy(pb2, pszNewData, cchNewData);
+ m_pbPartialToken = m_pbTokenBuffer = pb;
+ m_cbPartialToken += cchNewData;
+
+#ifdef _DEBUG
+ m_pbPartialToken[m_cbPartialToken] = '\0';
+#endif
+
+ ASSERT(m_cbPartialToken <= m_cbTokenBuffer - SPARE_BYTES);
+}
View
110 CookieMunger/notify.h
@@ -0,0 +1,110 @@
+#ifndef __NOTIFY_H__
+#define __NOTIFY_H__
+
+
+#define SZ_SESSION_ID_COOKIE_NAME "ASPSESSIONID"
+#define SZ_SESSION_ID_COOKIE_NAME_SIZE 12 // strlen(SZ_SESSION_ID_COOKIE_NAME)
+#define MAX_SESSION_ID_SIZE 24
+
+// the different types of munging available
+// off - no munging will be done
+// on - munging will always be done
+// smart - the munger will "test the waters" to see if cookies are getting through for each session.
+// If they are, the munger will not be effectively off for this session. Otherwise, cookies
+// will be munged as usual.
+typedef enum
+{
+ MungeMode_Off=0,
+ MungeMode_On,
+ MungeMode_Smart
+} MungeModeT;
+
+// the munging mode. This will be read from the registry upon initialization
+extern int g_mungeMode;
+
+// the session ID size is either 16 or 24 characters (depending on the server version)
+extern long g_SessionIDSize;
+
+extern CHAR g_szCookieExtra[];
+
+// states for OnSendRawData
+
+enum HN_STATE {
+ HN_UNDEFINED = 0,
+ HN_SEEN_URL,
+ HN_IN_HEADER,
+ HN_IN_BODY,
+};
+
+
+enum CONTENT_TYPE {
+ CT_UNDEFINED = 0,
+ CT_TEXT_HTML,
+};
+
+class CNotification
+{
+public:
+ enum {SPARE_BYTES = 2};
+
+ static
+ CNotification*
+ Create(
+ PHTTP_FILTER_CONTEXT pfc,
+ LPCSTR pszCookie);
+
+ static
+ void
+ Destroy(
+ PHTTP_FILTER_CONTEXT pfc);
+
+ static
+ CNotification*
+ Get(
+ PHTTP_FILTER_CONTEXT pfc)
+ {return static_cast<CNotification*>(pfc->pFilterContext);}
+
+ static
+ CNotification*
+ SetSessionID(
+ PHTTP_FILTER_CONTEXT pfc,
+ LPCSTR pszCookie);
+
+ LPCSTR
+ SessionID() const
+ {return m_szSessionID;}
+
+ HN_STATE
+ State() const
+ {return m_nState;}
+
+ void
+ AppendToken(
+ PHTTP_FILTER_CONTEXT pfc,
+ LPCSTR pszNewData,
+ int cchNewData);
+
+ bool MungingOff() const
+ {return ( !m_fEatCookies ) && ( !m_fTestCookies );}
+
+protected:
+ CNotification(
+ LPCSTR pszCookie);
+
+ ~CNotification();
+
+public:
+ HN_STATE m_nState;
+ CONTENT_TYPE m_ct;
+ CHAR m_szSessionID[ MAX_SESSION_ID_SIZE ];
+ LPSTR m_pszUrl;
+ LPBYTE m_pbPartialToken;
+ int m_cbPartialToken;
+ LPBYTE m_pbTokenBuffer;
+ int m_cbTokenBuffer;
+ DWORD m_cchContentLength;
+ bool m_fEatCookies;
+ bool m_fTestCookies;
+};
+
+#endif // __NOTIFY_H__
View
235 CookieMunger/readme.txt
@@ -0,0 +1,235 @@
+ CkyMunge: ISAPI filter for ASP session state for cookieless browsers
+ ====================================================================
+
+
+Table of Contents
+=================
+
+ Overview
+ Installation
+ Build Notes
+ Support
+ Change Notes
+
+
+Overview
+========
+
+This filter is a workaround that helps Active Server Pages deal with
+browsers that don't understand cookies or that refuse to accept
+cookies. ASP uses the ASPSESSIONID cookie to maintain its session
+state. ASP expects the browser to send this cookie to the server with
+every request that it makes. The ASPSESSIONID cookie is unique to a
+session. Without it, ASP does not know which session the user owns and
+it cannot keep the Session object up to date as the user moves from page
+to page.
+
+The CkyMunge filter works like this:
+
+1. The filter receives a request for a particular URL. If the
+ headers of the request contain either a "Cookie: ASPSESSIONID=xxx"
+ (IIS 3.0) header or a "Cookie: ASPSESSIONIDxxxxxxxx=xxx" (IIS 4.0)
+ header, that ASPSESSIONID is recorded. If the URL contains an
+ encoded ASPSESSIONID (see step 2), the ASPSESSIONID is
+ removed from the URL and a Cookie header is generated
+ instead. The ASPSESSIONID, if present, is recorded for this
+ transaction.
+
+2. CkyMunge filters all outgoing data. When ASP sends out a page in
+ response to the above request, CkyMunge removes the ASPSESSIONID
+ Set-Cookie headers, if present (so that browsers with cookie
+ warnings will have nothing to complain about), and munges any
+ local, relative URLs embedded in the page (e.g., http:foo/bar.asp will
+ become something like http:foo/bar.asp-ASP=PVZQGHUMEAYAHMFV).
+
+3. If the user clicks on one of those modified URLs, the request
+ comes back to the server (and filter) and step 1 starts all
+ over again.
+
+Notes:
+
+* Filtering outgoing raw data is an expensive process. Every
+ single byte of data sent out by IIS must be streamed through
+ the filter. The filter goes to some pains to be smart about
+ not unneccesarily processing data it's not interested in (such
+ as GIFs), but there are unavoidable performance costs
+ associated with IIS needing to copy all data from kernel memory to
+ user memory.
+
+ It is impossible to give an accurate prediction of how much
+ performance will degrade on your server if the CkyMunge filter
+ is installed. On a lightly loaded server, the difference
+ should not matter. On a heavily loaded server, the
+ performance might become unacceptable.
+
+ It is up to you to measure the performance of your server both
+ with and without the filter installed and see if the
+ convenience of supporting users who won't accept cookies or
+ users with old browsers outweighs the cost of poorer
+ performance.
+
+ User education may be a better solution in the long run.
+ "The ASPSESSIONID cookie is not used to track longterm
+ information about you. The cookie is destroyed as soon
+ as your browser shuts down and it is not stored on your
+ machine. It simply allows our webserver to keep track
+ of what's in your shopping basket as you move from page
+ to page in our application.
+
+ "If your browser doesn't support cookies, we'd like to
+ suggest that you upgrade to a newer one that does. When
+ you do, you'll enjoy our site much more because your
+ browser will also support frames and tables and other
+ newer features of HTML that we've made use of."
+
+* If you elect not to install this filter, you can minimize the
+ number of cookies sent by Active Server Pages in one of two ways:
+ * Put something into the Session object:
+ <% Session("something") = whatever %>
+ * Put a global.asa into the virtual root for your
+ application, with a do-nothing Session_OnStart subroutine.
+
+ In IIS 4.0 and beyond, you can turn off session state (and hence
+ cookies) entirely:
+ * On an individual page, place