Passed
Pull Request — master (#51)
by Arman
04:26
created

Params::getBoundary()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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