123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319 |
- #!/usr/bin/env php
- <?php
- /**
- * This script checks the constants defined in the curl PHP extension, against those documented on the cURL website:
- * https://curl.haxx.se/libcurl/c/symbols-in-versions.html
- *
- * See the discussion at: https://github.com/php/php-src/pull/2961
- */
- const CURL_DOC_FILE = 'https://curl.haxx.se/libcurl/c/symbols-in-versions.html';
- const SOURCE_FILE = __DIR__ . '/interface.c';
- const MIN_SUPPORTED_CURL_VERSION = '7.29.0';
- const IGNORED_CONSTANTS = [
- 'CURLOPT_PROGRESSDATA'
- ];
- const CONSTANTS_REGEX_PATTERN = '~^CURL(?:OPT|_VERSION)_[A-Z0-9_]+$~';
- $curlConstants = getCurlConstants();
- $sourceConstants = getSourceConstants();
- $notInPHP = []; // In cURL doc, but missing from PHP
- $notInCurl = []; // In the PHP source, but not in the cURL doc
- $outdated = []; // In the PHP source, but removed before the minimum supported cURL version
- foreach ($curlConstants as $name => [$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 <pre> tag in the page)
- preg_match('~<pre>([^<]+)</pre>~', $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 (in_array($name, IGNORED_CONSTANTS, true) || !preg_match(CONSTANTS_REGEX_PATTERN, $name)) {
- // not a wanted 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 (!preg_match(CONSTANTS_REGEX_PATTERN, $name)) {
- // not a wanted 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;
- }
- }
|