get_named_val()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 4
eloc 11
c 4
b 0
f 0
nc 4
nop 2
dl 0
loc 14
rs 9.9
1
<?php 
2
/*-
3
 * Copyright (c) 2013 - 2021 Rozhuk Ivan <[email protected]>
4
 * All rights reserved.
5
 * 
6
 * Subject to the following obligations and disclaimer of warranty, use and
7
 * redistribution of this software, in source or object code forms, with or
8
 * without modifications are expressly permitted by Whistle Communications;
9
 * provided, however, that:
10
 * 1. Any and all reproductions of the source or object code must include the
11
 *    copyright notice above and the following disclaimer of warranties; and
12
 * 2. No rights are granted, in any manner or form, to use Whistle
13
 *    Communications, Inc. trademarks, including the mark "WHISTLE
14
 *    COMMUNICATIONS" on advertising, endorsements, or otherwise except as
15
 *    such appears in the above copyright notice or in the software.
16
 * 
17
 * THIS SOFTWARE IS BEING PROVIDED BY WHISTLE COMMUNICATIONS "AS IS", AND
18
 * TO THE MAXIMUM EXTENT PERMITTED BY LAW, WHISTLE COMMUNICATIONS MAKES NO
19
 * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING THIS SOFTWARE,
20
 * INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED WARRANTIES OF
21
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
22
 * WHISTLE COMMUNICATIONS DOES NOT WARRANT, GUARANTEE, OR MAKE ANY
23
 * REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS OF THE USE OF THIS
24
 * SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY, RELIABILITY OR OTHERWISE.
25
 * IN NO EVENT SHALL WHISTLE COMMUNICATIONS BE LIABLE FOR ANY DAMAGES
26
 * RESULTING FROM OR ARISING OUT OF ANY USE OF THIS SOFTWARE, INCLUDING
27
 * WITHOUT LIMITATION, ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
28
 * PUNITIVE, OR CONSEQUENTIAL DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR
29
 * SERVICES, LOSS OF USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER ANY
30
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32
 * THIS SOFTWARE, EVEN IF WHISTLE COMMUNICATIONS IS ADVISED OF THE POSSIBILITY
33
 * OF SUCH DAMAGE.
34
 *
35
 * Author: Rozhuk Ivan <[email protected]>
36
 *
37
 */
38
39
/* xml-SOAP MediaServer/ContentDirectory:3 for UPnP/DLNA */
40
/* http://upnp.org/specs/av/UPnP-av-ContentDirectory-v3-Service.pdf */
41
42
 
43
/* Config.*/
44
$basedir = dirname(__FILE__).'/../../upnpdata'; /* File system path. */
45
$baseurl = '/upnpdata'; /* WEB URL path. */
46
47
date_default_timezone_set('UTC');
48
49
50
/* File types. */
51
$file_class = array(
52
	'm3u' => 'object.container.storageFolder',
53
	'xspf' => 'object.container.storageFolder',
54
	'xml' => 'object.container.storageFolder',
55
56
	'bmp' => 'object.item.imageItem.photo',
57
	'gif' => 'object.item.imageItem.photo',
58
	'ico' => 'object.item.imageItem.photo',
59
	'png' => 'object.item.imageItem.photo',
60
	'jpe' => 'object.item.imageItem.photo',
61
	'jpg' => 'object.item.imageItem.photo',
62
	'jpeg' => 'object.item.imageItem.photo',
63
	'tif' => 'object.item.imageItem.photo',
64
	'tiff' => 'object.item.imageItem.photo',
65
	'svg' => 'object.item.imageItem.photo',
66
	'svgz' => 'object.item.imageItem.photo',
67
68
	'flac' => 'object.item.audioItem.musicTrack',
69
	'mp3' => 'object.item.audioItem.musicTrack', 
70
	'wav' => 'object.item.audioItem.musicTrack',
71
	'wma' => 'object.item.audioItem.musicTrack',
72
73
	'flv' => 'object.item.videoItem',
74
	'f4v' => 'object.item.videoItem',
75
	'3g2' => 'object.item.videoItem',
76
	'3gp' => 'object.item.videoItem',
77
	'3gp2' => 'object.item.videoItem',
78
	'3gpp' => 'object.item.videoItem',
79
	'asf' => 'object.item.videoItem',
80
	'asx' => 'object.item.videoItem',
81
	'avi' => 'object.item.videoItem.movie',
82
	'dat' => 'object.item.videoItem',
83
	'iso' => 'object.item.videoItem',
84
	'm2t' => 'object.item.videoItem',
85
	'm2ts' => 'object.item.videoItem',
86
	'm2v' => 'object.item.videoItem',
87
	'm4v' => 'object.item.videoItem',
88
	'mp2v' => 'object.item.videoItem',
89
	'mp4' => 'object.item.videoItem',
90
	'mp4v' => 'object.item.videoItem',
91
	'mpe' => 'object.item.videoItem',
92
	'mpeg' => 'object.item.videoItem',
93
	'mpg' => 'object.item.videoItem',
94
	'mod' => 'object.item.videoItem',
95
	'mov' => 'object.item.videoItem',
96
	'mkv' => 'object.item.videoItem.videoBroadcast',
97
	'mts' => 'object.item.videoItem',
98
	'ogg' => 'object.item.videoItem',
99
	'swf' => 'object.item.videoItem',
100
	'vob' => 'object.item.videoItem',
101
	'ts' => 'object.item.videoItem',
102
	'webm' => 'object.item.videoItem',
103
	'wm' => 'object.item.videoItem',
104
	'wmv' => 'object.item.videoItem',
105
	'wmx' => 'object.item.videoItem',
106
);
107
108
/* MIME types. */
109
$mime_types = array(
110
	'txt' => 'text/plain',
111
	'htm' => 'text/html',
112
	'html' => 'text/html',
113
	'php' => 'text/html',
114
	'css' => 'text/css',
115
	'js' => 'application/javascript',
116
	'json' => 'application/json',
117
	'xml' => 'application/xml',
118
	'swf' => 'application/x-shockwave-flash',
119
120
	/* Images. */
121
	'png' => 'image/png',
122
	'jpe' => 'image/jpeg',
123
	'jpeg' => 'image/jpeg',
124
	'jpg' => 'image/jpeg',
125
	'gif' => 'image/gif',
126
	'bmp' => 'image/bmp',
127
	'ico' => 'image/vnd.microsoft.icon',
128
	'tiff' => 'image/tiff',
129
	'tif' => 'image/tiff',
130
	'svg' => 'image/svg+xml',
131
	'svgz' => 'image/svg+xml',
132
133
	/* Audio. */
134
	'flac' => 'audio/ogg',
135
	'mp3' => 'audio/mpeg', 
136
	'wav' => 'audio/vnd.wave',
137
	'wma' => 'audio/x-ms-wma',
138
139
	/* Video. */
140
	'3gp' => 'video/3gpp',
141
	'3gpp' => 'video/3gpp',
142
	'3g2' => 'video/3gpp2',
143
	'3gpp2' => 'video/3gpp2',
144
	'flv' => 'video/x-flv',
145
	'qt' => 'video/quicktime',
146
	'ogg' => 'video/ogg',
147
	'mov' => 'video/mpeg',
148
	'mp4' => 'video/mp4',
149
	'mkv' => 'video/x-mkv',
150
	'm2ts' => 'video/MP2T',
151
	'ts' => 'video/MP2T',
152
	'webm' => 'video/webm',
153
);
154
155
156
/* Auto variables. */
157
158
/* "urn:schemas-upnp-org:service:ContentDirectory:1#Browse" */
159
$http_hdr_soapact = $_SERVER['HTTP_SOAPACTION'];
160
$soap_shemas = strpos($http_hdr_soapact, 'urn:schemas-upnp-org:service:ContentDirectory:');
161
if (false === $soap_shemas)
162
	return (500);
163
$soap_service_ver = substr($http_hdr_soapact, ($soap_shemas + 46), 1);
164
$soap_service_func = substr($http_hdr_soapact, ($soap_shemas + 48), -1);
165
166
167
if (substr($basedir, -1, 1) !== '/') {
168
	$basedir = $basedir.'/';
169
}
170
$baseurl = implode('/', array_map('rawurlencode', explode('/', $baseurl)));
171
$baseurlpatch = 'http://'.$_SERVER['HTTP_HOST'].$baseurl;
172
if ('/' !== substr($baseurlpatch, -1, 1)) {
173
	$baseurlpatch = $baseurlpatch.'/';
174
}
175
/**
176
 * Apply workaround for the libxml PHP bugs:
177
 * @link https://bugs.php.net/bug.php?id=62577
178
 * @link https://bugs.php.net/bug.php?id=64938
179
 */
180
if (function_exists('libxml_disable_entity_loader')) {
181
	libxml_disable_entity_loader(false);
182
}
183
184
# $server = new SoapServer(null, array('uri' => "urn:schemas-upnp-org:service:ContentDirectory:3"));
185
$server = new SoapServer(dirname(__FILE__)."/../descr/ContentDirectory.wdsl",
186
		array('cache_wsdl' => WSDL_CACHE_MEMORY,
187
			'soap_version' => SOAP_1_2,
188
			'trace' => true
189
		));
190
191
192
function xml_encode($string) {
193
194
	return (str_replace(
195
	    array("&", "<", ">", /*'"',*/	"'"),
196
	    array("&amp;", "&lt;", "&gt;", /*"&quot;",*/	"&apos;"), 
197
	    $string));
198
}
199
200
function xml_decode($string) {
201
202
	return (str_replace(
203
	    array("&amp;", "&lt;", "&gt;", "&quot;", "&apos;"), 
204
	    array("&", "<", ">", '"', "'"),
205
	    $string));
206
}
207
208
209
function upnp_url_encode($url) {
210
211
	if ('http://' !== substr($url, 0, 7) ||
212
	    false === ($url_path_off = strrpos($url, '/', 8)))
213
		return (implode('/', array_map('rawurlencode', explode('/', $url))));
214
		//return (xml_encode(implode('/', array_map('rawurlencode', explode('/', $url)))));
215
		//return (xml_encode($url));
216
		//return ('<![CDATA[' . xml_encode($url) . ']]');
217
218
	return (substr($url, 0, $url_path_off).implode('/', array_map('rawurlencode', explode('/', substr($url, $url_path_off)))));
219
	//return (substr($url, 0, $url_path_off) . xml_encode(implode('/', array_map('rawurlencode', explode('/', substr($url, $url_path_off))))));
220
	//return (substr($url, 0, $url_path_off) . xml_encode(substr($url, $url_path_off)));
221
	//return ('<![CDATA[$url]]');
222
}
223
224
225
function upnp_get_class($file, $def) {
226
	global $file_class;
227
228
	if (!isset($file))
229
		return ($def);
230
	$dot = strrpos($file, '.');
231
	if (false === $dot)
232
		return ($def);
233
	$ext = strtolower(substr($file, ($dot + 1)));
234
	if (isset($file_class[$ext])) /* Skip unsupported file type. */
235
		return ($file_class[$ext]);
236
237
	return ($def);
238
}
239
240
241
function get_named_val($name, $buf) { /* ...val_name="value"... */
242
243
	$st_off = strpos($buf, $name);
244
	if (false === $st_off)
245
		return (null);
246
	$st_off += strlen($name);
247
	if ('="' !== substr($buf, $st_off, 2))
248
		return (null);
249
	$st_off += 2;
250
	$en_off = strpos($buf, '"', $st_off);
251
	if (false === $en_off)
252
		return (null);
253
254
	return (substr($buf, $st_off, ($en_off - $st_off)));
255
}
256
257
258
function m3u_calc_items_count($filename) {
259
260
	$items_count = 0;
261
	$fd = fopen($filename, 'r');
262
	if (false === $fd)
263
		return ($items_count);
264
	while (!feof($fd)) { /* Read the file line by line... */
265
		$buffer = trim(fgets($fd));
266
		if (false === strpos($buffer, '#EXTINF:')) /* Skip empty/bad lines. */
267
			continue;
268
		$entry = trim(fgets($fd));
269
		if (false === strpos($entry, '://'))
270
			continue;
271
		$items_count++;
272
	} 
273
	fclose($fd);
274
275
	return ($items_count);
276
}
277
278
279
function m3u_browse($filename, $ObjectID, $StartingIndex, $RequestedCount,
280
    $UpdateID, $Result) {
281
	$NumberReturned = 0;
282
	$TotalMatches = 0;
283
284
	/* Open the file. */
285
	$fd = fopen($filename, 'r');
286
	if (false === $fd) {
287
		return (array('Result' => '',
288
				'NumberReturned' => 0,
289
				'TotalMatches' => 0,
290
				'UpdateID' => $UpdateID));
291
	}
292
	$date = upnp_date(filectime($filename), 1);
293
	if (is_writable($filename)) {
294
		$Restricted = '0';
295
	} else {
296
		$Restricted = '1';
297
	}
298
299
	while (!feof($fd)) { /* Read the file line by line... */
300
		$buffer = trim(fgets($fd));
301
		if (false === strpos($buffer, '#EXTINF:')) { /* Skip empty/bad lines. */
302
			continue;
303
		}
304
		$entry = trim(fgets($fd));
305
		if (false === strpos($entry, '://'))
306
			continue;
307
		/* Ok, item matched and may be returned. */
308
		$TotalMatches++;
309
		if (0 < $StartingIndex &&
310
		    $TotalMatches < $StartingIndex)
311
			continue; /* Skip first items. */
312
		if (0 < $RequestedCount &&
313
		    $NumberReturned >= $RequestedCount)
314
			continue; /* Do not add more than requested. */
315
		$NumberReturned++;
316
		/* Add item to result. */
317
		$title = xml_encode(trim(substr($buffer, (strpos($buffer, ',') + 1))));
318
		//$en_entry = upnp_url_encode($entry);
319
		$en_entry = xml_encode($entry);
320
		$iclass = upnp_get_class($entry, 'object.item.videoItem.videoBroadcast');
321
		$mimetype = 'video/mpeg';
322
		if ('object.container.storageFolder' === $iclass) { /* Play list as folder! */
323
			$Result = $Result.
324
			    "<container id=\"$en_entry\" parentID=\"$ObjectID\" restricted=\"$Restricted\">".
325
				"<dc:title>$title</dc:title>".
326
				'<upnp:class>object.container.storageFolder</upnp:class>'.
327
			    '</container>';
328
		} else {
329
			$Result = $Result.
330
			    "<item id=\"$en_entry\" parentID=\"$ObjectID\" restricted=\"$Restricted\">".
331
				"<dc:title>$title</dc:title>".
332
				"<dc:date>$date</dc:date>".
333
				"<upnp:class>$iclass</upnp:class>".
334
				"<res protocolInfo=\"http-get:*:$mimetype:*\">$en_entry</res>".
335
			    '</item>';
336
		}
337
	} 
338
	fclose($fd);
339
340
	$Result = $Result.'</DIDL-Lite>';
341
	return (array('Result' => $Result,
342
			'NumberReturned' => $NumberReturned,
343
			'TotalMatches' => $TotalMatches,
344
			'UpdateID' => $UpdateID));
345
}
346
347
348
function upnp_mime_content_type($filename) {
349
	global $mime_types;
350
351
	$def = 'video/mpeg';
352
353
	if (!isset($filename))
354
		return ($def);
355
	$dot = strrpos($filename, '.');
356
	if (false === $dot)
357
		return ($def);
358
	$ext = strtolower(substr($filename, ($dot + 1)));
359
	if (array_key_exists($ext, $mime_types)) {
360
		return ($mime_types[$ext]);
361
	} elseif (function_exists('finfo_open')) {
362
		$finfo = finfo_open(FILEINFO_MIME);
363
		$mimetype = finfo_file($finfo, $filename);
364
		finfo_close($finfo);
365
		return ($mimetype);
366
	}
367
368
	return ($def);
369
}
370
371
/* Format:
372
 * 0: 2005-02-10
373
 * 1: 2004-05-08T10:00:00
374
 * */
375
function upnp_date($timedate, $format) {
376
	$res = date('Y-m-d', $timedate);
377
378
	if (1 === $format) {
379
		$res = $res.'T'.date('H:i:s', $timedate);
380
	}
381
382
	return ($res);
383
}
384
385
386
function browse_metadata($filename, $dir, $ObjectID, $UpdateID, $Result) {
387
388
	/* Is file/dir exist? */
389
	$stat = stat($filename);
390
	if (false === $stat) { /* No such file/dir. */
391
		return (array('Result' => '',
392
				'NumberReturned' => 0,
393
				'TotalMatches' => 0,
394
				'UpdateID' => $UpdateID));
395
	}
396
397
	/* Collect data. */
398
	if (is_writable($filename)) {
399
		$WriteStatus = 'WRITABLE';
400
		$Restricted = '0';
401
	} else {
402
		$WriteStatus = 'NOT_WRITABLE';
403
		$Restricted = '1';
404
	}
405
	$basefilename = basename($dir);
406
	if ('0' === $ObjectID) {
407
		$title = 'root';
408
		$ParentID = '-1';
409
	} else {
410
		$title = xml_encode($basefilename);
411
		$ParentID = upnp_url_encode(dirname($dir));
412
	}
413
414
	if (is_dir($filename)) { /* Dir. */
415
		$StorageTotal = disk_total_space($filename);
416
		$StorageFree = disk_free_space($filename);
417
		$StorageUsed = ($StorageTotal - $StorageFree);
418
		$ChildCount = (count(scandir($filename)) - 2);
419
		$Result = $Result.
420
		    "<container id=\"$ObjectID\" parentID=\"$ParentID\" restricted=\"$Restricted\" searchable=\"1\" childCount=\"$ChildCount\">".
421
			"<dc:title>$title</dc:title>".
422
			'<upnp:class>object.container.storageFolder</upnp:class>'.
423
			"<upnp:storageTotal>$StorageTotal</upnp:storageTotal>".
424
			"<upnp:storageFree>$StorageFree</upnp:storageFree>".
425
			"<upnp:storageUsed>$StorageUsed</upnp:storageUsed>".
426
			"<upnp:writeStatus>$WriteStatus</upnp:writeStatus>";
427
		if ('0' === $ObjectID) {
428
			$Result = $Result.
429
				'<upnp:searchClass includeDerived="1">object.item.audioItem</upnp:searchClass>'.
430
				'<upnp:searchClass includeDerived="1">object.item.imageItem</upnp:searchClass>'.
431
				'<upnp:searchClass includeDerived="1">object.item.videoItem</upnp:searchClass>';
432
		}
433
		$Result = $Result.'</container>';
434
	} else { /* File or playlist. */
435
		$iclass = upnp_get_class($basefilename, 'object.item.videoItem');
436
		if ('object.container.storageFolder' === $iclass) { /* Play list as folder! */
437
			$ChildCount = m3u_calc_items_count($filename);
438
			$Result = $Result.
439
			    "<container id=\"$ObjectID\" parentID=\"$ParentID\" restricted=\"$Restricted\" searchable=\"1\" childCount=\"$ChildCount\">".
440
				"<dc:title>$title</dc:title>".
441
				'<upnp:class>object.container.storageFolder</upnp:class>'.
442
			    '</container>';
443
		} else {
444
			$date = upnp_date(filectime($filename), 1);
445
			$size = filesize($filename);
446
			$mimetype = upnp_mime_content_type($filename);
447
			$Result = $Result.
448
			    "<item id=\"$ObjectID\" parentID=\"$ParentID\" restricted=\"$Restricted\">".
449
				"<dc:title>$title</dc:title>".
450
				"<dc:date>$date</dc:date>".
451
				"<upnp:class>$iclass</upnp:class>".
452
				"<res size=\"$size\" protocolInfo=\"http-get:*:$mimetype:*\">$ObjectID</res>".
453
			    '</item>';
454
		}
455
	}
456
	$Result = $Result.'</DIDL-Lite>';
457
	return (array('Result' => $Result,
458
			'NumberReturned' => 1,
459
			'TotalMatches' => 1,
460
			'UpdateID' => $UpdateID));
461
}
462
463
464
/* ContentDirectory funcs */
465
466
function GetSearchCapabilities() {
467
	// 'upnp:class'; /* dc:title,upnp:class,upnp:artist */
468
	//$SearchCaps = 'dc:creator,dc:date,dc:title,upnp:album,upnp:actor,upnp:artist,upnp:class,upnp:genre,@id,@parentID,@refID';
469
	$SearchCaps = 'dc:title';
470
471
	return ($SearchCaps);
472
}
473
474
475
function GetSortCapabilities() {
476
	$SortCaps = 'dc:title';
477
	/* dc:title,upnp:genre,upnp:album,dc:creator,res@size,
478
	 * res@duration,res@bitrate,dc:publisher,
479
	 * upnp:originalTrackNumber,dc:date,upnp:producer,upnp:rating,
480
	 * upnp:actor,upnp:director,dc:description
481
	 */
482
483
	return ($SortCaps);
484
}
485
486
487
function GetSortExtensionCapabilities() {
488
	$SortExtensionCaps = '';
489
490
	return ($SortExtensionCaps);
491
}
492
493
494
function GetFeatureList() {
495
	$FeatureList = '';
496
497
	return ($FeatureList);
498
}
499
500
501
function GetSystemUpdateID() {
502
	$Id = '1';
503
504
	return ($Id);
505
}
506
507
508
function GetServiceResetToken() {
509
	$ResetToken = '1';
510
511
	return ($ResetToken);
512
}
513
514
515
function Browse($ObjectID, $BrowseFlag, $Filter, $StartingIndex,
516
    $RequestedCount, $SortCriteria) {
517
	global $basedir, $baseurl, $baseurlpatch;
518
	$Result = '<DIDL-Lite'.
519
		    ' xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"'.
520
		    ' xmlns:dlna="urn:schemas-dlna-org:metadata-1-0/"'.
521
		    ' xmlns:dc="http://purl.org/dc/elements/1.1/"'.
522
		    ' xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"'.
523
		    ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'.
524
		    ' xsi:schemaLocation="'.
525
			'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/ http://www.upnp.org/schemas/av/didl-lite.xsd '.
526
			'urn:schemas-upnp-org:metadata-1-0/upnp/ http://www.upnp.org/schemas/av/upnp.xsd">';
527
	$NumberReturned = 0;
528
	$TotalMatches = 0;
529
	$UpdateID = 1;
530
531
	/* Check input param. */
532
	if (isset($ObjectID)) {
533
		$ObjectID_len = strlen($ObjectID);
534
		if ((1 === $ObjectID_len || (
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (1 === $ObjectID_len || ...substr($ObjectID, 0, 1), Probably Intended Meaning: 1 === $ObjectID_len || (...ubstr($ObjectID, 0, 1))
Loading history...
535
		     3 === $ObjectID_len && (
536
		      '_T' === substr($ObjectID, 1, 2) ||
537
		      '_D' === substr($ObjectID, 1, 2) ||
538
		      '_L' === substr($ObjectID, 1, 2)))) && (
539
		    '0' === substr($ObjectID, 0, 1) ||
540
		    'A' === substr($ObjectID, 0, 1) ||
541
		    'I' === substr($ObjectID, 0, 1) ||
542
		    'V' === substr($ObjectID, 0, 1) ||
543
		    'P' === substr($ObjectID, 0, 1) ||
544
		    'T' === substr($ObjectID, 0, 1))) { /* V, I, A, P, T - from X_GetFeatureList() */
545
			$ObjectID = '0';
546
			$dir = '';
547
		} else {
548
			$dir = rawurldecode(xml_decode($ObjectID));
549
			if ('/' !== substr($dir, -1, 1)) {
550
				$dir = $dir.'/';
551
			}
552
			/* Sec check: .. in path */
553
			$dotdotdir = '';
554
			$dirnames = explode('/', $dir);
555
			$dirnames_size = sizeof($dirnames);
556
			for ($di = 0; $di < $dirnames_size; $di++) {
557
				if ('.' === $dirnames[$di])
558
					continue;
559
				if ('..' === $dirnames[$di]) {
560
					$dir = '';
0 ignored issues
show
Unused Code introduced by
The assignment to $dir is dead and can be removed.
Loading history...
561
					break;
562
				}
563
				if ($dirnames_size >= $di) {
564
					$dotdotdir = $dotdotdir.$dirnames[$di].'/';
565
				}
566
			}
567
			$dir = $dotdotdir;
568
			if ('/' === substr($dir, 0, 1) /*|| !is_dir($basedir.$dir)*/) {
569
				$dir = '';
570
			}
571
			/* Remove tail slash from file name. */
572
			if (!is_dir($basedir.$dir) &&
573
			    '/' === substr($dir, -1, 1)) {
574
				$dir = substr($dir, 0, -1);
575
			}
576
		}
577
	} else {
578
		$ObjectID = '0';
579
		$dir = '';
580
	}
581
582
	if ('BrowseMetadata' === $BrowseFlag) {
583
		return (browse_metadata($basedir.$dir, $dir, $ObjectID,
584
		    $UpdateID, $Result));
585
	}
586
587
	if (!isset($StartingIndex)) {
588
		$StartingIndex = 0;
589
	}
590
	if (!isset($RequestedCount)) {
591
		$RequestedCount = 0;
592
	}
593
594
	if (!is_dir($basedir.$dir)) { /* Play list file? */
595
		return (m3u_browse($basedir.$dir, $ObjectID,
596
		    $StartingIndex, $RequestedCount, $UpdateID, $Result));
597
	}
598
599
	/* Scan directory and add to play list.*/
600
	$entries = scandir($basedir.$dir);
601
	/* Add dirs to play list. */
602
	foreach ($entries as $entry) {
603
		$filename = $basedir.$dir.$entry;
604
		if ('.' === substr($entry, 0, 1) ||
605
		    !is_dir($filename)) /* Skip files. */
606
			continue;
607
		/* Ok, item matched and may be returned. */
608
		$TotalMatches++;
609
		if (0 < $StartingIndex &&
610
		    $TotalMatches < $StartingIndex)
611
			continue; /* Skip first items. */
612
		if (0 < $RequestedCount &&
613
		    $NumberReturned >= $RequestedCount)
614
			continue; /* Do not add more than requested. */
615
		$NumberReturned++;
616
		/* Add item to result. */
617
		if (is_writable($filename)) {
618
			$Restricted = '0';
619
		} else {
620
			$Restricted = '1';
621
		}
622
		$title = xml_encode($entry);
623
		$en_entry = upnp_url_encode($dir.$entry);
624
		$ChildCount = (count(scandir($filename)) - 2);
625
		$Result = $Result.
626
		    "<container id=\"$en_entry\" parentID=\"$ObjectID\" restricted=\"$Restricted\" searchable=\"1\" childCount=\"$ChildCount\">".
627
			"<dc:title>$title</dc:title>".
628
			'<upnp:class>object.container.storageFolder</upnp:class>'.
629
		    '</container>';
630
	}
631
	/* Add files to play list. */
632
	foreach ($entries as $entry) {
633
		$filename = $basedir.$dir.$entry;
634
		if (is_dir($filename)) /* Skip dirs. */
635
			continue;
636
		$iclass = upnp_get_class($entry, null);
637
		if (null === $iclass) /* Skip unsupported file type. */
638
			continue;
639
		/* Ok, item matched and may be returned. */
640
		$TotalMatches++;
641
		if (0 < $StartingIndex &&
642
		    $TotalMatches < $StartingIndex)
643
			continue; /* Skip first items. */
644
		if (0 < $RequestedCount &&
645
		    $NumberReturned >= $RequestedCount)
646
			continue; /* Do not add more than requested. */
647
		$NumberReturned++;
648
		/* Add item to result. */
649
		if (is_writable($filename)) {
650
			$Restricted = '0';
651
		} else {
652
			$Restricted = '1';
653
		}
654
		$title = xml_encode($entry);
655
		$en_entry = upnp_url_encode($dir.$entry);
656
		if ('object.container.storageFolder' === $iclass) { /* Play list as folder! */
657
			$ChildCount = m3u_calc_items_count($filename);
658
			$Result = $Result.
659
			    "<container id=\"$en_entry\" parentID=\"$ObjectID\" restricted=\"$Restricted\" searchable=\"1\" childCount=\"$ChildCount\">".
660
				"<dc:title>$title</dc:title>".
661
				'<upnp:class>object.container.storageFolder</upnp:class>'.
662
			    '</container>';
663
		} else {
664
			$date = upnp_date(filectime($filename), 1);
665
			$size = filesize($filename);
666
			$mimetype = upnp_mime_content_type($filename);
667
			$res_info_ex = '';
668
			$Result = $Result.
669
			    "<item id=\"$en_entry\" parentID=\"$ObjectID\" restricted=\"$Restricted\">".
670
				"<dc:title>$title</dc:title>".
671
				"<dc:date>$date</dc:date>".
672
				"<upnp:class>$iclass</upnp:class>";
673
			if ('object.item.imageItem' === substr($iclass, 0, 21)) {
674
				$Result = $Result.
675
				    "<upnp:albumArtURI>$baseurlpatch$en_entry</upnp:albumArtURI>".
676
				    "<upnp:icon>$baseurlpatch$en_entry</upnp:icon>";
677
				$img_info = getimagesize($filename);
678
				if (false !== $img_info) {
679
					$res_info_ex = ' resolution="'.$img_info[0].'x'.$img_info[1].'"';
680
				}
681
			}
682
			$Result = $Result.
683
				"<res size=\"$size\"$res_info_ex protocolInfo=\"http-get:*:$mimetype:*\">$baseurlpatch$en_entry</res>".
684
			    '</item>';
685
		}
686
	}
687
688
	$Result = $Result.'</DIDL-Lite>';
689
	return (array('Result' => $Result,
690
			'NumberReturned' => $NumberReturned,
691
			'TotalMatches' => $TotalMatches,
692
			'UpdateID' => $UpdateID));
693
}
694
695
696
function Search($ContainerID, $SearchCriteria, $Filter, $StartingIndex,
697
    $RequestedCount, $SortCriteria) {
698
	global $basedir, $baseurl, $baseurlpatch;
699
	$Result = '<DIDL-Lite'.
700
		    ' xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"'.
701
		    ' xmlns:dlna="urn:schemas-dlna-org:metadata-1-0/"'.
702
		    ' xmlns:dc="http://purl.org/dc/elements/1.1/"'.
703
		    ' xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"'.
704
		    ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'.
705
		    ' xsi:schemaLocation="'.
706
			'urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/ http://www.upnp.org/schemas/av/didl-lite.xsd '.
707
			'urn:schemas-upnp-org:metadata-1-0/upnp/ http://www.upnp.org/schemas/av/upnp.xsd">';
708
	$NumberReturned = 0;
709
	$TotalMatches = 0;
710
	$UpdateID = 1;
711
712
	$Result = $Result.'</DIDL-Lite>';
713
714
	return (array('Result' => $Result,
715
			'NumberReturned' => $NumberReturned,
716
			'TotalMatches' => $TotalMatches,
717
			'UpdateID' => $UpdateID));
718
}
719
720
721
function CreateObject($ContainerID, $Elements) {
722
	$ObjectID = '';
723
	$Result = '';
724
725
	return (array('ObjectID' => $ObjectID,
726
			'Result' => $Result));
727
}
728
729
730
function DestroyObject($ObjectID) {
731
}
732
733
734
function UpdateObject($ObjectID, $CurrentTagValue, $NewTagValue) {
735
}
736
737
738
function MoveObject($ObjectID, $NewParentID, $NewObjectID) {
739
}
740
741
742
/* Samsung private. */
743
function X_GetFeatureList() {
744
	$FeatureList = 
745
		'<?xml version="1.0" encoding="UTF-8"?>'.
746
		'<Features'.
747
		' xmlns="urn:schemas-upnp-org:av:avs"'.
748
		' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'.
749
		' xmlns:sec="http://www.sec.co.kr/dlna"'.
750
		' xsi:schemaLocation="urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd">'.
751
			'<Feature name="samsung.com_BASICVIEW" version="1">'.
752
			    '<container id="A" type="object.item.audioItem"/>'.
753
			    '<container id="I" type="object.item.imageItem"/>'.
754
			    '<container id="V" type="object.item.videoItem"/>'.
755
			    '<container id="P" type="object.item.playlistItem"/>'.
756
			    '<container id="T" type="object.item.textItem"/>'.
757
			'</Feature>'.
758
		'</Features>';
759
760
	return ($FeatureList);
761
}
762
763
function X_SetBookmark($CategoryType, $RID, $ObjectID, $PosSecond) {
764
	/* Return:
765
	 * <sec:dcmInfo>BM=number of seconds</sec:dcmInfo>
766
	 * <upnp:lastPlaybackPosition>HH:MM:SS</upnp:lastPlaybackPosition>
767
	 */
768
}
769
770
771
772
/* Process request. */
773
774
$request_body = @file_get_contents('php://input');
775
if (is_string($request_body) === false) {
776
	header($_SERVER["SERVER_PROTOCOL"]." 400 Bad request");
777
	die();	
778
}
779
try {
780
	$server->addFunction(array('GetSearchCapabilities',
781
					'GetSortCapabilities',
782
					'GetSortExtensionCapabilities',
783
					'GetFeatureList',
784
					'GetSystemUpdateID',
785
					'GetServiceResetToken',
786
					'Browse',
787
					'Search',
788
					'CreateObject',
789
					'DestroyObject',
790
					'UpdateObject',
791
					'MoveObject',
792
					'X_GetFeatureList',
793
					'X_SetBookmark'
794
				));
795
	ob_start();
796
	/* Type checking done before, it is safe to ignore type warning here. */
797
	$server->handle(/** @scrutinizer ignore-type */ $request_body); 
798
	$soapXml = ob_get_clean();
799
} catch (Exception $e) {
800
	$server->fault($e->getCode(), $e->getMessage());
801
	throw $e;
802
}
803
804
805
/* Post processing. */
806
807
808
function get_resp_tag_name($sxml) {
809
	$tag_st = strpos($sxml, '<SOAP-ENV:Body><SOAP-ENV:');
810
	if (false === $tag_st)
811
		return (false);
812
	$tag_st += 25;
813
	$tag_end = strpos($sxml, '>', $tag_st);
814
	if (false === $tag_end)
815
		return (false);
816
	return (substr($sxml, $tag_st, ($tag_end - $tag_st)));
817
}
818
819
function get_tag_ns($req, $tag) {
820
	$rreq = strrev($req);
821
	$ns_st = strpos($rreq, strrev(":$tag>"));
822
	if (false === $ns_st)
823
		return (false);
824
	$ns_st += (strlen($tag) + 2);
825
	$ns_end = strpos($rreq, '/<', $ns_st);
826
	if (false === $ns_end)
827
		return (false);
828
	return (strrev(substr($rreq, $ns_st, ($ns_end - $ns_st))));
829
}
830
831
function tag_ns_replace($req, $sxml, $tag, $ns = false) {
832
	if (false === $tag)
833
		return ($sxml);
834
	if (false === $ns)
835
		$ns = get_tag_ns($req, $tag);
836
	if (false === $ns)
837
		return ($sxml);
838
	while ($tag_st = strpos($sxml, "<SOAP-ENV:$tag")) {
839
		$tag_end = strpos($sxml, '>', $tag_st);
840
		if (false === $tag_end)
841
			return ($sxml);
842
		$old_tag_data = substr($sxml, $tag_st, ($tag_end - $tag_st));
843
		$new_tag_data = str_replace('SOAP-ENV', $ns, $old_tag_data);
844
		$sxml = str_replace($old_tag_data, $new_tag_data, $sxml);
845
	}
846
	return (str_replace("</SOAP-ENV:$tag>", "</$ns:$tag>", $sxml));
847
}
848
849
850
/* Add encodingStyle attr. */
851
$soapXml = str_replace('<SOAP-ENV:Envelope', '<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"', $soapXml);
852
853
/* Add xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1" */
854
$soap_act_resp_tag = get_resp_tag_name($soapXml);
855
$soapXml = str_replace("<SOAP-ENV:$soap_act_resp_tag", "<SOAP-ENV:$soap_act_resp_tag xmlns:SOAP-ENV=\"urn:schemas-upnp-org:service:ContentDirectory:$soap_service_ver\"", $soapXml);
856
857
/* Restore name spaces from request. */
858
$soapXml = tag_ns_replace($request_body, $soapXml, 'Envelope');
859
$soapXml = tag_ns_replace($request_body, $soapXml, 'Body');
860
$soapXml = tag_ns_replace($request_body, $soapXml, $soap_act_resp_tag, get_tag_ns($request_body, $soap_service_func));
861
862
$length = strlen($soapXml);
863
header('Content-Length: '.$length);
864
echo $soapXml;
865
866
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
867