There's a problem with IdCookieManager. When server returns cookie where value contains "
, it recognize first occurrence of "
as end of value. You can reproduce it easily with next code:
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;
Digging into Indy sources I found that problem is in TIdCookie.ParseServerCookie()
. It uses IdGlobal.Fetch()
to extract value between quotes and ... it does what it does.
Would you recommend me how to let it parse whole value?
There's no neat solution (at least I haven't found one) ,so I've added OnHeadersAvailable
event listener to TIdHTTP
which rewrites cookie value after TIdCookie.ParseServerCookie()
executed and replace previously parsed cookies by adding new cookie with same name to TIdCookieManager
.
Code (DO NOT USE THIS HANDLER, THERE'S ONE MORE BELOW):
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;
Usage:
HTTP.OnHeadersAvailable := FixCookies; // Set it after object initialization
P.S. I haven't used delphi for long time, so I'm sure that code isn't perfect and lot of improvements could be done (welcome to comments), but it works.
I have no clue how it happened but using this event handler somehow brakes TIdCookieManager
and Cookie.CookieCollection.Count
returns 0
while there're cookies in collection which successfully added in further requests..
I have no mood to spend more time digging into Indy sources so I changed handler to modify existing cookies instead of replacing them. It doesn't brake anything (probably):
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. Are there any Indy developers? I just a bit confused how does first handler let CookieCollection to return zero.