skip to Main Content

I use this cmd to successfully download result.csv

curl.exe https://example.com/rest -u "xx:yy" -F [email protected] > result.csv

I want to do this using PHP. But no avail…
Help please.

This is my PHP:

$url = 'https://example.com/rest';
$xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<request xmlns="http://example.com"><search path="report_date" value="2023-07-01T00:00:00+0000"/>'; //and so on..
$ch = curl_init();
curl_setopt($ch, CURLOPT_USERPWD, 'xx:yy');
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, "xmlRequest=$xml");
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml'));
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

$data = curl_exec($ch);
var_dump($data); //just to check if it has requested data (will save to file later)
if(curl_errno($ch))
    print curl_error($ch);
else
    curl_close($ch);

It does not have requested data. But it gives this

string(346) "
500
An error has occured.
Please try again and if the problem persists, contact us.
"

2

Answers


  1. if your php code has it as a string, not a file, then you first have to put it in a file (because PHP’s libcurl api does not (yet?) support uploading files in multipart/form-data from strings), to get around this limitation you can do something like

    function stringToFile(string $data): array
    {
        $ret = array(
            "handle" => tmpfile(),
            "path" => null,
        );
        $ret['path'] = stream_get_meta_data($ret['handle'])['uri'];
        fwrite($ret['handle'], $data);
        rewind($ret['handle']);
        return $ret;
    }
    

    then change your code to

    $url = 'https://example.com/rest';
    $xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <request xmlns="http://example.com"><search path="report_date" value="2023-07-01T00:00:00+0000"/>'; //and so on..
    $xmlAsFile = stringToFile($xml);
    $ch = curl_init();
    
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, array(
        'xmlRequest' => new CURLFile($xmlAsFile['path'], 'application/xml', 'file.xml')
    ));
    (...)
    

    also you need to remove this:

    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml'));
    

    in multipart/form-data-requests, the Content-Type is supposed to be something like Content-Type: multipart/form-data; boundary=------------------------e74d6203325745a2

    but libcurl generate this header for you automatically, and you risk corrupting the POST request, making it invalid and un-parsable, if you change the outer Content-Type yourself.

    Fix those 2 things and your code should work.

    Meanwhile your file.xml will have it’s own "inner" Content-Type, here is a full sample with both the outer and inner Content-Type:

    POST / HTTP/1.1
    Host: localhost:9999
    User-Agent: curl/8.1.2
    Accept: */*
    Content-Length: 203
    Content-Type: multipart/form-data; boundary=------------------------0e405b89d0ec7f23
    
    --------------------------0e405b89d0ec7f23
    Content-Disposition: form-data; name="file"; filename="test.xml"
    Content-Type: application/xml
    
    <xml></xml>
    
    --------------------------0e405b89d0ec7f23--
    
    • as you can see, there is both an outer, and an inner, Content-Type, and the one you were overwriting with your curl_setopt($ch, CURLOPT_HTTPHEADER-call was the outer Content-Type (the wrong one)

    (Some variation of this question has been asked many times before, but I can’t find a duplicate right now, strange)

    Login or Signup to reply.
  2. What you can say as a rule of thumb is that you look for each curl(1) command line option (+ argument) and operand and find the appropriate curl_setopt($option, $value).

    So you can compare the command-line against the PHP code to locate potential issues that may cause the 500 error. Otherwise you can’t find out what is wrong in the PHP code while the command-line works.

    So let’s take a look what we’ve got here in that command-line:

    curl.exe https://example.com/rest -u "xx:yy" -F [email protected] > result.csv
    
    1. https://example.com/rest (URL)

      The URL syntax is protocol-dependent. You find a detailed description in RFC 3986. (ref)

    2. -u "xx:yy" (<user:password>)

      Specify the user name and password to use for server authentication. (ref)

    3. -F [email protected] (<name=content>)

      For HTTP protocol family, this lets curl emulate a filled-in form in which a user has pressed the submit button. This causes curl to POST data using the Content-Type multi‐part/form-data according to RFC 2388.

      This enables uploading of binary files etc. To force the ‘content’ part to be a file, prefix the file name with an @ sign. To just get the content part from a file, prefix the file name with the symbol <. The difference between @ and < is then that @ makes a file get attached in the post as a file upload, while the < makes a text field and just get the contents for that text field from a file (ref)

    Then lets see which curl_setopt() operations there are in the PHP code:

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_USERPWD, 'xx:yy');
    curl_setopt($ch, CURLOPT_URL,$url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, "xmlRequest=$xml");
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml'));
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    
    1. curl_setopt($ch, CURLOPT_USERPWD, 'xx:yy'); – OK, matches 2.) above.
    2. curl_setopt($ch, CURLOPT_URL,$url); – OK, matches 1.) above.
    3. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); – OK, your config.
    4. curl_setopt($ch, CURLOPT_TIMEOUT, 10); – OK, your config.
    5. curl_setopt($ch, CURLOPT_POST, true); – OK, mirrors the POST method matching 3.) above.
    6. curl_setopt($ch, CURLOPT_POSTFIELDS, "xmlRequest=$xml"); – NOT OK, does not match 3.) above. 3.) above does a file-upload, here you’re setting a form string value. This is not a file-upload.

    I can imagine this could trigger a 500 on the remote side, as they have little error handling and the process just crashes.

    You perhaps want to create a file-upload from the string so that the data of the file-upload is the string contents.

    If that is the case, this is how it works in PHP curl tailored to your -F [email protected] curl(1) option and argument example:

    curl_setopt($ch, CURLOPT_POSTFIELDS, [
        'xmlRequest' => new CURLStringFile($xml, 'file.xml'),
        #     ^                              ^       ^
        #     |                              |       `-- upload filename
        # name of the form-field             |
        #                                    |
        #                   string contents of the file (plain binary data)
    ]);
    

    Cf. CURLStringFile; answer to Send string as a file using curl and php and curl_setopt(CURLOPT_POSTFIELDS) (it already mentions CURLStringFile).

    let’s continue with the next lines:

    1. curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml')); – NOT OK, the original curl command line does not have it. Remove it.

    2. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); – NOT OK, the original curl command line does not have it. Remove it.

    3. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); – NOT OK, the original curl command line does not have it. Remove it.

    4. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); – NOT OK, the original curl command line does not have it. Remove it.

    As you have already written, you’re not yet interested in converting the redirect to file (the > result.csv at the end of the command-line), so this should already be it.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search