/*++
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;
}