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