Coverage Report

Created: 2022-10-14 11:23

/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
   | http://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
  int order;
81
} special_forms_t;
82
83
static int
84
compare_special_version_forms(char *form1, char *form2)
85
{
86
  int found1 = -1, found2 = -1;
87
  special_forms_t special_forms[11] = {
88
    {"dev", 0},
89
    {"alpha", 1},
90
    {"a", 1},
91
    {"beta", 2},
92
    {"b", 2},
93
    {"RC", 3},
94
    {"rc", 3},
95
    {"#", 4},
96
    {"pl", 5},
97
    {"p", 5},
98
    {NULL, 0},
99
  };
100
  special_forms_t *pp;
101
102
  for (pp = special_forms; pp && pp->name; pp++) {
103
    if (strncmp(form1, pp->name, strlen(pp->name)) == 0) {
104
      found1 = pp->order;
105
      break;
106
    }
107
  }
108
  for (pp = special_forms; pp && pp->name; pp++) {
109
    if (strncmp(form2, pp->name, strlen(pp->name)) == 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
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
/* {{{ proto int|bool|null version_compare(string ver1, string ver2 [, string oper])
203
  Compares two "PHP-standardized" version number strings */
204
205
PHP_FUNCTION(version_compare)
206
{
207
  char *v1, *v2, *op = NULL;
208
  size_t v1_len, v2_len, op_len = 0;
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_STRING(op, op_len)
216
  ZEND_PARSE_PARAMETERS_END();
217
218
  compare = php_version_compare(v1, v2);
219
  if (!op) {
220
    RETURN_LONG(compare);
221
  }
222
  if (!strncmp(op, "<", op_len) || !strncmp(op, "lt", op_len)) {
223
    RETURN_BOOL(compare == -1);
224
  }
225
  if (!strncmp(op, "<=", op_len) || !strncmp(op, "le", op_len)) {
226
    RETURN_BOOL(compare != 1);
227
  }
228
  if (!strncmp(op, ">", op_len) || !strncmp(op, "gt", op_len)) {
229
    RETURN_BOOL(compare == 1);
230
  }
231
  if (!strncmp(op, ">=", op_len) || !strncmp(op, "ge", op_len)) {
232
    RETURN_BOOL(compare != -1);
233
  }
234
  if (!strncmp(op, "==", op_len) || !strncmp(op, "=", op_len) || !strncmp(op, "eq", op_len)) {
235
    RETURN_BOOL(compare == 0);
236
  }
237
  if (!strncmp(op, "!=", op_len) || !strncmp(op, "<>", op_len) || !strncmp(op, "ne", op_len)) {
238
    RETURN_BOOL(compare != 0);
239
  }
240
241
  zend_argument_value_error(3, "must be a valid comparison operator");
242
}
243
244
/* }}} */