curl_close()从PHP 8开始不写CURLOPT_COOKIELTS会话文件

7eumitmz  于 2023-10-15  发布在  PHP
关注(0)|答案(1)|浏览(128)

对于那些遇到 session file bug的用户,其中cURL不会在CURLOPT_COOKIEJAR中写入任何内容(尽管文件存在并且是可写的),使用以下设置:
1.PHP 8.0以上版本
1.使用PHP cURL发出的 * 有状态 * 请求

  1. CURLOPT_COOKIEJAR和/或CURLOPT_COOKIEFILE设置为 * 可读/可写 * 位置
    1.使用curl_close()关闭句柄

TL;DR;

不要依赖curl_close()。完成cURL请求后使用unset($curlHandle)$curlHandle = null

Credits:@volkerschulz

为什么会这样

问题与此有关:PHP's curl_close() documentation
curl_close()的文档所述,* 此函数无效。在PHP 8.0.0之前,此函数用于关闭资源。
CURLOPT_COOKIEJAR documentation具有误导性,说明CURLOPT_COOKIEJAR应该填写 * 当句柄关闭时,保存所有内部cookie的文件名**,例如。在调用curl_close**.* 之后

PHP 5.6、7.x、8.x交叉兼容

为了修补必须保证在多个PHP版本上工作的代码,只需遵循以下模式:

$ch = curl_init($url);
...
...
$response = curl_exec($ch);
curl_close($ch);
// always unset curl handle after having closed it.
unset($ch);

可复制示例

这是一个重现问题的示例代码。
这样使用:
1.带补丁/usr/bin/php8.x test.php --with-patch
1.不带补丁/usr/bin/php8.x test.php

<?php

// exit if not called from CLI
if (php_sapi_name() !== 'cli') {
    die('This script can only be called from CLI');
}

// exit if the major version of PHP is not 8 or greater
if (substr(phpversion(), 0, 1) < 8) {
    die('This script requires PHP 8 or greater');
}

// exit if we don't have cURL support
if (! function_exists('curl_init')) {
    die('This script requires cURL support');
}

// get the command line option "--with-patch" (if any)
$withPatch = in_array('--with-patch', $argv);
$withPatchClaim = $withPatch ? 'with the unset($ch) patch' : 'without the unset($ch) patch';

// get the major+minor version of PHP
$phpVersion = substr(phpversion(), 0, 3);

// get the version of the cUrl module (if available)
$curlVersion = curl_version()['version'];

echo "Testing with PHP {$phpVersion}, cURL {$curlVersion}. Running {$withPatchClaim}\n";

// define our base path
$basepath = dirname(__FILE__);

// setup a randomic session file for cURL in the current directory, for debugging purposes
$rand = md5(uniqid(rand(), true));
$sessionFile = "{$basepath}/{$rand}.txt";
file_put_contents($sessionFile, '');

// init a curl request to https://dba.stackexchange.com/questions/320782/any-benefit-in-replacing-a-varchar-for-an-index-in-the-main-table
// this url is intended to save cookies in the session file
$url = 'https://dba.stackexchange.com/questions/320782/any-benefit-in-replacing-a-varchar-for-an-index-in-the-main-table';
$ch = curl_init($url);
if (! $ch) {
    die('Could not initialize a cURL session');
}

curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Encoding: utf-8',
    'Accept-Charset: utf-8;q=0.7,*;q=0.7',
    'Cache-Control: no-cache',
    'Connection: Keep-Alive',
    'Referer: https://www.google.com/',
    'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0',
]);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// see https://curl.se/libcurl/c/CURLOPT_COOKIELIST.html
curl_setopt($ch, CURLOPT_COOKIELIST, 'FLUSH');
curl_setopt($ch, CURLOPT_COOKIEJAR, $sessionFile);
// this would not work even with CURLOPT_COOKIEFILE pointing to the same file
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);

// execute the request
$response = curl_exec($ch);
$error = curl_error($ch);
$effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$primaryIp = curl_getinfo($ch, CURLINFO_PRIMARY_IP);
// as of PHP 8 onwards, the curl_close() call has no effect: https://www.php.net/manual/en/function.curl-close.php
curl_close($ch);

// if we've called this script with "--with-patch", apply the @volkerschulz patch https://stackoverflow.com/a/77261355/1916292
if ($withPatch) {
    unset($ch);
    // the garbage collection is not needed.
    // it is intentionally left here to let the reader understand that the issue is related to destroying the $ch variable
    gc_collect_cycles();
}

// make the response a one-liner
$response = preg_replace('/\s\s+/', ' ', str_replace([chr(13).chr(10), chr(10), chr(9)], ' ', $response));

// print the response (cap to a max of 128 chars)
echo "GET {$effectiveUrl} ({$httpStatusCode})\n";
echo "Primary IP: {$primaryIp}\n";
echo "Error: {$error}\n";
echo 'Response: '.substr($response, 0, 128)." ...\n";

// debug the contents of the session file
echo str_repeat('-', 80)."\n";
echo "Session file: {$sessionFile}\n";

// show the difference from $withPatch and without it
if (! $withPatch) {
    echo "Session file contents without the unset(\$ch) patch (BUGGED):\n";
    echo 'Session Filesize: '.filesize($sessionFile)."\n";
    echo file_get_contents($sessionFile)."\n";
} else {
    echo "Session file contents WITH the unset(\$ch) patch (WORKING):\n";
    echo 'Session Filesize: '.filesize($sessionFile)."\n";
    echo file_get_contents($sessionFile)."\n";
}

脚本输出启用补丁

maurizio:~/test (master ?) $ php8.2 test.php
Testing with PHP 8.2, cURL 7.81.0. Running without the unset($ch) patch
GET https://dba.stackexchange.com/questions/320782/any-benefit-in-replacing-a-varchar-for-an-index-in-the-main-table (200)
Primary IP: 104.18.10.86
Error:
Response: HTTP/2 200 date .... (OMISSIS)
    ----------------------------------------------------------------------
Session file: /home/maurizio/test/100881e90c76fd3a63ee2e6b3b710fea.txt
Session file contents without the unset($ch) patch (BUGGED):
Session Filesize: 0

脚本输出启用补丁

maurizio:~/test (master ?) $ php8.2 test.php --with-patch
Testing with PHP 8.2, cURL 7.81.0. Running with the unset($ch) patch
GET https://dba.stackexchange.com/questions/320782/any-benefit-in-replacing-a-varchar-for-an-index-in-the-main-table (200)
Primary IP: 104.18.10.86
Error:
Response: HTTP/2 200 date .... (OMISSIS)
----------------------------------------------------------------------
Session file: /home/maurizio/test/aa1524b52055bad838f18fae44e83b22.txt
Session file contents WITH the unset($ch) patch (WORKING):
Session Filesize: 431
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

#HttpOnly_.stackexchange.com    TRUE    /       TRUE    1696928967      __cf_bm OMISSIS
#HttpOnly_.stackexchange.com    TRUE    /       TRUE    1728549567      prov    OMISSIS
erhoui1w

erhoui1w1#

从PHP 8.0.0开始,curl_init()不再返回 resource,而是返回 CurlHandle 类型的 object。在PHP脚本退出后,垃圾收集器执行操作之前,该对象不会自动销毁。

  • CURLOPT_COOKIESIDE * 的文档说明:

当句柄关闭时,要保存所有内部cookie的文件名,例如。在调用curl_close之后。
当然,最后一部分是误导性的,因为根据其文档,从PHP 8.0.0开始,curl_close()不再有效。
如果您想强制关闭手柄(即销毁对象),您需要

unset($ch);

或放

$ch = null;

相关问题