I have spotted a "weird" php CURL behavior that is sending me nuts. Basically what I am doing is making a digest authenticated call with curl. Here's an extract of my code:
curl_setopt($this->c, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
curl_setopt($this->c, CURLOPT_USERPWD, $username . ":" . $password);
It works fine and the server actually comes back with a "YES, YOU PROVIDED THE RIGHT CREDENTIALS" kind of message. Only trouble is, the raw http response is a bit odd as it includes, as a matter of fact, 2 responses instead of one. Here's what curl_exec($this->c) spits out:
HTTP/1.0 401 Unauthorized
Date: Tue, 23 Oct 2012 08:41:18 GMT
Server: Apache/2.2.20 (Ubuntu)
X-Powered-By: PHP/5.3.6-13ubuntu3.9
WWW-Authenticate: Digest realm="dynamikrest-testing",qop="auth",nonce="5086582e95104",opaque="4b24e95490812b28b3bf139f9fbc9a66"
Vary: Accept-Encoding
Content-Length: 9
Connection: close
Content-Type: text/html
HTTP/1.1 200 OK
Date: Tue, 23 Oct 2012 08:41:18 GMT
Server: Apache/2.2.20 (Ubuntu)
X-Powered-By: PHP/5.3.6-13ubuntu3.9
Vary: Accept-Encoding
Content-Length: 9
Connection: close
Content-Type: text/html
"success"
I don't get why it includes the first response from the server (the one in which it states it requires authentication).
Can anyone throw some light on the issue? How do I avoid the responses' cumulation?
Cheers
It looks like curl has the same behavior if you use the -I option for headers:
curl -I --digest -u root:somepassword http://localhost/digest-test/
returns:
HTTP/1.1 401 Authorization Required
Date: Fri, 31 May 2013 13:48:35 GMT
Server: Apache/2.2.22 (Ubuntu)
WWW-Authenticate: Digest realm="Test Page", nonce="9RUL3wPeBAA=52ef6531dcdd1de61f239ed6dd234a3288d81701", algorithm=MD5, domain="/digest-test/ http://localhost", qop="auth"
Vary: Accept-Encoding
Content-Type: text/html; charset=iso-8859-1
HTTP/1.1 200 OK
Date: Fri, 31 May 2013 13:48:35 GMT
Server: Apache/2.2.22 (Ubuntu)
Authentication-Info: rspauth="4f5f8237e9760f777255f6618c21df4c", cnonce="MTQ3NDk1", nc=00000001, qop=auth
Vary: Accept-Encoding
Content-Type: text/html;charset=UTF-8
X-Pad: avoid browser bug
To only get the second header you could try this (not very optimal solution):
<?php
$ch = curl_init();
// set url
curl_setopt($ch, CURLOPT_URL, "http://localhost/digest-test/");
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
curl_setopt($ch, CURLOPT_USERPWD, "root:test");
// first authentication with a head request
curl_setopt($ch, CURLOPT_NOBODY, 1);
curl_exec($ch);
// the get the real output
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_HTTPGET, 1);
$output = curl_exec($ch);
echo $output;
I hit the same problem, and I think it was caused by PHP being compiled against an ancient version of libcurl (7.11.0 in my case, which is now nearly 10 years old). On a different machine with a more recent version of libcurl (7.29.0) the same code was fine, and my problems ended after getting my host to recompile their PHP to use the latest they had available (7.30.0).
This fix was suggested by a thread on the curl-library mailing list from 2008, where a user discovered the problem affected version 7.10.6 but not 7.12.1. I've searched the libcurl changelog around 7.12.0 and failed to find any clear entry about fixing this problem, though it might be covered by "general HTTP authentication improvements". Still, I'm now pretty confident that an old libcurl is the problem.
You can check which version of libcurl is used by your PHP from the 'cURL Information' entry in the output of phpinfo();
Related
Initially I was having issues trying to figure out why php curl under browser behaves differently when I tried to execute the same script by CLI.
By turning on the CURLOPT_VERBOSE with log output and compare the result of the CLI and browser, here are the differences I've seen:
CURL Under CLI
* About to connect() to proxy localhost port 3128 (#4)
* Trying ::1...
* Connection refused
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 3128 (#4)
* Establish HTTP proxy tunnel to someurl.com:443
* Server auth using Basic with user 'some_username'
> CONNECT someurl.com:443 HTTP/1.1
Host: someurl.com:443
Proxy-Connection: Keep-Alive
< HTTP/1.1 407 Proxy Authentication Required
< Mime-Version: 1.0
< Date: Fri, 11 Dec 2020 12:04:46 CST
< Via: 1.1 someotherurl.com:8080 (Cisco-WSA/12.0.1-334)
< Content-Type: text/html
< Connection: close
< Proxy-Connection: close
< Content-Length: 2109
< X-RBT-SCAR: 2.3.4.5:11517381:2000
< Proxy-Authenticate: Basic realm="Cntlm for parent"
* Authentication problem. Ignoring this.
<
* Received HTTP code 407 from proxy after CONNECT
* Connection #4 to host localhost left intact
CURL Under Browser
* About to connect() to someurl.com port 443 (#6)
* Trying 1.2.3.4...
* Connected to someurl.com (1.2.3.4) port 443 (#6)
* warning: ignoring value of ssl.verifyhost
* skipping SSL peer certificate verification
* SSL connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate:
* subject: C=US,ST=FL,L=Boca Raton,O=Telit IoT Platforms,OU=secureWISE,CN=someurl.com
* start date: Apr 15 21:18:15 2020 GMT
* expire date: May 15 21:18:15 2022 GMT
* common name: someurl.com
* issuer: E=support#securewise.net,CN=secureWISE CA-256,OU=SecureWISE Certificate Authority,O=ILS Technology LLC,O=Telit Wireless Solutions Inc,L=Boca Raton,ST=Florida,C=US
* Server auth using Basic with user 'some_username'
> GET /someurl HTTP/1.1
Authorization: Basic SomeAuthKey
Host: someurl.com
Accept: */*
< HTTP/1.1 200 OK
< Date: Fri, 11 Dec 2020 04:07:40 GMT
< Server: Apache-Coyote/1.1
< X-Powered-By: Undertow/1
< Set-Cookie: JSESSIONID=c2BBPwZBjGxCaH5om6unoKaI; path=/
< Set-Cookie: somekey=somevalue; path=/
< Content-Type: text/xml
< Content-Length: 125291
< Content-disposition: attachment; filename=somefilename.xml
< Vary: Accept-Encoding,User-Agent
< SWOrigin: sw_proxy
< Connection: close
<
* Closing connection 6
My initial hunch is that this has something to do with proxy (as this PC does use a proxy to go online)
And looking at the browser log, it seems as if proxy was skipped.
I've also checked the phpinfo() for both the browser and CLI, and I can see that there's proxy, http_proxy, https_proxy defined in the environment variables, as well as under $_SERVER for CLI, but not on browser, which makes me believe more that my assumption is correct.
So in order to combat this, I've tried adding the following code before the curl call:
if(isset($_SERVER['http_proxy']))
unset($_SERVER['http_proxy']);
if (isset($_SERVER['https_proxy']))
unset($_SERVER['https_proxy']);
if (isset($_SERVER['proxy']))
unset($_SERVER['proxy']);
if(isset($_ENV['http_proxy']))
unset($_ENV['http_proxy']);
if (isset($_ENV['https_proxy']))
unset($_ENV['https_proxy']);
if (isset($_ENV['proxy']))
unset($_ENV['proxy']);
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "someuser:somepass");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_VERBOSE, true);
$result = curl_exec($ch);
curl_close($ch);
But the verbose still shows that it still tries to go through the proxy when executed under CLI.
Any suggestion on this?
After digging around, it turns out all I had to do was to by pass the someurl.com in the /etc/cntlm.conf by including the url in the NoProxy config.
I'm trying to translate a curl command I was given into a command I can run through PHP.
The command is:
curl -F customerid=902 -F username=API1 -F password=somepassword -F reportname=1002 http://somerandomurl.com/api/v1/getreportcsv
When I try to run this through PHP (and eventually through C#) however, the web service returns an error. Any idea what could be wrong with my code that is making it error? I think the web service must be very specific about the headers/request:
$url = "http://somerandomurl.com/api/v1/getreportcsv";
$fields = [
"customerid" => "902",
"username" => "API1",
"password" => "somepassword",
"reportname" => "1002"
];
$fields_string = "";
foreach($fields as $key=>$value) { $fields_string .= $key.'='.$value.'&'; }
rtrim($fields_string,'&');
//open connection
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_POST,count($fields));
curl_setopt($ch,CURLOPT_POSTFIELDS,$fields_string);
//execute post
$result = curl_exec($ch);
print $result;
Wireshark shows the following differences:
The below is the one that works:
POST /somefolder/api/v1/getreportcsv HTTP/1.1
Host: somehost
Accept: */*
Content-Length: 65
Content-Type: application/x-www-form-urlencoded
customerid=902&username=API1&password=somepassword&reportname=1002&HTTP/1.1 200 OK
Server: GlassFish Server Open Source Edition 3.1.1
Content-Type: text/html;charset=UTF-8
Content-Length: 6
Date: Thu, 26 Nov 2015 22:51:23 GMT
ERROR
Whereas this one works:
POST /someurl/api/v1/getreportcsv HTTP/1.1
User-Agent: curl/7.33.0
Host: somehost
Accept: */*
Content-Length: 459
Expect: 100-continue
Content-Type: multipart/form-data; boundary=------------------------4b0d14cc31a40c5b
HTTP/1.1 100 Continue
--------------------------4b0d14cc31a40c5b
Content-Disposition: form-data; name="customerid"
902
--------------------------4b0d14cc31a40c5b
Content-Disposition: form-data; name="username"
API1
--------------------------4b0d14cc31a40c5b
Content-Disposition: form-data; name="password"
somepassword
--------------------------4b0d14cc31a40c5b
Content-Disposition: form-data; name="reportname"
1002
--------------------------4b0d14cc31a40c5b--
HTTP/1.1 200 OK
Server: GlassFish Server Open Source Edition 3.1.1
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 26 Nov 2015 23:13:57 GMT
2000
...snip... the results of the api
Obviously they are very different requests, however I wouldn't expect something to be so specific?
The question seems very specific to the service in question.
However, the problem might be with headers. According to curl man page:
-F [...] causes curl to POST data using the Content-Type
multipart/form-data according to RFC 2388
However, according to PHP manual, CURLOPT_POST option will send data using application/x-www-form-urlencoded.
According to the same manual, if value of CURLOPT_POSTFIELDS is an array, the Content-Type header will be set to multipart/form-data. You may also try to set the content type explicitly as a header.
Try setting the following cURL options:
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, count($fields));
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: multipart/form-data'));
If that does not work, it might help analysing all the headers send by the command line curl using the -v parameter and try to set them. Also might be good idea to set content length header explicitly.
During setting up for our production environment we encounter a wierd error, when trying to curl through our server, it returns a plain text representation of the response including the http headers this request is over ssl.
Date: Fri, 19 Jun 2015 02:15:00 GMT
Content-Type: text/html
Content-Length: 70619
Last-Modified: Thu, 18 Jun 2015 05:30:30 GMT
Connection: keep-alive
ETag: "55825776-113db"
Accept-Ranges: bytes
<!DOCTYPE html>
<html lang="en-us">
<head>
and so on...
Any ideas on how to fix this?
Set CURLOPT_HEADER to false in your curl_setopt like so:
curl_setopt($ch, CURLOPT_HEADER, false);
CURLOPT_HEADER - TRUE to include the header in the output. (ref)
Fixed this bug, it turns out I was setting CURLOPT_HEADER instead of CURLOPT_HTTPHEADER
I have a big trouble here in my hands. Randomly my server starts returning no buffer from my PHP files. I mean, I access somefile.php, and then I do some things in the system, when I try to access somefile.php again I suddenly got a ERR_EMPTY RESPONSE (in the browser).
I have already tried in all browsers. Same thing. When I ask to anyone else access the same file in a different machine, it goes ok, but in my machine, I still seeing the error. So I have decided to do a cURL request in the page and see what's going on, the result is below.
With the following code:
<?php
$ch = curl_init("http://192.168.1.15/curiaonline/projetobase/paroquialogada.php");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_POST, true);
?>
I got:
HTTP/1.1 200 OK
Date: Tue, 16 Jun 2015 20:41:59 GMT
Server: Apache/2.4.9 (Win64) PHP/5.5.12
X-Powered-By: PHP/5.5.12
Content-Length: 7
Connection: close
Content-Type: text/html
hy test"
* Hostname was NOT found in DNS cache
* Trying 192.168.1.15...
* Connected to 192.168.1.15 (192.168.1.15) port 80 (#0)
> POST /curiaonline/projetobase/paroquialogada.php HTTP/1.1
Host: 192.168.1.15
Accept: */*
Content-Type: application/x-www-form-urlencoded
Expect: 100-continue
< HTTP/1.1 200 OK
< Date: Tue, 16 Jun 2015 20:41:59 GMT
< Server: Apache/2.4.9 (Win64) PHP/5.5.12
< X-Powered-By: PHP/5.5.12
< Content-Length: 7
< Connection: close
< Content-Type: text/html
<
* Closing connection 0
and then I change the CURLOPT_POST to FALSE I got:
* Hostname was NOT found in DNS cache
* Trying 192.168.1.15...
* Connected to 192.168.1.15 (192.168.1.15) port 80 (#0)
> GET /curiaonline/projetobase/paroquialogada.php HTTP/1.1
Host: 192.168.1.15
Accept: */*
* Empty reply from server
* Connection #0 to host 192.168.1.15 left intact
The paroquialogada.php file only contains "hy test".
I have already tried:
To disable my firewall
To disable server firewall
To search for errors in PHP error log
To search for errors in Apache error log
To clean my dns cache
To renew my windows connection
I have found the problem. It was the security software for internet banking that I have to use to access my account, since I'm a Banco do Brasil's customer. This software provided by GAS Tecnologia see chrome request as viruses, and block the responses from the server in Windows. Anyone who have a likely problem, and use Internet Banking, just disable this software.
and then I change the CURLOPT_POST to FALSE I got:
If page content requires POST to handle request. Every request must with POST request.
for example assume a page which has like below
if( isset($_POST["blabla"]) ){
//then do something
}
if part works only with POST, so consider this issue.
Don't refresh your page on browser. Submit necessary values with POST.
Here is my code:
$url='http://celebcrust.com/?p=15055';
$ch = curl_init();
curl_setopt($ch, CURLOPT_COOKIESESSION, TRUE);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, TRUE);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
$httpData = curl_exec($ch);
var_export($httpData);
This code as an interactive demo on phpdiffle.org.
Why is it still redirecting? I'm trying to get the redirected to URL. I set FOLLOWLOCATION to FALSE but still.
Okay, here is how I do debug these things quickly (it's not working always, but for first try to hit the rubber on the road for more contact, this normally does it):
Requirements: Curl for the commandline (available probably for every computer system on earth, visit the homepage if you don't have it yet):
-i is to list headers as well (use -I for HEAD request if too much data comes) and then -v for verbose (shows what goes where):
$ curl -iv 'http://celebcrust.com/?p=15055'
* Adding handle: conn: 0xa50260
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0xa50260) send_pipe: 1, recv_pipe: 0
* About to connect() to celebcrust.com port 80 (#0)
* Trying 70.32.78.224...
* Connected to celebcrust.com (70.32.78.224) port 80 (#0)
> GET /?p=15055 HTTP/1.1
> User-Agent: curl/7.30.0
> Host: celebcrust.com
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Date: Sat, 31 Aug 2013 14:29:54 GMT
Date: Sat, 31 Aug 2013 14:29:54 GMT
* Server Apache is not blacklisted
< Server: Apache
Server: Apache
< X-Pingback: http://celebcrust.com/xmlrpc.php
X-Pingback: http://celebcrust.com/xmlrpc.php
< X-Powered-By: PleskLin
X-Powered-By: PleskLin
< Content-Length: 159
Content-Length: 159
< Connection: close
Connection: close
< Content-Type: text/html; charset=UTF-8
Content-Type: text/html; charset=UTF-8
<
<META HTTP-EQUIV=Refresh CONTENT="0; URL=http://www.celebgossip.com/2013/04/willie-nelson-celebrates-80th-birthday-stoned-and-auditi
oning-for-gandalf-39425/">
* Closing connection 0
As this shows the server does not send a Location: header so this totally explains that you don't see one.
Instead it sends HTML in the response body that is parsed by hypertext client (webbrowser) for a Refresh: HTTP-equivalent header value.
That is not the buisness of curl. You need to add a HTML parser and check for these, I suggest DOMDocument with it's ->loadHTML() method.