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