diff --git a/ext/curl/interface.c b/ext/curl/interface.c index ef0bc09497961..4a9af7cbff21f 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -1345,6 +1345,10 @@ PHP_MINIT_FUNCTION(curl) REGISTER_CURL_CONSTANT(CURLOPT_TCP_FASTOPEN); #endif +#if LIBCURL_VERSION_NUM >= 0x073700 /* Available since 7.55.0 */ + REGISTER_CURL_CONSTANT(CURLOPT_REQUEST_TARGET); +#endif + #if CURLOPT_FTPASCII != 0 REGISTER_CURL_CONSTANT(CURLOPT_FTPASCII); #endif @@ -2367,6 +2371,9 @@ static int _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue) /* {{{ #endif #if LIBCURL_VERSION_NUM >= 0x072d00 /* Available since 7.45.0 */ case CURLOPT_DEFAULT_PROTOCOL: +#endif +#if LIBCURL_VERSION_NUM >= 0x073700 /* Available since 7.55.0 */ + case CURLOPT_REQUEST_TARGET: #endif { zend_string *tmp_str; diff --git a/ext/curl/sync-constants.php b/ext/curl/sync-constants.php new file mode 100644 index 0000000000000..b7f8919571c54 --- /dev/null +++ b/ext/curl/sync-constants.php @@ -0,0 +1,321 @@ + [$introduced, $deprecated, $removed]) { + $inPHP = in_array($name, $sourceConstants); + + if ($removed !== null) { + if (version_compare($removed, MIN_SUPPORTED_CURL_VERSION) < 0) { + // constant removed before the minimum supported version + continue; + } + } + + if (! $inPHP) { + $notInPHP[$name] = [$introduced, $removed]; + } +} + +foreach ($sourceConstants as $name) { + if (! isset($curlConstants[$name])) { + $notInCurl[] = $name; + continue; + } + + $removed = $curlConstants[$name][2]; + + if ($removed === null) { + continue; + } + + if (version_compare($removed, MIN_SUPPORTED_CURL_VERSION) < 0) { + // constant removed before the minimum supported version + $outdated[$name] = $removed; + } +} + +$allGood = true; + +if ($notInPHP) { + uasort($notInPHP, function($a, $b) { + return version_compare($a[0], $b[0]); + }); + + $table = new AsciiTable(); + $table->add('Constant', 'Introduced', '', 'Removed', ''); + + foreach ($notInPHP as $name => [$introduced, $removed]) { + if ($removed === null) { + $removed = ''; + $removedHex = ''; + } else { + $removedHex = getHexVersion($removed); + } + + $table->add($name, $introduced, getHexVersion($introduced), $removed, $removedHex); + } + + echo "Constants missing from the PHP source:\n\n"; + echo $table, "\n"; + + $allGood = false; +} + +if ($notInCurl) { + $table = new AsciiTable(); + + foreach ($notInCurl as $name) { + $table->add($name); + } + + echo "Constants defined in the PHP source, but absent from the cURL documentation:\n\n"; + echo $table, "\n"; + + $allGood = false; +} + +if ($outdated) { + uasort($outdated, function($a, $b) { + return version_compare($a, $b); + }); + + $table = new AsciiTable(); + $table->add('Constant', 'Removed'); + + foreach ($outdated as $name => $version) { + $table->add($name, $version); + } + + echo "Constants defined in the PHP source, but removed before the minimum supported cURL version:\n\n"; + echo $table, "\n"; + + $allGood = false; +} + +if ($allGood) { + echo "All good! Source code and cURL documentation are in sync.\n"; +} + +/** + * Loads and parses the cURL constants from the online documentation. + * + * The result is an associative array where the key is the constant name, and the value is a numeric array with: + * - the introduced version + * - the deprecated version (nullable) + * - the removed version (nullable) + * + * @return array + */ +function getCurlConstants() : array +{ + $html = file_get_contents(CURL_DOC_FILE); + + // Extract the constant list from the HTML file (located in the only
 tag in the page)
+    preg_match('~
([^<]+)
~', $html, $matches); + $constantList = $matches[1]; + + /** + * Parse the cURL constant lines. Possible formats: + * + * Name Introduced Deprecated Removed + * CURLOPT_CRLFILE 7.19.0 + * CURLOPT_DNS_USE_GLOBAL_CACHE 7.9.3 7.11.1 + * CURLOPT_FTPASCII 7.1 7.11.1 7.15.5 + * CURLOPT_HTTPREQUEST 7.1 - 7.15.5 + */ + $regexp = '/^([A-Za-z0-9_]+) +([0-9\.]+)(?: +([0-9\.\-]+))?(?: +([0-9\.]+))?/m'; + preg_match_all($regexp, $constantList, $matches, PREG_SET_ORDER); + + $constants = []; + + foreach ($matches as $match) { + $name = $match[1]; + $introduced = $match[2]; + $deprecated = $match[3] ?? null; + $removed = $match[4] ?? null; + + if (strpos($name, 'CURLOPT_') !== 0) { + // not a CURLOPT_* constant + continue; + } + + if (in_array($name, IGNORED_CONSTANTS)) { + // purposefully ignored constant + continue; + } + + if ($deprecated === '-') { // deprecated version can be a hyphen + $deprecated = null; + } + + $constants[$name] = [$introduced, $deprecated, $removed]; + } + + return $constants; +} + +/** + * Parses the defined cURL constants from the PHP extension source code. + * + * The result is a numeric array whose values are the constant names. + * + * @return array + */ +function getSourceConstants() : array +{ + $source = file_get_contents(SOURCE_FILE); + + preg_match_all('/REGISTER_CURL_CONSTANT\(([A-Za-z0-9_]+)\)/', $source, $matches); + + $constants = []; + + foreach ($matches[1] as $name) { + if ($name === '__c') { // macro + continue; + } + + if (strpos($name, 'CURLOPT_') !== 0) { + // not a CURLOPT_* constant + continue; + } + + $constants[] = $name; + } + + return $constants; +} + +/** + * Converts a version number to its hex representation as used in the extension source code. + * + * Example: 7.25.1 => 0x071901 + * + * @param string $version + * + * @return string + * + * @throws \RuntimeException + */ +function getHexVersion(string $version) : string +{ + $parts = explode('.', $version); + + if (count($parts) === 2) { + $parts[] = '0'; + } + + if (count($parts) !== 3) { + throw new \RuntimeException('Invalid version number: ' . $version); + } + + $hex = '0x'; + + foreach ($parts as $value) { + if (! ctype_digit($value) || strlen($value) > 3) { + throw new \RuntimeException('Invalid version number: ' . $version); + } + + $value = (int) $value; + + if ($value > 255) { + throw new \RuntimeException('Invalid version number: ' . $version); + } + + $value = dechex($value); + + if (strlen($value) === 1) { + $value = '0' . $value; + } + + $hex .= $value; + } + + return $hex; +} + +/** + * A simple helper to create ASCII tables. + * It assumes that the same number of columns is always given to add(). + */ +class AsciiTable +{ + /** + * @var array + */ + private $values = []; + + /** + * @var array + */ + private $length = []; + + /** + * @var int + */ + private $padding = 4; + + /** + * @param string[] $values + * + * @return void + */ + public function add(string ...$values) : void + { + $this->values[] = $values; + + foreach ($values as $key => $value) { + $length = strlen($value); + + if (isset($this->length[$key])) { + $this->length[$key] = max($this->length[$key], $length); + } else { + $this->length[$key] = $length; + } + } + } + + /** + * @return string + */ + public function __toString() : string + { + $result = ''; + + foreach ($this->values as $values) { + foreach ($values as $key => $value) { + if ($key !== 0) { + $result .= str_repeat(' ', $this->padding); + } + + $result .= str_pad($value, $this->length[$key]); + } + + $result .= "\n"; + } + + return $result; + } +}