Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WTL

WinINet Test Application

4.71/5 (29 votes)
12 Apr 2008CPOL3 min read 1   4.1K  
A WTL application to test asynchronous WinINet functionality.

Image 1

Introduction

This is a WTL program that is able to test connections with web servers using just about every possible combination of requirements that are commonly encountered. The program is able to connect:

  • POST or GET
  • SSL
  • supply personal certificate, if required by the web server
  • vary receive time out
  • supply username/password for web server
  • ignore bad CA certificates

The program uses asynchronous WinINet calls to handle all communication and reporting. The program also demonstrates how to allow the user to select a personal certificate from the My store.

Background

I put this program together to help test some issues I encountered with an unusual firewall configuration and web server security requirements. The output from the WinINet call back routine is very helpful when debugging connection issues. I have placed a number of messages in the ATLTRACE macro output as well as the main dialog of the program. While example code for most of the operations can be found in a variety of locations, I needed to tie everything together into a single application, implemented with consistent error handling.

The program started as an amalgamation of several samples from Microsoft. After a few hours of coding and a quick port to WTL, the application was pretty well gutted and rewritten. I need to give credit where credit is due for this application: the Microsoft examples saved me a lot of time (except when they were wrong or incomplete and cost me a lot of time).

Using the Code

The code is very simple to use. Simply download the demonstration and compile it. I am using WTL 8.0 with VS 2005. You may need to download WTL.

This is a WTL modal dialog application. I used some wait functions developed by Andy Smith to get around blocking issues when a modal dialog is utilized. You'll really want to examine the main WinINet loop, located in SendRequest. Here, you will learn how to handle ERROR_INTERNET_INVALID_CA, ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED, and HTTP_STATUS_DENIED. The WinInetCallback method is utilized to supply information like personal certificates or user name and password for resubmission to the web server and pass communication errors to the WinINet loop in SendRequest via the context variable.

Obtaining a Pointer to a Personal Certificate

There are times when you may need to allow a user to select a personal certificate. For instance, you are writing an application that uses WinINet. Here is some simple code that allows a user to select from a list of personal certificates loaded in their certificate store:

#include <prsht.h>
#include <cryptuiapi.h>

...

HCERTSTORE        m_hMyStore;
PCCERT_CONTEXT    m_pHSCertContext;

...

// Open the 'MY' certificate store to be presented to the user
if(!(m_hMyStore = CertOpenStore(
    CERT_STORE_PROV_SYSTEM,
    0,
    NULL,
    CERT_SYSTEM_STORE_CURRENT_USER,
    L"MY")))
{
    // Let the user know we could not open the store.
    CString tMsg, tTitle;
    GetErrorMessage(GetLastError(), &tMsg);
    MessageBox((LPCTSTR) tMsg, 
               (LPCTSTR) tTitle.LoadString(IDS_TITLEFAILED), 
               MB_ICONEXCLAMATION);
    return FALSE;
}
else
{
    // If we get to this stage, the certificate store
    // was opened successfully.
    // Display a selection of certificates to choose from.
    m_pHSCertContext = CryptUIDlgSelectCertificateFromStore(
        m_hMyStore,
        NULL,
        NULL,
        NULL,
        CRYPTUI_SELECT_EXPIRATION_COLUMN | 
        CRYPTUI_SELECT_LOCATION_COLUMN |
        CRYPTUI_SELECT_FRIENDLYNAME_COLUMN |
        CRYPTUI_SELECT_INTENDEDUSE_COLUMN ,
        0,
        NULL);
}

//Show an error if a certificate is not seleted.
if (!m_pHSCertContext)
{
    CString tMsg, tTitle;
    MessageBox((LPCTSTR) tMsg.LoadString(IDS_ERR1), 
               (LPCTSTR) tTitle.LoadString(IDS_ERRT1), 
               MB_ICONEXCLAMATION);
    return FALSE;
}

Supplying a Personal Certificate on a WinINet Handle

TCP/IP communications are a little tricky, in that you cannot supply the certificate when you make the request. You need to call OpenRequest, and then wait for the INTERNET_STATUS_REQUEST_COMPLETE signal in your Callback routine. You can then check the returned error, looking for ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED in the INTERNET_ASYNC_RESULT struct. When you get the error, you need to use InternetSetOption on your OpenRequest handle to supply the certificate and then repeat the OpenRequest. It's really pretty simple. It's just that the docs and samples leave out a lot of the error checking and recovery work that you need. It helps to have a server setup with a valid SSL certificate and require personal certificates in order to connect. Here is a code fragment for handling ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED:

case INTERNET_STATUS_REQUEST_COMPLETE:
    {
        //check for errors
        if(ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED == pRes->dwError)
        {
            // The server requires a certificate

            // Save dwError so we can act on it.
            cpMainDlg->m_dwError = pRes->dwError;
            sMsg.Format("%s: CLIENT_AUTH_CERT_NEEDED (%d)", 
                        cpMainDlg->m_szMemo, pRes->dwError);

            // Check that we have a certificate pointer,
            // otherwise, it's time to fall over dead.
            if(cpMainDlg->m_pHSCertContext == NULL)
            {
                //write the callback information to the buffer
                sMsg += _T(" No cert provided by user");
            }
            else
            {
                // We have a pointer (there's no guarantee it's valid)
                // We have to be careful and check all returns for errors
                // as the pointer could point to la-la land if someone in
                // a different thread has closed the certificate store.

                // Attempt to set the option
                if (!::InternetSetOption( cpMainDlg->hRequest,
                    INTERNET_OPTION_CLIENT_CERT_CONTEXT ,
                    (LPVOID) cpMainDlg->m_pHSCertContext,
                    sizeof(CERT_CONTEXT) ))
                {
                    // Display the error.
                    CString sErrMsg;
                    cpMainDlg->GetErrorMessage(GetLastError(), &sErrMsg);
                    sMsg += _T(" ");
                    sMsg += sErrMsg;
                }
            }
        } // end if (ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED == dwError)
...
    }

Future Plans

I will add authentication handling for proxy servers in a future revision. Currently, only one set of credentials may be supplied, but the program needs to allow for two sets of credentials, one for the proxy server and one for the web server. I also want to add a file attachment dialog for testing the POST method with binary data or pseudo form data attached.

History

  • Version 1.0 - First release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)