Coverage Report

Created: 2026-06-02 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
/* }}} */