diff --git a/ext/tidy/tests/gh20374.phpt b/ext/tidy/tests/gh20374.phpt new file mode 100644 index 0000000000000..b17706f7a2734 --- /dev/null +++ b/ext/tidy/tests/gh20374.phpt @@ -0,0 +1,169 @@ +--TEST-- +GH-20374 (PHP with tidy and custom-tags) +--EXTENSIONS-- +tidy +--CREDITS-- +franck-paul +--FILE-- +ret; + } +} + +class MyThrowingStringable { + public function __toString(): string { + throw new Error('no'); + } +} + +$values = [ + 'string blocklevel' => 'blocklevel', + 'int' => 1, + 'double overflow' => (string) (2.0**80.0), + 'numeric string int 1' => '1', + 'numeric string double 1.0' => '1.0', + 'false' => false, + 'true' => true, + 'NAN' => NAN, + 'INF' => INF, + 'object with numeric string int 0' => new MyStringable('0'), + 'object with string blocklevel' => new MyStringable('blocklevel'), + 'object with string empty' => new MyStringable('empty'), + 'object with exception' => new MyThrowingStringable, +]; + +foreach ($values as $key => $value) { + echo "--- $key ---\n"; + $str = 'test'; + + $config = [ + 'custom-tags' => $value, + ]; + + $tidy = new tidy(); + try { + $tidy->parseString($str, $config, 'utf8'); + echo $tidy->value, "\n"; + } catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; + } +} + +?> +--EXPECT-- +--- string blocklevel --- + + + + + +test + + +--- int --- + + + + + +test + + +--- double overflow --- + + + + + +test + + +--- numeric string int 1 --- + + + + + +test + + +--- numeric string double 1.0 --- + + + + + +test + + +--- false --- + + + + + +test + + +--- true --- + + + + + +test + + +--- NAN --- + + + + + +test + + +--- INF --- + + + + + +test + + +--- object with numeric string int 0 --- + + + + + +test + + +--- object with string blocklevel --- + + + + + +test + + +--- object with string empty --- + + + + + + +test + + +--- object with exception --- +Error: no diff --git a/ext/tidy/tidy.c b/ext/tidy/tidy.c index 434d5a8493bbe..2cbbcbeae65d2 100644 --- a/ext/tidy/tidy.c +++ b/ext/tidy/tidy.c @@ -251,10 +251,37 @@ static int _php_tidy_set_tidy_opt(TidyDoc doc, char *optname, zval *value) zend_tmp_string_release(tmp_str); break; - case TidyInteger: - lval = zval_get_long(value); - if (tidyOptSetInt(doc, tidyOptGetId(opt), lval)) { - return SUCCESS; + case TidyInteger: /* integer or enum */ + ZVAL_DEREF(value); + /* Enum will correspond to a non-numeric string or object */ + if (Z_TYPE_P(value) == IS_STRING || Z_TYPE_P(value) == IS_OBJECT) { + double dval; + str = zval_try_get_tmp_string(value, &tmp_str); + if (UNEXPECTED(!str)) { + return FAILURE; + } + uint8_t type = is_numeric_string(ZSTR_VAL(str), ZSTR_LEN(str), &lval, &dval, true); + if (type == IS_DOUBLE) { + lval = zend_dval_to_lval_cap(dval); + type = IS_LONG; + } + if (type == IS_LONG) { + if (tidyOptSetInt(doc, tidyOptGetId(opt), lval)) { + zend_tmp_string_release(tmp_str); + return SUCCESS; + } + } else { + if (tidyOptSetValue(doc, tidyOptGetId(opt), ZSTR_VAL(str))) { + zend_tmp_string_release(tmp_str); + return SUCCESS; + } + } + zend_tmp_string_release(tmp_str); + } else { + lval = zval_get_long(value); + if (tidyOptSetInt(doc, tidyOptGetId(opt), lval)) { + return SUCCESS; + } } break;