Passed
Pull Request — master (#126)
by Arman
02:39
created

Params::getParsedFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 15
nc 2
nop 1
dl 0
loc 27
rs 9.7666
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Quantum PHP Framework
5
 *
6
 * An open source software development framework for PHP
7
 *
8
 * @package Quantum
9
 * @author Arman Ag. <[email protected]>
10
 * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
11
 * @link http://quantum.softberg.org/
12
 * @since 2.9.0
13
 */
14
15
namespace Quantum\Http\Request;
16
17
use Quantum\Libraries\Upload\File as FileUpload;
18
use Quantum\Libraries\Storage\FileSystem;
19
use Quantum\Exceptions\HttpException;
20
use Quantum\Exceptions\LangException;
21
use Quantum\Exceptions\DiException;
22
use Quantum\Environment\Server;
23
use ReflectionException;
24
use Quantum\Di\Di;
25
26
/**
27
 * Trait Params
28
 * @package Quantum\Http\Request
29
 */
30
trait Params
31
{
32
33
    /**
34
     * Gets the GET params
35
     * @return array
36
     */
37
    private static function getParams(): array
38
    {
39
        $getParams = [];
40
41
        if (!empty($_GET)) {
42
            $getParams = filter_input_array(INPUT_GET) ?: [];
43
        }
44
45
        return $getParams;
46
    }
47
48
    /**
49
     * Gets the POST params
50
     * @return array
51
     */
52
    private static function postParams(): array
53
    {
54
        $postParams = [];
55
56
        if (!empty($_POST)) {
57
            $postParams = filter_input_array(INPUT_POST) ?: [];
58
        }
59
60
        return $postParams;
61
    }
62
63
    /**
64
     * Parses the raw input parameters sent via PUT, PATCH or DELETE methods
65
     * @return array[]
66
     * @throws DiException
67
     * @throws HttpException
68
     * @throws LangException
69
     * @throws ReflectionException
70
     */
71
    private static function parsedParams(): array
72
    {
73
        $parsedParams = [
74
            'params' => [],
75
            'files' => []
76
        ];
77
78
        if (in_array(self::$__method, ['PUT', 'PATCH', 'POST'])) {
79
80
            $rawInput = file_get_contents('php://input');
81
82
            switch (self::$server->contentType(true)) {
83
                case self::CONTENT_FORM_DATA:
0 ignored issues
show
Bug introduced by
The constant Quantum\Http\Request\Params::CONTENT_FORM_DATA was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
84
                    $parsedParams = self::parseRawInput($rawInput);
85
                    break;
86
                case self::CONTENT_JSON_PAYLOAD:
0 ignored issues
show
Bug introduced by
The constant Quantum\Http\Request\Params::CONTENT_JSON_PAYLOAD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
87
                    $parsedParams['params'] = json_decode($rawInput, true);
88
                    break;
89
                case self::CONTENT_URL_ENCODED:
0 ignored issues
show
Bug introduced by
The constant Quantum\Http\Request\Params::CONTENT_URL_ENCODED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
90
                    parse_str(urldecode($rawInput), $result);
91
                    $parsedParams['params'] = $result;
92
                    break;
93
                default:
94
                    throw HttpException::contentTypeNotSupported();
95
            }
96
        }
97
98
        return $parsedParams;
99
    }
100
101
    /**
102
     * Parses the raw input
103
     * @param string $rawInput
104
     * @return array[]
105
     * @throws DiException
106
     * @throws ReflectionException
107
     */
108
    private static function parseRawInput(string $rawInput): array
109
    {
110
        $boundary = self::getBoundary();
111
112
        if ($boundary) {
113
            $blocks = self::getBlocks($boundary, $rawInput);
114
115
            return self::processBlocks($blocks);
116
        }
117
118
        return [
119
            'params' => [],
120
            'files' => []
121
        ];
122
123
    }
124
125
    /**
126
     * Gets the boundary
127
     * @return string|null
128
     */
129
    private static function getBoundary(): ?string
130
    {
131
        $contentType = (new Server)->contentType();
132
133
        if (!$contentType) {
134
            return null;
135
        }
136
137
        preg_match('/boundary=(.*)$/', $contentType, $match);
138
139
        if (!count($match)) {
140
            return null;
141
        }
142
143
        return $match[1];
144
    }
145
146
    /**
147
     * Gets the blocks
148
     * @param string $boundary
149
     * @param string $rawInput
150
     * @return array
151
     */
152
    private static function getBlocks(string $boundary, string $rawInput): array
153
    {
154
        $result = preg_split("/-+$boundary/", $rawInput);
155
        array_pop($result);
156
        return $result;
157
    }
158
159
    /**
160
     * Process blocks
161
     * @param array $blocks
162
     * @return array
163
     * @throws DiException
164
     * @throws ReflectionException
165
     */
166
    private static function processBlocks(array $blocks): array
167
    {
168
        $params = [];
169
        $files = [];
170
171
        foreach ($blocks as $id => $block) {
172
            if (empty($block)) {
173
                continue;
174
            }
175
176
            if (strpos($block, 'filename') !== false) {
177
                list($nameParam, $file) = self::getParsedFile($block);
178
179
                if (!$file) {
180
                    continue;
181
                }
182
183
                $arrayParam = self::arrayParam($nameParam);
184
185
                if (is_array($arrayParam)) {
186
                    list($name, $key) = $arrayParam;
187
188
                    if ($key === '') {
189
                        $files[$name][] = $file;
190
                    } else {
191
                        $files[$name][$key] = $file;
192
                    }
193
                } else {
194
                    $files[$nameParam] = $file;
195
                }
196
            } else if (strpos($block, 'application/octet-stream') !== false) {
197
                $params = array_merge($params, self::getParsedStream($block));
198
            } else {
199
                $params = array_merge($params, self::getParsedParameter($block));
200
            }
201
        }
202
203
        return [
204
            'params' => $params,
205
            'files' => $files
206
        ];
207
    }
208
209
    /**
210
     * @param string $block
211
     * @return array|string[]
212
     */
213
    private static function getParsedStream(string $block): array
214
    {
215
        preg_match('/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s', $block, $match);
216
217
        return [$match[1] => $match[2] ?? ''];
218
    }
219
220
    /**
221
     * Gets the parsed file
222
     * @param string $block
223
     * @return array|null
224
     * @throws DiException
225
     * @throws ReflectionException
226
     */
227
    private static function getParsedFile(string $block): ?array
228
    {
229
        list($name, $filename, $type, $content) = self::parseFileData($block);
230
231
        if (!$content) {
232
            return null;
233
        }
234
235
        $fs = Di::get(FileSystem::class);
236
237
        $tempName = tempnam(sys_get_temp_dir(), 'qt_');
238
239
        $fs->put($tempName, $content);
240
241
        $file = new FileUpload([
242
            'name' => $filename,
243
            'type' => $type,
244
            'tmp_name' => $tempName,
245
            'error' => UPLOAD_ERR_OK,
246
            'size' => $fs->size($tempName),
247
        ]);
248
249
        register_shutdown_function(function () use ($fs, $tempName) {
250
            $fs->remove($tempName);
251
        });
252
253
        return [$name, $file];
254
    }
255
256
    /**
257
     * Parses the file string
258
     * @param string $block
259
     * @return array
260
     */
261
    private static function parseFileData(string $block): array
262
    {
263
        $block = ltrim($block, "\r\n");
264
265
        list($rawHeaders, $content) = explode("\r\n\r\n", $block);
266
267
        list($name, $filename, $contentType) = self::parseHeaders($rawHeaders);
268
269
        $content = substr($content, 0, strlen($content) - 2);
270
271
        return [
272
            $name,
273
            $filename,
274
            $contentType,
275
            $content
276
        ];
277
    }
278
279
    /**
280
     * Parses and returns the parameter
281
     * @param string $block
282
     * @return array
283
     */
284
    private static function getParsedParameter(string $block): array
285
    {
286
        $data = [];
287
288
        if (preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $block, $match)) {
289
            if (preg_match('/^(.*)\[\]$/i', $match[1], $tmp)) {
290
                $data[$tmp[1]][] = $match[2] ?? '';
291
            } else {
292
                $data[$match[1]] = $match[2] ?? '';
293
            }
294
        }
295
296
        return $data;
297
    }
298
299
    /**
300
     * Parses the header information of the file string
301
     * @param string $rawHeaders
302
     * @return array
303
     */
304
    private static function parseHeaders(string $rawHeaders): array
305
    {
306
        $rawHeaders = explode("\r\n", $rawHeaders);
307
308
        $name = '-unknown-';
309
        $filename = '-unknown-';
310
        $contentType = 'application/octet-stream';
311
312
        foreach ($rawHeaders as $header) {
313
314
            list($key, $value) = explode(':', $header);
315
316
            if ($key == 'Content-Type') {
317
                $contentType = ltrim($value, ' ');
318
            }
319
320
            if (preg_match('/name=\"([^\"]*)\"/', $header, $match)) {
321
                $name = $match[1];
322
            }
323
324
            if (preg_match('/filename=\"([^\"]*)\"/', $header, $match)) {
325
                $filename = $match[1];
326
            }
327
        }
328
329
        return [$name, $filename, $contentType];
330
    }
331
332
    /**
333
     * Finds if the param is array type or not
334
     * @param string $parameter
335
     * @return array|string
336
     */
337
    private static function arrayParam(string $parameter)
338
    {
339
        if (strpos($parameter, '[') !== false) {
340
            if (preg_match('/^([^[]*)\[([^]]*)\](.*)$/', $parameter, $match)) {
341
                return [$match[1], $match[2]];
342
            }
343
        }
344
345
        return $parameter;
346
    }
347
348
}