xref: /PHP-8.2/ext/curl/sync-constants.php (revision edb9f65f)
1#!/usr/bin/env php
2<?php
3
4/**
5 * This script checks the constants defined in the curl PHP extension, against those documented on the cURL website:
6 * https://curl.se/libcurl/c/symbols-in-versions.html
7 *
8 * See the discussion at: https://github.com/php/php-src/pull/2961
9 */
10
11const CURL_DOC_FILE = 'https://curl.se/libcurl/c/symbols-in-versions.html';
12
13const SOURCE_FILE = __DIR__ . '/curl_arginfo.h';
14
15const MIN_SUPPORTED_CURL_VERSION = '7.61.0';
16
17const IGNORED_CURL_CONSTANTS = [
18    'CURLOPT_PROGRESSDATA',
19    'CURLOPT_XFERINFODATA',
20];
21
22const IGNORED_PHP_CONSTANTS = [
23    'CURLOPT_BINARYTRANSFER',
24    'CURLOPT_RETURNTRANSFER',
25    'CURLOPT_SAFE_UPLOAD',
26];
27
28const CONSTANTS_REGEX_PATTERN = '~^CURL(?:E|INFO|OPT|_VERSION|_HTTP)_[A-Z0-9_]+$~';
29
30/**
31 * A simple helper to create ASCII tables.
32 * It assumes that the same number of columns is always given to add().
33 */
34class AsciiTable
35{
36    /**
37     * @var array
38     */
39    private $values = [];
40
41    /**
42     * @var array
43     */
44    private $length = [];
45
46    /**
47     * @var int
48     */
49    private $padding = 4;
50
51    /**
52     * @param string[] $values
53     *
54     * @return void
55     */
56    public function add(string ...$values) : void
57    {
58        $this->values[] = $values;
59
60        foreach ($values as $key => $value) {
61            $length = strlen($value);
62
63            if (isset($this->length[$key])) {
64                $this->length[$key] = max($this->length[$key], $length);
65            } else {
66                $this->length[$key] = $length;
67            }
68        }
69    }
70
71    /**
72     * @return string
73     */
74    public function __toString() : string
75    {
76        $result = '';
77
78        foreach ($this->values as $values) {
79            foreach ($values as $key => $value) {
80                if ($key !== 0) {
81                    $result .= str_repeat(' ', $this->padding);
82                }
83
84                $result .= str_pad($value, $this->length[$key]);
85            }
86
87            $result .= "\n";
88        }
89
90        return $result;
91    }
92}
93
94$curlConstants   = getCurlConstants();
95$sourceConstants = getSourceConstants();
96
97$notInPHP  = []; // In cURL doc, but missing from PHP
98$notInCurl = []; // In the PHP source, but not in the cURL doc
99$outdated  = []; // In the PHP source, but removed before the minimum supported cURL version
100
101foreach ($curlConstants as $name => [$introduced, $deprecated, $removed]) {
102    $inPHP = in_array($name, $sourceConstants);
103
104    if ($removed !== null) {
105        if (version_compare($removed, MIN_SUPPORTED_CURL_VERSION) <= 0) {
106            // constant removed before the minimum supported version
107            continue;
108        }
109    }
110
111    if (! $inPHP) {
112        $notInPHP[$name] = [$introduced, $removed];
113    }
114}
115
116foreach ($sourceConstants as $name) {
117    if (! isset($curlConstants[$name])) {
118        $notInCurl[] = $name;
119        continue;
120    }
121
122    $removed = $curlConstants[$name][2];
123
124    if ($removed === null) {
125        continue;
126    }
127
128    if (version_compare($removed, MIN_SUPPORTED_CURL_VERSION) <= 0) {
129        // constant removed before the minimum supported version
130        $outdated[$name] = $removed;
131    }
132}
133
134$allGood = true;
135
136if ($notInPHP) {
137    uasort($notInPHP, function($a, $b) {
138        return version_compare($a[0], $b[0]);
139    });
140
141    $table = new AsciiTable();
142    $table->add('Constant', 'Introduced', '', 'Removed', '');
143
144    foreach ($notInPHP as $name => [$introduced, $removed]) {
145        if ($removed === null) {
146            $removed = '';
147            $removedHex = '';
148        } else {
149            $removedHex = getHexVersion($removed);
150        }
151
152        $table->add($name, $introduced, getHexVersion($introduced), $removed, $removedHex);
153    }
154
155    echo "Constants missing from the PHP source:\n\n";
156    echo $table, "\n";
157
158    $allGood = false;
159}
160
161if ($notInCurl) {
162    $table = new AsciiTable();
163
164    foreach ($notInCurl as $name) {
165        $table->add($name);
166    }
167
168    echo "Constants defined in the PHP source, but absent from the cURL documentation:\n\n";
169    echo $table, "\n";
170
171    $allGood = false;
172}
173
174if ($outdated) {
175    uasort($outdated, function($a, $b) {
176        return version_compare($a, $b);
177    });
178
179    $table = new AsciiTable();
180    $table->add('Constant', 'Removed');
181
182    foreach ($outdated as $name => $version) {
183        $table->add($name, $version);
184    }
185
186    echo "Constants defined in the PHP source, but removed before the minimum supported cURL version:\n\n";
187    echo $table, "\n";
188
189    $allGood = false;
190}
191
192if ($allGood) {
193    echo "All good! Source code and cURL documentation are in sync.\n";
194}
195
196/**
197 * Loads and parses the cURL constants from the online documentation.
198 *
199 * The result is an associative array where the key is the constant name, and the value is a numeric array with:
200 * - the introduced version
201 * - the deprecated version (nullable)
202 * - the removed version (nullable)
203 *
204 * @return array
205 */
206function getCurlConstants() : array
207{
208    $html = file_get_contents(CURL_DOC_FILE);
209
210    // Extract the constant list from the HTML file (located in the only <pre> tag in the page)
211    preg_match('~<table>(.*?)</table>~s', $html, $matches);
212    $constantList = $matches[1];
213
214    /**
215     * Parse the cURL constant lines. Possible formats:
216     *
217     * Name                            Introduced    Deprecated  Removed
218     * CURLOPT_CRLFILE                 7.19.0
219     * CURLOPT_DNS_USE_GLOBAL_CACHE    7.9.3         7.11.1
220     * CURLOPT_FTPASCII                7.1           7.11.1      7.15.5
221     * CURLOPT_HTTPREQUEST             7.1           -           7.15.5
222     */
223    $regexp = '@<tr><td>(?:<a href=".*?">)?(?<const>[A-Za-z0-9_]+)(?:</a>)?</td><td>(?:<a href=".*?">)?(?<added>[\d\.]+)(?:</a>)?</td><td>(?:<a href=".*?">)?(?<deprecated>[\d\.]+)?(?:</a>)?</td><td>(<a href=".*?">)?(?<removed>[\d\.]+)?(</a>)?</td></tr>@m';
224    preg_match_all($regexp, $constantList, $matches, PREG_SET_ORDER);
225
226    $constants = [];
227
228    foreach ($matches as $match) {
229        $name       = $match['const'];
230        $introduced = $match['added'];
231        $deprecated = $match['deprecated'] ?? null;
232        $removed    = $match['removed'] ?? null;
233
234        if (in_array($name, IGNORED_CURL_CONSTANTS, true) || !preg_match(CONSTANTS_REGEX_PATTERN, $name)) {
235            // not a wanted constant
236            continue;
237        }
238
239        if ($deprecated === '-') { // deprecated version can be a hyphen
240            $deprecated = null;
241        }
242
243        $constants[$name] = [$introduced, $deprecated, $removed];
244    }
245
246    return $constants;
247}
248
249/**
250 * Parses the defined cURL constants from the PHP extension source code.
251 *
252 * The result is a numeric array whose values are the constant names.
253 *
254 * @return array
255 */
256function getSourceConstants() : array
257{
258    $source = file_get_contents(SOURCE_FILE);
259
260    preg_match_all('/REGISTER_LONG_CONSTANT\(\"\w+\", (\w+), .+\)/', $source, $matches);
261
262    $constants = [];
263
264    foreach ($matches[1] as $name) {
265        if ($name === '__c') { // macro
266            continue;
267        }
268
269        if (in_array($name, IGNORED_PHP_CONSTANTS, true) || !preg_match(CONSTANTS_REGEX_PATTERN, $name)) {
270            // not a wanted constant
271            continue;
272        }
273
274        $constants[] = $name;
275    }
276
277    return $constants;
278}
279
280/**
281 * Converts a version number to its hex representation as used in the extension source code.
282 *
283 * Example: 7.25.1 => 0x071901
284 *
285 * @param string $version
286 *
287 * @return string
288 *
289 * @throws \RuntimeException
290 */
291function getHexVersion(string $version) : string
292{
293    $parts = explode('.', $version);
294
295    if (count($parts) === 2) {
296        $parts[] = '0';
297    }
298
299    if (count($parts) !== 3) {
300        throw new \RuntimeException('Invalid version number: ' . $version);
301    }
302
303    $hex = '0x';
304
305    foreach ($parts as $value) {
306        if (! ctype_digit($value) || strlen($value) > 3) {
307            throw new \RuntimeException('Invalid version number: ' . $version);
308        }
309
310        $value = (int) $value;
311
312        if ($value > 255) {
313            throw new \RuntimeException('Invalid version number: ' . $version);
314        }
315
316        $value = dechex($value);
317
318        if (strlen($value) === 1) {
319            $value = '0' . $value;
320        }
321
322        $hex .= $value;
323    }
324
325    return $hex;
326}
327