Completed
Push — master ( 555540...e6ec8d )
by smiley
01:43
created

message_helpers.php ➔ r_rawurlencode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 1
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @filesource   message_helpers.php
4
 * @created      28.08.2018
5
 * @author       smiley <[email protected]>
6
 * @copyright    2018 smiley
7
 * @license      MIT
8
 */
9
10
namespace chillerlan\HTTP\Psr7;
11
12
use InvalidArgumentException;
13
use Psr\Http\Message\{MessageInterface, RequestInterface, ResponseInterface, UploadedFileInterface};
14
15
const PSR7_INCLUDES = true;
16
17
/**
18
 * @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
19
 */
20
const MIMETYPES = [
21
	'3gp'     => 'video/3gpp',
22
	'7z'      => 'application/x-7z-compressed',
23
	'aac'     => 'audio/x-aac',
24
	'ai'      => 'application/postscript',
25
	'aif'     => 'audio/x-aiff',
26
	'asc'     => 'text/plain',
27
	'asf'     => 'video/x-ms-asf',
28
	'atom'    => 'application/atom+xml',
29
	'avi'     => 'video/x-msvideo',
30
	'bmp'     => 'image/bmp',
31
	'bz2'     => 'application/x-bzip2',
32
	'cer'     => 'application/pkix-cert',
33
	'crl'     => 'application/pkix-crl',
34
	'crt'     => 'application/x-x509-ca-cert',
35
	'css'     => 'text/css',
36
	'csv'     => 'text/csv',
37
	'cu'      => 'application/cu-seeme',
38
	'deb'     => 'application/x-debian-package',
39
	'doc'     => 'application/msword',
40
	'docx'    => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
41
	'dvi'     => 'application/x-dvi',
42
	'eot'     => 'application/vnd.ms-fontobject',
43
	'eps'     => 'application/postscript',
44
	'epub'    => 'application/epub+zip',
45
	'etx'     => 'text/x-setext',
46
	'flac'    => 'audio/flac',
47
	'flv'     => 'video/x-flv',
48
	'gif'     => 'image/gif',
49
	'gz'      => 'application/gzip',
50
	'htm'     => 'text/html',
51
	'html'    => 'text/html',
52
	'ico'     => 'image/x-icon',
53
	'ics'     => 'text/calendar',
54
	'ini'     => 'text/plain',
55
	'iso'     => 'application/x-iso9660-image',
56
	'jar'     => 'application/java-archive',
57
	'jpe'     => 'image/jpeg',
58
	'jpeg'    => 'image/jpeg',
59
	'jpg'     => 'image/jpeg',
60
	'js'      => 'text/javascript',
61
	'json'    => 'application/json',
62
	'latex'   => 'application/x-latex',
63
	'log'     => 'text/plain',
64
	'm4a'     => 'audio/mp4',
65
	'm4v'     => 'video/mp4',
66
	'mid'     => 'audio/midi',
67
	'midi'    => 'audio/midi',
68
	'mov'     => 'video/quicktime',
69
	'mkv'     => 'video/x-matroska',
70
	'mp3'     => 'audio/mpeg',
71
	'mp4'     => 'video/mp4',
72
	'mp4a'    => 'audio/mp4',
73
	'mp4v'    => 'video/mp4',
74
	'mpe'     => 'video/mpeg',
75
	'mpeg'    => 'video/mpeg',
76
	'mpg'     => 'video/mpeg',
77
	'mpg4'    => 'video/mp4',
78
	'oga'     => 'audio/ogg',
79
	'ogg'     => 'audio/ogg',
80
	'ogv'     => 'video/ogg',
81
	'ogx'     => 'application/ogg',
82
	'pbm'     => 'image/x-portable-bitmap',
83
	'pdf'     => 'application/pdf',
84
	'pgm'     => 'image/x-portable-graymap',
85
	'png'     => 'image/png',
86
	'pnm'     => 'image/x-portable-anymap',
87
	'ppm'     => 'image/x-portable-pixmap',
88
	'ppt'     => 'application/vnd.ms-powerpoint',
89
	'pptx'    => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
90
	'ps'      => 'application/postscript',
91
	'qt'      => 'video/quicktime',
92
	'rar'     => 'application/x-rar-compressed',
93
	'ras'     => 'image/x-cmu-raster',
94
	'rss'     => 'application/rss+xml',
95
	'rtf'     => 'application/rtf',
96
	'sgm'     => 'text/sgml',
97
	'sgml'    => 'text/sgml',
98
	'svg'     => 'image/svg+xml',
99
	'swf'     => 'application/x-shockwave-flash',
100
	'tar'     => 'application/x-tar',
101
	'tif'     => 'image/tiff',
102
	'tiff'    => 'image/tiff',
103
	'torrent' => 'application/x-bittorrent',
104
	'ttf'     => 'application/x-font-ttf',
105
	'txt'     => 'text/plain',
106
	'wav'     => 'audio/x-wav',
107
	'webm'    => 'video/webm',
108
	'wma'     => 'audio/x-ms-wma',
109
	'wmv'     => 'video/x-ms-wmv',
110
	'woff'    => 'application/x-font-woff',
111
	'wsdl'    => 'application/wsdl+xml',
112
	'xbm'     => 'image/x-xbitmap',
113
	'xls'     => 'application/vnd.ms-excel',
114
	'xlsx'    => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
115
	'xml'     => 'application/xml',
116
	'xpm'     => 'image/x-xpixmap',
117
	'xwd'     => 'image/x-xwindowdump',
118
	'yaml'    => 'text/yaml',
119
	'yml'     => 'text/yaml',
120
	'zip'     => 'application/zip',
121
];
122
123
/**
124
 * Normalizes an array of header lines to format "Name: Value"
125
 *
126
 * @param array $headers
127
 *
128
 * @return array
129
 */
130
function normalize_request_headers(array $headers):array{
131
	$normalized_headers = [];
132
133
	foreach($headers as $key => $val){
134
135
		if(is_numeric($key)){
136
137
			if(is_string($val)){
138
				$header = explode(':', $val, 2);
139
140
				if(count($header) !== 2){
141
					continue;
142
				}
143
144
				$key = $header[0];
145
				$val = $header[1];
146
			}
147
			elseif(is_array($val)){
148
				$key = array_keys($val)[0];
149
				$val = array_values($val)[0];
150
			}
151
			else{
152
				continue;
153
			}
154
		}
155
156
		$key = strtolower(trim($key));
157
158
		$normalized_headers[$key] = trim($val);
159
	}
160
161
	return $normalized_headers;
162
}
163
164
/**
165
 * @param string|array $data
166
 *
167
 * @return string|array
168
 */
169
function r_rawurlencode($data){
170
171
	if(is_array($data)){
172
		return array_map(__NAMESPACE__.'\\r_rawurlencode', $data);
173
	}
174
175
	return rawurlencode($data);
176
}
177
178
/**
179
 * from https://github.com/abraham/twitteroauth/blob/master/src/Util.php
180
 *
181
 * @param array  $params
182
 * @param bool   $urlencode
183
 * @param string $delimiter
184
 * @param string $enclosure
185
 *
186
 * @return string
187
 */
188
function build_http_query(array $params, bool $urlencode = null, string $delimiter = null, string $enclosure = null):string{
189
190
	if(empty($params)){
191
		return '';
192
	}
193
194
	// urlencode both keys and values
195
	if($urlencode ?? true){
196
		$params = array_combine(
197
			r_rawurlencode(array_keys($params)),
198
			r_rawurlencode(array_values($params))
199
		);
200
	}
201
202
	// Parameters are sorted by name, using lexicographical byte value ordering.
203
	// Ref: Spec: 9.1.1 (1)
204
	uksort($params, 'strcmp');
205
206
	$pairs     = [];
207
	$enclosure = $enclosure ?? '';
208
209
	foreach($params as $parameter => $value){
210
211
		if(is_array($value)){
212
			// If two or more parameters share the same name, they are sorted by their value
213
			// Ref: Spec: 9.1.1 (1)
214
			// June 12th, 2010 - changed to sort because of issue 164 by hidetaka
215
			sort($value, SORT_STRING);
216
217
			foreach($value as $duplicateValue){
218
				$pairs[] = $parameter.'='.$enclosure.$duplicateValue.$enclosure;
219
			}
220
221
		}
222
		else{
223
			$pairs[] = $parameter.'='.$enclosure.$value.$enclosure;
224
		}
225
226
	}
227
228
	// For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
229
	// Each name-value pair is separated by an '&' character (ASCII code 38)
230
	return implode($delimiter ?? '&', $pairs);
231
}
232
233
const BOOLEANS_AS_BOOL = 0;
234
const BOOLEANS_AS_INT = 1;
235
const BOOLEANS_AS_STRING = 2;
236
const BOOLEANS_AS_INT_STRING = 3;
237
/**
238
 * @param iterable  $params
239
 * @param int|null  $bool_cast converts booleans to a type determined like following:
240
 *                             BOOLEANS_AS_BOOL      : unchanged boolean value (default)
241
 *                             BOOLEANS_AS_INT       : integer values 0 or 1
242
 *                             BOOLEANS_AS_STRING    : "true"/"false" strings
243
 *                             BOOLEANS_AS_INT_STRING: "0"/"1"
244
 *
245
 * @param bool|null $remove_empty remove empty and NULL values
246
 *
247
 * @return array
248
 */
249
function clean_query_params(iterable $params, int $bool_cast = null, bool $remove_empty = null):iterable{
250
	$p = [];
251
	$bool_cast = $bool_cast ?? BOOLEANS_AS_BOOL;
252
	$remove_empty = $remove_empty ?? true;
253
254
	foreach($params as $key => $value){
255
256
		if(is_bool($value)){
257
258
			if($bool_cast === BOOLEANS_AS_BOOL){
259
				$p[$key] = $value;
260
			}
261
			elseif($bool_cast === BOOLEANS_AS_INT){
262
				$p[$key] = (int)$value;
263
			}
264
			elseif($bool_cast === BOOLEANS_AS_STRING){
265
				$p[$key] = $value ? 'true' : 'false';
266
			}
267
			elseif($bool_cast === BOOLEANS_AS_INT_STRING){
268
				$p[$key] = (string)(int)$value;
269
			}
270
271
		}
272
		elseif(is_iterable($value)){
273
			$p[$key] = call_user_func_array(__NAMESPACE__.'\\clean_query_params', [$value, $bool_cast, $remove_empty]);
274
		}
275
		elseif($remove_empty === true && ($value === null || (!is_numeric($value) && empty($value)))){
276
			continue;
277
		}
278
		else{
279
			$p[$key] = $value;
280
		}
281
	}
282
283
	return $p;
284
}
285
286
/**
287
 * merges additional query parameters into an existing query string
288
 *
289
 * @param string $uri
290
 * @param array  $query
291
 *
292
 * @return string
293
 */
294
function merge_query(string $uri, array $query):string{
295
	parse_str(parse_url($uri, PHP_URL_QUERY), $parsedquery);
296
297
	$requestURI = explode('?', $uri)[0];
298
	$params     = array_merge($parsedquery, $query);
299
300
	if(!empty($params)){
301
		$requestURI .= '?'.build_http_query($params);
302
	}
303
304
	return $requestURI;
305
}
306
307
/**
308
 * Return an UploadedFile instance array.
309
 *
310
 * @param array $files A array which respect $_FILES structure
311
 *
312
 * @throws \InvalidArgumentException for unrecognized values
313
 * @return array
314
 */
315
function normalize_files(array $files):array{
316
	$normalized = [];
317
318
	foreach($files as $key => $value){
319
320
		if($value instanceof UploadedFileInterface){
321
			$normalized[$key] = $value;
322
		}
323
		elseif(is_array($value) && isset($value['tmp_name'])){
324
			$normalized[$key] = create_uploaded_file_from_spec($value);
325
		}
326
		elseif(is_array($value)){
327
			$normalized[$key] = normalize_files($value);
328
			continue;
329
		}
330
		else{
331
			throw new InvalidArgumentException('Invalid value in files specification');
332
		}
333
334
	}
335
336
	return $normalized;
337
}
338
339
/**
340
 * Create and return an UploadedFile instance from a $_FILES specification.
341
 *
342
 * If the specification represents an array of values, this method will
343
 * delegate to normalizeNestedFileSpec() and return that return value.
344
 *
345
 * @param array $value $_FILES struct
346
 *
347
 * @return array|\Psr\Http\Message\UploadedFileInterface
348
 */
349
function create_uploaded_file_from_spec(array $value){
350
351
	if(is_array($value['tmp_name'])){
352
		return normalize_nested_file_spec($value);
353
	}
354
355
	return new UploadedFile($value['tmp_name'], (int)$value['size'], (int)$value['error'], $value['name'], $value['type']);
356
}
357
358
/**
359
 * Normalize an array of file specifications.
360
 *
361
 * Loops through all nested files and returns a normalized array of
362
 * UploadedFileInterface instances.
363
 *
364
 * @param array $files
365
 *
366
 * @return \Psr\Http\Message\UploadedFileInterface[]
367
 */
368
function normalize_nested_file_spec(array $files = []):array{
369
	$normalizedFiles = [];
370
371
	foreach(array_keys($files['tmp_name']) as $key){
372
		$spec = [
373
			'tmp_name' => $files['tmp_name'][$key],
374
			'size'     => $files['size'][$key],
375
			'error'    => $files['error'][$key],
376
			'name'     => $files['name'][$key],
377
			'type'     => $files['type'][$key],
378
		];
379
380
		$normalizedFiles[$key] = create_uploaded_file_from_spec($spec);
381
	}
382
383
	return $normalizedFiles;
384
}
385
386
/**
387
 * @todo
388
 *
389
 * @param \Psr\Http\Message\ResponseInterface $response
390
 * @param bool|null                           $assoc
391
 *
392
 * @return mixed
393
 */
394
function get_json(ResponseInterface $response, bool $assoc = null){
395
	return json_decode($response->getBody()->getContents(), $assoc);
396
}
397
398
/**
399
 * @todo
400
 *
401
 * @param \Psr\Http\Message\ResponseInterface $response
402
 *
403
 * @return \SimpleXMLElement
404
 */
405
function get_xml(ResponseInterface $response){
406
	return simplexml_load_string($response->getBody()->getContents());
407
}
408
409
/**
410
 * Returns the string representation of an HTTP message. (from Guzzle)
411
 *
412
 * @param MessageInterface $message Message to convert to a string.
413
 *
414
 * @return string
415
 */
416
function message_to_string(MessageInterface $message):string{
417
418
	if($message instanceof RequestInterface){
419
		$msg = trim($message->getMethod().' '.$message->getRequestTarget()).' HTTP/'.$message->getProtocolVersion();
420
421
		if(!$message->hasHeader('host')){
422
			$msg .= "\r\nHost: ".$message->getUri()->getHost();
423
		}
424
425
	}
426
	elseif($message instanceof ResponseInterface){
427
		$msg = 'HTTP/'.$message->getProtocolVersion().' '.$message->getStatusCode().' '.$message->getReasonPhrase();
428
	}
429
430
	foreach($message->getHeaders() as $name => $values){
431
		$msg .= "\r\n".$name.': '.implode(', ', $values);
0 ignored issues
show
Bug introduced by
The variable $msg does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
432
	}
433
434
	return $msg."\r\n\r\n".$message->getBody();
435
}
436