Search code examples
freepascallazarusindy10

Indy 10 - add cookies manually


I can export my cookies using "EditThisCookie" for Google Chrome.

The export looks like this:

"domain": ".stackoverflow.com",
"expirationDate": 1698322697,
"hostOnly": false,
"httpOnly": false,
"name": "_ga",
"path": "/",
"sameSite": "unspecified",
"secure": false,
"session": false,
"storeId": "0",
"value": "some",
"id": 1    

Now, I wish to store/parse this string to an existing TIdHTTP Cookie Manager.

How can I add these values to the Cookie Manager using TIdCookie?

This is what I tried, with no luck:

procedure ImportCookies (JSONCookies: string; CookieManager: TIdCookieManager);
var
  StringList: TStringList;
  I : Integer;
  InCookie : Boolean;
  MyCookie: TidCookie;
    
  function BoolStrToBool(s: string): Boolean;
  begin
    if Copy(UpperCase(Trim(s)), 1, 4) = 'TRUE' then Exit(True) else Exit(False);
  end;
    
begin
  InCookie         := False;
  StringList       := TStringList.Create;
  try
    StringList.Text:= JSONCookies;
    for I := 0 to StringList.Count - 1 do
    begin
      if Trim(StringList.Strings[i]) = '{' then
      begin
        InCookie := True;
        MyCookie := CookieManager.CookieCollection.Add;
        Continue;
      end;
    
      if (Trim(StringList.Strings[i]) = '}') or (Trim(StringList.Strings[i]) = '},') then
      begin
        InCookie := False;
        Continue;
      end;
    
      if InCookie then
      begin
        if Copy(StringList.Strings[i], 1, 9) = '"domain":' then
        begin
          StringList.Strings[i] := Copy(StringList.Strings[i], 10, MaxInt);
          MyCookie.Domain := GetBetween(StringList.Strings[i], '"', '"');
          Continue;
        end;
        if Copy(StringList.Strings[i], 1, 11) = '"hostOnly":' then
        begin
          StringList.Strings[i] := Copy(StringList.Strings[i], 12, MaxInt);
          MyCookie.HostOnly := BoolStrToBool(StringList.Strings[i]);
          Continue;
        end;
        if Copy(StringList.Strings[i], 1, 11) = '"httpOnly":' then
        begin
          StringList.Strings[i] := Copy(StringList.Strings[i], 12, MaxInt);
          MyCookie.HttpOnly := BoolStrToBool(StringList.Strings[i]);
          Continue;
        end;
        if Copy (StringList.Strings[i], 1, 7) = '"name":' then
        begin
          StringList.Strings[i] := Copy(StringList.Strings[i], 8, MaxInt);
          MyCookie.CookieName := GetBetween(StringList.Strings[i], '"', '"');
          Continue;
        end;
        if Copy(StringList.Strings[i], 1, 7) = '"path":' then
        begin
          StringList.Strings[i] := Copy(StringList.Strings[i], 8, MaxInt);
          MyCookie.Path := GetBetween(StringList.Strings[i], '"', '"');
          Continue;
        end;
        if Copy(StringList.Strings[i], 1, 9) = '"secure":' then
        begin
          StringList.Strings[i] := Copy(StringList.Strings[i], 10, MaxInt);
          MyCookie.Secure := BoolStrToBool(StringList.Strings[i]);
          Continue;
        end;
        if Copy(StringList.Strings[i], 1, 8) = '"value":' then
        begin
          StringList.Strings[i] := Copy(StringList.Strings[i], 9, MaxInt);
          MyCookie.Path := GetBetween(StringList.Strings[i], '"', '"');
          Continue;
        end;
      end;
    end;
  finally
    StringList.Free;
  end;
end;           

I don't see why I'd need a TIdURI instance - the origin shouldn't matter, since there's a destination domain already?!


Solution

  • FYI, your code can be greatly simplified if you use an actual JSON parser, as well as existing RTL/library functions, such as:

    • StrUtils.StartsText() or IdGlobal.TextStartsWith()
    • SysUtils.StrToBool()
    • SysUtils.AnsiExtractQuotedStr() or IdGlobalProtocols.UnquotedStr()
    • IdGlobal.PosInStrAray()
    • etc.

    Or, you could simply reformat the exported cookie data into a standard HTTP cookie format and then let TIdCookieManager.AddServerCookie() do all of the parsing for you (but then you would need a TIdURI).

    In any case, TIdURI is used by TIdCookieManager's parser, as certain aspects of a cookie need to be validated against the origin it came from. For instance, when determining the cookie's Path, or matching it against a domain/host, or even just knowing what the origin host is for a HostOnly cookie, or whether an HttpOnly cookie was received over HTTP, etc. There are rules to cookie processing, when receiving cookies, and when sending cookies to servers, that are tied to the cookie's origin URL.

    In your case, since you are skipping TIdCookieManager.AddServerCookie(), you don't really need a TIdURI since Chrome has already done the heavy processing for you.

    However, one thing that AddServerCookie() does do, which you are not doing, is it adds the TIdCookie to an internal TIdCookies list that is later used when deciding which cookies get sent back to which servers in TIdHTTP requests (see TIdCookieManager.GenerateClientCookies()).

    So, it is not enough to just call TIdCookieManager.CookieCollection.Add(), which merely creates the TIdCookie object. You would also need to call TIdCookieManager.CookieCollection.AddCookie() - which also takes a TIdURI, though this one is optional, whereas the one in AddServerCookie() is required.

    So, try something more like this:

    procedure ImportCookies(JSONCookies: string; CookieManager: TIdCookieManager);
    var
      StringList: TStringList;
      I : Integer;
      MyCookie: TidCookie;
      S, FieldName, FieldValue: string;
    
      procedure AddMyCookieIfReady;
      begin
        if MyCookie <> nil then
        begin
          if not CookieManager.CookieCollection.AddCookie(MyCookie, nil) then
          begin
            MyCookie.Collection := nil;
            MyCookie.Free;
          end;
          MyCookie = nil;
        end;
      end;
    
    begin
      StringList := TStringList.Create;
      try
        StringList.Text := JSONCookies;
        MyCookie := nil;
    
        for I := 0 to StringList.Count - 1 do
        begin
          s := Trim(StringList.Strings[I]);
    
          if s = '{' then
          begin
            AddMyCookieIfReady;
            MyCookie := CookieManager.CookieCollection.Add;
            Continue;
          end;
        
          if TextStartsWith(s, '}') then
          begin
            AddMyCookieIfReady;
            Continue;
          end;
        
          if (MyCookie = nil) or (not TextStartsWith(s, '"')) then
            Continue;
    
          Delete(s, 1, 1);
          FieldName := Fetch(s, '"');
          Fetch(s, ':');
          FieldValue := UnquotedStr(TrimLeft(s));
    
          case PosInStrArray(FieldName, ['domain', 'hostOnly', 'httpOnly', 'name', 'path', 'secure', 'value'], False) of
            0: begin
              if TextStartsWith(FieldValue, '.') then Delete(FieldValue, 1, 1);
              MyCookie.Domain := FieldValue;
            end;
            1: MyCookie.HostOnly := StrToBool(FieldValue);
            2: MyCookie.HttpOnly := StrToBool(FieldValue);
            3: MyCookie.CookieName := FieldValue;
            4: MyCookie.Path := FieldValue;
            5: MyCookie.Secure := StrToBool(FieldValue);
            6: MyCookie.Value := FieldValue;
          end;
        end;
    
        AddMyCookieIfReady;
      finally
        StringList.Free;
      end;
    end;