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