CnInetUtils.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. {******************************************************************************}
  2. { CnPack For Delphi/C++Builder }
  3. { 中国人自己的开放源码第三方开发包 }
  4. { (C)Copyright 2001-2018 CnPack 开发组 }
  5. { ------------------------------------ }
  6. { }
  7. { 本开发包是开源的自由软件,您可以遵照 CnPack 的发布协议来修 }
  8. { 改和重新发布这一程序。 }
  9. { }
  10. { 发布这一开发包的目的是希望它有用,但没有任何担保。甚至没有 }
  11. { 适合特定目的而隐含的担保。更详细的情况请参阅 CnPack 发布协议。 }
  12. { }
  13. { 您应该已经和开发包一起收到一份 CnPack 发布协议的副本。如果 }
  14. { 还没有,可访问我们的网站: }
  15. { }
  16. { 网站地址:http://www.cnpack.org }
  17. { 电子邮件:master@cnpack.org }
  18. { }
  19. {******************************************************************************}
  20. unit CnInetUtils;
  21. {* |<PRE>
  22. ================================================================================
  23. * 软件名称:网络通讯组件包
  24. * 单元名称:使WinInet 封装单元
  25. * 单元作者:周劲羽 (zjy@cnpack.org)
  26. * 备 注:定义了 TCnHTTP,使用 WinInet 来读取 HTTP 数据
  27. * 开发平台:PWin2000Pro + Delphi 5.01
  28. * 兼容测试:PWin9X/2000/XP + Delphi 5/6/7 + C++Builder 5/6
  29. * 本 地 化:该单元中的字符串均符合本地化处理方式
  30. * 单元标识:$Id$
  31. * 修改记录:2005.09.14 V1.1
  32. * 增加 UserAgent 和 Proxy 设置(由 illk 提供)
  33. * 2003.03.09 V1.0
  34. * 创建单元
  35. ================================================================================
  36. |</PRE>}
  37. interface
  38. {$I CnPack.inc}
  39. uses
  40. Windows, SysUtils, Classes, WinInet;
  41. type
  42. //==============================================================================
  43. // 使用 WinInet 读取 HTTP 文件的类
  44. //==============================================================================
  45. { TCnInet }
  46. TCnInetProgressEvent = procedure (Sender: TObject; TotalSize, CurrSize: Integer;
  47. var Abort: Boolean) of object;
  48. {* 数据下载进度事件
  49. |<PRE>
  50. Sender - 线程对象
  51. TotalSize - 总字节数,如果为 -1,表示长度未知
  52. CurrSize - 当前完成字节数
  53. Abort - 是否中断
  54. |</PRE>}
  55. TCnURLInfo = record
  56. Protocol: string;
  57. Host: string;
  58. Port: string;
  59. PathName: string;
  60. Username: string;
  61. Password: string;
  62. end;
  63. TCnInetProxyMode = (pmDirect, pmIE, pmProxy);
  64. {* 使用代理的方式:直连、IE设置、指定代理 }
  65. TCnInet = class
  66. {* 使用 WinInet 读取 HTTP(S)/FTP 文件的类。}
  67. private
  68. hSession: HINTERNET;
  69. FAborted: Boolean;
  70. FGetDataFail: Boolean;
  71. FOnProgress: TCnInetProgressEvent;
  72. FUserAgent: string;
  73. FDecoding: Boolean;
  74. FDecodingValid: Boolean;
  75. FProxyServer: string;
  76. FProxyUserName: string;
  77. FProxyPassWord: string;
  78. FHttpRequestHeaders: TStringList;
  79. FSendTimeOut: Cardinal;
  80. FConnectTimeOut: Cardinal;
  81. FReceiveTimeOut: Cardinal;
  82. FProxyMode: TCnInetProxyMode;
  83. FNoCookie: Boolean;
  84. FEncodeUrlPath: Boolean;
  85. function ParseURL(URL: string; var Info: TCnURLInfo): Boolean;
  86. protected
  87. procedure DoProgress(TotalSize, CurrSize: Integer);
  88. function InitInet: Boolean;
  89. procedure CloseInet;
  90. function GetStreamFromHandle(Handle: HINTERNET; TotalSize: Integer;
  91. Stream: TStream): Boolean;
  92. function GetHTTPStream(Info: TCnURLInfo; Stream: TStream; APost: TStrings): Boolean;
  93. function GetFTPStream(Info: TCnURLInfo; Stream: TStream): Boolean;
  94. public
  95. constructor Create;
  96. destructor Destroy; override;
  97. procedure Abort;
  98. {* 中断当前处理}
  99. function GetStream(const AURL: string; Stream: TStream; APost: TStrings = nil): Boolean;
  100. {* 从 AURL 地址读取数据到流 Stream,如果 APost 不为 nil 则执行 Post 调用}
  101. function GetString(const AURL: string; APost: TStrings = nil): AnsiString;
  102. {* 从 AURL 地址返回一个字符串,如果 APost 不为 nil 则执行 Post 调用}
  103. function GetFile(const AURL, FileName: string; APost: TStrings = nil): Boolean;
  104. {* 从 AURL 地址读取数据保存到文件 FileName,如果 APost 不为 nil 则执行 Post 调用}
  105. property OnProgress: TCnInetProgressEvent read FOnProgress write FOnProgress;
  106. {* 数据进度事件}
  107. property Aborted: Boolean read FAborted;
  108. {* 是否已被中断}
  109. property GetDataFail: Boolean read FGetDataFail;
  110. {* 上一次的数据读取是否成功}
  111. property Decoding: Boolean read FDecoding write FDecoding default True;
  112. {* 是否支持 gzip, deflate 解压}
  113. property UserAgent: string read FUserAgent write FUserAgent;
  114. {* 设置UserAgent 浏览器识别标示}
  115. property ProxyMode: TCnInetProxyMode read FProxyMode write FProxyMode;
  116. {* 使用代理的方式}
  117. property ProxyServer: string read FProxyServer write FProxyServer;
  118. {* 代理服务器设置: [协议=][协议://]服务器[:端口] 如 127.0.0.1:8080}
  119. property ProxyUserName: string read FProxyUserName write FProxyUserName;
  120. {* 代理服务器用户名}
  121. property ProxyPassWord: string read FProxyPassWord write FProxyPassWord;
  122. {* 代理服务器用户密码}
  123. property HttpRequestHeaders: TStringList read FHttpRequestHeaders;
  124. {* 请求信息头}
  125. property NoCookie: Boolean read FNoCookie write FNoCookie;
  126. {* 是否不使用 Cookie,如果需要在 HttpRequestHeaders 中指定 Cookie,应设为 True}
  127. property EncodeUrlPath: Boolean read FEncodeUrlPath write FEncodeUrlPath default True;
  128. {* 是否自动为 Url 路径中的特殊字符编码}
  129. property ConnectTimeOut: Cardinal read FConnectTimeOut write FConnectTimeOut;
  130. {* 连接超时}
  131. property SendTimeOut: Cardinal read FSendTimeOut write FSendTimeOut;
  132. {* 发送超时}
  133. property ReceiveTimeOut: Cardinal read FReceiveTimeOut write FReceiveTimeOut;
  134. {* 接收超时}
  135. end;
  136. TCnHTTP = class(TCnInet);
  137. TCnFTP = class(TCnInet);
  138. function EncodeURL(const URL: string): string;
  139. {* 将 URL 中的特殊字符转换成 %XX 的形式}
  140. function CnInet_GetStream(const AURL: string; Stream: TStream; APost: TStrings = nil): Boolean;
  141. function CnInet_GetString(const AURL: string; APost: TStrings = nil): AnsiString;
  142. function CnInet_GetFile(const AURL, FileName: string; APost: TStrings = nil): Boolean;
  143. implementation
  144. const
  145. csBufferSize = 4096;
  146. INTERNET_OPTION_HTTP_DECODING = 65;
  147. SAcceptEncoding = 'Accept-Encoding: gzip,deflate';
  148. function EncodeURL(const URL: string): string;
  149. const
  150. UnsafeChars = ['*', '#', '%', '<', '>', '+', ' '];
  151. var
  152. i: Integer;
  153. InStr, OutStr: AnsiString;
  154. begin
  155. InStr := AnsiString(URL);
  156. OutStr := '';
  157. for i := 1 to Length(InStr) do begin
  158. if (InStr[i] in UnsafeChars) or (InStr[i] >= #$80) or (InStr[i] < #32) then
  159. OutStr := OutStr + '%' + AnsiString(IntToHex(Ord(InStr[i]), 2))
  160. else
  161. OutStr := OutStr + InStr[i];
  162. end;
  163. Result := string(OutStr);
  164. end;
  165. function CnInet_GetStream(const AURL: string; Stream: TStream; APost: TStrings): Boolean;
  166. begin
  167. with TCnInet.Create do
  168. try
  169. Result := GetStream(AURL, Stream, APost);
  170. finally
  171. Free;
  172. end;
  173. end;
  174. function CnInet_GetString(const AURL: string; APost: TStrings): AnsiString;
  175. begin
  176. with TCnInet.Create do
  177. try
  178. Result := GetString(AURL, APost);
  179. finally
  180. Free;
  181. end;
  182. end;
  183. function CnInet_GetFile(const AURL, FileName: string; APost: TStrings): Boolean;
  184. begin
  185. with TCnInet.Create do
  186. try
  187. Result := GetFile(AURL, FileName, APost);
  188. finally
  189. Free;
  190. end;
  191. end;
  192. //==============================================================================
  193. // 使用 WinInet 读取 HTTP 文件的类
  194. //==============================================================================
  195. { TCnInet }
  196. constructor TCnInet.Create;
  197. begin
  198. inherited;
  199. FDecoding := True;
  200. FUserAgent := 'CnPack Internet Utils';
  201. FHttpRequestHeaders := TStringList.Create;
  202. FProxyMode := pmIE;
  203. end;
  204. destructor TCnInet.Destroy;
  205. begin
  206. CloseInet;
  207. FHttpRequestHeaders.Free;
  208. inherited;
  209. end;
  210. procedure TCnInet.CloseInet;
  211. begin
  212. if hSession <> nil then
  213. begin
  214. InternetCloseHandle(hSession);
  215. hSession := nil;
  216. end;
  217. end;
  218. function TCnInet.InitInet: Boolean;
  219. var
  220. Flag: LongBool;
  221. begin
  222. if hSession = nil then
  223. begin
  224. if (FProxyMode <> pmProxy) or (Length(FProxyServer) = 0) then
  225. begin
  226. if FProxyMode = pmDirect then
  227. hSession := InternetOpen(PChar(FUserAgent), INTERNET_OPEN_TYPE_DIRECT,
  228. nil, nil, 0)
  229. else
  230. hSession := InternetOpen(PChar(FUserAgent), INTERNET_OPEN_TYPE_PRECONFIG,
  231. nil, nil, 0);
  232. end
  233. else
  234. begin
  235. hSession := InternetOpen(PChar(FUserAgent), INTERNET_OPEN_TYPE_PROXY,
  236. PChar(FProxyServer), nil, 0);
  237. if Length(FProxyUserName) > 0 then
  238. InternetSetOption(hSession, INTERNET_OPTION_PROXY_USERNAME, PChar(FProxyUserName), Length(FProxyUserName));
  239. if Length(FProxyPassWord) > 0 then
  240. InternetSetOption(hSession, INTERNET_OPTION_PROXY_PASSWORD, PChar(FProxyPassWord), Length(FProxyPassWord));
  241. if FConnectTimeOut <> 0 then
  242. InternetSetOption(hSession, INTERNET_OPTION_CONNECT_TIMEOUT, @FConnectTimeOut, SizeOf(Cardinal));
  243. if FSendTimeOut <> 0 then
  244. InternetSetOption(hSession, INTERNET_OPTION_SEND_TIMEOUT, @FSendTimeOut, SizeOf(Cardinal));
  245. if FReceiveTimeOut <> 0 then
  246. InternetSetOption(hSession, INTERNET_OPTION_RECEIVE_TIMEOUT, @FReceiveTimeOut, SizeOf(Cardinal));
  247. end;
  248. if FDecoding then
  249. begin
  250. Flag := True;
  251. FDecodingValid := InternetSetOption(hSession, INTERNET_OPTION_HTTP_DECODING, PChar(@Flag), SizeOf(Flag));
  252. end;
  253. end;
  254. Result := hSession <> nil;
  255. end;
  256. procedure TCnInet.Abort;
  257. begin
  258. FAborted := True;
  259. end;
  260. procedure TCnInet.DoProgress(TotalSize, CurrSize: Integer);
  261. begin
  262. if Assigned(FOnProgress) then
  263. FOnProgress(Self, TotalSize, CurrSize, FAborted);
  264. end;
  265. function TCnInet.ParseURL(URL: string; var Info: TCnURLInfo): Boolean;
  266. var
  267. Idx: Integer;
  268. Buff: string;
  269. function ExtractStr(var ASrc: string; ADelim: string;
  270. ADelete: Boolean = True): string;
  271. var
  272. Idx: Integer;
  273. begin
  274. Idx := Pos(ADelim, ASrc);
  275. if Idx = 0 then
  276. begin
  277. Result := ASrc;
  278. if ADelete then
  279. ASrc := '';
  280. end
  281. else
  282. begin
  283. Result := Copy(ASrc, 1, Idx - 1);
  284. if ADelete then
  285. ASrc := Copy(ASrc, Idx + Length(ADelim), MaxInt);
  286. end;
  287. end;
  288. begin
  289. Result := False;
  290. URL := Trim(URL);
  291. Idx := Pos('://', URL);
  292. if Idx > 0 then
  293. begin
  294. Info.Protocol := Copy(URL, 1, Idx - 1);
  295. Delete(URL, 1, Idx + 2);
  296. if URL = '' then Exit;
  297. Buff := ExtractStr(URL, '/');
  298. Idx := Pos('@', Buff);
  299. Info.Password := Copy(Buff, 1, Idx - 1);
  300. if Idx > 0 then Delete(Buff, 1, Idx);
  301. Info.UserName := ExtractStr(Info.Password, ':');
  302. if Length(Info.UserName) = 0 then
  303. Info.Password := '';
  304. Info.Host := ExtractStr(Buff, ':');
  305. Info.Port := Buff;
  306. Info.PathName := URL;
  307. Result := True;
  308. end;
  309. end;
  310. function TCnInet.GetStream(const AURL: string; Stream: TStream; APost: TStrings = nil): Boolean;
  311. var
  312. Info: TCnURLInfo;
  313. begin
  314. Result := False;
  315. if not ParseURL(AURL, Info) then
  316. Exit;
  317. FAborted := False;
  318. if not InitInet or FAborted then
  319. Exit;
  320. if SameText(Info.Protocol, 'http') or SameText(Info.Protocol, 'https') then
  321. Result := GetHTTPStream(Info, Stream, APost)
  322. else if SameText(Info.Protocol, 'ftp') then
  323. Result := GetFTPStream(Info, Stream);
  324. if FAborted then
  325. Result := False;
  326. FGetDataFail := not Result;
  327. end;
  328. function TCnInet.GetStreamFromHandle(Handle: HINTERNET; TotalSize: Integer;
  329. Stream: TStream): Boolean;
  330. var
  331. CurrSize, Readed: Cardinal;
  332. Buf: array[0..csBufferSize - 1] of Byte;
  333. begin
  334. Result := False;
  335. CurrSize := 0;
  336. Readed := 0;
  337. repeat
  338. if not InternetReadFile(Handle, @Buf, csBufferSize, Readed) then
  339. Exit;
  340. if Readed > 0 then
  341. begin
  342. Stream.Write(Buf, Readed);
  343. Inc(CurrSize, Readed);
  344. DoProgress(TotalSize, CurrSize);
  345. if Aborted then Exit;
  346. end;
  347. until Readed = 0;
  348. Result := True;
  349. end;
  350. function TCnInet.GetFTPStream(Info: TCnURLInfo; Stream: TStream): Boolean;
  351. var
  352. hConnect, hFtp: HINTERNET;
  353. FindData: TWin32FindData;
  354. TotalSize: Integer;
  355. begin
  356. Result := False;
  357. hConnect := nil;
  358. hFtp := nil;
  359. try
  360. hConnect := InternetConnect(hSession, PChar(Info.Host),
  361. StrToIntDef(Info.Port, INTERNET_DEFAULT_FTP_PORT),
  362. PChar(Info.Username), PChar(Info.Password),
  363. INTERNET_SERVICE_FTP, 0, 0);
  364. if (hConnect = nil) or FAborted then
  365. Exit;
  366. hFtp := FtpFindFirstFile(hConnect, PChar(Info.PathName), FindData,
  367. INTERNET_FLAG_NEED_FILE, 0);
  368. if hFtp <> nil then
  369. begin
  370. InternetCloseHandle(hFtp);
  371. TotalSize := FindData.nFileSizeLow;
  372. end
  373. else
  374. TotalSize := -1;
  375. hFtp := FtpOpenFile(hConnect, PChar(Info.PathName), GENERIC_READ,
  376. FTP_TRANSFER_TYPE_BINARY, 0);
  377. if (hFtp = nil) or FAborted then
  378. Exit;
  379. Result := GetStreamFromHandle(hFtp, TotalSize, Stream);
  380. finally
  381. if hFtp <> nil then InternetCloseHandle(hFtp);
  382. if hConnect <> nil then InternetCloseHandle(hConnect);
  383. end;
  384. end;
  385. function TCnInet.GetHTTPStream(Info: TCnURLInfo; Stream: TStream; APost: TStrings): Boolean;
  386. var
  387. IsHttps: Boolean;
  388. PathName: string;
  389. hConnect, hRequest: HINTERNET;
  390. SizeStr: array[0..63] of Char;
  391. BufLen, Index: Cardinal;
  392. i: Integer;
  393. Port: Word;
  394. Flag: Cardinal;
  395. Verb, Opt: string;
  396. POpt: PChar;
  397. OptLen: Integer;
  398. begin
  399. Result := False;
  400. hConnect := nil;
  401. hRequest := nil;
  402. try
  403. IsHttps := SameText(Info.Protocol, 'https');
  404. if IsHttps then
  405. begin
  406. Port := StrToIntDef(Info.Port, INTERNET_DEFAULT_HTTPS_PORT);
  407. Flag := INTERNET_FLAG_RELOAD or INTERNET_FLAG_SECURE or
  408. INTERNET_FLAG_IGNORE_CERT_CN_INVALID or INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;
  409. end
  410. else
  411. begin
  412. Port := StrToIntDef(Info.Port, INTERNET_DEFAULT_HTTP_PORT);
  413. Flag := INTERNET_FLAG_RELOAD;
  414. end;
  415. if FNoCookie then
  416. Flag := Flag + INTERNET_FLAG_NO_COOKIES;
  417. hConnect := InternetConnect(hSession, PChar(Info.Host), Port, nil, nil,
  418. INTERNET_SERVICE_HTTP, 0, 0);
  419. if (hConnect = nil) or FAborted then
  420. Exit;
  421. if APost <> nil then
  422. begin
  423. Verb := 'POST';
  424. Opt := '';
  425. for i := 0 to APost.Count - 1 do
  426. if Opt = '' then
  427. Opt := EncodeURL(APost[i])
  428. else
  429. Opt := Opt + '&' + EncodeURL(APost[i]);
  430. POpt := PChar(Opt);
  431. OptLen := Length(Opt);
  432. end
  433. else
  434. begin
  435. Verb := 'GET';
  436. POpt := nil;
  437. OptLen := 0;
  438. end;
  439. PathName := Info.PathName;
  440. if EncodeUrlPath then
  441. PathName := EncodeURL(PathName);
  442. hRequest := HttpOpenRequest(hConnect, PChar(Verb), PChar(PathName),
  443. HTTP_VERSION, nil, nil, Flag, 0);
  444. if (hRequest = nil) or FAborted then
  445. Exit;
  446. if FDecoding and FDecodingValid then
  447. HttpAddRequestHeaders(hRequest, PChar(SAcceptEncoding),
  448. Length(SAcceptEncoding), HTTP_ADDREQ_FLAG_REPLACE or HTTP_ADDREQ_FLAG_ADD);
  449. for i := 0 to FHttpRequestHeaders.Count - 1 do
  450. HttpAddRequestHeaders(hRequest, PChar(FHttpRequestHeaders[i]),
  451. Length(FHttpRequestHeaders[i]), HTTP_ADDREQ_FLAG_REPLACE or HTTP_ADDREQ_FLAG_ADD);
  452. if HttpSendRequest(hRequest, nil, 0, POpt, OptLen) then
  453. begin
  454. if FAborted then Exit;
  455. FillChar(SizeStr, SizeOf(SizeStr), 0);
  456. BufLen := SizeOf(SizeStr);
  457. Index := 0;
  458. HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH, @SizeStr, BufLen, Index);
  459. if FAborted then Exit;
  460. Result := GetStreamFromHandle(hRequest, StrToIntDef(SizeStr, -1), Stream);
  461. end;
  462. finally
  463. if hRequest <> nil then InternetCloseHandle(hRequest);
  464. if hConnect <> nil then InternetCloseHandle(hConnect);
  465. end;
  466. end;
  467. function TCnInet.GetString(const AURL: string; APost: TStrings): AnsiString;
  468. var
  469. Stream: TMemoryStream;
  470. begin
  471. try
  472. Stream := TMemoryStream.Create;
  473. try
  474. if GetStream(AURL, Stream, APost) then
  475. begin
  476. SetLength(Result, Stream.Size);
  477. Move(Stream.Memory^, PAnsiChar(Result)^, Stream.Size);
  478. end
  479. else
  480. Result := '';
  481. finally
  482. Stream.Free;
  483. end;
  484. except
  485. Result := '';
  486. end;
  487. end;
  488. function TCnInet.GetFile(const AURL, FileName: string; APost: TStrings): Boolean;
  489. var
  490. Stream: TFileStream;
  491. begin
  492. try
  493. Stream := TFileStream.Create(FileName, fmCreate or fmShareDenyWrite);
  494. try
  495. Stream.Size := 0;
  496. Result := GetStream(AURL, Stream, APost);
  497. finally
  498. Stream.Free;
  499. end;
  500. except
  501. Result := False;
  502. end;
  503. end;
  504. end.