首先,一个 HTTP 的 URL 有下面几个部分组成:
- 协议类型。是 HTTP 还是 HTTPS 协议,分别对应于 http:// 和 https://;
- 用户名和密码。这个位于协议类型的后面,用户名和密码之间采用 “:” 进行分隔,跟主机的域名或IP部分用 ‘@’ 符号进行分隔;
- 域名或IP地址;
- 要访问的资源地址(URI),以 “/” 开始;
- 附加的额外参数,与 URI 之间以 “?” 进行分隔,多个参数之前用 “&” 进行分隔。每个参数如果分成名称和参数部分,则名称和参数部分采用 “=” 分隔
一个典型的 URL 如下所示 :
http://www.baidu.com/s?wd=abcd&tag=qdac
分别对应下:协议为 HTTP ,用户名和密码没有,域名为 www.baidu.com,URI 为 /s ,参数有两个,分别为 wd 和 tag,它们的值分别为 abcd 和 qdac。
根据上面的规则,我们定义了一个下面的结构:
THttpParam = record
Name, Value: String;
end;
THttpUrl = record
Host, URI, UserName, Password: QStringW;
Params: array of THttpParam;
Port: Word;
Https: Boolean;
private
public
function Encode(AUseUtf8: Boolean): QStringW;
function Decode(const AUrl: QStringW): Boolean;
class function Escape(const AURI: QStringW; AUseUtf8: Boolean)
: QStringW; static;
class function Unescape(const S: QStringW): QStringW; static;
end;只所以用记录(结构体)而不是类,是因为它可以在栈上运行,可以简化编码。
上面的定义实现了几个函数:
- Encode 函数用于将各个部分组合成一个有效的 URL ;
- Decode 函数用于解析一个 URL ,并将其各部分的值,保存到相应的成员中;
- Escape 函数用于将一个字符串按照 URL 编码规则进行转义,主要用于中文和特殊字符的处理;
- Unescape 函数用于将一个使用 URL 编码规则的字符串反转为正常的字符串,注意这个字符串应该是转义过的字符串,否则可能出现乱码
好了,下面看具体的实现:
{ THttpUrl }
function THttpUrl.Decode(const AUrl: QStringW): Boolean;
var
p, ps: PWideChar;
i: Integer;
begin
Result := true;
p := PWideChar(AUrl);
if StartWithW(p, 'https://', true) then
begin
Https := true;
Inc(p, 8);
Port := 443;
end
else
begin
Https := False;
if StartWithW(p, 'http://', true) then
Inc(p, 7);
Port := 80;
end;
if StrStrW(p, '@') <> nil then // http://username:password@host
begin
UserName := DecodeTokenW(p, ':@', #0, False, False);
if p^ = ':' then
Inc(p);
Password := DecodeTokenW(p, '@', #0, False, true);
end;
Host := DecodeTokenW(p, ':/', #0, False, False);
if p^ = ':' then
begin
Inc(p);
ps := p;
SkipUntilW(p, '/');
if TryStrToInt(StrDupX(ps, p - ps), i) then
Port := i
else
Result := False;
end;
URI := DecodeTokenW(p, '?', #0, False, False);
if Length(URI) = 0 then
URI := '/';
if p^ = '?' then
begin
Inc(p);
SetLength(Params, 16);
i := 0;
repeat
if i = Length(Params) then
SetLength(Params, i + 16);
Params[i].Name := Unescape(DecodeTokenW(p, '=', #0, False, False));
if p^ = '=' then
Inc(p);
Params[i].Value := Unescape(DecodeTokenW(p, '&', #0, False, False));
Inc(i);
until p^ = #0;
SetLength(Params, i);
end
else
SetLength(Params, 0);
end;
function THttpUrl.Encode(AUseUtf8: Boolean): QStringW;
var
i: Integer;
ADefPort: Boolean;
begin
if Https then
begin
Result := 'https://';
ADefPort := Port = 443;
end
else
begin
Result := 'http://';
ADefPort := Port = 80;
end;
if Length(UserName) > 0 then
begin
Result := Result + Escape(UserName, AUseUtf8);
if Length(Password) > 0 then
Result := Result + ':' + Escape(Password, AUseUtf8);
Result := Result + '@';
end;
Result := Result + Host;
if not ADefPort then
Result := Result + ':' + IntToStr(Port);
if Length(URI) > 0 then
begin
if PWideChar(URI)^ <> '/' then
URI := '/' + URI;
end
else
URI := '/';
Result := Result + URI;
if Length(Params) > 0 then
begin
Result := Result + '?';
for i := 0 to High(Params) do
begin
if i <> 0 then
Result := Result + '&';
Result := Result + Escape(Params[i].Name, AUseUtf8);
if Length(Params[i].Value) > 0 then
Result := Result + '=' + Escape(Params[i].Value, AUseUtf8);
end;
end;
end;
class function THttpUrl.Escape(const AURI: QStringW; AUseUtf8: Boolean)
: QStringW;
var
S: QStringA;
p: PQCharA;
i, L: Integer;
pd: PWideChar;
begin
if AUseUtf8 then
S := qstring.Utf8Encode(AURI)
else
S := qstring.AnsiEncode(AURI);
p := PQCharA(S);
SetLength(Result, S.Length * 3);
pd := PWideChar(Result);
while p^ <> 0 do
begin
if p^ in [$30 .. $39, $41 .. $5A, $61 .. $7A] then
pd^ := WideChar(p^)
else
begin
pd^ := '%';
Inc(pd);
pd^ := HexChar(p^ shr 4);
Inc(pd);
pd^ := HexChar(p^ and $F);
end;
Inc(pd);
Inc(p);
end;
SetLength(Result, pd - PWideChar(Result));
end;
class function THttpUrl.Unescape(const S: QStringW): QStringW;
var
ATemp: TBytes;
p: PWideChar;
i: Integer;
ABom: Boolean;
begin
if Pos(S, '%') = 0 then
Result := S
else
begin
SetLength(ATemp, Length(S) shl 1);
p := PWideChar(S);
i := 0;
while p^ <> #0 do
begin
if p^ = '%' then
begin
Inc(p);
ATemp[i] := HexValue(p^) shl 4;
Inc(p);
ATemp[i] := ATemp[i] + HexValue(p^);
end
else if Ord(p^) <= $FF then
ATemp[i] := Ord(p^)
else
begin
ATemp[i] := Ord(p^) shr 8;
Inc(i);
ATemp[i] := Ord(p^) and $FF;
end;
Inc(i);
Inc(p);
end;
case DetectTextEncoding(@ATemp[0], i, ABom) of
teUtf8:
Result := qstring.Utf8Decode(@ATemp[0], i)
else
Result := qstring.AnsiDecode(@ATemp[0], i);
end;
end;
end;当然了,这个代码引用了 QString.pas 中的几个函数,所以你要用的话,一样需要引入 QString 单元。
至于疗效嘛,暂略吧。套用一句话,谁用谁知道!
