This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /* |
||
4 | * This file is part of the Ariadne Component Library. |
||
5 | * |
||
6 | * (c) Muze <[email protected]> |
||
7 | * |
||
8 | * For the full copyright and license information, please view the LICENSE |
||
9 | * file that was distributed with this source code. |
||
10 | */ |
||
11 | |||
12 | namespace arc\http; |
||
13 | |||
14 | /** |
||
15 | * This class contains static methods to help parse HTTP headers. |
||
16 | * @package arc\http |
||
17 | */ |
||
18 | final class headers |
||
19 | { |
||
20 | |||
21 | /** |
||
22 | * Parse response headers string from a HTTP request into an array of headers. e.g. |
||
23 | * [ 'Location' => 'http://www.example.com', ... ] |
||
24 | * When multiple headers with the same name are present, all values will form an array, in the order in which |
||
25 | * they are present in the source. |
||
26 | * @param string|string[] $headers The headers string to parse. |
||
27 | * @return array |
||
28 | */ |
||
29 | 14 | public static function parse( $headers ) { |
|
30 | 14 | if ( !is_array($headers) && !$headers instanceof \ArrayObject ) { |
|
31 | 10 | $headers = array_filter( |
|
32 | 10 | array_map( 'trim', explode( "\n", (string) $headers ) ) |
|
33 | ); |
||
34 | } |
||
35 | 14 | $result = []; |
|
36 | 14 | $currentName = ''; |
|
37 | 14 | foreach( $headers as $key => $header ) { |
|
38 | 14 | if ( !is_array($header) ) { |
|
39 | 14 | @list($name, $value) = array_map('trim', explode(':', $header, 2) ); |
|
0 ignored issues
–
show
|
|||
40 | } else { |
||
41 | $name = $header; |
||
42 | $value = null; |
||
43 | } |
||
44 | 14 | if ( isset( $value ) ) { |
|
45 | 14 | $result = self::addHeader($result, $name, $value); |
|
46 | 12 | } else if (is_numeric($key)) { |
|
47 | 12 | if ( $currentName ) { |
|
48 | 2 | $result = self::addHeader($result, $currentName, $name); |
|
49 | } else { |
||
50 | 12 | $result[] = $name; |
|
51 | } |
||
52 | 12 | $name = $key; |
|
53 | } else { |
||
54 | $result = self::addHeader($result, $key, $name); |
||
55 | $name = $key; |
||
56 | } |
||
57 | 14 | $currentName = ( is_numeric($name) ? $currentName : $name ); |
|
58 | } |
||
59 | 14 | return $result; |
|
60 | } |
||
61 | |||
62 | /** |
||
63 | * Return an array with values from a Comma seperated header like Cache-Control or Accept |
||
64 | * e.g. 'max-age=300,public,no-store' |
||
65 | * results in |
||
66 | * [ 0 => [ 'max-age' => '300' ], 1 => [ 'public' => 'public' ], 2 => ['no-store' => 'no-store'] ] |
||
67 | * @param string $header |
||
68 | * @return array |
||
69 | */ |
||
70 | 12 | public static function parseHeader($header) |
|
71 | { |
||
72 | 12 | $header = (strpos($header, ':')!==false) ? explode(':', $header)[1] : $header; |
|
73 | 12 | $parts = array_map('trim', explode(',', $header)); |
|
74 | 12 | $header = []; |
|
75 | 12 | foreach ( $parts as $part ) { |
|
76 | 12 | $elements = array_map('trim', explode(';', $part)); |
|
77 | 12 | $result = []; |
|
78 | 12 | foreach ($elements as $element) { |
|
79 | 12 | @list($name, $value) = array_map('trim', explode( '=', $element)); |
|
0 ignored issues
–
show
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.
If you suppress an error, we recommend checking for the error condition explicitly: // For example instead of
@mkdir($dir);
// Better use
if (@mkdir($dir) === false) {
throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
![]() |
|||
80 | 12 | if ( !isset($value) ) { |
|
81 | 12 | $result['value'] = $name; |
|
82 | } else { |
||
83 | 12 | $result[$name] = (isset($value) ? $value : $name); |
|
84 | } |
||
85 | } |
||
86 | 12 | $header[] = $result; |
|
87 | } |
||
88 | 12 | return $header; |
|
89 | } |
||
90 | |||
91 | /** |
||
92 | * Merge multiple occurances of a comma seperated header |
||
93 | * @param array $headers |
||
94 | * @return array |
||
95 | */ |
||
96 | 10 | public static function mergeHeaders( $headers ) |
|
97 | { |
||
98 | 10 | $result = []; |
|
99 | 10 | if ( is_string($headers) ) { |
|
100 | 6 | $result = self::parseHeader( $headers ); |
|
101 | 4 | } else foreach ( $headers as $header ) { |
|
102 | 4 | if (is_string($header)) { |
|
103 | 4 | $header = self::parseHeader($header); |
|
104 | } |
||
105 | 4 | $result = array_merge( $result, $header ); |
|
106 | } |
||
107 | 10 | return $result; |
|
108 | } |
||
109 | |||
110 | /** |
||
111 | * Parse response headers to determine if and how long you may cache the response. Doesn't understand ETags. |
||
112 | * @param string|string[] $headers Headers string or array as returned by parse() |
||
113 | * @param bool $private Whether to store a private cache (true) or public cache image (false). Default is public. |
||
114 | * @return int The number of seconds you may cache this result starting from now. |
||
115 | */ |
||
116 | 12 | public static function parseCacheTime( $headers, $private=false ) |
|
117 | { |
||
118 | 12 | $result = null; |
|
119 | 12 | if ( is_string($headers) || ( !isset($headers['Cache-Control']) && !isset($headers['Expires']) ) ) { |
|
120 | 2 | $headers = \arc\http\headers::parse( $headers ); |
|
0 ignored issues
–
show
It seems like
$headers defined by \arc\http\headers::parse($headers) on line 120 can also be of type array<integer|string,str...ull","Expires":"null"}> ; however, arc\http\headers::parse() does only seem to accept string|array<integer,string> , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
121 | } |
||
122 | 12 | if ( isset( $headers['Cache-Control'] ) ) { |
|
123 | 10 | $header = self::mergeHeaders( $headers['Cache-Control'] ); |
|
124 | 10 | $result = self::getCacheControlTime( $header, $private ); |
|
125 | } |
||
126 | 12 | if ( !isset($result) && isset( $headers['Expires'] ) ) { |
|
127 | 2 | $result = strtotime( self::getLastHeader( $headers['Expires'] ) ) - time(); |
|
128 | } |
||
129 | 12 | return (int) $result; |
|
130 | } |
||
131 | |||
132 | /** |
||
133 | * Parses Accept-* header and returns best matching value from the $acceptable list |
||
134 | * Takes into account the Q value and wildcards. Does not take into account other parameters |
||
135 | * currently ( e.g. text/html;level=1 ) |
||
136 | * @param array|string $header The Accept-* header (Accept:, Accept-Lang:, Accept-Encoding: etc.) |
||
137 | * @param array $acceptable List of acceptable values, in order of preference |
||
138 | * @return string |
||
139 | */ |
||
140 | 2 | public static function accept( $header, $acceptable ) |
|
141 | { |
||
142 | 2 | if ( is_string($header) ) { |
|
143 | 2 | $header = \arc\http\headers::parseHeader( $header ); |
|
144 | } |
||
145 | 2 | $ordered = self::orderByQuality($header); |
|
146 | 2 | foreach( $ordered as $value ) { |
|
147 | 2 | if ( self::isAcceptable($value['value'], $acceptable) ) { |
|
0 ignored issues
–
show
The expression
self::isAcceptable($value['value'], $acceptable) of type string|false is loosely compared to true ; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
148 | 2 | return $value['value']; |
|
149 | } |
||
150 | } |
||
151 | } |
||
152 | |||
153 | 14 | public static function addHeader($headers, $name, $value) |
|
154 | { |
||
155 | 14 | if ( !isset($headers[ $name]) ) { |
|
156 | // first entry for this header |
||
157 | 14 | $headers[ $name ] = $value; |
|
158 | 6 | } else if ( is_string($headers[ $name ]) ) { |
|
159 | // second header entry with same name |
||
160 | 6 | $headers[ $name ] = [ |
|
161 | 6 | $headers[ $name ], |
|
162 | 6 | $value |
|
163 | ]; |
||
164 | } else { // third or later header entry with same name |
||
165 | 2 | $headers[ $name ][] = $value; |
|
166 | } |
||
167 | 14 | return $headers; |
|
168 | } |
||
169 | |||
170 | 10 | private static function getCacheControlTime( $header, $private ) |
|
171 | { |
||
172 | 10 | $result = null; |
|
173 | 10 | $dontcache = false; |
|
174 | 10 | foreach ( $header as $value ) { |
|
175 | 10 | if ( isset($value['value']) ) { |
|
176 | 10 | switch($value['value']) { |
|
177 | 10 | case 'private': |
|
178 | 2 | if ( !$private ) { |
|
179 | 2 | $dontcache = true; |
|
180 | } |
||
181 | 2 | break; |
|
182 | 8 | case 'no-cache': |
|
183 | 8 | case 'no-store': |
|
184 | 8 | case 'must-revalidate': |
|
185 | 8 | case 'proxy-revalidate': |
|
186 | 2 | $dontcache = true; |
|
187 | 10 | break; |
|
188 | } |
||
189 | 10 | } else if ( isset($value['max-age']) || isset($value['s-maxage']) ) { |
|
190 | 10 | $maxage = (int) (isset($value['max-age']) ? $value['max-age'] : $value['s-maxage']); |
|
191 | 10 | if ( isset($result) ) { |
|
192 | 10 | $result = min($result, $maxage); |
|
193 | } else { |
||
194 | 10 | $result = $maxage; |
|
195 | } |
||
196 | } |
||
197 | } |
||
198 | 10 | if ( $dontcache ) { |
|
199 | 4 | $result = 0; |
|
200 | } |
||
201 | 10 | return $result; |
|
202 | } |
||
203 | |||
204 | 2 | private static function orderByQuality($header) |
|
205 | { |
||
206 | 2 | $getQ = function($entry) { |
|
207 | 2 | $q = ( isset($entry['q']) ? floatval($entry['q']) : 1); |
|
208 | 2 | $name = $entry['value']; |
|
209 | 2 | if ( $name[ strlen($name)-1 ] == '*' || $name[0] == '*' ) { |
|
210 | 2 | $q -= 0.0001; // exact matches are preferred over wildcards |
|
211 | } |
||
212 | 2 | return $q; |
|
213 | 2 | }; |
|
214 | 2 | usort($header, function($a,$b) use ($getQ) { |
|
215 | 2 | return ($getQ($a)>$getQ($b) ? -1 : 1); |
|
216 | 2 | }); |
|
217 | 2 | return $header; |
|
218 | } |
||
219 | |||
220 | 2 | private static function pregEscape($string) { |
|
221 | 2 | $special = ".\\+'?[^]$(){}=!<>|:"; |
|
222 | // * and - are not included, since they are allowed in the accept mimetypes |
||
223 | 2 | return AddCSlashes($string, $special); |
|
224 | } |
||
225 | |||
226 | 2 | private static function isAcceptable($name, $acceptable) |
|
227 | { |
||
228 | 2 | $name = str_replace('*', '.*', $name); |
|
229 | 2 | $result = preg_grep('|'.self::pregEscape($name).'|', $acceptable); |
|
230 | 2 | return current($result); |
|
231 | } |
||
232 | |||
233 | /** |
||
234 | * Return the last value sent for a specific header, uses the output of parse(). |
||
235 | * @param (mixed) $headers An array with multiple header strings or a single string. |
||
236 | * @return array|mixed |
||
237 | */ |
||
238 | 2 | private static function getLastHeader($headers) { |
|
239 | 2 | if ( is_array($headers) ) { |
|
240 | return end($headers); |
||
241 | } |
||
242 | 2 | return $headers; |
|
243 | } |
||
244 | |||
245 | |||
246 | } |
If you suppress an error, we recommend checking for the error condition explicitly: