Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ PHP NEWS

- Standard:
. Fixed quoted_printable_decode() truncating input at NUL bytes. (Weilin Du)
. Fixed version_compare() silently truncating version strings at NUL bytes by
rejecting such input. (Weilin Du)
. Fixed version_compare() silently truncating version strings at NUL bytes.
(Weilin Du)
. Fixed bug #60110 (fclose(), file_put_contents(), copy() do not return false
properly). (Jakub Zelenka, Ilija Tovilo)
. Fixed three Windows-only proc_open() defects: an uninitialized
Expand Down
1 change: 1 addition & 0 deletions ext/standard/php_versioning.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@

PHPAPI char *php_canonicalize_version(const char *);
PHPAPI int php_version_compare(const char *, const char *);
PHPAPI int php_version_compare_ex(const char *, size_t, const char *, size_t);

#endif
38 changes: 19 additions & 19 deletions ext/standard/tests/versioning/version_compare_null_bytes.phpt
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
--TEST--
version_compare() rejects NUL bytes in version strings
version_compare() compares version strings after NUL bytes
--FILE--
<?php
foreach ([
["1.0\0suffix", '1.0'],
['1.0', "1.0\0suffix"],
["\0", '1.0'],
['1.0', "\0"],
] as [$version1, $version2]) {
try {
version_compare($version1, $version2, 'eq');
} catch (ValueError $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}
}
var_dump(version_compare('1.0', '1.0'));
var_dump(version_compare('1.0', '1.0', 'eq'));
var_dump(version_compare("1.0\0pl1", '1.0'));
var_dump(version_compare('1.0', "1.0\0pl1"));
var_dump(version_compare("1.0\0pl1", '1.0pl1'));
var_dump(version_compare("1.0\0dev", '1.0', 'lt'));
var_dump(version_compare("\0", ''));
var_dump(version_compare('', "\0"));
var_dump(version_compare("\0", "\0"));
var_dump(version_compare("\x001.0", '.1.0'));
var_dump(version_compare("1.0\0", '1.0'));
var_dump(version_compare("#\0pl1", '#.pl1'));
?>
--EXPECT--
ValueError: version_compare(): Argument #1 ($version1) must not contain any null bytes
ValueError: version_compare(): Argument #2 ($version2) must not contain any null bytes
ValueError: version_compare(): Argument #1 ($version1) must not contain any null bytes
ValueError: version_compare(): Argument #2 ($version2) must not contain any null bytes
int(1)
int(-1)
int(0)
bool(true)
int(1)
int(-1)
int(0)
int(0)
int(0)
int(0)
66 changes: 49 additions & 17 deletions ext/standard/versioning.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@

/* {{{ php_canonicalize_version() */

PHPAPI char *
php_canonicalize_version(const char *version)
static char *
php_canonicalize_version_ex(const char *version, size_t len)
{
size_t len = strlen(version);
char *buf = safe_emalloc(len * 2, sizeof(char), 1), *q, lp, lq;
const char *p;
const char *p, *end = version + len;

if (len == 0) {
*buf = '\0';
Expand All @@ -37,8 +36,11 @@ php_canonicalize_version(const char *version)
p = version;
q = buf;
*q++ = lp = *p++;
if (lp == '\0') {
q[-1] = '.';
}

while (*p) {
while (p < end) {
/* s/[-_+]/./g;
* s/([^\d\.])([^\D\.])/$1.$2/g;
* s/([^\D\.])([^\d\.])/$1.$2/g;
Expand All @@ -48,7 +50,7 @@ php_canonicalize_version(const char *version)
#define isspecialver(x) ((x)=='-'||(x)=='_'||(x)=='+')

lq = *(q - 1);
if (isspecialver(*p)) {
if (*p == '\0' || isspecialver(*p)) {
if (lq != '.') {
*q++ = '.';
}
Expand Down Expand Up @@ -78,6 +80,12 @@ php_canonicalize_version(const char *version)
return buf;
}

PHPAPI char *
php_canonicalize_version(const char *version)
{
return php_canonicalize_version_ex(version, strlen(version));
}

/* }}} */
/* {{{ compare_special_version_forms() */

Expand Down Expand Up @@ -123,31 +131,49 @@ static int compare_special_version_forms(char *form1, char *form2)
/* }}} */
/* {{{ php_version_compare() */

static char *php_copy_special_version(const char *version, size_t len)
{
char *copy = estrndup(version, len);

for (size_t i = 0; i < len; i++) {
if (copy[i] == '\0') {
copy[i] = '.';
}
}
return copy;
}

PHPAPI int
php_version_compare(const char *orig_ver1, const char *orig_ver2)
php_version_compare_ex(const char *orig_ver1, size_t ver1_len, const char *orig_ver2, size_t ver2_len)
{
char *ver1;
char *ver2;
char *p1, *p2, *n1, *n2;
long l1, l2;
int compare = 0;

if (!*orig_ver1 || !*orig_ver2) {
if (!*orig_ver1 && !*orig_ver2) {
if (ver1_len == 0 || ver2_len == 0) {
if (ver1_len == 0 && ver2_len == 0) {
return 0;
} else {
return *orig_ver1 ? 1 : -1;
return ver1_len != 0 ? 1 : -1;
}
}
if (orig_ver1[0] == '#') {
ver1 = estrdup(orig_ver1);
ver1 = php_copy_special_version(orig_ver1, ver1_len);
} else {
ver1 = php_canonicalize_version(orig_ver1);
ver1 = php_canonicalize_version_ex(orig_ver1, ver1_len);
}
if (orig_ver2[0] == '#') {
ver2 = estrdup(orig_ver2);
ver2 = php_copy_special_version(orig_ver2, ver2_len);
} else {
ver2 = php_canonicalize_version(orig_ver2);
ver2 = php_canonicalize_version_ex(orig_ver2, ver2_len);
}
if (!*ver1 || !*ver2) {
compare = *ver1 ? 1 : (*ver2 ? -1 : 0);
efree(ver1);
efree(ver2);
return compare;
}
p1 = n1 = ver1;
p2 = n2 = ver2;
Expand Down Expand Up @@ -204,6 +230,12 @@ php_version_compare(const char *orig_ver1, const char *orig_ver2)
return compare;
}

PHPAPI int
php_version_compare(const char *orig_ver1, const char *orig_ver2)
{
return php_version_compare_ex(orig_ver1, strlen(orig_ver1), orig_ver2, strlen(orig_ver2));
}

/* }}} */
/* {{{ Compares two "PHP-standardized" version number strings */

Expand All @@ -215,13 +247,13 @@ PHP_FUNCTION(version_compare)
int compare;

ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_PATH(v1, v1_len)
Z_PARAM_PATH(v2, v2_len)
Z_PARAM_STRING(v1, v1_len)
Z_PARAM_STRING(v2, v2_len)
Z_PARAM_OPTIONAL
Z_PARAM_STR_OR_NULL(op)
ZEND_PARSE_PARAMETERS_END();

compare = php_version_compare(v1, v2);
compare = php_version_compare_ex(v1, v1_len, v2, v2_len);
if (!op) {
RETURN_LONG(compare);
}
Expand Down
Loading