Coverage Report

Created: 2025-06-13 06:43

/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
/* }}} */