1441 lines
40 KiB
C
1441 lines
40 KiB
C
/*++
|
|
Copyright (c) 2002 - 2006 Microsoft Corporation. All Rights Reserved.
|
|
|
|
THIS CODE AND INFORMATION IS PROVIDED "AS-IS" WITHOUT WARRANTY OF
|
|
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
PARTICULAR PURPOSE.
|
|
|
|
THIS CODE IS NOT SUPPORTED BY MICROSOFT.
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#include "async.h"
|
|
|
|
//Action to perfom GET or POST
|
|
DWORD g_action;
|
|
CONST LPWSTR DEFAULTHOSTNAME = L"www.microsoft.com";
|
|
//Host to connect to
|
|
LPWSTR g_hostName;
|
|
//Resource to get from the server
|
|
LPWSTR g_resource = L"/"; //By default request the root object
|
|
//File containing data to post
|
|
LPWSTR g_inputFile;
|
|
//File to write the data received from the server
|
|
LPWSTR g_outputFile;
|
|
//Flag to indicate the use of a proxy
|
|
BOOL g_bUseProxy = FALSE;
|
|
//Name of the proxy to use
|
|
LPWSTR g_proxy = NULL;
|
|
//Flag to indicate the use of SSL
|
|
BOOL g_bSecure=FALSE;
|
|
//Callback function
|
|
INTERNET_STATUS_CALLBACK g_callback;
|
|
//Structures to be used in the Async File IO callbacks
|
|
IO_BUF *g_readIO;
|
|
IO_BUF *g_writeIO;
|
|
//Timeout for the async operations
|
|
DWORD g_userTimeout = DEFAULT_TIMEOUT;
|
|
//Indicate if we had to create a temp file
|
|
BOOL g_bCreatedTempFile = FALSE;
|
|
//Pointer to RtlNtStatusToDosError function
|
|
PRtlNtStatusToDosError g_pfnRtlNtStatusToDosError = NULL;
|
|
|
|
//Counters of callings to malloc and free
|
|
#ifdef DEBUG
|
|
LONG allocCount = 0;
|
|
LONG freeCount = 0;
|
|
#endif
|
|
|
|
int __cdecl wmain(int argc,
|
|
__in_ecount(argc) LPWSTR *argv)
|
|
{
|
|
DWORD dwError = 0;
|
|
DWORD dwSync= 0;
|
|
BOOL bRequestSuccess;
|
|
DWORD dwFileSize = 0;
|
|
DWORD dwOpenType = INTERNET_OPEN_TYPE_PRECONFIG; //Use pre-configured options as default
|
|
INTERNET_PORT serverPort = INTERNET_DEFAULT_HTTP_PORT;
|
|
DWORD dwRequestFlags = 0;
|
|
LPWSTR verb;
|
|
INTERNET_BUFFERS buffersIn;
|
|
MAIN_CONTEXT mainContext;
|
|
APP_CONTEXT context;
|
|
DWORD dwStatus = STATUS_SUCCESS;
|
|
|
|
//Parse the command line arguments
|
|
ParseArguments(argc,
|
|
argv);
|
|
|
|
if((dwError = SetFunctionEntryPoint()) != ERROR_SUCCESS)
|
|
{
|
|
LogSysError(dwError, L"SetFunctionEntryPoint");
|
|
}
|
|
|
|
//Initialize the context for the Session and Connection handles
|
|
InitMainContext(&mainContext);
|
|
|
|
if(g_bUseProxy)
|
|
{
|
|
dwOpenType = INTERNET_OPEN_TYPE_PROXY;
|
|
}
|
|
//Create Session handle and specify Async Mode
|
|
mainContext.hSession = InternetOpen(L"WinInet HTTP Async Session", //User Agent
|
|
dwOpenType, //Preconfig or Proxy
|
|
g_proxy, //g_proxy name
|
|
NULL, //g_proxy bypass, do not bypass any address
|
|
INTERNET_FLAG_ASYNC); // 0 for Synchronous
|
|
|
|
if (!mainContext.hSession)
|
|
{
|
|
LogInetError(GetLastError(),L"InternetOpen");
|
|
dwStatus = STATUS_FAILURE;
|
|
goto Exit;
|
|
}
|
|
|
|
//Set the dwStatus callback for the handle to the Callback function
|
|
g_callback = InternetSetStatusCallback(mainContext.hSession,
|
|
(INTERNET_STATUS_CALLBACK)CallBack );
|
|
|
|
if (g_callback == INTERNET_INVALID_STATUS_CALLBACK)
|
|
{
|
|
LogInetError(GetLastError(),L"InternetSetStatusCallback");
|
|
dwStatus = STATUS_FAILURE;
|
|
goto Exit;
|
|
}
|
|
|
|
//Set the correct server port if using SSL
|
|
//Also set the flag for HttpOpenRequest
|
|
if(g_bSecure)
|
|
{
|
|
serverPort = INTERNET_DEFAULT_HTTPS_PORT;
|
|
dwRequestFlags = INTERNET_FLAG_SECURE;
|
|
}
|
|
|
|
//Create Connection handle and provide context for async operations
|
|
mainContext.hConnect = InternetConnect(mainContext.hSession,
|
|
g_hostName, //Name of the server to connect to
|
|
serverPort, //HTTP (80) or HTTPS (443)
|
|
NULL, //Do not provide a user name for the server
|
|
NULL, //Do not provide a password for the server
|
|
INTERNET_SERVICE_HTTP,
|
|
0, //Do not provide any special flag
|
|
(DWORD_PTR)&mainContext); //Provide the context to be
|
|
//used during the callbacks
|
|
//For HTTP InternetConnect returns synchronously.
|
|
//For FTP, ERROR_IO_PENDING should be verified too
|
|
if (!mainContext.hConnect )
|
|
{
|
|
LogInetError(GetLastError(),L"InternetConnect");
|
|
dwStatus = STATUS_FAILURE;
|
|
goto Exit;
|
|
}
|
|
|
|
|
|
//Initialize the context to be used in the asynchronous calls
|
|
InitRequestContext(&mainContext,
|
|
&context);
|
|
|
|
//Open the file to dump the response entity body and
|
|
//if required the file with the data to post
|
|
OpenFiles(&context);
|
|
|
|
//Verify if we've opened a file to post and get its size
|
|
if (context.hFile)
|
|
{
|
|
dwFileSize = GetFileSize(context.hFile,
|
|
NULL);
|
|
}
|
|
|
|
//Set the initial state of the context and the verb depending on the operation to perform
|
|
if(g_action == GET)
|
|
{
|
|
context.dwState = GET_REQ;
|
|
verb=L"GET";
|
|
}
|
|
else
|
|
{
|
|
context.dwState = POST_REQ;
|
|
verb=L"POST";
|
|
}
|
|
|
|
//We're overriding WinInet's default behavior.
|
|
//Setting this flags, we make sure we get the response from the server and not the cache.
|
|
//Also ask WinInet not to store the response in the cache.
|
|
dwRequestFlags |= INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE;
|
|
|
|
//Create a Request handle
|
|
context.hRequest = HttpOpenRequest(context.mainContext->hConnect,
|
|
verb, //GET or POST
|
|
g_resource, //root "/" by default
|
|
NULL, //USe default HTTP/1.1 as the version
|
|
NULL, //Do not provide any referrer
|
|
NULL, //Do not provide Accept types
|
|
dwRequestFlags, //(0 or INTERNET_FLAG_SECURE) |
|
|
// INTERNET_FLAG_RELOAD |
|
|
// INTERNET_FLAG_NO_CACHE_WRITE
|
|
(DWORD_PTR)&context);
|
|
|
|
if (!context.hRequest )
|
|
{
|
|
LogInetError(GetLastError(),L"HttpOpenRequest");
|
|
dwStatus = STATUS_FAILURE;
|
|
goto Exit;
|
|
}
|
|
|
|
//Send the request using two different options.
|
|
//HttpSendRequest for GET and HttpSendRequestEx for POST.
|
|
//HttpSendRequest can also be used also to post data to a server,
|
|
//to do so, the data should be provided using the lpOptional
|
|
//parameter and it's size on dwOptionalLength.
|
|
//Here we decided to depict the use of both HttpSendRequest functions.
|
|
if (g_action == GET)
|
|
{
|
|
bRequestSuccess = HttpSendRequest(context.hRequest,
|
|
NULL, //do not provide additional Headers
|
|
0, //dwHeadersLength
|
|
NULL, //Do not send any data
|
|
0); //dwOptionalLength
|
|
}
|
|
else
|
|
{
|
|
//Prepare the Buffers to be passed to HttpSendRequestEx
|
|
ZeroMemory(&buffersIn,sizeof(INTERNET_BUFFERS));
|
|
buffersIn.dwStructSize = sizeof(INTERNET_BUFFERS);
|
|
buffersIn.lpvBuffer = NULL;
|
|
buffersIn.dwBufferLength = 0;
|
|
buffersIn.dwBufferTotal = dwFileSize; //content-length of data to post
|
|
|
|
bRequestSuccess = HttpSendRequestEx(context.hRequest,
|
|
&buffersIn,
|
|
NULL, //Do not use output buffers
|
|
0, //dwFlags reserved
|
|
(DWORD_PTR)&context);
|
|
}
|
|
|
|
if (!bRequestSuccess && (dwError=GetLastError())!=ERROR_IO_PENDING)
|
|
{
|
|
LogInetError(dwError,L"HttpSendRequest(Ex)");
|
|
dwStatus = STATUS_FAILURE;
|
|
goto Exit;
|
|
}
|
|
|
|
//If you're using a UI thread, this call is not required
|
|
dwSync = WaitForSingleObject(context.hEvent,
|
|
g_userTimeout); // Wait until we receive the completion
|
|
|
|
switch(dwSync)
|
|
{
|
|
case WAIT_OBJECT_0:
|
|
printf("Done!\n");
|
|
break;
|
|
case WAIT_ABANDONED:
|
|
fprintf(stderr,
|
|
"The callback thread was terminated\n");
|
|
dwStatus = STATUS_FAILURE;
|
|
break;
|
|
case WAIT_TIMEOUT:
|
|
fprintf(stderr,
|
|
"Timeout while waiting for event\n");
|
|
dwStatus = STATUS_FAILURE;
|
|
break;
|
|
}
|
|
|
|
Exit:
|
|
|
|
CleanUp(&context);
|
|
return dwStatus;
|
|
}
|
|
|
|
VOID CALLBACK
|
|
CallBack(HINTERNET hInternet ,
|
|
__in DWORD_PTR dwContext,
|
|
DWORD dwInternetStatus,
|
|
__in_bcount(dwStatusInformationLength) LPVOID lpvStatusInformation,
|
|
DWORD dwStatusInformationLength)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Callback routine for asynchronous WinInet operations
|
|
|
|
Arguments:
|
|
hInternet - The handle for which the callback function is called.
|
|
dwContext - Pointer to the application defined context.
|
|
dwInternetStatus - Status code indicating why the callback is called.
|
|
lpvStatusInformation - Pointer to a buffer holding callback specific data.
|
|
dwStatusInformationLength - Specifies size of lpvStatusInformation buffer.
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
DWORD dwError;
|
|
DWORD dwBytes = 0;
|
|
BOOL bQuit = FALSE;
|
|
MAIN_CONTEXT *mainContext;
|
|
APP_CONTEXT *appContext;
|
|
|
|
//Perform the correct casting given the type of structure passed in the callback
|
|
DWORD *dwStructType = (DWORD*)dwContext;
|
|
|
|
if( *dwStructType == STRUCT_TYPE_MAIN_CONTEXT)
|
|
{
|
|
mainContext = (MAIN_CONTEXT*)dwContext;
|
|
}
|
|
else
|
|
{
|
|
appContext = (APP_CONTEXT*)dwContext;
|
|
}
|
|
|
|
fprintf(stderr,"Callback Received for Handle %p \t",hInternet);
|
|
|
|
switch(dwInternetStatus)
|
|
{
|
|
case INTERNET_STATUS_COOKIE_SENT:
|
|
fprintf(stderr,"Status: Cookie found and will be sent with request\n");
|
|
break;
|
|
case INTERNET_STATUS_COOKIE_RECEIVED:
|
|
fprintf(stderr,"Status: Cookie Received\n");
|
|
break;
|
|
case INTERNET_STATUS_COOKIE_HISTORY:
|
|
{
|
|
InternetCookieHistory cookieHistory;
|
|
fprintf(stderr,"Status: Cookie History\n");
|
|
|
|
//Verify we've a valid pointer with the correct size
|
|
if(lpvStatusInformation &&
|
|
dwStatusInformationLength == sizeof(InternetCookieHistory))
|
|
{
|
|
cookieHistory = *((InternetCookieHistory*)lpvStatusInformation);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr,"Cookie History not valid\n");
|
|
goto ExitSwitch;
|
|
}
|
|
if(cookieHistory.fAccepted)
|
|
{
|
|
fprintf(stderr,"Cookie Accepted\n");
|
|
}
|
|
if(cookieHistory.fLeashed)
|
|
{
|
|
fprintf(stderr,"Cookie Leashed\n");
|
|
}
|
|
if(cookieHistory.fDowngraded)
|
|
{
|
|
fprintf(stderr,"Cookie Downgraded\n");
|
|
}
|
|
if(cookieHistory.fRejected)
|
|
{
|
|
fprintf(stderr,"Cookie Rejected\n");
|
|
}
|
|
}
|
|
ExitSwitch:
|
|
break;
|
|
case INTERNET_STATUS_CLOSING_CONNECTION:
|
|
fprintf(stderr,"Status: Closing Connection\n");
|
|
break;
|
|
case INTERNET_STATUS_CONNECTED_TO_SERVER:
|
|
fprintf(stderr,"Status: Connected to Server\n");
|
|
break;
|
|
case INTERNET_STATUS_CONNECTING_TO_SERVER:
|
|
fprintf(stderr,"Status: Connecting to Server\n");
|
|
break;
|
|
case INTERNET_STATUS_CONNECTION_CLOSED:
|
|
fprintf(stderr,"Status: Connection Closed\n");
|
|
break;
|
|
case INTERNET_STATUS_HANDLE_CLOSING:
|
|
fprintf(stderr,"Status: Handle Closing\n");
|
|
//Signal the event for closing the handle
|
|
//only for the Request Handle
|
|
if(appContext)
|
|
{
|
|
SetEvent(appContext->hEvent);
|
|
}
|
|
break;
|
|
case INTERNET_STATUS_HANDLE_CREATED:
|
|
//Verify we've a valid pointer
|
|
if(lpvStatusInformation)
|
|
{
|
|
fprintf(stderr,
|
|
"Handle %x created\n",
|
|
((LPINTERNET_ASYNC_RESULT)lpvStatusInformation)->dwResult);
|
|
}
|
|
break;
|
|
case INTERNET_STATUS_INTERMEDIATE_RESPONSE:
|
|
fprintf(stderr,"Status: Intermediate response\n");
|
|
break;
|
|
case INTERNET_STATUS_RECEIVING_RESPONSE:
|
|
fprintf(stderr,"Status: Receiving Response\n");
|
|
break;
|
|
case INTERNET_STATUS_RESPONSE_RECEIVED:
|
|
//Verify we've a valid pointer with the correct size
|
|
if(lpvStatusInformation &&
|
|
dwStatusInformationLength == sizeof(DWORD))
|
|
{
|
|
dwBytes = *((LPDWORD)lpvStatusInformation);
|
|
fprintf(stderr,"Status: Response Received (%d Bytes)\n",dwBytes);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr,"Response Received: lpvStatusInformation not valid\n");
|
|
}
|
|
break;
|
|
case INTERNET_STATUS_REDIRECT:
|
|
fprintf(stderr,"Status: Redirect\n");
|
|
break;
|
|
case INTERNET_STATUS_REQUEST_COMPLETE:
|
|
fprintf(stderr,"Status: Request complete\n");
|
|
|
|
//check for error first
|
|
dwError = ((LPINTERNET_ASYNC_RESULT)lpvStatusInformation)->dwError ;
|
|
|
|
if ( dwError != ERROR_SUCCESS)
|
|
{
|
|
LogInetError(dwError,L"Request_Complete");
|
|
exit(1);
|
|
}
|
|
|
|
switch (appContext->dwState)
|
|
{
|
|
case POST_REQ:
|
|
//read bytes to write
|
|
if((dwError = DoReadFile(appContext))!=ERROR_SUCCESS
|
|
&& dwError!= ERROR_IO_PENDING)
|
|
{
|
|
LogSysError(dwError,L"DoReadFile");
|
|
exit(1);
|
|
}
|
|
|
|
break;
|
|
|
|
case POST_RES: //fall through
|
|
case GET_REQ:
|
|
|
|
if(!appContext->dwDownloaded )
|
|
{
|
|
|
|
EnterCriticalSection(&appContext->crSection);
|
|
{
|
|
appContext->bReceiveDone=TRUE;
|
|
|
|
if (!appContext->lPendingWrites)
|
|
{
|
|
bQuit = TRUE;
|
|
}
|
|
|
|
}
|
|
LeaveCriticalSection(&appContext->crSection);
|
|
|
|
if(bQuit)
|
|
{
|
|
SetEvent(appContext->hEvent);
|
|
}
|
|
|
|
break;
|
|
}
|
|
else if(appContext->dwDownloaded !=INVALID_DOWNLOAD_VALUE)
|
|
{
|
|
|
|
ZeroMemory(g_writeIO,sizeof(IO_BUF));
|
|
|
|
InterlockedIncrement(&appContext->lPendingWrites);
|
|
|
|
g_writeIO->aContext = appContext;
|
|
|
|
g_writeIO->lpo.Offset=appContext->dwWriteOffset;
|
|
CopyMemory(&g_writeIO->buffer,
|
|
appContext->pszOutBuffer,
|
|
appContext->dwDownloaded);
|
|
|
|
if(!WriteFile(appContext->hRes,
|
|
&g_writeIO->buffer,
|
|
appContext->dwDownloaded,
|
|
NULL,
|
|
&g_writeIO->lpo))
|
|
{
|
|
if((dwError=GetLastError())!= ERROR_IO_PENDING)
|
|
{
|
|
LogSysError(dwError,L"WriteFile");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
appContext->dwWriteOffset += appContext->dwDownloaded;
|
|
|
|
}
|
|
else
|
|
{
|
|
//appContext->dwDownloaded ==INVALID_DOWNLOAD_VALUE
|
|
//We're in the initial state of the response's download
|
|
fprintf(stderr,"Ready to start reading the Response Entity Body\n");
|
|
}
|
|
|
|
DoInternetRead(appContext);
|
|
|
|
break;
|
|
}
|
|
|
|
break;
|
|
case INTERNET_STATUS_REQUEST_SENT:
|
|
//Verify we've a valid pointer with the correct size
|
|
if(lpvStatusInformation &&
|
|
dwStatusInformationLength == sizeof(DWORD))
|
|
{
|
|
dwBytes = *((LPDWORD)lpvStatusInformation);
|
|
fprintf(stderr,"Status: Request sent (%d Bytes)\n",dwBytes);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr,"Request sent: lpvStatusInformation not valid\n");
|
|
}
|
|
break;
|
|
case INTERNET_STATUS_DETECTING_PROXY:
|
|
fprintf(stderr,"Status: Detecting Proxy\n");
|
|
break;
|
|
case INTERNET_STATUS_RESOLVING_NAME:
|
|
fprintf(stderr,"Status: Resolving Name\n");
|
|
break;
|
|
case INTERNET_STATUS_NAME_RESOLVED:
|
|
fprintf(stderr,"Status: Name Resolved\n");
|
|
break;
|
|
case INTERNET_STATUS_SENDING_REQUEST:
|
|
fprintf(stderr,"Status: Sending request\n");
|
|
break;
|
|
case INTERNET_STATUS_STATE_CHANGE:
|
|
fprintf(stderr,"Status: State Change\n");
|
|
break;
|
|
case INTERNET_STATUS_P3P_HEADER:
|
|
fprintf(stderr,"Status: Received P3P header\n");
|
|
break;
|
|
default:
|
|
fprintf(stderr,"Status: Unknown (%d)\n",dwInternetStatus);
|
|
break;
|
|
}
|
|
}
|
|
|
|
DWORD
|
|
DoReadFile(__in APP_CONTEXT* aContext)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This routine handles asynschronous file reads.
|
|
|
|
Arguments:
|
|
aContext - Pointer to application context structure
|
|
|
|
Return Value:
|
|
Error code for the operation.
|
|
|
|
--*/
|
|
{
|
|
DWORD dwError = ERROR_SUCCESS;
|
|
|
|
ZeroMemory(g_readIO,sizeof(IO_BUF));
|
|
|
|
g_readIO->aContext = aContext;
|
|
|
|
g_readIO->lpo.Offset = aContext->dwReadOffset;
|
|
|
|
if ( !ReadFile(aContext->hFile,
|
|
&g_readIO->buffer,
|
|
BUFFER_LEN,
|
|
NULL,
|
|
&g_readIO->lpo) )
|
|
{
|
|
|
|
if ( (dwError=GetLastError()) == ERROR_HANDLE_EOF )
|
|
{
|
|
//Clear the error code since we've handled the error conditions
|
|
dwError = DoCompleteReadFile(aContext);
|
|
}
|
|
else if (dwError != ERROR_IO_PENDING )
|
|
{
|
|
LogSysError(dwError,L"ReadFile");
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
return dwError;
|
|
}
|
|
|
|
DWORD DoCompleteReadFile(__in APP_CONTEXT* aContext)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This routine handles asynschronous file reads.
|
|
|
|
Arguments:
|
|
aContext - Pointer to application context structure
|
|
|
|
Return Value:
|
|
Error Code for the operation.
|
|
|
|
--*/
|
|
{
|
|
|
|
DWORD dwError=ERROR_SUCCESS;
|
|
|
|
fprintf(stderr,"Finished posting file\n");
|
|
aContext->dwState = POST_RES;
|
|
if( !HttpEndRequest(aContext->hRequest,NULL,0,0))
|
|
{
|
|
if ( (dwError = GetLastError()) == ERROR_IO_PENDING)
|
|
{
|
|
fprintf(stderr,"Waiting for HttpEndRequest to complete \n");
|
|
}
|
|
else
|
|
{
|
|
LogInetError(dwError,L"HttpEndRequest");
|
|
goto Exit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DoInternetRead(aContext);
|
|
}
|
|
|
|
Exit:
|
|
return dwError;
|
|
}
|
|
|
|
VOID
|
|
DoInternetWrite(__in APP_CONTEXT *aContext)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This routine handles WinInet Writes. If a write completes synchronously
|
|
this routine issues a file read to gather data to post.
|
|
|
|
Arguments:
|
|
aContext - Pointer to application context structure
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
DWORD dwError = 0;
|
|
|
|
if(InternetWriteFile(aContext->hRequest,
|
|
aContext->pszOutBuffer,
|
|
aContext->dwRead,
|
|
&aContext->dwWritten))
|
|
{
|
|
//read bytes to write
|
|
if((dwError=DoReadFile(aContext)) != ERROR_SUCCESS
|
|
&& dwError != ERROR_IO_PENDING)
|
|
|
|
{
|
|
LogSysError(dwError,L"DoReadFile");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if ( (dwError=GetLastError()) == ERROR_IO_PENDING)
|
|
{
|
|
fprintf(stderr,"Waiting for InternetWriteFile to complete\n");
|
|
}
|
|
else if ( dwError != ERROR_SUCCESS)
|
|
{
|
|
LogInetError(dwError,L"InternetWriteFile");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
DoInternetRead(__in APP_CONTEXT *aContext)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This routine handles WinInet/Internet reads.
|
|
|
|
Arguments:
|
|
aContext - Pointer to application context structure
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
DWORD dwError=0;
|
|
|
|
while (InternetReadFile(aContext->hRequest,
|
|
aContext->pszOutBuffer,
|
|
BUFFER_LEN,
|
|
&aContext->dwDownloaded))
|
|
{
|
|
//completed synchronously ; callback won't be issued
|
|
BOOL bQuit = FALSE;
|
|
|
|
if ( !aContext->dwDownloaded )
|
|
{
|
|
EnterCriticalSection(&aContext->crSection);
|
|
{
|
|
aContext->bReceiveDone = TRUE;
|
|
|
|
if (!aContext->lPendingWrites)
|
|
{
|
|
bQuit = TRUE;
|
|
}
|
|
|
|
}
|
|
LeaveCriticalSection(&aContext->crSection);
|
|
|
|
if (bQuit)
|
|
{
|
|
SetEvent(aContext->hEvent);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ZeroMemory(g_writeIO,sizeof(IO_BUF));
|
|
|
|
InterlockedIncrement(&aContext->lPendingWrites);
|
|
|
|
g_writeIO->aContext = aContext;
|
|
|
|
g_writeIO->lpo.Offset = aContext->dwWriteOffset;
|
|
CopyMemory(&g_writeIO->buffer,
|
|
aContext->pszOutBuffer,
|
|
aContext->dwDownloaded);
|
|
|
|
if (!WriteFile(aContext->hRes,
|
|
&g_writeIO->buffer,
|
|
aContext->dwDownloaded,
|
|
NULL,
|
|
&g_writeIO->lpo))
|
|
{
|
|
|
|
if ( (dwError = GetLastError()) != ERROR_IO_PENDING )
|
|
{
|
|
LogSysError(dwError,L"WriteFile");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
aContext->dwWriteOffset += aContext->dwDownloaded;
|
|
|
|
}
|
|
|
|
if ( (dwError=GetLastError()) == ERROR_IO_PENDING)
|
|
{
|
|
fprintf(stderr,"Waiting for InternetReadFile to complete\n");
|
|
}
|
|
else
|
|
{
|
|
LogInetError(dwError,L"InternetReadFile");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
VOID CALLBACK
|
|
WriteFileCallBack(DWORD dwErrorCode,
|
|
DWORD dwNumberOfBytesTransfered,
|
|
__in LPOVERLAPPED lpOverlapped)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Callback routine for Asynchronous file write completions. This
|
|
routine determines if the response is completely received and
|
|
all writes are completed before signalling the event to terminate
|
|
the program. Frees the Overlapped object.
|
|
|
|
Arguments:
|
|
dwErrorCode - I/O completion Status
|
|
dwNumberOfBytesTransfered - Number of bytes transfered
|
|
lpOverlapped - Pointer to the overlapped structure used in WriteFile
|
|
The way IO_BUF structure is designed, this pointer also
|
|
points to the IO_BUF structure.
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
BOOL bQuit = FALSE;
|
|
APP_CONTEXT *aContext;
|
|
IO_BUF *ioBuf;
|
|
UNREFERENCED_PARAMETER(dwNumberOfBytesTransfered);
|
|
|
|
if (dwErrorCode == ERROR_SUCCESS)
|
|
{
|
|
ioBuf = CONTAINING_RECORD(lpOverlapped,
|
|
IO_BUF,
|
|
lpo);
|
|
aContext = ioBuf->aContext;
|
|
|
|
EnterCriticalSection(&aContext->crSection);
|
|
{
|
|
if (!InterlockedDecrement(&aContext->lPendingWrites)
|
|
&& aContext->bReceiveDone)
|
|
{
|
|
bQuit = TRUE;
|
|
|
|
}
|
|
}
|
|
LeaveCriticalSection(&aContext->crSection);
|
|
|
|
if (bQuit)
|
|
{
|
|
SetEvent(aContext->hEvent);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
VOID CALLBACK
|
|
ReadFileCallBack(DWORD dwErrorCode,
|
|
DWORD dwNumberOfBytesTransfered,
|
|
__in LPOVERLAPPED lpOverlapped)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Callback routine for Asynchronous file read completions. On successful
|
|
file read this routine triggers a WinInet Write operation to transfer data
|
|
to the http server.
|
|
|
|
Arguments:
|
|
dwErrorCode - I/O completion Status
|
|
dwNumberOfBytesTransfered - Number of bytes read
|
|
lpOverlapped - Pointer to the overlapped structure used in WriteFile
|
|
The way IO_BUF structure is designed, this pointer also
|
|
points to the IO_BUF structure.
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
LPSTR buffer;
|
|
APP_CONTEXT *aContext;
|
|
IO_BUF *ioBuf;
|
|
|
|
if ( dwErrorCode == ERROR_SUCCESS ||
|
|
g_pfnRtlNtStatusToDosError(dwErrorCode) == ERROR_HANDLE_EOF )
|
|
{
|
|
ioBuf = CONTAINING_RECORD(lpOverlapped,
|
|
IO_BUF,
|
|
lpo);
|
|
|
|
aContext = ioBuf->aContext;
|
|
if ( dwErrorCode == ERROR_SUCCESS)
|
|
{
|
|
buffer = ioBuf->buffer;
|
|
|
|
aContext->dwReadOffset += dwNumberOfBytesTransfered;
|
|
aContext->dwRead = dwNumberOfBytesTransfered;
|
|
|
|
CopyMemory(aContext->pszOutBuffer,
|
|
buffer,
|
|
aContext->dwRead);
|
|
|
|
DoInternetWrite(aContext);
|
|
}
|
|
else //ERROR_HANDLE_EOF
|
|
{
|
|
DoCompleteReadFile(aContext);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
InitMainContext(__inout MAIN_CONTEXT *aMainContext)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This routine initializes the session and connection handles.
|
|
|
|
Arguments:
|
|
aMainContext - Pointer to MAIN_CONTEXT structure
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
aMainContext->dwStructType = STRUCT_TYPE_MAIN_CONTEXT;
|
|
aMainContext->hSession = NULL;
|
|
aMainContext->hConnect = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
InitRequestContext(__in MAIN_CONTEXT* aMainContext,
|
|
__inout APP_CONTEXT *aContext)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This routine initializes application request context variables to appropriate
|
|
values.
|
|
|
|
Arguments:
|
|
aContext - Pointer to Application context structure
|
|
aMainContext - Pointer to MAIN_CONTEXT structure containing the sesion and
|
|
connection handles.
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
aContext->dwStructType = STRUCT_TYPE_APP_CONTEXT;
|
|
aContext->mainContext = aMainContext;
|
|
aContext->hRequest = NULL;
|
|
aContext->dwDownloaded = INVALID_DOWNLOAD_VALUE;
|
|
aContext->dwRead = 0;
|
|
aContext->dwWritten = 0;
|
|
aContext->dwReadOffset = 0;
|
|
aContext->dwWriteOffset = 0;
|
|
aContext->lPendingWrites = 0;
|
|
aContext->bReceiveDone = FALSE;
|
|
aContext->hFile = NULL;
|
|
aContext->hRes = NULL;
|
|
|
|
aContext->pszOutBuffer = Malloc(BUFFER_LEN);
|
|
|
|
//create event
|
|
aContext->hEvent = CreateEvent(NULL, //Sec attrib
|
|
FALSE, //Auto reset
|
|
FALSE, //Initial state unsignalled
|
|
L"MAIN_SYNC");
|
|
|
|
if (!aContext->hEvent)
|
|
{
|
|
LogSysError(GetLastError(),L"CreateEvent");
|
|
exit(1);
|
|
}
|
|
|
|
//initialize critical section
|
|
InitializeCriticalSection(&aContext->crSection);
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
CleanUp(__in APP_CONTEXT* aContext)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Used to cleanup application context before exiting.
|
|
|
|
Arguments:
|
|
aContext - Application context structure
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
DWORD dwSync = 0;
|
|
|
|
if( aContext->hFile)
|
|
{
|
|
CloseHandle(aContext->hFile);
|
|
}
|
|
if( aContext->hRes)
|
|
{
|
|
CloseHandle(aContext->hRes);
|
|
}
|
|
if( aContext->hRequest )
|
|
{
|
|
InternetCloseHandle(aContext->hRequest );
|
|
// Wait for the closing of the handle
|
|
dwSync = WaitForSingleObject(aContext->hEvent,INFINITE);
|
|
if(WAIT_ABANDONED == dwSync)
|
|
{
|
|
fprintf(stderr,"The callback thread has terminated.\n");
|
|
}
|
|
}
|
|
if( aContext->mainContext->hConnect )
|
|
{
|
|
//Remove the callback from the Connection handle.
|
|
//Since se set the callback on the session handle previous to create the conenction and
|
|
//request handles, they inherited the callback function.
|
|
//Setting the callback function to null in the Connect handle, will ensure we don't get
|
|
//a notification when the handle is closed
|
|
g_callback = InternetSetStatusCallback( aContext->mainContext->hConnect, NULL );
|
|
|
|
//Call InternetCloseHandle and do not wait for the closing notification
|
|
//in the callback funciton
|
|
InternetCloseHandle(aContext->mainContext->hConnect );
|
|
|
|
}
|
|
if( aContext->mainContext->hSession)
|
|
{
|
|
//Remove the callback from the Session handle
|
|
g_callback = InternetSetStatusCallback( aContext->mainContext->hSession, NULL );
|
|
//At this point the Session handle should be valid
|
|
InternetCloseHandle(aContext->mainContext->hSession );
|
|
}
|
|
if( aContext->hEvent)
|
|
{
|
|
CloseHandle(aContext->hEvent);
|
|
}
|
|
|
|
DeleteCriticalSection(&aContext->crSection);
|
|
|
|
Free(aContext->pszOutBuffer);
|
|
|
|
//Free the structures containing the overlapped structure
|
|
Free(g_readIO);
|
|
Free(g_writeIO);
|
|
|
|
//Free the buffer for the temporary name if needed.
|
|
if(g_bCreatedTempFile)
|
|
{
|
|
Free(g_outputFile);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
printf("Cleanup:\nAlloc count is %d\n",allocCount);
|
|
printf("Free count is %d\n",freeCount);
|
|
#endif
|
|
}
|
|
|
|
VOID
|
|
OpenFiles(__inout APP_CONTEXT *aContext)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This routine opens files in async mode and binds a thread from the
|
|
thread-pool to handle the callback for asynchronous operations. Always
|
|
opens a file to write output to.
|
|
|
|
Arguments:
|
|
aContext - Pointer to Application context structure
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
if ( g_action == POST)
|
|
{
|
|
//Open input file
|
|
aContext->hFile = CreateFile(g_inputFile,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL, // handle cannot be inherited
|
|
OPEN_ALWAYS, // if file exists, open it
|
|
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
|
|
NULL); //No template file
|
|
|
|
if (!aContext->hFile)
|
|
{
|
|
LogSysError(GetLastError(),L"CreateFile");
|
|
exit(1);
|
|
}
|
|
|
|
if ( ! BindIoCompletionCallback(aContext->hFile,ReadFileCallBack,0))
|
|
{
|
|
LogSysError(GetLastError(),L"BindIoCompletionCallback");
|
|
exit(1);
|
|
|
|
}
|
|
}
|
|
|
|
//Open output file
|
|
aContext->hRes = CreateFile(g_outputFile,
|
|
GENERIC_WRITE,
|
|
0, //Open exclusively
|
|
NULL, //handle cannot be inherited
|
|
CREATE_ALWAYS, // if file exists, delete it
|
|
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
|
|
NULL); //No template file
|
|
|
|
if (!aContext->hRes)
|
|
{
|
|
LogSysError(GetLastError(),L"CreateFile");
|
|
exit(1);
|
|
}
|
|
|
|
if ( ! BindIoCompletionCallback(aContext->hRes,WriteFileCallBack,0) )
|
|
{
|
|
LogSysError(GetLastError(),L"BindIoCompletionCallback");
|
|
exit(1);
|
|
}
|
|
|
|
//Allocate Memory for the IO_BUFF structures
|
|
g_writeIO = Malloc(sizeof(IO_BUF));
|
|
g_readIO = Malloc(sizeof(IO_BUF));
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
ParseArguments(int argc,
|
|
__in_ecount(argc) LPWSTR *argv)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This routine is used to Parse command line arguments. Flags are
|
|
case sensitive.
|
|
|
|
Arguments:
|
|
argc - Number of arguments
|
|
argv - Pointer to the argument vector
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
int i;
|
|
DWORD dwError = 0;
|
|
UINT uRetVal;
|
|
|
|
for (i = 1; i < argc; ++i)
|
|
{
|
|
if ( wcsncmp(argv[i],L"-",1))
|
|
{
|
|
printf("Invalid switch %ws\n",argv[i]);
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
switch(argv[i][1])
|
|
{
|
|
case L'p':
|
|
|
|
g_bUseProxy = 1;
|
|
if (i < argc-1)
|
|
{
|
|
g_proxy = argv[++i];
|
|
}
|
|
break;
|
|
|
|
case L'h':
|
|
|
|
if ( i < argc-1)
|
|
{
|
|
g_hostName = argv[++i];
|
|
}
|
|
|
|
break;
|
|
|
|
case L'o':
|
|
|
|
if ( i < argc-1)
|
|
{
|
|
g_resource = argv[++i];
|
|
}
|
|
|
|
break;
|
|
|
|
case L'r':
|
|
|
|
if ( i < argc-1)
|
|
{
|
|
g_inputFile = argv[++i];
|
|
}
|
|
|
|
break;
|
|
|
|
case L'w':
|
|
|
|
if ( i < argc-1)
|
|
{
|
|
g_outputFile = argv[++i];
|
|
}
|
|
|
|
break;
|
|
|
|
case L'a':
|
|
|
|
if ( i < argc-1)
|
|
{
|
|
if ( !_wcsnicmp(argv[i+1],L"get",3))
|
|
{
|
|
g_action = GET;
|
|
}
|
|
else if (!_wcsnicmp(argv[i+1],L"post",4))
|
|
{
|
|
g_action = POST;
|
|
}
|
|
}
|
|
++i;
|
|
break;
|
|
|
|
case L's':
|
|
g_bSecure = TRUE;
|
|
break;
|
|
|
|
case L't':
|
|
if ( i < argc-1)
|
|
{
|
|
//Verify the user provided a valid number for the default time
|
|
if (0 != isdigit((UCHAR)argv[i+1][0]))
|
|
{
|
|
g_userTimeout = _wtoi(argv[++i]);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ShowUsage();
|
|
exit(1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!g_hostName)
|
|
{
|
|
printf("Defaulting hostname to: %ws\n",DEFAULTHOSTNAME);
|
|
g_hostName = DEFAULTHOSTNAME;
|
|
}
|
|
|
|
if (!g_action)
|
|
{
|
|
printf("Defaulting action to: GET\n");
|
|
g_action = GET;
|
|
}
|
|
|
|
if (!g_inputFile && g_action == POST)
|
|
{
|
|
printf("Error: File to post not specified\n");
|
|
dwError++;
|
|
}
|
|
|
|
if (!g_outputFile)
|
|
{
|
|
g_bCreatedTempFile = TRUE;
|
|
g_outputFile = Malloc(MAX_PATH);
|
|
// Create a temporary file.
|
|
uRetVal = GetTempFileName(L".", // current directory
|
|
L"TMP", // temp file name prefix
|
|
0, // create unique name
|
|
g_outputFile); // buffer for name
|
|
if (uRetVal == 0)
|
|
{
|
|
printf ("GetTempFileName failed with error %d.\n", GetLastError());
|
|
dwError++;
|
|
}
|
|
else
|
|
{
|
|
printf("Defaulting output file to: %ws\n", g_outputFile);
|
|
}
|
|
|
|
}
|
|
|
|
if (dwError)
|
|
{
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
ShowUsage(VOID)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Shows the usage of the application.
|
|
|
|
Arguments:
|
|
None.
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
printf("Usage: async [-a {get|post}] [-h <hostname>] [-o <resourcename>] [-s] ");
|
|
printf("[-p <proxyname>] [-w <output filename>] [-r <file to post>] [-t <userTimeout>]\n");
|
|
printf("Flag Semantics: \n");
|
|
printf("-a : Specify action (\"get\" if omitted)\n");
|
|
printf("-h : Specify Hostname (\"www.microsoft.com\" if omitted)\n");
|
|
printf("-o : Specify resource name in the server (\"/\" if omitted)\n");
|
|
printf("-s : Use secure connection - https\n");
|
|
printf("-p : Specify Proxy\n");
|
|
printf("-w : Specify file to write output to (generate temp file if omitted)\n");
|
|
printf("-r : Specify file to post data from\n");
|
|
printf("-t : Specify time to wait for completing the operation in async mode. Default 2 minutes");
|
|
}
|
|
|
|
VOID
|
|
LogInetError(DWORD err,
|
|
__in LPCWSTR str)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This routine is used to log WinInet errors in human readable form.
|
|
|
|
Arguments:
|
|
err - Error number obtained from GetLastError()
|
|
str - String pointer holding caller-context information
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
DWORD dwResult;
|
|
LPTSTR msgBuffer = Malloc(ERR_MSG_LEN*sizeof(TCHAR));
|
|
|
|
ZeroMemory(msgBuffer,ERR_MSG_LEN*sizeof(TCHAR));
|
|
|
|
dwResult = FormatMessage(FORMAT_MESSAGE_FROM_HMODULE,
|
|
GetModuleHandle(L"wininet.dll"),
|
|
err,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
msgBuffer,
|
|
ERR_MSG_LEN,
|
|
NULL);
|
|
|
|
if (dwResult)
|
|
{
|
|
fprintf(stderr,"%ws: %ws\n",str,msgBuffer);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr,
|
|
"Error %d while formatting message for %d in %ws\n",
|
|
GetLastError(),
|
|
err,
|
|
str);
|
|
}
|
|
Free(msgBuffer);
|
|
}
|
|
|
|
VOID
|
|
LogSysError(DWORD err,
|
|
__in LPCWSTR str)
|
|
/*++
|
|
|
|
Routine Description:
|
|
This routine is used to log System Errors in human readable form.
|
|
|
|
Arguments:
|
|
err - Error number obtained from GetLastError()
|
|
str - String pointer holding caller-context information
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
DWORD dwResult;
|
|
LPTSTR msgBuffer = Malloc(ERR_MSG_LEN*sizeof(TCHAR));
|
|
|
|
ZeroMemory(msgBuffer,ERR_MSG_LEN*sizeof(TCHAR));
|
|
|
|
dwResult = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
|
|
NULL,
|
|
err,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
msgBuffer,
|
|
ERR_MSG_LEN,
|
|
NULL);
|
|
|
|
if (dwResult)
|
|
{
|
|
fprintf(stderr,
|
|
"%ws: %ws\n",
|
|
str,
|
|
msgBuffer);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr,
|
|
"Error %d while formatting message for %d in %ws\n",
|
|
GetLastError(),
|
|
err,
|
|
str);
|
|
}
|
|
Free(msgBuffer);
|
|
}
|
|
|
|
VOID *
|
|
Malloc(size_t size)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Wrapper around malloc handling errors and alloc counts.
|
|
|
|
Arguments:
|
|
size - number of bytes to allocate
|
|
|
|
Return Value:
|
|
VOID pointer to block of memory allocated
|
|
|
|
--*/
|
|
{
|
|
VOID *ptr;
|
|
|
|
#ifdef DEBUG
|
|
InterlockedIncrement(&allocCount);
|
|
#endif
|
|
|
|
ptr = malloc(size);
|
|
|
|
if (!ptr)
|
|
{
|
|
fprintf(stderr,"Out of Memory\n");
|
|
exit(1);
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
|
|
|
|
VOID
|
|
Free(__in VOID *memblock)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Wrapper around free to keep up free counts.
|
|
|
|
Arguments:
|
|
memblock - pointer to memblock to be freed
|
|
|
|
Return Value:
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
#ifdef DEBUG
|
|
InterlockedIncrement(&freeCount);
|
|
#endif
|
|
|
|
free(memblock);
|
|
}
|
|
|
|
DWORD
|
|
SetFunctionEntryPoint()
|
|
/*++
|
|
|
|
Routine Description:
|
|
Get the funciton pointer for RtlNtStatusToDosError.
|
|
|
|
Arguments:
|
|
None
|
|
|
|
Return Value:
|
|
Error code for the operation.
|
|
|
|
--*/
|
|
{
|
|
DWORD dwError = ERROR_SUCCESS;
|
|
|
|
HMODULE hModule = GetModuleHandle( L"ntdll.dll" );
|
|
|
|
if(hModule)
|
|
{
|
|
SetLastError( ERROR_SUCCESS );
|
|
|
|
g_pfnRtlNtStatusToDosError = ( PRtlNtStatusToDosError ) GetProcAddress( hModule, "RtlNtStatusToDosError" );
|
|
}
|
|
|
|
dwError = GetLastError();
|
|
|
|
return dwError;
|
|
}
|