Indy, TIdURI.PathEncode, URLEncode and ParamsEncode and more

Frequently in Delphi we come across the need to encode a string to stuff into a URL query string parameter (as per web forms).  One would expect that Indy contains well-tested functions to handle this.  Well, Indy contains some functions to help with this, but they may not work quite as you expect.  In fact, they may not be much use at all.

Indy contains a component called TIdURI.  It contains, among other things, the member functions URLEncode, PathEncode, and ParamsEncode. At first glance, these seem to do what you would need.  But in fact, they don’t.

URLEncode will take a full URL, split it into path, document and query components, encode each of those, and return the full string.  PathEncode is intended to handle the nuances of the path and document components of the URL, and ParamsEncode handles query strings.

Sounds great, right?  Well, it works until you have a query parameter that has an ampersand (&) in it.  Say my beloved end user want to search for big&little.  It seems that you could pass the following in:

s := TIdURI.URLEncode(''+SearchText);

But then we get no change in our result:

s = '';

And you can already see the problem: little is now a separate parameter in the query string.  How can we work around this?  Can we pre-encode ampersand to %26 before you pass in the parameters?

s := TIdURI.URLEncode(''+ReplaceStr(SearchText, '&', '%26'));


s = '';

And obviously we can’t do it ourselves afterwards, because we too won’t know which ampersands are which.  You could do correction of ampersand by encoding each parameter component separately and then post-processing the component for ampersand and other characters before final assembly using ParamsEncode. But you’ll soon find that it’s not enough anyway.  =, / and ? are also not encoded, although they should be.  Finally, URLEncode does not support internationalized domain names (IDN).

Given that these functions are not a complete solution, it’s probably best to avoid them altogether.

The problem is analogous to the Javascript encodeURI vs encodeURIComponent issue.

So to write your own…  I haven’t found a good Delphi solution online (and I searched a bit), so here’s a function I’ve cobbled together (use at your own risk!) to encode parameter names and values. You do need to encode each component of the parameter string separately, of course.

function EncodeURIComponent(const ASrc: string): UTF8String;
  HexMap: UTF8String = '0123456789ABCDEF';

  function IsSafeChar(ch: Integer): Boolean;
    if (ch >= 48) and (ch <= 57) then Result := True    // 0-9
     else if (ch >= 65) and (ch <= 90) then Result := True  // A-Z
     else if (ch >= 97) and (ch <= 122) then Result := True  // a-z
     else if (ch = 33) then Result := True // !
     else if (ch >= 39) and (ch <= 42) then Result := True // '()*
     else if (ch >= 45) and (ch <= 46) then Result := True // -.
     else if (ch = 95) then Result := True // _
     else if (ch = 126) then Result := True // ~
     else Result := False;
   I, J: Integer;
   ASrcUTF8: UTF8String;
   Result := '';    {Do not Localize}
   ASrcUTF8 := UTF8Encode(ASrc);
   // UTF8Encode call not strictly necessary but
   // prevents implicit conversion warning
   I := 1; J := 1;
   SetLength(Result, Length(ASrcUTF8) * 3); // space to %xx encode every byte
   while I <= Length(ASrcUTF8) do
     if IsSafeChar(Ord(ASrcUTF8[I])) then
       Result[J] := ASrcUTF8[I];
     else if ASrcUTF8[I] = ' ' then
       Result[J] := '+';
       Result[J] := '%';
       Result[J+1] := HexMap[(Ord(ASrcUTF8[I]) shr 4) + 1];
       Result[J+2] := HexMap[(Ord(ASrcUTF8[I]) and 15) + 1];
   SetLength(Result, J-1);

To use this, do something like the following:

function GetAURL(const param, value: string): UTF8String;
  Result := ''+

Hope this helps. Sorry, I haven't got an IDN solution in this post!

4 thoughts on “Indy, TIdURI.PathEncode, URLEncode and ParamsEncode and more

  1. Yeah, return value should probably be RawByteString or AnsiString(1252) for clarity, not UTF8String. Won’t change anything in this case as guaranteed to return only chars between $33 and $7F!

  2. In Delphi XE5’s (don’t know how far back this goes) REST.Utils unit:

    function URIEncode(const S: string): string;

    Looks like it does everything properly. It’s used by the REST.Client unit’s TCustomRESTRequest.

    1. Thanks James – it’s certainly not in XE2 but will investigate when I get a chance (have XE5, just have not yet had the time to move to it).

Comments are closed.