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 | namespace CyberLine\phUPnP; |
||
4 | |||
5 | /** |
||
6 | * Class Scanner |
||
7 | * |
||
8 | * @package CyberLine\phUPnP |
||
9 | * @author Alexander Over <[email protected]> |
||
10 | */ |
||
11 | class Scanner implements \JsonSerializable |
||
12 | { |
||
13 | /** @var string */ |
||
14 | static protected $host = '239.255.255.250'; |
||
15 | |||
16 | /** @var int */ |
||
17 | static protected $port = 1900; |
||
18 | |||
19 | /** |
||
20 | * Maximum wait time in seconds. Should be between 1 and 120 inclusive. |
||
21 | * |
||
22 | * @var int |
||
23 | */ |
||
24 | protected $delayResponse = 1; |
||
25 | |||
26 | /** @var int */ |
||
27 | protected $timeout = 5; |
||
28 | |||
29 | /** @var string */ |
||
30 | protected $userAgent = 'iOS/5.0 UDAP/2.0 iPhone/4'; |
||
31 | |||
32 | /** @var array */ |
||
33 | protected $searchTypes = [ |
||
34 | 'ssdp:all', |
||
35 | 'upnp:rootdevice', |
||
36 | ]; |
||
37 | |||
38 | /** @var string */ |
||
39 | protected $searchType = 'ssdp:all'; |
||
40 | |||
41 | /** @var array */ |
||
42 | private $devices = []; |
||
43 | |||
44 | /** |
||
45 | * @param integer $delayResponse |
||
46 | * @return $this |
||
47 | */ |
||
48 | public function setDelayResponse($delayResponse) |
||
49 | { |
||
50 | if ((int)$delayResponse >= 1 && (int)$delayResponse <= 120) { |
||
51 | $this->delayResponse = (int)$delayResponse; |
||
52 | |||
53 | return $this; |
||
54 | } |
||
55 | |||
56 | throw new \OutOfRangeException( |
||
57 | sprintf( |
||
58 | '%d is not a valid delay. Valid delay is between 1 and 120 (seconds)', |
||
59 | $delayResponse |
||
60 | ) |
||
61 | ); |
||
62 | } |
||
63 | |||
64 | /** |
||
65 | * @param int $timeout |
||
66 | * @return Scanner |
||
67 | */ |
||
68 | public function setTimeout($timeout) |
||
69 | { |
||
70 | if ((int)$timeout <= (int)$this->delayResponse) { |
||
71 | $this->timeout = (int)$timeout; |
||
72 | |||
73 | return $this; |
||
74 | } |
||
75 | |||
76 | throw new \OutOfBoundsException( |
||
77 | sprintf( |
||
78 | 'Timeout of %d is smaller then delay of %d', |
||
79 | $timeout, |
||
80 | $this->delayResponse |
||
81 | ) |
||
82 | ); |
||
83 | } |
||
84 | |||
85 | /** |
||
86 | * @param string $userAgent |
||
87 | * @return Scanner |
||
88 | */ |
||
89 | public function setUserAgent($userAgent) |
||
90 | { |
||
91 | $this->userAgent = $userAgent; |
||
92 | |||
93 | return $this; |
||
94 | } |
||
95 | |||
96 | /** |
||
97 | * @param string $searchType |
||
98 | * @return $this |
||
99 | */ |
||
100 | public function setSearchType($searchType) |
||
101 | { |
||
102 | if (in_array($searchType, $this->searchTypes)) { |
||
103 | $this->searchType = $searchType; |
||
104 | |||
105 | return $this; |
||
106 | } |
||
107 | |||
108 | throw new \InvalidArgumentException( |
||
109 | sprintf( |
||
110 | '%s is not a valid searchtype. Valid searchtypes are: %s', |
||
111 | $searchType, |
||
112 | implode(', ', $this->searchTypes) |
||
113 | ) |
||
114 | ); |
||
115 | } |
||
116 | |||
117 | /** |
||
118 | * Main scan function |
||
119 | * |
||
120 | * @return array |
||
121 | */ |
||
122 | public function discover() |
||
123 | { |
||
124 | $devices = $this->doMSearchRequest(); |
||
125 | |||
126 | if (empty($devices)) { |
||
127 | return []; |
||
128 | } |
||
129 | |||
130 | $targets = []; |
||
131 | foreach ($devices as $key => $device) { |
||
132 | $devices[$key] = $this->parseMSearchResponse($device); |
||
133 | array_push($targets, $devices[$key]['location']); |
||
134 | } |
||
135 | |||
136 | foreach ($this->fetchUpnpXmlDeviceInfo($targets) as $location => $xml) { |
||
137 | if (!empty($xml)) { |
||
138 | $this->parseXmlDeviceResponse($location, $xml); |
||
139 | } |
||
140 | } |
||
141 | |||
142 | return $this->devices; |
||
143 | } |
||
144 | |||
145 | /** |
||
146 | * Fetch all available UPnP devices via unicast |
||
147 | * |
||
148 | * @return array |
||
149 | */ |
||
150 | protected function doMSearchRequest() |
||
151 | { |
||
152 | $request = $this->getMSearchRequest(); |
||
153 | |||
154 | $socket = socket_create(AF_INET, SOCK_DGRAM, 0); |
||
155 | @socket_set_option($socket, 1, 6, true); |
||
0 ignored issues
–
show
|
|||
156 | socket_sendto($socket, $request, strlen($request), 0, static::$host, static::$port); |
||
157 | socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, [ |
||
158 | 'sec' => $this->timeout, |
||
159 | 'usec' => '0' |
||
160 | ]); |
||
161 | |||
162 | $response = []; |
||
163 | $from = null; |
||
164 | $port = null; |
||
165 | do { |
||
166 | $buffer = null; |
||
167 | @socket_recvfrom($socket, $buffer, 1024, MSG_WAITALL, $from, $port); |
||
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.');
}
![]() |
|||
168 | if (!is_null($buffer)) { |
||
169 | array_push($response, $buffer); |
||
170 | } |
||
171 | } while (!is_null($buffer)); |
||
172 | |||
173 | socket_close($socket); |
||
174 | |||
175 | return $response; |
||
176 | } |
||
177 | |||
178 | /** |
||
179 | * Prepare Msearch request string |
||
180 | * |
||
181 | * @return string |
||
182 | */ |
||
183 | protected function getMSearchRequest() |
||
184 | { |
||
185 | $ssdpMessage = [ |
||
186 | 'M-SEARCH * HTTP/1.1', |
||
187 | sprintf('HOST: %s:%d', static::$host, static::$port), |
||
188 | 'MAN: "ssdp:discover"', |
||
189 | sprintf('MX: %d', $this->delayResponse), |
||
190 | sprintf('ST: %s', $this->searchType), |
||
191 | sprintf('USER-AGENT: %s', $this->userAgent), |
||
192 | ]; |
||
193 | |||
194 | return implode("\r\n", $ssdpMessage) . "\r\n"; |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Parse response from device to a more readable format |
||
199 | * |
||
200 | * @param $response |
||
201 | * @return array |
||
202 | */ |
||
203 | protected function parseMSearchResponse($response) |
||
204 | { |
||
205 | $mapping = [ |
||
206 | 'http' => 'http', |
||
207 | 'cach' => 'cache-control', |
||
208 | 'date' => 'date', |
||
209 | 'ext' => 'ext', |
||
210 | 'loca' => 'location', |
||
211 | 'serv' => 'server', |
||
212 | 'st:' => 'st', |
||
213 | 'usn:' => 'usn', |
||
214 | 'cont' => 'content-length', |
||
215 | ]; |
||
216 | |||
217 | $parsedResponse = []; |
||
218 | foreach (explode("\r\n", $response) as $resultLine) { |
||
219 | foreach ($mapping as $key => $replace) { |
||
220 | if (stripos($resultLine, $key) === 0) { |
||
221 | $parsedResponse[$replace] = str_ireplace($replace . ': ', '', $resultLine); |
||
222 | } |
||
223 | } |
||
224 | } |
||
225 | |||
226 | return $parsedResponse; |
||
227 | } |
||
228 | |||
229 | /** |
||
230 | * @param $location |
||
231 | * @param $xml |
||
232 | */ |
||
233 | protected function parseXmlDeviceResponse($location, $xml) |
||
234 | { |
||
235 | try { |
||
236 | $simpleXML = new \SimpleXMLElement($xml); |
||
237 | if (!property_exists($simpleXML, 'URLBase')) { |
||
238 | $location = parse_url($location); |
||
239 | $simpleXML->URLBase = sprintf('%s://%s:%d/', $location['scheme'], $location['host'], $location['port']); |
||
240 | } |
||
241 | array_push($this->devices, $simpleXML); |
||
242 | } catch (\Exception $e) { /* SimpleXML parsing failed */ } |
||
243 | } |
||
244 | |||
245 | /** |
||
246 | * Fetch XML's from all devices async |
||
247 | * |
||
248 | * @param array $targets |
||
249 | * @return array |
||
250 | */ |
||
251 | protected function fetchUpnpXmlDeviceInfo(array $targets) |
||
252 | { |
||
253 | $targets = array_values(array_unique($targets)); |
||
254 | $multi = curl_multi_init(); |
||
255 | |||
256 | $curl = $xmls = []; |
||
257 | foreach ($targets as $key => $target) { |
||
258 | $curl[$key] = curl_init(); |
||
259 | curl_setopt($curl[$key], CURLOPT_URL, $target); |
||
260 | curl_setopt($curl[$key], CURLOPT_TIMEOUT, $this->timeout); |
||
261 | curl_setopt($curl[$key], CURLOPT_RETURNTRANSFER, true); |
||
262 | curl_multi_add_handle($multi, $curl[$key]); |
||
263 | } |
||
264 | |||
265 | $active = null; |
||
266 | do { |
||
267 | $mrc = curl_multi_exec($multi, $active); |
||
268 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); |
||
269 | |||
270 | while ($active && $mrc == CURLM_OK) { |
||
271 | if (curl_multi_select($multi) != -1) { |
||
272 | do { |
||
273 | $mrc = curl_multi_exec($multi, $active); |
||
274 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); |
||
275 | } |
||
276 | } |
||
277 | |||
278 | foreach ($curl as $key => $handle) { |
||
279 | $xmls[$targets[$key]] = curl_multi_getcontent($handle); |
||
280 | curl_multi_remove_handle($multi, $handle); |
||
281 | } |
||
282 | |||
283 | curl_multi_close($multi); |
||
284 | |||
285 | return $xmls; |
||
286 | } |
||
287 | |||
288 | /** |
||
289 | * @return array |
||
290 | */ |
||
291 | public function jsonSerialize() |
||
292 | { |
||
293 | if (empty($this->devices)) { |
||
294 | $this->discover(); |
||
295 | } |
||
296 | |||
297 | return [ |
||
298 | 'total' => count($this->devices), |
||
299 | 'devices' => $this->devices, |
||
300 | ]; |
||
301 | } |
||
302 | } |
||
303 |
If you suppress an error, we recommend checking for the error condition explicitly: