// Uptime.cpp
// by Steve Tibbett

#include "stdafx.h"
#include "Uptime.h"

///////////////////////////////////////////////////////////////////////
// The one and only CUptimeFilter object

CUptimeFilter theFilter;

///////////////////////////////////////////////////////////////////////
// CUptimeFilter implementation

CUptimeFilter::CUptimeFilter()
{
	// Remember when the server was started
	time(&m_tStartup);
	
	m_nLastHour = -1;
}

CUptimeFilter::~CUptimeFilter()
{
}

// Helper function for writing data to the user
void writeStr(CHttpFilterContext* pCtxt, CString& str)
{
	DWORD dwLen = str.GetLength();
	pCtxt->WriteClient(str.GetBuffer(0), &dwLen, 0);
}

BOOL CUptimeFilter::GetFilterVersion(PHTTP_FILTER_VERSION pVer)
{
	// Call default implementation for initialization
	CHttpFilter::GetFilterVersion(pVer);

	// Clear the flags set by base class
	pVer->dwFlags &= ~SF_NOTIFY_ORDER_MASK;

	// Set the flags we are interested in
	pVer->dwFlags |= SF_NOTIFY_ORDER_LOW | SF_NOTIFY_SECURE_PORT | SF_NOTIFY_NONSECURE_PORT
			 | SF_NOTIFY_PREPROC_HEADERS;

	// Load description string
	TCHAR sz[SF_MAX_FILTER_DESC_LEN+1];
	ISAPIVERIFY(::LoadString(AfxGetResourceHandle(),
			IDS_FILTER, sz, SF_MAX_FILTER_DESC_LEN));
	_tcscpy(pVer->lpszFilterDesc, sz);
	return TRUE;
}

//
// This function records a hit on the string argument in the
// Map passed in.
//

void CUptimeFilter::RecordHit(CMapStringToOb& Map, const char *str)
{
	// Convert to a CString object for map lookup
	CString strName(str);

	// Find the LogInfo in the map
	LogInfo *pLI = (LogInfo *)Map[strName];
	if (pLI)
	{
		// Found it; increment the hit count
		pLI->m_dwHits++;
	} else
	{
		// Didn't find it - create a new one
		pLI = new LogInfo;
		pLI->m_dwHits = 1;
		Map[strName] = pLI;
	};
}


// 
// Show a map of strings to hits.
//

void CUptimeFilter::ShowHitMap(CHttpFilterContext* pCtxt, CMapStringToOb& Map, const char *pszTitle, const char *pszHdr)
{
	// Write the header string
	CString strHdr;
	strHdr.Format("<p>%s<p><table border=1><tr><td>%s</td><td>Hits</td></tr>\n", pszTitle, pszHdr);
	writeStr(pCtxt, strHdr);

	// Iterate through the map
	POSITION Pos = Map.GetStartPosition();
	while (Pos)
	{
		CObject *pValue;
		CString strKey;
		LogInfo *pLI;

		Map.GetNextAssoc(Pos, strKey, pValue);

		// Make sure the pValue is a LogInfo; it should always be but if
		// something goes wrong, at least we won't crash
		pLI = dynamic_cast<LogInfo *>(pValue);
		if (pLI)
		{
			strHdr.Format("<tr><td>%s</td><td>%d</td></tr>\n", strKey.GetBuffer(0), pLI->m_dwHits);
			writeStr(pCtxt, strHdr);
		};
	};

	strHdr ="</table>\n";
	writeStr(pCtxt, strHdr);
}

DWORD CUptimeFilter::OnPreprocHeaders(CHttpFilterContext* pCtxt,
	PHTTP_FILTER_PREPROC_HEADERS pHeaderInfo)
{
	// Get the URL; truncate it at 1024 bytes.
	char urlvar[1024];
	DWORD dwLen = sizeof(urlvar);
	pHeaderInfo->GetHeader(pCtxt->m_pFC, "url", urlvar, &dwLen);
	urlvar[1023] = 0;

	// Check for our hard-coded URL
	if (strcmp(urlvar, "/server-status")==0)
	{
		// Yep; the user wants to see the stats report.  Lock the 
		// variables section...
		CSingleLock slStats(&m_mxStats, TRUE);

		// Print the header
		CString strHdr;
		strHdr.Format("<html><head><title>Server Status</title></head><body><h1>Server Status</h1>\n");
		writeStr(pCtxt, strHdr);

		// Print the server uptime
		strHdr.Format("Up since: %s<br>\n", asctime(localtime(&m_tStartup)));
		writeStr(pCtxt, strHdr);

		// Print the hits since startup
		strHdr.Format("Hits since server startup: %d<br>\n", m_dwHits);
		writeStr(pCtxt, strHdr);

		// Show the URL hit report
		ShowHitMap(pCtxt, m_mByPage, "URL Hit Map", "URL");

		// Show the IP address hit report
		ShowHitMap(pCtxt, m_mByUser, "Hits by User", "IP Address");

		// Show the web browser hit report
		ShowHitMap(pCtxt, m_mByBrowser, "Hits by Browser", "Browser");

		// Show the referer hit report
		ShowHitMap(pCtxt, m_mByReferer, "Referers", "Referer");

		// Hourly Chart
		strHdr ="<p>Hourly Hits (Last 24 hours)<p><table border=1>\n";
		writeStr(pCtxt, strHdr);

		for (int h=0; h<24; h++)
		{
			strHdr.Format("<tr><td>%d:00</td><td>%d hits</td></tr>\n", h, m_dwHours[h]);
			writeStr(pCtxt, strHdr);
		};

		// Show the recent hits report

		strHdr.Format("</table><p>%d Recent Hits<p><table border=1>\n", nMaxRecentHits);
		writeStr(pCtxt, strHdr);

		POSITION Pos = m_slRecentHits.GetHeadPosition();
		while (Pos)
		{
			CString& str = m_slRecentHits.GetNext(Pos);
			writeStr(pCtxt, str);
		};

		strHdr ="</table></body></html>\n";
		writeStr(pCtxt, strHdr);

		// Done!  Return SF_STATUS_REQ_FINISHED so IIS doesn't try to 
		// pass this request on to anyone else.
		return SF_STATUS_REQ_FINISHED;
	} else
	{
		// Normal hit; record that it's happening
		CSingleLock slStats(&m_mxStats, TRUE);
		char temp[1024];

		// Record this as a hit in the Total Hits counter
		m_dwHits++;

		// Record IP hit
		CString strIP;
		DWORD dwLen = sizeof(temp);
		if (pCtxt->GetServerVariable("REMOTE_ADDR", temp, &dwLen))
		{
			strIP = temp;
			RecordHit(m_mByUser, temp);
		};

		// Record User Agent
		dwLen = sizeof(temp);
		if (pHeaderInfo->GetHeader(pCtxt->m_pFC, "User-agent:", temp, &dwLen))
		{
			RecordHit(m_mByBrowser, temp);
		};

		// Record Referer
		dwLen = sizeof(temp);
		if (pHeaderInfo->GetHeader(pCtxt->m_pFC, "Referer:", temp, &dwLen))
		{
			RecordHit(m_mByReferer, temp);
		};
		
		// Record URL hit
		CString strUrl(urlvar);
		strUrl = strUrl.SpanExcluding("?");
		strUrl.MakeLower();
		RecordHit(m_mByPage, strUrl);

		// Log to hourly hits
		time_t now;
		time(&now);
		struct tm *tptr = localtime(&now);

		if (tptr->tm_hour != m_nLastHour)
		{
			for (int h = m_nLastHour+1; h != tptr->tm_hour; h = (h+1) % 24)
			{
				m_dwHours[h] = 0;
			};

			m_dwHours[tptr->tm_hour] = 1;
			m_nLastHour = tptr->tm_hour;
		} else
		{
			m_dwHours[tptr->tm_hour]++;
		};

		CString strLog;
		strLog.Format("<tr><td>%s</td><td>%s</td><td>%s</td></tr>\n", 
			asctime(tptr), 
			(const char *)strIP, urlvar);

		if (m_slRecentHits.GetCount() >= nMaxRecentHits)
		{
			m_slRecentHits.RemoveHead();
		};

		m_slRecentHits.AddTail(strLog);
	};

	return SF_STATUS_REQ_NEXT_NOTIFICATION;
}

DWORD CUptimeFilter::OnEndOfNetSession(CHttpFilterContext* pCtxt)
{
	// TODO: React to this notification accordingly and
	// return the appropriate status code
	return SF_STATUS_REQ_NEXT_NOTIFICATION;
}

// Do not edit the following lines, which are needed by ClassWizard.
#if 0
BEGIN_MESSAGE_MAP(CUptimeFilter, CHttpFilter)
	//{{AFX_MSG_MAP(CUptimeFilter)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif	// 0

///////////////////////////////////////////////////////////////////////
// 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;
}

****/
