]> git.mxchange.org Git - friendica.git/blob - src/Util/HTTPInputData.php
e3b48cf58dacb0e738d9dabfebb931d4beacbace
[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                         $raw_headers = '';
99                 }
100
101                 fclose($stream);
102
103                 return $result;
104         }
105
106         private static function parseRawHeader($stream, string $raw_headers, string $boundary, array $result)
107         {
108                 $variables = $result['variables'];
109                 $files     = $result['files'];
110
111                 $headers = [];
112
113                 foreach (explode("\r\n", $raw_headers) as $header) {
114                         if (strpos($header, ':') === false) {
115                                 continue;
116                         }
117                         list($name, $value) = explode(':', $header, 2);
118                         $headers[strtolower($name)] = ltrim($value, ' ');
119                 }
120
121                 if (!isset($headers['content-disposition'])) {
122                         return ['variables' => $variables, 'files' => $files];
123                 }
124
125                 if (!preg_match('/^(.+); *name="([^"]+)"(; *filename="([^"]+)")?/', $headers['content-disposition'], $matches)) {
126                         return ['variables' => $variables, 'files' => $files];
127                 }
128
129                 $name     = $matches[2];
130                 $filename = $matches[4] ?? '';
131
132                 if (!empty($filename)) {
133                         $files[$name] = self::fetchFileData($stream, $boundary, $headers, $filename);
134                         return ['variables' => $variables, 'files' => $files];
135                 } else {
136                         $variables = self::fetchVariables($stream, $boundary, $name, $variables);
137                 }
138
139                 return ['variables' => $variables, 'files' => $files];
140         }
141
142         private static function fetchFileData($stream, string $boundary, array $headers, string $filename)
143         {
144                 $error = UPLOAD_ERR_OK;
145
146                 if (isset($headers['content-type'])) {
147                         $tmp = explode(';', $headers['content-type']);
148                         $contentType = $tmp[0];
149                 } else {
150                         $contentType = 'unknown';
151                 }
152
153                 $tmpnam     = tempnam(ini_get('upload_tmp_dir'), 'php');
154                 $fileHandle = fopen($tmpnam, 'wb');
155
156                 if ($fileHandle === false) {
157                         $error = UPLOAD_ERR_CANT_WRITE;
158                 } else {
159                         $lastLine = NULL;
160                         while (($chunk = fgets($stream, 8096)) !== false && strpos($chunk, $boundary) !== 0) {
161                                 if ($lastLine !== NULL) {
162                                         if (fwrite($fileHandle, $lastLine) === false) {
163                                                 $error = UPLOAD_ERR_CANT_WRITE;
164                                                 break;
165                                         }
166                                 }
167                                 $lastLine = $chunk;
168                         }
169
170                         if ($lastLine !== NULL && $error !== UPLOAD_ERR_CANT_WRITE) {
171                                 if (fwrite($fileHandle, rtrim($lastLine, "\r\n")) === false) {
172                                         $error = UPLOAD_ERR_CANT_WRITE;
173                                 }
174                         }
175                 }
176
177                 return [
178                         'name'     => $filename,
179                         'type'     => $contentType,
180                         'tmp_name' => $tmpnam,
181                         'error'    => $error,
182                         'size'     => filesize($tmpnam)
183                 ];
184         }
185
186         private static function fetchVariables($stream, string $boundary, string $name, array $variables)
187         {
188                 $fullValue = '';
189                 $lastLine = NULL;
190
191                 while (($chunk = fgets($stream)) !== false && strpos($chunk, $boundary) !== 0) {
192                         if ($lastLine !== NULL) {
193                                 $fullValue .= $lastLine;
194                         }
195
196                         $lastLine = $chunk;
197                 }
198
199                 if ($lastLine !== NULL) {
200                         $fullValue .= rtrim($lastLine, "\r\n");
201                 }
202
203                 if (isset($headers['content-type'])) {
204                         $encoding = '';
205
206                         foreach (explode(';', $headers['content-type']) as $part) {
207                                 if (strpos($part, 'charset') !== false) {
208                                         $part = explode($part, '=', 2);
209                                         if (isset($part[1])) {
210                                                 $encoding = $part[1];
211                                         }
212                                         break;
213                                 }
214                         }
215
216                         if ($encoding !== '' && strtoupper($encoding) !== 'UTF-8' && strtoupper($encoding) !== 'UTF8') {
217                                         $tmp = mb_convert_encoding($fullValue, 'UTF-8', $encoding);
218                                         if ($tmp !== false) {
219                                                 $fullValue = $tmp;
220                                         }
221                         }
222                 }
223
224                 $fullValue = $name . '=' . $fullValue;
225
226                 $tmp = [];
227                 parse_str($fullValue, $tmp);
228
229                 return self::expandVariables(explode('[', $name), $variables, $tmp);
230         }
231
232         private static function expandVariables(array $names, $variables, array $values)
233         {
234                 if (!is_array($variables)) {
235                         return $values;
236                 }
237
238                 $name = rtrim(array_shift($names), ']');
239                 if ($name !== '') {
240                         $name = $name . '=p';
241
242                         $tmp = [];
243                         parse_str($name, $tmp);
244
245                         $tmp  = array_keys($tmp);
246                         $name = reset($tmp);
247                 }
248
249                 if ($name === '') {
250                         $variables[] = reset($values);
251                 } elseif (isset($variables[$name]) && isset($values[$name])) {
252                         $variables[$name] = self::expandVariables($names, $variables[$name], $values[$name]);
253                 } elseif (isset($values[$name])) {
254                         $variables[$name] = $values[$name];
255                 }
256
257                 return $variables;
258         }
259 }
260 ?>