-
-
Save daald/5f37de8352e1c8ca62db to your computer and use it in GitHub Desktop.
FastCGI-ISAPI bridge for running Windows ISAPI Server Extension DLLs under Wine/Linux - WORK IN PROGRESS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
Copyright (c) 2013-2014 "Daniel Alder" | |
FastCGI-ISAPI bridge for running Windows ISAPI Server Extension DLLs under Wine/Linux | |
[http://github.com/daald] | |
This file is part of fastcgi-to-isapi. | |
fastcgi-to-isapi is free software: you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation, either version 3 of the License, or | |
(at your option) any later version. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License | |
along with this program. If not, see <http://www.gnu.org/licenses/>. | |
**/ | |
// see http://www.fastcgi.com/devkit/doc/fastcgi-whitepaper/fastcgi.htm | |
#include <stdlib.h> | |
#include <fcgi_stdio.h> | |
//#include <httpext.h> | |
#include "../includes_win_other/httpext.h" | |
const DWORD MAX_PREFETCH_ISAPI_INPUT = 0xC000; // 48K; http://www.hummel1.de/prog/isapi/isapiinput.htm | |
void verbose(const char *fmt, ...); | |
#define CHECK_CONN_ID(hConn) \ | |
if (hConn != curCon->connId) { \ | |
verbose("Wrong ConnID called!"); \ | |
SetLastError(ERROR_INVALID_PARAMETER); \ | |
return FALSE; \ | |
} | |
char* getenvdef(const char* name, char* def) { | |
char* result = getenv(name); | |
if (result != NULL) | |
return result; | |
else | |
return def; | |
} | |
typedef struct _WRAPPER_CONNECTION_DATA { | |
HCONN connId; | |
EXTENSION_CONTROL_BLOCK ecb; | |
} WRAPPER_CONNECTION_DATA, *LPWRAPPER_CONNECTION_DATA; | |
LPWRAPPER_CONNECTION_DATA curCon; | |
BOOL (PASCAL GetServerVariableImpl)(HCONN hConn, LPSTR lpszVariableName, LPVOID lpvBuffer, LPDWORD lpdwSize) { | |
verbose(" GetServerVariableImpl called %s.", lpszVariableName); | |
CHECK_CONN_ID(hConn); | |
// see http://msdn.microsoft.com/en-us/library/aa927099.aspx | |
// The lpszVariableName parameter can be used to retrieve a specific request (client) header by using the HTTP_headername value. For example, supplying the value HTTP_ACCEPT returns the Accept header, and HTTP_VERSION returns the Version header. | |
char* result = getenv(lpszVariableName); | |
verbose(" %s: %s", lpszVariableName, result); | |
if (result == NULL) { | |
SetLastError(ERROR_NO_DATA); | |
return FALSE; | |
} | |
size_t len = strlen(result); | |
strncpy(lpvBuffer, result, *lpdwSize-1); | |
((LPSTR)lpvBuffer)[*lpdwSize-1] = 0; | |
if (len > *lpdwSize-1) { | |
SetLastError(ERROR_INSUFFICIENT_BUFFER); | |
return FALSE; | |
} | |
//pECB->GetServerVariable( pECB->ConnID, "User-Agent", varBuffer, &bufLen); | |
//TODO support only the keys given in the MS list / ERROR_INVALID_INDEX | |
*lpdwSize = len + 1; | |
return TRUE; | |
} | |
BOOL (PASCAL WriteClientImpl)(HCONN hConn, LPVOID lpvBuffer, LPDWORD lpdwSize, DWORD dwReserved) { | |
verbose(" WriteClientImpl called."); | |
CHECK_CONN_ID(hConn); | |
size_t res = fwrite(lpvBuffer, 1, *lpdwSize, stdout); | |
*lpdwSize = res; | |
return (res > 0)?TRUE:FALSE; | |
} | |
BOOL (PASCAL ReadClientImpl)(HCONN hConn, LPVOID lpvBuffer, LPDWORD lpdwSize) { | |
verbose(" ReadClientImpl called."); | |
CHECK_CONN_ID(hConn); | |
size_t res = fread(lpvBuffer, 1, *lpdwSize, stdin); | |
*lpdwSize = res; | |
return (res > 0)?TRUE:FALSE; | |
} | |
BOOL (PASCAL ServerSupportFunctionImpl)(HCONN hConn, DWORD dwHSERequest, LPVOID lpvBuffer, LPDWORD lpdwSize, LPDWORD lpdwDataType) { | |
CHECK_CONN_ID(hConn); | |
// see http://msdn.microsoft.com/en-us/library/aa926202.aspx | |
switch (dwHSERequest) { | |
case HSE_REQ_SEND_URL_REDIRECT_RESP: | |
verbose(" ServerSupportFunction %s not implemented", "HSE_REQ_SEND_URL_REDIRECT_RESP"); | |
return FALSE;//TODO implement | |
case HSE_REQ_SEND_URL: | |
verbose(" ServerSupportFunction %s not implemented", "HSE_REQ_SEND_URL"); | |
return FALSE;//TODO implement | |
case HSE_REQ_SEND_RESPONSE_HEADER: | |
verbose(" ServerSupportFunction(%s) called", "HSE_REQ_SEND_RESPONSE_HEADER"); | |
if (dwHSERequest != 0) | |
printf("Status: %s\r\n", (char*)lpvBuffer); | |
if (lpdwDataType != NULL) { | |
fwrite((void*)lpdwDataType, 1, *lpdwSize, stdout); | |
} else | |
printf("\r\n"); // skip to output of content | |
return TRUE; | |
case HSE_REQ_DONE_WITH_SESSION: | |
verbose(" ServerSupportFunction %s not implemented", "HSE_REQ_DONE_WITH_SESSION"); | |
return FALSE;//TODO implement | |
case HSE_REQ_SEND_RESPONSE_HEADER_EX: | |
verbose(" ServerSupportFunction(%s) called", "HSE_REQ_SEND_RESPONSE_HEADER_EX"); | |
LPHSE_SEND_HEADER_EX_INFO hi = (LPHSE_SEND_HEADER_EX_INFO)lpvBuffer; | |
if (hi->pszStatus != NULL) | |
printf("Status: %s\r\n", hi->pszStatus); | |
if (hi->pszHeader != NULL) { | |
// assert strlen(pszHeader) == cchHeader | |
// assert cchHeader >= 2 | |
// assert pszHeader[cchHeader-2] == '\r' | |
// assert pszHeader[cchHeader-1] == '\n' | |
fwrite(hi->pszHeader, 1, hi->cchHeader, stdout); | |
} else | |
printf("\r\n"); // skip to output of content | |
if (hi->fKeepConn == FALSE) | |
verbose(" HSE_SEND_HEADER_EX_INFO.fKeepConn==FALSE not implemented"); | |
return TRUE; | |
case HSE_REQ_END_RESERVED: | |
verbose(" ServerSupportFunction %s not implemented", "HSE_REQ_END_RESERVED"); | |
return FALSE;//TODO implement | |
default: | |
verbose(" ServerSupportFunction with unknown dequest %lu called", dwHSERequest); | |
return FALSE;//TODO implement | |
} | |
} | |
int main( int argc, const char* argv[] ) | |
{ | |
int count = 0; | |
#ifndef COMPILED_IN_ISAPI | |
verbose("Initializing dll"); | |
HMODULE isapidll; | |
if (argc < 2) { | |
verbose("Need argument (dll name)"); | |
exit(78); | |
} | |
isapidll = LoadLibrary(argv[1]); | |
if (!isapidll) { | |
verbose("DLL not found"); | |
exit(127); | |
} | |
PFN_HTTPEXTENSIONPROC isapidllExtProc; | |
isapidllExtProc = GetProcAddress(isapidll, "HttpExtensionProc"); | |
#endif //!COMPILED_IN_ISAPI | |
verbose("Starting loop"); | |
while(FCGI_Accept() >= 0) { | |
verbose("FCGI run %d", count++); | |
curCon = malloc(sizeof(WRAPPER_CONNECTION_DATA)); | |
curCon->connId = (HCONN)1; // there is only one connection without threading | |
curCon->ecb.cbSize = sizeof(EXTENSION_CONTROL_BLOCK); | |
curCon->ecb.dwVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR); //winapi windef.h | |
curCon->ecb.ConnID = curCon->connId; | |
curCon->ecb.dwHttpStatusCode = 200; | |
strncpy(curCon->ecb.lpszLogData, "FastCGI to ISAPI wrapper", HSE_LOG_BUFFER_LEN-1); | |
curCon->ecb.lpszMethod = getenvdef("REQUEST_METHOD", "GET"); | |
verbose("curCon->ecb.lpszMethod '%s'", curCon->ecb.lpszMethod); | |
curCon->ecb.lpszQueryString = getenv("QUERY_STRING"); | |
verbose("curCon->ecb.lpszQueryString '%s'", curCon->ecb.lpszQueryString); | |
curCon->ecb.lpszPathInfo = getenv("PATH_INFO"); | |
verbose("curCon->ecb.lpszPathInfo '%s'", curCon->ecb.lpszPathInfo); | |
curCon->ecb.lpszPathTranslated = getenv("PATH_TRANSLATED"); | |
verbose("curCon->ecb.lpszPathTranslated '%s'", curCon->ecb.lpszPathTranslated); | |
curCon->ecb.lpszContentType = getenv("CONTENT_TYPE"); | |
verbose("curCon->ecb.lpszContentType '%s'", curCon->ecb.lpszContentType); | |
curCon->ecb.GetServerVariable = &GetServerVariableImpl; | |
curCon->ecb.WriteClient = &WriteClientImpl; | |
curCon->ecb.ReadClient = &ReadClientImpl; | |
curCon->ecb.ServerSupportFunction = &ServerSupportFunctionImpl; | |
char *contentLength = getenv("CONTENT_LENGTH"); | |
int len = 0; | |
if (contentLength != NULL) | |
len = strtoul(contentLength, NULL, 10); | |
curCon->ecb.cbTotalBytes = len; | |
curCon->ecb.cbAvailable = len; | |
if (curCon->ecb.cbAvailable > MAX_PREFETCH_ISAPI_INPUT) | |
curCon->ecb.cbAvailable = MAX_PREFETCH_ISAPI_INPUT; | |
char* fetchedData = NULL; | |
if (curCon->ecb.cbAvailable > 0) { | |
// pre-read beginning of data into lpbData | |
fetchedData = malloc(curCon->ecb.cbAvailable + 1);//don't know if null-terminating is mandatory, but we do | |
fread(fetchedData, 1, curCon->ecb.cbAvailable, stdin); | |
//TODO necessary? rewind(stdin); // calls to curCon->ecb.ReadClient() should? read from beginning of data | |
} | |
curCon->ecb.lpbData = fetchedData; | |
verbose(" Calling ISAPI"); | |
DWORD extRet; | |
#ifdef COMPILED_IN_ISAPI | |
extRet = HttpExtensionProc(&(curCon->ecb)); | |
#else //!COMPILED_IN_ISAPI | |
extRet = isapidllExtProc(&(curCon->ecb)); | |
#endif //COMPILED_IN_ISAPI | |
switch (extRet) { | |
case HSE_STATUS_SUCCESS: | |
/* The extension has finished processing and the server should disconnect the client and free up allocated resources. */ | |
verbose(" ISAPI finished with %s", "HSE_STATUS_SUCCESS"); | |
break; | |
case HSE_STATUS_SUCCESS_AND_KEEP_CONN: | |
/* The extension has finished processing and the server should wait for the next HTTP request if the client supports Keep-Alive connections. The extension can return this only if it was able to send the correct Content-Length header to the client. */ | |
verbose(" ISAPI finished with %s", "HSE_STATUS_SUCCESS_AND_KEEP_CONN"); | |
break; | |
case HSE_STATUS_PENDING: | |
/* The extension has queued the request for processing and will notify the server when it has finished. See HSE_REQ_DONE_WITH_SESSION under ServerSupportFunction. | |
If the ISAPI extension is using HSE_STATUS_PENDING, then responses can be sent from work done in another thread after returning HSE_STATUS_PENDING. */ | |
verbose(" ISAPI finished with %s", "HSE_STATUS_PENDING"); | |
break; | |
case HSE_STATUS_ERROR: | |
/* The extension has encountered an error while processing the request, so the server can disconnect the client and free up allocated resources. An HTTP status code of 500 is written to the IIS log for the request. */ | |
verbose(" ISAPI finished with %s", "HSE_STATUS_ERROR"); | |
verbose(" returning http error 500"); | |
printf("Status: 500 Internal Server Error\r\n" | |
"\r\n" | |
"<h1>500 Internal Server Error</h1>\r\n" | |
"The server encountered an unexpected condition which prevented it from fulfilling the request.\r\n"); | |
break; | |
default: | |
verbose(" ISAPI finished with an unknown exit code %d", extRet); | |
verbose(" returning http error 500"); | |
printf("Status: 500 Internal Server Error\r\n" | |
"\r\n" | |
"<h1>500 Internal Server Error</h1>\r\n" | |
"The server encountered an unexpected condition which prevented it from fulfilling the request.\r\n"); | |
} | |
if (fetchedData != NULL) | |
free(fetchedData); | |
verbose("Request done"); | |
} | |
verbose("Program end"); | |
exit(0); | |
} | |
#undef fprintf | |
#undef stderr | |
#undef vfprintf | |
void verbose(const char *fmt, ...) | |
{ | |
va_list args; | |
va_start(args, fmt); | |
fprintf(stderr, ">> "); | |
vfprintf(stderr, fmt, args); | |
fprintf(stderr, "\n"); | |
va_end(args); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment