/*++ Copyright (c) 1997 Microsoft Corporation Module Name: ctetest.c Abstract: ISAPI Extension sample illustrating Chunked Transfer Encoding. --*/ #include "ctetest.h" // // if chunksize= is not specified, use this value // #define DEFAULT_CHUNK_SIZE 1024 // // auxiliary functions prototypes // static BOOL SendChunkedFile( EXTENSION_CONTROL_BLOCK *, DWORD, LPCSTR ); static BOOL SendHttpHeaders( EXTENSION_CONTROL_BLOCK *, LPCSTR, LPCSTR, BOOL ); static BOOL GetFileMimeType( LPCSTR, LPSTR, DWORD ); static BOOL GetQueryStringField( LPCSTR, LPCSTR, LPSTR, DWORD ); static void DisplayExampleUsage( EXTENSION_CONTROL_BLOCK * ); DWORD WINAPI HttpExtensionProc( IN EXTENSION_CONTROL_BLOCK *pECB ) /*++ Purpose: Illustrate chunk transfer encoding in ISAPI HTTP Extension DLL. Process "GET" requests that specify a filename and transfer chunk size. Arguments: pECB - pointer to the extenstion control block Returns: HSE_STATUS_SUCCESS on successful transmission completion HSE_STATUS_ERROR on failure --*/ { DWORD dwChunksize = 0; char szPath[MAX_PATH]; char szHeaders[1024]; DWORD cchPath, cchHeaders; char szChunkSize[32]; // // if request method is not "GET", bail out // if( _stricmp( pECB->lpszMethod, "GET" ) != 0 ) { return HSE_STATUS_ERROR; } // // process chunksize= query argument, if any // if( GetQueryStringField( pECB->lpszQueryString, "chunksize", szChunkSize, sizeof szChunkSize )) { dwChunksize = atoi( szChunkSize ); } if( dwChunksize == 0 ) { dwChunksize = DEFAULT_CHUNK_SIZE; } // // process file= query argument // cchPath = sizeof szPath; if( !GetQueryStringField( pECB->lpszQueryString, "file", szPath, cchPath )) { // // no file specified - display usage and report success // DisplayExampleUsage( pECB ); return HSE_STATUS_SUCCESS; } // // use ServerSupportFunction to map virtual file name to local // path (otherwise users get access to any file on the system) // if( !pECB->ServerSupportFunction( pECB->ConnID, HSE_REQ_MAP_URL_TO_PATH, szPath, &cchPath, NULL ) ) { return HSE_STATUS_ERROR; } // // see if we can get file attributes, report error if not // if( GetFileAttributes( szPath ) == 0xFFFFFFFF ) { return HSE_STATUS_ERROR; } // // begin preparing the headers // strcpy( szHeaders, "Transfer-encoding: chunked\r\nContent-type: " ); // // obtain MIME type for this file and append it to the headers // cchHeaders = strlen( szHeaders ); GetFileMimeType( szPath, szHeaders + cchHeaders, sizeof szHeaders - cchHeaders ); // // terminate headers with empty line // strcat( szHeaders, "\r\n\r\n" ); // // try sending headers to the client // if( !SendHttpHeaders( pECB, "200 OK", szHeaders, TRUE ) ) { return HSE_STATUS_ERROR; } // // try sending the file using CTE encoding // if( !SendChunkedFile( pECB, dwChunksize, szPath ) ) { return HSE_STATUS_ERROR; } return HSE_STATUS_SUCCESS; } BOOL WINAPI GetExtensionVersion( OUT HSE_VERSION_INFO *pVer ) /*++ Purpose: This is required ISAPI Extension DLL entry point. Arguments: pVer - poins to extension version info structure Returns: always returns TRUE --*/ { // // tell the server our version number and extension description // pVer->dwExtensionVersion = MAKELONG( HSE_VERSION_MINOR, HSE_VERSION_MAJOR ); lstrcpyn( pVer->lpszExtensionDesc, "ISAPI CTE test", HSE_MAX_EXT_DLL_NAME_LEN); return TRUE; } BOOL WINAPI TerminateExtension( DWORD dwFlags ) /*++ Purpose: This is optional ISAPI extension DLL entry point. If present, it will be called before unloading the DLL, giving it a chance to perform any shutdown procedures. Arguments: dwFlags - specifies whether the DLL can refuse to unload or not Returns: TRUE, if the DLL can be unloaded --*/ { return TRUE; } BOOL WINAPI DllMain ( IN HINSTANCE hInstance, IN DWORD fdwReason, IN LPVOID lpvReserved ) /*++ Purpose: Perform any required DLL initialization here. Returns: TRUE if DLL was successfully initialized FALSE otherwise --*/ { // // Nothing needs to be done. This function exists a template. // return TRUE; } static BOOL SendChunkedFile( EXTENSION_CONTROL_BLOCK *pECB, DWORD dwChunkSize, LPCSTR pszPath ) /*++ Purpose: Transfer the specified file using chunked encoding. Illustrates the usage of CteBeginWrite(), CteWrite() and CteEndWrite() functions. Arguments: pECB - pointer to extenstion control block dwChunkSize - chunk size for transfer encoding pszPath - local file path Returns: TRUE if the file was successfully transfered, FALSE otherwise --*/ { HANDLE hFile; HCTE_ENCODER hCteWrite; BOOL fSuccess = FALSE; // // try accessing file // hFile = CreateFile( pszPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); if( hFile != INVALID_HANDLE_VALUE ) { BYTE buf[4096]; DWORD cbread; // // prepare chunk transfer encoder // hCteWrite = CteBeginWrite( pECB, dwChunkSize ); if ( hCteWrite ) { for( ;; ) { if( !ReadFile( hFile, buf, sizeof buf, &cbread, NULL ) ) { // // if ReadFile fails, break out of loop and cause // the function to return FALSE (failure) // break; } if( cbread == 0 ) { // // ReadFile succeded, but read 0 bytes - // we've achieved EOF and everything is transmitted. // break out and return success! // fSuccess = TRUE; break; } // // transmit one buffer full of data, // if( !CteWrite( hCteWrite, buf, cbread ) ) { // // CteWrite failed - break out and return FALSE // break; } } // // finish transfer and release encoder context // if( !CteEndWrite( hCteWrite ) ) { fSuccess = FALSE; } } CloseHandle( hFile ); } return fSuccess; } static BOOL SendHttpHeaders( EXTENSION_CONTROL_BLOCK *pECB, LPCSTR pszStatus, LPCSTR pszHeaders, BOOL fKeepConn ) /*++ Purpose: Send specified HTTP status string and any additional header strings using new ServerSupportFunction() request HSE_SEND_HEADER_EX_INFO Arguments: pECB - pointer to the extension control block pszStatus - HTTP status string (e.g. "200 OK") pszHeaders - any additional headers, separated by CRLFs and terminated by empty line fKeepConn - specifies whether to keep TCP connection open or close it after request is processed. Returns: TRUE if headers were successfully sent FALSE otherwise --*/ { HSE_SEND_HEADER_EX_INFO header_ex_info; BOOL success; header_ex_info.pszStatus = pszStatus; header_ex_info.pszHeader = pszHeaders; header_ex_info.cchStatus = strlen( pszStatus ); header_ex_info.cchHeader = strlen( pszHeaders ); header_ex_info.fKeepConn = fKeepConn; success = pECB->ServerSupportFunction( pECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER_EX, &header_ex_info, NULL, NULL ); return success; } static BOOL GetFileMimeType( LPCSTR pszPath, LPSTR pszType, DWORD cbMax ) /*++ Purpose: Given the file name, obtain MIME type for "Content-type:" header field. We try to find MIME type string under HCR\.xyz key, "Content Type" value. If that fails, we return default "application/ocetet-stream". Arguments: pszPath - file path pszType - points to the buffer that will receive MIME type string cbMax - specifies the maximum number of characters to copy to the buffer, including the NUL character. If the text exceed this limit, it will be truncated. Returns: TRUE, because we can always use default MIME type. --*/ { LPSTR pszExt; HKEY hKey; DWORD value_type; LONG result; // // set MIME type to empty string // *pszType = '\0'; // // try to locate file extension // pszExt = strrchr( pszPath, '.' ); if( pszExt != NULL ) { // // for file extension .xyz, MIME Type can be found // HKEY_CLASSES_ROOT\.xyz key in the registry // result = RegOpenKeyEx( HKEY_CLASSES_ROOT, pszExt, 0L, KEY_READ, &hKey ); if( result == ERROR_SUCCESS) { // // we sucessfully opened the key. // try getting the "Content Type" value // result = RegQueryValueEx( hKey, "Content Type", NULL, &value_type, (BYTE *)pszType, &cbMax ); // // if we failed to get the value or it is not string, // clear content-type field // if( result != ERROR_SUCCESS || value_type != REG_SZ ) { *pszType = '\0'; } RegCloseKey( hKey ); } } // // if at this point we don't have MIME type, use default // if( *pszType == '\0' ) { strncpy( pszType, "application/octet_stream", cbMax ); } return TRUE; } static void DisplayExampleUsage( EXTENSION_CONTROL_BLOCK *pECB ) /*++ Purpose: Send short plaintext description of our usage to the user. Arguments: pECB - pointer to the extension control block --*/ { DWORD dwLength; static char szUsage[] = "Example usage:\r\n" "http://localhost/scripts/ctetest.dll" "?file=/default.htm+chunksize=512\r\n"; char szHeaders[1024]; // // send simple headers and sample usage instruction // dwLength = sizeof szUsage - 1; sprintf( szHeaders, "Content-Length: %u\r\n" "Content-Type: text/plain\r\n\r\n", dwLength ); if( SendHttpHeaders( pECB, "200 OK", szHeaders, FALSE ) ) { pECB->WriteClient( pECB->ConnID, szUsage, &dwLength, HSE_IO_SYNC ); } } BOOL GetQueryStringField( LPCSTR pszQueryString, LPCSTR pszKey, LPSTR buf, DWORD cbuf ) /*++ Purpose: Assuming "key1=value1+key2=value2" syntax, extract the value for specified key. Arguments: pszQueryString - query string provided by ECB pszKey - key name buf - buffer for parameter value cbuf - buffer size Returns: TRUE if the value was successfully extracted FALSE otherwise --*/ { int len, keylen; LPCSTR p = pszQueryString; // // compute key and query lengths, bail out if either is missing // keylen = strlen( pszKey ); len = strlen( p ); if( keylen == 0 || len == 0 ) return FALSE; // // process one "+" delimited section at a time // for( ;; ) { // // skip any leading blanks, bail out if end of line found // while( *p <= ' ' ) { if( *p == '\0' ) { return FALSE; } p++; len--; } // // if the key won't fit into the rest of the command line, bail out // if( keylen + 1 > len ) { return FALSE; } // // is this the key we are looking for? // if( _memicmp( p, pszKey, keylen ) == 0 && p[keylen] == '=' ) { // // found it - skip '=' and break out of the loop // p += keylen + 1; break; } // // no match, try advancing to next '+' section // while( *p != '+' ) { if( *p == '\0' ) { return FALSE; } p++; len--; } // // found '+', skip it // p++; len--; } // // copy the value up to: the end of line, cbuf chars, or // '+' separator, whichever comes first // while( *p && *p != '+' ) { if( cbuf <= 1 ) { break; } *(buf++) = *(p++); cbuf--; } // // zero-terminate the value, report success // *buf = '\0'; return TRUE; }