/* * ======================================================================= * * File: stress .c * * stress tester for isapi dll's * * based on cgiwrap * * ======================================================================= * * */ #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include // These are things that go out in the Response Header // #define HTTP_VER "HTTP/1.0" #define SERVER_VERSION "Http-Srv-Beta2/1.0" // // Simple wrappers for the heap APIS // #define xmalloc(s) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s)) #define xfree(s) HeapFree(GetProcessHeap(),0,(s)) // // The mandatory exports from the ISAPI DLL // #define NUM_THREADS 10 #define ITERATIONS 1 HANDLE StartNow; // quick and dirty environment typedef CMapStringToString TEnvironment; TEnvironment IsapiEnvironment; typedef struct _TResults { LONG ok; LONG bad; } TResults; CStringArray IsapiFileList; // list of filenames CStringArray TestNames; // --TEST-- CStringArray IsapiGetData; // --GET-- CStringArray IsapiPostData; // --POST-- CStringArray IsapiMatchData; // --EXPECT-- CArray Results; typedef struct _TIsapiContext { HANDLE in; HANDLE out; DWORD tid; TEnvironment env; } TIsapiContext; // // Prototypes of the functions this sample implements // extern "C" { HINSTANCE hDll; typedef BOOL (WINAPI *VersionProc)(HSE_VERSION_INFO *) ; typedef DWORD (WINAPI *HttpExtProc)(EXTENSION_CONTROL_BLOCK *); BOOL WINAPI FillExtensionControlBlock(EXTENSION_CONTROL_BLOCK *, TIsapiContext *) ; BOOL WINAPI GetServerVariable(HCONN, LPSTR, LPVOID, LPDWORD ); BOOL WINAPI ReadClient(HCONN,LPVOID,LPDWORD); BOOL WINAPI WriteClient(HCONN,LPVOID,LPDWORD,DWORD); BOOL WINAPI ServerSupportFunction(HCONN,DWORD,LPVOID,LPDWORD,LPDWORD); VersionProc IsapiGetExtensionVersion; HttpExtProc IsapiHttpExtensionProc; HSE_VERSION_INFO version_info; } char * MakeDateStr(VOID); char * GetEnv(char *); DWORD CALLBACK IsapiThread(void *); int stress_main(const char *filename, const char *arg, const char *postfile, const char *matchdata); BOOL bUseTestFiles = FALSE; char temppath[MAX_PATH]; void stripcrlf(char *line) { DWORD l = strlen(line)-1; if (line[l]==10 || line[l]==13) line[l]=0; l = strlen(line)-1; if (line[l]==10 || line[l]==13) line[l]=0; } #define COMPARE_BUF_SIZE 1024 BOOL CompareFiles(const char*f1, const char*f2) { FILE *fp1, *fp2; bool retval; char buf1[COMPARE_BUF_SIZE], buf2[COMPARE_BUF_SIZE]; int length1, length2; if ((fp1=fopen(f1, "r"))==NULL) { return FALSE; } if ((fp2=fopen(f2, "r"))==NULL) { fclose(fp1); return FALSE; } retval = TRUE; // success oriented while (true) { length1 = fread(buf1, 1, sizeof(buf1), fp1); length2 = fread(buf2, 1, sizeof(buf2), fp2); // check for end of file if (feof(fp1)) { if (!feof(fp2)) { retval = FALSE; } break; } else if (feof(fp2)) { if (!feof(fp1)) { retval = FALSE; } break; } // compare data if (length1!=length2 || memcmp(buf1, buf2, length1)!=0) { retval = FALSE; break; } } fclose(fp1); fclose(fp2); return retval; } BOOL CompareStringWithFile(const char *filename, const char *str, unsigned int str_length) { FILE *fp; bool retval; char buf[COMPARE_BUF_SIZE]; unsigned int offset=0, readbytes; if ((fp=fopen(filename, "r"))==NULL) { return FALSE; } retval = TRUE; // success oriented while (true) { readbytes = fread(buf, 1, sizeof(buf), fp); // check for end of file if (feof(fp)) { break; } if (offset+readbytes > str_length || memcmp(buf, str+offset, readbytes)!=NULL) { retval = FALSE; break; } } fclose(fp); return retval; } BOOL ReadGlobalEnvironment(const char *environment) { if (environment) { FILE *fp = fopen(environment, "r"); DWORD i=0; if (fp) { char line[2048]; while (fgets(line,sizeof(line)-1,fp)) { // file.php arg1 arg2 etc. char *p = strchr(line, '='); if (p) { *p=0; IsapiEnvironment[line]=p+1; } } fclose(fp); return IsapiEnvironment.GetCount() > 0; } } return FALSE; } BOOL ReadFileList(const char *filelist) { FILE *fp = fopen(filelist, "r"); if (!fp) { printf("Unable to open %s\r\n", filelist); } char line[2048]; int i=0; while (fgets(line,sizeof(line)-1,fp)) { // file.php arg1 arg2 etc. stripcrlf(line); if (strlen(line)>3) { char *p = strchr(line, ' '); if (p) { *p = 0; // get file IsapiFileList.Add(line); IsapiGetData.Add(p+1); } else { // just a filename is all IsapiFileList.Add(line); IsapiGetData.Add(""); } } // future use IsapiPostData.Add(""); IsapiMatchData.Add(""); TestNames.Add(""); i++; } Results.SetSize(TestNames.GetSize()); fclose(fp); return IsapiFileList.GetSize() > 0; } void DoThreads() { if (IsapiFileList.GetSize() == 0) { printf("No Files to test\n"); return; } printf("Starting Threads...\n"); // loop creating threads DWORD tid; HANDLE threads[NUM_THREADS]; for (DWORD i=0; i< NUM_THREADS; i++) { threads[i]=CreateThread(NULL, 0, IsapiThread, NULL, CREATE_SUSPENDED, &tid); } for (i=0; i< NUM_THREADS; i++) { if (threads[i]) ResumeThread(threads[i]); } // wait for threads to finish WaitForMultipleObjects(NUM_THREADS, threads, TRUE, INFINITE); } void DoFileList(const char *filelist, const char *environment) { // read config files if (!ReadFileList(filelist)) { printf("No Files to test!\r\n"); return; } ReadGlobalEnvironment(environment); DoThreads(); } /** * ParseTestFile * parse a single phpt file and add it to the arrays */ BOOL ParseTestFile(const char *path, const char *fn) { // parse the test file char filename[MAX_PATH]; _snprintf(filename, sizeof(filename)-1, "%s\\%s", path, fn); char line[1024]; memset(line, 0, sizeof(line)); CString cTest, cSkipIf, cPost, cGet, cFile, cExpect; printf("Reading %s\r\n", filename); enum state {none, test, skipif, post, get, file, expect} parsestate = none; FILE *fp = fopen(filename, "r"); char *tn = _tempnam(temppath,"pht."); char *en = _tempnam(temppath,"exp."); FILE *ft = fopen(tn, "w+"); FILE *fe = fopen(en, "w+"); if (fp && ft && fe) { while (fgets(line,sizeof(line)-1,fp)) { if (line[0]=='-') { if (_strnicmp(line, "--TEST--", 8)==0) { parsestate = test; continue; } else if (_strnicmp(line, "--SKIPIF--", 10)==0) { parsestate = skipif; continue; } else if (_strnicmp(line, "--POST--", 8)==0) { parsestate = post; continue; } else if (_strnicmp(line, "--GET--", 7)==0) { parsestate = get; continue; } else if (_strnicmp(line, "--FILE--", 8)==0) { parsestate = file; continue; } else if (_strnicmp(line, "--EXPECT--", 10)==0) { parsestate = expect; continue; } } switch (parsestate) { case test: stripcrlf(line); cTest = line; break; case skipif: cSkipIf += line; break; case post: cPost += line; break; case get: cGet += line; break; case file: fputs(line, ft); break; case expect: fputs(line, fe); break; } } fclose(fp); fclose(ft); fclose(fe); if (!cTest.IsEmpty()) { IsapiFileList.Add(tn); TestNames.Add(cTest); IsapiGetData.Add(cGet); IsapiPostData.Add(cPost); IsapiMatchData.Add(en); return TRUE; } } return FALSE; } /** * GetTestFiles * Recurse through the path and subdirectories, parse each phpt file */ BOOL GetTestFiles(const char *path) { // find all files .phpt under testpath\tests char FindPath[MAX_PATH]; WIN32_FIND_DATA fd; memset(&fd, 0, sizeof(WIN32_FIND_DATA)); _snprintf(FindPath, sizeof(FindPath)-1, "%s\\*.*",path); HANDLE fh = FindFirstFile(FindPath, &fd); if (fh != INVALID_HANDLE_VALUE) { do { if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && !strchr(fd.cFileName, '.')) { // subdirectory, recurse into it char NewFindPath[MAX_PATH]; _snprintf(NewFindPath, sizeof(NewFindPath)-1, "%s\\%s",path, fd.cFileName); GetTestFiles(NewFindPath); } else if (strstr(fd.cFileName, ".phpt")) { // got test file, parse it now if (ParseTestFile(path, fd.cFileName)) { printf("Test File Added: %s\\%s\r\n", path, fd.cFileName); } } memset(&fd, 0, sizeof(WIN32_FIND_DATA)); } while (FindNextFile(fh, &fd) != 0); FindClose(fh); } return IsapiFileList.GetSize() > 0; } void DeleteTempFiles(const char *mask) { char FindPath[MAX_PATH]; WIN32_FIND_DATA fd; memset(&fd, 0, sizeof(WIN32_FIND_DATA)); _snprintf(FindPath, sizeof(FindPath)-1, "%s\\%s",temppath, mask); HANDLE fh = FindFirstFile(FindPath, &fd); if (fh != INVALID_HANDLE_VALUE) { do { char NewFindPath[MAX_PATH]; _snprintf(NewFindPath, sizeof(NewFindPath)-1, "%s\\%s",temppath, fd.cFileName); DeleteFile(NewFindPath); memset(&fd, 0, sizeof(WIN32_FIND_DATA)); } while (FindNextFile(fh, &fd) != 0); FindClose(fh); } } void DoTestFiles(const char *filelist, const char *environment) { if (!GetTestFiles(filelist)) { printf("No Files to test!\r\n"); return; } Results.SetSize(IsapiFileList.GetSize()); ReadGlobalEnvironment(environment); DoThreads(); printf("\r\nRESULTS:\r\n"); // show results: DWORD r = Results.GetSize(); for (DWORD i=0; i< r; i++) { TResults result = Results.GetAt(i); printf("%s\r\nOK: %d FAILED: %d\r\n", TestNames.GetAt(i), result.ok, result.bad); } // delete temp files printf("Deleting Temp Files\r\n"); DeleteTempFiles("exp.*"); DeleteTempFiles("pht.*"); printf("Done\r\n"); } int main(int argc, char* argv[]) { LPVOID lpMsgBuf; char *filelist=NULL, *environment=NULL; if (argc < 3) { // look for phpt files in tests printf("USAGE: stresstest [L|T] filelist [environment]\r\n"); return 0; } else { if (argv[1][0]=='T') bUseTestFiles = TRUE; if (argc > 1) filelist = argv[2]; if (argc > 2) environment = argv[3]; } GetTempPath(sizeof(temppath), temppath); hDll = LoadLibrary("php4isapi.dll"); // Load our DLL if (!hDll) { FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, NULL ); fprintf(stderr,"Error: Dll 'php4isapi.dll' not found -%d\n%s\n", GetLastError(),lpMsgBuf); LocalFree( lpMsgBuf ); return -1; } // // Find the exported functions IsapiGetExtensionVersion = (VersionProc)GetProcAddress(hDll,"GetExtensionVersion"); if (!IsapiGetExtensionVersion) { fprintf(stderr,"Can't Get Extension Version %d\n",GetLastError()); return -1; } IsapiHttpExtensionProc = (HttpExtProc)GetProcAddress(hDll,"HttpExtensionProc"); if (!IsapiHttpExtensionProc) { fprintf(stderr,"Can't Get Extension proc %d\n",GetLastError()); return -1; } // This should really check if the version information matches what we // expect. // if (!IsapiGetExtensionVersion(&version_info) ) { fprintf(stderr,"Fatal: GetExtensionVersion failed\n"); return -1; } if (bUseTestFiles) { char TestPath[MAX_PATH]; if (filelist != NULL) _snprintf(TestPath, sizeof(TestPath)-1, "%s\\tests", filelist); else strcpy(TestPath, "tests"); DoTestFiles(TestPath, environment); } else { DoFileList(filelist, environment); } // cleanup // We should really free memory (e.g., from GetEnv), but we'll be dead // soon enough FreeLibrary(hDll); return 0; } DWORD CALLBACK IsapiThread(void *p) { DWORD filecount = IsapiFileList.GetSize(); for (DWORD j=0; jenv.Lookup(lpszVariableName, value)) { rc = value.GetLength(); strncpy((char *)lpBuffer, value, *lpdwSize-1); } else rc = GetEnvironmentVariable(lpszVariableName,(char *)lpBuffer,*lpdwSize) ; if (!rc) { // return of 0 indicates the variable was not found SetLastError(ERROR_NO_DATA); return FALSE; } if (rc > *lpdwSize) { SetLastError(ERROR_INSUFFICIENT_BUFFER); return FALSE; } *lpdwSize =rc + 1 ; // GetEnvironmentVariable does not count the NULL return TRUE; } // // Again, we don't have an HCONN, so we simply wrap ReadClient() to // ReadFile on stdin. The semantics of the two functions are the same // BOOL WINAPI ReadClient(HCONN hConn, LPVOID lpBuffer, LPDWORD lpdwSize) { TIsapiContext *c = (TIsapiContext *)hConn; if (!c) return FALSE; if (c->in != INVALID_HANDLE_VALUE) return ReadFile(c->in,lpBuffer,(*lpdwSize), lpdwSize,NULL); return FALSE; } // // ditto for WriteClient() // BOOL WINAPI WriteClient(HCONN hConn, LPVOID lpBuffer, LPDWORD lpdwSize, DWORD dwReserved) { TIsapiContext *c = (TIsapiContext *)hConn; if (!c) return FALSE; if (c->out != INVALID_HANDLE_VALUE) return WriteFile(c->out,lpBuffer,*lpdwSize, lpdwSize,NULL); return FALSE; } // // This is a special callback function used by the DLL for certain extra // functionality. Look at the API help for details. // BOOL WINAPI ServerSupportFunction(HCONN hConn, DWORD dwHSERequest, LPVOID lpvBuffer, LPDWORD lpdwSize, LPDWORD lpdwDataType){ char *lpszRespBuf; char * temp = NULL; DWORD dwBytes; BOOL bRet; switch(dwHSERequest) { case (HSE_REQ_SEND_RESPONSE_HEADER) : lpszRespBuf = (char *)xmalloc(*lpdwSize);//+ 80);//accomodate our header if (!lpszRespBuf) return FALSE; wsprintf(lpszRespBuf,"%s", //HTTP_VER, /* Default response is 200 Ok */ //lpvBuffer?lpvBuffer:"200 Ok", /* Create a string for the time. */ //temp=MakeDateStr(), //SERVER_VERSION, /* If this exists, it is a pointer to a data buffer to be sent. */ lpdwDataType?(char *)lpdwDataType:NULL); if (temp) xfree(temp); dwBytes = strlen(lpszRespBuf); bRet = WriteClient(0,lpszRespBuf,&dwBytes,0); xfree(lpszRespBuf); break; // // A real server would do cleanup here case (HSE_REQ_DONE_WITH_SESSION): //ExitThread(0); break; // // This sends a redirect (temporary) to the client. // The header construction is similar to RESPONSE_HEADER above. // case (HSE_REQ_SEND_URL_REDIRECT_RESP): lpszRespBuf = (char *)xmalloc(*lpdwSize +80) ; if (!lpszRespBuf) return FALSE; wsprintf(lpszRespBuf,"%s %s %s\r\n", HTTP_VER, "302 Moved Temporarily", (lpdwSize > 0)?lpvBuffer:0); xfree(temp); dwBytes = strlen(lpszRespBuf); bRet = WriteClient(0,lpszRespBuf,&dwBytes,0); xfree(lpszRespBuf); break; default: return FALSE; break; } return bRet; } // // Makes a string of the date and time from GetSystemTime(). // This is in UTC, as required by the HTTP spec.` // char * MakeDateStr(void){ SYSTEMTIME systime; char *szDate= (char *)xmalloc(64); char * DaysofWeek[] = {"Sun","Mon","Tue","Wed","Thurs","Fri","Sat"}; char * Months[] = {"NULL","Jan","Feb","Mar","Apr","May","Jun","Jul","Aug", "Sep","Oct","Nov","Dec"}; GetSystemTime(&systime); wsprintf(szDate,"%s, %d %s %d %d:%d.%d",DaysofWeek[systime.wDayOfWeek], systime.wDay, Months[systime.wMonth], systime.wYear, systime.wHour,systime.wMinute, systime.wSecond ); return szDate; } // // Fill the ECB up // BOOL WINAPI FillExtensionControlBlock(EXTENSION_CONTROL_BLOCK *ECB, TIsapiContext *context) { char * temp; ECB->cbSize = sizeof(EXTENSION_CONTROL_BLOCK); ECB->dwVersion = MAKELONG(HSE_VERSION_MINOR,HSE_VERSION_MAJOR); ECB->ConnID = (void *)context; // // Pointers to the functions the DLL will call. // ECB->GetServerVariable = GetServerVariable; ECB->ReadClient = ReadClient; ECB->WriteClient = WriteClient; ECB->ServerSupportFunction = ServerSupportFunction; // // Fill in the standard CGI environment variables // ECB->lpszMethod = GetEnv("REQUEST_METHOD"); if (!ECB->lpszMethod) ECB->lpszMethod = "GET"; ECB->lpszQueryString = GetEnv("QUERY_STRING"); ECB->lpszPathInfo = GetEnv("PATH_INFO"); ECB->lpszPathTranslated = GetEnv("PATH_TRANSLATED"); ECB->cbTotalBytes=( (temp=GetEnv("CONTENT_LENGTH")) ? (atoi(temp)): 0); ECB->cbAvailable = 0; ECB->lpbData = (unsigned char *)""; ECB->lpszContentType = GetEnv("CONTENT_TYPE"); return TRUE; } // // Works like _getenv(), but uses win32 functions instead. // char * GetEnv(LPSTR lpszEnvVar) { char *var,dummy; DWORD dwLen; if (!lpszEnvVar) return ""; dwLen =GetEnvironmentVariable(lpszEnvVar,&dummy,1); if (dwLen == 0) return ""; var = (char *)xmalloc(dwLen); if (!var) return ""; (void)GetEnvironmentVariable(lpszEnvVar,var,dwLen); return var; }