1 /* Copyright (C) 2005 by Steven A. Bone, sbone@pobox.com. All rights reserved.
3 ** Redistribution and use in source and binary forms, with or without
4 ** modification, are permitted provided that the following conditions
6 ** 1. Redistributions of source code must retain the above copyright
7 ** notice, this list of conditions and the following disclaimer.
8 ** 2. Redistributions in binary form must reproduce the above copyright
9 ** notice, this list of conditions and the following disclaimer in the
10 ** documentation and/or other materials provided with the distribution.
11 ** 3. The name of the author may not be used to endorse or promote products
12 ** derived from this software without specific prior written permission.
14 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 ** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 Note that the Platform SDK headers and
28 link libraries for Windows XP SP2 or newer are required to compile
29 xmlrpc-c for this module. If you are not using this server, it is
30 safe to exclude the xmlrpc_server_w32httpsys.c file from the xmlrpc
31 project and these dependencies will not be required. You can get the
32 latest platform SDK at
33 http://www.microsoft.com/msdownload/platformsdk/sdkupdate/
34 Be sure after installation to choose the program to "register the PSDK
35 directories with Visual Studio" so the newer headers are found.
46 #include "xmlrpc-c/base.h"
47 #include "xmlrpc-c/server.h"
48 #include "xmlrpc-c/server_w32httpsys.h"
51 #if MUST_BUILD_HTTP_SYS_SERVER > 0
53 /* See compilation note above if this header is not found! */
58 #pragma comment( lib, "httpapi" )
61 /* XXX - This variable is *not* currently threadsafe. Once the server has
62 ** been started, it must be treated as read-only. */
63 static xmlrpc_registry *global_registryP;
65 //set TRUE if you want a log
68 static char g_fLogFile[MAX_PATH];
69 //do you want OutputDebugString() to be called?
70 static BOOL g_bDebugString;
75 #define INITIALIZE_HTTP_RESPONSE( resp, status, reason ) \
78 RtlZeroMemory( (resp), sizeof(*(resp)) ); \
79 (resp)->StatusCode = (status); \
80 (resp)->pReason = (reason); \
81 (resp)->ReasonLength = (USHORT) strlen(reason); \
85 #define ADD_KNOWN_HEADER(Response, HeaderId, RawValue) \
88 (Response).Headers.KnownHeaders[(HeaderId)].pRawValue = (RawValue); \
89 (Response).Headers.KnownHeaders[(HeaderId)].RawValueLength = \
90 (USHORT) strlen(RawValue); \
93 #define ALLOC_MEM(cb) HeapAlloc(GetProcessHeap(), 0, (cb))
94 #define FREE_MEM(ptr) HeapFree(GetProcessHeap(), 0, (ptr))
97 // Prototypes for Internal Functions.
102 const xmlrpc_server_httpsys_parms * const parmsP
108 IN PHTTP_REQUEST pRequest,
109 IN USHORT StatusCode,
115 SendHttpResponseAuthRequired(
117 IN PHTTP_REQUEST pRequest
122 xmlrpc_env * const envP,
124 IN PHTTP_REQUEST pRequest
127 __inline void TraceA(const char *format, ...);
128 __inline void TraceW(const wchar_t *format, ...);
132 // External Function Implementation.
136 xmlrpc_server_httpsys(
137 xmlrpc_env * const envP,
138 const xmlrpc_server_httpsys_parms * const parmsP,
139 unsigned int const parm_size
143 HANDLE hReqQueue = NULL;
144 HTTPAPI_VERSION HttpApiVersion = HTTPAPI_VERSION_1;
147 XMLRPC_ASSERT_ENV_OK(envP);
149 if (parm_size < XMLRPC_HSSIZE(authfn))
151 xmlrpc_env_set_fault_formatted(
152 envP, XMLRPC_INTERNAL_ERROR,
153 "You must specify members at least up through "
154 "'authfn' in the server parameters argument. "
155 "That would mean the parameter size would be >= %u "
156 "but you specified a size of %u",
157 XMLRPC_HSSIZE(authfn), parm_size);
161 //Set logging options
162 if (parmsP->logLevel>0)
167 if (parmsP->logLevel>1)
170 g_bDebugString=FALSE;
172 if (!parmsP->logFile)
175 StringCchPrintfA(g_fLogFile,MAX_PATH,parmsP->logFile);
177 //construct the URL we are listening on
178 if (parmsP->useSSL!=0)
179 StringCchPrintf(wszURL,35,L"https://+:%u/RPC2",parmsP->portNum);
181 StringCchPrintf(wszURL,35,L"http://+:%u/RPC2",parmsP->portNum);
183 global_registryP = parmsP->registryP;
185 // Initialize HTTP APIs.
186 retCode = HttpInitialize(
188 HTTP_INITIALIZE_SERVER, // Flags
191 if (retCode != NO_ERROR)
193 xmlrpc_env_set_fault_formatted(
194 envP, XMLRPC_INTERNAL_ERROR,
195 "HttpInitialize failed with %lu \n ",
200 // Create a Request Queue Handle
201 retCode = HttpCreateHttpHandle(
202 &hReqQueue, // Req Queue
205 if (retCode != NO_ERROR)
207 xmlrpc_env_set_fault_formatted(
208 envP, XMLRPC_INTERNAL_ERROR,
209 "HttpCreateHttpHandle failed with %lu \n ",
214 retCode = HttpAddUrl(
215 hReqQueue, // Req Queue
216 wszURL, // Fully qualified URL
220 if (retCode != NO_ERROR)
222 xmlrpc_env_set_fault_formatted(
223 envP, XMLRPC_INTERNAL_ERROR,
224 "HttpAddUrl failed with %lu \n ",
229 TraceW( L"we are listening for requests on the following url: %ws\n", wszURL);
231 // Loop while receiving requests
234 TraceW( L"Calling DoReceiveRequests()\n");
235 retCode = DoReceiveRequests(hReqQueue, parmsP);
236 if(NO_ERROR == retCode)
238 TraceW( L"DoReceiveRequests() returned NO_ERROR, breaking");
245 TraceW( L"Tearing down the server.\n", wszURL);
247 // Call HttpRemoveUrl for the URL that we added.
248 HttpRemoveUrl( hReqQueue, wszURL );
250 // Close the Request Queue handle.
252 CloseHandle(hReqQueue);
254 // Call HttpTerminate.
255 HttpTerminate(HTTP_INITIALIZE_SERVER, NULL);
260 // Internal Function Implementations.
263 __inline void TraceA(const char *format, ...)
272 va_start(arglist, format);
275 FILE *fout = fopen(g_fLogFile, "a+t");
278 vfprintf(fout, format, arglist);
283 StringCchVPrintfA(str,4096, format, arglist);
289 OutputDebugStringA(str);
297 __inline void TraceW(const wchar_t *format, ...)
306 va_start(arglist, format);
309 FILE *fout = fopen(g_fLogFile, "a+t");
312 vfwprintf(fout, format, arglist);
317 StringCchVPrintfW(str, 4096, format, arglist);
322 OutputDebugStringW(str);
331 * This is a blocking function that merely sits on the request queue
332 * for our URI and processes them one at a time. Once a request comes
333 * in, we check it for content-type, content-length, and verb. As long
334 * as the initial validations are done, we pass the request to the
335 * processRPCCall() function, which collects the body of the request
336 * and processes it. If we get an error back other than network type,
337 * we are responsible for notifing the client.
342 const xmlrpc_server_httpsys_parms * const parmsP
346 HTTP_REQUEST_ID requestId;
348 PHTTP_REQUEST pRequest;
349 PCHAR pRequestBuffer;
350 ULONG RequestBufferLength;
352 char szHeaderBuf[255];
355 // Allocate a 2K buffer. Should be good for most requests, we'll grow
356 // this if required. We also need space for a HTTP_REQUEST structure.
357 RequestBufferLength = sizeof(HTTP_REQUEST) + 2048;
358 pRequestBuffer = (PCHAR) ALLOC_MEM( RequestBufferLength );
359 if (pRequestBuffer == NULL)
361 return ERROR_NOT_ENOUGH_MEMORY;
364 pRequest = (PHTTP_REQUEST)pRequestBuffer;
366 // Wait for a new request -- This is indicated by a NULL request ID.
367 HTTP_SET_NULL_ID( &requestId );
370 RtlZeroMemory(pRequest, RequestBufferLength);
372 result = HttpReceiveHttpRequest(
373 hReqQueue, // Req Queue
376 pRequest, // HTTP request buffer
377 RequestBufferLength,// req buffer length
378 &bytesRead, // bytes received
382 if(NO_ERROR == result)
384 // Got a request with a filled buffer.
385 switch(pRequest->Verb)
389 TraceW(L"Got a POST request for %ws \n",pRequest->CookedUrl.pFullUrl);
391 //Check if we need use authorization.
394 xmlrpc_env_init(&env);
395 if(pRequest->Headers.KnownHeaders[HttpHeaderAuthorization].RawValueLength<6)
397 xmlrpc_env_set_fault( &env, XMLRPC_REQUEST_REFUSED_ERROR,
398 "Authorization header too short.");
402 //unencode the headers
403 if(_strnicmp("basic ",pRequest->Headers.KnownHeaders[HttpHeaderAuthorization].pRawValue,6)!=0)
405 xmlrpc_env_set_fault( &env, XMLRPC_REQUEST_REFUSED_ERROR,
406 "Authorization header is not of type basic.");
410 xmlrpc_mem_block * decoded;
412 decoded = xmlrpc_base64_decode(&env,pRequest->Headers.KnownHeaders[HttpHeaderAuthorization].pRawValue+6,pRequest->Headers.KnownHeaders[HttpHeaderAuthorization].RawValueLength-6);
413 if(!env.fault_occurred)
420 pDecodedStr = (char*)malloc(decoded->_size+1);
421 memcpy(pDecodedStr,decoded->_block,decoded->_size);
422 pDecodedStr[decoded->_size]='\0';
423 pUser = pPass = pDecodedStr;
424 pColon=strchr(pDecodedStr,':');
429 //The authfn should set env to fail if auth is denied.
430 parmsP->authfn(&env,pUser,pPass);
434 xmlrpc_env_set_fault( &env, XMLRPC_REQUEST_REFUSED_ERROR,
435 "Decoded auth not of the correct format.");
440 XMLRPC_MEMBLOCK_FREE(char, decoded);
443 if(env.fault_occurred)
445 //request basic authorization, as the user did not provide it.
446 xmlrpc_env_clean(&env);
447 TraceW(L"POST request did not provide valid authorization header.");
448 result = SendHttpResponseAuthRequired( hReqQueue, pRequest);
451 xmlrpc_env_clean(&env);
454 //Check content type to make sure it is text/xml.
455 memcpy(szHeaderBuf,pRequest->Headers.KnownHeaders[HttpHeaderContentType].pRawValue,pRequest->Headers.KnownHeaders[HttpHeaderContentType].RawValueLength);
456 szHeaderBuf[pRequest->Headers.KnownHeaders[HttpHeaderContentType].RawValueLength]='\0';
457 if (_stricmp(szHeaderBuf,"text/xml")!=0)
459 //We only handle text/xml data. Anything else is not valid.
460 TraceW(L"POST request had an unsupported content-type: %s \n", szHeaderBuf);
461 result = SendHttpResponse(
471 //Check content length to make sure it exists and is not too big.
472 memcpy(szHeaderBuf,pRequest->Headers.KnownHeaders[HttpHeaderContentLength].pRawValue,pRequest->Headers.KnownHeaders[HttpHeaderContentLength].RawValueLength);
473 szHeaderBuf[pRequest->Headers.KnownHeaders[HttpHeaderContentLength].RawValueLength]='\0';
474 lContentLength = atol(szHeaderBuf);
475 if (lContentLength<=0)
477 //Make sure a content length was supplied.
478 TraceW(L"POST request did not include a content-length \n", szHeaderBuf);
479 result = SendHttpResponse(
488 if((size_t) lContentLength > xmlrpc_limit_get(XMLRPC_XML_SIZE_LIMIT_ID))
490 //Content-length is too big for us to handle
491 TraceW(L"POST request content-length is too big for us to handle: %d bytes \n", lContentLength);
492 result = SendHttpResponse(
496 "content-length too large",
502 //our initial validations of POST, content-type, and content-length
503 //all check out. Collect and pass the complete buffer to the
506 xmlrpc_env_init(&env);
507 processRPCCall(&env,hReqQueue, pRequest);
508 if (env.fault_occurred)
510 //if we fail and it is anything other than a network error,
511 //we should return a failure response to the client.
512 if (env.fault_code != XMLRPC_NETWORK_ERROR)
514 if (env.fault_string)
515 result = SendHttpResponse(
523 result = SendHttpResponse(
533 xmlrpc_env_clean(&env);
537 //We only handle POST data. Anything else is not valid.
538 TraceW(L"Got an unsupported Verb request for URI %ws \n", pRequest->CookedUrl.pFullUrl);
540 result = SendHttpResponse(
544 "Method Not Allowed",
549 if(result != NO_ERROR)
554 // Reset the Request ID so that we pick up the next request.
555 HTTP_SET_NULL_ID( &requestId );
557 else if(result == ERROR_MORE_DATA)
559 // The input buffer was too small to hold the request headers
560 // We have to allocate more buffer & call the API again.
562 // When we call the API again, we want to pick up the request
563 // that just failed. This is done by passing a RequestID.
564 // This RequestID is picked from the old buffer.
565 requestId = pRequest->RequestId;
567 // Free the old buffer and allocate a new one.
568 RequestBufferLength = bytesRead;
569 FREE_MEM( pRequestBuffer );
570 pRequestBuffer = (PCHAR) ALLOC_MEM( RequestBufferLength );
572 if (pRequestBuffer == NULL)
574 result = ERROR_NOT_ENOUGH_MEMORY;
578 pRequest = (PHTTP_REQUEST)pRequestBuffer;
581 else if(ERROR_CONNECTION_INVALID == result &&
582 !HTTP_IS_NULL_ID(&requestId))
584 // The TCP connection got torn down by the peer when we were
585 // trying to pick up a request with more buffer. We'll just move
586 // onto the next request.
587 HTTP_SET_NULL_ID( &requestId );
599 FREE_MEM( pRequestBuffer );
606 * SendHttpResponse sends a text/html content type back with
607 * the user specified status code and reason. Used for returning
613 IN PHTTP_REQUEST pRequest,
614 IN USHORT StatusCode,
616 IN PSTR pEntityString
619 HTTP_RESPONSE response;
620 HTTP_DATA_CHUNK dataChunk;
623 CHAR szServerHeader[20];
625 // Initialize the HTTP response structure.
626 INITIALIZE_HTTP_RESPONSE(&response, StatusCode, pReason);
628 ADD_KNOWN_HEADER(response, HttpHeaderContentType, "text/html");
630 StringCchPrintfA(szServerHeader,20, "xmlrpc-c %s",XMLRPC_C_VERSION);
631 ADD_KNOWN_HEADER(response, HttpHeaderServer, szServerHeader);
635 // Add an entity chunk
636 dataChunk.DataChunkType = HttpDataChunkFromMemory;
637 dataChunk.FromMemory.pBuffer = pEntityString;
638 dataChunk.FromMemory.BufferLength = (ULONG) strlen(pEntityString);
640 response.EntityChunkCount = 1;
641 response.pEntityChunks = &dataChunk;
644 // Since we are sending all the entity body in one call, we don't have
645 // to specify the Content-Length.
646 result = HttpSendHttpResponse(
647 hReqQueue, // ReqQueueHandle
648 pRequest->RequestId, // Request ID
650 &response, // HTTP response
652 &bytesSent, // bytes sent (OPTIONAL)
653 NULL, // pReserved2 (must be NULL)
654 0, // Reserved3 (must be 0)
655 NULL, // LPOVERLAPPED (OPTIONAL)
656 NULL // pReserved4 (must be NULL)
659 if(result != NO_ERROR)
661 TraceW(L"HttpSendHttpResponse failed with %lu \n", result);
668 * SendHttpResponseAuthRequired sends a 401 status code requesting authorization
671 SendHttpResponseAuthRequired(
673 IN PHTTP_REQUEST pRequest
676 HTTP_RESPONSE response;
679 CHAR szServerHeader[20];
681 // Initialize the HTTP response structure.
682 INITIALIZE_HTTP_RESPONSE(&response, 401, "Authentication Required");
684 // Add the WWW_Authenticate header.
685 ADD_KNOWN_HEADER(response, HttpHeaderWwwAuthenticate, "Basic realm=\"xmlrpc\"");
687 StringCchPrintfA(szServerHeader,20, "xmlrpc-c %s",XMLRPC_C_VERSION);
688 ADD_KNOWN_HEADER(response, HttpHeaderServer, szServerHeader);
690 // Since we are sending all the entity body in one call, we don't have
691 // to specify the Content-Length.
692 result = HttpSendHttpResponse(
693 hReqQueue, // ReqQueueHandle
694 pRequest->RequestId, // Request ID
696 &response, // HTTP response
698 &bytesSent, // bytes sent (OPTIONAL)
699 NULL, // pReserved2 (must be NULL)
700 0, // Reserved3 (must be 0)
701 NULL, // LPOVERLAPPED (OPTIONAL)
702 NULL // pReserved4 (must be NULL)
705 if(result != NO_ERROR)
707 TraceW(L"SendHttpResponseAuthRequired failed with %lu \n", result);
714 * processRPCCall() is called after some validations. The assumption is that
715 * the request is an HTTP post of content-type text/xml with a content-length
716 * that is less than the maximum the library can handle.
718 * The caller should check the error status, and if the error was other than
719 * a network type, respond back to the client to let them know the call failed.
723 xmlrpc_env * const envP,
725 IN PHTTP_REQUEST pRequest
728 HTTP_RESPONSE response;
731 PUCHAR pEntityBuffer;
732 ULONG EntityBufferLength;
734 #define MAX_ULONG_STR ((ULONG) sizeof("4294967295"))
735 CHAR szContentLength[MAX_ULONG_STR];
736 CHAR szServerHeader[20];
737 HTTP_DATA_CHUNK dataChunk;
738 ULONG TotalBytesRead = 0;
739 xmlrpc_mem_block * body;
740 xmlrpc_mem_block * output;
746 // Allocate some space for an entity buffer.
747 EntityBufferLength = 2048;
748 pEntityBuffer = (PUCHAR) ALLOC_MEM( EntityBufferLength );
749 if (pEntityBuffer == NULL)
751 xmlrpc_env_set_fault_formatted(
752 envP, XMLRPC_INTERNAL_ERROR,
757 // NOTE: If we had passed the HTTP_RECEIVE_REQUEST_FLAG_COPY_BODY
758 // flag with HttpReceiveHttpRequest(), the entity would have
759 // been a part of HTTP_REQUEST (using the pEntityChunks field).
760 // Since we have not passed that flag, we can be assured that
761 // there are no entity bodies in HTTP_REQUEST.
762 if(pRequest->Flags & HTTP_REQUEST_FLAG_MORE_ENTITY_BODY_EXISTS)
764 //Allocate some space for an XMLRPC memory block.
765 body = xmlrpc_mem_block_new(envP, 0);
766 if (envP->fault_occurred)
769 // The entity body can be sent over multiple calls. Let's collect all
770 // of these in a buffer and send the buffer to the xmlrpc-c library
773 // Read the entity chunk from the request.
775 result = HttpReceiveRequestEntityBody(
789 XMLRPC_TYPED_MEM_BLOCK_APPEND(char, envP, body,
790 pEntityBuffer, BytesRead);
791 if(envP->fault_occurred)
796 case ERROR_HANDLE_EOF:
797 // We have read the last request entity body. We can now
798 // process the suppossed XMLRPC data.
801 XMLRPC_TYPED_MEM_BLOCK_APPEND(char, envP, body,
802 pEntityBuffer, BytesRead);
803 if(envP->fault_occurred)
807 // We will send the response over multiple calls.
808 // This is achieved by passing the
809 // HTTP_SEND_RESPONSE_FLAG_MORE_DATA flag.
811 // NOTE: Since we are accumulating the TotalBytesRead in
812 // a ULONG, this will not work for entity bodies that
813 // are larger than 4 GB. For supporting large entity
814 // bodies, we would have to use a ULONGLONG.
815 TraceA("xmlrpc_server RPC2 handler processing RPC request.\n");
818 output = xmlrpc_registry_process_call(
819 envP, global_registryP, NULL,
820 XMLRPC_MEMBLOCK_CONTENTS(char, body),
821 XMLRPC_MEMBLOCK_SIZE(char, body));
822 if (envP->fault_occurred)
825 // Initialize the HTTP response structure.
826 INITIALIZE_HTTP_RESPONSE(&response, 200, "OK");
828 //Add the content-length
829 StringCchPrintfA(szContentLength,MAX_ULONG_STR, "%lu",
830 XMLRPC_MEMBLOCK_SIZE(char, output));
833 HttpHeaderContentLength,
836 //Add the content-type
837 ADD_KNOWN_HEADER(response, HttpHeaderContentType, "text/xml");
839 StringCchPrintfA(szServerHeader,20, "xmlrpc-c %s",XMLRPC_C_VERSION);
840 ADD_KNOWN_HEADER(response, HttpHeaderServer, szServerHeader);
843 result = HttpSendHttpResponse(
844 hReqQueue, // ReqQueueHandle
845 pRequest->RequestId, // Request ID
846 HTTP_SEND_RESPONSE_FLAG_MORE_DATA,
847 &response, // HTTP response
849 &bytesSent, // bytes sent (optional)
852 NULL, // LPOVERLAPPED
855 if(result != NO_ERROR)
857 TraceW(L"HttpSendHttpResponse failed with %lu \n", result);
858 xmlrpc_env_set_fault_formatted(
859 envP, XMLRPC_NETWORK_ERROR,
860 "HttpSendHttpResponse failed with %lu", result);
864 // Send entity body from a memory chunk.
865 dataChunk.DataChunkType = HttpDataChunkFromMemory;
866 dataChunk.FromMemory.BufferLength = (ULONG)XMLRPC_MEMBLOCK_SIZE(char, output);
867 dataChunk.FromMemory.pBuffer = XMLRPC_MEMBLOCK_CONTENTS(char, output);
869 result = HttpSendResponseEntityBody(
872 0, // This is the last send.
873 1, // Entity Chunk Count.
881 if(result != NO_ERROR)
883 TraceW(L"HttpSendResponseEntityBody failed with %lu \n", result);
884 xmlrpc_env_set_fault_formatted(
885 envP, XMLRPC_NETWORK_ERROR,
886 "HttpSendResponseEntityBody failed with %lu", result);
892 TraceW(L"HttpReceiveRequestEntityBody failed with %lu \n", result);
893 xmlrpc_env_set_fault_formatted(
894 envP, XMLRPC_NETWORK_ERROR,
895 "HttpReceiveRequestEntityBody failed with %lu", result);
902 // This request does not have an entity body.
903 TraceA("Received a bad request (no body in HTTP post).\n");
904 xmlrpc_env_set_fault_formatted(
905 envP, XMLRPC_PARSE_ERROR,
906 "Bad POST request (no body)");
913 FREE_MEM(pEntityBuffer);
916 XMLRPC_MEMBLOCK_FREE(char, output);
919 XMLRPC_MEMBLOCK_FREE(char, body);
924 #endif /* #if MUST_BUILD_HTTP_SYS_SERVER <> 0 */