{******************************************************************************} { CnPack For Delphi/C++Builder } { 中国人自己的开放源码第三方开发包 } { (C)Copyright 2001-2018 CnPack 开发组 } { ------------------------------------ } { } { 本开发包是开源的自由软件,您可以遵照 CnPack 的发布协议来修 } { 改和重新发布这一程序。 } { } { 发布这一开发包的目的是希望它有用,但没有任何担保。甚至没有 } { 适合特定目的而隐含的担保。更详细的情况请参阅 CnPack 发布协议。 } { } { 您应该已经和开发包一起收到一份 CnPack 发布协议的副本。如果 } { 还没有,可访问我们的网站: } { } { 网站地址:http://www.cnpack.org } { 电子邮件:master@cnpack.org } { } {******************************************************************************} unit CnInetUtils; {* |
================================================================================ * 软件名称:网络通讯组件包 * 单元名称:使WinInet 封装单元 * 单元作者:周劲羽 (zjy@cnpack.org) * 备 注:定义了 TCnHTTP,使用 WinInet 来读取 HTTP 数据 * 开发平台:PWin2000Pro + Delphi 5.01 * 兼容测试:PWin9X/2000/XP + Delphi 5/6/7 + C++Builder 5/6 * 本 地 化:该单元中的字符串均符合本地化处理方式 * 单元标识:$Id$ * 修改记录:2005.09.14 V1.1 * 增加 UserAgent 和 Proxy 设置(由 illk 提供) * 2003.03.09 V1.0 * 创建单元 ================================================================================ |} interface {$I CnPack.inc} uses Windows, SysUtils, Classes, WinInet; type //============================================================================== // 使用 WinInet 读取 HTTP 文件的类 //============================================================================== { TCnInet } TCnInetProgressEvent = procedure (Sender: TObject; TotalSize, CurrSize: Integer; var Abort: Boolean) of object; {* 数据下载进度事件 |
Sender - 线程对象
TotalSize - 总字节数,如果为 -1,表示长度未知
CurrSize - 当前完成字节数
Abort - 是否中断
|}
TCnURLInfo = record
Protocol: string;
Host: string;
Port: string;
PathName: string;
Username: string;
Password: string;
end;
TCnInetProxyMode = (pmDirect, pmIE, pmProxy);
{* 使用代理的方式:直连、IE设置、指定代理 }
TCnInet = class
{* 使用 WinInet 读取 HTTP(S)/FTP 文件的类。}
private
hSession: HINTERNET;
FAborted: Boolean;
FGetDataFail: Boolean;
FOnProgress: TCnInetProgressEvent;
FUserAgent: string;
FDecoding: Boolean;
FDecodingValid: Boolean;
FProxyServer: string;
FProxyUserName: string;
FProxyPassWord: string;
FHttpRequestHeaders: TStringList;
FSendTimeOut: Cardinal;
FConnectTimeOut: Cardinal;
FReceiveTimeOut: Cardinal;
FProxyMode: TCnInetProxyMode;
FNoCookie: Boolean;
FEncodeUrlPath: Boolean;
function ParseURL(URL: string; var Info: TCnURLInfo): Boolean;
protected
procedure DoProgress(TotalSize, CurrSize: Integer);
function InitInet: Boolean;
procedure CloseInet;
function GetStreamFromHandle(Handle: HINTERNET; TotalSize: Integer;
Stream: TStream): Boolean;
function GetHTTPStream(Info: TCnURLInfo; Stream: TStream; APost: TStrings): Boolean;
function GetFTPStream(Info: TCnURLInfo; Stream: TStream): Boolean;
public
constructor Create;
destructor Destroy; override;
procedure Abort;
{* 中断当前处理}
function GetStream(const AURL: string; Stream: TStream; APost: TStrings = nil): Boolean;
{* 从 AURL 地址读取数据到流 Stream,如果 APost 不为 nil 则执行 Post 调用}
function GetString(const AURL: string; APost: TStrings = nil): AnsiString;
{* 从 AURL 地址返回一个字符串,如果 APost 不为 nil 则执行 Post 调用}
function GetFile(const AURL, FileName: string; APost: TStrings = nil): Boolean;
{* 从 AURL 地址读取数据保存到文件 FileName,如果 APost 不为 nil 则执行 Post 调用}
property OnProgress: TCnInetProgressEvent read FOnProgress write FOnProgress;
{* 数据进度事件}
property Aborted: Boolean read FAborted;
{* 是否已被中断}
property GetDataFail: Boolean read FGetDataFail;
{* 上一次的数据读取是否成功}
property Decoding: Boolean read FDecoding write FDecoding default True;
{* 是否支持 gzip, deflate 解压}
property UserAgent: string read FUserAgent write FUserAgent;
{* 设置UserAgent 浏览器识别标示}
property ProxyMode: TCnInetProxyMode read FProxyMode write FProxyMode;
{* 使用代理的方式}
property ProxyServer: string read FProxyServer write FProxyServer;
{* 代理服务器设置: [协议=][协议://]服务器[:端口] 如 127.0.0.1:8080}
property ProxyUserName: string read FProxyUserName write FProxyUserName;
{* 代理服务器用户名}
property ProxyPassWord: string read FProxyPassWord write FProxyPassWord;
{* 代理服务器用户密码}
property HttpRequestHeaders: TStringList read FHttpRequestHeaders;
{* 请求信息头}
property NoCookie: Boolean read FNoCookie write FNoCookie;
{* 是否不使用 Cookie,如果需要在 HttpRequestHeaders 中指定 Cookie,应设为 True}
property EncodeUrlPath: Boolean read FEncodeUrlPath write FEncodeUrlPath default True;
{* 是否自动为 Url 路径中的特殊字符编码}
property ConnectTimeOut: Cardinal read FConnectTimeOut write FConnectTimeOut;
{* 连接超时}
property SendTimeOut: Cardinal read FSendTimeOut write FSendTimeOut;
{* 发送超时}
property ReceiveTimeOut: Cardinal read FReceiveTimeOut write FReceiveTimeOut;
{* 接收超时}
end;
TCnHTTP = class(TCnInet);
TCnFTP = class(TCnInet);
function EncodeURL(const URL: string): string;
{* 将 URL 中的特殊字符转换成 %XX 的形式}
function CnInet_GetStream(const AURL: string; Stream: TStream; APost: TStrings = nil): Boolean;
function CnInet_GetString(const AURL: string; APost: TStrings = nil): AnsiString;
function CnInet_GetFile(const AURL, FileName: string; APost: TStrings = nil): Boolean;
implementation
const
csBufferSize = 4096;
INTERNET_OPTION_HTTP_DECODING = 65;
SAcceptEncoding = 'Accept-Encoding: gzip,deflate';
function EncodeURL(const URL: string): string;
const
UnsafeChars = ['*', '#', '%', '<', '>', '+', ' '];
var
i: Integer;
InStr, OutStr: AnsiString;
begin
InStr := AnsiString(URL);
OutStr := '';
for i := 1 to Length(InStr) do begin
if (InStr[i] in UnsafeChars) or (InStr[i] >= #$80) or (InStr[i] < #32) then
OutStr := OutStr + '%' + AnsiString(IntToHex(Ord(InStr[i]), 2))
else
OutStr := OutStr + InStr[i];
end;
Result := string(OutStr);
end;
function CnInet_GetStream(const AURL: string; Stream: TStream; APost: TStrings): Boolean;
begin
with TCnInet.Create do
try
Result := GetStream(AURL, Stream, APost);
finally
Free;
end;
end;
function CnInet_GetString(const AURL: string; APost: TStrings): AnsiString;
begin
with TCnInet.Create do
try
Result := GetString(AURL, APost);
finally
Free;
end;
end;
function CnInet_GetFile(const AURL, FileName: string; APost: TStrings): Boolean;
begin
with TCnInet.Create do
try
Result := GetFile(AURL, FileName, APost);
finally
Free;
end;
end;
//==============================================================================
// 使用 WinInet 读取 HTTP 文件的类
//==============================================================================
{ TCnInet }
constructor TCnInet.Create;
begin
inherited;
FDecoding := True;
FUserAgent := 'CnPack Internet Utils';
FHttpRequestHeaders := TStringList.Create;
FProxyMode := pmIE;
end;
destructor TCnInet.Destroy;
begin
CloseInet;
FHttpRequestHeaders.Free;
inherited;
end;
procedure TCnInet.CloseInet;
begin
if hSession <> nil then
begin
InternetCloseHandle(hSession);
hSession := nil;
end;
end;
function TCnInet.InitInet: Boolean;
var
Flag: LongBool;
begin
if hSession = nil then
begin
if (FProxyMode <> pmProxy) or (Length(FProxyServer) = 0) then
begin
if FProxyMode = pmDirect then
hSession := InternetOpen(PChar(FUserAgent), INTERNET_OPEN_TYPE_DIRECT,
nil, nil, 0)
else
hSession := InternetOpen(PChar(FUserAgent), INTERNET_OPEN_TYPE_PRECONFIG,
nil, nil, 0);
end
else
begin
hSession := InternetOpen(PChar(FUserAgent), INTERNET_OPEN_TYPE_PROXY,
PChar(FProxyServer), nil, 0);
if Length(FProxyUserName) > 0 then
InternetSetOption(hSession, INTERNET_OPTION_PROXY_USERNAME, PChar(FProxyUserName), Length(FProxyUserName));
if Length(FProxyPassWord) > 0 then
InternetSetOption(hSession, INTERNET_OPTION_PROXY_PASSWORD, PChar(FProxyPassWord), Length(FProxyPassWord));
if FConnectTimeOut <> 0 then
InternetSetOption(hSession, INTERNET_OPTION_CONNECT_TIMEOUT, @FConnectTimeOut, SizeOf(Cardinal));
if FSendTimeOut <> 0 then
InternetSetOption(hSession, INTERNET_OPTION_SEND_TIMEOUT, @FSendTimeOut, SizeOf(Cardinal));
if FReceiveTimeOut <> 0 then
InternetSetOption(hSession, INTERNET_OPTION_RECEIVE_TIMEOUT, @FReceiveTimeOut, SizeOf(Cardinal));
end;
if FDecoding then
begin
Flag := True;
FDecodingValid := InternetSetOption(hSession, INTERNET_OPTION_HTTP_DECODING, PChar(@Flag), SizeOf(Flag));
end;
end;
Result := hSession <> nil;
end;
procedure TCnInet.Abort;
begin
FAborted := True;
end;
procedure TCnInet.DoProgress(TotalSize, CurrSize: Integer);
begin
if Assigned(FOnProgress) then
FOnProgress(Self, TotalSize, CurrSize, FAborted);
end;
function TCnInet.ParseURL(URL: string; var Info: TCnURLInfo): Boolean;
var
Idx: Integer;
Buff: string;
function ExtractStr(var ASrc: string; ADelim: string;
ADelete: Boolean = True): string;
var
Idx: Integer;
begin
Idx := Pos(ADelim, ASrc);
if Idx = 0 then
begin
Result := ASrc;
if ADelete then
ASrc := '';
end
else
begin
Result := Copy(ASrc, 1, Idx - 1);
if ADelete then
ASrc := Copy(ASrc, Idx + Length(ADelim), MaxInt);
end;
end;
begin
Result := False;
URL := Trim(URL);
Idx := Pos('://', URL);
if Idx > 0 then
begin
Info.Protocol := Copy(URL, 1, Idx - 1);
Delete(URL, 1, Idx + 2);
if URL = '' then Exit;
Buff := ExtractStr(URL, '/');
Idx := Pos('@', Buff);
Info.Password := Copy(Buff, 1, Idx - 1);
if Idx > 0 then Delete(Buff, 1, Idx);
Info.UserName := ExtractStr(Info.Password, ':');
if Length(Info.UserName) = 0 then
Info.Password := '';
Info.Host := ExtractStr(Buff, ':');
Info.Port := Buff;
Info.PathName := URL;
Result := True;
end;
end;
function TCnInet.GetStream(const AURL: string; Stream: TStream; APost: TStrings = nil): Boolean;
var
Info: TCnURLInfo;
begin
Result := False;
if not ParseURL(AURL, Info) then
Exit;
FAborted := False;
if not InitInet or FAborted then
Exit;
if SameText(Info.Protocol, 'http') or SameText(Info.Protocol, 'https') then
Result := GetHTTPStream(Info, Stream, APost)
else if SameText(Info.Protocol, 'ftp') then
Result := GetFTPStream(Info, Stream);
if FAborted then
Result := False;
FGetDataFail := not Result;
end;
function TCnInet.GetStreamFromHandle(Handle: HINTERNET; TotalSize: Integer;
Stream: TStream): Boolean;
var
CurrSize, Readed: Cardinal;
Buf: array[0..csBufferSize - 1] of Byte;
begin
Result := False;
CurrSize := 0;
Readed := 0;
repeat
if not InternetReadFile(Handle, @Buf, csBufferSize, Readed) then
Exit;
if Readed > 0 then
begin
Stream.Write(Buf, Readed);
Inc(CurrSize, Readed);
DoProgress(TotalSize, CurrSize);
if Aborted then Exit;
end;
until Readed = 0;
Result := True;
end;
function TCnInet.GetFTPStream(Info: TCnURLInfo; Stream: TStream): Boolean;
var
hConnect, hFtp: HINTERNET;
FindData: TWin32FindData;
TotalSize: Integer;
begin
Result := False;
hConnect := nil;
hFtp := nil;
try
hConnect := InternetConnect(hSession, PChar(Info.Host),
StrToIntDef(Info.Port, INTERNET_DEFAULT_FTP_PORT),
PChar(Info.Username), PChar(Info.Password),
INTERNET_SERVICE_FTP, 0, 0);
if (hConnect = nil) or FAborted then
Exit;
hFtp := FtpFindFirstFile(hConnect, PChar(Info.PathName), FindData,
INTERNET_FLAG_NEED_FILE, 0);
if hFtp <> nil then
begin
InternetCloseHandle(hFtp);
TotalSize := FindData.nFileSizeLow;
end
else
TotalSize := -1;
hFtp := FtpOpenFile(hConnect, PChar(Info.PathName), GENERIC_READ,
FTP_TRANSFER_TYPE_BINARY, 0);
if (hFtp = nil) or FAborted then
Exit;
Result := GetStreamFromHandle(hFtp, TotalSize, Stream);
finally
if hFtp <> nil then InternetCloseHandle(hFtp);
if hConnect <> nil then InternetCloseHandle(hConnect);
end;
end;
function TCnInet.GetHTTPStream(Info: TCnURLInfo; Stream: TStream; APost: TStrings): Boolean;
var
IsHttps: Boolean;
PathName: string;
hConnect, hRequest: HINTERNET;
SizeStr: array[0..63] of Char;
BufLen, Index: Cardinal;
i: Integer;
Port: Word;
Flag: Cardinal;
Verb, Opt: string;
POpt: PChar;
OptLen: Integer;
begin
Result := False;
hConnect := nil;
hRequest := nil;
try
IsHttps := SameText(Info.Protocol, 'https');
if IsHttps then
begin
Port := StrToIntDef(Info.Port, INTERNET_DEFAULT_HTTPS_PORT);
Flag := INTERNET_FLAG_RELOAD or INTERNET_FLAG_SECURE or
INTERNET_FLAG_IGNORE_CERT_CN_INVALID or INTERNET_FLAG_IGNORE_CERT_DATE_INVALID;
end
else
begin
Port := StrToIntDef(Info.Port, INTERNET_DEFAULT_HTTP_PORT);
Flag := INTERNET_FLAG_RELOAD;
end;
if FNoCookie then
Flag := Flag + INTERNET_FLAG_NO_COOKIES;
hConnect := InternetConnect(hSession, PChar(Info.Host), Port, nil, nil,
INTERNET_SERVICE_HTTP, 0, 0);
if (hConnect = nil) or FAborted then
Exit;
if APost <> nil then
begin
Verb := 'POST';
Opt := '';
for i := 0 to APost.Count - 1 do
if Opt = '' then
Opt := EncodeURL(APost[i])
else
Opt := Opt + '&' + EncodeURL(APost[i]);
POpt := PChar(Opt);
OptLen := Length(Opt);
end
else
begin
Verb := 'GET';
POpt := nil;
OptLen := 0;
end;
PathName := Info.PathName;
if EncodeUrlPath then
PathName := EncodeURL(PathName);
hRequest := HttpOpenRequest(hConnect, PChar(Verb), PChar(PathName),
HTTP_VERSION, nil, nil, Flag, 0);
if (hRequest = nil) or FAborted then
Exit;
if FDecoding and FDecodingValid then
HttpAddRequestHeaders(hRequest, PChar(SAcceptEncoding),
Length(SAcceptEncoding), HTTP_ADDREQ_FLAG_REPLACE or HTTP_ADDREQ_FLAG_ADD);
for i := 0 to FHttpRequestHeaders.Count - 1 do
HttpAddRequestHeaders(hRequest, PChar(FHttpRequestHeaders[i]),
Length(FHttpRequestHeaders[i]), HTTP_ADDREQ_FLAG_REPLACE or HTTP_ADDREQ_FLAG_ADD);
if HttpSendRequest(hRequest, nil, 0, POpt, OptLen) then
begin
if FAborted then Exit;
FillChar(SizeStr, SizeOf(SizeStr), 0);
BufLen := SizeOf(SizeStr);
Index := 0;
HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH, @SizeStr, BufLen, Index);
if FAborted then Exit;
Result := GetStreamFromHandle(hRequest, StrToIntDef(SizeStr, -1), Stream);
end;
finally
if hRequest <> nil then InternetCloseHandle(hRequest);
if hConnect <> nil then InternetCloseHandle(hConnect);
end;
end;
function TCnInet.GetString(const AURL: string; APost: TStrings): AnsiString;
var
Stream: TMemoryStream;
begin
try
Stream := TMemoryStream.Create;
try
if GetStream(AURL, Stream, APost) then
begin
SetLength(Result, Stream.Size);
Move(Stream.Memory^, PAnsiChar(Result)^, Stream.Size);
end
else
Result := '';
finally
Stream.Free;
end;
except
Result := '';
end;
end;
function TCnInet.GetFile(const AURL, FileName: string; APost: TStrings): Boolean;
var
Stream: TFileStream;
begin
try
Stream := TFileStream.Create(FileName, fmCreate or fmShareDenyWrite);
try
Stream.Size := 0;
Result := GetStream(AURL, Stream, APost);
finally
Stream.Free;
end;
except
Result := False;
end;
end;
end.