Another Use for an ISAPI Extension

In the Chapter 9 discussion of ASP, our examination of how ASP was implemented on IIS led to the discovery that the ASP.dll responsible for implementing ASP is in fact an ISAPI DLL, exporting the functions required of any ISAPI extension. This discovery gives developers a powerful tool if they should require special scripting support not possible with the standard ASP scripting. Why might this be useful?

  • Some development teams might have special knowledge or history of using a particular language for development. Implementing this language as a script language on IIS might offer the ability to leverage existing code and programmer skills for Web development.
  • Special scripting support allows scripted access to databases that are not, for practical purposes, accessible through an ODBC data source. For instance, until recently, Pervasive Software's Pervasive.SQL was not known for speedy ODBC access. Furthermore, if the files to be accessed did not have data definition files, they could be valid Pervasive.SQL files, but they could not be accessed via ODBC.
  • Imagine that you wanted the ease of a scripted application, but you needed the security of source code not offered by tools such as ASP. One possibility would be the creation of a scripting DLL that would accept encrypted script. Given an editor that allowed developers to work easily on the encrypted script, you could freely distribute the script without fear of compromising trade secrets embedded within it.

Of these three reasons, the first seems to be the most prevalent among people who have approached me on the subject. On several occasions, Perl and Python advocates have approached me after one of my talks on ISAPI. While implementing a scripting language is not for the squeamish and is fraught with manifold difficulties, it is not an overwhelming task.

For demonstration purposes, I created a simple ISAPI extension using MFC. The complete IspMFC_NG project is on the companion CD, though it is there merely as an example of what ended up not working. I set up the extension to translate any file with an extension of .isp (Internet scripting program). One of the first things I needed to do was to get the full path of the current script being executed so that my program could read and translate, as needed, the script I requested . This led to a default function similar to the following function that as yet contained no actual translation code:

 voidCIspExtension::Default(CHttpServerContext*pCtxt) { charpath[255]; charscript[255]; DWORDcBytes=255; StartContent(pCtxt); WriteTitle(pCtxt); *pCtxt<<_T("Pathinformation<BR>"); pCtxt->GetServerVariable("PATH_TRANSLATED",path,&cBytes); *pCtxt<<path<<"<BR>\r\n"; pCtxt->GetServerVariable("SCRIPT_NAME",script,&cBytes); *pCtxt<<script<<"<BR>\r\n"; strcat(path,"\"); strcat(path,&script[1]); *pCtxt<<path<<"<BR>\r\n"; EndContent(pCtxt); } 

When called directly as a normal ISAPI extension ( http://server/path/isp.dll? ), the extension worked as expected, giving me the proper path for the "script" being executed, in my example, c:\inetpub\ wwwroot \isp.dll. Next I used the IIS configuration screen shown in Figure 10-1 and associated the .isp extension with this ISP.dll. I created a simple file with an .isp extension and used a URL that specified the name of the script file ( http://server/path/test.isp ), and the path came back as expected, c:\inetpub\wwwroot\test.isp.

Finally I tried adding a query string to the end of the URL. Here is where the intelligence of the MFC ISAPI classes got in my way. I received a status 400 in the browser when the URL with the query string was called. This made sense. The framework could not parse my query and find an appropriate function. Of course, it was possible to override the CallFunction method, but this seemed like overkill for the fairly simple requirement to funnel all requests to a single function to interpret a script.

At this point I was at a crossroads , one familiar to folks who use frameworks like MFC. I had two choices. First I could dig farther into the framework and override the functionality at the core of the CHttpServer class. This was in some ways an attractive solution because the ISAPI classes are interesting to investigate and the convenience of using the classes was clear. Comparing the code for the initial simple raw ISAPI example to the MFC examples reminded me of this convenience.

On the other hand, what was my goal? Was I looking to become an expert at the MFC ISAPI classes, or was I looking to get a scripting DLL done? I knew how much work I had to do to get the raw ISAPI scripting DLL done. Was I comfortable enough with the MFC classes to be sure I was not buying into a lot more work than I wanted? Would my customizations to the CallFunction method come back to haunt me? Were the MFC parsing features overkill when all scripting requests would be funneled into a single function in any event?

All these concerns led me to create a raw ISAPI scripting DLL. Given that the basic framework was in place already, I took the code from the original raw ISAPI extension and converted it for use as a scripting DLL. The scripting being done in this simple example was as follows . Wherever I saw the string "!date", I replaced it with the date. I replaced "!time" with the time, and I replaced "!name" with "Doug Reilly". The resulting source is presented in Listing 10-7.

Listing 10-7

ISPRaw.cpp

 //ISPRaw.cpp:DefinestheentrypointfortheDLLapplication. // #include"stdafx.h" BOOLAPIENTRYDllMain(HANDLEhModule, DWORDul_reason_for_call, LPVOIDlpReserved) { returnTRUE; } BOOLWINAPIGetExtensionVersion(HSE_VERSION_INFO*pVer) { pVer->dwExtensionVersion=1; strcpy(pVer->lpszExtensionDesc, "ISAPI1TestforInsideWindowsServerDevelopment"); return(TRUE); } DWORDWINAPIHttpExtensionProc(EXTENSION_CONTROL_BLOCK*lpECB) { DWORDcBuf; DWORDcBytes; charszMessage[1024]; charpath[255]; charquery[255]; FILE*in; HSE_SEND_HEADER_EX_INFOHeaderExInfo; //Prepareaheader.... HeaderExInfo.pszStatus="200OK"; HeaderExInfo.cchStatus=strlen(HeaderExInfo.pszStatus); HeaderExInfo.pszHeader="Content-type:text/html\r\n\r\n"; HeaderExInfo.cchHeader=strlen(HeaderExInfo.pszHeader); HeaderExInfo.fKeepConn=FALSE; //SendheadersusingIIS-providedcallback. //ThesefunctionsareusedquiteabitinISAPIprogramming. lpECB->ServerSupportFunction(lpECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER_EX, &HeaderExInfo, NULL, NULL); //Getthepaththatwaspassedinforthisscript,translated //toaformatappropriateforfilereading.... lpECB->GetServerVariable(lpECB->ConnID, "PATH_TRANSLATED",path,&cBytes); //Intherealworld,wewouldmakethequerystringavailable //tothescriptaswell. cBytes=255; lpECB->GetServerVariable(lpECB->ConnID, "QUERY_STRING",query,&cBytes); in=fopen(path,"rt"); if(in!=0) { intch; charszTemp[100]; szMessage[0]=' 
 //  ISPRaw.cpp  :  Defines  the  entry  point  for  the  DLL  application. // #include  "stdafx.h" BOOL  APIENTRY  DllMain(HANDLE  hModule,                                              DWORD    ul_reason_for_call,                                              LPVOID  lpReserved) {         return  TRUE; } BOOL  WINAPI  GetExtensionVersion(HSE_VERSION_INFO  *pVer) {         pVer->dwExtensionVersion  =  1; strcpy (pVer->lpszExtensionDesc,                 "ISAPI1  Test  for  Inside  Windows  Server  Development");         return(TRUE); } DWORD  WINAPI  HttpExtensionProc(EXTENSION_CONTROL_BLOCK  *lpECB) {         DWORD  cBuf;         DWORD  cBytes;         char  szMessage[1024];         char  path[255];         char  query[255];         FILE  *in;         HSE_SEND_HEADER_EX_INFO  HeaderExInfo;                 //  Prepare  a  header....         HeaderExInfo.pszStatus  =  "200  OK";         HeaderExInfo.cchStatus  =  strlen(HeaderExInfo.pszStatus);         HeaderExInfo.pszHeader  =  "Content-type:  text/html\r\n\r\n";         HeaderExInfo.cchHeader  =  strlen(HeaderExInfo.pszHeader);         HeaderExInfo.fKeepConn  =  FALSE;                 //  Send  headers  using  IIS-provided  callback.         //  These  functions  are  used  quite  a  bit  in  ISAPI  programming.         lpECB->ServerSupportFunction(lpECB->ConnID,                 HSE_REQ_SEND_RESPONSE_HEADER_EX,                 &HeaderExInfo,                 NULL,                 NULL);         //  Get  the  path  that  was  passed  in  for  this  script,  translated         //    to  a  format  appropriate  for  file  reading....         lpECB->GetServerVariable(lpECB->ConnID,                 "PATH_TRANSLATED",  path,&cBytes);         //  In  the  real  world,  we  would  make  the  query  string  available         //    to  the  script  as  well.         cBytes  =  255;         lpECB->GetServerVariable(lpECB->ConnID,                 "QUERY_STRING",  query,&cBytes);         in  =  fopen(path,  "rt");         if  (in  !=  0)         {                 int  ch;                 char  szTemp[100];                 szMessage[0]  =  '\0';                 //  Read  through  the  script...                 while  ((ch  =  fgetc(in))  !=  EOF)                 {                         //  ...until  we  see  our  special  signal  character.                         if  (ch  !=  '!')                         {                                 //  Just  pass  along  the  text.                                 szTemp[0]  =  ch;                                 szTemp[1]  =  '\0';                                 strcat(szMessage,  szTemp);                         }                         else                         {                                 //  Get  the  4 characters after  the  '!'.                                 szTemp[0]  =  fgetc(in);                                 szTemp[1]  =  fgetc(in);                                 szTemp[2]  =  fgetc(in);                                 szTemp[3]  = fgetc (in);                                 szTemp[4]  =  '\0';                                 //  Copy  in  the  time  in  place  of  '!time'.                                 if  (!(strnicmp(szTemp,  "time",4)))                                 {                                         char  szTime[255];                                         _strtime(szTime);                                         strcat(szMessage,  szTime);                                 }                                 //  Copy  in  the  date  in  place  of  '!date'.                                 else  if  (!(strnicmp(szTemp,  "date",  4)))                                 {                                         char  szDate[255];                                         _strdate(szDate);                                         strcat(szMessage,  szDate);                                 }                                 //  Copy  in  my  name  in  place  of  '!name'.                                 else  if  (!(strnicmp(szTemp,  "name",  4)))                                 {                                         strcat(szMessage,  "Douglas  Reilly");                                 }                                 else                                 {                                         strcat(szMessage,  "!");                                         strcat(szMessage,  szTemp);                                 }                         }                 }         }         //  Set  the  length  of  the  buffer  being  written.         cBuf  =  strlen(szMessage);         //  Use  another  of  the  functions  provided  by  IIS  to  write  content         //    to  the  browser.         lpECB->WriteClient(lpECB->ConnID,                 szMessage,  &cBuf,  HSE_IO_SYNC);         return  HSE_STATUS_SUCCESS; } 
'; //Readthroughthescript... while((ch=fgetc(in))!=EOF) { //...untilweseeourspecialsignalcharacter. if(ch!='!') { //Justpassalongthetext. szTemp[0]=ch; szTemp[1]='
 //  ISPRaw.cpp  :  Defines  the  entry  point  for  the  DLL  application. // #include  "stdafx.h" BOOL  APIENTRY  DllMain(HANDLE  hModule,                                              DWORD    ul_reason_for_call,                                              LPVOID  lpReserved) {         return  TRUE; } BOOL  WINAPI  GetExtensionVersion(HSE_VERSION_INFO  *pVer) {         pVer->dwExtensionVersion  =  1; strcpy (pVer->lpszExtensionDesc,                 "ISAPI1  Test  for  Inside  Windows  Server  Development");         return(TRUE); } DWORD  WINAPI  HttpExtensionProc(EXTENSION_CONTROL_BLOCK  *lpECB) {         DWORD  cBuf;         DWORD  cBytes;         char  szMessage[1024];         char  path[255];         char  query[255];         FILE  *in;         HSE_SEND_HEADER_EX_INFO  HeaderExInfo;                 //  Prepare  a  header....         HeaderExInfo.pszStatus  =  "200  OK";         HeaderExInfo.cchStatus  =  strlen(HeaderExInfo.pszStatus);         HeaderExInfo.pszHeader  =  "Content-type:  text/html\r\n\r\n";         HeaderExInfo.cchHeader  =  strlen(HeaderExInfo.pszHeader);         HeaderExInfo.fKeepConn  =  FALSE;                 //  Send  headers  using  IIS-provided  callback.         //  These  functions  are  used  quite  a  bit  in  ISAPI  programming.         lpECB->ServerSupportFunction(lpECB->ConnID,                 HSE_REQ_SEND_RESPONSE_HEADER_EX,                 &HeaderExInfo,                 NULL,                 NULL);         //  Get  the  path  that  was  passed  in  for  this  script,  translated         //    to  a  format  appropriate  for  file  reading....         lpECB->GetServerVariable(lpECB->ConnID,                 "PATH_TRANSLATED",  path,&cBytes);         //  In  the  real  world,  we  would  make  the  query  string  available         //    to  the  script  as  well.         cBytes  =  255;         lpECB->GetServerVariable(lpECB->ConnID,                 "QUERY_STRING",  query,&cBytes);         in  =  fopen(path,  "rt");         if  (in  !=  0)         {                 int  ch;                 char  szTemp[100];                 szMessage[0]  =  '\0';                 //  Read  through  the  script...                 while  ((ch  =  fgetc(in))  !=  EOF)                 {                         //  ...until  we  see  our  special  signal  character.                         if  (ch  !=  '!')                         {                                 //  Just  pass  along  the  text.                                 szTemp[0]  =  ch;                                 szTemp[1]  =  '\0';                                 strcat(szMessage,  szTemp);                         }                         else                         {                                 //  Get  the  4 characters after  the  '!'.                                 szTemp[0]  =  fgetc(in);                                 szTemp[1]  =  fgetc(in);                                 szTemp[2]  =  fgetc(in);                                 szTemp[3]  = fgetc (in);                                 szTemp[4]  =  '\0';                                 //  Copy  in  the  time  in  place  of  '!time'.                                 if  (!(strnicmp(szTemp,  "time",4)))                                 {                                         char  szTime[255];                                         _strtime(szTime);                                         strcat(szMessage,  szTime);                                 }                                 //  Copy  in  the  date  in  place  of  '!date'.                                 else  if  (!(strnicmp(szTemp,  "date",  4)))                                 {                                         char  szDate[255];                                         _strdate(szDate);                                         strcat(szMessage,  szDate);                                 }                                 //  Copy  in  my  name  in  place  of  '!name'.                                 else  if  (!(strnicmp(szTemp,  "name",  4)))                                 {                                         strcat(szMessage,  "Douglas  Reilly");                                 }                                 else                                 {                                         strcat(szMessage,  "!");                                         strcat(szMessage,  szTemp);                                 }                         }                 }         }         //  Set  the  length  of  the  buffer  being  written.         cBuf  =  strlen(szMessage);         //  Use  another  of  the  functions  provided  by  IIS  to  write  content         //    to  the  browser.         lpECB->WriteClient(lpECB->ConnID,                 szMessage,  &cBuf,  HSE_IO_SYNC);         return  HSE_STATUS_SUCCESS; } 
'; strcat(szMessage,szTemp); } else { //Getthe4charactersafterthe'!'. szTemp[0]=fgetc(in); szTemp[1]=fgetc(in); szTemp[2]=fgetc(in); szTemp[3]=fgetc(in); szTemp[4]='
 //  ISPRaw.cpp  :  Defines  the  entry  point  for  the  DLL  application. // #include  "stdafx.h" BOOL  APIENTRY  DllMain(HANDLE  hModule,                                              DWORD    ul_reason_for_call,                                              LPVOID  lpReserved) {         return  TRUE; } BOOL  WINAPI  GetExtensionVersion(HSE_VERSION_INFO  *pVer) {         pVer->dwExtensionVersion  =  1; strcpy (pVer->lpszExtensionDesc,                 "ISAPI1  Test  for  Inside  Windows  Server  Development");         return(TRUE); } DWORD  WINAPI  HttpExtensionProc(EXTENSION_CONTROL_BLOCK  *lpECB) {         DWORD  cBuf;         DWORD  cBytes;         char  szMessage[1024];         char  path[255];         char  query[255];         FILE  *in;         HSE_SEND_HEADER_EX_INFO  HeaderExInfo;                 //  Prepare  a  header....         HeaderExInfo.pszStatus  =  "200  OK";         HeaderExInfo.cchStatus  =  strlen(HeaderExInfo.pszStatus);         HeaderExInfo.pszHeader  =  "Content-type:  text/html\r\n\r\n";         HeaderExInfo.cchHeader  =  strlen(HeaderExInfo.pszHeader);         HeaderExInfo.fKeepConn  =  FALSE;                 //  Send  headers  using  IIS-provided  callback.         //  These  functions  are  used  quite  a  bit  in  ISAPI  programming.         lpECB->ServerSupportFunction(lpECB->ConnID,                 HSE_REQ_SEND_RESPONSE_HEADER_EX,                 &HeaderExInfo,                 NULL,                 NULL);         //  Get  the  path  that  was  passed  in  for  this  script,  translated         //    to  a  format  appropriate  for  file  reading....         lpECB->GetServerVariable(lpECB->ConnID,                 "PATH_TRANSLATED",  path,&cBytes);         //  In  the  real  world,  we  would  make  the  query  string  available         //    to  the  script  as  well.         cBytes  =  255;         lpECB->GetServerVariable(lpECB->ConnID,                 "QUERY_STRING",  query,&cBytes);         in  =  fopen(path,  "rt");         if  (in  !=  0)         {                 int  ch;                 char  szTemp[100];                 szMessage[0]  =  '\0';                 //  Read  through  the  script...                 while  ((ch  =  fgetc(in))  !=  EOF)                 {                         //  ...until  we  see  our  special  signal  character.                         if  (ch  !=  '!')                         {                                 //  Just  pass  along  the  text.                                 szTemp[0]  =  ch;                                 szTemp[1]  =  '\0';                                 strcat(szMessage,  szTemp);                         }                         else                         {                                 //  Get  the  4 characters after  the  '!'.                                 szTemp[0]  =  fgetc(in);                                 szTemp[1]  =  fgetc(in);                                 szTemp[2]  =  fgetc(in);                                 szTemp[3]  = fgetc (in);                                 szTemp[4]  =  '\0';                                 //  Copy  in  the  time  in  place  of  '!time'.                                 if  (!(strnicmp(szTemp,  "time",4)))                                 {                                         char  szTime[255];                                         _strtime(szTime);                                         strcat(szMessage,  szTime);                                 }                                 //  Copy  in  the  date  in  place  of  '!date'.                                 else  if  (!(strnicmp(szTemp,  "date",  4)))                                 {                                         char  szDate[255];                                         _strdate(szDate);                                         strcat(szMessage,  szDate);                                 }                                 //  Copy  in  my  name  in  place  of  '!name'.                                 else  if  (!(strnicmp(szTemp,  "name",  4)))                                 {                                         strcat(szMessage,  "Douglas  Reilly");                                 }                                 else                                 {                                         strcat(szMessage,  "!");                                         strcat(szMessage,  szTemp);                                 }                         }                 }         }         //  Set  the  length  of  the  buffer  being  written.         cBuf  =  strlen(szMessage);         //  Use  another  of  the  functions  provided  by  IIS  to  write  content         //    to  the  browser.         lpECB->WriteClient(lpECB->ConnID,                 szMessage,  &cBuf,  HSE_IO_SYNC);         return  HSE_STATUS_SUCCESS; } 
'; //Copyinthetimeinplaceof'!time'. if(!(strnicmp(szTemp,"time",4))) { charszTime[255]; _strtime(szTime); strcat(szMessage,szTime); } //Copyinthedateinplaceof'!date'. elseif(!(strnicmp(szTemp,"date",4))) { charszDate[255]; _strdate(szDate); strcat(szMessage,szDate); } //Copyinmynameinplaceof'!name'. elseif(!(strnicmp(szTemp,"name",4))) { strcat(szMessage,"DouglasReilly"); } else { strcat(szMessage,"!"); strcat(szMessage,szTemp); } } } } //Setthelengthofthebufferbeingwritten. cBuf=strlen(szMessage); //UseanotherofthefunctionsprovidedbyIIStowritecontent //tothebrowser. lpECB->WriteClient(lpECB->ConnID, szMessage,&cBuf,HSE_IO_SYNC); returnHSE_STATUS_SUCCESS; }

The most significant part of the listing is within HttpExtensionProc . The header is sent as in the initial ISAPI example. Next the translated path is retrieved, using the GetServerVariable callback embedded in the EXTENSION_CONTROL_BLOCK pointer. The PATH_TRANSLATED server variable handles converting the virtual path into a path appropriate for use with the C standard file I/O functions. The fopen function is used to open the file, and characters are read in one at a time and appended to the szMessage character buffer that will be used to send information back to the browser.

While reading in characters from the script file, the character stream is monitored , and when the program sees an exclamation point (!), it checks the next four characters for the literal "time", "date", or "name". If the program finds a match, it replaces all five characters of the metatag with the time, date, or name. If the four characters after the ! character are not one of the strings to be substituted, the program appends the ! and the following four characters to the szMessage buffer.

Figure 10-12 shows the screen that results on a client browser when ISPRaw.dll processes the simple script shown in Listing 10-8. The result that ISPRaw.dll sends to the browser is standard HTML code.

click to view at full size.

Figure 10-12 The result of calling a simple ISP script processed by ISPRaw.dll.

Listing 10-8

 <HTML> <Title>test</TITLE> <BODY> <Center><H1>Hi!</H1> <br><H2> Timeis!Time,dateis!date.<BR> Mynameis!Name.</H2> </CENTER> </BODY> </HTML> 


Inside Server-Based Applications
Inside Server-Based Applications (DV-MPS General)
ISBN: 1572318171
EAN: 2147483647
Year: 1999
Pages: 91

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net