// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1998 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

#include "stdafx.h"
#include <afxtempl.h>
#include <afxinet.h>

#pragma warning(disable: 4706) // assignment within conditional

#ifdef AFX_INET_SEG
#pragma code_seg(AFX_INET_SEG)
#endif

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

#ifdef _AFXDLL
#pragma comment(lib, "wininet.lib")
#endif

/////////////////////////////////////////////////////////////////////////////
// non-localized useful strings

typedef struct tagServiceTable {
	DWORD dwService;
	LPCTSTR pstrIdentifier;
} SvcTable;

AFX_STATIC_DATA const TCHAR _afxURLftp[] = _T("ftp://");
AFX_STATIC_DATA const TCHAR _afxURLgopher[] = _T("gopher://");
AFX_STATIC_DATA const TCHAR _afxURLhttp[] = _T("http://");

const LPCTSTR CHttpConnection::szHtmlVerbs[] = {
	_T("POST"),
	_T("GET"),
	_T("HEAD"),
	_T("PUT"),
	_T("LINK"),
	_T("DELETE"),
	_T("UNLINK"),
};


/////////////////////////////////////////////////////////////////////////////
// map of HINTERNETs to CInternetSessions* for callbacks

// forward declared because we need a #pragma -- see end of this file

class CSessionMapPtrToPtr : public CMapPtrToPtr
{
private:
	CCriticalSection m_sect;

public:
	CSessionMapPtrToPtr() { }
	~CSessionMapPtrToPtr() { }

	void SetAt(HINTERNET hInternet, CInternetSession* pSess)
	{
		m_sect.Lock();
		CMapPtrToPtr::SetAt(hInternet, pSess);
		m_sect.Unlock();
	}

	void RemoveKey(HINTERNET hInternet)
	{
		m_sect.Lock();
		CMapPtrToPtr::RemoveKey(hInternet);
		m_sect.Unlock();
	}

	BOOL Lookup(HINTERNET hInternet, CInternetSession*& refpSession)
	{
		BOOL bRet;
		m_sect.Lock();
		bRet = CMapPtrToPtr::Lookup(hInternet, (void*&) refpSession);
		m_sect.Unlock();
		return bRet;
	}
};

extern CSessionMapPtrToPtr _afxSessionMap;


/////////////////////////////////////////////////////////////////////////////
// Global Functions

AFX_STATIC BOOL AFXAPI _AfxParseURLWorker(LPCTSTR pstrURL,
	LPURL_COMPONENTS lpComponents, DWORD& dwServiceType,
	INTERNET_PORT& nPort, DWORD dwFlags)
{
	// this function will return bogus stuff if lpComponents
	// isn't set up to copy the components

	ASSERT(lpComponents != NULL && pstrURL != NULL);
	if (lpComponents == NULL || pstrURL == NULL)
		return FALSE;
	ASSERT(lpComponents->dwHostNameLength == 0 ||
			lpComponents->lpszHostName != NULL);
	ASSERT(lpComponents->dwUrlPathLength == 0 ||
			lpComponents->lpszUrlPath != NULL);
	ASSERT(lpComponents->dwUserNameLength == 0 ||
			lpComponents->lpszUserName != NULL);
	ASSERT(lpComponents->dwPasswordLength == 0 ||
			lpComponents->lpszPassword != NULL);

	ASSERT(AfxIsValidAddress(lpComponents, sizeof(URL_COMPONENTS), TRUE));

	LPTSTR pstrCanonicalizedURL;
	TCHAR szCanonicalizedURL[INTERNET_MAX_URL_LENGTH];
	DWORD dwNeededLength = INTERNET_MAX_URL_LENGTH;
	BOOL bRetVal;
	BOOL bMustFree = FALSE;
	DWORD dwCanonicalizeFlags = dwFlags &
		(ICU_NO_ENCODE | ICU_DECODE | ICU_NO_META |
		ICU_ENCODE_SPACES_ONLY | ICU_BROWSER_MODE);
	DWORD dwCrackFlags = dwFlags & (ICU_ESCAPE | ICU_USERNAME);

	bRetVal = InternetCanonicalizeUrl(pstrURL, szCanonicalizedURL,
		&dwNeededLength, dwCanonicalizeFlags);

	if (!bRetVal)
	{
		if (::GetLastError() != ERROR_INSUFFICIENT_BUFFER)
			return FALSE;

		pstrCanonicalizedURL = new TCHAR[dwNeededLength];
		bMustFree = TRUE;
		bRetVal = InternetCanonicalizeUrl(pstrURL, pstrCanonicalizedURL,
			&dwNeededLength, dwCanonicalizeFlags);
		if (!bRetVal)
		{
			delete [] pstrCanonicalizedURL;
			return FALSE;
		}
	}
	else
		pstrCanonicalizedURL = szCanonicalizedURL;

	// now that it's safely canonicalized, crack it

	bRetVal = InternetCrackUrl(pstrCanonicalizedURL, 0,
						dwCrackFlags, lpComponents);
	if (bMustFree)
		delete [] pstrCanonicalizedURL;

	// convert to MFC-style service ID

	if (!bRetVal)
		dwServiceType = AFX_INET_SERVICE_UNK;
	else
	{
		nPort = lpComponents->nPort;
		switch (lpComponents->nScheme)
		{
		case INTERNET_SCHEME_FTP:
			dwServiceType = AFX_INET_SERVICE_FTP;
			break;

		case INTERNET_SCHEME_GOPHER:
			dwServiceType = AFX_INET_SERVICE_GOPHER;
			break;

		case INTERNET_SCHEME_HTTP:
			dwServiceType = AFX_INET_SERVICE_HTTP;
			break;

		case INTERNET_SCHEME_HTTPS:
			dwServiceType = AFX_INET_SERVICE_HTTPS;
			break;

		case INTERNET_SCHEME_FILE:
			dwServiceType = AFX_INET_SERVICE_FILE;
			break;

		case INTERNET_SCHEME_NEWS:
			dwServiceType = AFX_INET_SERVICE_NNTP;
			break;

		case INTERNET_SCHEME_MAILTO:
			dwServiceType = AFX_INET_SERVICE_MAILTO;
			break;

		default:
			dwServiceType = AFX_INET_SERVICE_UNK;
		}
	}

	return bRetVal;
}

BOOL AFXAPI AfxParseURLEx(LPCTSTR pstrURL, DWORD& dwServiceType,
	CString& strServer, CString& strObject, INTERNET_PORT& nPort,
	CString& strUsername, CString& strPassword, DWORD dwFlags/* = 0*/)
{
	dwServiceType = AFX_INET_SERVICE_UNK;

	ASSERT(pstrURL != NULL);
	if (pstrURL == NULL)
		return FALSE;

	URL_COMPONENTS urlComponents;
	memset(&urlComponents, 0, sizeof(URL_COMPONENTS));
	urlComponents.dwStructSize = sizeof(URL_COMPONENTS);

	urlComponents.dwHostNameLength = INTERNET_MAX_HOST_NAME_LENGTH;
	urlComponents.lpszHostName = strServer.GetBuffer(INTERNET_MAX_HOST_NAME_LENGTH+1);
	urlComponents.dwUrlPathLength = INTERNET_MAX_PATH_LENGTH;
	urlComponents.lpszUrlPath = strObject.GetBuffer(INTERNET_MAX_PATH_LENGTH+1);
	urlComponents.dwUserNameLength = INTERNET_MAX_USER_NAME_LENGTH;
	urlComponents.lpszUserName = strUsername.GetBuffer(INTERNET_MAX_USER_NAME_LENGTH+1);
	urlComponents.dwPasswordLength = INTERNET_MAX_PASSWORD_LENGTH;
	urlComponents.lpszPassword = strPassword.GetBuffer(INTERNET_MAX_PASSWORD_LENGTH+1);

	BOOL bRetVal = _AfxParseURLWorker(pstrURL, &urlComponents,
					dwServiceType, nPort, dwFlags);

	strServer.ReleaseBuffer();
	strObject.ReleaseBuffer();
	strUsername.ReleaseBuffer();
	strPassword.ReleaseBuffer();
	return bRetVal;
}

BOOL AFXAPI AfxParseURL(LPCTSTR pstrURL, DWORD& dwServiceType,
	CString& strServer, CString& strObject, INTERNET_PORT& nPort)
{
	dwServiceType = AFX_INET_SERVICE_UNK;

	ASSERT(pstrURL != NULL);
	if (pstrURL == NULL)
		return FALSE;

	URL_COMPONENTS urlComponents;
	memset(&urlComponents, 0, sizeof(URL_COMPONENTS));
	urlComponents.dwStructSize = sizeof(URL_COMPONENTS);

	urlComponents.dwHostNameLength = INTERNET_MAX_URL_LENGTH;
	urlComponents.lpszHostName = strServer.GetBuffer(INTERNET_MAX_URL_LENGTH+1);
	urlComponents.dwUrlPathLength = INTERNET_MAX_URL_LENGTH;
	urlComponents.lpszUrlPath = strObject.GetBuffer(INTERNET_MAX_URL_LENGTH+1);

	BOOL bRetVal = _AfxParseURLWorker(pstrURL, &urlComponents,
					dwServiceType, nPort, ICU_BROWSER_MODE);

	strServer.ReleaseBuffer();
	strObject.ReleaseBuffer();
	return bRetVal;
}


DWORD AFXAPI AfxGetInternetHandleType(HINTERNET hQuery)
{
	DWORD dwServiceType;
	DWORD dwTypeLen = sizeof(dwServiceType);
	if (hQuery == NULL ||
		!InternetQueryOption(hQuery, INTERNET_OPTION_HANDLE_TYPE,
			&dwServiceType, &dwTypeLen))
		return AFX_INET_SERVICE_UNK;
	else
		return dwServiceType;
}

AFX_STATIC BOOL AFXAPI
_AfxQueryCStringInternetOption(HINTERNET hHandle, DWORD dwOption, CString& refString)
{
	DWORD dwLength = 0;
	LPTSTR pstrBuffer;

	if (hHandle == NULL)
		return FALSE;

	if (!InternetQueryOption(hHandle, dwOption, NULL, &dwLength) &&
		GetLastError() != ERROR_INSUFFICIENT_BUFFER)
	{
		refString.Empty();
		return FALSE;
	}

	pstrBuffer = refString.GetBuffer(dwLength);
	BOOL bRet = InternetQueryOption(hHandle, dwOption, pstrBuffer, &dwLength);
	refString.ReleaseBuffer();
	return bRet;
}

#ifdef _DEBUG
void AFXAPI AfxInternetStatusCallbackDebug(HINTERNET hInternet,
	DWORD dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation,
	DWORD dwStatusInformationLength)
{
	UNUSED_ALWAYS(hInternet);
	TRACE1("Internet ctxt=%d: ", dwContext);

	switch (dwInternetStatus)
	{
	case INTERNET_STATUS_RESOLVING_NAME:
		TRACE1("resolving name for %s\n", lpvStatusInformation);
		break;

	case INTERNET_STATUS_NAME_RESOLVED:
		TRACE1("resolved name for %s!\n", lpvStatusInformation);
		break;

	case INTERNET_STATUS_HANDLE_CREATED:
		TRACE1("handle %8.8X created\n", hInternet);
		break;

	case INTERNET_STATUS_CONNECTING_TO_SERVER:
		{
		sockaddr* pSockAddr = (sockaddr*) lpvStatusInformation;
		TRACE1("connecting to socket address \"%s\"\n", pSockAddr->sa_data);
		}
		break;

	case INTERNET_STATUS_REQUEST_SENT:
		TRACE0("request sent!\n");
		break;

	case INTERNET_STATUS_SENDING_REQUEST:
		TRACE0("sending request...\n");
		break;

	case INTERNET_STATUS_CONNECTED_TO_SERVER:
		TRACE0("connected to socket address!\n");
		break;

	case INTERNET_STATUS_RECEIVING_RESPONSE:
		TRACE0("receiving response...\n");
		break;

	case INTERNET_STATUS_RESPONSE_RECEIVED:
		TRACE0("response received!\n");
		break;

	case INTERNET_STATUS_CLOSING_CONNECTION:
		TRACE1("closing connection %8.8X\n", hInternet);
		break;

	case INTERNET_STATUS_CONNECTION_CLOSED:
		TRACE1("connection %8.8X closed!\n", hInternet);
		break;

	case INTERNET_STATUS_HANDLE_CLOSING:
		TRACE1("handle %8.8X closed!\n", hInternet);
		break;

	case INTERNET_STATUS_REQUEST_COMPLETE:
		if (dwStatusInformationLength == sizeof(INTERNET_ASYNC_RESULT))
		{
			INTERNET_ASYNC_RESULT* pResult = (INTERNET_ASYNC_RESULT*) lpvStatusInformation;
			TRACE2("request complete, dwResult = %8.8X, dwError = %8.8X\n",
				pResult->dwResult, pResult->dwError);
		}
		else
			TRACE0("request complete.\n");
		break;

	case INTERNET_STATUS_CTL_RESPONSE_RECEIVED:
	case INTERNET_STATUS_REDIRECT:
	default:
		TRACE1("Unknown status: %d\n", dwInternetStatus);
		break;
	}

	return;
}
#endif // _DEBUG

void AFXAPI AfxInternetStatusCallback(HINTERNET hInternet, DWORD dwContext,
	DWORD dwInternetStatus, LPVOID lpvStatusInformation,
	DWORD dwStatusInformationLength)
{
	CInternetSession* pSession;

#ifdef _DEBUG
	if (afxTraceFlags & traceInternet)
		AfxInternetStatusCallbackDebug(hInternet, dwContext,
			dwInternetStatus, lpvStatusInformation, dwStatusInformationLength);
#endif

	if (_afxSessionMap.Lookup(hInternet, pSession))
	{
		pSession->OnStatusCallback(dwContext, dwInternetStatus,
			lpvStatusInformation, dwStatusInformationLength);
	}

	// note that an entry we can't match is simply ignored as
	// WININET can send notifications for handles that we can't
	// see -- such as when using InternetOpenURL()
}


/////////////////////////////////////////////////////////////////////////////
// CInternetSession

CInternetSession::~CInternetSession()
{
	Close();
}

CInternetSession::CInternetSession(LPCTSTR pstrAgent /* = NULL */,
	DWORD dwContext /* = 1 */,
	DWORD dwAccessType /* = PRE_CONFIG_INTERNET_ACCESS */,
	LPCTSTR pstrProxyName /* = NULL */,
	LPCTSTR pstrProxyBypass /* = NULL */,
	DWORD dwFlags /* = 0 */)
{
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);
	m_bCallbackEnabled = FALSE;
	m_pOldCallback = NULL;

	m_dwContext = dwContext;
	if (pstrAgent == NULL)
		pstrAgent = AfxGetAppName();
	m_hSession = InternetOpen(pstrAgent, dwAccessType,
		pstrProxyName, pstrProxyBypass, dwFlags);

	if (m_hSession == NULL)
		AfxThrowInternetException(m_dwContext);
	else
		_afxSessionMap.SetAt(m_hSession, this);
}

void CInternetSession::Close()
{
	if (m_bCallbackEnabled)
		EnableStatusCallback(FALSE);

	if (m_hSession != NULL)
	{
		InternetCloseHandle(m_hSession);
		_afxSessionMap.RemoveKey(m_hSession);
		m_hSession = NULL;
	}
}

CGopherConnection* CInternetSession::GetGopherConnection(LPCTSTR pstrServer,
	LPCTSTR pstrUserName /* = NULL */, LPCTSTR pstrPassword /* = NULL */,
	INTERNET_PORT nPort /* = INTERNET_INVALID_PORT_NUMBER */)
{
	ASSERT(AfxIsValidString(pstrServer));

	CGopherConnection* pReturn = new CGopherConnection(this,
		pstrServer, pstrUserName, pstrPassword, m_dwContext, nPort);
	return pReturn;
}

CFtpConnection* CInternetSession::GetFtpConnection(LPCTSTR pstrServer,
	LPCTSTR pstrUserName /* = NULL */, LPCTSTR pstrPassword /* = NULL */,
	INTERNET_PORT nPort /* = INTERNET_INVALID_PORT_NUMBER */,
	BOOL bPassive /* = FALSE */)
{
	ASSERT(AfxIsValidString(pstrServer));

	CFtpConnection* pReturn = new CFtpConnection(this,
		pstrServer, pstrUserName, pstrPassword, m_dwContext,
		nPort, bPassive);
	return pReturn;
}

CHttpConnection* CInternetSession::GetHttpConnection(LPCTSTR pstrServer,
	INTERNET_PORT nPort /* = INTERNET_INVALID_PORT_NUMBER */,
	LPCTSTR pstrUserName /* = NULL */, LPCTSTR pstrPassword /* = NULL */)
{
	ASSERT(AfxIsValidString(pstrServer));

	CHttpConnection* pReturn = new CHttpConnection(this,
		pstrServer, nPort, pstrUserName, pstrPassword, m_dwContext);
	return pReturn;
}

CHttpConnection* CInternetSession::GetHttpConnection(LPCTSTR pstrServer,
	DWORD dwFlags, INTERNET_PORT nPort /* = INTERNET_INVALID_PORT_NUMBER */,
	LPCTSTR pstrUserName /* = NULL */, LPCTSTR pstrPassword /* = NULL */)
{
	ASSERT(AfxIsValidString(pstrServer));
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);

	CHttpConnection* pReturn = new CHttpConnection(this, pstrServer,
			dwFlags, nPort, pstrUserName, pstrPassword, m_dwContext);
	return pReturn;
}

CStdioFile* CInternetSession::OpenURL(LPCTSTR pstrURL,
	DWORD dwContext /* = 0 */, DWORD dwFlags /* = INTERNET_FLAG_TRANSFER_BINARY */,
	LPCTSTR pstrHeaders /* = NULL */, DWORD dwHeadersLength /* = 0 */)
{
	ASSERT(AfxIsValidString(pstrURL));
	ASSERT(dwHeadersLength == 0 || pstrHeaders != NULL);
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);

	// must have TRANSFER_BINARY or TRANSFER_ASCII but not both
#define _AFX_TRANSFER_MASK (INTERNET_FLAG_TRANSFER_BINARY | INTERNET_FLAG_TRANSFER_ASCII)
	ASSERT((dwFlags & _AFX_TRANSFER_MASK) != 0);
	ASSERT((dwFlags & _AFX_TRANSFER_MASK) != _AFX_TRANSFER_MASK);

	if (dwContext == 1)
		dwContext = m_dwContext;

	DWORD dwServiceType;
	CString strServer;
	CString strObject;
	INTERNET_PORT nPort;
	CStdioFile* pReturn;

	BOOL bParsed = AfxParseURL(pstrURL, dwServiceType, strServer, strObject, nPort);

	// if it turns out to be a file...
	if (bParsed && dwServiceType == AFX_INET_SERVICE_FILE)
	{
		int nMode = CFile::modeRead | CFile::shareCompat;
		if (dwFlags & INTERNET_FLAG_TRANSFER_BINARY)
			nMode |= CFile::typeBinary;
		else
			nMode |= CFile::typeText;

		pReturn = new CStdioFile(strObject, nMode);
	}
	else
	{
		HINTERNET hOpener;

		hOpener = InternetOpenUrl(m_hSession, pstrURL, pstrHeaders,
			dwHeadersLength, dwFlags, dwContext);

		if (hOpener == NULL)
			AfxThrowInternetException(m_dwContext);

		if (!bParsed)
			dwServiceType = AfxGetInternetHandleType(hOpener);

		switch (dwServiceType)
		{
			case INTERNET_HANDLE_TYPE_GOPHER_FILE:
			case AFX_INET_SERVICE_GOPHER:
			//WINBUG: WININET supplies no way to
			// convert from a URL to a Gopher locator
				pReturn = new CGopherFile(hOpener, m_hSession, _T(""),
					0, dwContext);
				_afxSessionMap.SetAt(hOpener, this);
				break;

			case INTERNET_HANDLE_TYPE_FTP_FILE:
			case AFX_INET_SERVICE_FTP:
				pReturn = new CInternetFile(hOpener, m_hSession, strObject,
					strServer, dwContext, TRUE);
				_afxSessionMap.SetAt(hOpener, this);
				break;

			case INTERNET_HANDLE_TYPE_HTTP_REQUEST:
			case AFX_INET_SERVICE_HTTP:
			case AFX_INET_SERVICE_HTTPS:
				pReturn = new CHttpFile(hOpener, m_hSession, strObject, strServer,
					CHttpConnection::szHtmlVerbs[CHttpConnection::HTTP_VERB_GET],
					dwContext);
				_afxSessionMap.SetAt(hOpener, this);
				break;

			default:
				TRACE1("Error: Unidentified service type: %8.8X\n", dwServiceType);
				pReturn = NULL;
		}
	}

	return pReturn;
}

BOOL CInternetSession::SetOption(DWORD dwOption, LPVOID lpBuffer,
	DWORD dwBufferLength, DWORD dwFlags /* = 0 */)
{
	ASSERT(AfxIsValidAddress(lpBuffer, dwBufferLength, FALSE));
	ASSERT(dwOption >= INTERNET_FIRST_OPTION &&
		dwOption <= INTERNET_LAST_OPTION);
	ASSERT(dwBufferLength != 0);
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);

	// bogus flag?
	ASSERT(dwFlags == 0 || ((dwFlags & ISO_VALID_FLAGS) == dwFlags));

	return InternetSetOptionEx(m_hSession, dwOption,
		lpBuffer, dwBufferLength, dwFlags);
}

BOOL CInternetSession::QueryOption(DWORD dwOption, LPVOID lpBuffer,
	LPDWORD lpdwBufferLength) const
{
	ASSERT(dwOption >= INTERNET_FIRST_OPTION &&
		dwOption <= INTERNET_LAST_OPTION);
	ASSERT_POINTER(lpdwBufferLength, DWORD);
	ASSERT(AfxIsValidAddress(lpBuffer, *lpdwBufferLength));
	ASSERT(*lpdwBufferLength != 0);

	return InternetQueryOption(m_hSession, dwOption,
		lpBuffer, lpdwBufferLength);
}

BOOL CInternetSession::QueryOption(DWORD dwOption, DWORD& dwValue) const
{
	DWORD dwLen = sizeof(DWORD);
	return InternetQueryOption(m_hSession, dwOption,
		&dwValue, &dwLen);
}

BOOL CInternetSession::QueryOption(DWORD dwOption, CString& refString) const
{
	ASSERT(dwOption >= INTERNET_FIRST_OPTION &&
		dwOption <= INTERNET_LAST_OPTION);

	return _AfxQueryCStringInternetOption(m_hSession, dwOption, refString);
}

void CInternetSession::OnStatusCallback(DWORD dwContext,
	DWORD dwInternetStatus, LPVOID lpvStatusInformation,
	DWORD dwStatusInformationLength)
{
	ASSERT(m_bCallbackEnabled != NULL);

	if (m_pOldCallback != NULL)
	{
		(*m_pOldCallback)(m_hSession, dwContext, dwInternetStatus,
			lpvStatusInformation, dwStatusInformationLength);
	}
}

BOOL CInternetSession::EnableStatusCallback(BOOL bEnable /* = TRUE */)
{
	ASSERT(!bEnable || m_hSession != NULL);
	if (m_hSession == NULL)
		return FALSE;

	BOOL bResult = TRUE;

	if (bEnable)
	{
		ASSERT(!m_bCallbackEnabled);
		if (!m_bCallbackEnabled)
		{
			INTERNET_STATUS_CALLBACK pRet =
				InternetSetStatusCallback(m_hSession, AfxInternetStatusCallback);

			if (pRet != INTERNET_INVALID_STATUS_CALLBACK)
			{
				m_pOldCallback = pRet;
				m_bCallbackEnabled = TRUE;
			}
			else
				AfxThrowInternetException(m_dwContext);
		}
	}
	else
	{
		ASSERT(m_bCallbackEnabled);

		if (m_bCallbackEnabled)
		{
			InternetSetStatusCallback(m_hSession, NULL);
			m_bCallbackEnabled = FALSE;
		}
	}

	return bResult;
}

BOOL CInternetSession::SetCookie(LPCSTR pstrUrl, LPCTSTR pstrCookieName, LPCTSTR pstrCookieData)
{
	ASSERT(AfxIsValidString(pstrUrl));
	ASSERT(AfxIsValidString(pstrCookieName));
	return InternetSetCookie(pstrUrl, pstrCookieName, pstrCookieData);
}

BOOL CInternetSession::GetCookie(LPCSTR pstrUrl, LPCTSTR pstrCookieName, LPTSTR pstrCookieData, DWORD dwBufLen)
{
	ASSERT(AfxIsValidString(pstrUrl));
	ASSERT(AfxIsValidString(pstrCookieName));
	ASSERT(pstrCookieData != NULL);
	return InternetGetCookie(pstrUrl, pstrCookieName, pstrCookieData, &dwBufLen);
}

DWORD CInternetSession::GetCookieLength(LPCSTR pstrUrl, LPCTSTR pstrCookieName)
{
	ASSERT(AfxIsValidString(pstrUrl));
	ASSERT(AfxIsValidString(pstrCookieName));

	DWORD dwRet;
	if (!InternetGetCookie(pstrUrl, pstrCookieName, NULL, &dwRet))
		dwRet = 0;
	return dwRet;
}

BOOL CInternetSession::GetCookie(LPCSTR pstrUrl, LPCTSTR pstrCookieName, CString& strCookieData)
{
	ASSERT(AfxIsValidString(pstrUrl));
	ASSERT(AfxIsValidString(pstrCookieName));

	DWORD dwLen = GetCookieLength(pstrUrl, pstrCookieName);

	LPTSTR pstrTarget = strCookieData.GetBuffer(dwLen+1);
	BOOL bRetVal = InternetGetCookie(pstrUrl, pstrCookieName, pstrTarget, &dwLen);
	strCookieData.ReleaseBuffer(dwLen);

	if (!bRetVal)
		strCookieData.Empty();
	return bRetVal;
}

#ifdef _DEBUG
void CInternetSession::Dump(CDumpContext& dc) const
{
	CObject::Dump(dc);
	dc << "m_hSession = " << m_hSession;
	dc << "\nm_dwContext = " << m_dwContext;
}
#endif


/////////////////////////////////////////////////////////////////////////////
// Internet Files

CInternetFile::CInternetFile(HINTERNET hFile, HINTERNET /* hSession */,
	LPCTSTR pstrFileName, LPCTSTR pstrServer, DWORD dwContext, BOOL bReadMode)
	: m_dwContext(dwContext)
{
	// caller must set _afxSessionMap()!

	ASSERT(AfxIsValidString(pstrServer));
	ASSERT(AfxIsValidString(pstrFileName));
	ASSERT(hFile != NULL);

	m_strFileName = pstrFileName;
	m_strServerName = pstrServer;

	m_hFile = hFile;
	m_bReadMode = bReadMode;

	m_pbReadBuffer = NULL;
	m_pbWriteBuffer = NULL;

	m_nReadBufferSize = 0;
	m_nReadBufferPos = 0;
	m_nWriteBufferSize = 0;
	m_nWriteBufferPos = 0;
	m_nReadBufferBytes = 0;
}

CInternetFile::CInternetFile(HINTERNET hFile,
	LPCTSTR pstrFileName, CInternetConnection* pConnection, BOOL bReadMode)
{
	ASSERT(AfxIsValidString(pstrFileName));
	ASSERT(pConnection != NULL);
	ASSERT_VALID(pConnection);
	ASSERT(hFile != NULL);

	_afxSessionMap.SetAt(hFile, pConnection->GetSession());

	m_strFileName = pstrFileName;

	m_dwContext = pConnection->GetContext();
	m_strServerName = pConnection->GetServerName();
	m_hFile = hFile;
	m_bReadMode = bReadMode;

	m_pbReadBuffer = NULL;
	m_pbWriteBuffer = NULL;

	m_nReadBufferSize = 0;
	m_nReadBufferPos = 0;
	m_nWriteBufferSize = 0;
	m_nWriteBufferPos = 0;
	m_nReadBufferBytes = 0;
}

BOOL CInternetFile::QueryOption(DWORD dwOption, LPVOID lpBuffer,
	LPDWORD lpdwBufferLength) const
{
	ASSERT(dwOption >= INTERNET_FIRST_OPTION &&
		dwOption <= INTERNET_LAST_OPTION);
	ASSERT_POINTER(lpdwBufferLength, DWORD);
	ASSERT(AfxIsValidAddress(lpBuffer, *lpdwBufferLength));
	ASSERT(*lpdwBufferLength != 0);
	ASSERT(m_hFile != NULL);

	return InternetQueryOption(m_hFile, dwOption,
		lpBuffer, lpdwBufferLength);
}

BOOL CInternetFile::QueryOption(DWORD dwOption, DWORD& dwValue) const
{
	ASSERT(m_hFile != NULL);

	DWORD dwLen = sizeof(DWORD);
	return InternetQueryOption(m_hFile, dwOption,
		&dwValue, &dwLen);
}

BOOL CInternetFile::QueryOption(DWORD dwOption, CString& refString) const
{
	ASSERT(dwOption >= INTERNET_FIRST_OPTION &&
		dwOption <= INTERNET_LAST_OPTION);
	ASSERT(m_hFile != NULL);

	return _AfxQueryCStringInternetOption(m_hFile, dwOption, refString);
}

BOOL CInternetFile::SetOption(DWORD dwOption, LPVOID lpBuffer,
	DWORD dwBufferLength, DWORD dwFlags /* = 0 */)
{
	ASSERT(dwOption >= INTERNET_FIRST_OPTION &&
		dwOption <= INTERNET_LAST_OPTION);
	ASSERT(AfxIsValidAddress(lpBuffer, dwBufferLength, FALSE));
	ASSERT(dwBufferLength != 0);
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);

	// bogus flag?
	ASSERT(dwFlags == 0 || ((dwFlags & ISO_VALID_FLAGS) == dwFlags));

	return InternetSetOptionEx(m_hFile, dwOption,
		lpBuffer, dwBufferLength, dwFlags);
}

BOOL CInternetFile::SetReadBufferSize(UINT nReadSize)
{
	ASSERT_VALID(this);
	BOOL bRet = TRUE;

	if (nReadSize != -1 && nReadSize != m_nReadBufferSize)
	{
		if (m_nReadBufferPos > nReadSize)
			bRet = FALSE;
		else
		{
			if (nReadSize == 0)
			{
				delete [] m_pbReadBuffer;
				m_pbReadBuffer = NULL;
			}
			else if (m_pbReadBuffer == NULL)
			{
				m_pbReadBuffer = new BYTE[nReadSize];
				m_nReadBufferPos = nReadSize;
			}
			else
			{
				DWORD dwMoved = m_nReadBufferSize - m_nReadBufferPos;
				LPBYTE pbTemp = m_pbReadBuffer;
				m_pbReadBuffer = new BYTE[nReadSize];

				if (dwMoved > 0)
				{
					memcpy(m_pbReadBuffer, pbTemp + m_nReadBufferPos, dwMoved);
					m_nReadBufferPos = 0;
					m_nReadBufferBytes = dwMoved;
				}
				else
				{
					m_nReadBufferBytes = 0;
					m_nReadBufferPos = nReadSize;
				}
				delete [] pbTemp;
			}

			m_nReadBufferSize = nReadSize;
		}
	}

	return bRet;
}

BOOL CInternetFile::SetWriteBufferSize(UINT nWriteSize)
{
	ASSERT_VALID(this);
	BOOL bRet = TRUE;

	if (nWriteSize != m_nWriteBufferSize)
	{
		if (m_nWriteBufferPos > nWriteSize)
			Flush();

		if (nWriteSize == 0)
		{
			delete [] m_pbWriteBuffer;
			m_pbWriteBuffer = NULL;
		}
		else if (m_pbWriteBuffer == NULL)
		{
			m_pbWriteBuffer = new BYTE[nWriteSize];
			m_nWriteBufferPos = 0;
		}
		else
		{
			LPBYTE pbTemp = m_pbWriteBuffer;
			m_pbWriteBuffer = new BYTE[nWriteSize];

			memcpy(m_pbWriteBuffer, pbTemp, m_nWriteBufferPos);
			delete [] pbTemp;
		}

		m_nWriteBufferSize = nWriteSize;
	}

	return bRet;
}

LONG CInternetFile::Seek(LONG lOffset, UINT nFrom)
{
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);
	ASSERT(m_bReadMode);
	ASSERT(m_pbReadBuffer == NULL);

	// can't do this on a file for writing
	// can't do this on a file that's got a buffer

	if (!m_bReadMode || m_pbReadBuffer != NULL)
		AfxThrowInternetException(m_dwContext, ERROR_INVALID_HANDLE);

	switch (nFrom)
	{
		case begin:
			nFrom = FILE_BEGIN;
			break;

		case current:
			nFrom = FILE_CURRENT;
			break;

		case end:
			nFrom = FILE_END;
			break;

		default:
			ASSERT(FALSE);  // got a bogus nFrom value
			AfxThrowInternetException(m_dwContext, ERROR_INVALID_PARAMETER);
			break;
	}

	LONG lRet;
	lRet = InternetSetFilePointer(m_hFile, lOffset, NULL, nFrom, m_dwContext);
	if (lRet == -1)
		AfxThrowInternetException(m_dwContext);

	return lRet;
}

CInternetFile::~CInternetFile()
{
	if (m_hFile != NULL)
	{
#ifdef _DEBUG
		USES_CONVERSION;
		LPCTSTR pszName = A2CT(GetRuntimeClass()->m_lpszClassName);
		TRACE2("Warning: destroying an open %s with handle %8.8X\n",
			pszName, m_hFile);
#endif
		Close();
	}

	if (m_pbReadBuffer != NULL)
		delete m_pbReadBuffer;

	if (m_pbWriteBuffer != NULL)
		delete m_pbWriteBuffer;
}

void CInternetFile::Abort()
{
	ASSERT_VALID(this);
	if (m_hFile != NULL)
		Close();
	m_strFileName.Empty();
}

void CInternetFile::Flush()
{
	if (m_pbWriteBuffer != NULL && m_nWriteBufferPos > 0)
	{
		DWORD dwBytes;

		if (!InternetWriteFile(m_hFile, m_pbWriteBuffer,
				m_nWriteBufferPos, &dwBytes))
			AfxThrowInternetException(m_dwContext);

		if (dwBytes != m_nWriteBufferPos)
			AfxThrowInternetException(m_dwContext);

		m_nWriteBufferPos = 0;
	}
}

void CInternetFile::Close()
{
	if (m_hFile != NULL)
	{
		Flush();
		InternetCloseHandle(m_hFile);
		_afxSessionMap.RemoveKey(m_hFile);
		m_hFile = NULL;

		if (m_pbWriteBuffer != NULL)
		{
			delete [] m_pbWriteBuffer;
			m_pbWriteBuffer = NULL;
		}

		if (m_pbReadBuffer != NULL)
		{
			delete [] m_pbReadBuffer;
			m_pbReadBuffer = NULL;
		}
	}
}

UINT CInternetFile::Read(LPVOID lpBuf, UINT nCount)
{
	ASSERT_VALID(this);
	ASSERT(AfxIsValidAddress(lpBuf, nCount));
	ASSERT(m_hFile != NULL);
	ASSERT(m_bReadMode);

	DWORD dwBytes;

	if (!m_bReadMode || m_hFile == NULL)
		AfxThrowInternetException(m_dwContext, ERROR_INVALID_HANDLE);

	if (m_pbReadBuffer == NULL)
	{
		if (!InternetReadFile(m_hFile, (LPVOID) lpBuf, nCount, &dwBytes))
				AfxThrowInternetException(m_dwContext);
		return dwBytes;
	}

	LPBYTE lpbBuf = (LPBYTE) lpBuf;

	// if the requested size is bigger than our buffer,
	// then handle it directly

	if (nCount >= m_nReadBufferSize)
	{
		DWORD dwMoved = max(0, (long)m_nReadBufferBytes - (long)m_nReadBufferPos);
		memcpy(lpBuf, m_pbReadBuffer + m_nReadBufferPos, dwMoved);
		m_nReadBufferPos = m_nReadBufferSize;
		if (!InternetReadFile(m_hFile, lpbBuf+dwMoved, nCount-dwMoved, &dwBytes))
				AfxThrowInternetException(m_dwContext);
		dwBytes += dwMoved;
	}
	else
	{
		if (m_nReadBufferPos + nCount >= m_nReadBufferBytes)
		{
			DWORD dwMoved = max(0, (long)m_nReadBufferBytes - (long)m_nReadBufferPos);
			memcpy(lpbBuf, m_pbReadBuffer + m_nReadBufferPos, dwMoved);

			DWORD dwRead;
			if (!InternetReadFile(m_hFile, m_pbReadBuffer, m_nReadBufferSize,
					&dwRead))
				AfxThrowInternetException(m_dwContext);
			m_nReadBufferBytes = dwRead;

			dwRead = min(nCount - dwMoved, m_nReadBufferBytes);
			memcpy(lpbBuf + dwMoved, m_pbReadBuffer, dwRead);
			m_nReadBufferPos = dwRead;
			dwBytes = dwMoved + dwRead;
		}
		else
		{
			memcpy(lpbBuf, m_pbReadBuffer + m_nReadBufferPos, nCount);
			m_nReadBufferPos += nCount;
			dwBytes = nCount;
		}
	}

	return dwBytes;
}

void CInternetFile::Write(const void* lpBuf, UINT nCount)
{
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);
	ASSERT(AfxIsValidAddress(lpBuf, nCount, FALSE));
	ASSERT(m_bReadMode == FALSE || m_bReadMode == -1);

	if (m_bReadMode == TRUE || m_hFile == NULL)
		AfxThrowInternetException(m_dwContext, ERROR_INVALID_HANDLE);

	DWORD dwBytes;
	if (m_pbWriteBuffer == NULL)
	{
		if (!InternetWriteFile(m_hFile, lpBuf, nCount, &dwBytes))
			AfxThrowInternetException(m_dwContext);

		if (dwBytes != nCount)
			AfxThrowInternetException(m_dwContext);
	}
	else
	{
		if ((m_nWriteBufferPos + nCount) >= m_nWriteBufferSize)
		{
			// write what is in the buffer just now

			if (!InternetWriteFile(m_hFile, m_pbWriteBuffer,
					m_nWriteBufferPos, &dwBytes))
				AfxThrowInternetException(m_dwContext);

			// reset the buffer position since it is now clean

			m_nWriteBufferPos = 0;
		}

		// if we can't hope to buffer the write request,
		// do it immediately ... otherwise, buffer it!

		if (nCount >= m_nWriteBufferSize)
		{
			if (!InternetWriteFile(m_hFile, (LPVOID) lpBuf, nCount, &dwBytes))
				AfxThrowInternetException(m_dwContext);
		}
		else
		{
			memcpy(m_nWriteBufferPos + m_pbWriteBuffer, lpBuf, nCount);
			m_nWriteBufferPos += nCount;
		}
	}
}

void CInternetFile::WriteString(LPCTSTR pstr)
{
	ASSERT(m_bReadMode == TRUE || m_bReadMode == -1);
	ASSERT(AfxIsValidString(pstr));
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	if (m_bReadMode == TRUE)
		AfxThrowInternetException(m_dwContext, ERROR_INVALID_HANDLE);

	Write(pstr, lstrlen(pstr));
}

LPTSTR CInternetFile::ReadString(LPTSTR pstr, UINT nMax)
{
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);
	ASSERT(AfxIsValidAddress(pstr, nMax*sizeof(TCHAR)));
	DWORD dwRead;

	// if we're reading line-by-line, we must have a buffer

	if (m_pbReadBuffer == NULL)
	{
		if (!SetReadBufferSize(4096))   // arbitrary but reasonable
			return NULL;
		if (!InternetReadFile(m_hFile, m_pbReadBuffer, m_nReadBufferSize,
				&dwRead))
			AfxThrowInternetException(m_dwContext);
		m_nReadBufferBytes = dwRead;
		m_nReadBufferPos = 0;
	}

	LPTSTR pstrChar = (LPTSTR) (m_pbReadBuffer + m_nReadBufferPos);
	LPTSTR pstrTarget = pstr;

	while (--nMax)
	{
		if (m_nReadBufferPos >= m_nReadBufferBytes)
		{
			if (!InternetReadFile(m_hFile, m_pbReadBuffer, m_nReadBufferSize,
					&dwRead))
				AfxThrowInternetException(m_dwContext);
			m_nReadBufferBytes = dwRead;
			if (m_nReadBufferBytes == 0)
			{
				*pstrTarget = '\0';
				if (pstrTarget == pstr)
					return NULL;
				else
					return pstr;
			}
			else
			{
				m_nReadBufferPos = 0;
				pstrChar = (LPTSTR) m_pbReadBuffer;
			}
		}

		if (*pstrChar != '\r')
			*pstrTarget++ = *pstrChar;

		m_nReadBufferPos++;
		if (*pstrChar++ == '\n')
			break;
	}

	*pstrTarget = '\0';
	return pstr;
}

BOOL CInternetFile::ReadString(CString& rString)
{
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	rString = _T("");    // empty string without deallocating
	const int nMaxSize = 128;

	LPTSTR pstrPlace = rString.GetBuffer(nMaxSize);
	LPTSTR pstrResult;
	int nLen;

	do
	{
		pstrResult = ReadString(pstrPlace, nMaxSize);
		rString.ReleaseBuffer();

		// if string is read completely or EOF
		if (pstrResult == NULL ||
			(nLen = lstrlen(pstrPlace)) < (nMaxSize-1) ||
			pstrPlace[nLen-1] == '\n')
			break;

		nLen = rString.GetLength();
		pstrPlace = rString.GetBuffer(nMaxSize + nLen) + nLen;
	} while (1);

	// remove '\n' from end of string if present
	pstrPlace = rString.GetBuffer(0);
	nLen = rString.GetLength();
	if (nLen != 0 && pstrPlace[nLen-1] == '\n')
		pstrPlace[nLen-1] = '\0';
	rString.ReleaseBuffer();

	return (pstrResult != NULL);
}

DWORD CInternetFile::GetLength() const
{
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	DWORD dwRet = 0;

	if (m_hFile != NULL)
	{
		if (!InternetQueryDataAvailable(m_hFile, &dwRet, 0, 0))
			dwRet = 0;
	}

	return dwRet;
}

void CInternetFile::LockRange(DWORD /* dwPos */, DWORD /* dwCount */)
{
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	AfxThrowNotSupportedException();
}

void CInternetFile::UnlockRange(DWORD /* dwPos */, DWORD /* dwCount */)
{
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	AfxThrowNotSupportedException();
}

void CInternetFile::SetLength(DWORD)
{
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	AfxThrowNotSupportedException();
}

CFile* CInternetFile::Duplicate() const
{
	ASSERT_VALID(this);
	ASSERT(m_pStream != NULL);

	AfxThrowNotSupportedException();
	return NULL;
}

#ifdef _DEBUG
void CInternetFile::AssertValid() const
{
	// Don't call CStdioFile's AsssertValid()
	CFile::AssertValid();

	ASSERT(m_hConnection != NULL);

	// make sure we really have a decent handle
	if (m_hFile != NULL)
	{
		DWORD dwResult = AfxGetInternetHandleType(m_hFile);

		if (IsKindOf(RUNTIME_CLASS(CHttpFile)))
		{
			ASSERT(dwResult == INTERNET_HANDLE_TYPE_HTTP_REQUEST);
		}
		else if (IsKindOf(RUNTIME_CLASS(CGopherFile)))
		{
			ASSERT(dwResult == INTERNET_HANDLE_TYPE_GOPHER_FILE ||
				dwResult == INTERNET_HANDLE_TYPE_GOPHER_FIND_HTML ||
				dwResult == INTERNET_HANDLE_TYPE_GOPHER_FILE_HTML ||
				dwResult == INTERNET_HANDLE_TYPE_HTTP_REQUEST);
		}
		else if (IsKindOf(RUNTIME_CLASS(CInternetFile)))
		{
			ASSERT(dwResult == INTERNET_HANDLE_TYPE_FTP_FILE ||
				dwResult == INTERNET_HANDLE_TYPE_FTP_FILE_HTML ||
				dwResult == INTERNET_HANDLE_TYPE_FTP_FIND_HTML ||
				dwResult == INTERNET_HANDLE_TYPE_HTTP_REQUEST);
		}
		else
			ASSERT(FALSE);  // some bogus object!
	}
}

void CInternetFile::Dump(CDumpContext& dc) const
{
	CObject::Dump(dc);

	dc << "\na " << GetRuntimeClass()->m_lpszClassName;
	dc << " with handle " << (UINT)m_hFile;
}
#endif

/////////////////////////////////////////////////////////////////////////////
// CInternetConnection

CInternetConnection::CInternetConnection(CInternetSession* pSession,
	LPCTSTR pstrServerName,
	INTERNET_PORT nPort /* = INTERNET_INVALID_PORT_NUMBER */,
	DWORD dwContext /* = 1 */)
	: m_strServerName(pstrServerName)
{
	ASSERT(pSession != NULL);
	ASSERT_VALID(pSession);
	ASSERT(pstrServerName != NULL);

	m_nPort = nPort;
	m_pSession = pSession;
	m_hConnection = NULL;
	if (dwContext == 1)
		dwContext = pSession->GetContext();
	m_dwContext = dwContext;
}

CInternetConnection::~CInternetConnection()
{
	if (m_hConnection != NULL)
	{
#ifdef _DEBUG
		USES_CONVERSION;
		LPCTSTR pszName = A2CT(GetRuntimeClass()->m_lpszClassName);
		TRACE3("Warning: Disconnecting %s handle %8.8X in context %8.8X at destruction.\n",
			pszName, m_hConnection, m_dwContext);
#endif
		Close();
	}
}

BOOL CInternetConnection::SetOption(DWORD dwOption, LPVOID lpBuffer,
	DWORD dwBufferLength, DWORD dwFlags /* = 0 */)
{
	ASSERT(dwOption >= INTERNET_FIRST_OPTION &&
		dwOption <= INTERNET_LAST_OPTION);
	ASSERT(AfxIsValidAddress(lpBuffer, dwBufferLength, FALSE));
	ASSERT(dwBufferLength != 0);
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);

	// bogus flag?
	ASSERT(dwFlags == 0 || ((dwFlags & ISO_VALID_FLAGS) == dwFlags));

	return InternetSetOptionEx(m_hConnection, dwOption,
		lpBuffer, dwBufferLength, dwFlags);
}

BOOL CInternetConnection::QueryOption(DWORD dwOption, LPVOID lpBuffer,
	LPDWORD lpdwBufferLength) const
{
	ASSERT(dwOption >= INTERNET_FIRST_OPTION &&
		dwOption <= INTERNET_LAST_OPTION);
	ASSERT_POINTER(lpdwBufferLength, DWORD);
	ASSERT(AfxIsValidAddress(lpBuffer, *lpdwBufferLength));
	ASSERT(*lpdwBufferLength != 0);

	return InternetQueryOption(m_hConnection, dwOption,
		lpBuffer, lpdwBufferLength);
}

BOOL CInternetConnection::QueryOption(DWORD dwOption, DWORD& dwValue) const
{
	DWORD dwLen = sizeof(DWORD);
	return InternetQueryOption(m_hConnection, dwOption,
		&dwValue, &dwLen);
}

BOOL CInternetConnection::QueryOption(DWORD dwOption, CString& refString) const
{
	ASSERT(dwOption >= INTERNET_FIRST_OPTION &&
		dwOption <= INTERNET_LAST_OPTION);

	return _AfxQueryCStringInternetOption(m_hConnection, dwOption, refString);
}

void CInternetConnection::Close()
{
	if (m_hConnection != NULL)
	{
		InternetCloseHandle(m_hConnection);
		_afxSessionMap.RemoveKey(m_hConnection);
		m_hConnection = NULL;
	}
}

#ifdef _DEBUG
void CInternetConnection::Dump(CDumpContext& dc) const
{
	CObject::Dump(dc);
	dc << "m_hConnection = " << m_hConnection;
}

void CInternetConnection::AssertValid() const
{
	CObject::AssertValid();
}
#endif


/////////////////////////////////////////////////////////////////////////////
// CFtpConnection

CFtpConnection::~CFtpConnection()
{
}

CFtpConnection::CFtpConnection(CInternetSession* pSession,
	HINTERNET hConnected, LPCTSTR pstrServer, DWORD dwContext)
	: CInternetConnection(pSession, pstrServer, INTERNET_INVALID_PORT_NUMBER,
	dwContext)
{
	ASSERT(pSession != NULL);
	ASSERT(AfxIsValidString(pstrServer));

	BOOL bBadType = FALSE;
	if (AfxGetInternetHandleType(hConnected) != INTERNET_HANDLE_TYPE_CONNECT_FTP)
	{
		ASSERT(FALSE);      // used the wrong handle type
		bBadType = TRUE;
	}

	m_strServerName = pstrServer;
	m_hConnection = hConnected;
	if (m_hConnection == NULL || bBadType)
		AfxThrowInternetException(m_dwContext, ERROR_INVALID_HANDLE);
	else
		_afxSessionMap.SetAt(m_hConnection, m_pSession);
}

CFtpConnection::CFtpConnection(CInternetSession* pSession,
	LPCTSTR pstrServer, LPCTSTR pstrUserName /* = NULL */,
	LPCTSTR pstrPassword /* = NULL */, DWORD dwContext /* = 0 */,
	INTERNET_PORT nPort /* = INTERNET_INVALID_PORT_NUMBER */,
	BOOL bPassive /* = FALSE */)
	: CInternetConnection(pSession, pstrServer, nPort, dwContext)
{
	ASSERT(pSession != NULL);
	ASSERT_KINDOF(CInternetSession, pSession);
	ASSERT(AfxIsValidString(pstrServer));

	m_strServerName = pstrServer;

	m_hConnection = InternetConnect((HINTERNET) *pSession, pstrServer,
		nPort, pstrUserName, pstrPassword, INTERNET_SERVICE_FTP,
		(bPassive ? INTERNET_FLAG_PASSIVE : 0), m_dwContext);

	if (m_hConnection == NULL)
		AfxThrowInternetException(m_dwContext, ::GetLastError());
	else
		_afxSessionMap.SetAt(m_hConnection, m_pSession);
}

void CFtpConnection::Close()
{
	CInternetConnection::Close();
}

BOOL CFtpConnection::Remove(LPCTSTR pstrFileName)
{
	ASSERT_VALID(this);
	ASSERT(m_hConnection != NULL);
	ASSERT(AfxIsValidString(pstrFileName));

	return FtpDeleteFile(m_hConnection, pstrFileName);
}

BOOL CFtpConnection::Rename(LPCTSTR pstrExisting, LPCTSTR pstrNew)
{
	ASSERT_VALID(this);
	ASSERT(m_hConnection != NULL);
	ASSERT(AfxIsValidString(pstrExisting));
	ASSERT(AfxIsValidString(pstrNew));

	return FtpRenameFile(m_hConnection, pstrExisting, pstrNew);
}

BOOL CFtpConnection::CreateDirectory(LPCTSTR pstrDirName)
{
	ASSERT_VALID(this);
	ASSERT(m_hConnection != NULL);
	ASSERT(AfxIsValidString(pstrDirName));

	return FtpCreateDirectory(m_hConnection, pstrDirName);
}

BOOL CFtpConnection::RemoveDirectory(LPCTSTR pstrDirName)
{
	ASSERT_VALID(this);
	ASSERT(m_hConnection != NULL);
	ASSERT(AfxIsValidString(pstrDirName));

	return FtpRemoveDirectory(m_hConnection, pstrDirName);
}

BOOL CFtpConnection::SetCurrentDirectory(LPCTSTR pstrDirName)
{
	ASSERT_VALID(this);
	ASSERT(m_hConnection != NULL);
	ASSERT(AfxIsValidString(pstrDirName));

	return FtpSetCurrentDirectory(m_hConnection, pstrDirName);
}

BOOL CFtpConnection::GetCurrentDirectory(LPTSTR pstrDirName,
	LPDWORD lpdwLen) const
{
	ASSERT_VALID(this);
	ASSERT(m_hConnection != NULL);
	ASSERT(AfxIsValidAddress(pstrDirName, *lpdwLen));
	ASSERT(lpdwLen != 0);

	return FtpGetCurrentDirectory(m_hConnection, pstrDirName, lpdwLen);
}

BOOL CFtpConnection::GetCurrentDirectoryAsURL(CString& strDirName) const
{
	CString strDirectory;
	if (!GetCurrentDirectory(strDirectory))
		return FALSE;

	strDirName = _afxURLftp;
	strDirName += GetServerName();

	if (strDirectory[0] != '/')
		strDirName += '/';

	strDirName += strDirectory;
	return TRUE;
}

BOOL CFtpConnection::GetCurrentDirectoryAsURL(LPTSTR pstrName,
	LPDWORD lpdwLen) const
{
	ASSERT(lpdwLen != NULL);
	ASSERT_POINTER(lpdwLen, DWORD);
	ASSERT(AfxIsValidAddress(pstrName, *lpdwLen));
	ASSERT(*lpdwLen != 0);

	CString strTemp;

	if (lpdwLen == NULL || !GetCurrentDirectoryAsURL(strTemp))
		return FALSE;

	if (pstrName == NULL)
		*lpdwLen = strTemp.GetLength();
	else
		lstrcpyn(pstrName, (LPCTSTR) strTemp, max(0, *lpdwLen -1));

	return TRUE;
}

BOOL CFtpConnection::GetCurrentDirectory(CString& strDirName) const
{
	ASSERT_VALID(this);
	ASSERT(m_hConnection != NULL);

	DWORD dwLen = INTERNET_MAX_PATH_LENGTH;
	LPTSTR pstrTarget = strDirName.GetBufferSetLength(dwLen);
	BOOL bRet = FtpGetCurrentDirectory(m_hConnection, pstrTarget, &dwLen);

	if (bRet)
		strDirName.ReleaseBuffer(dwLen);
	else
		strDirName.ReleaseBuffer(0);

	return bRet;
}

CInternetFile* CFtpConnection::OpenFile(LPCTSTR pstrFileName,
	DWORD dwAccess /* = GENERIC_READ */,
	DWORD dwFlags /* = FTP_TRANSFER_TYPE_BINARY */,
	DWORD dwContext /* = 1 */)
{
	ASSERT_VALID(this);
	ASSERT(m_hConnection != NULL);
	ASSERT(dwAccess != (GENERIC_READ | GENERIC_WRITE));
	ASSERT(dwAccess == GENERIC_READ || dwAccess == GENERIC_WRITE);
	ASSERT(AfxIsValidString(pstrFileName));

	HINTERNET hFile;
	if (dwContext == 1)
		dwContext = m_dwContext;

	hFile = FtpOpenFile(m_hConnection, pstrFileName, dwAccess,
		dwFlags, dwContext);
	if (hFile == NULL)
		AfxThrowInternetException(dwContext);

	CInternetFile* pFile = new CInternetFile(hFile, pstrFileName, this,
		(dwAccess == GENERIC_READ));
	return pFile;
}

BOOL CFtpConnection::PutFile(LPCTSTR pstrLocalFile, LPCTSTR pstrRemoteFile,
	DWORD dwFlags /* = FTP_TRANSFER_TYPE_BINARY */,
	DWORD dwContext /* = 1 */)
{
	ASSERT_VALID(this);
	ASSERT(m_hConnection != NULL);
	ASSERT(AfxIsValidString(pstrRemoteFile));
	ASSERT(AfxIsValidString(pstrLocalFile));
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);

	if (dwContext == 1)
		dwContext = m_dwContext;

	return FtpPutFile(m_hConnection, pstrLocalFile, pstrRemoteFile,
		dwFlags, dwContext);
}

BOOL CFtpConnection::GetFile(LPCTSTR pstrRemoteFile, LPCTSTR pstrLocalFile,
	BOOL bFailIfExists /* = TRUE */,
	DWORD dwAttributes /* = FILE_ATTRIBUTE_NORMAL */,
	DWORD dwFlags /* = FTP_TRANSFER_TYPE_BINARY */, DWORD dwContext /* = 1 */)
{
	ASSERT_VALID(this);
	ASSERT(m_hConnection != NULL);
	ASSERT(AfxIsValidString(pstrRemoteFile));
	ASSERT(AfxIsValidString(pstrLocalFile));
	ASSERT(!(dwAttributes & FILE_ATTRIBUTE_DIRECTORY));
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);

	if (dwContext == 1)
		dwContext = m_dwContext;

	return FtpGetFile(m_hConnection, pstrRemoteFile, pstrLocalFile,
		bFailIfExists, dwAttributes, dwFlags, dwContext);
}

#ifdef _DEBUG
void CFtpConnection::Dump(CDumpContext& dc) const
{
	CInternetConnection::Dump(dc);
	dc << "\nm_strServerName = " << m_strServerName;
}

void CFtpConnection::AssertValid() const
{
	ASSERT(m_pSession != NULL);
	if (m_hConnection != NULL)
	{
		ASSERT(AfxGetInternetHandleType(m_hConnection)
				== INTERNET_HANDLE_TYPE_CONNECT_FTP);
	}
}
#endif


/////////////////////////////////////////////////////////////////////////////
// CGopherConnection

CGopherConnection::~CGopherConnection()
{
}

CGopherConnection::CGopherConnection(CInternetSession* pSession,
	LPCTSTR pstrServer, LPCTSTR pstrUserName /* = NULL */,
	LPCTSTR pstrPassword /* = NULL */, DWORD dwContext /* = 0 */,
	INTERNET_PORT nPort /* = INTERNET_INVALID_PORT_NUMBER */)
	: CInternetConnection(pSession, pstrServer, nPort, dwContext)
{
	ASSERT(pSession != NULL);
	ASSERT_KINDOF(CInternetSession, pSession);
	ASSERT(AfxIsValidString(pstrServer));

	m_hConnection = InternetConnect((HINTERNET) *pSession, pstrServer,
		nPort, pstrUserName, pstrPassword, INTERNET_SERVICE_GOPHER,
		0, m_dwContext);

	if (m_hConnection == NULL)
		AfxThrowInternetException(m_dwContext);
	else
		_afxSessionMap.SetAt(m_hConnection, m_pSession);
}

CGopherConnection::CGopherConnection(CInternetSession* pSession,
	HINTERNET hConnected, LPCTSTR pstrServer, DWORD dwContext)
	: CInternetConnection(pSession, pstrServer,
		INTERNET_INVALID_PORT_NUMBER, dwContext)
{
	ASSERT(pSession != NULL);
	ASSERT(AfxIsValidString(pstrServer));

	BOOL bBadType = FALSE;
	if (AfxGetInternetHandleType(hConnected) != INTERNET_HANDLE_TYPE_CONNECT_GOPHER)
	{
		ASSERT(FALSE);      // used the wrong handle type
		bBadType = TRUE;
	}

	m_hConnection = hConnected;
	if (m_hConnection == NULL || bBadType)
		AfxThrowInternetException(m_dwContext);
	else
		_afxSessionMap.SetAt(m_hConnection, m_pSession);
}

CGopherLocator CGopherConnection::CreateLocator(LPCTSTR pstrLocator)
{
	CGopherLocator ret(pstrLocator, lstrlen(pstrLocator));
	return ret;
}

CGopherLocator CGopherConnection::CreateLocator(LPCTSTR pstrServerName,
	LPCTSTR pstrDisplayString, LPCTSTR pstrSelectorString, DWORD dwGopherType,
	INTERNET_PORT nPort /* = INTERNET_INVALID_PORT_NUMBER */)
{
	TCHAR szLocator[MAX_GOPHER_LOCATOR_LENGTH];
	DWORD dwLocLen = MAX_GOPHER_LOCATOR_LENGTH;
	ASSERT(AfxIsValidString(pstrDisplayString));
	ASSERT(AfxIsValidString(pstrServerName));
	ASSERT(AfxIsValidString(pstrSelectorString));

	if (!GopherCreateLocator(pstrServerName, nPort,
			pstrDisplayString, pstrSelectorString, dwGopherType,
			szLocator, &dwLocLen))
		AfxThrowInternetException(0);

	CGopherLocator ret(szLocator, dwLocLen);
	return ret;
}

CGopherLocator CGopherConnection::CreateLocator(
	LPCTSTR pstrDisplayString, LPCTSTR pstrSelectorString, DWORD dwGopherType)
{
	TCHAR szLocator[MAX_GOPHER_LOCATOR_LENGTH];
	DWORD dwLocLen = MAX_GOPHER_LOCATOR_LENGTH;
	ASSERT(AfxIsValidString(pstrDisplayString));
	ASSERT(AfxIsValidString(pstrSelectorString));

	if (!GopherCreateLocator(m_strServerName, m_nPort,
			pstrDisplayString, pstrSelectorString, dwGopherType,
			szLocator, &dwLocLen))
		AfxThrowInternetException(m_dwContext);

	CGopherLocator ret(szLocator, dwLocLen);
	return ret;
}


BOOL CGopherConnection::GetAttribute(CGopherLocator& refLocator,
	CString strRequestedAttributes, CString& strResult)
{
	DWORD dwLen = 4*MIN_GOPHER_ATTRIBUTE_LENGTH; // more than the minimum
	BOOL bRet;
	LPTSTR pstrResult = strResult.GetBuffer(dwLen);

	if (!GopherGetAttribute(m_hConnection, (LPCTSTR) refLocator,
			pstrResult, NULL, dwLen, &dwLen,
			NULL, m_dwContext))
	{
		bRet = FALSE;
		strResult.ReleaseBuffer(0);
	}
	else
	{
		bRet = TRUE;
		strResult.ReleaseBuffer(dwLen);
	}

	return bRet;
}

CGopherFile* CGopherConnection::OpenFile(CGopherLocator& refLocator,
	DWORD dwFlags /* = 0 */, LPCTSTR pstrView /* = NULL */,
	DWORD dwContext /* = 1 */)
{
	ASSERT_VALID(this);
	ASSERT(m_hConnection != NULL);

	HINTERNET hFile;
	if (dwContext == 1)
		dwContext = m_dwContext;

	hFile = GopherOpenFile(m_hConnection, (LPCTSTR) refLocator, pstrView,
		dwFlags, dwContext);

	if (hFile == NULL)
		AfxThrowInternetException(dwContext);

	CGopherFile* pFile = new CGopherFile(hFile, refLocator, this);
	return pFile;
}

void CGopherConnection::Close()
{
	CInternetConnection::Close();
}

#ifdef _DEBUG
void CGopherConnection::Dump(CDumpContext& dc) const
{
	CInternetConnection::Dump(dc);
	dc << "\nm_strServerName = " << m_strServerName;
}

void CGopherConnection::AssertValid() const
{
	ASSERT(m_pSession != NULL);
	if (m_hConnection != NULL)
	{
		ASSERT(AfxGetInternetHandleType(m_hConnection)
				== INTERNET_HANDLE_TYPE_CONNECT_GOPHER);
	}
}
#endif


/////////////////////////////////////////////////////////////////////////////
// CHttpConnection

CHttpConnection::~CHttpConnection()
{
}

CHttpConnection::CHttpConnection(CInternetSession* pSession,
	HINTERNET hConnected, LPCTSTR pstrServer, DWORD dwContext /* = 0 */)
	: CInternetConnection(pSession, pstrServer, INTERNET_INVALID_PORT_NUMBER, dwContext)
{
	ASSERT(pSession != NULL);
	ASSERT(AfxIsValidString(pstrServer));

	BOOL bBadType = FALSE;
	if (AfxGetInternetHandleType(hConnected) != INTERNET_HANDLE_TYPE_CONNECT_HTTP)
	{
		ASSERT(FALSE);      // used the wrong handle type
		bBadType = TRUE;
	}

	m_hConnection = hConnected;
	if (m_hConnection == NULL || bBadType)
		AfxThrowInternetException(m_dwContext, ERROR_INVALID_HANDLE);
	else
		_afxSessionMap.SetAt(m_hConnection, m_pSession);
}

CHttpConnection::CHttpConnection(CInternetSession* pSession,
	LPCTSTR pstrServer,
	INTERNET_PORT nPort /* = INTERNET_INVALID_PORT_NUMBER */,
	LPCTSTR pstrUserName /* = NULL */,
	LPCTSTR pstrPassword /* = NULL */, DWORD dwContext /* = 1 */)
	: CInternetConnection(pSession, pstrServer, nPort, dwContext)
{
	ASSERT(pSession != NULL);
	ASSERT_KINDOF(CInternetSession, pSession);
	ASSERT(AfxIsValidString(pstrServer));

	m_hConnection = InternetConnect((HINTERNET) *pSession, pstrServer,
		nPort, pstrUserName, pstrPassword, INTERNET_SERVICE_HTTP,
		0, m_dwContext);

	if (m_hConnection == NULL)
		AfxThrowInternetException(m_dwContext);
	else
		_afxSessionMap.SetAt(m_hConnection, m_pSession);
}

CHttpConnection::CHttpConnection(CInternetSession* pSession,
	LPCTSTR pstrServer, DWORD dwFlags,
	INTERNET_PORT nPort /* = INTERNET_INVALID_PORT_NUMBER */,
	LPCTSTR pstrUserName /* = NULL */,
	LPCTSTR pstrPassword /* = NULL */,
	DWORD dwContext /* = 1 */)
	: CInternetConnection(pSession, pstrServer, nPort, dwContext)
{
	ASSERT(pSession != NULL);
	ASSERT_KINDOF(CInternetSession, pSession);
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);
	ASSERT(AfxIsValidString(pstrServer));

	m_hConnection = InternetConnect((HINTERNET) *pSession, pstrServer,
		nPort, pstrUserName, pstrPassword, INTERNET_SERVICE_HTTP,
		dwFlags, m_dwContext);

	if (m_hConnection == NULL)
		AfxThrowInternetException(m_dwContext);
	else
		_afxSessionMap.SetAt(m_hConnection, m_pSession);
}

void CHttpConnection::Close()
{
	CInternetConnection::Close();
}

CHttpFile* CHttpConnection::OpenRequest(LPCTSTR pstrVerb,
	LPCTSTR pstrObjectName, LPCTSTR pstrReferer, DWORD dwContext,
	LPCTSTR* ppstrAcceptTypes, LPCTSTR pstrVersion, DWORD dwFlags)
{
	ASSERT_VALID(this);
	ASSERT(m_hConnection != NULL);
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);

	if (dwContext == 1)
		dwContext = m_dwContext;

	if (pstrVersion == NULL)
		pstrVersion = HTTP_VERSION;

	HINTERNET hFile;
	hFile = HttpOpenRequest(m_hConnection, pstrVerb, pstrObjectName,
		pstrVersion, pstrReferer, ppstrAcceptTypes, dwFlags, dwContext);

	CHttpFile* pRet = new CHttpFile(hFile, pstrVerb, pstrObjectName, this);
	pRet->m_dwContext = dwContext;
	return pRet;
}

CHttpFile* CHttpConnection::OpenRequest(int nVerb,
	LPCTSTR pstrObjectName, LPCTSTR pstrReferer /* = NULL */, DWORD dwContext,
	LPCTSTR* ppstrAcceptTypes /* = NULL */,
	LPCTSTR pstrVersion /* = NULL */, DWORD dwFlags)
{
	ASSERT_VALID(this);
	ASSERT(m_hConnection != NULL);
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);
	ASSERT(AfxIsValidString(pstrObjectName));

	ASSERT(nVerb >= _HTTP_VERB_MIN && nVerb <= _HTTP_VERB_MAX);

	LPCTSTR pstrVerb;
	if (nVerb >= _HTTP_VERB_MIN && nVerb <= _HTTP_VERB_MAX)
		pstrVerb = szHtmlVerbs[nVerb];
	else
		pstrVerb = _T("");

	return OpenRequest(pstrVerb, pstrObjectName, pstrReferer,
		dwContext, ppstrAcceptTypes, pstrVersion, dwFlags);
}

#ifdef _DEBUG
void CHttpConnection::Dump(CDumpContext& dc) const
{
	CInternetConnection::Dump(dc);
	dc << "\nm_strServerName = " << m_strServerName;
}

void CHttpConnection::AssertValid() const
{
	ASSERT(m_pSession != NULL);
	if (m_hConnection != NULL)
	{
		ASSERT(AfxGetInternetHandleType(m_hConnection)
				== INTERNET_HANDLE_TYPE_CONNECT_HTTP);
	}
}
#endif


/////////////////////////////////////////////////////////////////////////////
// CHttpFile

CHttpFile::CHttpFile(HINTERNET hFile, HINTERNET hSession, LPCTSTR pstrObject,
	LPCTSTR pstrServer, LPCTSTR pstrVerb, DWORD dwContext)
 : CInternetFile(hFile, hSession, pstrObject, pstrServer, dwContext, TRUE),
	m_strVerb(pstrVerb), m_strObject(pstrObject)
{
	// caller must set _afxSessionMap!
	ASSERT(AfxIsValidString(pstrVerb));
}


CHttpFile::CHttpFile(HINTERNET hFile, LPCTSTR pstrVerb, LPCTSTR pstrObject,
	CHttpConnection* pConnection)
 : CInternetFile(hFile, pstrObject, pConnection, TRUE),
	m_strVerb(pstrVerb), m_strObject(pstrObject)
{
	ASSERT(pstrVerb != NULL);
	ASSERT(pstrObject != NULL);
	ASSERT(pConnection != NULL);
	ASSERT_VALID(pConnection);
}

CHttpFile::~CHttpFile()
{
}

void CHttpFile::Close()
{
	CInternetFile::Close();
}

DWORD CHttpFile::ErrorDlg(CWnd* pParent /* = NULL */,
	DWORD dwError /* = ERROR_INTERNET_INCORRECT_PASSWORD */,
	DWORD dwFlags /* = FLAGS_ERROR_UI_FLAGS_GENERATE_DATA | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS*/,
	LPVOID* lppvData /* = NULL */)
{
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);
	HWND hWnd;
	LPVOID lpEmpty;
	LPVOID* lppvHolder;

	if (lppvData == NULL)
	{
		lpEmpty = NULL;
		lppvHolder = &lpEmpty;
	}
	else
		lppvHolder = lppvData;

	if (pParent == NULL || pParent->m_hWnd == NULL)
		hWnd = GetDesktopWindow();
	else
		hWnd = pParent->m_hWnd;

	return InternetErrorDlg(hWnd, m_hFile, dwError, dwFlags, lppvHolder);
}

CString CHttpFile::GetVerb() const
{
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	return m_strVerb;
}

CString CHttpFile::GetObject() const
{
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	return m_strObject;
}

CString CHttpFile::GetFileURL() const
{
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	CString str(_afxURLhttp);
	if (m_hConnection != NULL)
	{
		str += m_strServerName;
		int nLen = m_strObject.GetLength();
		if (nLen > 0)
		{
			if (m_strObject[0] != '/' && m_strObject[0] != '\\')
				str += '/';
			str += m_strObject;
		}
	}

	return str;
}

BOOL CHttpFile::AddRequestHeaders(LPCTSTR pstrHeaders,
	DWORD dwModifiers /* = HTTP_ADDREQ_FLAG_ADD */,
	int dwHeadersLen /* = -1 */)
{
	ASSERT(AfxIsValidString(pstrHeaders));
	ASSERT(dwHeadersLen == 0 || pstrHeaders != NULL);
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	if (dwHeadersLen == -1)
		if (pstrHeaders == NULL)
			dwHeadersLen = 0;
		else
			dwHeadersLen = lstrlen(pstrHeaders);

	return HttpAddRequestHeaders(m_hFile, pstrHeaders, dwHeadersLen,
		dwModifiers);
}

BOOL CHttpFile::AddRequestHeaders(CString& str,
	DWORD dwModifiers /* = HTTP_ADDREQ_FLAG_ADD */)
{
	return AddRequestHeaders((LPCTSTR) str, dwModifiers, str.GetLength());
}

BOOL CHttpFile::SendRequest(LPCTSTR pstrHeaders /* = NULL */,
	DWORD dwHeadersLen /* = 0 */, LPVOID lpOptional /* = NULL */,
	DWORD dwOptionalLen /* = 0 */)
{
	ASSERT(dwOptionalLen == 0 || lpOptional != NULL);
	ASSERT(dwHeadersLen == 0 || pstrHeaders != NULL);
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	BOOL bRet = HttpSendRequest(m_hFile,
		pstrHeaders, dwHeadersLen, lpOptional, dwOptionalLen);

	if (!bRet)
		AfxThrowInternetException(m_dwContext);

	return bRet;
}

BOOL CHttpFile::EndRequest(
	DWORD dwFlags /* = 0 */,
	LPINTERNET_BUFFERS lpBuffIn /* = NULL */, DWORD dwContext /* = 1 */)
{
	ASSERT(m_hFile != NULL);
	ASSERT(m_bReadMode == -1);

	if (dwContext == 1)
		dwContext = m_dwContext;

	BOOL bRet = HttpEndRequest(m_hFile, lpBuffIn, dwFlags, dwContext);

	if (!bRet)
		AfxThrowInternetException(m_dwContext);
	return bRet;
}

BOOL CHttpFile::SendRequestEx(DWORD dwTotalLen,
	DWORD dwFlags /* = HSR_INITIATE */, DWORD dwContext /* = 1 */)
{
	ASSERT(m_hFile != NULL);

	INTERNET_BUFFERS buffer;
	memset(&buffer, 0, sizeof(buffer));
	buffer.dwStructSize = sizeof(buffer);
	buffer.dwBufferTotal = dwTotalLen;

	if (dwContext == 1)
		dwContext = m_dwContext;

	return SendRequestEx(&buffer, NULL, dwFlags, dwContext);
}

BOOL CHttpFile::SendRequestEx(LPINTERNET_BUFFERS lpBuffIn,
	LPINTERNET_BUFFERS lpBuffOut, DWORD dwFlags /* = HSR_INITIATE */,
	DWORD dwContext /* = 1 */)
{
	ASSERT(m_hFile != NULL);
	ASSERT_NULL_OR_POINTER(lpBuffIn, INTERNET_BUFFERS);
	ASSERT_NULL_OR_POINTER(lpBuffOut, INTERNET_BUFFERS);

	if (dwContext == 1)
		dwContext = m_dwContext;

	BOOL bRet = HttpSendRequestEx(m_hFile, lpBuffIn, lpBuffOut,
		dwFlags, dwContext);

	if (!bRet)
		AfxThrowInternetException(m_dwContext);

	m_bReadMode = -1;
	return bRet;
}

BOOL CHttpFile::SendRequest(CString& strHeaders,
	LPVOID lpOptional /* = NULL */, DWORD dwOptionalLen /* = 0 */)
{
	ASSERT(dwOptionalLen == 0 || lpOptional != NULL);
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	return SendRequest((LPCTSTR) strHeaders, strHeaders.GetLength(),
		lpOptional, dwOptionalLen);
}

BOOL CHttpFile::QueryInfo(DWORD dwInfoLevel,
	LPVOID lpvBuffer, LPDWORD lpdwBufferLength, LPDWORD lpdwIndex) const
{
	ASSERT((HTTP_QUERY_HEADER_MASK & dwInfoLevel) <= HTTP_QUERY_MAX &&
		dwInfoLevel != 0);
	ASSERT(lpvBuffer != NULL && *lpdwBufferLength > 0);
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	return HttpQueryInfo(m_hFile, dwInfoLevel, lpvBuffer,
		lpdwBufferLength, lpdwIndex);
}

BOOL CHttpFile::QueryInfo(DWORD dwInfoLevel, DWORD& dwResult,
	LPDWORD lpdwIndex /* = NULL */) const
{
	dwInfoLevel |= HTTP_QUERY_FLAG_NUMBER;
	DWORD dwDWSize = sizeof(DWORD);
	return QueryInfo(dwInfoLevel, &dwResult, &dwDWSize, lpdwIndex);
}

BOOL CHttpFile::QueryInfo(DWORD dwInfoLevel, SYSTEMTIME* pSystemTime,
	LPDWORD lpdwIndex /* = NULL */) const
{
	dwInfoLevel |= HTTP_QUERY_FLAG_SYSTEMTIME;
	DWORD dwTimeSize = sizeof(SYSTEMTIME);
	return QueryInfo(dwInfoLevel, pSystemTime, &dwTimeSize, lpdwIndex);
}

BOOL CHttpFile::QueryInfoStatusCode(DWORD& dwStatusCode) const
{
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	TCHAR szBuffer[80];
	DWORD dwLen = _countof(szBuffer);
	BOOL bRet;

	bRet = HttpQueryInfo(m_hFile, HTTP_QUERY_STATUS_CODE,
				szBuffer, &dwLen, NULL);

	if (bRet)
		dwStatusCode = (DWORD) _ttol(szBuffer);
	return bRet;
}

BOOL CHttpFile::QueryInfo(DWORD dwInfoLevel, CString& str,
	LPDWORD lpdwIndex) const
{
	ASSERT(dwInfoLevel <= HTTP_QUERY_MAX && dwInfoLevel >= 0);
	ASSERT_VALID(this);
	ASSERT(m_hFile != NULL);

	BOOL bRet;
	DWORD dwLen = 0;

	// ask for nothing to see how long the return really is

	str.Empty();
	if (HttpQueryInfo(m_hFile, dwInfoLevel, NULL, &dwLen, 0))
		bRet = TRUE;
	else
	{
		// now that we know how long it is, ask for exactly that much
		// space and really request the header from the API

		LPTSTR pstr = str.GetBufferSetLength(dwLen);
		bRet = HttpQueryInfo(m_hFile, dwInfoLevel, pstr, &dwLen, lpdwIndex);
		if (bRet)
			str.ReleaseBuffer(dwLen);
		else
			str.ReleaseBuffer(0);
	}

	return bRet;
}

#ifdef _DEBUG
void CHttpFile::Dump(CDumpContext& dc) const
{
	dc << "\nm_strFileName = " << m_strFileName;
	dc << "\nm_strVerb = " << m_strVerb;
}

void CHttpFile::AssertValid() const
{
	CInternetFile::AssertValid();
}
#endif

/////////////////////////////////////////////////////////////////////////////
// CGopherFile

CGopherFile::CGopherFile(HINTERNET hFile, CGopherLocator& refLocator,
	CGopherConnection* pConnection)
	: CInternetFile(hFile, _T(""), pConnection, TRUE),
		m_Locator(refLocator)
{
	ASSERT(pConnection != NULL);
	ASSERT_VALID(pConnection);
}

CGopherFile::CGopherFile(HINTERNET hFile, HINTERNET hSession,
	LPCTSTR pstrLocator, DWORD dwLocLen, DWORD dwContext)
	: CInternetFile(hFile, hSession, _T(""), _T(""), dwContext, TRUE),
		m_Locator(pstrLocator, dwLocLen)
{
	// caller muset set _afxSessionMap!
}

CGopherFile::~CGopherFile()
{
}

void CGopherFile::Close()
{
	CInternetFile::Close();
}

void CGopherFile::Write(const void* lpBuf, UINT nCount)
{
	UNUSED_ALWAYS(lpBuf);
	UNUSED_ALWAYS(nCount);

	ASSERT(FALSE);
	AfxThrowNotSupportedException();
}

void CGopherFile::WriteString(LPCTSTR pstr)
{
	UNUSED_ALWAYS(pstr);

	ASSERT(FALSE);
	AfxThrowNotSupportedException();
}

#ifdef _DEBUG
void CGopherFile::Dump(CDumpContext& dc) const
{
	CInternetFile::Dump(dc);
}

void CGopherFile::AssertValid() const
{
	CInternetFile::AssertValid();
}
#endif

/////////////////////////////////////////////////////////////////////////////
// CFtpFileFind

CFtpFileFind::CFtpFileFind(CFtpConnection* pConnection, DWORD dwContext)
{
	ASSERT(pConnection != NULL);
	ASSERT_KINDOF(CFtpConnection, pConnection);

	m_pConnection = pConnection;
	if (dwContext == 1)
		dwContext = pConnection->GetContext();
	m_dwContext = dwContext;
	m_chDirSeparator = '/';
}

CFtpFileFind::~CFtpFileFind()
{
}

BOOL CFtpFileFind::FindFile(LPCTSTR pstrName /* = NULL */,
	DWORD dwFlags /* = INTERNET_FLAG_RELOAD */)
{
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);
	ASSERT(m_pConnection != NULL);
	ASSERT_VALID(m_pConnection);

	if (m_pConnection == NULL)
		return FALSE;

	Close();
	m_pNextInfo = new WIN32_FIND_DATA;
	m_bGotLast = FALSE;

	if (pstrName == NULL)
		pstrName = _T("*");
	lstrcpy(((LPWIN32_FIND_DATA) m_pNextInfo)->cFileName, pstrName);

	m_hContext = FtpFindFirstFile((HINTERNET) *m_pConnection,
		pstrName, (LPWIN32_FIND_DATA) m_pNextInfo, dwFlags, m_dwContext);

	if (m_hContext == NULL)
	{
		Close();
		return FALSE;
	}

	LPCTSTR pstrRoot = _tcspbrk(pstrName, _T("\\/"));
	CString strCWD;
	m_pConnection->GetCurrentDirectory(strCWD);

	if (pstrRoot == NULL)
	{
		if (m_pConnection->SetCurrentDirectory(pstrName))
		{
			m_pConnection->GetCurrentDirectory(m_strRoot);
			m_pConnection->SetCurrentDirectory(strCWD);
		}
		else
			m_strRoot = strCWD;
	}
	else
	{
		// find the last forward or backward whack

		int nLast;
		LPCTSTR pstrOther = _tcsrchr(pstrName, '\\');
		pstrRoot = _tcsrchr(pstrName, '/');

		if (pstrRoot == NULL)
			pstrRoot = pstrName;
		if (pstrOther == NULL)
			pstrOther = pstrName;

		if (pstrRoot >= pstrOther)
			nLast = pstrRoot - pstrName;
		else
			nLast = pstrOther - pstrName;

		// from the start to the last whack is the root

		if (nLast == 0)
			nLast++;

		m_strRoot = pstrName;
		m_strRoot = m_strRoot.Left(nLast);
	}

	return TRUE;
}

BOOL CFtpFileFind::FindNextFile()
{
	ASSERT(m_hContext != NULL);
	if (m_hContext == NULL)
		return FALSE;

	if (m_pFoundInfo == NULL)
		m_pFoundInfo = new WIN32_FIND_DATA;

	ASSERT_VALID(this);
	void* pTemp = m_pFoundInfo;
	m_pFoundInfo = m_pNextInfo;
	m_pNextInfo = pTemp;

	return InternetFindNextFile(m_hContext, m_pNextInfo);
}

void CFtpFileFind::CloseContext()
{
	if (m_hContext != NULL && m_hContext != INVALID_HANDLE_VALUE)
	{
		InternetCloseHandle(m_hContext);
		m_hContext = NULL;
	}

	return;
}

CString CFtpFileFind::GetFileURL() const
{
	ASSERT_VALID(this);
	ASSERT(m_hContext != NULL);

	CString str;

	if (m_hContext != NULL)
	{
		str += _afxURLftp;
		str += m_pConnection->GetServerName();
		str += GetFilePath();
	}

	return str;
}

#ifdef _DEBUG
void CFtpFileFind::Dump(CDumpContext& dc) const
{
	CFileFind::Dump(dc);
	dc << "m_hContext = " << m_hContext;
}

void CFtpFileFind::AssertValid() const
{
	CFileFind::AssertValid();
}
#endif


/////////////////////////////////////////////////////////////////////////////
// CGopherFileFind

CGopherFileFind::CGopherFileFind(CGopherConnection* pConnection,
	DWORD dwContext)
{
	ASSERT(pConnection != NULL);
	ASSERT_KINDOF(CGopherConnection, pConnection);

	m_pConnection = pConnection;
	if (dwContext == 1)
		dwContext = pConnection->GetContext();
	m_dwContext = dwContext;
}

CGopherFileFind::~CGopherFileFind()
{
}

BOOL CGopherFileFind::FindFile(LPCTSTR pstrString,
	DWORD dwFlags /* = INTERNET_FLAG_RELOAD */)
{
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);
	Close();

	m_pNextInfo = new GOPHER_FIND_DATA;
	m_bGotLast = FALSE;

	m_hContext = GopherFindFirstFile((HINTERNET) *m_pConnection,
		NULL, pstrString,
		(GOPHER_FIND_DATA*) m_pNextInfo, dwFlags, m_dwContext);

	if (m_hContext == NULL)
		Close();
	return (m_hContext != NULL);
}

BOOL CGopherFileFind::FindFile(CGopherLocator& refLocator,
	LPCTSTR pstrString, DWORD dwFlags /* = INTERNET_FLAG_RELOAD */)
{
	ASSERT((dwFlags & INTERNET_FLAG_ASYNC) == 0);
	Close();

	m_pNextInfo = new GOPHER_FIND_DATA;
	m_pFoundInfo = new GOPHER_FIND_DATA;
	m_bGotLast = FALSE;

	m_hContext = GopherFindFirstFile((HINTERNET) *m_pConnection,
		(LPCTSTR) refLocator, pstrString,
		(GOPHER_FIND_DATA*) m_pNextInfo, dwFlags, m_dwContext);

	if (m_hContext == NULL)
		Close();
	return (m_hContext != NULL);
}

BOOL CGopherFileFind::FindNextFile()
{
	ASSERT(m_hContext != NULL);
	if (m_hContext == NULL)
		return FALSE;

	if (m_pFoundInfo == NULL)
		m_pFoundInfo = new GOPHER_FIND_DATA;

	ASSERT_VALID(this);
	void* pTemp = m_pFoundInfo;
	m_pFoundInfo = m_pNextInfo;
	m_pNextInfo = pTemp;

	return InternetFindNextFile(m_hContext, m_pNextInfo);
}

void CGopherFileFind::CloseContext()
{
	if (m_hContext != NULL && m_hContext != INVALID_HANDLE_VALUE)
	{
		InternetCloseHandle(m_hContext);
		m_hContext = NULL;
	}

	return;
}

CString CGopherFileFind::GetFileName() const
{
	AfxThrowNotSupportedException();
	return CString();
}

CString CGopherFileFind::GetFilePath() const
{
	AfxThrowNotSupportedException();
	return CString();
}

CString CGopherFileFind::GetFileTitle() const
{
	AfxThrowNotSupportedException();
	return CString();
}

BOOL CGopherFileFind::IsDots() const
{
	// gophers never have dots
	return FALSE;
}

BOOL CGopherFileFind::GetLastWriteTime(FILETIME* pTimeStamp) const
{
	ASSERT(m_hContext != NULL);
	ASSERT_POINTER(pTimeStamp, FILETIME);
	ASSERT_VALID(this);

	if (m_pFoundInfo != NULL && pTimeStamp != NULL)
	{
		*pTimeStamp = ((LPGOPHER_FIND_DATA) m_pFoundInfo)->LastModificationTime;
		return TRUE;
	}
	else
		return FALSE;
}

BOOL CGopherFileFind::GetLastAccessTime(FILETIME* pTimeStamp) const
{
	ASSERT_POINTER(pTimeStamp, FILETIME);
	return GetLastWriteTime(pTimeStamp);
}

BOOL CGopherFileFind::GetCreationTime(FILETIME* pTimeStamp) const
{
	ASSERT_POINTER(pTimeStamp, FILETIME);
	return GetLastWriteTime(pTimeStamp);
}

BOOL CGopherFileFind::GetLastWriteTime(CTime& refTime) const
{
	ASSERT(m_hContext != NULL);
	ASSERT_VALID(this);

	if (m_pFoundInfo != NULL)
	{
		refTime = CTime(((LPGOPHER_FIND_DATA) m_pFoundInfo)->LastModificationTime);
		return TRUE;
	}
	else
		return FALSE;
}

BOOL CGopherFileFind::GetCreationTime(CTime& refTime) const
{
	return GetLastWriteTime(refTime);
}

BOOL CGopherFileFind::GetLastAccessTime(CTime& refTime) const
{
	return GetLastWriteTime(refTime);
}


CString CGopherFileFind::GetFileURL() const
{
	AfxThrowNotSupportedException();
	return CString();
}

CString CGopherFileFind::GetRoot() const
{
	AfxThrowNotSupportedException();
	return CString();
}

CGopherLocator CGopherFileFind::GetLocator() const
{
	ASSERT_VALID(this);
	ASSERT(m_pConnection != NULL && m_hContext != NULL);

	return m_pConnection->CreateLocator(
		((LPGOPHER_FIND_DATA) m_pFoundInfo)->Locator);
}

CString CGopherFileFind::GetScreenName() const
{
	ASSERT_VALID(this);
	ASSERT(m_hContext != NULL);

	CString str;

	if (m_pFoundInfo != NULL)
		str = ((LPGOPHER_FIND_DATA) m_pFoundInfo)->DisplayString;

	return str;
}

DWORD CGopherFileFind::GetLength() const
{
	ASSERT_VALID(this);

	if (m_pFoundInfo != NULL)
		return ((LPGOPHER_FIND_DATA) m_pFoundInfo)->SizeLow;
	else
		return 0;
}

#if defined(_X86_) || defined(_ALPHA_)
__int64 CGopherFileFind::GetLength64() const
{
	ASSERT_VALID(this);

	if (m_pFoundInfo != NULL)
		return ((LPGOPHER_FIND_DATA) m_pFoundInfo)->SizeLow +
				(((LPGOPHER_FIND_DATA) m_pFoundInfo)->SizeHigh << 32);
	else
		return 0;
}
#endif

#ifdef _DEBUG
void CGopherFileFind::Dump(CDumpContext& dc) const
{
	CFileFind::Dump(dc);
	dc << "m_hContext = " << m_hContext;
}

void CGopherFileFind::AssertValid() const
{
	CFileFind::AssertValid();
}
#endif


/////////////////////////////////////////////////////////////////////////////
// CGopherLocator

CGopherLocator::CGopherLocator(LPCTSTR pstrLocator, DWORD dwLocLen)
{
	ASSERT(AfxIsValidString(pstrLocator));
	LPTSTR pstr = m_Locator.GetBufferSetLength(dwLocLen);
	memcpy(pstr, pstrLocator, dwLocLen);
	m_Locator.ReleaseBuffer(dwLocLen);
	m_dwBufferLength = dwLocLen;
}

CGopherLocator::~CGopherLocator()
{
}

/////////////////////////////////////////////////////////////////////////////
// exception handling

void AFXAPI AfxThrowInternetException(DWORD dwContext, DWORD dwError /* = 0 */)
{
	if (dwError == 0)
		dwError = ::GetLastError();

	CInternetException* pException = new CInternetException(dwError);
	pException->m_dwContext = dwContext;

	TRACE1("Warning: throwing CInternetException for error %d\n", dwError);
	THROW(pException);
}


BOOL CInternetException::GetErrorMessage(LPTSTR pstrError, UINT nMaxError,
		PUINT pnHelpContext)
{
	ASSERT(pstrError != NULL && AfxIsValidString(pstrError, nMaxError));

	if (pnHelpContext != NULL)
		*pnHelpContext = 0;

	LPTSTR lpBuffer;
	BOOL bRet = TRUE;

	HINSTANCE hWinINetLibrary;
	hWinINetLibrary = ::LoadLibraryA("WININET.DLL");

	if (hWinINetLibrary == NULL ||
		FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE,
			hWinINetLibrary, m_dwError,
			MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT),
			(LPTSTR) &lpBuffer, 0, NULL) == 0)
	{
		// it failed! try Windows...

		bRet = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
			NULL,  m_dwError,
			MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT),
			(LPTSTR) &lpBuffer, 0, NULL);
	}

	if (!bRet)
		*pstrError = '\0';
	else
	{
		if (m_dwError == ERROR_INTERNET_EXTENDED_ERROR)
		{
			LPTSTR lpExtended;
			DWORD dwLength = 0;
			DWORD dwError;

			// find the length of the error
			if (!InternetGetLastResponseInfo(&dwError, NULL, &dwLength) &&
				GetLastError() == ERROR_INSUFFICIENT_BUFFER)
			{
				lpExtended = (LPTSTR) LocalAlloc(LPTR, dwLength);
				InternetGetLastResponseInfo(&dwError, lpExtended, &dwLength);
				lstrcpyn(pstrError, lpExtended, nMaxError);
				pstrError += dwLength;
				nMaxError -= dwLength;
				if (nMaxError < 0)
					nMaxError = 0;
				LocalFree(lpExtended);
			}
			else
				TRACE0("Warning: Extended error reported with no response info\n");
			bRet = TRUE;
		}
		else
		{
			lstrcpyn(pstrError, lpBuffer, nMaxError);
			bRet = TRUE;
		}

		LocalFree(lpBuffer);
	}

#ifndef _AFXDLL
	::FreeLibrary(hWinINetLibrary);
#endif
	return bRet;
}

CInternetException::CInternetException(DWORD dwError)
{
	m_dwError = dwError;
}

CInternetException::~CInternetException()
{
}

#ifdef _DEBUG
void CInternetException::Dump(CDumpContext& dc) const
{
	CObject::Dump(dc);

	dc << "m_dwError = " << m_dwError;
	dc << "\nm_dwContext = " << m_dwContext;
}
#endif

/////////////////////////////////////////////////////////////////////////////
// Inline function declarations expanded out-of-line

#ifndef _AFX_ENABLE_INLINES

// expand inlines for OLE dialog APIs
static char _szAfxInetInl[] = "afxinet.inl";
#undef THIS_FILE
#define THIS_FILE _szAfxInetInl
#define _AFXINET_INLINE
#include "afxinet.inl"

#endif //!_AFX_ENABLE_INLINES

/////////////////////////////////////////////////////////////////////////////
// Pre-startup code

#ifdef AFX_INIT_SEG
#pragma code_seg(AFX_INIT_SEG)
#endif

IMPLEMENT_DYNAMIC(CInternetException, CException)
IMPLEMENT_DYNAMIC(CInternetFile, CStdioFile)
IMPLEMENT_DYNAMIC(CHttpFile, CInternetFile)
IMPLEMENT_DYNAMIC(CGopherFile, CInternetFile)
IMPLEMENT_DYNAMIC(CInternetSession, CObject)
IMPLEMENT_DYNAMIC(CInternetConnection, CObject)
IMPLEMENT_DYNAMIC(CFtpConnection, CInternetConnection)
IMPLEMENT_DYNAMIC(CHttpConnection, CInternetConnection)
IMPLEMENT_DYNAMIC(CGopherConnection, CInternetConnection)
IMPLEMENT_DYNAMIC(CFtpFileFind, CFileFind)
IMPLEMENT_DYNAMIC(CGopherFileFind, CFileFind)

#pragma warning(disable: 4074)
#pragma init_seg(compiler)

CSessionMapPtrToPtr _afxSessionMap;
