1

I'm sending a number of parameters to an API using the TIdHTTP.Get() method.

I pull values for the actual API parameters from string variables or component Text properties (like a ComboBox, for example). Everything is fine until any of those values contains a space.

For example, one of the parameters is a full name field (example: 'John Smith')

Since it contains a space between the first and last name, once I send it to the API using te TIdHTTP.Get() method, it throws a 400 Bad Request error and fails.

If I eliminate the space from the value for that/any particular parameter, it goes through fine.

Code I'm using to test:

httpObject := TIdHTTP.Create;
    
httpObject.HTTPOptions := [hoForceEncodeParams];
httpobject.MaxAuthRetries := 3;
httpObject.ProtocolVersion := pv1_1;
httpObject.Request.Accept := 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8';
httpObject.Request.UserAgent := 'Mozilla/3.0 (compatible;Indy Library)';
httpObject.Request.ContentType := 'application/x-www-form-urlencoded; charset=utf-8';
    
URL := 'url string containing the parameters'; //string variable
httpObject.Get(URL);

API documentation says:

image

How can I address this?

Using Delphi Community Edition (which is 10.3) and its accompanying Indy components.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
t1f
  • 3,021
  • 3
  • 31
  • 61
  • Why are you specifying a Content-Type if you use the `GET` method? – Olivier May 25 '21 at 15:59
  • @Olivier API documentation indicates to do so. Edited my question and added part of the doc. Commenting out that line of code, and/or the `hoForceEncodeParams` one doesn't fix it. – t1f May 25 '21 at 16:00
  • That's not standard. The `POST` method should be used in that case. – Olivier May 25 '21 at 16:04
  • @Olivier My thought exactly, vaguely remembering that is for `POST`. However, as I mentioned, if I don't use that - it still fails the same. Could the API be badly designed forcing it to accept only `GET` using that `ContentType`? Or is the doc simply badly written maybe? I haven't designed one so far so no clue if it might be badly built – t1f May 25 '21 at 16:05
  • @Olivier just tried using `POST`. Same failure. o.O – t1f May 25 '21 at 16:08
  • How do you pass parameters? The doc says "providing data in a URL". If you do so, then the content-type is useless (it's probably an error in the documentation). – Olivier May 25 '21 at 16:09
  • @Olivier a string variable containing the URL and the parameters and their accompanying values. Edited my question with extra code. If I send a `POST` it says, simply: `HTTP/1.1 415` – t1f May 25 '21 at 16:14
  • Then you can remove the Content-Type. – Olivier May 25 '21 at 16:16

2 Answers2

3

You are sending parameters in the URL, not in the request body, so setting the Request.ContentType property and enabling the hoForceEncodeParams option are completely unnecessary and can be omitted.

You need to encode the parameter values when you build up a URL to send a request to. You can use the TIdURI class for that, eg:

uses
  ..., IdHTTP, IdURI;

URL := 'http://server/shipment?param1='+TIdURI.ParamsEncode(value1)+'&param2='+TIdURI.ParamsEncode(value2)...;
httpObject.Get(URL);
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thank you for another great answer! – t1f May 25 '21 at 16:49
  • 2
    Encoding parameter values is kinda obvious once one grasps what would happen when `/` or `&` can occur - then `https://server/shipment?param1=male/female&param2=guys&dolls&param3=...` would be screwed. – AmigoJack May 25 '21 at 21:04
2

You have stated that you are submitting the data as Content-Type: application/x-www-form-urlencoded

That format does not allow spaces. You need to properly encode what you are submitting.

Two ways you can do this:

With Indy:

Encoded := TIdURI.URLEncode(str);

With TNetEncoding

Encoded := TNetEncoding.URL.Encode(str);
Rob Lambden
  • 2,175
  • 6
  • 15
  • Can you add an example on how I can encode it prior to sending it to the API ? – t1f May 25 '21 at 15:57
  • Do the lines in the answer not help? (I accidentally hit submit before putting them in!) – Rob Lambden May 25 '21 at 15:59
  • It might. What is `Encoded` and `str`? I haven't used TIdURI so far. – t1f May 25 '21 at 16:03
  • 1
    `str` is what you want to encode - you haven't said what this is or where you get it from in your sample. It's what you are trying to send. `Encoded` is the result of the encoding. So instead of sending whatever it is you send, pass that value to `TIdURI.URLEncode` and send the result of that function. – Rob Lambden May 25 '21 at 16:06
  • Worked. Thanks a lot :) – t1f May 25 '21 at 16:24
  • 1
    It is better to use `TIdURI.ParamsEncode()` on the individual parameters, than to use `IdURI.URLEncode()` on the entire URL. – Remy Lebeau May 25 '21 at 16:34
  • @RemyLebeau Mind adding why to your answer? I'm curious as to the reasoning (since I'm clueless about IdURI) but I wouldn't want to bother you further if you think it's unnecessary / too much of a hassle. Thanks! – t1f May 25 '21 at 16:47
  • 1
    @t1f in a nutshell, because `TIdURI.URLEncode()` takes a full unencoded URL as input, breaks it up into its constituent pieces, encodes those pieces, and then stitches them back together. Which is overkill in this situation. And also, because `TIdURI` simply doesn't have a good parser to begin with, so if you give it a full URL that is not already encoded, it may not parse it entirely correctly. So, the less parsing it has to do, the better. Since you already have the constituent pieces you need (ie, the parameter values), you can skip the parsing step. – Remy Lebeau May 25 '21 at 16:59
  • @RemyLebeau Much more clearer now. Thank you! – t1f May 25 '21 at 17:17