4
4
5
5
use PhpParser \Node ;
6
6
use PHPStan \Analyser \Scope ;
7
+ use PHPStan \Php \PhpVersion ;
7
8
use PHPStan \Reflection \FunctionReflection ;
8
9
use PHPStan \Reflection \ParametersAcceptorSelector ;
10
+ use PHPStan \ShouldNotHappenException ;
9
11
use PHPStan \Type \Constant \ConstantBooleanType ;
10
12
use PHPStan \Type \DynamicFunctionReturnTypeExtension ;
13
+ use PHPStan \Type \NeverType ;
11
14
use PHPStan \Type \Type ;
12
15
use PHPStan \Type \TypeCombinator ;
13
16
use function count ;
17
+ use function is_string ;
18
+ use function parse_url ;
19
+ use function str_contains ;
20
+ use function strcasecmp ;
21
+ use function strlen ;
14
22
15
23
class CurlInitReturnTypeExtension implements DynamicFunctionReturnTypeExtension
16
24
{
17
25
26
+ /** @see https://github.com/curl/curl/blob/curl-8_9_1/lib/urldata.h#L135 */
27
+ private const CURL_MAX_INPUT_LENGTH = 8000000 ;
28
+
29
+ public function __construct (private PhpVersion $ phpVersion )
30
+ {
31
+ }
32
+
18
33
public function isFunctionSupported (FunctionReflection $ functionReflection ): bool
19
34
{
20
35
return $ functionReflection ->getName () === 'curl_init ' ;
@@ -26,10 +41,49 @@ public function getTypeFromFunctionCall(
26
41
Scope $ scope ,
27
42
): Type
28
43
{
29
- $ argsCount = count ($ functionCall ->getArgs ());
44
+ $ args = $ functionCall ->getArgs ();
45
+ $ argsCount = count ($ args );
30
46
$ returnType = ParametersAcceptorSelector::selectSingle ($ functionReflection ->getVariants ())->getReturnType ();
47
+ $ notFalseReturnType = TypeCombinator::remove ($ returnType , new ConstantBooleanType (false ));
31
48
if ($ argsCount === 0 ) {
32
- return TypeCombinator::remove ($ returnType , new ConstantBooleanType (false ));
49
+ return $ notFalseReturnType ;
50
+ }
51
+
52
+ $ urlArgType = $ scope ->getType ($ args [0 ]->value );
53
+ if ($ urlArgType ->isNull ()->yes ()) {
54
+ return $ notFalseReturnType ;
55
+ }
56
+ if ($ urlArgType ->isString ()->yes ()) {
57
+ $ urlArgValue = $ urlArgType ->getConstantScalarValues ()[0 ] ?? null ;
58
+ if ($ urlArgValue !== null ) {
59
+ if (!is_string ($ urlArgValue )) {
60
+ throw new ShouldNotHappenException ();
61
+ }
62
+ if (str_contains ($ urlArgValue , "\0" )) {
63
+ if (!$ this ->phpVersion ->throwsValueErrorForInternalFunctions ()) {
64
+ // https://github.com/php/php-src/blob/php-7.4.33/ext/curl/interface.c#L112-L115
65
+ return new ConstantBooleanType (false );
66
+ }
67
+ // https://github.com/php/php-src/blob/php-8.0.0/ext/curl/interface.c#L104-L107
68
+ return new NeverType ();
69
+ }
70
+ if ($ this ->phpVersion ->getVersionId () < 80000 ) {
71
+ // Before PHP 8.0 an unparsable URL or a file:// scheme would fail if open_basedir is used
72
+ // Since we can't detect open_basedir properly, we'll always consider a failure possible if these
73
+ // conditions are given
74
+ // https://github.com/php/php-src/blob/php-7.4.33/ext/curl/interface.c#L139-L158
75
+ $ parsedUrlArg = parse_url ($ urlArgValue );
76
+ if ($ parsedUrlArg === false || (isset ($ parsedUrlArg ['scheme ' ]) && strcasecmp ($ parsedUrlArg ['scheme ' ], 'file ' ) === 0 )) {
77
+ return $ returnType ;
78
+ }
79
+ }
80
+ if (strlen ($ urlArgValue ) > self ::CURL_MAX_INPUT_LENGTH ) {
81
+ // Since libcurl 7.65.0 this would always fail, but no current PHP version requires it at the moment
82
+ // https://github.com/curl/curl/commit/5fc28510a4664f46459d9a40187d81cc08571e60
83
+ return $ returnType ;
84
+ }
85
+ return $ notFalseReturnType ;
86
+ }
33
87
}
34
88
35
89
return $ returnType ;
0 commit comments