基于WinInet的HTTPS文件下载实现

Coquettish

发布日期: 2018-12-22 10:15:55 浏览量: 1674
评分:
star star star star star star star star star_border star_border
*转载请注明来自write-bug.com

背景

如果你之前写过基于WinInet库的HTTP下载文件,那么你在看完本文之后,就会发觉,这是和HTTP文件下载的代码几乎是一模一样的,就是有几个地方的区别而已。但是,本文不是对HTTP和HTTPS在WinInet库中的区别进行总结的,总结就另外写。

本文就是基于WinInet网络库,实现通过HTTPS传输协议下载文件功能的小程序。现在,就把开发过程的思路和编程分享给大家。

主要函数介绍

介绍HTTPS下载文件使用到的主要的WinInet库中的API函数。

1. InternetOpen介绍

函数声明

  1. HINTERNET InternetOpen(
  2. In LPCTSTR lpszAgent,
  3. In DWORD dwAccessType,
  4. In LPCTSTR lpszProxyName,
  5. In LPCTSTR lpszProxyBypass,
  6. In DWORD dwFlags
  7. );

参数
lpszAgent
指向一个空结束的字符串,该字符串指定调用WinInet函数的应用程序或实体的名称。使用此名称作为用户代理的HTTP协议。
dwAccessType
指定访问类型,参数可以是下列值之一:

Value Meaning
INTERNET_OPEN_TYPE_DIRECT 使用直接连接网络
INTERNET_OPEN_TYPE_PRECONFIG 获取代理或直接从注册表中的配置,使用代理连接网络
INTERNETOPEN_TYPE_PRECONFIG WITH_NO_AUTOPROXY 获取代理或直接从注册表中的配置,并防止启动Microsoft JScript或Internet设置(INS)文件的使用
INTERNET_OPEN_TYPE_PROXY 通过代理的请求,除非代理旁路列表中提供的名称解析绕过代理,在这种情况下,该功能的使用

lpszProxyName
指针指向一个空结束的字符串,该字符串指定的代理服务器的名称,不要使用空字符串;如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY,则此参数应该设置为NULL。

lpszProxyBypass
指向一个空结束的字符串,该字符串指定的可选列表的主机名或IP地址。如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY的 ,参数省略则为NULL。

dwFlags
参数可以是下列值的组合:

VALUE MEANING
INTERNET_FLAG_ASYNC 使异步请求处理的后裔从这个函数返回的句柄
INTERNET_FLAG_FROM_CACHE 不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND
INTERNET_FLAG_OFFLINE 不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND

返回值
成功:返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinInet函数。
失败:返回NULL。

2. InternetConnect介绍

函数声明

  1. HINTERNET WINAPI InternetConnect(
  2. HINTERNET hInternet,
  3. LPCTSTR lpszServerName,
  4. INTERNET_PORT nServerPort,
  5. LPCTSTR lpszUserName,
  6. LPCTSTR lpszPassword,
  7. DWORD dwService,
  8. DWORD dwFlags,
  9. DWORD dwContext
  10. );

参数说明
hInternet:由InternetOpen返回的句柄。
lpszServerName:连接的ip或者主机名
nServerPort:连接的端口。
lpszUserName:用户名,如无置NULL。
lpszPassword:密码,如无置NULL。
dwService:使用的服务类型,可以使用以下

  • INTERNET_SERVICE_FTP = 1:连接到一个 FTP 服务器上
  • INTERNET_SERVICE_GOPHER = 2
  • INTERNET_SERVICE_HTTP = 3:连接到一个 HTTP 服务器上

dwFlags:文档传输形式及缓存标记。一般置0。
dwContext:当使用回叫信号时, 用来识别应用程序的前后关系。
返回值
成功返回非0。如果返回0。要InternetCloseHandle释放这个句柄。

3. HttpOpenRequest介绍

函数声明

  1. HINTERNET HttpOpenRequest(
  2. _In_ HINTERNET hConnect,
  3. _In_ LPCTSTR lpszVerb,
  4. _In_ LPCTSTR lpszObjectName,
  5. _In_ LPCTSTR lpszVersion,
  6. _In_ LPCTSTR lpszReferer,
  7. _In_ LPCTSTR *lplpszAcceptTypes,
  8. _In_ DWORD dwFlags,
  9. _In_ DWORD_PTR dwContext
  10. );

参数

hConnect:由InternetConnect返回的句柄。

lpszVerb:一个指向某个包含在请求中要用的动词的字符串指针。如果为NULL,则使用“GET”。

lpszObjectName:一个指向某个包含特殊动词的目标对象的字符串的指针。通常为文件名称、可执行模块或者查找标识符。

lpszVersion:一个指向以null结尾的字符串的指针,该字符串包含在请求中使用的HTTP版本,Internet Explorer中的设置将覆盖该参数中指定的值。如果此参数为NULL,则该函数使用1.1或1.0的HTTP版本,这取决于Internet Explorer设置的值。

lpszReferer:一个指向指定了包含着所需的URL (pstrObjectName)的文档地址(URL)的指针。如果为NULL,则不指定HTTP头。

lplpszAcceptTypes:一个指向某空终止符的字符串的指针,该字符串表示客户接受的内容类型。如果该字符串为NULL,服务器认为客户接受“text/*”类型的文档 (也就是说,只有纯文本文档,并且不是图片或其它二进制文件)。内容类型与CGI变量CONTENT_TYPE相同,该变量确定了要查询的含有相关信息的数据的类型,如HTTP POST和PUT。

dwFlags:dwFlags的值可以是下面一个或者多个。

价值 说明
INTERNET_FLAG_DONT_CACHE 不缓存的数据,在本地或在任何网关。 相同的首选值INTERNET_FLAG_NO_CACHE_WRITE。
INTERNET_FLAG_EXISTING_CONNECT 如果可能的话,重用现有的连接到每个服务器请求新的请求而产生的InternetOpenUrl创建一个新的会话。 这个标志是有用的,只有对FTP连接,因为FTP是唯一的协议,通常在同一会议执行多个操作。 在Win 32 API的缓存一个单一的Internet连接句柄为每个HINTERNET处理产生的InternetOpen。
INTERNET_FLAG -超链接 强制重载如果没有到期的时间也没有最后修改时间从服务器在决定是否加载该项目从网络返回。
INTERNET_FLAG_IGNORE_CERT_CN_INVALID 禁用的Win32上网功能的SSL /厘为基础的打击是从给定的请求服务器返回的主机名称证书检查。 Win32的上网功能用来对付证书由匹配主机名和HTTP请求一个简单的通配符规则比较简单的检查。
INTERNET_FLAG_IGNORE_CERT_DATE_INVALID 禁用的Win32上网功能的SSL /厘为基础的HTTP请求适当的日期,证书的有效性检查。
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP 禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许对HTTP重定向的URL从HTTPS。
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS 禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许从HTTP重定向到HTTPS网址。
INTERNET_FLAG_KEEP_CONNECTION 使用保持活动语义,如果有的话,给HTTP请求连接。 这个标志是必需的微软网络(MSN),NT LAN管理器(NTLM)和其他类型的身份验证。
INTERNET_FLAG_MAKE_PERSISTENT 不再支持。
INTERNET_FLAG_MUST_CACHE_REQUEST 导致一个临时文件如果要创建的文件不能被缓存。 相同的首选值INTERNET_FLAG_NEED_FILE。
INTERNET_FLAG_NEED_FILE 导致一个临时文件如果要创建的文件不能被缓存。
INTERNET_FLAG_NO_AUTH 不尝试HTTP请求身份验证自动。
INTERNET_FLAG_NO_AUTO_REDIRECT 不自动处理HTTP请求重定向只。
INTERNET_FLAG_NO_CACHE_WRITE 不缓存的数据,在本地或在任何网关。
INTERNET_FLAG_NO_COOKIES 不会自动添加Cookie标头的请求,并不会自动添加返回的Cookie的HTTP请求的Cookie数据库。
INTERNET_FLAG_NO_UI 禁用cookie的对话框。
INTERNET_FLAG_PASSIVE 使用被动FTP语义FTP文件和目录。
INTERNET_FLAG_RAW_DATA 返回一个数据WIN32_FIND_DATA结构时,FTP目录检索信息。 如果这个标志,或者未指定代理的电话是通过一个CERN,InternetOpenUrl返回的HTML版本的目录。
INTERNET_FLAG_PRAGMA_NOCACHE 强制要求被解决的原始服务器,即使在代理缓存的副本存在。
INTERNET_FLAG_READ_PREFETCH 该标志目前已停用。
INTERNET_FLAG_RELOAD 从导线获取数据,即使是一个本地缓存。
INTERNET_FLAG_RESYNCHRONIZE 重整HTTP资源,如果资源已被修改自上一次被下载。 所有的FTP资源增值。
INTERNET_FLAG_SECURE 请确保在使用SSL或PCT线交易。 此标志仅适用于HTTP请求。

dwContext:OpenRequest操作的上下文标识符。

4. InternetReadFile介绍

函数声明

  1. BOOL InternetReadFile( __in HINTERNET hFile,
  2. __out LPVOID lpBuffer,
  3. __in DWORD dwNumberOfBytesToRead,
  4. __out LPDWORD lpdwNumberOfBytesRead
  5. );

参数

  • hFile[in]

    由InternetOpenUrl,FtpOpenFile, 或HttpOpenRequest函数返回的句柄.

  • lpBuffer[out]

    缓冲器指针

  • dwNumberOfBytesToRead[in]

    欲读数据的字节量。

  • lpdwNumberOfBytesRead[out]

    接收读取字节量的变量。该函数在做任何工作或错误检查之前都设置该值为零

返回值
成功:返回TRUE,失败,返回FALSE

程序设计原理

该部分讲解下程序设计的原理以及实现的流程,让大家有个宏观的认识。原理是:

  • 首先,使用 InternetCrackUrl 函数分解URL,从URL中提取网站的域名、路径以及URL的附加信息等。关于 InternetCrackUrl 分解URL的介绍和实现,可以参考 “URL分解之InternetCrackUrl” 这篇文章

  • 使用 InternetOpen 建立会话,获取会话句柄

  • 使用 InternetConnect 与网站建立连接,获取连接句柄

  • 设置HTTPS的访问标志,使用 HttpOpenRequest 打开HTTP的“GET”请求

  • 使用 HttpSendRequest 发送访问请求,同时根据出错返回的错误码,来判断是否设置忽略未知的证书颁发机构,以确保能正常访问HTTPS网站

  • 根据返回的Response Header的数据中,获取将要接收数据的长度

  • 使用 InternetReadFile 接收数据

  • 关闭句柄,释放资源

其中,上面的 8 个步骤中,要注意第 4 步访问标志的标志设置;注意第 5 步的忽略未知的证书颁发机构的设置;同时,还要注意的就是第 6 步,获取返回的数据长度,是从响应信息头中的获取“Content-Length: ”(注意有个空格)这个字段的数据。

编程实现

1. 导入WinInet库

  1. #include <WinInet.h>
  2. #pragma comment(lib, "WinInet.lib")

2. HTTPS文件下载编程实现

  1. // 数据下载
  2. // 输入:下载数据的URL路径
  3. // 输出:下载数据内容、下载数据内容长度
  4. BOOL Https_Download(char *pszDownloadUrl, BYTE **ppDownloadData, DWORD *pdwDownloadDataSize)
  5. {
  6. // INTERNET_SCHEME_HTTPS、INTERNET_SCHEME_HTTP、INTERNET_SCHEME_FTP等
  7. char szScheme[MAX_PATH] = {0};
  8. char szHostName[MAX_PATH] = { 0 };
  9. char szUserName[MAX_PATH] = { 0 };
  10. char szPassword[MAX_PATH] = { 0 };
  11. char szUrlPath[MAX_PATH] = { 0 };
  12. char szExtraInfo[MAX_PATH] = { 0 };
  13. ::RtlZeroMemory(szScheme, MAX_PATH);
  14. ::RtlZeroMemory(szHostName, MAX_PATH);
  15. ::RtlZeroMemory(szUserName, MAX_PATH);
  16. ::RtlZeroMemory(szPassword, MAX_PATH);
  17. ::RtlZeroMemory(szUrlPath, MAX_PATH);
  18. ::RtlZeroMemory(szExtraInfo, MAX_PATH);
  19. // 分解URL
  20. if (FALSE == Https_UrlCrack(pszDownloadUrl, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, MAX_PATH))
  21. {
  22. return FALSE;
  23. }
  24. // 数据下载
  25. HINTERNET hInternet = NULL;
  26. HINTERNET hConnect = NULL;
  27. HINTERNET hRequest = NULL;
  28. DWORD dwOpenRequestFlags = 0;
  29. BOOL bRet = FALSE;
  30. unsigned char *pResponseHeaderIInfo = NULL;
  31. DWORD dwResponseHeaderIInfoSize = 2048;
  32. BYTE *pBuf = NULL;
  33. DWORD dwBufSize = 64*1024;
  34. BYTE *pDownloadData = NULL;
  35. DWORD dwDownloadDataSize = 0;
  36. DWORD dwRet = 0;
  37. DWORD dwOffset = 0;
  38. do
  39. {
  40. // 建立会话
  41. hInternet = ::InternetOpen("WinInetGet/0.1", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
  42. if (NULL == hInternet)
  43. {
  44. Https_ShowError("InternetOpen");
  45. break;
  46. }
  47. // 建立连接(与HTTP的区别 -- 端口)
  48. hConnect = ::InternetConnect(hInternet, szHostName, INTERNET_DEFAULT_HTTPS_PORT, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0);
  49. if (NULL == hConnect)
  50. {
  51. Https_ShowError("InternetConnect");
  52. break;
  53. }
  54. // 打开并发送HTTPS请求(与HTTP的区别--标志)
  55. dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |
  56. INTERNET_FLAG_KEEP_CONNECTION |
  57. INTERNET_FLAG_NO_AUTH |
  58. INTERNET_FLAG_NO_COOKIES |
  59. INTERNET_FLAG_NO_UI |
  60. // HTTPS SETTING
  61. INTERNET_FLAG_SECURE |
  62. INTERNET_FLAG_IGNORE_CERT_CN_INVALID |
  63. INTERNET_FLAG_RELOAD;
  64. if (0 < ::lstrlen(szExtraInfo))
  65. {
  66. // 注意此处的连接
  67. ::lstrcat(szUrlPath, szExtraInfo);
  68. }
  69. hRequest = ::HttpOpenRequest(hConnect, "GET", szUrlPath, NULL, NULL, NULL, dwOpenRequestFlags, 0);
  70. if (NULL == hRequest)
  71. {
  72. Https_ShowError("HttpOpenRequest");
  73. break;
  74. }
  75. // 发送请求(与HTTP的区别--对无效的证书颁发机构的处理)
  76. bRet = ::HttpSendRequest(hRequest, NULL, 0, NULL, 0);
  77. if (FALSE == bRet)
  78. {
  79. if (ERROR_INTERNET_INVALID_CA == ::GetLastError())
  80. {
  81. DWORD dwFlags = 0;
  82. DWORD dwBufferSize = sizeof(dwFlags);
  83. // 获取INTERNET_OPTION_SECURITY_FLAGS标志
  84. bRet = ::InternetQueryOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, &dwBufferSize);
  85. if (bRet)
  86. {
  87. // 设置INTERNET_OPTION_SECURITY_FLAGS标志
  88. // 忽略未知的证书颁发机构
  89. dwFlags = dwFlags | SECURITY_FLAG_IGNORE_UNKNOWN_CA;
  90. bRet = ::InternetSetOption(hRequest, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags));
  91. if (bRet)
  92. {
  93. // // 再次发送请求
  94. bRet = ::HttpSendRequest(hRequest, NULL, 0, NULL, 0);
  95. if (FALSE == bRet)
  96. {
  97. Https_ShowError("HttpSendRequest");
  98. break;
  99. }
  100. }
  101. else
  102. {
  103. Https_ShowError("InternetSetOption");
  104. break;
  105. }
  106. }
  107. else
  108. {
  109. Https_ShowError("InternetQueryOption");
  110. break;
  111. }
  112. }
  113. else
  114. {
  115. Https_ShowError("HttpSendRequest");
  116. break;
  117. }
  118. }
  119. // 接收响应的报文信息头(Get Response Header)
  120. pResponseHeaderIInfo = new unsigned char[dwResponseHeaderIInfoSize];
  121. if (NULL == pResponseHeaderIInfo)
  122. {
  123. break;
  124. }
  125. ::RtlZeroMemory(pResponseHeaderIInfo, dwResponseHeaderIInfoSize);
  126. bRet = ::HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, pResponseHeaderIInfo, &dwResponseHeaderIInfoSize, NULL);
  127. if (FALSE == bRet)
  128. {
  129. Https_ShowError("HttpQueryInfo");
  130. break;
  131. }
  132. #ifdef _DEBUG
  133. printf("[HTTPS_Download_ResponseHeaderIInfo]\n\n%s\n\n", pResponseHeaderIInfo);
  134. #endif
  135. // 从 中字段 "Content-Length: "(注意有个空格) 获取数据长度
  136. bRet = Https_GetContentLength((char *)pResponseHeaderIInfo, &dwDownloadDataSize);
  137. if (FALSE == bRet)
  138. {
  139. break;
  140. }
  141. // 接收报文主体内容(Get Response Body)
  142. pBuf = new BYTE[dwBufSize];
  143. if (NULL == pBuf)
  144. {
  145. break;
  146. }
  147. pDownloadData = new BYTE[dwDownloadDataSize];
  148. if (NULL == pDownloadData)
  149. {
  150. break;
  151. }
  152. ::RtlZeroMemory(pDownloadData, dwDownloadDataSize);
  153. do
  154. {
  155. ::RtlZeroMemory(pBuf, dwBufSize);
  156. bRet = ::InternetReadFile(hRequest, pBuf, dwBufSize, &dwRet);
  157. if (FALSE == bRet)
  158. {
  159. Https_ShowError("InternetReadFile");
  160. break;
  161. }
  162. ::RtlCopyMemory((pDownloadData + dwOffset), pBuf, dwRet);
  163. dwOffset = dwOffset + dwRet;
  164. } while (dwDownloadDataSize > dwOffset);
  165. // 返回数据
  166. *ppDownloadData = pDownloadData;
  167. *pdwDownloadDataSize = dwDownloadDataSize;
  168. } while (FALSE);
  169. // 关闭 释放
  170. if (NULL != pBuf)
  171. {
  172. delete[]pBuf;
  173. pBuf = NULL;
  174. }
  175. if (NULL != pResponseHeaderIInfo)
  176. {
  177. delete[]pResponseHeaderIInfo;
  178. pResponseHeaderIInfo = NULL;
  179. }
  180. if (NULL != hRequest)
  181. {
  182. ::InternetCloseHandle(hRequest);
  183. hRequest = NULL;
  184. }
  185. if (NULL != hConnect)
  186. {
  187. ::InternetCloseHandle(hConnect);
  188. hConnect = NULL;
  189. }
  190. if (NULL != hInternet)
  191. {
  192. ::InternetCloseHandle(hInternet);
  193. hInternet = NULL;
  194. }
  195. return bRet;
  196. }

程序测试

在main函数中,调用上述封装好的函数,下载文件进行测试。

main函数为:

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. char szHttpsDownloadUrl[] = "https://download.microsoft.com/download/0/2/3/02389126-40A7-46FD-9D83-802454852703/vc_mbcsmfc.exe";
  4. BYTE *pHttpsDownloadData = NULL;
  5. DWORD dwHttpsDownloadDataSize = 0;
  6. // HTTPS下载
  7. if (FALSE == Https_Download(szHttpsDownloadUrl, &pHttpsDownloadData, &dwHttpsDownloadDataSize))
  8. {
  9. return 1;
  10. }
  11. // 将数据保存成文件
  12. Https_SaveToFile("https_downloadsavefile.exe", pHttpsDownloadData, dwHttpsDownloadDataSize);
  13. // 释放内存
  14. delete []pHttpsDownloadData;
  15. pHttpsDownloadData = NULL;
  16. system("pause");
  17. return 0;
  18. }

测试结果:

根据返回的Response Header知道,成功下载67453208字节大小的数据。

查看目录,有65873KB大小的“https_downloadsavefile.zip”文件成功生成,所以,数据下载成功。

总结

基于 WinInet 库的 HTTPS 下载文件原理并不复杂,但是,因为涉及较多的 API,每个 API 的执行都需要依靠上一个 API 成功执行返回的数据。所以,要仔细检查。如果出错,也要耐心调试,根据返回的错误码,结合程序前后部分的代码,仔细分析原因。

同时要注意在使用 HttpOpenRequest 和 HttpSendRequest 函数中,对 HTTPS 的标志设置以及忽略未知的证书颁发机构。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download HTTPS_Download_Test.7z ( 151.03kb, 30次下载 )

发送私信

童心未泯,是一件值得骄傲的事情

29
文章数
19
评论数
最近文章
eject