719 lines
14 KiB
C
719 lines
14 KiB
C
/*++
|
||
|
||
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;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|