LOGPARSE.CPP

// LOGPARSE.CPP - Implementation file for your Internet Server 
// LogParse Extension

#include "stdafx.h"
#include "LogParse.h"
#include "urlmon.h"

///////////////////////////////////////////////////////////////////////
// The one and only CWinApp object
// NOTE: You may remove this object if you alter your project to no
// longer use MFC in a DLL.

CWinApp theApp;

#define USE_DECODER 1

#define MAX_SIZE 2048
#define LOG_BUF_SIZE 512
#define NUMLOG 23

TCHAR LogEntries[NUMLOG][LOG_BUF_SIZE] = {
"", //0 remote addr
"-", //1 user name, "-" means anonymous
"", //2 request date
"", //3 request time
"W3SVC", //4 service type (default = W3SVC)
"", //5 server name
"", //6 server ip
"0", //7 elapsed time (default = 0)
"0", //8 bytes received (default = 0)
"0", //9 bytes sent (default = 0)
"200", //a service response code (default = 200)
"0", //b system (NT) response code (default = 0)
"GET", //c method type (HEAD,POST, default = GET)
"", //d uri
"", //e user agent
"-", //f referer (default = "-" means unknown)
"", //10 cookie
"-", //11 query string ("-" means no query string)
"", //12 duration
"", //13 context
"", //14 client cache
"-", //15 metatag
"-" //16 additional parameters (ignored)
};


///////////////////////////////////////////////////////////////////////
// command-parsing map

BEGIN_PARSE_MAP(CLogParseExtension, CHttpServer)
// TODO: insert your ON_PARSE_COMMAND() and
// ON_PARSE_COMMAND_PARAMS() here to hook up your commands.
// For example:
DEFAULT_PARSE_COMMAND(LogParse, CLogParseExtension)
ON_PARSE_COMMAND(LogParse, CLogParseExtension, ITS_PSTR)
ON_PARSE_COMMAND_PARAMS("input_text=default")
END_PARSE_MAP(CLogParseExtension)


///////////////////////////////////////////////////////////////////////
// The one and only CLogParseExtension object

CLogParseExtension theExtension;


///////////////////////////////////////////////////////////////////////
// CLogParseExtension implementation

CLogParseExtension::CLogParseExtension()
{
m_hFile = NULL;
m_ContentLen = 0;
m_szEncoding = NULL;
m_cbURILen = 0;
m_fInLog = FALSE;
}

CLogParseExtension::~CLogParseExtension()
{
if (m_szEncoding)
GlobalFree(m_szEncoding);

}

BOOL CLogParseExtension::GetExtensionVersion(HSE_VERSION_INFO* pVer)
{
// Call default implementation for initialization
CHttpServer::GetExtensionVersion(pVer);

// Load description string
TCHAR sz[HSE_MAX_EXT_DLL_NAME_LEN+1];
ISAPIVERIFY(::LoadString(AfxGetResourceHandle(),
IDS_SERVER, sz, HSE_MAX_EXT_DLL_NAME_LEN));
_tcscpy(pVer->lpszExtensionDesc, sz);
return TRUE;
}

///////////////////////////////////////////////////////////////////////
// CLogParseExtension command handlers

// pszString : post string (using FORM)
void CLogParseExtension::LogParse(CHttpServerContext* pCtxt, LPTSTR pszString)
{

StartContent(pCtxt);
WriteTitle(pCtxt);

*pCtxt << _T("Server DLL, just shown that we were here. \r\n");

// real work starts
DWORD err;
DWORD dwLen = sizeof(DWORD);
char buf[LOG_BUF_SIZE];
DWORD cbsize = LOG_BUF_SIZE;
BOOL bret;
BOOL fEncoded = FALSE;

// get server variables
if (!pCtxt->GetServerVariable("CONTENT_LENGTH", buf, &dwLen) ||
!pCtxt->GetServerVariable("REQUEST_METHOD", (void *)LogEntries[12], &cbsize) )
{
err = GetLastError();
*pCtxt << _T("LogParse failed: couldn't get server variables.\r\n");
goto _exithere;
}

if (lstrcmp(LogEntries[12], _T("POST")))
{
*pCtxt << _T("LogParse failed: wrong request method.\r\n");
goto _exithere;
}
else
{
cbsize = LOG_BUF_SIZE;
m_szEncoding = (LPTSTR)GlobalAlloc(LPTR, LOG_BUF_SIZE);
if (!m_szEncoding)
{
*pCtxt << _T("LogParse failed: couldn't allocate g_szEncoding \r\n");
goto _exithere;
}

if (pCtxt->GetServerVariable("HTTP_CONTENT_TRANSFER_ENCODING", (void *)m_szEncoding, &cbsize))
fEncoded = TRUE;

if (*pszString == NULL)
{
*pCtxt << _T("LogParse: no posting string \r\n");
goto _exithere;
}

bret = OpenLogFile(pCtxt);
if (!bret)
{
*pCtxt << _T("LogParse: create/open file failed \r\n");
goto _exithere;
}

dwLen = AppendContentToLogfile(pCtxt, pszString, atoi(buf), fEncoded);

ISAPITRACE1("LogParse: server logged %d bytes\n", dwLen);
ISAPITRACE1("LogParse: encoding by %s\n", m_szEncoding);

}


_exithere:
EndContent(pCtxt);

}

BOOL CLogParseExtension::OpenLogFile(CHttpServerContext* pCtxt)
{
SYSTEMTIMEst;
TCHAR szLogPath[MAX_PATH];
DWORD cbpath = MAX_PATH;
TCHARpszfile[MAX_PATH];
HKEY hkey;

// generate filename based on date
GetLocalTime(&st);
wsprintf(pszfile, "ie%.2d%.2d%.2d.log", st.wYear, st.wMonth, st.wDay);

if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, g_szRegW3SVC, 0, KEY_READ, &hkey))
{
*pCtxt << _T("LogParse failed: RegOpenKeyEx\r\n");
return FALSE;
}

if (ERROR_SUCCESS != RegQueryValueEx(hkey, g_szRegLogFileDirectory,
NULL, NULL, (LPBYTE)szLogPath, &cbpath))
{
*pCtxt << _T("LogParse failed: GetLogFileDirectory.\r\n");
RegCloseKey(hkey);
return FALSE;
}

RegCloseKey(hkey);

lstrcpy(m_szLogFile, szLogPath);
lstrcat(m_szLogFile, g_szLogFilePath);
lstrcat(m_szLogFile, pszfile);

m_hFile = CreateFile(m_szLogFile, GENERIC_READ|GENERIC_WRITE, 0, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

if (m_hFile == INVALID_HANDLE_VALUE)
return FALSE;

SetFilePointer(m_hFile, 0, NULL, FILE_END);

return TRUE;
}

DWORD CLogParseExtension::AppendContentToLogfile(CHttpServerContext* pCtxt, LPCTSTR pszContent, DWORD ContentLen, BOOL fEncoded)
{
LPTSTR pszDecContent = NULL;
LPTSTR pszLog = NULL;
DWORD dwWritten = 0;

if (fEncoded)
{
DWORD dwlen;
//decoding
dwlen = ContentLen * 4;
pszDecContent = (LPTSTR)GlobalAlloc(LPTR, dwlen);
if (DecodeContent(pCtxt, pszContent, ContentLen, pszDecContent, dwlen))
pszLog = pszDecContent;
else
return 0; // failed to decompress the data somehow
}
else
{
pszLog = (LPTSTR)pszContent;
m_ContentLen = lstrlenA(pszLog);
}

//transform to W3C flexible log format
dwWritten = ConvertToW3CFlexible(pCtxt, pszLog);

if (pszDecContent)
GlobalFree(pszDecContent);

return dwWritten;
}

// we need to extract out the decoder's name from "x-filtername" string
BOOL CLogParseExtension::DecodeContent(CHttpServerContext* pCtxt, LPCTSTR pszEncodedString,
DWORD cbEnc, LPTSTR pszDecString, DWORD cbDec)
{
#ifdef USE_DECODER
LPWSTRpwszEncoding = NULL;
LPTSTR pszEncoding = m_szEncoding;
DWORD dwlen;
BOOL bInDash = FALSE;
BOOLbret = FALSE;
IEncodingFilterFactory* pEflt = NULL;
IDataFilter* pDF = NULL;

//parse encoding header string
while (*pszEncoding != 0)
{
if (bInDash)
break;

if (*pszEncoding == '-')
bInDash = TRUE;

pszEncoding ++;
};

dwlen = (lstrlenA(pszEncoding) + 1) * sizeof(WCHAR);
pwszEncoding = (LPWSTR)GlobalAlloc(LPTR, dwlen);

//find/load decoder
if (!pwszEncoding)
{
*pCtxt << _T("LogParse: DecodeContent mem error \r\n");
goto _exitDecode;
}

dwlen = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pszEncoding, lstrlenA(pszEncoding),
pwszEncoding, dwlen);

if (dwlen == 0)
{
*pCtxt << _T("LogParse : DecodeContent wide char convert failed\r\n");
goto _exitDecode;
}

CoCreateInstance(CLSID_StdEncodingFilterFac, NULL, CLSCTX_INPROC_SERVER,
IID_IEncodingFilterFactory, (void**)&pEflt);

if (pEflt)
{
pEflt->GetDefaultFilter(pwszEncoding, L"text", &pDF);
if (pDF)
{
LONG lInUsed = 0;
LONG lOutUsed = 0;
HRESULT hrCode = NOERROR;

hrCode = pDF->DoEncode(
0,
cbEnc,
(BYTE *)pszEncodedString,
cbDec,
(BYTE *)pszDecString,
cbEnc,
&lInUsed,
&lOutUsed,
0);

if (SUCCEEDED(hrCode))
{
m_ContentLen = (DWORD)lOutUsed;
bret = TRUE;
}
pDF->Release();

}

pEflt->Release();

}

_exitDecode:
if (pwszEncoding)
GlobalFree(pwszEncoding);

return bret;
#else
return TRUE;
#endif
}


DWORD CLogParseExtension::ConvertToW3CFlexible(CHttpServerContext* pCtxt, LPTSTR pszClientLog)
{

#ifdef _DEBUG
#define NUMVARS 20

char varnames[NUMVARS][30] = {
"AUTH_TYPE",
"CONTENT_LENGTH",
"CONTENT_TYPE",
"PATH_INFO",
"PATH_TRANSLATED",
"QUERY_STRING",
"REMOTE_ADDR",
"REMOTE_HOST",
"REMOTE_USER",
"UNMAPPED_REMOTE_USER",
"REQUEST_METHOD",
"SCRIPT_NAME",
"SERVER_NAME",
"SERVER_PORT",
"SERVER_PORT_SECURE",
"SERVER_PROTOCOL",
"SERVER_SOFTWARE",
"ALL_HTTP",
"HTTP_ACCEPT",
"URL"
};

char buf[1024];
unsigned long bufsize;

for (int index = 0; index < NUMVARS; index++) {

bufsize = 1024;
if (pCtxt->GetServerVariable (&varnames[index][0], buf, &bufsize)) {

ISAPITRACE2("[%s] = [%s]\n", &varnames[index], buf);
}
else {

ISAPITRACE1("[%s] = (Oops!)\n", varnames[index]);
}
}
#endif

DWORD dwFixedBytes = 0;
DWORD dwTotalBytes = 0;
TCHAR szSpace[] = " ";
LPTSTR pszServerLog;
DWORD dwCount = m_ContentLen + 1; // over 1 from end
DWORD dwmove;

dwFixedBytes = ProcessFixedLogFields(pCtxt);
dwFixedBytes = dwFixedBytes + NUMLOG*lstrlen(szSpace) + lstrlen(LogEntries[4])
+ 6*lstrlen(LogEntries[7]) + lstrlen(LogEntries[10]);

// parse client log
m_cbURILen = 0;
do
{
DWORD dwthisclientlog = 0;
DWORD dwthislog = 0;
DWORD dwwritten;

dwthisclientlog = ProcessClientLog(pCtxt, pszClientLog, &dwmove);
if (dwthisclientlog == 0)
break;

dwthislog = dwFixedBytes + dwthisclientlog;

// fill in temp buffer
pszServerLog = (LPTSTR)GlobalAlloc(LPTR, dwthislog);
if (!pszServerLog)
{
ISAPITRACE0("LogParse: mem alloc failed, pszServerLog\n");
continue;
}

int ilog, i;
int index = 0;
for (ilog=0; ilog<NUMLOG; ilog++)
{
for (i=0; LogEntries[ilog][i]; i++, index++)
pszServerLog[index] = LogEntries[ilog][i];

// Add space
pszServerLog[index++] = ' ';
}

// null terminated
pszServerLog[index] = 0;

// write to server log file
WriteFile(m_hFile, pszServerLog, index, &dwwritten, NULL);
dwTotalBytes += dwwritten;
GlobalFree(pszServerLog);

// keep track of end of string
dwCount -= dwmove;
if (dwCount == 0)
break;

pszClientLog += dwmove;

} while (TRUE);

CloseHandle(m_hFile);

return dwTotalBytes;
}

#define NUMSERVER 7
#define NUMCLIENT 5

char Varnames[NUMSERVER][20] = {
"REMOTE_ADDR",
"REMOTE_USER",
"SERVER_NAME",
"SERVER_PORT",
"HTTP_USER_AGENT",
"HTTP_REFERER",
"QUERY_STRING"
};

int SvrIdx[NUMSERVER+NUMCLIENT] = {0, 1, 5, 6, 14, 15, 17, 19, 20, 2, 3, 18};


DWORD CLogParseExtension::ProcessFixedLogFields(CHttpServerContext* pCtxt)
{
DWORD dwBytes;
DWORD cbsize;
char buf[1024];

// remote addr
dwBytes = 0;
for (int i=0; i<NUMSERVER; i++)
{
cbsize = LOG_BUF_SIZE;
if (!pCtxt->GetServerVariable(Varnames[i], buf, &cbsize))
{
ISAPITRACE1("LogParse: can't get %s\n", Varnames[i]);
}
else
{
if (*buf)
lstrcpy(LogEntries[SvrIdx[i]], buf);
}
dwBytes += lstrlen(LogEntries[SvrIdx[i]]);
}

// method type
dwBytes += lstrlen(LogEntries[12]);

// cookie
cbsize = LOG_BUF_SIZE;
if (!GetUserCookie(pCtxt, LogEntries[16], &cbsize))
{
ISAPITRACE0("LogParse: can't get request method\n");
cbsize = 0;
}
dwBytes += cbsize;

return dwBytes;
}


BOOL CLogParseExtension::GetUserCookie(CHttpServerContext* pCtxt, LPTSTR pszCookie, LPDWORD pcb)
{
BOOL bret = FALSE;
DWORD sizeof_cookies = sizeof_header;
char * cookie;

cookie = (char *)GlobalAlloc(LPTR, sizeof_cookies);
if (!cookie)
return bret;

char * interse_cookie = &cookie[0]; // interse' cookie

// Obtain the list of cookies.
if (pCtxt->GetServerVariable("ALL_HTTP", interse_cookie, &sizeof_cookies))
{
if (interse_cookie = strstr (interse_cookie, "HTTP_COOKIE:"))
{
// Does the client already have a Interse' cookie? Also, remove cookies
// to the left of the Interse' cookie.
//
if (interse_cookie = strstr (interse_cookie, interselog_cookiename))
{
// Remove cookies to the right of Interse' cookie.
//
char * next_cookie = strstr (interse_cookie, ";");
if (next_cookie) *next_cookie = 0;
*pcb = lstrlen(interse_cookie);
if (*pcb >= LOG_BUF_SIZE)
{
ISAPITRACE0("LogParse: cookie memory overflow!!\n");
}
lstrcpy(pszCookie, interse_cookie);
bret = TRUE;
}
else
{
*pszCookie = 0;
*pcb = 0;
}
}
else
{
*pszCookie = 0;
*pcb = 0;
}
}

return bret;
}

/*
* client log is in "extended server log format"
* #Fields: s-URI
* http://www.msnbc.com/news/66784.asp
* #Fields: x-context x-cache x-date x-time x-duration x-meta
* N 1 06-10-1997 14:03:54 00:12:32 customestring
* N 1 06-11-1997 10:13:04 00:05:13
* N 1 06-17-1997 04:07:15 00:11:33
*/

DWORD CLogParseExtension::ProcessClientLog(CHttpServerContext* pCtxt, LPTSTR pszClientLog, LPDWORD pMove)
{
char* pszNext;
DWORD cbBytes = 0;
LPTSTR pszInbuf = pszClientLog;

if (m_cbURILen == 0)
{
// look for URI
if (*pszClientLog == ' ')
pszClientLog ++;

if (memicmp(pszClientLog, g_szURIFields, lstrlen(g_szURIFields)))
return 0;

pszClientLog += lstrlen(g_szURIFields);
pszNext = strstr(pszClientLog, " ");
if (pszNext)
*pszNext = 0;

lstrcpy(LogEntries[13], pszClientLog);
m_cbURILen = pszNext - pszClientLog;
pszClientLog += m_cbURILen;
pszClientLog ++; // the space (pNext)
}

cbBytes = m_cbURILen;

// next line should be either log or #Fields: x-context.... or end_of_string
if (!memicmp(pszClientLog, g_szFields, lstrlen(g_szFields)))
{
pszClientLog = strstr(pszClientLog, g_szMeta);
pszClientLog += lstrlen(g_szMeta);
if (*pszClientLog == ' ')
pszClientLog ++; // the 1st space in log cache!!
}
else if (*pszClientLog == 0)
{
// end of log string
return 0;
}

for (int i = NUMSERVER; i < NUMSERVER+NUMCLIENT; i ++)
{
pszNext = strstr(pszClientLog, " ");
if (pszNext)
*pszNext = 0;
lstrcpy(LogEntries[SvrIdx[i]], pszClientLog);
cbBytes += lstrlen(LogEntries[SvrIdx[i]]);
pszClientLog += lstrlen(LogEntries[SvrIdx[i]]);
pszClientLog ++ ; // the space
}

// look for URI
if (!memicmp(pszClientLog, g_szURIFields, lstrlen(g_szURIFields)))
{
m_cbURILen = 0;
}
//
*pMove = pszClientLog - pszInbuf;
return cbBytes;

}

// Do not edit the following lines, which are needed by ClassWizard.
#ifdef PROCESS_MESSAGE
BEGIN_MESSAGE_MAP(CLogParseExtension, CHttpServer)
//{{AFX_MSG_MAP(CLogParseExtension)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif



///////////////////////////////////////////////////////////////////////
// If your extension will not use MFC, you'll need this code to make
// sure the extension objects can find the resource handle for the
// module. If you convert your extension to not be dependent on MFC,
// remove the comments arounn the following AfxGetResourceHandle()
// and DllMain() functions, as well as the g_hInstance global.

/****

static HINSTANCE g_hInstance;

HINSTANCE AFXISAPI AfxGetResourceHandle()
{
return g_hInstance;
}

BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ulReason,
LPVOID lpReserved)
{
if (ulReason == DLL_PROCESS_ATTACH)
{
g_hInstance = hInst;
}

return TRUE;
}

****/

/* W3C flexible log file format */
/*
157.56.230.204//remote addr
-//user name, "-" means anonymous
06/17/97//request date
19:51:44//request time
W3SVC//service type (default = W3SVC)
A282527//server name
157.56.230.168//server ip
340//elapsed time (default = 0)
450//bytes received (default = 0)
123//bytes sent (default = 0)
200//service response code (default = 200)
0//system (NT) response code (default = 0)
GET//method type (HEAD,POST, default = GET)
/impsrvr/index.html//uri
Mozilla/2.0 (compatible; MSIE 3.02; WIndows NT)//user agent
-//referer (default = "-" means unknown)
INTERSE=157222862038521//cookie
-//query string ("-" means no query string)
00:00:18//duration
N//context
1//client cache
-//additional parameters (ignored)
*/