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 *
php_canonicalize_version(const char * version)28 php_canonicalize_version(const char *version)
29 {
30 size_t len = strlen(version);
31 char *buf = safe_emalloc(len, 2, 1), *q, lp, lq;
32 const char *p;
33
34 if (len == 0) {
35 *buf = '\0';
36 return buf;
37 }
38
39 p = version;
40 q = buf;
41 *q++ = lp = *p++;
42
43 while (*p) {
44 /* s/[-_+]/./g;
45 * s/([^\d\.])([^\D\.])/$1.$2/g;
46 * s/([^\D\.])([^\d\.])/$1.$2/g;
47 */
48 #define isdig(x) (isdigit(x)&&(x)!='.')
49 #define isndig(x) (!isdigit(x)&&(x)!='.')
50 #define isspecialver(x) ((x)=='-'||(x)=='_'||(x)=='+')
51
52 lq = *(q - 1);
53 if (isspecialver(*p)) {
54 if (lq != '.') {
55 *q++ = '.';
56 }
57 } else if ((isndig(lp) && isdig(*p)) || (isdig(lp) && isndig(*p))) {
58 if (lq != '.') {
59 *q++ = '.';
60 }
61 *q++ = *p;
62 } else if (!isalnum(*p)) {
63 if (lq != '.') {
64 *q++ = '.';
65 }
66 } else {
67 *q++ = *p;
68 }
69 lp = *p++;
70 }
71 *q++ = '\0';
72 return buf;
73 }
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
compare_special_version_forms(char * form1,char * form2)84 static int compare_special_version_forms(char *form1, char *form2)
85 {
86 int found1 = -1, found2 = -1;
87 special_forms_t special_forms[11] = {
88 {ZEND_STRL("dev"), 0},
89 {ZEND_STRL("alpha"), 1},
90 {ZEND_STRL("a"), 1},
91 {ZEND_STRL("beta"), 2},
92 {ZEND_STRL("b"), 2},
93 {ZEND_STRL("RC"), 3},
94 {ZEND_STRL("rc"), 3},
95 {ZEND_STRL("#"), 4},
96 {ZEND_STRL("pl"), 5},
97 {ZEND_STRL("p"), 5},
98 {NULL, 0, 0},
99 };
100 special_forms_t *pp;
101
102 for (pp = special_forms; pp && pp->name; pp++) {
103 if (strncmp(form1, pp->name, pp->name_len) == 0) {
104 found1 = pp->order;
105 break;
106 }
107 }
108 for (pp = special_forms; pp && pp->name; pp++) {
109 if (strncmp(form2, pp->name, pp->name_len) == 0) {
110 found2 = pp->order;
111 break;
112 }
113 }
114 return ZEND_NORMALIZE_BOOL(found1 - found2);
115 }
116
117 /* }}} */
118 /* {{{ php_version_compare() */
119
120 PHPAPI int
php_version_compare(const char * orig_ver1,const char * orig_ver2)121 php_version_compare(const char *orig_ver1, const char *orig_ver2)
122 {
123 char *ver1;
124 char *ver2;
125 char *p1, *p2, *n1, *n2;
126 long l1, l2;
127 int compare = 0;
128
129 if (!*orig_ver1 || !*orig_ver2) {
130 if (!*orig_ver1 && !*orig_ver2) {
131 return 0;
132 } else {
133 return *orig_ver1 ? 1 : -1;
134 }
135 }
136 if (orig_ver1[0] == '#') {
137 ver1 = estrdup(orig_ver1);
138 } else {
139 ver1 = php_canonicalize_version(orig_ver1);
140 }
141 if (orig_ver2[0] == '#') {
142 ver2 = estrdup(orig_ver2);
143 } else {
144 ver2 = php_canonicalize_version(orig_ver2);
145 }
146 p1 = n1 = ver1;
147 p2 = n2 = ver2;
148 while (*p1 && *p2 && n1 && n2) {
149 if ((n1 = strchr(p1, '.')) != NULL) {
150 *n1 = '\0';
151 }
152 if ((n2 = strchr(p2, '.')) != NULL) {
153 *n2 = '\0';
154 }
155 if (isdigit(*p1) && isdigit(*p2)) {
156 /* compare element numerically */
157 l1 = strtol(p1, NULL, 10);
158 l2 = strtol(p2, NULL, 10);
159 compare = ZEND_NORMALIZE_BOOL(l1 - l2);
160 } else if (!isdigit(*p1) && !isdigit(*p2)) {
161 /* compare element names */
162 compare = compare_special_version_forms(p1, p2);
163 } else {
164 /* mix of names and numbers */
165 if (isdigit(*p1)) {
166 compare = compare_special_version_forms("#N#", p2);
167 } else {
168 compare = compare_special_version_forms(p1, "#N#");
169 }
170 }
171 if (compare != 0) {
172 break;
173 }
174 if (n1 != NULL) {
175 p1 = n1 + 1;
176 }
177 if (n2 != NULL) {
178 p2 = n2 + 1;
179 }
180 }
181 if (compare == 0) {
182 if (n1 != NULL) {
183 if (isdigit(*p1)) {
184 compare = 1;
185 } else {
186 compare = php_version_compare(p1, "#N#");
187 }
188 } else if (n2 != NULL) {
189 if (isdigit(*p2)) {
190 compare = -1;
191 } else {
192 compare = php_version_compare("#N#", p2);
193 }
194 }
195 }
196 efree(ver1);
197 efree(ver2);
198 return compare;
199 }
200
201 /* }}} */
202 /* {{{ Compares two "PHP-standardized" version number strings */
203
PHP_FUNCTION(version_compare)204 PHP_FUNCTION(version_compare)
205 {
206 char *v1, *v2;
207 zend_string *op = NULL;
208 size_t v1_len, v2_len;
209 int compare;
210
211 ZEND_PARSE_PARAMETERS_START(2, 3)
212 Z_PARAM_STRING(v1, v1_len)
213 Z_PARAM_STRING(v2, v2_len)
214 Z_PARAM_OPTIONAL
215 Z_PARAM_STR_OR_NULL(op)
216 ZEND_PARSE_PARAMETERS_END();
217
218 compare = php_version_compare(v1, v2);
219 if (!op) {
220 RETURN_LONG(compare);
221 }
222 if (zend_string_equals_literal(op, "<") || zend_string_equals_literal(op, "lt")) {
223 RETURN_BOOL(compare == -1);
224 }
225 if (zend_string_equals_literal(op, "<=") || zend_string_equals_literal(op, "le")) {
226 RETURN_BOOL(compare != 1);
227 }
228 if (zend_string_equals_literal(op, ">") || zend_string_equals_literal(op, "gt")) {
229 RETURN_BOOL(compare == 1);
230 }
231 if (zend_string_equals_literal(op, ">=") || zend_string_equals_literal(op, "ge")) {
232 RETURN_BOOL(compare != -1);
233 }
234 if (zend_string_equals_literal(op, "==") || zend_string_equals_literal(op, "=") || zend_string_equals_literal(op, "eq")) {
235 RETURN_BOOL(compare == 0);
236 }
237 if (zend_string_equals_literal(op, "!=") || zend_string_equals_literal(op, "<>") || zend_string_equals_literal(op, "ne")) {
238 RETURN_BOOL(compare != 0);
239 }
240
241 zend_argument_value_error(3, "must be a valid comparison operator");
242 }
243
244 /* }}} */
245