//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
/*--
Module Name: PARSER.CPP
Abstract: HTTP request parser
--*/

#include "httpd.h"

// Methods
// NOTE: This follows same order as "enum VERB" list in request.h.

const char *rg_cszMethods[] = {
	"GET",
	"HEAD",
	"POST",
	"OPTIONS",
	"PUT",
	"MOVE",
	"COPY",
	"DELETE",
	"MKCOL",
	"PROPFIND",
	"PROPPATCH",
//	"SEARCH",
	"LOCK",
	"UNLOCK",
#if 0 // unsupported DAV vebs
	"SUBSCRIBE",
	"UNSUBSCRIBE",
	"POLL",
	"BDELETE",
	"BCOPY",
	"BMOVE",
	"BPROPPATCH",
	"BPROPFIND",
	"X-MS-ENUMATTS",
#endif
};

const int cKnownMethods = ARRAYSIZEOF(rg_cszMethods);

// other Header tokens
const char cszHTTPVER[] = "HTTP/%d.%d";


// String constants

// const CHAR  cszAccept_CHAR set[]         = "Accept-CHAR set:";
// const CHAR  cszAccept_Encoding[]         = "Accept-Encoding:";
// const CHAR  cszAccept_Language[]         = "Accept-Language:";
const CHAR  cszAccept_Ranges[]              = "Accept-Ranges:";
const DWORD ccAccept_Ranges                 = SVSUTIL_CONSTSTRLEN(cszAccept_Ranges);
const CHAR  cszAccept[]                     = "Accept:";
const DWORD ccAccept                        = SVSUTIL_CONSTSTRLEN(cszAccept);
// const CHAR  cszAge[]                     = "Age:";
const CHAR  cszAllow[]                      = "Allow:";
const DWORD ccAllow                         = SVSUTIL_CONSTSTRLEN(cszAllow);
// const CHAR  cszAtomic[]                  = "Atomic:";
const CHAR  cszAuthorization[]              = "Authorization:";
const DWORD ccAuthorization                 = SVSUTIL_CONSTSTRLEN(cszAuthorization);
// const CHAR  cszCollection_Member[]       = "Collection-Member:";
// const CHAR  cszCompatibility[]           = "Compatibility:";
const CHAR  cszConnection[]                 = "Connection:";
const DWORD ccConnection                    = SVSUTIL_CONSTSTRLEN(cszConnection);
// const CHAR  cszContent_Base[]            = "Content-Base:";
// const CHAR  cszContent_Encoding[]        = "Content-Encoding:";
// const CHAR  cszContent_Language[]        = "Content-Language:";
const CHAR  cszContent_Length[]             = "Content-Length:";
const DWORD ccContent_Length                = SVSUTIL_CONSTSTRLEN(cszContent_Length);
const CHAR  cszContent_Location[]           = "Content-Location:";
const DWORD ccContent_Location              = SVSUTIL_CONSTSTRLEN(cszContent_Location);
// const CHAR  cszContent_MD5[]             = "Content-MD5:";
const CHAR  cszContent_Range[]              = "Content-Range:";
const DWORD ccContent_Range                 = SVSUTIL_CONSTSTRLEN(cszContent_Range);
const CHAR  cszContent_Type[]               = "Content-Type:";
const DWORD ccContent_Type                  = SVSUTIL_CONSTSTRLEN(cszContent_Type);
// const CHAR  cszContent_Disposition[]     = "Content-Disposition:";
const CHAR  cszCookie[]                     = "Cookie:";
const DWORD ccCookie                        = SVSUTIL_CONSTSTRLEN(cszCookie);
const CHAR  cszDate[]                       = "Date:";
const DWORD ccDate                          = SVSUTIL_CONSTSTRLEN(cszDate);
const CHAR  cszDepth[]                      = "Depth:";
const DWORD ccDepth                         = SVSUTIL_CONSTSTRLEN(cszDepth);
const CHAR  cszDestination[]                = "Destination:";
const DWORD ccDestination                   = SVSUTIL_CONSTSTRLEN(cszDestination);
// const CHAR  cszDestroy[]                 = "Destroy:";
// const CHAR  cszExpires[]                 = "Expires:";
const CHAR  cszETag[]                       = "ETag:";
const DWORD ccETag                          = SVSUTIL_CONSTSTRLEN(cszETag);
// const CHAR  cszFrom[]                    = "From:";
const CHAR  cszHost[]                       = "Host:";
const DWORD ccHost                          = SVSUTIL_CONSTSTRLEN(cszHost);
const CHAR  cszIfToken[]                    = "If:";
const DWORD ccIfToken                       = SVSUTIL_CONSTSTRLEN(cszIfToken);
const CHAR  cszIf_Match[]                   = "If-Match:";
const DWORD ccIf_Match                      = SVSUTIL_CONSTSTRLEN(cszIf_Match);
const CHAR  cszIf_Modified_Since[]          = "If-Modified-Since:";
const DWORD ccIf_Modified_Since             = SVSUTIL_CONSTSTRLEN(cszIf_Modified_Since);
const CHAR  cszIf_None_Match[]              = "If-None-Match:";
const DWORD ccIf_None_Match                 = SVSUTIL_CONSTSTRLEN(cszIf_None_Match);
const CHAR  cszIf_None_State_Match[]        = "If-None-State-Match:";
const DWORD ccIf_None_State_Match           = SVSUTIL_CONSTSTRLEN(cszIf_None_State_Match);
const CHAR  cszIf_Range[]                   = "If-Range:";
const DWORD ccIf_Range                      = SVSUTIL_CONSTSTRLEN(cszIf_Range);
const CHAR  cszIf_State_Match[]             = "If-State-Match:";
const DWORD ccIf_State_Match                = SVSUTIL_CONSTSTRLEN(cszIf_State_Match);
const CHAR  cszIf_Unmodified_Since[]        = "If-Unmodified-Since:";
const DWORD ccIf_Unmodified_Since           = SVSUTIL_CONSTSTRLEN(cszIf_Unmodified_Since);
// const CHAR  cszLast_Modified[]           = "Last-Modified:";
const CHAR  cszLocation[]                   = "Location:";
const DWORD ccLocation                      = SVSUTIL_CONSTSTRLEN(cszLocation);
const CHAR  cszLockInfo[]                   = "Lock-Info:";
const DWORD ccLockInfo                      = SVSUTIL_CONSTSTRLEN(cszLockInfo);
const CHAR  cszLockToken[]                  = "Lock-Token:";
const DWORD ccLockToken                     = SVSUTIL_CONSTSTRLEN(cszLockToken);
// const CHAR  cszMS_Author_Via[]           = "MS-Author-Via:";
// const CHAR  cszMS_Exchange_FlatURL[]     = "MS-Exchange-Permanent-URL:";
const CHAR  cszOverwrite[]                  = "Overwrite:";
const DWORD ccOverwrite                     = SVSUTIL_CONSTSTRLEN(cszOverwrite);
const CHAR  cszAllowRename[]                = "Allow-Rename:";
const DWORD ccAllowRename                   = SVSUTIL_CONSTSTRLEN(cszAllowRename);
const CHAR  cszPublic[]                     = "Public:";
const DWORD ccPublic                        = SVSUTIL_CONSTSTRLEN(cszPublic);
const CHAR  cszRange[]                      = "Range:";
const DWORD ccRange                         = SVSUTIL_CONSTSTRLEN(cszRange);
const CHAR  cszReferer[]                    = "Referer:";
const DWORD ccReferer                       = SVSUTIL_CONSTSTRLEN(cszReferer);
// const CHAR  cszRetry_After[]             = "Retry-After:";
const CHAR  cszServer[]                     = "Server:";
const DWORD ccServer                        = SVSUTIL_CONSTSTRLEN(cszServer);
// const CHAR  cszSet_Cookie[]              = "Set-Cookie:";
const CHAR  cszTimeOut[]                    = "Timeout:";
const DWORD ccTimeOut                       = SVSUTIL_CONSTSTRLEN(cszTimeOut);
const CHAR  cszTransfer_Encoding[]          = "Transfer-Encoding:";
const DWORD ccTransfer_Encoding             = SVSUTIL_CONSTSTRLEN(cszTransfer_Encoding);
const CHAR  cszTranslate[]                  = "Translate:";
const DWORD ccTranslate                     = SVSUTIL_CONSTSTRLEN(cszTranslate);
// const CHAR  cszUpdate[]                  = "Update:";
// const CHAR  cszUser_Agent[]              = "User-Agent:";
// const CHAR  cszVary[]                    = "Vary:";
// const CHAR  cszWarning[]                 = "Warning:";
const CHAR  cszWWW_Authenticate[]           = "WWW-Authenticate:";
const DWORD ccWWW_Authenticate              = SVSUTIL_CONSTSTRLEN(cszWWW_Authenticate);
// const CHAR  cszVersioning_Support[]      = "Versioning-Support:";
// const CHAR  cszBrief[]                   = "Brief:";

//
//    Common header values
//
const CHAR cszDateFmtGMT[]                 = "%3s, %02d %3s %04d %02d:%02d:%02d GMT";
const CHAR* rgWkday[]                      = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", 0 };
const CHAR* rgMonth[]                      = { "", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 0 };

const CHAR  cszNone[]                      = "none";
const DWORD ccNone                         = SVSUTIL_CONSTSTRLEN(cszNone);
const CHAR  cszChunked[]                   = "chunked";
const DWORD ccChunked                      = SVSUTIL_CONSTSTRLEN(cszChunked);

const CHAR  cszBasic[]                     = "Basic";
const DWORD ccBasic                        = SVSUTIL_CONSTSTRLEN(cszBasic);
const CHAR  cszNTLM[]                      = "NTLM";
const DWORD ccNTLM                         = SVSUTIL_CONSTSTRLEN(cszNTLM);
const CHAR  cszNegotiate[]                 = "Negotiate";
const DWORD ccNegotiate                    = SVSUTIL_CONSTSTRLEN(cszNegotiate);

const CHAR  cszBytes[]                     = "bytes";
const DWORD ccBytes                        = SVSUTIL_CONSTSTRLEN(cszBytes);
const CHAR  cszAnd[]                       = "and";
const DWORD ccAnd                          = SVSUTIL_CONSTSTRLEN(cszAnd);
const CHAR  cszOr[]                        = "or";
const DWORD ccOr                           = SVSUTIL_CONSTSTRLEN(cszOr);
const CHAR  cszMS_Author_Via_Dav[]         = "DAV";
const DWORD ccMS_Author_Via_Dav            = SVSUTIL_CONSTSTRLEN(cszMS_Author_Via_Dav);


const CHAR  cszHttpPrefix[]                = "http://";
const DWORD ccHttpPrefix                   = SVSUTIL_CONSTSTRLEN(cszHttpPrefix);
const CHAR  cszHttpsPrefix[]               = "https://";
const DWORD ccHttpsPrefix                  = SVSUTIL_CONSTSTRLEN(cszHttpsPrefix);


const CHAR  cszRespStatus[]                = "HTTP/1.%d %d %s\r\n";
const DWORD ccRespStatus                   = SVSUTIL_CONSTSTRLEN(cszRespStatus);
const CHAR  cszRespServer[]                = "Server: %s\r\n";
const DWORD ccRespServer                   = SVSUTIL_CONSTSTRLEN(cszRespServer);
const CHAR  cszRespType[]                  = "Content-Type: %s\r\n";
const DWORD ccRespType                     = SVSUTIL_CONSTSTRLEN(cszRespType);
const CHAR  cszRespLength[]                = "Content-Length: %d\r\n";
const DWORD ccRespLength                   = SVSUTIL_CONSTSTRLEN(cszRespLength);

const CHAR  cszRespDate[]                  = "Date: ";
const DWORD ccRespDate                     = SVSUTIL_CONSTSTRLEN(cszRespDate);
const CHAR  cszRespLocation[]              = "Location: ";
const DWORD ccRespLocation                 = SVSUTIL_CONSTSTRLEN(cszRespLocation);

const CHAR  cszConnectionResp[]            = "Connection: %s\r\n";
const DWORD ccConnectionResp               = SVSUTIL_CONSTSTRLEN(cszConnectionResp);
const CHAR  cszKeepAlive[]                 = "keep-alive";
const DWORD ccKeepAlive                    = SVSUTIL_CONSTSTRLEN(cszKeepAlive);
const CHAR  cszCRLF[]                      = "\r\n";
const DWORD ccCRLF                         = SVSUTIL_CONSTSTRLEN(cszCRLF);
const CHAR  cszRespLastMod[]               = "Last-Modified:";
const DWORD ccRespLastMod                  = SVSUTIL_CONSTSTRLEN(cszRespLastMod);


const CHAR  cszOpaquelocktokenPrefix[]     = "opaquelocktoken:";
const DWORD ccOpaquelocktokenPrefix        = SVSUTIL_CONSTSTRLEN(cszOpaquelocktokenPrefix);


#define PFNPARSE(x)	&(CHttpRequest::Parse##x)
#define TABLEENTRY(csz, id, pfn)	   { csz, sizeof(csz)-1, id, PFNPARSE(pfn) }

typedef (CHttpRequest::*PFNPARSEPROC)(PCSTR pszTok, TOKEN idHeader);

typedef struct tagHeaderDesc {
	const char*		sz;
	int		        iLen;
	TOKEN		    id;
	PFNPARSEPROC	pfn;
} HEADERDESC;


const HEADERDESC rgHeaders[] =
{
// General headers
	TABLEENTRY(cszConnection, TOK_CONNECTION, Connection),
	//TABLEENTRY(cszDate,	  TOK_DATE,   Date),
	//TABLEENTRY(cszPragma, TOK_PRAGMA, Pragma),
// Request headers
	TABLEENTRY(cszCookie, TOK_COOKIE, Cookie),
	TABLEENTRY(cszAccept, TOK_ACCEPT, Accept),
	//TABLEENTRY(cszReferer,  TOK_REFERER Referer),
	//TABLEENTRY(cszUserAgent,TOK_UAGENT, UserAgent),
	TABLEENTRY(cszAuthorization, TOK_AUTH,  Authorization),
	TABLEENTRY(cszIf_Modified_Since, TOK_IFMOD, IfModifiedSince),
// Entity Headers
	//TABLEENTRY(cszContentEncoding, TOK_ENCODING Encoding),
	TABLEENTRY(cszContent_Type, TOK_TYPE,	 ContentType),
	TABLEENTRY(cszContent_Length, TOK_LENGTH,	 ContentLength),
	TABLEENTRY(cszHost, TOK_HOST,    Host),
	{ 0, 0, (TOKEN)0, 0}
};



// Parse all the headers, line by line
BOOL CHttpRequest::ParseHeaders() {	
	DEBUG_CODE_INIT;
	PSTR pszTok;
	PWSTR pwszTemp;
	PSTR pszPathInfo = NULL;
	int i, iLen;
	BOOL ret = FALSE;

	if (!m_bufRequest.NextTokenWS(&pszTok, &iLen)) {
		m_rs = STATUS_BADREQ;
		myleave(287);
	}

	if (! ParseMethod(pszTok,iLen)) {
		m_rs = STATUS_BADREQ;
		myleave(288);
	}

	if (!m_bufRequest.NextLine()) {
		m_rs = STATUS_BADREQ;
		myleave(290);
	}
	
	// outer-loop. one header per iteration
	while (m_bufRequest.NextTokenHeaderName(&pszTok, &iLen)) {
		// compare token with tokens in table
		for (i=0; rgHeaders[i].sz; i++) 	{
			//DEBUGMSG(ZONE_PARSER, (L"HTTPD: Comparing %a %d %d\r\n", rgHeaders[i].sz, rgHeaders[i].iLen, rgHeaders[i].pfn));
			if ( ((int)rgHeaders[i].iLen == iLen) &&
				0==_memicmp(rgHeaders[i].sz, pszTok, iLen) )
					break;
		}
		if (rgHeaders[i].pfn) {
			DEBUGMSG(ZONE_PARSER, (L"HTTPD: Parsing %a\r\n", rgHeaders[i].sz));
			// call the specific function to parse this header. 
			if (! ((this->*(rgHeaders[i].pfn))(pszTok, rgHeaders[i].id)) ) {
				DEBUGMSG(ZONE_PARSER, (L"HTTPD: Parser: failed to parse %a\r\n", rgHeaders[i].sz));

				// If none of the Parse functions set last error, set to bad req
				if (m_rs == STATUS_OK)
					m_rs = STATUS_BADREQ;

				myleave(296);
			}
		}
		else {
			DEBUGMSG(ZONE_PARSER, (L"HTTPD: Ignoring header %a\r\n", pszTok));
		}
		if (!m_bufRequest.NextLine()) {
			m_rs = STATUS_BADREQ;
			myleave(290);
		}
	}

	if (!m_bufRequest.NextLine()) { // eat the blank line
		m_rs = STATUS_BADREQ;
		myleave(295);
	}
	DEBUGMSG(ZONE_PARSER, (L"HTTPD: Parser: DONE\r\n"));

	// check what we got 
	if (!m_pszMethod) {
		DEBUGMSG(ZONE_PARSER, (L"HTTPD: Parser: missing URL or method, illformatted Request-line\r\n"));
		m_rs = STATUS_BADREQ;
		myleave(291);
	}

	// Once we've read the request line, give filter shot at modifying the
	// remaining headers.
	if (g_pVars->m_fFilters && 
		! CallFilter(SF_NOTIFY_PREPROC_HEADERS))
		myleave(292);

	if (!m_fResolvedHostWebSite) {
		// Now that we have Host: (if we'll get it) map website on first request.
		m_pWebsite = MapWebsite(m_socket,m_pszHost,&m_pVrootTable);

		if (!m_pWebsite) {
			m_pWebsite = g_pVars; // reset for when we write out log/filters.
			DEBUGMSG(ZONE_ERROR,(L"HTTPD: Unable to map website on socket %x\r\n",m_socket));
			m_rs = STATUS_NOTFOUND;
			myleave(297);
		}
		m_fResolvedHostWebSite = TRUE;
	}

	m_wszPath = m_pVrootTable->URLAtoPathW(m_pszURL, &m_pVRoot, &m_pszPathInfo,TRUE);

	if (g_pVars->m_fFilters &&
	    ! CallFilter(SF_NOTIFY_URL_MAP))
		myleave(293);

	// get extension
	if (m_wszPath && (pwszTemp = wcsrchr(m_wszPath, '.')))
		m_wszExt = MySzDupW(pwszTemp);

	if (m_wszPath)
		m_ccWPath = wcslen(m_wszPath);

	// Is the request chunked?
	CHAR *szHeader = FindHttpHeader(cszTransfer_Encoding,ccTransfer_Encoding);
	if (szHeader && (0 == _strnicmp(szHeader,cszChunked,ccChunked))) {
		m_fIsChunkedUpload = TRUE;
		// Per spec, ignore content-length if Chunked upload ever set
		m_dwContentLength = 0; 
	}

	ret = TRUE;
done:
	DEBUGMSG_ERR(ZONE_PARSER,(L"HTTPD: Parse headers failed, err = %d\r\n",err));
	return ret;
}

VERB FindMethod(PCSTR pszMethod, int cbMethod) {
	for (int i = 0; i < cKnownMethods; i++) {
		if (0 == memcmp(pszMethod,rg_cszMethods[i],cbMethod)) {
			DEBUGCHK( (VERB_UNKNOWN+i+1) <= VERB_LAST_VALID);
			return (VERB) (VERB_UNKNOWN+i+1);
		}
	}
	return VERB_UNKNOWN;
}

BOOL CHttpRequest::ParseMethod(PCSTR pszMethod, int cbMethod) {
	DEBUG_CODE_INIT;
	PSTR pszTok, pszTok2;
	int iLen;
	BOOL ret = FALSE;

	// save method
	m_pszMethod = MySzDupA(pszMethod);
	m_idMethod = FindMethod(pszMethod,cbMethod);

	// get URL and HTTP/x.y together (allows for spaces in URL)
	if (!m_bufRequest.NextTokenEOL(&pszTok, &iLen))
		myretleave(FALSE, 201);

	// separate out the HTTP/x.y
	if (pszTok2 = strrchr(pszTok, ' ')) {
		*pszTok2 = 0;
		iLen = pszTok2-pszTok;
		pszTok2++;
	}
	
	// clean up & parse the URL
	if (! MyCrackURL(pszTok, iLen))
		goto done;
	
	// get version (optional. HTTP 0.9 wont have this)
	if (!pszTok2)
		m_dwVersion = MAKELONG(9, 0);
	else {
		int iMajor, iMinor;
		if (2 != sscanf(pszTok2, cszHTTPVER, &iMajor, &iMinor)) {
			DEBUGMSG(ZONE_ERROR,(L"HTTPD: unable to parse HTTP version from request\r\n"));
			pszTok2[-1] = ' ';	// reset this to a space
			myretleave(FALSE,202);
		}
		m_dwVersion = MAKELONG(iMinor, iMajor);
		
		pszTok2[-1] = ' ';	// reset this to a space
	}
	ret = TRUE;

done:
	DEBUGMSG_ERR(ZONE_PARSER, (L"HTTPD: end ParseMethod (iGLE=%d iErr=%d)\r\n", GLE(err),err));
	return ret;
}

// We assume a raw URL in the form that we receive in the HTTP headers (no scheme, port number etc)
// We extract the path, extra-path, and query
BOOL CHttpRequest::MyCrackURL(PSTR pszRawURL, int iLen) {
	DEBUG_CODE_INIT;
	BOOL ret = FALSE;
	PSTR  pszDecodedURL=0, pszTemp=0;
	int iLen2;

	// special case handeling of 'OPTIONS *' case.
	if (*pszRawURL == '*' && *(pszRawURL+1) == 0 && (m_idMethod == VERB_OPTIONS)) {
		m_fOptionsAsterisk = TRUE;
		m_pszURL = MySzDupA("*");
		return TRUE;
	}

	// In all other cases, HTTPD and a number of ISAPIs assume that the path
	// begins with a '/'.  If it doesn't reject request now.
	if (*pszRawURL != '/') {
		m_rs = STATUS_BADREQ;
		goto done;
	}

	// decode URL (convert escape sequences etc)
	if (NULL == (pszDecodedURL = MyRgAllocNZ(CHAR, iLen+1)))
		myleave(382);

	// Use private Internet canonicalization to avoid wininet dependency.

	// There's no memory allocation in our version of this fcn, always succeeds.
	// The size needed to hold the unencoded URL will always be less than or
	// equal to the original URL.
	svsutil_HttpCanonicalizeUrlA(pszRawURL, pszDecodedURL);

	// get query string
	if (pszTemp = strchr(pszDecodedURL, '?')) {
		if (NULL == (m_pszQueryString = MySzDupA(pszTemp+1)))
			goto done;
		*pszTemp = 0;
	}

	// Searching for an embedded ISAPI dll name, ie /wwww/isapi.dll/a/b.
	// We load the file /www/isapi.dll and set PATH_INFO to /a/b
	// Emebbed ASP file names are handled similiarly.
	if (g_pVars->m_fExtensions) {
		if (pszTemp = strstr(pszDecodedURL,".dll/")) {
			m_pszPathInfo = MySzDupA(pszTemp + sizeof(".dll/") - 2);
			pszTemp[sizeof(".dll/") - 2] = 0;
		}
		else if (pszTemp = strstr(pszDecodedURL,".asp/")) {
			m_pszPathInfo = MySzDupA(pszTemp + sizeof(".asp/") - 2);
			pszTemp[sizeof(".asp/") - 2] = 0;
		}
	}
	
	// save a copy of the cleaned up URL (MINUS query!)
	// alloc one extra char in case we have to send a redirect back (see request.cpp)
	iLen2 = strlen(pszDecodedURL);
	if (NULL == (m_pszURL = MySzAllocA(1+iLen2)))
		goto done;

	Nstrcpy(m_pszURL, pszDecodedURL, iLen2); // copy null-term too.
		
	ret = TRUE;
done:
	MyFree(pszDecodedURL);  
	DEBUGMSG_ERR(ZONE_PARSER, (L"HTTPD: end MyCrackURL(%a) path=%s ext=%s query=%a (iGLE=%d iErr=%d)\r\n", 
		pszRawURL, m_wszPath, m_wszExt, m_pszQueryString, GLE(err), err));
	
	return ret;

}

BOOL CHttpRequest::ParseContentLength(PCSTR pszMethod, TOKEN id) {
	PSTR pszTok = 0;
	int iLen = 0;

	if (m_dwContentLength) {
		DEBUGMSG(ZONE_PARSER,(L"HTTPD: Error: Duplicate Content-length headers in request\r\n"));
		return FALSE;
	}
	
	// get length (first token after "Content-Type;")
	if (m_bufRequest.NextTokenEOL(&pszTok, &iLen) && pszTok && iLen) {
		m_dwContentLength = atoi(pszTok);
		if ((int)m_dwContentLength < 0) {
			DEBUGMSG(ZONE_ERROR,(L"HTTPD: Error: Content-Length = %d, negative.  Rejecting the request\r\n",(int)m_dwContentLength));
			return FALSE;
		}
		
		return TRUE;
	}

	return FALSE;
}

BOOL CHttpRequest::ParseCookie(PCSTR pszMethod, TOKEN id) {
	PSTR pszTok = 0;
	int iLen = 0;

	if (m_pszCookie) {
		DEBUGMSG(ZONE_PARSER,(L"HTTPD: Error: Duplicate Cookie headers in request\r\n"));
		return FALSE;
	}
	
	// get cookie (upto \r\n after "Cookies;")
	if (m_bufRequest.NextTokenEOL(&pszTok, &iLen) && pszTok && iLen) {
		if (NULL == (m_pszCookie = MySzDupA(pszTok)))
			return FALSE;
		return TRUE;
	}
	return FALSE;
}

BOOL CHttpRequest::ParseAccept(PCSTR pszMethod, TOKEN id) {
	PSTR pszTok = 0;
	int iLen = 0;

	if (m_pszAccept) {
		DEBUGMSG(ZONE_PARSER,(L"HTTPD: Error: Duplicate accept headers in request\r\n"));
		return FALSE;
	}
	
	// get cookie (upto \r\n after "Cookies;")
	if (m_bufRequest.NextTokenEOL(&pszTok, &iLen) && pszTok && iLen) {
		if (NULL == (m_pszAccept = MySzDupA(pszTok)))
			return FALSE;
		return TRUE;
	}
	return FALSE;
}


BOOL CHttpRequest::ParseContentType(PCSTR pszMethod, TOKEN id) {
	PSTR pszTok = 0;
	int iLen = 0;

	if (m_pszContentType) {
		DEBUGMSG(ZONE_PARSER,(L"HTTPD: Error: Duplicate content-type headers in request\r\n"));
		return FALSE;
	}

	// get type (first token after "Content-Type;")
	if (m_bufRequest.NextTokenEOL(&pszTok, &iLen) && pszTok && iLen) {
		if (NULL == (m_pszContentType = MySzDupA(pszTok)))
			return FALSE;
		return TRUE;
	}
	return FALSE;
}

const char cszDateParseFmt[] = " %*3s, %02hd %3s %04hd %02hd:%02hd:%02hd GMT; length=%d";

BOOL CHttpRequest::ParseIfModifiedSince(PCSTR pszMethod, TOKEN id) {
	PSTR pszTok = 0;
	int iLen = 0;
	int i = 0;
	char szMonth[10];
	SYSTEMTIME st;
	ZEROMEM(&st);

	if (m_ftIfModifiedSince.dwLowDateTime || m_ftIfModifiedSince.dwHighDateTime) {
		DEBUGMSG(ZONE_PARSER,(L"HTTPD:  Error: Duplicate if-modified-since headers in request\r\n"));
		return FALSE;
	}
	
	// get the date (rest of line after If-Modified-Since)
	// NOTE: Note we are handling only one date format (the "reccomended" one)
	if (m_bufRequest.NextTokenEOL(&pszTok, &iLen) && pszTok && iLen) 	{
		i = sscanf(pszTok, cszDateParseFmt, &st.wDay, szMonth, &st.wYear, &st.wHour, &st.wMinute, &st.wSecond, &m_dwIfModifiedLength);
		if ( i >= 6) {
			// try to match month
			for (i=0; rgMonth[i]; i++) {
				if (0==strcmpi(szMonth, rgMonth[i]))  {
					st.wMonth = i;
					// convert to filetime & store
					SystemTimeToFileTime(&st, &m_ftIfModifiedSince);
					return TRUE;
				}
			}
		}
		DEBUGMSG(ZONE_ERROR, (L"HTTPD: Failed to parse If-Modified-Since(%a) Parsed: day=%02d month=%a(%d) year=%04d time=%02d:%02d:%02d len=%d\r\n", 
			pszTok, st.wDay, szMonth, i, st.wYear, st.wHour, st.wMinute, st.wSecond, m_dwIfModifiedLength));
	}
	return FALSE;
}

BOOL CHttpRequest::ParseAuthorization(PCSTR pszMethod, TOKEN id) {
	DEBUG_CODE_INIT;
	BOOL ret = FALSE;
	PSTR pszTok=0;
	int iLen=0;

	if (m_pszAuthType || m_pszRawRemoteUser) {
		DEBUGMSG(ZONE_PARSER,(L"HTTPD: Error: Duplicate Authorization headers in request\r\n"));
		myretleave(FALSE,99);
	}

	// get the auth scheme (first token after "Authorization;")
	if (!m_bufRequest.NextTokenWS(&pszTok, &iLen) || !pszTok || !iLen)
		myretleave(FALSE, 91);

	if (NULL == (m_pszAuthType = MySzDupA(pszTok)))
		myretleave(FALSE,100);

	// Read in the authentication paramaters and store for after we do vroot parsing.
	if (!m_bufRequest.NextTokenWS(&pszTok, &iLen) || !pszTok || !iLen)
		myretleave(FALSE, 97);

	if (NULL == (m_pszRawRemoteUser = MySzDupA(pszTok)))
		myretleave(FALSE, 98);
	
	ret = TRUE;
done:
	DEBUGMSG_ERR(ZONE_ERROR, (L"HTTPD: Auth FAILED (err=%d ret=%d)\r\n", err, ret));
	return ret; 
}

BOOL CHttpRequest::ParseConnection(PCSTR pszMethod, TOKEN id) {
	PSTR pszTok = 0;
	int iLen = 0;
	
	// get first token after "Connnection;"
	if (m_bufRequest.NextTokenEOL(&pszTok, &iLen) && pszTok && iLen) {
		if (0==strcmpi(pszTok, cszKeepAlive))
			m_fKeepAlive = TRUE;
		return TRUE;
	}
	return FALSE;
}

BOOL CHttpRequest::ParseHost(PCSTR pszMethod, TOKEN id) {
	PSTR pszTok = 0;
	int iLen = 0;

	if (m_pszHost) {
		DEBUGMSG(ZONE_PARSER,(L"HTTPD:  Error: Duplicate host header in request\r\n"));
		return FALSE;
	}

	if (!m_bufRequest.NextTokenEOL(&pszTok, &iLen))
		return FALSE;

	if (NULL == (m_pszHost = MySzDupA(pszTok)))
		return FALSE;

	return TRUE;
}

//
// CChunkParser parses chunked data during uploads.
//
class CChunkParser {
private:
	// Beginning of the buffer to start parsing.  This will usually contain
	// the trailing \r\n from the previous chunk (except on first read). 
	// Do not free this pointer, object doesn't own the memory.
	BYTE *m_pChunk;
	// Whether the trailer \r\n is present in this chunk
	BOOL m_fReadTrailerCRLF;
	// Size of buffer read in
	DWORD m_dwBufferSize;
	// Next bytes to parse out
	DWORD m_dwNextOut;
	// Maximum chunk-extension we'll read in.  This is to mitigate DoS type attacks
	// where malicious client sends a huge chunk extension just to have HTTPD sit
	// and parse it forever.
	static const DWORD m_dwMaxChunkExtension = 5000;
	// Pointer to calling request object (in case we need to read additional bytes)
	CHttpRequest *m_pRequest;
	// CBuffer and socket are only valid for ParseInitialChunkedUpload().
	CBuffer      *m_pBuffer;
	SOCKET        m_socket;

	// Retrieve the next byte to parse.
	BOOL GetNextChunkedChar(CHAR *pc);
	// Check is next character what we expect?
	BOOL CheckNextByte(CHAR cExpectedByte, BOOL *pfReadError=NULL);

public:
	CChunkParser(CHttpRequest *pRequest, CBuffer *pBuffer, SOCKET sock) {
		m_pRequest         = pRequest;
		m_fReadTrailerCRLF = FALSE;
        m_pBuffer          = pBuffer;
        m_socket           = sock;
	}

	// Used for ISAPI ReadClient
	CChunkParser(CHttpRequest *pRequest) {
		m_pRequest         = pRequest;
		m_fReadTrailerCRLF = TRUE;
		m_pBuffer          = NULL;
		m_socket           = INVALID_SOCKET;
	}
	
	// Needs to be called multiple times per read
	void SetChunkHeader(BYTE *pChunk, DWORD dwBufferSize) {
		m_pChunk       = pChunk;
		m_dwBufferSize = dwBufferSize;
		m_dwNextOut    = 0;
	}

	// Parse out the chunk headers
	BOOL ParseChunkHeader(DWORD *pdwChunkHeaderBytesToSwallow, DWORD *pdwChunkSize);
};


// Retrieve the next byte to parse.  Either use the buffer read in already, or else
// read in one more byte from the network.
BOOL CChunkParser::GetNextChunkedChar(CHAR *pc) {
	if (m_dwNextOut < m_dwBufferSize) {
		// The next byte has been read in already.
		*pc = m_pChunk[m_dwNextOut];
		m_dwNextOut++;
		return TRUE;
	}

    BOOL fRet;

	// Otherwise we need to read in the next byte now.  Using m_pBuffer directly
	// versus ReadClientNonChunked is the result of the way buffer is implemented in HTTPD 
	// and particular the processing of the first bytes of POST on an SSL request.
	// Fixing HTTP core buffer to not require extra step is too involved at present.

	if (! m_pBuffer) {
		DEBUGCHK((m_pBuffer==NULL) && (m_socket == INVALID_SOCKET));
		DWORD dwToRead = 1;
		fRet = m_pRequest->ReadClientNonChunked((PVOID)pc,&dwToRead);
	}
	else {
		DEBUGCHK(m_pChunk==((BYTE*)m_pBuffer->m_pszBuf+m_pBuffer->m_iNextUnparsedChunk));
		HRINPUT hi = m_pBuffer->RecvBody(m_socket,1,TRUE,FALSE,m_pRequest,FALSE);
		if (hi != S_OK)
			return FALSE;

		// Adjust pointers and lengths, which may have changed during the read
		m_pChunk = (BYTE *)m_pBuffer->m_pszBuf + m_pBuffer->m_iNextUnparsedChunk;
		m_dwBufferSize = m_pBuffer->m_iNextIn - m_pBuffer->m_iNextUnparsedChunk;

		*pc = m_pChunk[m_dwNextOut];
		m_dwNextOut++;

		fRet = TRUE;
	}

    return fRet;
}

// Get next byte and make sure it's what we expect it to be
BOOL CChunkParser::CheckNextByte(CHAR cExpectedByte, BOOL *pfReadError) {
	CHAR c;
	if (! GetNextChunkedChar(&c)) {
		if (pfReadError)
			*pfReadError = TRUE;
		return FALSE;
	}

	if (pfReadError)
		*pfReadError = FALSE;

	if (c !=  cExpectedByte) {
		// Even though this isn't most useful error, we set it anyway so that 
		// calling ISAPI extension doesn't get confused by what may 
		// already be in lastError value.
		SetLastError(ERROR_INTERNAL_ERROR);
		return FALSE;
	}

	return TRUE;
}

// Parse out the chunk header, returning the size of the chunk itself
// as well as the chunk body size.  This function will always parse out an entire chunk header,
// calling recv() itself if needed to get additional chunk data in the event that the 
// entire chunk header wasn't read in the previous recv().
BOOL CChunkParser::ParseChunkHeader(DWORD *pdwChunkHeaderBytesToSwallow, DWORD *pdwChunkSize) {
	DEBUGCHK(m_pChunk && (m_dwNextOut == 0));

	CHAR c1;
	DWORD dwChunkSize = 0;
	DWORD dwChunkExtensionSize = 0;

	//
	//  Swallow the CRLF from the preceeding chunk.
	//
	if (m_fReadTrailerCRLF) {
		if (! CheckNextByte('\r') || !CheckNextByte('\n')) {
			DEBUGMSG(ZONE_ERROR,(L"HTTPD: ERROR: Unable to read trailing CRLF for chunked header\r\n"));
			return FALSE;
		}
	}
	else {
		// Always read this after the first chunk is processed.
		m_fReadTrailerCRLF = TRUE;
	}

	// 
	//  Read in the length of the new chunk.  Length is encoded in base 16, will
	//  be terminated by a CRLF or by a ';' for the optional chunk-extension field
	//
	while (1) {
		DWORD dwDigit;
		if (! GetNextChunkedChar(&c1)) {
			DEBUGMSG(ZONE_ERROR,(L"HTTPD: ERROR: Unable to read number of bytes in chunk\r\n"));
			return FALSE;
		}

		if (isdigit(c1))
			dwDigit = c1 - '0';
		else if ((c1 >= 'A') && (c1 <= 'F'))
			dwDigit = c1 - 'A' + 10;
		else if ((c1 >= 'a') && (c1 <= 'f'))
			dwDigit = c1 - 'a' + 10;
		else if (c1 == ';')
			break; // optional extension
		else if (c1 == '\r')
			break; // closing CRLF is beginning
		else {
			DEBUGMSG(ZONE_ERROR,(L"HTTPD: ERROR: Chunked upload failed because non-hex digit/; specified in number\r\n"));
			SetLastError(ERROR_INTERNAL_ERROR);
			return FALSE;
		}

		// dwChunkSize = dwChunkSize*16 + dwDigit
		if (FAILED(ULongMult(dwChunkSize,16,&dwChunkSize)) || FAILED(ULongAdd(dwChunkSize,dwDigit,&dwChunkSize)))  {
			DEBUGMSG(ZONE_ERROR,(L"HTTPD: ERROR: Chunked upload failed because of integer overflow on chunked length\r\n"));
			SetLastError(ERROR_INTERNAL_ERROR);
			return FALSE;            
		}
	}

	//
	//  Read the optional chunk-extension.  This begins with a ';' and is terminated
	//  by a CRLF.  We just swallow chunk-extension.
	//
	if (c1 == ';') {
		while (dwChunkExtensionSize < m_dwMaxChunkExtension) {
			if (! GetNextChunkedChar(&c1)) {
				DEBUGMSG(ZONE_ERROR,(L"HTTPD: ERROR: Unable to read a byte from chunked-extension\r\n"));
				return FALSE;
			}
			dwChunkExtensionSize++;

			if (c1 == '\r') {
				// Check for '\n'.  It is not required at this stage, spec allows
				// for \r without \n to be part of chunk-extension.
				BOOL fReadError;

				if (CheckNextByte('\n',&fReadError))
					break; // We're done reading.

				if (fReadError) {
					DEBUGMSG(ZONE_ERROR,(L"HTTPD: ERROR: Unable to read a byte from chunked-extension\r\n"));
					return FALSE;
				}

				dwChunkExtensionSize++;
			}
		}

		if (dwChunkExtensionSize >= m_dwMaxChunkExtension) {
			DEBUGMSG(ZONE_ERROR,(L"HTTPD: ERROR: Chunk-Extension has exceeded maximum size (%d) bytes.  Ending request\r\n",m_dwMaxChunkExtension));
			SetLastError(ERROR_INTERNAL_ERROR);
			return FALSE;
		}
	}
	else {
		// In non-chunk extension case, just read closing LF
		DEBUGCHK(c1 == '\r');

		if (! CheckNextByte('\n')) {
			DEBUGMSG(ZONE_ERROR,(L"HTTPD: Could not read closing '\\n' in chunked header\r\n"));
			return FALSE;
		}     
	}

	//
	//  Indicate to caller how many bytes are in chunk and how many bytes of its buffer
	//  we've read.  If we've had to call recv() because buffer caller passed wasn't
	//  big enough, we still only return size of its buffer (rather than additional recv()
	//  bytes) because caller only uses this to figure out how much of buffer to throw-away

	*pdwChunkSize                 = dwChunkSize;
	*pdwChunkHeaderBytesToSwallow = m_dwNextOut;

	return TRUE;
}

// When client has sent HTTPD a chunked-upload, parse through any initial POST
// data and validated chunked headers and strip them out so ISAPI doesn't have to.
BOOL CBuffer::ParseInitialChunkedUpload(CHttpRequest *pRequest, SOCKET sock, DWORD *pdwBytesInNextChunk) {
	DEBUGCHK((*pdwBytesInNextChunk) == 0);
	DEBUGCHK(m_iNextUnparsedChunk == 0);

	DWORD dwRemaining; // Amount of POST data that remains to be processed.
	DWORD dwBytesInNextChunk;
	HRINPUT hi;

	DWORD dwChunkHeaderSwallow; // # of bytes in current chunked header, to ignore

	CChunkParser chunkParser(pRequest,this,sock);

	m_iNextUnparsedChunk = m_iNextOut;

	while (1) {
		// 
		// Parse the chunked header, and then remove the chunk header itself
		// from the stream via memmove
		// 
		BufferConsistencyChecks();

		dwRemaining = m_iNextIn - m_iNextUnparsedChunk;
		chunkParser.SetChunkHeader(GetNextChunkToParse(), dwRemaining);
			
		if (! chunkParser.ParseChunkHeader(&dwChunkHeaderSwallow,&dwBytesInNextChunk))
			return FALSE;

		// Recalculate dwRemaining because m_iNextIn may have been incremented on a read.
		dwRemaining = m_iNextIn - m_iNextUnparsedChunk;

		DEBUGCHK(dwChunkHeaderSwallow <= dwRemaining);

		if (dwBytesInNextChunk == 0) {
			// we've read in last chunk.
			dwBytesInNextChunk = (DWORD)-1;
			// There may be extra info after this chunk.  Discard it.
			m_iNextDecrypt = m_iNextInFollow = m_iNextIn = m_iNextUnparsedChunk;
			break;
		}

		if (dwRemaining) {
			DWORD dwToMove = dwRemaining-dwChunkHeaderSwallow;
			memmove(GetNextChunkToParse(),GetNextChunkToParse()+dwChunkHeaderSwallow,dwToMove);
		}

		// Because we've stripped bits out of buffer, cleanup other pointers as well
		m_iNextDecrypt  = m_iNextDecrypt - dwChunkHeaderSwallow;
		m_iNextInFollow = m_iNextInFollow - dwChunkHeaderSwallow;
		m_iNextIn       = m_iNextIn - dwChunkHeaderSwallow;
		dwRemaining     = m_iNextIn - m_iNextUnparsedChunk;

		if (dwBytesInNextChunk > dwRemaining) {
			//
			//  There are more bytes in current chunk than we have received already.
			//  Since we want to still give ISAPI extensions upto PostReadSize worth of 
			//  data (usually 48KB), receive additional chunk (or as much as we can)
			//  into the buffer.
			//

			m_iNextUnparsedChunk += dwRemaining;
			dwBytesInNextChunk -= dwRemaining;

			if (Count() >= g_pVars->m_dwPostReadSize) {
				// We have already read PostReadSize (48KB usually) worth of data.
				// The ISAPI extension must do ReadClient() from here on to get more data.
				break;
			}

			// Either read in remainder of chunk or as much data as we can per m_dwPostReadSize.
			DWORD dwToRead = min(g_pVars->m_dwPostReadSize-Count(),dwBytesInNextChunk);

			DWORD dwInitialBufEnd = m_iNextIn;

			// Receive the data
			hi = RecvBody(sock,dwToRead,TRUE,FALSE,pRequest,FALSE);
			if (hi != S_OK)
				return FALSE;

			// Above call will fill internal buffer to at least dwToRead bytes
			// or else it will fail.  It may get more for SSL case (since
			// it reads in enough to decrypt entire buffer, rather than leaving 
			// unencrypted stuff at end).
			DWORD dwNumBytesRead = m_iNextIn - dwInitialBufEnd;
			DEBUGCHK(dwNumBytesRead >= dwToRead);

			if (dwNumBytesRead < dwBytesInNextChunk) {
				// We're hit the maximum amount we can read on an initial POST.  
				// Decrement the amount remaining in this block, but can't do anything
				// beyond this.  It's on ISAPI ReadClient() now.
				dwBytesInNextChunk -= dwNumBytesRead;
				break; 
			}
		}
		else {
			// We have already read into the entire chunk into memory on a previous recv()
			// We'll go through loop again to look at next chunk.
		}

		m_iNextUnparsedChunk += dwBytesInNextChunk;
	}

	BufferConsistencyChecks();

	m_iNextDecrypt = m_iNextInFollow = m_iNextIn;

	*pdwBytesInNextChunk = dwBytesInNextChunk;
	return TRUE;
}

// When ISAPI extension is calling ReadClient() and there is chunked uploading,
// do appropriate stripping out of chunked headers and do basic validation.
BOOL CHttpRequest::ReadClientChunked(PVOID pv, PDWORD pdw) {
	DEBUGCHK(m_fIsChunkedUpload);

	//
	//  Directly receive bytes from network
	//

	if (m_dwBytesRemainingInChunk == (DWORD)-1) {
		// Signal to ISAPI extension that there is no more data available to be read.
		*pdw = 0;
		return TRUE;
	}

	// Do not recv() beyond current chunk length (since this could be last chunk)
	DWORD dwRead = min(*pdw,m_dwBytesRemainingInChunk);

	// Call through to real read client to perform the read
	BOOL fReadClient = ReadClientNonChunked(pv,&dwRead);
	if (! fReadClient)
		return FALSE;

	*pdw = dwRead;

	if (m_dwBytesRemainingInChunk > dwRead) {
		// The caller buffer was not large enough to contain the current chunk
		// we're reading.  In this case we're done.
		m_dwBytesRemainingInChunk -= *pdw;
		return TRUE;
	}

	//
	//  We've read to the end of current chunk, need to process chunk headers
	//  and trailers and strip them from what we send back to ISAPI.  If there
	//  are multiple chunks in one read buffer, handle that, but don't read 
	//  beyond last chunk in buffer (since ISAPI does its own buffer mgmt).  
	//

	// Number of bytes past the chunk body, starting with trailing CRLF
	DWORD dwRemaining    = *pdw - m_dwBytesRemainingInChunk;
	BYTE *pbUnparsedData = (BYTE*)pv + m_dwBytesRemainingInChunk;

	CChunkParser chunkParser(this);

	while (1) {
		DWORD dwChunkHeaderSwallow;
		chunkParser.SetChunkHeader(pbUnparsedData, dwRemaining);
		
		if (! chunkParser.ParseChunkHeader(&dwChunkHeaderSwallow, &m_dwBytesRemainingInChunk))
			return FALSE;

		DEBUGCHK(dwChunkHeaderSwallow <= dwRemaining);

		// Account for bytes we've stripped out in chunked header/trailer.
		*pdw = (*pdw) - dwChunkHeaderSwallow;
		dwRemaining -= dwChunkHeaderSwallow;

		// Remove the chunk trailer & header from the stream to be returned to client.
		if (dwRemaining)
			memmove(pbUnparsedData,pbUnparsedData+dwChunkHeaderSwallow,dwRemaining);

		if (m_dwBytesRemainingInChunk == 0) {
			// This is the last chunk.  Setup for next ReadClient() call so we indicate this.
			m_dwBytesRemainingInChunk = (DWORD)-1;
			break;
		}

		if (m_dwBytesRemainingInChunk > dwRemaining) {
			// The size of this chunk is larger than the rest of the buffer we have
			// available.  So ISAPI will need another ReadClient with new buffer.
			m_dwBytesRemainingInChunk -= dwRemaining;
			break;
		}
		else {
			// The buffer contains the entire chunk body.  Read next chunk.
			pbUnparsedData = pbUnparsedData + m_dwBytesRemainingInChunk;
		}
	}

	return TRUE;
}


