/* Copyright (C) 2005 by Steven A. Bone, sbone@pobox.com. All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission. 
**  
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE. */

/* COMPILATION NOTE:
   Note that the Platform SDK headers and
   link libraries for Windows XP SP2 or newer are required to compile
   xmlrpc-c for this module.  If you are not using this server, it is 
   safe to exclude the xmlrpc_server_w32httpsys.c file from the xmlrpc
   project and these dependencies will not be required.  You can get the 
   latest platform SDK at 
   http://www.microsoft.com/msdownload/platformsdk/sdkupdate/
   Be sure after installation to choose the program to "register the PSDK
   directories with Visual Studio" so the newer headers are found.
*/

#ifndef UNICODE
#define UNICODE
#endif

#ifndef _UNICODE
#define _UNICODE
#endif

#include "xmlrpc-c/base.h"
#include "xmlrpc-c/server.h"
#include "xmlrpc-c/server_w32httpsys.h"
#include "version.h"

#if MUST_BUILD_HTTP_SYS_SERVER > 0

/* See compilation note above if this header is not found! */
#include <http.h>
#include <windows.h>
#include <strsafe.h>

#pragma comment( lib, "httpapi" )


/* XXX - This variable is *not* currently threadsafe. Once the server has
** been started, it must be treated as read-only. */
static xmlrpc_registry *global_registryP;

//set TRUE if you want a log
static BOOL g_bDebug;
//set log filename
static char g_fLogFile[MAX_PATH];
//do you want OutputDebugString() to be called?
static BOOL g_bDebugString;

//
// Macros.
//
#define INITIALIZE_HTTP_RESPONSE( resp, status, reason )                    \
    do                                                                      \
    {                                                                       \
        RtlZeroMemory( (resp), sizeof(*(resp)) );                           \
        (resp)->StatusCode = (status);                                      \
        (resp)->pReason = (reason);                                         \
        (resp)->ReasonLength = (USHORT) strlen(reason);                     \
    } while (FALSE)


#define ADD_KNOWN_HEADER(Response, HeaderId, RawValue)                      \
    do                                                                      \
    {                                                                       \
        (Response).Headers.KnownHeaders[(HeaderId)].pRawValue = (RawValue); \
        (Response).Headers.KnownHeaders[(HeaderId)].RawValueLength =        \
            (USHORT) strlen(RawValue);                                      \
    } while(FALSE)

#define ALLOC_MEM(cb) HeapAlloc(GetProcessHeap(), 0, (cb))
#define FREE_MEM(ptr) HeapFree(GetProcessHeap(), 0, (ptr))

//
// Prototypes for Internal Functions.
//
DWORD
DoReceiveRequests(
    HANDLE hReqQueue,
	const xmlrpc_server_httpsys_parms * const parmsP
    );

DWORD
SendHttpResponse(
    IN HANDLE        hReqQueue,
    IN PHTTP_REQUEST pRequest,
    IN USHORT        StatusCode,
    IN PSTR          pReason,
    IN PSTR          pEntity
    );

DWORD
SendHttpResponseAuthRequired(
    IN HANDLE        hReqQueue,
    IN PHTTP_REQUEST pRequest
    );

void
processRPCCall(
    xmlrpc_env *     const envP,
    IN HANDLE        hReqQueue,
    IN PHTTP_REQUEST pRequest
    );

__inline void TraceA(const char *format, ...);
__inline void TraceW(const wchar_t *format, ...);


//
// External Function Implementation.
//

void
xmlrpc_server_httpsys(
	xmlrpc_env *                        const envP,
    const xmlrpc_server_httpsys_parms * const parmsP,
    unsigned int                        const parm_size
	)
{
	ULONG           retCode;
    HANDLE          hReqQueue      = NULL;
    HTTPAPI_VERSION HttpApiVersion = HTTPAPI_VERSION_1;
	WCHAR           wszURL[35];

	XMLRPC_ASSERT_ENV_OK(envP);

    if (parm_size < XMLRPC_HSSIZE(authfn))
	{
        xmlrpc_env_set_fault_formatted(
            envP, XMLRPC_INTERNAL_ERROR,
            "You must specify members at least up through "
            "'authfn' in the server parameters argument.  "
            "That would mean the parameter size would be >= %u "
            "but you specified a size of %u",
            XMLRPC_HSSIZE(authfn), parm_size);
		return;
	}

	//Set logging options
	if (parmsP->logLevel>0)
		g_bDebug=TRUE;
	else
		g_bDebug=FALSE;

	if (parmsP->logLevel>1)
		g_bDebugString=TRUE;
	else
		g_bDebugString=FALSE;

	if (!parmsP->logFile)
		g_bDebug=FALSE;
	else
		StringCchPrintfA(g_fLogFile,MAX_PATH,parmsP->logFile);

	//construct the URL we are listening on
	if (parmsP->useSSL!=0)
		StringCchPrintf(wszURL,35,L"https://+:%u/RPC2",parmsP->portNum);
	else
		StringCchPrintf(wszURL,35,L"http://+:%u/RPC2",parmsP->portNum);

	global_registryP = parmsP->registryP;

	// Initialize HTTP APIs.
	retCode = HttpInitialize( 
				HttpApiVersion,
				HTTP_INITIALIZE_SERVER,    // Flags
				NULL                       // Reserved
				);
	if (retCode != NO_ERROR)
	{
		xmlrpc_env_set_fault_formatted(
			envP, XMLRPC_INTERNAL_ERROR,
			"HttpInitialize failed with %lu \n ",
			retCode);
		return;
	}

	// Create a Request Queue Handle
	retCode = HttpCreateHttpHandle(
				&hReqQueue,        // Req Queue
				0                  // Reserved
				);
	if (retCode != NO_ERROR)
	{ 
		xmlrpc_env_set_fault_formatted(
			envP, XMLRPC_INTERNAL_ERROR,
			"HttpCreateHttpHandle failed with %lu \n ",
			retCode);
		goto CleanUp;
	}

	retCode = HttpAddUrl(
				hReqQueue,    // Req Queue
				wszURL,      // Fully qualified URL
				NULL          // Reserved
				);

	if (retCode != NO_ERROR)
	{
		xmlrpc_env_set_fault_formatted(
			envP, XMLRPC_INTERNAL_ERROR,
			"HttpAddUrl failed with %lu \n ",
			retCode);
		goto CleanUp;
	}

	TraceW( L"we are listening for requests on the following url: %ws\n", wszURL);

	// Loop while receiving requests
	for(;;)
	{
		TraceW( L"Calling DoReceiveRequests()\n");
		retCode = DoReceiveRequests(hReqQueue, parmsP);
		if(NO_ERROR == retCode)
		{
			TraceW( L"DoReceiveRequests() returned NO_ERROR, breaking");
			break;
		}
	}

CleanUp:

	TraceW( L"Tearing down the server.\n", wszURL);

	// Call HttpRemoveUrl for the URL that we added.
	HttpRemoveUrl( hReqQueue, wszURL );

	// Close the Request Queue handle.
	if(hReqQueue)
		CloseHandle(hReqQueue);

	// Call HttpTerminate.
	HttpTerminate(HTTP_INITIALIZE_SERVER, NULL);
	return;
}

//
// Internal Function Implementations.
//

__inline void TraceA(const char *format, ...)
{
	if(g_bDebug)
	{
		if (format)
		{
			va_list arglist;
			char str[4096];

			va_start(arglist, format);
			if (g_fLogFile)
			{
				FILE *fout = fopen(g_fLogFile, "a+t");
				if (fout)
				{
					vfprintf(fout, format, arglist);
					fclose(fout);
				}
			}

			StringCchVPrintfA(str,4096, format, arglist);
			printf(str);

			if (g_bDebugString)
			{
				
				OutputDebugStringA(str);
			}

			va_end(arglist);
		}
	}
}

__inline void TraceW(const wchar_t *format, ...)
{
	if(g_bDebug)
	{
		if (format)
		{
			va_list arglist;
			wchar_t str[4096];

			va_start(arglist, format);
			if (g_fLogFile)
			{
				FILE *fout = fopen(g_fLogFile, "a+t");
				if (fout)
				{
					vfwprintf(fout, format, arglist);
					fclose(fout);
				}
			}
			
			StringCchVPrintfW(str, 4096, format, arglist);
			wprintf(str);
			
			if (g_bDebugString)
			{				
				OutputDebugStringW(str);
			}

			va_end(arglist);
		}
	}
}

/*
 * This is a blocking function that merely sits on the request queue
 * for our URI and processes them one at a time.  Once a request comes
 * in, we check it for content-type, content-length, and verb.  As long
 * as the initial validations are done, we pass the request to the 
 * processRPCCall() function, which collects the body of the request
 * and processes it.  If we get an error back other than network type,
 * we are responsible for notifing the client.
 */
DWORD
DoReceiveRequests(
    IN HANDLE hReqQueue,
	const xmlrpc_server_httpsys_parms * const parmsP
    )
{
    ULONG              result;
    HTTP_REQUEST_ID    requestId;
    DWORD              bytesRead;
    PHTTP_REQUEST      pRequest;
    PCHAR              pRequestBuffer;
    ULONG              RequestBufferLength;
	xmlrpc_env			env;
	char				szHeaderBuf[255];
	long				lContentLength;

    // Allocate a 2K buffer. Should be good for most requests, we'll grow 
    // this if required. We also need space for a HTTP_REQUEST structure.
    RequestBufferLength = sizeof(HTTP_REQUEST) + 2048;
    pRequestBuffer      = (PCHAR) ALLOC_MEM( RequestBufferLength );
    if (pRequestBuffer == NULL)
    {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    pRequest = (PHTTP_REQUEST)pRequestBuffer;

    // Wait for a new request -- This is indicated by a NULL request ID.
    HTTP_SET_NULL_ID( &requestId );
    for(;;)
    {
        RtlZeroMemory(pRequest, RequestBufferLength);

        result = HttpReceiveHttpRequest(
                    hReqQueue,          // Req Queue
                    requestId,          // Req ID
                    0,                  // Flags
                    pRequest,           // HTTP request buffer
                    RequestBufferLength,// req buffer length
                    &bytesRead,         // bytes received
                    NULL                // LPOVERLAPPED
                    );

        if(NO_ERROR == result)
        {
            // Got a request with a filled buffer.
            switch(pRequest->Verb)
            {
                case HttpVerbPOST:

					TraceW(L"Got a POST request for %ws \n",pRequest->CookedUrl.pFullUrl);				
					
					//Check if we need use authorization.
					if(parmsP->authfn)
					{
						xmlrpc_env_init(&env);
						if(pRequest->Headers.KnownHeaders[HttpHeaderAuthorization].RawValueLength<6)
						{
							xmlrpc_env_set_fault( &env, XMLRPC_REQUEST_REFUSED_ERROR, 
								"Authorization header too short.");
						}
						else
						{
							//unencode the headers
							if(_strnicmp("basic ",pRequest->Headers.KnownHeaders[HttpHeaderAuthorization].pRawValue,6)!=0)
							{
								xmlrpc_env_set_fault( &env, XMLRPC_REQUEST_REFUSED_ERROR, 
									"Authorization header is not of type basic.");
							}
							else
							{
								xmlrpc_mem_block * decoded;
								
								decoded = xmlrpc_base64_decode(&env,pRequest->Headers.KnownHeaders[HttpHeaderAuthorization].pRawValue+6,pRequest->Headers.KnownHeaders[HttpHeaderAuthorization].RawValueLength-6);
								if(!env.fault_occurred)
								{
									char *pDecodedStr;
									char *pUser;
									char *pPass;
									char *pColon;

									pDecodedStr = (char*)malloc(decoded->_size+1);
									memcpy(pDecodedStr,decoded->_block,decoded->_size);
									pDecodedStr[decoded->_size]='\0';
									pUser = pPass = pDecodedStr;
									pColon=strchr(pDecodedStr,':');
									if(pColon)
									{
										*pColon='\0';
										pPass=pColon+1;
										//The authfn should set env to fail if auth is denied.
										parmsP->authfn(&env,pUser,pPass);
									}
									else
									{
										xmlrpc_env_set_fault( &env, XMLRPC_REQUEST_REFUSED_ERROR, 
											"Decoded auth not of the correct format.");
									}
									free(pDecodedStr);
								}
								if(decoded)
									XMLRPC_MEMBLOCK_FREE(char, decoded);
							}
						}
						if(env.fault_occurred)
						{
							//request basic authorization, as the user did not provide it.
							xmlrpc_env_clean(&env);
							TraceW(L"POST request did not provide valid authorization header.");
							result = SendHttpResponseAuthRequired( hReqQueue, pRequest);
							break;
						}
						xmlrpc_env_clean(&env);
					}
					
					//Check content type to make sure it is text/xml.
					memcpy(szHeaderBuf,pRequest->Headers.KnownHeaders[HttpHeaderContentType].pRawValue,pRequest->Headers.KnownHeaders[HttpHeaderContentType].RawValueLength);
					szHeaderBuf[pRequest->Headers.KnownHeaders[HttpHeaderContentType].RawValueLength]='\0';
					if (_stricmp(szHeaderBuf,"text/xml")!=0)
					{
						//We only handle text/xml data.  Anything else is not valid.
						TraceW(L"POST request had an unsupported content-type: %s \n", szHeaderBuf);
						result = SendHttpResponse(
									hReqQueue, 
									pRequest,
									400,
									"Bad Request",
									NULL
									);
						break;
					}

					//Check content length to make sure it exists and is not too big.
					memcpy(szHeaderBuf,pRequest->Headers.KnownHeaders[HttpHeaderContentLength].pRawValue,pRequest->Headers.KnownHeaders[HttpHeaderContentLength].RawValueLength);
					szHeaderBuf[pRequest->Headers.KnownHeaders[HttpHeaderContentLength].RawValueLength]='\0';
					lContentLength = atol(szHeaderBuf);
					if (lContentLength<=0)
					{
						//Make sure a content length was supplied.
						TraceW(L"POST request did not include a content-length \n", szHeaderBuf);
						result = SendHttpResponse(
									hReqQueue, 
									pRequest,
									411,
									"Length Required",
									NULL
									);
						break;
					}						
					if((size_t) lContentLength > xmlrpc_limit_get(XMLRPC_XML_SIZE_LIMIT_ID))
					{
						//Content-length is too big for us to handle
						TraceW(L"POST request content-length is too big for us to handle: %d bytes \n", lContentLength);
						result = SendHttpResponse(
									hReqQueue, 
									pRequest,
									500,
									"content-length too large",
									NULL
									);
						break;
					}

					//our initial validations of POST, content-type, and content-length
					//all check out.  Collect and pass the complete buffer to the 
					//XMLRPC-C library
					
					xmlrpc_env_init(&env);
                    processRPCCall(&env,hReqQueue, pRequest);
					if (env.fault_occurred) 
					{
						//if we fail and it is anything other than a network error,
						//we should return a failure response to the client.
						if (env.fault_code != XMLRPC_NETWORK_ERROR)
						{
							if (env.fault_string)
								result = SendHttpResponse(
									hReqQueue, 
									pRequest,
									500,
									env.fault_string,
									NULL
									);
							else
								result = SendHttpResponse(
									hReqQueue, 
									pRequest,
									500,
									"Unknown Error",
									NULL
									);
						}
					}
					
					xmlrpc_env_clean(&env);
                    break;

                default:
					//We only handle POST data.  Anything else is not valid.
                    TraceW(L"Got an unsupported Verb request for URI %ws \n", pRequest->CookedUrl.pFullUrl);
			
                    result = SendHttpResponse(
                                hReqQueue, 
                                pRequest,
                                405,
                                "Method Not Allowed",
                                NULL
                                );
                    break;
            }
            if(result != NO_ERROR)
            {
                break;
            }

            // Reset the Request ID so that we pick up the next request.
            HTTP_SET_NULL_ID( &requestId );
        }
        else if(result == ERROR_MORE_DATA)
        {
            // The input buffer was too small to hold the request headers
            // We have to allocate more buffer & call the API again. 
            //
            // When we call the API again, we want to pick up the request
            // that just failed. This is done by passing a RequestID.
            // This RequestID is picked from the old buffer.
            requestId = pRequest->RequestId;

            // Free the old buffer and allocate a new one.
            RequestBufferLength = bytesRead;
            FREE_MEM( pRequestBuffer );
            pRequestBuffer = (PCHAR) ALLOC_MEM( RequestBufferLength );

            if (pRequestBuffer == NULL)
            {
                result = ERROR_NOT_ENOUGH_MEMORY;
                break;
            }

            pRequest = (PHTTP_REQUEST)pRequestBuffer;

        }
        else if(ERROR_CONNECTION_INVALID == result && 
                !HTTP_IS_NULL_ID(&requestId))
        {
            // The TCP connection got torn down by the peer when we were
            // trying to pick up a request with more buffer. We'll just move
            // onto the next request.            
            HTTP_SET_NULL_ID( &requestId );
        }
        else
        {
            break;
        }

    } // for(;;)
Cleanup:

    if(pRequestBuffer)
    {
        FREE_MEM( pRequestBuffer );
    }

    return result;
}

/*
 * SendHttpResponse sends a text/html content type back with
 * the user specified status code and reason.  Used for returning
 * errors to clients.
 */
DWORD
SendHttpResponse(
    IN HANDLE        hReqQueue,
    IN PHTTP_REQUEST pRequest,
    IN USHORT        StatusCode,
    IN PSTR          pReason,
    IN PSTR          pEntityString
    )
{
    HTTP_RESPONSE   response;
    HTTP_DATA_CHUNK dataChunk;
    DWORD           result;
    DWORD           bytesSent;
	CHAR			szServerHeader[20];

    // Initialize the HTTP response structure.
    INITIALIZE_HTTP_RESPONSE(&response, StatusCode, pReason);

    ADD_KNOWN_HEADER(response, HttpHeaderContentType, "text/html");
	
	StringCchPrintfA(szServerHeader,20, "Xmlrpc-c/%s",XMLRPC_C_VERSION);
	ADD_KNOWN_HEADER(response, HttpHeaderServer, szServerHeader);
   
    if(pEntityString)
    {
        // Add an entity chunk
        dataChunk.DataChunkType           = HttpDataChunkFromMemory;
        dataChunk.FromMemory.pBuffer      = pEntityString;
        dataChunk.FromMemory.BufferLength = (ULONG) strlen(pEntityString);

        response.EntityChunkCount         = 1;
        response.pEntityChunks            = &dataChunk;
    }

    // Since we are sending all the entity body in one call, we don't have 
    // to specify the Content-Length.
    result = HttpSendHttpResponse(
                    hReqQueue,           // ReqQueueHandle
                    pRequest->RequestId, // Request ID
                    0,                   // Flags
                    &response,           // HTTP response
                    NULL,                // pReserved1
                    &bytesSent,          // bytes sent   (OPTIONAL)
                    NULL,                // pReserved2   (must be NULL)
                    0,                   // Reserved3    (must be 0)
                    NULL,                // LPOVERLAPPED (OPTIONAL)
                    NULL                 // pReserved4   (must be NULL)
                    );

    if(result != NO_ERROR)
    {
		TraceW(L"HttpSendHttpResponse failed with %lu \n", result);
    }

    return result;
}

/*
 * SendHttpResponseAuthRequired sends a 401 status code requesting authorization
 */
DWORD
SendHttpResponseAuthRequired(
    IN HANDLE        hReqQueue,
    IN PHTTP_REQUEST pRequest
    )
{
    HTTP_RESPONSE   response;
    DWORD           result;
    DWORD           bytesSent;
	CHAR			szServerHeader[20];

    // Initialize the HTTP response structure.
    INITIALIZE_HTTP_RESPONSE(&response, 401, "Authentication Required");

    // Add the WWW_Authenticate header.
    ADD_KNOWN_HEADER(response, HttpHeaderWwwAuthenticate, "Basic realm=\"xmlrpc\"");
	
	StringCchPrintfA(szServerHeader,20, "Xmlrpc-c/%s",XMLRPC_C_VERSION);
	ADD_KNOWN_HEADER(response, HttpHeaderServer, szServerHeader);
   
    // Since we are sending all the entity body in one call, we don't have 
    // to specify the Content-Length.
    result = HttpSendHttpResponse(
                    hReqQueue,           // ReqQueueHandle
                    pRequest->RequestId, // Request ID
                    0,                   // Flags
                    &response,           // HTTP response
                    NULL,                // pReserved1
                    &bytesSent,          // bytes sent   (OPTIONAL)
                    NULL,                // pReserved2   (must be NULL)
                    0,                   // Reserved3    (must be 0)
                    NULL,                // LPOVERLAPPED (OPTIONAL)
                    NULL                 // pReserved4   (must be NULL)
                    );

    if(result != NO_ERROR)
    {
		TraceW(L"SendHttpResponseAuthRequired failed with %lu \n", result);
    }

    return result;
}

/*
 * processRPCCall() is called after some validations.  The assumption is that
 * the request is an HTTP post of content-type text/xml with a content-length
 * that is less than the maximum the library can handle.
 *
 * The caller should check the error status, and if the error was other than
 * a network type, respond back to the client to let them know the call failed.
 */
void
processRPCCall(
    xmlrpc_env *     const envP,
    IN HANDLE        hReqQueue,
    IN PHTTP_REQUEST pRequest
    )
{
    HTTP_RESPONSE   response;
    DWORD           result;
    DWORD           bytesSent;
    PUCHAR          pEntityBuffer;
    ULONG           EntityBufferLength;
    ULONG           BytesRead;
#define MAX_ULONG_STR ((ULONG) sizeof("4294967295"))
    CHAR            szContentLength[MAX_ULONG_STR];
	CHAR			szServerHeader[20];
    HTTP_DATA_CHUNK dataChunk;
    ULONG           TotalBytesRead = 0;
	xmlrpc_mem_block * body;
	xmlrpc_mem_block * output;

    BytesRead  = 0;
	body	   = NULL;
	output     = NULL;

    // Allocate some space for an entity buffer.
    EntityBufferLength = 2048;	
    pEntityBuffer      = (PUCHAR) ALLOC_MEM( EntityBufferLength );
    if (pEntityBuffer == NULL)
    {
		xmlrpc_env_set_fault_formatted(
				envP, XMLRPC_INTERNAL_ERROR,
				"Out of Memory");
        goto Done;
    }

	// NOTE: If we had passed the HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY
    //       flag with HttpReceiveHttpRequest(), the entity would have
    //       been a part of HTTP_REQUEST (using the pEntityChunks field).
    //       Since we have not passed that flag, we can be assured that 
    //       there are no entity bodies in HTTP_REQUEST.
    if(pRequest->Flags & HTTP_REQUEST_FLAG_MORE_ENTITY_BODY_EXISTS)
    {
		//Allocate some space for an XMLRPC memory block.
		body = xmlrpc_mem_block_new(envP, 0);
		if (envP->fault_occurred) 
			goto Done;

        // The entity body can be sent over multiple calls. Let's collect all
        // of these in a buffer and send the buffer to the xmlrpc-c library 
        do
        {
            // Read the entity chunk from the request.
            BytesRead = 0; 
            result = HttpReceiveRequestEntityBody(
                        hReqQueue,
                        pRequest->RequestId,
                        0,
                        pEntityBuffer,
                        EntityBufferLength,
                        &BytesRead,
                        NULL
                        );
            switch(result)
            {
                case NO_ERROR:
                    if(BytesRead != 0)
                    {
						XMLRPC_TYPED_MEM_BLOCK_APPEND(char, envP, body, 
                                          pEntityBuffer, BytesRead);
						if(envP->fault_occurred)
							goto Done;						
                    }
                    break;

                case ERROR_HANDLE_EOF:
                    // We have read the last request entity body. We can now 
                    // process the suppossed XMLRPC data.
                    if(BytesRead != 0)
                    {
						XMLRPC_TYPED_MEM_BLOCK_APPEND(char, envP, body, 
                                          pEntityBuffer, BytesRead);
						if(envP->fault_occurred)
							goto Done;
                    }

                    // We will send the response over multiple calls. 
					// This is achieved by passing the 
                    // HTTP_SEND_RESPONSE_FLAG_MORE_DATA flag.
                    
                    // NOTE: Since we are accumulating the TotalBytesRead in 
                    //       a ULONG, this will not work for entity bodies that
                    //       are larger than 4 GB. For supporting large entity
                    //       bodies, we would have to use a ULONGLONG.
					TraceA("xmlrpc_server RPC2 handler processing RPC request.\n");
										
					// Process the RPC.
					output = xmlrpc_registry_process_call(
										envP, global_registryP, NULL, 
										XMLRPC_MEMBLOCK_CONTENTS(char, body),
										XMLRPC_MEMBLOCK_SIZE(char, body));
					if (envP->fault_occurred) 
						goto Done;

					// Initialize the HTTP response structure.
					INITIALIZE_HTTP_RESPONSE(&response, 200, "OK");

					//Add the content-length
					StringCchPrintfA(szContentLength,MAX_ULONG_STR, "%lu",
										XMLRPC_MEMBLOCK_SIZE(char, output));
					ADD_KNOWN_HEADER(
							response, 
							HttpHeaderContentLength, 
							szContentLength );

					//Add the content-type
					ADD_KNOWN_HEADER(response, HttpHeaderContentType, "text/xml");
					
					StringCchPrintfA(szServerHeader,20, "Xmlrpc-c/%s",XMLRPC_C_VERSION);					
					ADD_KNOWN_HEADER(response, HttpHeaderServer, szServerHeader);

					//send the response
					result = HttpSendHttpResponse(
							hReqQueue,           // ReqQueueHandle
							pRequest->RequestId, // Request ID
							HTTP_SEND_RESPONSE_FLAG_MORE_DATA,
							&response,           // HTTP response
							NULL,                // pReserved1
							&bytesSent,          // bytes sent (optional)
							NULL,                // pReserved2
							0,                   // Reserved3
							NULL,                // LPOVERLAPPED
							NULL                 // pReserved4
							);
					if(result != NO_ERROR)
					{
						TraceW(L"HttpSendHttpResponse failed with %lu \n", result);
						xmlrpc_env_set_fault_formatted(
							envP, XMLRPC_NETWORK_ERROR,
							"HttpSendHttpResponse failed with %lu", result);
						goto Done;
					}

					// Send entity body from a memory chunk.
					dataChunk.DataChunkType = HttpDataChunkFromMemory;
					dataChunk.FromMemory.BufferLength = (ULONG)XMLRPC_MEMBLOCK_SIZE(char, output);
					dataChunk.FromMemory.pBuffer = XMLRPC_MEMBLOCK_CONTENTS(char, output);

					result = HttpSendResponseEntityBody(
								hReqQueue,
								pRequest->RequestId,
								0,                    // This is the last send.
								1,                    // Entity Chunk Count.
								&dataChunk,
								NULL,
								NULL,
								0,
								NULL,
								NULL
								);
					if(result != NO_ERROR)
					{
						TraceW(L"HttpSendResponseEntityBody failed with %lu \n", result);
						xmlrpc_env_set_fault_formatted(
								envP, XMLRPC_NETWORK_ERROR,
								"HttpSendResponseEntityBody failed with %lu", result);
						goto Done;
					}
					goto Done;
                    break;
                default:
					TraceW(L"HttpReceiveRequestEntityBody failed with %lu \n", result);
					xmlrpc_env_set_fault_formatted(
								envP, XMLRPC_NETWORK_ERROR,
								"HttpReceiveRequestEntityBody failed with %lu", result);
                    goto Done;
            }
        } while(TRUE);
    }
    else
    {
		// This request does not have an entity body. 
		TraceA("Received a bad request (no body in HTTP post).\n");
		xmlrpc_env_set_fault_formatted(
				envP, XMLRPC_PARSE_ERROR,
				"Bad POST request (no body)");
        goto Done;
    }

Done:

    if(pEntityBuffer)
        FREE_MEM(pEntityBuffer);

	if(output)
		XMLRPC_MEMBLOCK_FREE(char, output);

	if(body)
		XMLRPC_MEMBLOCK_FREE(char, body);

    return;
}

#endif /* #if MUST_BUILD_HTTP_SYS_SERVER <> 0 */
