65.9K
CodeProject 正在变化。 阅读更多。
Home

WinINet 测试应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (27投票s)

2008年4月12日

CPOL

3分钟阅读

viewsIcon

123671

downloadIcon

4106

一个用于测试异步 WinINet 功能的 WTL 应用程序。

引言

这是一个 WTL 程序,能够使用几乎所有常见组合的必需项来测试与 Web 服务器的连接。该程序能够连接

  • POST 或 GET
  • SSL
  • 根据 Web 服务器的要求,提供个人证书
  • 改变接收超时时间
  • 为 Web 服务器提供用户名/密码
  • 忽略错误的 CA 证书

该程序使用异步 WinINet 调用来处理所有通信和报告。该程序还演示了如何允许用户从“我的”存储区中选择个人证书。

背景

我编写了这个程序来帮助测试我在遇到不寻常的防火墙配置和 Web 服务器安全要求时遇到的一些问题。WinINet 回调例程的输出在调试连接问题时非常有帮助。我已经将许多消息放置在 ATLTRACE 宏输出以及程序的主对话框中。虽然大多数操作的示例代码可以在各种位置找到,但我需要将所有内容整合到一个单一的应用程序中,并使用一致的错误处理来实现。

该程序最初是来自 Microsoft 的几个示例的组合。经过几个小时的编码和快速移植到 WTL 后,该应用程序几乎被完全删除并重写。我需要为这个应用程序给予应有的荣誉:Microsoft 的示例为我节省了很多时间(除了它们错误或不完整并且花费了我很多时间的时候)。

Using the Code

代码非常易于使用。只需下载演示并编译它即可。我正在使用 WTL 8.0 和 VS 2005。您可能需要下载 WTL

这是一个 WTL 模式对话框应用程序。我使用由 Andy Smith 开发的一些等待函数来解决使用模式对话框时出现的阻塞问题。您真的需要检查位于 SendRequest 中的主要 WinINet 循环。在这里,您将学习如何处理 ERROR_INTERNET_INVALID_CAERROR_INTERNET_CLIENT_AUTH_CERT_NEEDEDHTTP_STATUS_DENIEDWinInetCallback 方法用于提供信息,例如个人证书或用户名和密码,以便重新提交给 Web 服务器,并通过上下文变量将通信错误传递给 SendRequest 中的 WinINet 循环。

获取指向个人证书的指针

有时您可能需要允许用户选择个人证书。例如,您正在编写一个使用 WinINet 的应用程序。以下是一些简单的代码,允许用户从其证书存储区中加载的个人证书列表中进行选择

#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;
}

在 WinINet 句柄上提供个人证书

TCP/IP 通信有点棘手,因为您无法在发出请求时提供证书。您需要调用 OpenRequest,然后等待您的回调例程中的 INTERNET_STATUS_REQUEST_COMPLETE 信号。然后,您可以检查返回的错误,查找 INTERNET_ASYNC_RESULT 结构中的 ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED。当您收到错误时,您需要在 OpenRequest 句柄上使用 InternetSetOption 来提供证书,然后重复 OpenRequest。这真的很简单。只是文档和示例省略了您需要的大量错误检查和恢复工作。拥有一个带有有效 SSL 证书并需要个人证书才能连接的服务器设置会有所帮助。以下是处理 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)
...
    }

未来计划

我将在将来的修订版中添加对代理服务器的身份验证处理。目前,只能提供一组凭据,但该程序需要允许两组凭据,一组用于代理服务器,一组用于 Web 服务器。我还想添加一个文件附件对话框,用于测试 POST 方法以及二进制数据或伪表单数据附件。

历史

  • 版本 1.0 - 首次发布。
© . All rights reserved.