]> git.mxchange.org Git - friendica.git/blob - src/Util/HTTPInputData.php
5b151a2015532631ab6f1bae5b1993be95e8613a
[friendica.git] / src / Util / HTTPInputData.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2021, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Util;
23
24 /**
25  * Derived from the work of Reid Johnson <https://codereview.stackexchange.com/users/4020/reid-johnson>
26  * @see https://codereview.stackexchange.com/questions/69882/parsing-multipart-form-data-in-php-for-put-requests
27  */
28 class HTTPInputData
29 {
30         public static function process()
31         {
32                 $content_parts = explode(';', $_SERVER['CONTENT_TYPE'] ?? 'application/x-www-form-urlencoded');
33
34                 $boundary = '';
35                 $encoding = '';
36
37                 $content_type = array_shift($content_parts);
38
39                 foreach ($content_parts as $part) {
40                         if (strpos($part, 'boundary') !== false) {
41                                 $part = explode('=', $part, 2);
42                                 if (!empty($part[1])) {
43                                         $boundary = '--' . $part[1];
44                                 }
45                         } elseif (strpos($part, 'charset') !== false) {
46                                 $part = explode('=', $part, 2);
47                                 if (!empty($part[1])) {
48                                         $encoding = $part[1];
49                                 }
50                         }
51                         if ($boundary !== '' && $encoding !== '') {
52                                 break;
53                         }
54                 }
55
56                 if ($content_type == 'multipart/form-data') {
57                         return self::fetchFromMultipart($boundary);
58                 }
59
60                 // can be handled by built in PHP functionality
61                 $content = file_get_contents('php://input');
62
63                 $variables = json_decode($content);
64
65                 if (empty($variables)) {
66                         parse_str($content, $variables);
67                 }
68
69                 return ['variables' => $variables, 'files' => []];
70         }
71
72         private static function fetchFromMultipart(string $boundary)
73         {
74                 $result = ['variables' => [], 'files' => []];
75
76                 $stream = fopen('php://input', 'rb');
77
78                 $sanity = fgets($stream, strlen($boundary) + 5);
79
80                 // malformed file, boundary should be first item
81                 if (rtrim($sanity) !== $boundary) {
82                         return $result;
83                 }
84
85                 $raw_headers = '';
86
87                 while (($chunk = fgets($stream)) !== false) {
88                         if ($chunk === $boundary) {
89                                 continue;
90                         }
91
92                         if (!empty(trim($chunk))) {
93                                 $raw_headers .= $chunk;
94                                 continue;
95                         }
96
97                         $result = self::parseRawHeader($stream, $raw_headers, $boundary, $result);
98
99                         $raw_headers = '';
100                 }
101
102                 fclose($stream);
103
104                 return $result;
105         }
106
107         private static function parseRawHeader($stream, string $raw_headers, string $boundary, array $result)
108         {
109                 $variables = $result['variables'];
110                 $files     = $result['files'];
111
112                 $headers = [];
113
114                 foreach (explode("\r\n", $raw_headers) as $header) {
115                         if (strpos($header, ':') === false) {
116                                 continue;
117                         }
118                         list($name, $value) = explode(':', $header, 2);
119
120                         $headers[strtolower($name)] = ltrim($value, ' ');
121                 }
122
123                 if (!isset($headers['content-disposition'])) {
124                         return ['variables' => $variables, 'files' => $files];
125                 }
126
127                 if (!preg_match('/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/', $headers['content-disposition'], $matches)) {
128                         return ['variables' => $variables, 'files' => $files];
129                 }
130
131                 $name     = $matches[2];
132                 $filename = $matches[4] ?? '';
133
134                 if (!empty($filename)) {
135                         $files[$name] = self::fetchFileData($stream, $boundary, $headers, $filename);
136                         return ['variables' => $variables, 'files' => $files];
137                 } else {
138                         $variables = self::fetchVariables($stream, $boundary, $name, $variables);
139                 }
140
141                 return ['variables' => $variables, 'files' => $files];
142         }
143
144         private static function fetchFileData($stream, string $boundary, array $headers, string $filename)
145         {
146                 $error = UPLOAD_ERR_OK;
147
148                 if (isset($headers['content-type'])) {
149                         $tmp = explode(';', $headers['content-type']);
150
151                         $contentType = $tmp[0];
152                 } else {
153                         $contentType = 'unknown';
154                 }
155
156                 $tmpnam     = tempnam(ini_get('upload_tmp_dir'), 'php');
157                 $fileHandle = fopen($tmpnam, 'wb');
158
159                 if ($fileHandle === false) {
160                         $error = UPLOAD_ERR_CANT_WRITE;
161                 } else {
162                         $lastLine = null;
163                         while (($chunk = fgets($stream, 8096)) !== false && strpos($chunk, $boundary) !== 0) {
164                                 if ($lastLine !== null) {
165                                         if (fwrite($fileHandle, $lastLine) === false) {
166                                                 $error = UPLOAD_ERR_CANT_WRITE;
167                                                 break;
168                                         }
169                                 }
170                                 $lastLine = $chunk;
171                         }
172
173                         if ($lastLine !== null && $error !== UPLOAD_ERR_CANT_WRITE) {
174                                 if (fwrite($fileHandle, rtrim($lastLine, "\r\n")) === false) {
175                                         $error = UPLOAD_ERR_CANT_WRITE;
176                                 }
177                         }
178                 }
179
180                 return [
181                         'name'     => $filename,
182                         'type'     => $contentType,
183                         'tmp_name' => $tmpnam,
184                         'error'    => $error,
185                         'size'     => filesize($tmpnam)
186                 ];
187         }
188
189         private static function fetchVariables($stream, string $boundary, string $name, array $variables)
190         {
191                 $fullValue = '';
192                 $lastLine  = null;
193
194                 while (($chunk = fgets($stream)) !== false && strpos($chunk, $boundary) !== 0) {
195                         if ($lastLine !== null) {
196                                 $fullValue .= $lastLine;
197                         }
198
199                         $lastLine = $chunk;
200                 }
201
202                 if ($lastLine !== null) {
203                         $fullValue .= rtrim($lastLine, "\r\n");
204                 }
205
206                 if (isset($headers['content-type'])) {
207                         $encoding = '';
208
209                         foreach (explode(';', $headers['content-type']) as $part) {
210                                 if (strpos($part, 'charset') !== false) {
211                                         $part = explode($part, '=', 2);
212                                         if (isset($part[1])) {
213                                                 $encoding = $part[1];
214                                         }
215                                         break;
216                                 }
217                         }
218
219                         if ($encoding !== '' && strtoupper($encoding) !== 'UTF-8' && strtoupper($encoding) !== 'UTF8') {
220                                 $tmp = mb_convert_encoding($fullValue, 'UTF-8', $encoding);
221                                 if ($tmp !== false) {
222                                         $fullValue = $tmp;
223                                 }
224                         }
225                 }
226
227                 $fullValue = $name . '=' . $fullValue;
228
229                 $tmp = [];
230                 parse_str($fullValue, $tmp);
231
232                 return self::expandVariables(explode('[', $name), $variables, $tmp);
233         }
234
235         private static function expandVariables(array $names, $variables, array $values)
236         {
237                 if (!is_array($variables)) {
238                         return $values;
239                 }
240
241                 $name = rtrim(array_shift($names), ']');
242                 if ($name !== '') {
243                         $name = $name . '=p';
244
245                         $tmp = [];
246                         parse_str($name, $tmp);
247
248                         $tmp  = array_keys($tmp);
249                         $name = reset($tmp);
250                 }
251
252                 if ($name === '') {
253                         $variables[] = reset($values);
254                 } elseif (isset($variables[$name]) && isset($values[$name])) {
255                         $variables[$name] = self::expandVariables($names, $variables[$name], $values[$name]);
256                 } elseif (isset($values[$name])) {
257                         $variables[$name] = $values[$name];
258                 }
259
260                 return $variables;
261         }
262 }