一个 HTTP URL 解析和编码的辅助实现

首先,一个 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 单元。

至于疗效嘛,暂略吧。套用一句话,谁用谁知道!

 

 

分享到: