IdCookieManager 剪切了包含 quotechar 的服务器 cookie 的一部分

IdCookieManager cut part of server cookie which contains quotechar

IdCookieManager 有问题。当值包含 " 的服务器 returns cookie 时,它​​会将第一次出现的 " 识别为值的结尾。您可以使用下一个代码轻松重现它:

procedure TSomeObject.Test;
var
  HTTP: TIdHTTP;
  Cookie: TIdCookieManager;
  i: Integer;
begin
  HTTP := TIdHTTP.Create(nil);
  Cookie := TIdCookieManager.Create(HTTP);
  HTTP.CookieManager := Cookie;
  HTTP.HandleRedirects := True;

  HTTP.Get('http://httpbin.org/cookies/set?test_cookie1=' +
    TIdURI.ParamsEncode('{"key": 123}') + '&test_cookie2=&test_cookie3=value');

  for i := 0 to Cookie.CookieCollection.Count - 1 do
    Form1.Memo1.Lines.Add(Cookie.CookieCollection[i].CookieName + ' = ' +
      Cookie.CookieCollection[i].Value);

  HTTP.Free;
end;

深入研究 Indy 资源,我发现问题出在 TIdCookie.ParseServerCookie() 中。它使用 IdGlobal.Fetch() 来提取引号之间的值并且...它会做它所做的事情。

你能推荐我如何让它解析整个值吗?

没有完美的解决方案(至少我还没有找到),所以我添加了 OnHeadersAvailable 事件侦听器到 TIdHTTP 重写TIdCookie.ParseServerCookie() 执行后的 cookie 值,并通过向 TIdCookieManager.

添加同名的新 cookie 来替换之前解析的 cookie

代码 (请勿使用此处理程序,下面还有一个)

procedure TSomeObject.FixCookies(Sender: TObject; AHeaders: TIdHeaderList;
var VContinue: Boolean);
const
  CookieDelimiter = ';';
  QuoteChar = '"';
  SpaceChar = ' ';
var
  RawHeader, RawCookie, RawCookieValue: string;
  i, CookieDelimiterPos, CookieNamePos, CookieValuePos: Integer;
  Cookie: TIdCookie;
begin
  for i := 0 to AHeaders.Count - 1 do
  begin
    RawHeader := AHeaders[i];
    if Pos('Set-Cookie', RawHeader) = 1 then  // starts with "Set-Cookie"
    begin
      for CookieNamePos := Length('Set-Cookie') + 2 to Length(RawHeader) do
        if RawHeader[CookieNamePos] <> SpaceChar then
          Break;

      RawCookie := Copy(RawHeader, CookieNamePos,
        Length(RawHeader) - CookieNamePos + 1);
      CookieDelimiterPos := Pos(CookieDelimiter, RawCookie);
      CookieValuePos := Pos('=', RawCookie);

      if (CookieDelimiterPos > 0) and (CookieValuePos > 0) then
      begin
        RawCookieValue := Copy(RawCookie, CookieValuePos + 1,
          CookieDelimiterPos - CookieValuePos - 1);
        if (Length(RawCookieValue) > 0) and (RawCookieValue[1] = QuoteChar) and
          (RawCookieValue[Length(RawCookieValue)] = QuoteChar) then
          RawCookieValue := Copy(RawCookieValue, 2, Length(RawCookieValue) - 2);

        with (Sender as TIdHTTP) do
        begin
          Cookie := TIdCookie.Create(nil);
          if Cookie.ParseServerCookie(RawCookie, URL) then
          begin
            Cookie.Value := RawCookieValue;
            CookieManager.CookieCollection.AddCookie(Cookie, URL);
          end
          else
            Cookie.Free;
        end;
      end;
    end;
  end;

  VContinue := True;
end;

用法:

HTTP.OnHeadersAvailable := FixCookies; // Set it after object initialization

P.S. 我已经很久没有使用 delphi 了,所以我确信代码并不完美,还有很多改进可以做到(欢迎评论),但是它有效


我不知道它是怎么发生的,但是使用这个事件处理程序以某种方式阻止了 TIdCookieManagerCookie.CookieCollection.Count returns 0,而收集中有成功的 cookie在进一步的请求中添加..

我没有心情花更多时间挖掘 Indy 源代码,所以我更改了处理程序以修改现有 cookie,而不是替换它们。它不会制动任何东西 (可能):

procedure TSomeObject.FixCookies(Sender: TObject; AHeaders: TIdHeaderList;
  var VContinue: Boolean);
const
  CookieDelimiter = ';';
  QuoteChar = '"';
  SpaceChar = ' ';
var
  RawHeader, RawCookie, RawCookieValue, RawCookieName: string;
  i, CookieDelimiterPos, CookieNamePos, CookieValuePos, CookieIndex: Integer;
begin
  for i := 0 to AHeaders.Count - 1 do
  begin
    RawHeader := AHeaders[i];
    if Pos('Set-Cookie', RawHeader) = 1 then  // starts with "Set-Cookie"
    begin
      for CookieNamePos := Length('Set-Cookie') + 2 to Length(RawHeader) do
        if RawHeader[CookieNamePos] <> SpaceChar then
          Break;

      RawCookie := Copy(RawHeader, CookieNamePos,
        Length(RawHeader) - CookieNamePos + 1);
      CookieDelimiterPos := Pos(CookieDelimiter, RawCookie);
      CookieValuePos := Pos('=', RawCookie);

      if (CookieDelimiterPos > 0) and (CookieValuePos > 0) then
      begin
        RawCookieName := Copy(RawCookie, 1, CookieValuePos - 1);
        RawCookieValue := Copy(RawCookie, CookieValuePos + 1,
          CookieDelimiterPos - CookieValuePos - 1);
        if (Length(RawCookieValue) > 0) and (RawCookieValue[1] = QuoteChar) and
          (RawCookieValue[Length(RawCookieValue)] = QuoteChar) then
          RawCookieValue := Copy(RawCookieValue, 2, Length(RawCookieValue) - 2);

        with (Sender as TIdHTTP) do
        begin
          CookieIndex := CookieManager.CookieCollection.GetCookieIndex
            (RawCookieName);
          if CookieIndex >= 0 then
            CookieManager.CookieCollection[CookieIndex].Value := RawCookieValue
        end;
      end;
    end;
  end;

  VContinue := True;
end;

P.S. 有 Indy 开发者吗?我只是有点困惑第一个处理程序如何让 CookieCollection 为 return 零。