Completed
Push — master ( 5b4ebb...27518a )
by Andrew
01:57
created

Request::getPost()   B

Complexity

Conditions 9
Paths 11

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.0555
c 0
b 0
f 0
cc 9
nc 11
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
final 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
     * Remove all uploaded files
69
     */
70
    public function cleanUploadedFiles(): void
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
     * Set a buffer size to read uploaded files
79
     */
80
    public static function setBufferSize(int $size): void
81
    {
82
        static::$bufferSize = $size;
0 ignored issues
show
Comprehensibility introduced by
Since PHPFastCGI\FastCGIDaemon\Http\Request is declared final, using late-static binding will have no effect. You might want to replace static with self instead.

Late static binding only has effect in subclasses. A final class cannot be extended anymore so late static binding cannot occurr. Consider replacing static:: with self::.

To learn more about late static binding, please refer to the PHP core documentation.

Loading history...
83
    }
84
85
    public static function getBufferSize(): int
86
    {
87
        return static::$bufferSize;
0 ignored issues
show
Comprehensibility introduced by
Since PHPFastCGI\FastCGIDaemon\Http\Request is declared final, using late-static binding will have no effect. You might want to replace static with self instead.

Late static binding only has effect in subclasses. A final class cannot be extended anymore so late static binding cannot occurr. Consider replacing static:: with self::.

To learn more about late static binding, please refer to the PHP core documentation.

Loading history...
88
    }
89
90
    public static function setUploadDir(string $dir): void
91
    {
92
        static::$uploadDir = $dir;
0 ignored issues
show
Comprehensibility introduced by
Since PHPFastCGI\FastCGIDaemon\Http\Request is declared final, using late-static binding will have no effect. You might want to replace static with self instead.

Late static binding only has effect in subclasses. A final class cannot be extended anymore so late static binding cannot occurr. Consider replacing static:: with self::.

To learn more about late static binding, please refer to the PHP core documentation.

Loading history...
93
    }
94
95
    public static function getUploadDir(): string
96
    {
97
        return static::$uploadDir ?: sys_get_temp_dir();
0 ignored issues
show
Comprehensibility introduced by
Since PHPFastCGI\FastCGIDaemon\Http\Request is declared final, using late-static binding will have no effect. You might want to replace static with self instead.

Late static binding only has effect in subclasses. A final class cannot be extended anymore so late static binding cannot occurr. Consider replacing static:: with self::.

To learn more about late static binding, please refer to the PHP core documentation.

Loading history...
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103
    public function getQuery()
104
    {
105
        $query = null;
106
107
        if (isset($this->params['QUERY_STRING'])) {
108
            parse_str($this->params['QUERY_STRING'], $query);
109
        }
110
111
        return $query ?: [];
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function getPost()
118
    {
119
        $post = null;
120
121
        if (isset($this->params['REQUEST_METHOD']) && isset($this->params['CONTENT_TYPE'])) {
122
            $requestMethod = $this->params['REQUEST_METHOD'];
123
            $contentType   = $this->params['CONTENT_TYPE'];
124
125
            if (strcasecmp($requestMethod, 'POST') === 0 && stripos($contentType, 'multipart/form-data') === 0) {
126
                if (preg_match('/boundary=(?P<quote>[\'"]?)(.*)(?P=quote)/', $contentType, $matches)) {
127
                    list($postData, $this->uploadedFiles) = $this->parseMultipartFormData($this->stdin, $matches[2]);
128
                    parse_str($postData, $post);
129
130
                    return $post;
131
                }
132
            }
133
134
            if (strcasecmp($requestMethod, 'POST') === 0 && stripos($contentType, 'application/x-www-form-urlencoded') === 0) {
135
                $postData = stream_get_contents($this->stdin);
136
                rewind($this->stdin);
137
138
                parse_str($postData, $post);
139
            }
140
        }
141
142
        return $post ?: [];
143
    }
144
145
    private function parseMultipartFormData($stream, $boundary) {
146
        $post = "";
147
        $files = [];
148
        $fieldType = $fieldName = $filename = $mimeType = null;
149
        $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...
150
151
        while (!feof($stream)) {
152
            $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...
153
            $buffer = stream_get_line($stream, static::$bufferSize,  "\n" . ($getContent ? '--'.$boundary : ''));
0 ignored issues
show
Comprehensibility introduced by
Since PHPFastCGI\FastCGIDaemon\Http\Request is declared final, using late-static binding will have no effect. You might want to replace static with self instead.

Late static binding only has effect in subclasses. A final class cannot be extended anymore so late static binding cannot occurr. Consider replacing static:: with self::.

To learn more about late static binding, please refer to the PHP core documentation.

Loading history...
154
            $buffer = trim($buffer, "\r");
155
156
            // Find the empty line between headers and body
157
            if ($inHeader && strlen($buffer) == 0) {
158
                $inHeader = false;
159
160
                continue;
161
            }
162
163
            if ($getContent) {
164
                if ($fieldType === 'data') {
165
                    $post .= (isset($post[0]) ? '&' : '') . $fieldName . "=" . urlencode($buffer);
166
                } elseif ($fieldType === 'file' && $filename) {
167
                    $tmpPath = tempnam($this->getUploadDir(), 'fastcgi_upload');
168
                    $err = file_put_contents($tmpPath, $buffer);
169
                    $files[$fieldName] = [
170
                        'type' => $mimeType ?: 'application/octet-stream',
171
                        'name' => $filename,
172
                        'tmp_name' => $tmpPath,
173
                        'error' => ($err === false) ? true : 0,
174
                        'size' => filesize($tmpPath),
175
                    ];
176
                    $filename = $mimeType = null;
177
                }
178
                $fieldName = $fieldType = null;
179
180
                continue;
181
            }
182
183
            // Assert: We may be in the header, lets try to find 'Content-Disposition' and 'Content-Type'.
184
            if (strpos($buffer, 'Content-Disposition') === 0) {
185
                $inHeader = true;
186
                if (preg_match('/name=\"([^\"]*)\"/', $buffer, $matches)) {
187
                    $fieldName = $matches[1];
188
                }
189
                if (preg_match('/filename=\"([^\"]*)\"/', $buffer, $matches)) {
190
                    $filename = $matches[1];
191
                    $fieldType = 'file';
192
                } else {
193
                    $fieldType = 'data';
194
                }
195
            } elseif (strpos($buffer, 'Content-Type') === 0) {
196
                $inHeader = true;
197
                if (preg_match('/Content-Type: (.*)?/', $buffer, $matches)) {
198
                    $mimeType = trim($matches[1]);
199
                }
200
            }
201
        }
202
203
        return [$post, $files];
204
    }
205
206
    /**
207
     * {@inheritdoc}
208
     */
209
    public function getCookies()
210
    {
211
        $cookies = [];
212
213
        if (isset($this->params['HTTP_COOKIE'])) {
214
            $cookiePairs = explode(';', $this->params['HTTP_COOKIE']);
215
216
            foreach ($cookiePairs as $cookiePair) {
217
                list($name, $value) = explode('=', trim($cookiePair));
218
                $cookies[$name] = $value;
219
            }
220
        }
221
222
        return $cookies;
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228
    public function getStdin()
229
    {
230
        return $this->stdin;
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     */
236
    public function getServerRequest()
237
    {
238
        if (!class_exists(ServerRequest::class)) {
239
            throw new \RuntimeException('You need to install zendframework/zend-diactoros^1.8 to use PSR-7 requests.');
240
        }
241
242
        $query   = $this->getQuery();
243
        $post    = $this->getPost();
244
        $cookies = $this->getCookies();
245
246
        $server  = ServerRequestFactory::normalizeServer($this->params);
247
        $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...
248
        $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...
249
        $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...
250
251
        $files = [];
252
        foreach ($this->uploadedFiles as $file) {
253
            $files[] = createUploadedFile($file);
254
        }
255
256
        $request = new ServerRequest($server, $files, $uri, $method, $this->stdin, $headers);
257
258
        return $request
259
            ->withCookieParams($cookies)
260
            ->withQueryParams($query)
261
            ->withParsedBody($post);
262
    }
263
264
    /**
265
     * {@inheritdoc}
266
     */
267
    public function getHttpFoundationRequest()
268
    {
269
        if (!class_exists(HttpFoundationRequest::class)) {
270
            throw new \RuntimeException('You need to install symfony/http-foundation:^4.0 to use HttpFoundation requests.');
271
        }
272
273
        $query   = $this->getQuery();
274
        $post    = $this->getPost();
275
        $cookies = $this->getCookies();
276
277
        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 274 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...
278
    }
279
}
280