Completed
Pull Request — master (#37)
by Tobias
01:43
created

Request::getUploadDir()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
namespace PHPFastCGI\FastCGIDaemon\Http;
4
5
use Symfony\Component\HttpFoundation\Request as HttpFoundationRequest;
6
use function Zend\Diactoros\createUploadedFile;
7
use Zend\Diactoros\ServerRequest;
8
use Zend\Diactoros\ServerRequestFactory;
9
10
/**
11
 * The default implementation of the RequestInterface.
12
 */
13
class Request implements RequestInterface
14
{
15
    /**
16
     * @var int
17
     */
18
    private static $bufferSize = 10485760; // 10 MB
19
20
    /**
21
     * @var string
22
     */
23
    private static $uploadDir = null;
24
25
    /**
26
     * @var array
27
     */
28
    private $uploadedFiles = [];
29
30
    /**
31
     * @var array
32
     */
33
    private $params;
34
35
    /**
36
     * @var resource
37
     */
38
    private $stdin;
39
40
    /**
41
     * Constructor.
42
     *
43
     * @param array    $params The FastCGI server params as an associative array
44
     * @param resource $stdin  The FastCGI stdin data as a stream resource
45
     */
46
    public function __construct(array $params, $stdin)
47
    {
48
        $this->params = [];
49
50
        foreach ($params as $name => $value) {
51
            $this->params[strtoupper($name)] = $value;
52
        }
53
54
        $this->stdin  = $stdin;
55
56
        rewind($this->stdin);
57
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62
    public function getParams()
63
    {
64
        return $this->params;
65
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70
    public function cleanUploadedFiles()
71
    {
72
        foreach ($this->uploadedFiles as $file) {
73
            @unlink($file['tmp_name']);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
74
        }
75
    }
76
77
    /**
78
     * {@inheritdoc}
79
     */
80
    public static function setBufferSize($size)
81
    {
82
        static::$bufferSize = $size;
0 ignored issues
show
Bug introduced by
Since $bufferSize is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $bufferSize to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
83
    }
84
85
    /**
86
     * {@inheritdoc}
87
     */
88
    public static function getBufferSize()
89
    {
90
        return static::$bufferSize;
0 ignored issues
show
Bug introduced by
Since $bufferSize is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $bufferSize to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96
    public static function setUploadDir($dir)
97
    {
98
        static::$uploadDir = $dir;
0 ignored issues
show
Bug introduced by
Since $uploadDir is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $uploadDir to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104
    public static function getUploadDir()
105
    {
106
        return static::$uploadDir ?: sys_get_temp_dir();
0 ignored issues
show
Bug introduced by
Since $uploadDir is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $uploadDir to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112
    public function getQuery()
113
    {
114
        $query = null;
115
116
        if (isset($this->params['QUERY_STRING'])) {
117
            parse_str($this->params['QUERY_STRING'], $query);
118
        }
119
120
        return $query ?: [];
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function getPost()
127
    {
128
        $post = null;
129
130
        if (isset($this->params['REQUEST_METHOD']) && isset($this->params['CONTENT_TYPE'])) {
131
            $requestMethod = $this->params['REQUEST_METHOD'];
132
            $contentType   = $this->params['CONTENT_TYPE'];
133
134
            if (strcasecmp($requestMethod, 'POST') === 0 && stripos($contentType, 'multipart/form-data') === 0) {
135
                if (preg_match('/boundary=(?P<quote>[\'"]?)(.*)(?P=quote)/', $contentType, $matches)) {
136
                    list($postData, $this->uploadedFiles) = $this->parseMultipartFormData($this->stdin, $matches[2]);
137
                    parse_str($postData, $post);
138
139
                    return $post;
140
                }
141
            }
142
143
            if (strcasecmp($requestMethod, 'POST') === 0 && stripos($contentType, 'application/x-www-form-urlencoded') === 0) {
144
                $postData = stream_get_contents($this->stdin);
145
                rewind($this->stdin);
146
147
                parse_str($postData, $post);
148
            }
149
        }
150
151
        return $post ?: [];
152
    }
153
154
    private function parseMultipartFormData($stream, $boundary) {
155
        $post = "";
156
        $files = [];
157
        $fieldType = $fieldName = $filename = $mimeType = null;
158
        $inHeader = $getContent = false;
0 ignored issues
show
Unused Code introduced by
$getContent is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
159
160
        while (!feof($stream)) {
161
            $getContent = $fieldName && !$inHeader;
0 ignored issues
show
Bug Best Practice introduced by
The expression $fieldName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
162
            $buffer = stream_get_line($stream, static::$bufferSize,  "\n" . ($getContent ? '--'.$boundary : ''));
0 ignored issues
show
Bug introduced by
Since $bufferSize is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $bufferSize to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
163
            $buffer = trim($buffer, "\r");
164
165
            // Find the empty line between headers and body
166
            if ($inHeader && strlen($buffer) == 0) {
167
                $inHeader = false;
168
169
                continue;
170
            }
171
172
            if ($getContent) {
173
                if ($fieldType === 'data') {
174
                    $post .= (isset($post[0]) ? '&' : '') . $fieldName . "=" . urlencode($buffer);
175
                } elseif ($fieldType === 'file' && $filename) {
176
                    $tmpPath = $this->getUploadDir().'/'.substr(md5(rand().time()), 0, 16);
177
                    $err = file_put_contents($tmpPath, $buffer);
178
                    $files[$fieldName] = [
179
                        'type' => $mimeType ?: 'application/octet-stream',
180
                        'name' => $filename,
181
                        'tmp_name' => $tmpPath,
182
                        'error' => ($err === false) ? true : 0,
183
                        'size' => filesize($tmpPath),
184
                    ];
185
                    $filename = $mimeType = null;
186
                }
187
                $fieldName = $fieldType = null;
188
189
                continue;
190
            }
191
192
            // Assert: We may be in the header, lets try to find 'Content-Disposition' and 'Content-Type'.
193
            if (strpos($buffer, 'Content-Disposition') === 0) {
194
                $inHeader = true;
195
                if (preg_match('/name=\"([^\"]*)\"/', $buffer, $matches)) {
196
                    $fieldName = $matches[1];
197
                }
198
                if (preg_match('/filename=\"([^\"]*)\"/', $buffer, $matches)) {
199
                    $filename = $matches[1];
200
                    $fieldType = 'file';
201
                } else {
202
                    $fieldType = 'data';
203
                }
204
            } elseif (strpos($buffer, 'Content-Type') === 0) {
205
                $inHeader = true;
206
                if (preg_match('/Content-Type: (.*)?/', $buffer, $matches)) {
207
                    $mimeType = trim($matches[1]);
208
                }
209
            }
210
        }
211
212
        return [$post, $files];
213
    }
214
215
    /**
216
     * {@inheritdoc}
217
     */
218
    public function getCookies()
219
    {
220
        $cookies = [];
221
222
        if (isset($this->params['HTTP_COOKIE'])) {
223
            $cookiePairs = explode(';', $this->params['HTTP_COOKIE']);
224
225
            foreach ($cookiePairs as $cookiePair) {
226
                list($name, $value) = explode('=', trim($cookiePair));
227
                $cookies[$name] = $value;
228
            }
229
        }
230
231
        return $cookies;
232
    }
233
234
    /**
235
     * {@inheritdoc}
236
     */
237
    public function getStdin()
238
    {
239
        return $this->stdin;
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     */
245
    public function getServerRequest()
246
    {
247
        if (!class_exists(ServerRequest::class)) {
248
            throw new \RuntimeException('You need to install zendframework/zend-diactoros^1.8 to use PSR-7 requests.');
249
        }
250
251
        $query   = $this->getQuery();
252
        $post    = $this->getPost();
253
        $cookies = $this->getCookies();
254
255
        $server  = ServerRequestFactory::normalizeServer($this->params);
256
        $headers = ServerRequestFactory::marshalHeaders($server);
0 ignored issues
show
Deprecated Code introduced by
The method Zend\Diactoros\ServerReq...ctory::marshalHeaders() has been deprecated with message: since 1.8.0; use Zend\Diactoros\marshalHeadersFromSapi().

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
257
        $uri     = ServerRequestFactory::marshalUriFromServer($server, $headers);
0 ignored issues
show
Deprecated Code introduced by
The method Zend\Diactoros\ServerReq...:marshalUriFromServer() has been deprecated with message: since 1.8.0; use Zend\Diactoros\marshalUriFromSapi() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
258
        $method  = ServerRequestFactory::get('REQUEST_METHOD', $server, 'GET');
0 ignored issues
show
Deprecated Code introduced by
The method Zend\Diactoros\ServerRequestFactory::get() has been deprecated with message: since 1.8.0; no longer used internally.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
259
260
        $files = [];
261
        foreach ($this->uploadedFiles as $file) {
262
            $files[] = createUploadedFile($file);
263
        }
264
265
        $request = new ServerRequest($server, $files, $uri, $method, $this->stdin, $headers);
266
267
        return $request
268
            ->withCookieParams($cookies)
269
            ->withQueryParams($query)
270
            ->withParsedBody($post);
271
    }
272
273
    /**
274
     * {@inheritdoc}
275
     */
276
    public function getHttpFoundationRequest()
277
    {
278
        if (!class_exists(HttpFoundationRequest::class)) {
279
            throw new \RuntimeException('You need to install symfony/http-foundation:^4.0 to use HttpFoundation requests.');
280
        }
281
282
        $query   = $this->getQuery();
283
        $post    = $this->getPost();
284
        $cookies = $this->getCookies();
285
286
        return new HttpFoundationRequest($query, $post, [], $cookies, $this->uploadedFiles, $this->params, $this->stdin);
0 ignored issues
show
Bug introduced by
It seems like $post defined by $this->getPost() on line 283 can also be of type null; however, Symfony\Component\HttpFo...\Request::__construct() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
287
    }
288
}
289