Skip to content

Commit 9cf20f3

Browse files
authored
filter_var() should return non empty string only when it will not be sanitized
1 parent 2bef413 commit 9cf20f3

File tree

2 files changed

+68
-2
lines changed

2 files changed

+68
-2
lines changed

src/Type/Php/FilterVarDynamicReturnTypeExtension.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@
2828
class FilterVarDynamicReturnTypeExtension implements DynamicFunctionReturnTypeExtension
2929
{
3030

31+
/**
32+
* All validation filters match 0x100.
33+
*/
34+
private const VALIDATION_FILTER_BITMASK = 0x100;
35+
3136
private ReflectionProvider $reflectionProvider;
3237

3338
private ConstantStringType $flagsString;
@@ -66,6 +71,7 @@ private function getFilterTypeMap(): array
6671
$this->getConstant('FILTER_SANITIZE_STRING') => $stringType,
6772
$this->getConstant('FILTER_SANITIZE_URL') => $stringType,
6873
$this->getConstant('FILTER_VALIDATE_BOOLEAN') => $booleanType,
74+
$this->getConstant('FILTER_VALIDATE_DOMAIN') => $stringType,
6975
$this->getConstant('FILTER_VALIDATE_EMAIL') => $stringType,
7076
$this->getConstant('FILTER_VALIDATE_FLOAT') => $floatType,
7177
$this->getConstant('FILTER_VALIDATE_INT') => $intType,
@@ -130,7 +136,9 @@ public function getTypeFromFunctionCall(
130136
$type = $this->getFilterTypeMap()[$filterValue] ?? $mixedType;
131137
$otherType = $this->getOtherType($flagsArg, $scope);
132138

133-
if ($inputType->isNonEmptyString()->yes()) {
139+
if ($inputType->isNonEmptyString()->yes()
140+
&& $type instanceof StringType
141+
&& !$this->canStringBeSanitized($filterValue, $flagsArg, $scope)) {
134142
$type = new IntersectionType([$type, new AccessoryNonEmptyStringType()]);
135143
}
136144

@@ -222,4 +230,22 @@ private function getFlagsValue(Type $exprType): Type
222230
return $exprType->getOffsetValueType($this->flagsString);
223231
}
224232

233+
private function canStringBeSanitized(int $filterValue, ?Node\Arg $flagsArg, Scope $scope): bool
234+
{
235+
// If it is a validation filter, the string will not be changed
236+
if (($filterValue & self::VALIDATION_FILTER_BITMASK) !== 0) {
237+
return false;
238+
}
239+
240+
// FILTER_DEFAULT will not sanitize, unless it has FILTER_FLAG_STRIP_LOW,
241+
// FILTER_FLAG_STRIP_HIGH, or FILTER_FLAG_STRIP_BACKTICK
242+
if ($filterValue === $this->getConstant('FILTER_DEFAULT')) {
243+
return $this->hasFlag($this->getConstant('FILTER_FLAG_STRIP_LOW'), $flagsArg, $scope)
244+
|| $this->hasFlag($this->getConstant('FILTER_FLAG_STRIP_HIGH'), $flagsArg, $scope)
245+
|| $this->hasFlag($this->getConstant('FILTER_FLAG_STRIP_BACKTICK'), $flagsArg, $scope);
246+
}
247+
248+
return true;
249+
}
250+
225251
}

tests/PHPStan/Analyser/data/filter-var-returns-non-empty-string.php

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,51 @@ public function run(string $str): void
1414
$return = filter_var($str, FILTER_DEFAULT);
1515
assertType('non-empty-string|false', $return);
1616

17-
$return = filter_var($str, FILTER_SANITIZE_STRING);
17+
$return = filter_var($str, FILTER_DEFAULT, FILTER_FLAG_STRIP_LOW);
18+
assertType('string|false', $return);
19+
20+
$return = filter_var($str, FILTER_DEFAULT, FILTER_FLAG_STRIP_HIGH);
21+
assertType('string|false', $return);
22+
23+
$return = filter_var($str, FILTER_DEFAULT, FILTER_FLAG_STRIP_BACKTICK);
24+
assertType('string|false', $return);
25+
26+
$return = filter_var($str, FILTER_VALIDATE_EMAIL);
1827
assertType('non-empty-string|false', $return);
1928

29+
$return = filter_var($str, FILTER_VALIDATE_REGEXP);
30+
assertType('non-empty-string|false', $return);
31+
32+
$return = filter_var($str, FILTER_VALIDATE_URL);
33+
assertType('non-empty-string|false', $return);
34+
35+
$return = filter_var($str, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE);
36+
assertType('non-empty-string|null', $return);
37+
38+
$return = filter_var($str, FILTER_VALIDATE_IP);
39+
assertType('non-empty-string|false', $return);
40+
41+
$return = filter_var($str, FILTER_VALIDATE_MAC);
42+
assertType('non-empty-string|false', $return);
43+
44+
$return = filter_var($str, FILTER_VALIDATE_DOMAIN);
45+
assertType('non-empty-string|false', $return);
46+
47+
$return = filter_var($str, FILTER_SANITIZE_STRING);
48+
assertType('string|false', $return);
49+
50+
$return = filter_var($str, FILTER_VALIDATE_INT);
51+
assertType('int|false', $return);
52+
2053
$str2 = '';
2154
$return = filter_var($str2, FILTER_DEFAULT);
2255
assertType('string|false', $return);
56+
57+
$return = filter_var($str2, FILTER_VALIDATE_URL);
58+
assertType('string|false', $return);
59+
60+
$str2 = 'foo';
61+
$return = filter_var($str2, FILTER_VALIDATE_INT);
62+
assertType('int|false', $return);
2363
}
2464
}

0 commit comments

Comments
 (0)