mirror of
https://github.com/php-curl-class/php-curl-class.git
synced 2026-09-03 14:27:59 +00:00
Merge pull request #247 from Sigill/post-redirect-get
Support post-redirect-get ("PRG") requests.
This commit is contained in:
@@ -53,8 +53,28 @@ $curl->post('http://www.example.com/login/', array(
|
||||
'username' => 'myusername',
|
||||
'password' => 'mypassword',
|
||||
));
|
||||
|
||||
// Perform a post-redirect-get request (POST data and follow 303 redirections using GET requests).
|
||||
$curl = new Curl();
|
||||
$curl->setOpt(CURLOPT_FOLLOWLOCATION, true);¬
|
||||
$curl->post('http://www.example.com/login/', array(
|
||||
'username' => 'myusername',
|
||||
'password' => 'mypassword',
|
||||
));
|
||||
|
||||
// POST data and follow 303 redirections by POSTing data again.
|
||||
// Please note that 303 redirections should not be handled this way:
|
||||
// https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
|
||||
$curl = new Curl();
|
||||
$curl->setOpt(CURLOPT_FOLLOWLOCATION, true);¬
|
||||
$curl->post('http://www.example.com/login/', array(
|
||||
'username' => 'myusername',
|
||||
'password' => 'mypassword',
|
||||
), false);
|
||||
```
|
||||
|
||||
A POST request performs by default a post-redirect-get (see above). Other request methods force an option which conflicts with the post-redirect-get behavior. Due to technical limitations of PHP engines <5.5.11 and HHVM, it is not possible to reset this option. It is therefore impossible to perform a post-redirect-get request using a php-curl-class Curl object that has already been used to perform other types of requests. Either use a new php-curl-class Curl object or upgrade your PHP engine.
|
||||
|
||||
```php
|
||||
$curl = new Curl();
|
||||
$curl->setBasicAuthentication('username', 'password');
|
||||
@@ -192,7 +212,7 @@ Curl::head($url, $data = array())
|
||||
Curl::headerCallback($ch, $header)
|
||||
Curl::options($url, $data = array())
|
||||
Curl::patch($url, $data = array())
|
||||
Curl::post($url, $data = array())
|
||||
Curl::post($url, $data = array(), $post_redirect_get = false)
|
||||
Curl::progress($callback)
|
||||
Curl::put($url, $data = array())
|
||||
Curl::setBasicAuthentication($username, $password = '')
|
||||
@@ -229,7 +249,7 @@ MultiCurl::addGet($url, $data = array())
|
||||
MultiCurl::addHead($url, $data = array())
|
||||
MultiCurl::addOptions($url, $data = array())
|
||||
MultiCurl::addPatch($url, $data = array())
|
||||
MultiCurl::addPost($url, $data = array())
|
||||
MultiCurl::addPost($url, $data = array(), $post_redirect_get = false)
|
||||
MultiCurl::addPut($url, $data = array())
|
||||
MultiCurl::beforeSend($callback)
|
||||
MultiCurl::close()
|
||||
|
||||
+35
-2
@@ -505,18 +505,51 @@ class Curl
|
||||
* @access public
|
||||
* @param $url
|
||||
* @param $data
|
||||
* @param $follow_303_with_post If true, will cause 303 redirections to be followed using
|
||||
* a POST request (default: false).
|
||||
* Notes:
|
||||
* - Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set to true.
|
||||
* - According to the HTTP specs (see [1]), a 303 redirection should be followed using
|
||||
* the GET method. 301 and 302 must not.
|
||||
* - In order to force a 303 redirection to be performed using the same method, the
|
||||
* underlying cURL object must be set in a special state (the CURLOPT_CURSTOMREQUEST
|
||||
* option must be set to the method to use after the redirection). Due to a limitation
|
||||
* of the cURL extension of PHP < 5.5.11 ([2], [3]) and of HHVM, it is not possible
|
||||
* to reset this option. Using these PHP engines, it is therefore impossible to
|
||||
* restore this behavior on an existing php-curl-class Curl object.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* [1] https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.2
|
||||
* [2] https://github.com/php/php-src/pull/531
|
||||
* [3] http://php.net/ChangeLog-5.php#5.5.11
|
||||
*/
|
||||
public function post($url, $data = array())
|
||||
public function post($url, $data = array(), $follow_303_with_post = false)
|
||||
{
|
||||
if (is_array($url)) {
|
||||
$follow_303_with_post = (bool)$data;
|
||||
$data = $url;
|
||||
$url = $this->baseUrl;
|
||||
}
|
||||
|
||||
$this->setURL($url);
|
||||
$this->setOpt(CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
|
||||
if ($follow_303_with_post) {
|
||||
$this->setOpt(CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
} else {
|
||||
if (isset($this->options[CURLOPT_CUSTOMREQUEST])) {
|
||||
if ((version_compare(PHP_VERSION, '5.5.11') < 0) || defined('HHVM_VERSION')) {
|
||||
trigger_error('Due to technical limitations of PHP <= 5.5.11 and HHVM, it is not possible to '
|
||||
. 'perform a post-redirect-get request using a php-curl-class Curl object that '
|
||||
. 'has already been used to perform other types of requests. Either use a new '
|
||||
. 'php-curl-class Curl object or upgrade your PHP engine.',
|
||||
E_USER_ERROR);
|
||||
} else {
|
||||
$this->setOpt(CURLOPT_CUSTOMREQUEST, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->setOpt(CURLOPT_POST, true);
|
||||
$this->setOpt(CURLOPT_POSTFIELDS, $this->buildPostData($data));
|
||||
return $this->exec();
|
||||
|
||||
+14
-2
@@ -191,12 +191,16 @@ class MultiCurl
|
||||
* @access public
|
||||
* @param $url
|
||||
* @param $data
|
||||
* @param $post_redirect_get If true, will cause 303 redirections to be followed using
|
||||
* GET requests (default: false).
|
||||
* Note: Redirections are only followed if the CURLOPT_FOLLOWLOCATION option is set to true.
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function addPost($url, $data = array())
|
||||
public function addPost($url, $data = array(), $post_redirect_get = false)
|
||||
{
|
||||
if (is_array($url)) {
|
||||
$post_redirect_get = (bool)$data;
|
||||
$data = $url;
|
||||
$url = $this->baseUrl;
|
||||
}
|
||||
@@ -208,7 +212,15 @@ class MultiCurl
|
||||
}
|
||||
|
||||
$curl->setURL($url);
|
||||
$curl->setOpt(CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
|
||||
/*
|
||||
* For post-redirect-get requests, the CURLOPT_CUSTOMREQUEST option must not
|
||||
* be set, otherwise cURL will perform POST requests for redirections.
|
||||
*/
|
||||
if (!$post_redirect_get) {
|
||||
$curl->setOpt(CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
}
|
||||
|
||||
$curl->setOpt(CURLOPT_POST, true);
|
||||
$curl->setOpt(CURLOPT_POSTFIELDS, $curl->buildPostData($data));
|
||||
$this->addHandle($curl);
|
||||
|
||||
@@ -26,14 +26,27 @@ class Test
|
||||
}
|
||||
return $this->curl->response;
|
||||
}
|
||||
}
|
||||
|
||||
function test($instance, $before, $after)
|
||||
{
|
||||
$instance->server('request_method', $before);
|
||||
\PHPUnit_Framework_Assert::assertEquals($before, $instance->curl->responseHeaders['X-REQUEST-METHOD']);
|
||||
$instance->server('request_method', $after);
|
||||
\PHPUnit_Framework_Assert::assertEquals($after, $instance->curl->responseHeaders['X-REQUEST-METHOD']);
|
||||
/*
|
||||
* When chaining requests, the method must be forced, otherwise a
|
||||
* previously forced method might be inherited.
|
||||
* Especially, POSTs must be configured to not perform post-redirect-get.
|
||||
*/
|
||||
private function chained_request($request_method)
|
||||
{
|
||||
if ($request_method === 'POST') {
|
||||
$this->server('request_method', $request_method, array(), true);
|
||||
} else {
|
||||
$this->server('request_method', $request_method);
|
||||
}
|
||||
\PHPUnit_Framework_Assert::assertEquals($request_method, $this->curl->responseHeaders['X-REQUEST-METHOD']);
|
||||
}
|
||||
|
||||
public function chain_requests($first, $second)
|
||||
{
|
||||
$this->chained_request($first);
|
||||
$this->chained_request($second);
|
||||
}
|
||||
}
|
||||
|
||||
function create_png()
|
||||
|
||||
@@ -408,6 +408,47 @@ class CurlTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('foo=bar&file=%40not-a-file', $test->curl->response);
|
||||
}
|
||||
|
||||
public function testPostRedirectGet()
|
||||
{
|
||||
// Follow 303 redirection with GET
|
||||
$test = new Test();
|
||||
$test->curl->setOpt(CURLOPT_FOLLOWLOCATION, true);
|
||||
$this->assertEquals('Redirected: GET', $test->server('post_redirect_get', 'POST'));
|
||||
|
||||
// Follow 303 redirection with POST
|
||||
$test = new Test();
|
||||
$test->curl->setOpt(CURLOPT_FOLLOWLOCATION, true);
|
||||
$this->assertEquals('Redirected: POST', $test->server('post_redirect_get', 'POST', array(), true));
|
||||
|
||||
// On compatible PHP engines, ensure that it is possible to reuse an existing Curl object
|
||||
if ((version_compare(PHP_VERSION, '5.5.11') > 0) && !defined('HHVM_VERSION')) {
|
||||
$this->assertEquals('Redirected: GET', $test->server('post_redirect_get', 'POST'));
|
||||
}
|
||||
}
|
||||
|
||||
public function testPostRedirectGetReuseObjectIncompatibleEngine()
|
||||
{
|
||||
if ((version_compare(PHP_VERSION, '5.5.11') > 0) && !defined('HHVM_VERSION')) {
|
||||
$this->markTestSkipped('This test is not applicable to this platform.');
|
||||
}
|
||||
|
||||
try {
|
||||
// Follow 303 redirection with POST
|
||||
$test = new Test();
|
||||
$test->curl->setOpt(CURLOPT_FOLLOWLOCATION, true);
|
||||
$test->server('post_redirect_get', 'POST', array(), true);
|
||||
|
||||
// On incompatible PHP engines, reusing an existing Curl object to perform a
|
||||
// post-redirect-get request will trigger a PHP error
|
||||
$test->server('post_redirect_get', 'POST');
|
||||
|
||||
$this->assertTrue(false,
|
||||
'Reusing an existing Curl object on incompatible PHP engines shall trigger an error.');
|
||||
} catch (PHPUnit_Framework_Error $e) {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function testPutRequestMethod()
|
||||
{
|
||||
$test = new Test();
|
||||
@@ -538,6 +579,7 @@ class CurlTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertFalse(is_bool($download_test->curl->rawResponse));
|
||||
|
||||
// Remove server file.
|
||||
$download_test = new Test();
|
||||
$this->assertEquals('true', $download_test->server('upload_cleanup', 'POST', array(
|
||||
'file_path' => $uploaded_file_path,
|
||||
)));
|
||||
@@ -2457,78 +2499,78 @@ class CurlTest extends PHPUnit_Framework_TestCase
|
||||
public function testRequestMethodSuccessiveGetRequests()
|
||||
{
|
||||
$test = new Test();
|
||||
Helper\test($test, 'GET', 'POST');
|
||||
Helper\test($test, 'GET', 'PUT');
|
||||
Helper\test($test, 'GET', 'PATCH');
|
||||
Helper\test($test, 'GET', 'DELETE');
|
||||
Helper\test($test, 'GET', 'HEAD');
|
||||
Helper\test($test, 'GET', 'OPTIONS');
|
||||
$test->chain_requests('GET', 'POST');
|
||||
$test->chain_requests('GET', 'PUT');
|
||||
$test->chain_requests('GET', 'PATCH');
|
||||
$test->chain_requests('GET', 'DELETE');
|
||||
$test->chain_requests('GET', 'HEAD');
|
||||
$test->chain_requests('GET', 'OPTIONS');
|
||||
}
|
||||
|
||||
public function testRequestMethodSuccessivePostRequests()
|
||||
{
|
||||
$test = new Test();
|
||||
Helper\test($test, 'POST', 'GET');
|
||||
Helper\test($test, 'POST', 'PUT');
|
||||
Helper\test($test, 'POST', 'PATCH');
|
||||
Helper\test($test, 'POST', 'DELETE');
|
||||
Helper\test($test, 'POST', 'HEAD');
|
||||
Helper\test($test, 'POST', 'OPTIONS');
|
||||
$test->chain_requests('POST', 'GET');
|
||||
$test->chain_requests('POST', 'PUT');
|
||||
$test->chain_requests('POST', 'PATCH');
|
||||
$test->chain_requests('POST', 'DELETE');
|
||||
$test->chain_requests('POST', 'HEAD');
|
||||
$test->chain_requests('POST', 'OPTIONS');
|
||||
}
|
||||
|
||||
public function testRequestMethodSuccessivePutRequests()
|
||||
{
|
||||
$test = new Test();
|
||||
Helper\test($test, 'PUT', 'GET');
|
||||
Helper\test($test, 'PUT', 'POST');
|
||||
Helper\test($test, 'PUT', 'PATCH');
|
||||
Helper\test($test, 'PUT', 'DELETE');
|
||||
Helper\test($test, 'PUT', 'HEAD');
|
||||
Helper\test($test, 'PUT', 'OPTIONS');
|
||||
$test->chain_requests('PUT', 'GET');
|
||||
$test->chain_requests('PUT', 'POST');
|
||||
$test->chain_requests('PUT', 'PATCH');
|
||||
$test->chain_requests('PUT', 'DELETE');
|
||||
$test->chain_requests('PUT', 'HEAD');
|
||||
$test->chain_requests('PUT', 'OPTIONS');
|
||||
}
|
||||
|
||||
public function testRequestMethodSuccessivePatchRequests()
|
||||
{
|
||||
$test = new Test();
|
||||
Helper\test($test, 'PATCH', 'GET');
|
||||
Helper\test($test, 'PATCH', 'POST');
|
||||
Helper\test($test, 'PATCH', 'PUT');
|
||||
Helper\test($test, 'PATCH', 'DELETE');
|
||||
Helper\test($test, 'PATCH', 'HEAD');
|
||||
Helper\test($test, 'PATCH', 'OPTIONS');
|
||||
$test->chain_requests('PATCH', 'GET');
|
||||
$test->chain_requests('PATCH', 'POST');
|
||||
$test->chain_requests('PATCH', 'PUT');
|
||||
$test->chain_requests('PATCH', 'DELETE');
|
||||
$test->chain_requests('PATCH', 'HEAD');
|
||||
$test->chain_requests('PATCH', 'OPTIONS');
|
||||
}
|
||||
|
||||
public function testRequestMethodSuccessiveDeleteRequests()
|
||||
{
|
||||
$test = new Test();
|
||||
Helper\test($test, 'DELETE', 'GET');
|
||||
Helper\test($test, 'DELETE', 'POST');
|
||||
Helper\test($test, 'DELETE', 'PUT');
|
||||
Helper\test($test, 'DELETE', 'PATCH');
|
||||
Helper\test($test, 'DELETE', 'HEAD');
|
||||
Helper\test($test, 'DELETE', 'OPTIONS');
|
||||
$test->chain_requests('DELETE', 'GET');
|
||||
$test->chain_requests('DELETE', 'POST');
|
||||
$test->chain_requests('DELETE', 'PUT');
|
||||
$test->chain_requests('DELETE', 'PATCH');
|
||||
$test->chain_requests('DELETE', 'HEAD');
|
||||
$test->chain_requests('DELETE', 'OPTIONS');
|
||||
}
|
||||
|
||||
public function testRequestMethodSuccessiveHeadRequests()
|
||||
{
|
||||
$test = new Test();
|
||||
Helper\test($test, 'HEAD', 'GET');
|
||||
Helper\test($test, 'HEAD', 'POST');
|
||||
Helper\test($test, 'HEAD', 'PUT');
|
||||
Helper\test($test, 'HEAD', 'PATCH');
|
||||
Helper\test($test, 'HEAD', 'DELETE');
|
||||
Helper\test($test, 'HEAD', 'OPTIONS');
|
||||
$test->chain_requests('HEAD', 'GET');
|
||||
$test->chain_requests('HEAD', 'POST');
|
||||
$test->chain_requests('HEAD', 'PUT');
|
||||
$test->chain_requests('HEAD', 'PATCH');
|
||||
$test->chain_requests('HEAD', 'DELETE');
|
||||
$test->chain_requests('HEAD', 'OPTIONS');
|
||||
}
|
||||
|
||||
public function testRequestMethodSuccessiveOptionsRequests()
|
||||
{
|
||||
$test = new Test();
|
||||
Helper\test($test, 'OPTIONS', 'GET');
|
||||
Helper\test($test, 'OPTIONS', 'POST');
|
||||
Helper\test($test, 'OPTIONS', 'PUT');
|
||||
Helper\test($test, 'OPTIONS', 'PATCH');
|
||||
Helper\test($test, 'OPTIONS', 'DELETE');
|
||||
Helper\test($test, 'OPTIONS', 'HEAD');
|
||||
$test->chain_requests('OPTIONS', 'GET');
|
||||
$test->chain_requests('OPTIONS', 'POST');
|
||||
$test->chain_requests('OPTIONS', 'PUT');
|
||||
$test->chain_requests('OPTIONS', 'PATCH');
|
||||
$test->chain_requests('OPTIONS', 'DELETE');
|
||||
$test->chain_requests('OPTIONS', 'HEAD');
|
||||
}
|
||||
|
||||
public function testMemoryLeak()
|
||||
|
||||
@@ -2224,4 +2224,25 @@ class MultiCurlTest extends PHPUnit_Framework_TestCase
|
||||
$multi_curl->close();
|
||||
$this->assertFalse(is_resource($multi_curl->multiCurl));
|
||||
}
|
||||
|
||||
public function testMultiPostRedirectGet()
|
||||
{
|
||||
// Deny post-redirect-get
|
||||
$multi_curl = new MultiCurl(Test::TEST_URL);
|
||||
$multi_curl->setOpt(CURLOPT_FOLLOWLOCATION, true);
|
||||
$multi_curl->setHeader('X-DEBUG-TEST', 'post_redirect_get');
|
||||
$multi_curl->addPost(array(), false)->complete(function($instance) {
|
||||
PHPUnit_Framework_Assert::assertEquals('Redirected: POST', $instance->response);
|
||||
});
|
||||
$multi_curl->start();
|
||||
|
||||
// Allow post-redirect-get
|
||||
$multi_curl = new MultiCurl(Test::TEST_URL);
|
||||
$multi_curl->setOpt(CURLOPT_FOLLOWLOCATION, true);
|
||||
$multi_curl->setHeader('X-DEBUG-TEST', 'post_redirect_get');
|
||||
$multi_curl->addPost(array(), true)->complete(function($instance) {
|
||||
PHPUnit_Framework_Assert::assertEquals('Redirected: GET', $instance->response);
|
||||
});
|
||||
$multi_curl->start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,6 +264,24 @@ if ($test === 'http_basic_auth') {
|
||||
} elseif ($test === 'data_values') {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($data_values);
|
||||
exit;
|
||||
} elseif ($test === 'post_redirect_get') {
|
||||
if (isset($_GET['redirect'])) {
|
||||
echo "Redirected: $request_method";
|
||||
} else {
|
||||
if ($request_method === 'POST') {
|
||||
if (function_exists('http_response_code')) {
|
||||
http_response_code(303);
|
||||
} else {
|
||||
header('HTTP/1.1 303 See Other');
|
||||
}
|
||||
|
||||
header('Location: ?redirect');
|
||||
} else {
|
||||
echo "Request method is $request_method, but POST was expected";
|
||||
}
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user