/src/php-src/ext/standard/versioning.c
Line | Count | Source |
1 | | /* |
2 | | +----------------------------------------------------------------------+ |
3 | | | Copyright © The PHP Group and Contributors. | |
4 | | +----------------------------------------------------------------------+ |
5 | | | This source file is subject to the Modified BSD License that is | |
6 | | | bundled with this package in the file LICENSE, and is available | |
7 | | | through the World Wide Web at <https://www.php.net/license/>. | |
8 | | | | |
9 | | | SPDX-License-Identifier: BSD-3-Clause | |
10 | | +----------------------------------------------------------------------+ |
11 | | | Author: Stig Sæther Bakken <ssb@php.net> | |
12 | | +----------------------------------------------------------------------+ |
13 | | */ |
14 | | |
15 | | #include <stdio.h> |
16 | | #include <sys/types.h> |
17 | | #include <ctype.h> |
18 | | #include <stdlib.h> |
19 | | #include <string.h> |
20 | | #include "php.h" |
21 | | #include "php_versioning.h" |
22 | | |
23 | | /* {{{ php_canonicalize_version() */ |
24 | | |
25 | | PHPAPI char * |
26 | | php_canonicalize_version(const char *version) |
27 | 0 | { |
28 | 0 | size_t len = strlen(version); |
29 | 0 | char *buf = safe_emalloc(len, 2, 1), *q, lp, lq; |
30 | 0 | const char *p; |
31 | |
|
32 | 0 | if (len == 0) { |
33 | 0 | *buf = '\0'; |
34 | 0 | return buf; |
35 | 0 | } |
36 | | |
37 | 0 | p = version; |
38 | 0 | q = buf; |
39 | 0 | *q++ = lp = *p++; |
40 | |
|
41 | 0 | while (*p) { |
42 | | /* s/[-_+]/./g; |
43 | | * s/([^\d\.])([^\D\.])/$1.$2/g; |
44 | | * s/([^\D\.])([^\d\.])/$1.$2/g; |
45 | | */ |
46 | 0 | #define isdig(x) (isdigit((unsigned char)(x))&&(x)!='.') |
47 | 0 | #define isndig(x) (!isdigit((unsigned char)(x))&&(x)!='.') |
48 | 0 | #define isspecialver(x) ((x)=='-'||(x)=='_'||(x)=='+') |
49 | |
|
50 | 0 | lq = *(q - 1); |
51 | 0 | if (isspecialver(*p)) { |
52 | 0 | if (lq != '.') { |
53 | 0 | *q++ = '.'; |
54 | 0 | } |
55 | 0 | } else if ((isndig(lp) && isdig(*p)) || (isdig(lp) && isndig(*p))) { |
56 | 0 | if (lq != '.') { |
57 | 0 | *q++ = '.'; |
58 | 0 | } |
59 | 0 | *q++ = *p; |
60 | 0 | } else if (!isalnum((unsigned char)*p)) { |
61 | 0 | if (lq != '.') { |
62 | 0 | *q++ = '.'; |
63 | 0 | } |
64 | 0 | } else { |
65 | 0 | *q++ = *p; |
66 | 0 | } |
67 | 0 | lp = *p++; |
68 | 0 | } |
69 | | |
70 | | /* Check if the last component is empty (i.e. the last character is a dot) |
71 | | * and remove it if it is. */ |
72 | 0 | if (*(q - 1) == '.') { |
73 | 0 | *(q - 1) = '\0'; |
74 | 0 | } else { |
75 | 0 | *q = '\0'; |
76 | 0 | } |
77 | |
|
78 | 0 | return buf; |
79 | 0 | } |
80 | | |
81 | | /* }}} */ |
82 | | /* {{{ compare_special_version_forms() */ |
83 | | |
84 | | typedef struct { |
85 | | const char *name; |
86 | | uint8_t name_len; |
87 | | int order; |
88 | | } special_forms_t; |
89 | | |
90 | | static int compare_special_version_forms(char *form1, char *form2) |
91 | 0 | { |
92 | 0 | int found1 = -1, found2 = -1; |
93 | 0 | special_forms_t special_forms[11] = { |
94 | 0 | {ZEND_STRL("dev"), 0}, |
95 | 0 | {ZEND_STRL("alpha"), 1}, |
96 | 0 | {ZEND_STRL("a"), 1}, |
97 | 0 | {ZEND_STRL("beta"), 2}, |
98 | 0 | {ZEND_STRL("b"), 2}, |
99 | 0 | {ZEND_STRL("RC"), 3}, |
100 | 0 | {ZEND_STRL("rc"), 3}, |
101 | 0 | {ZEND_STRL("#"), 4}, |
102 | 0 | {ZEND_STRL("pl"), 5}, |
103 | 0 | {ZEND_STRL("p"), 5}, |
104 | 0 | {NULL, 0, 0}, |
105 | 0 | }; |
106 | 0 | special_forms_t *pp; |
107 | |
|
108 | 0 | for (pp = special_forms; pp && pp->name; pp++) { |
109 | 0 | if (strncmp(form1, pp->name, pp->name_len) == 0) { |
110 | 0 | found1 = pp->order; |
111 | 0 | break; |
112 | 0 | } |
113 | 0 | } |
114 | 0 | for (pp = special_forms; pp && pp->name; pp++) { |
115 | 0 | if (strncmp(form2, pp->name, pp->name_len) == 0) { |
116 | 0 | found2 = pp->order; |
117 | 0 | break; |
118 | 0 | } |
119 | 0 | } |
120 | 0 | return ZEND_NORMALIZE_BOOL(found1 - found2); |
121 | 0 | } |
122 | | |
123 | | /* }}} */ |
124 | | /* {{{ php_version_compare() */ |
125 | | |
126 | | PHPAPI int |
127 | | php_version_compare(const char *orig_ver1, const char *orig_ver2) |
128 | 0 | { |
129 | 0 | char *ver1; |
130 | 0 | char *ver2; |
131 | 0 | char *p1, *p2, *n1, *n2; |
132 | 0 | long l1, l2; |
133 | 0 | int compare = 0; |
134 | |
|
135 | 0 | if (!*orig_ver1 || !*orig_ver2) { |
136 | 0 | if (!*orig_ver1 && !*orig_ver2) { |
137 | 0 | return 0; |
138 | 0 | } else { |
139 | 0 | return *orig_ver1 ? 1 : -1; |
140 | 0 | } |
141 | 0 | } |
142 | 0 | if (orig_ver1[0] == '#') { |
143 | 0 | ver1 = estrdup(orig_ver1); |
144 | 0 | } else { |
145 | 0 | ver1 = php_canonicalize_version(orig_ver1); |
146 | 0 | } |
147 | 0 | if (orig_ver2[0] == '#') { |
148 | 0 | ver2 = estrdup(orig_ver2); |
149 | 0 | } else { |
150 | 0 | ver2 = php_canonicalize_version(orig_ver2); |
151 | 0 | } |
152 | 0 | p1 = n1 = ver1; |
153 | 0 | p2 = n2 = ver2; |
154 | 0 | while (*p1 && *p2 && n1 && n2) { |
155 | 0 | if ((n1 = strchr(p1, '.')) != NULL) { |
156 | 0 | *n1 = '\0'; |
157 | 0 | } |
158 | 0 | if ((n2 = strchr(p2, '.')) != NULL) { |
159 | 0 | *n2 = '\0'; |
160 | 0 | } |
161 | 0 | if (isdigit((unsigned char)*p1) && isdigit((unsigned char)*p2)) { |
162 | | /* compare element numerically */ |
163 | 0 | l1 = strtol(p1, NULL, 10); |
164 | 0 | l2 = strtol(p2, NULL, 10); |
165 | 0 | compare = ZEND_NORMALIZE_BOOL(l1 - l2); |
166 | 0 | } else if (!isdigit((unsigned char)*p1) && !isdigit((unsigned char)*p2)) { |
167 | | /* compare element names */ |
168 | 0 | compare = compare_special_version_forms(p1, p2); |
169 | 0 | } else { |
170 | | /* mix of names and numbers */ |
171 | 0 | if (isdigit((unsigned char)*p1)) { |
172 | 0 | compare = compare_special_version_forms("#N#", p2); |
173 | 0 | } else { |
174 | 0 | compare = compare_special_version_forms(p1, "#N#"); |
175 | 0 | } |
176 | 0 | } |
177 | 0 | if (compare != 0) { |
178 | 0 | break; |
179 | 0 | } |
180 | 0 | if (n1 != NULL) { |
181 | 0 | p1 = n1 + 1; |
182 | 0 | } |
183 | 0 | if (n2 != NULL) { |
184 | 0 | p2 = n2 + 1; |
185 | 0 | } |
186 | 0 | } |
187 | 0 | if (compare == 0) { |
188 | 0 | if (n1 != NULL) { |
189 | 0 | if (isdigit((unsigned char)*p1)) { |
190 | 0 | compare = 1; |
191 | 0 | } else { |
192 | 0 | compare = php_version_compare(p1, "#N#"); |
193 | 0 | } |
194 | 0 | } else if (n2 != NULL) { |
195 | 0 | if (isdigit((unsigned char)*p2)) { |
196 | 0 | compare = -1; |
197 | 0 | } else { |
198 | 0 | compare = php_version_compare("#N#", p2); |
199 | 0 | } |
200 | 0 | } |
201 | 0 | } |
202 | 0 | efree(ver1); |
203 | 0 | efree(ver2); |
204 | 0 | return compare; |
205 | 0 | } |
206 | | |
207 | | /* }}} */ |
208 | | /* {{{ Compares two "PHP-standardized" version number strings */ |
209 | | |
210 | | PHP_FUNCTION(version_compare) |
211 | 0 | { |
212 | 0 | char *v1, *v2; |
213 | 0 | zend_string *op = NULL; |
214 | 0 | size_t v1_len, v2_len; |
215 | 0 | int compare; |
216 | |
|
217 | 0 | ZEND_PARSE_PARAMETERS_START(2, 3) |
218 | 0 | Z_PARAM_STRING(v1, v1_len) |
219 | 0 | Z_PARAM_STRING(v2, v2_len) |
220 | 0 | Z_PARAM_OPTIONAL |
221 | 0 | Z_PARAM_STR_OR_NULL(op) |
222 | 0 | ZEND_PARSE_PARAMETERS_END(); |
223 | | |
224 | 0 | compare = php_version_compare(v1, v2); |
225 | 0 | if (!op) { |
226 | 0 | RETURN_LONG(compare); |
227 | 0 | } |
228 | 0 | if (zend_string_equals_literal(op, "<") || zend_string_equals_literal(op, "lt")) { |
229 | 0 | RETURN_BOOL(compare == -1); |
230 | 0 | } |
231 | 0 | if (zend_string_equals_literal(op, "<=") || zend_string_equals_literal(op, "le")) { |
232 | 0 | RETURN_BOOL(compare != 1); |
233 | 0 | } |
234 | 0 | if (zend_string_equals_literal(op, ">") || zend_string_equals_literal(op, "gt")) { |
235 | 0 | RETURN_BOOL(compare == 1); |
236 | 0 | } |
237 | 0 | if (zend_string_equals_literal(op, ">=") || zend_string_equals_literal(op, "ge")) { |
238 | 0 | RETURN_BOOL(compare != -1); |
239 | 0 | } |
240 | 0 | if (zend_string_equals_literal(op, "==") || zend_string_equals_literal(op, "=") || zend_string_equals_literal(op, "eq")) { |
241 | 0 | RETURN_BOOL(compare == 0); |
242 | 0 | } |
243 | 0 | if (zend_string_equals_literal(op, "!=") || zend_string_equals_literal(op, "<>") || zend_string_equals_literal(op, "ne")) { |
244 | 0 | RETURN_BOOL(compare != 0); |
245 | 0 | } |
246 | | |
247 | 0 | zend_argument_value_error(3, "must be a valid comparison operator"); |
248 | 0 | } |
249 | | |
250 | | /* }}} */ |