Search code examples
delphicurlfiremonkeyindy

Upload file using IdHTTP to specified host based on curl call


I am trying to create an FMX Android application that needs to upload a file to given host.

The host owner provided me with a following curl statement to upload a file:

curl -T http://<ip-address>:<port> file.txt

And when called from Windows Command Prompt it produces the following output (IP and port redacted):

*   Trying <ip-address>...
* TCP_NODELAY set
* Connected to <ip-address> (<ip-address>) port <port> (#0)
> PUT /file.txt HTTP/1.1
> Host: <ip-address>:<port>
> User-Agent: curl/7.55.1
> Accept: */*
> Content-Length: 682
> Expect: 100-continue
>
* Done waiting for 100-continue
* We are completely uploaded and fine
< HTTP/1.1 200 OK
< content-length: 0
< date: Tue, 16 Jun 2020 19:24:18 GMT
< connection: close
<
* Closing connection 0

and file is visible in the expected directory on the host machine.

Now I'm trying to have the same behavior using Indy, but either there is some black-box magic occurring on the server-side, or there is an issue with my code - with some solutions the request seems to finalize, but there is no file in the specified directory (and status code 200 is returned).

I have also tried using Fiddler to reproduce the curl call, and in the response of a composed query also received 200 status code, but still - no file was properly uploaded.

When inspecting Wireshark packets for these solutions they all seemed similar to the curl call - however, I am not an expert in using this tool, so maybe there are issues not visible to my newbie eye.

I tried using following solutions:
- How do I upload a file using http post? Delphi 2009 - but using PUT instead of POST
- Single file upload example
- Post a file through https using indy / delphi components

And in my case they do not work - so my guess is that the server is somehow not accepting these.

I know it's very hard to say what I'm doing wrong without me actually putting some code, but right now it's a total mess - but maybe it's not hard to somehow show me how to convert this curl call to work with TIdHttp?

If not, I will try to modify my question soon with more information.

EDIT: As requested, I'm adding some sample code:

var
  FHTTP: TIdHTTP;
  file2send: string;
  sr: TStringStream;
  FPutData : TFileStream;
begin
  sr := TStringStream.Create;
  if OpenDialog1.Execute then
    file2send := OpenDialog1.FileName;

  FHTTP := TIdHTTP.Create(self);
  FPutData := TFileStream.Create(file2send, fmOpenRead or
    fmShareDenyWrite);
  FHTTP.Put(PutURL.text, FPutData, sr);

end;

Solution

  • You don't need the TStringStream since you don't use it. You can pass TStream(nil) in that parameter.

    Also, you are calling FHTTP.Put() unconditionally, even if OpenDialog.Execute() returns false.

    Also, make sure the URL you pass to FHTTP.Put() is 'http://<ip-address>:<port>/file.txt' not 'http://<ip-address>:<port>'.

    Also, consider setting FHTTP.Request.UserAgent, as some web servers reject Indy's default UserAgent.

    Also, consider Free'ing your objects, especially if you ever plan to upgrade to 10.4 Sydney or later, since ARC for object lifetime management was removed in 10.4.

    Try something more like this:

    var
      FHTTP: TIdHTTP;
      file2send: string;
      FPutData : TFileStream;
    begin
      if not OpenDialog1.Execute then Exit;
    
      file2send := OpenDialog1.FileName;
    
      FHTTP := TIdHTTP.Create(nil);
      try
        FHTTP.Request.UserAgent := 'curl/7.55.1';
        FPutData := TFileStream.Create(file2send, fmOpenRead or fmShareDenyWrite);
        try
          FHTTP.Put(PutURL.Text, FPutData, TStream(nil));
          { or, maybe something like:
          URL := PutURL.Text + '/' + ExtractFileName(file2send);
          FHTTP.Put(URL, FPutData, TStream(nil));
          }
        finally
          FPutData.Free;
        end;
      finally
        FHTTP.Free;
      end;
    end;