1/* MiniDLNA project
2 *
3 * http://sourceforge.net/projects/minidlna/
4 *
5 * MiniDLNA media server
6 * Copyright (C) 2008-2009  Justin Maggard
7 *
8 * This file is part of MiniDLNA.
9 *
10 * MiniDLNA is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
12 * published by the Free Software Foundation.
13 *
14 * MiniDLNA is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
21 *
22 * Portions of the code from the MiniUPnP project:
23 *
24 * Copyright (c) 2006-2007, Thomas Bernard
25 * All rights reserved.
26 *
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions are met:
29 *     * Redistributions of source code must retain the above copyright
30 *       notice, this list of conditions and the following disclaimer.
31 *     * Redistributions in binary form must reproduce the above copyright
32 *       notice, this list of conditions and the following disclaimer in the
33 *       documentation and/or other materials provided with the distribution.
34 *     * The name of the author may not be used to endorse or promote products
35 *       derived from this software without specific prior written permission.
36 *
37 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
38 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
39 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
40 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
41 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
43 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
44 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
45 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
46 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
47 * POSSIBILITY OF SUCH DAMAGE.
48 */
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#include <sys/socket.h>
53#include <unistd.h>
54#include <dirent.h>
55#include <sys/stat.h>
56#include <sys/types.h>
57#include <arpa/inet.h>
58#include <netinet/in.h>
59#include <netdb.h>
60#include <ctype.h>
61
62#include "config.h"
63#include "upnpglobalvars.h"
64#include "utils.h"
65#include "upnphttp.h"
66#include "upnpsoap.h"
67#include "upnpreplyparse.h"
68#include "getifaddr.h"
69#include "scanner.h"
70#include "sql.h"
71#include "log.h"
72
73static void
74BuildSendAndCloseSoapResp(struct upnphttp * h,
75                          const char * body, int bodylen)
76{
77	static const char beforebody[] =
78		"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
79		"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
80		"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
81		"<s:Body>";
82
83	static const char afterbody[] =
84		"</s:Body>"
85		"</s:Envelope>\r\n";
86
87	BuildHeader_upnphttp(h, 200, "OK",  sizeof(beforebody) - 1
88		+ sizeof(afterbody) - 1 + bodylen );
89
90	memcpy(h->res_buf + h->res_buflen, beforebody, sizeof(beforebody) - 1);
91	h->res_buflen += sizeof(beforebody) - 1;
92
93	memcpy(h->res_buf + h->res_buflen, body, bodylen);
94	h->res_buflen += bodylen;
95
96	memcpy(h->res_buf + h->res_buflen, afterbody, sizeof(afterbody) - 1);
97	h->res_buflen += sizeof(afterbody) - 1;
98
99	SendResp_upnphttp(h);
100	CloseSocket_upnphttp(h);
101}
102
103static void
104GetSystemUpdateID(struct upnphttp * h, const char * action)
105{
106	static const char resp[] =
107		"<u:%sResponse "
108		"xmlns:u=\"%s\">"
109		"<Id>%d</Id>"
110		"</u:%sResponse>";
111
112	char body[512];
113	int bodylen;
114	struct NameValueParserData data;
115	char *dummyArgument;
116
117	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
118  dummyArgument = GetValueFromNameValueList(&data, "dummyArgument");
119
120  if(dummyArgument)
121  {
122			SoapError(h, 500, "Invalid Args");
123      goto get_system_update_id_error;
124  }
125	bodylen = snprintf(body, sizeof(body), resp,
126		action, "urn:schemas-upnp-org:service:ContentDirectory:1",
127		updateID, action);
128	BuildSendAndCloseSoapResp(h, body, bodylen);
129
130get_system_update_id_error:
131	ClearNameValueList(&data);
132
133}
134
135static void
136IsAuthorizedValidated(struct upnphttp * h, const char * action)
137{
138	static const char resp[] =
139		"<u:%sResponse "
140		"xmlns:u=\"%s\">"
141		"<Result>%d</Result>"
142		"</u:%sResponse>";
143
144	char body[512];
145	int bodylen;
146	struct NameValueParserData data;
147	const char * id;
148	char *dummyArgument;
149
150	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
151	id = GetValueFromNameValueList(&data, "DeviceID");
152  dummyArgument = GetValueFromNameValueList(&data, "dummyArgument");
153
154  if(dummyArgument)
155  {
156			SoapError(h, 402, "Invalid Args");
157      goto is_authorized_error;
158  }
159
160	if (!id)
161		id = strstr(h->req_buf + h->req_contentoff, "<DeviceID");
162	if(id)
163	{
164		bodylen = snprintf(body, sizeof(body), resp,
165			action, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
166			1, action);
167		BuildSendAndCloseSoapResp(h, body, bodylen);
168	}
169	else
170		SoapError(h, 402, "Invalid Args");
171
172is_authorized_error:
173	ClearNameValueList(&data);
174}
175
176static void
177GetProtocolInfo(struct upnphttp * h, const char * action)
178{
179	static const char resp[] =
180		"<u:%sResponse "
181		"xmlns:u=\"%s\">"
182		"<Source>"
183		RESOURCE_PROTOCOL_INFO_VALUES
184		"</Source>"
185		"<Sink></Sink>"
186		"</u:%sResponse>";
187
188	char * body;
189	int bodylen;
190
191	struct NameValueParserData data;
192	char *dummyArgument;
193
194	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
195  dummyArgument = GetValueFromNameValueList(&data, "dummyArgument");
196
197  if(dummyArgument)
198  {
199			SoapError(h, 402, "Invalid Args");
200      goto get_protocol_id_error;
201  }
202	bodylen = asprintf(&body, resp,
203		action, "urn:schemas-upnp-org:service:ConnectionManager:1",
204		action);
205	BuildSendAndCloseSoapResp(h, body, bodylen);
206	free(body);
207get_protocol_id_error:
208	ClearNameValueList(&data);
209}
210
211static void
212GetSortCapabilities(struct upnphttp * h, const char * action)
213{
214	static const char resp[] =
215		"<u:%sResponse "
216		"xmlns:u=\"%s\">"
217		"<SortCaps>"
218		  "dc:title,"
219		  "dc:date,"
220		  "upnp:class,"
221		  "upnp:album,"
222		  "upnp:originalTrackNumber"
223		"</SortCaps>"
224		"</u:%sResponse>";
225
226	char body[512];
227	int bodylen;
228
229	struct NameValueParserData data;
230	char *dummyArgument;
231
232	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
233  dummyArgument = GetValueFromNameValueList(&data, "dummyArgument");
234
235  if(dummyArgument)
236  {
237			SoapError(h, 402, "Invalid Args");
238      goto get_sort_capabilities_error;
239  }
240	bodylen = snprintf(body, sizeof(body), resp,
241		action, "urn:schemas-upnp-org:service:ContentDirectory:1",
242		action);
243	BuildSendAndCloseSoapResp(h, body, bodylen);
244
245get_sort_capabilities_error:
246	ClearNameValueList(&data);
247}
248
249static void
250GetSearchCapabilities(struct upnphttp * h, const char * action)
251{
252
253	static const char resp[] =
254		"<u:%sResponse xmlns:u=\"%s\">"
255		"<SearchCaps>"
256		  "dc:creator,"
257		  "dc:date,"
258		  "dc:title,"
259		  "upnp:album,"
260		  "upnp:actor,"
261		  "upnp:artist,"
262		  "upnp:class,"
263		  "upnp:genre,"
264		  "@refID"
265		"</SearchCaps>"
266		"</u:%sResponse>";
267
268	char body[512];
269	int bodylen;
270
271	struct NameValueParserData data;
272	char *SearchCaps,*dummyArgument;
273
274	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
275  SearchCaps = GetValueFromNameValueList(&data, "SearchCaps");
276  dummyArgument = GetValueFromNameValueList(&data, "dummyArgument");
277
278  if(dummyArgument)
279  {
280			SoapError(h, 402, "Invalid Args");
281      goto search_capabilities_error;
282  }
283/*
284  if(!SearchCaps)
285  {
286		SoapError(h, 402, "Invalid Args");
287      goto search_capabilities_error;
288  }
289*/
290	bodylen = snprintf(body, sizeof(body), resp,
291		action, "urn:schemas-upnp-org:service:ContentDirectory:1",
292		action);
293	BuildSendAndCloseSoapResp(h, body, bodylen);
294search_capabilities_error:
295	ClearNameValueList(&data);
296}
297
298static void
299GetCurrentConnectionIDs(struct upnphttp * h, const char * action)
300{
301	/* TODO: Use real data. - JM */
302	static const char resp[] =
303		"<u:%sResponse "
304		"xmlns:u=\"%s\">"
305		"<ConnectionIDs>0</ConnectionIDs>"
306		"</u:%sResponse>";
307
308	char body[512];
309	int bodylen;
310	struct NameValueParserData data;
311	char *dummyArgument;
312
313	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
314  dummyArgument = GetValueFromNameValueList(&data, "dummyArgument");
315
316  if(dummyArgument)
317  {
318			SoapError(h, 402, "Invalid Args");
319      goto get_current_connection_ids_error;
320  }
321
322	bodylen = snprintf(body, sizeof(body), resp,
323		action, "urn:schemas-upnp-org:service:ConnectionManager:1",
324		action);
325	BuildSendAndCloseSoapResp(h, body, bodylen);
326
327get_current_connection_ids_error:
328	ClearNameValueList(&data);
329
330}
331
332static void
333GetCurrentConnectionInfo(struct upnphttp * h, const char * action)
334{
335	/* TODO: Use real data. - JM */
336	static const char resp[] =
337		"<u:%sResponse "
338		"xmlns:u=\"%s\">"
339		"<RcsID>-1</RcsID>"
340		"<AVTransportID>-1</AVTransportID>"
341		"<ProtocolInfo></ProtocolInfo>"
342		"<PeerConnectionManager></PeerConnectionManager>"
343		"<PeerConnectionID>-1</PeerConnectionID>"
344		"<Direction>Output</Direction>"
345		"<Status>Unknown</Status>"
346		"</u:%sResponse>";
347
348	struct NameValueParserData data;
349	char * ConnectionID,*dummyArgument;
350
351	char body[sizeof(resp)+128];
352	int bodylen;
353	const char * id_str;
354	int id;
355	char *endptr;
356	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
357  dummyArgument = GetValueFromNameValueList(&data, "dummyArgument");
358
359  if(dummyArgument)
360  {
361			SoapError(h, 402, "Invalid Args");
362      goto get_connection_info_error;
363  }
364
365
366	id_str = GetValueFromNameValueList(&data, "ConnectionID");
367	DPRINTF(E_INFO, L_HTTP, "GetCurrentConnectionInfo(%s)\n", id_str);
368	if(id_str)
369		id = strtol(id_str, &endptr, 10);
370	if(!id_str || !id_str[0] || endptr[0] || id != 0)
371	{
372		SoapError(h, 402, "Invalid Args");
373	}
374	else if(id != 0)
375	{
376		SoapError(h, 701, "No such object error");
377	}
378	else
379	{
380		bodylen = snprintf(body, sizeof(body), resp,
381			action, "urn:schemas-upnp-org:service:ConnectionManager:1",
382			action);
383		BuildSendAndCloseSoapResp(h, body, bodylen);
384	}
385get_connection_info_error:
386	ClearNameValueList(&data);
387}
388
389static void
390mime_to_ext(const char * mime, char * buf)
391{
392	switch( *mime )
393	{
394		/* Audio extensions */
395		case 'a':
396			if( strcmp(mime+6, "mpeg") == 0 )
397				strcpy(buf, "mp3");
398			else if( strcmp(mime+6, "mp4") == 0 )
399				strcpy(buf, "m4a");
400			else if( strcmp(mime+6, "x-ms-wma") == 0 )
401				strcpy(buf, "wma");
402			else if( strcmp(mime+6, "x-flac") == 0 )
403				strcpy(buf, "flac");
404			else if( strcmp(mime+6, "flac") == 0 )
405				strcpy(buf, "flac");
406			else if( strcmp(mime+6, "x-wav") == 0 )
407				strcpy(buf, "wav");
408			else if( strncmp(mime+6, "L16", 3) == 0 )
409				strcpy(buf, "pcm");
410			else if( strcmp(mime+6, "3gpp") == 0 )
411				strcpy(buf, "3gp");
412			else if( strcmp(mime, "application/ogg") == 0 )
413				strcpy(buf, "ogg");
414			else
415				strcpy(buf, "dat");
416			break;
417		case 'v':
418			if( strcmp(mime+6, "avi") == 0 )
419				strcpy(buf, "avi");
420			else if( strcmp(mime+6, "divx") == 0 )
421				strcpy(buf, "avi");
422			else if( strcmp(mime+6, "x-msvideo") == 0 )
423				strcpy(buf, "avi");
424			else if( strcmp(mime+6, "mpeg") == 0 )
425				strcpy(buf, "mpg");
426			else if( strcmp(mime+6, "mp4") == 0 )
427				strcpy(buf, "mp4");
428			else if( strcmp(mime+6, "x-ms-wmv") == 0 )
429				strcpy(buf, "wmv");
430			else if( strcmp(mime+6, "x-matroska") == 0 )
431				strcpy(buf, "mkv");
432			else if( strcmp(mime+6, "x-mkv") == 0 )
433				strcpy(buf, "mkv");
434			else if( strcmp(mime+6, "x-flv") == 0 )
435				strcpy(buf, "flv");
436			else if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
437				strcpy(buf, "mpg");
438			else if( strcmp(mime+6, "quicktime") == 0 )
439				strcpy(buf, "mov");
440			else if( strcmp(mime+6, "3gpp") == 0 )
441				strcpy(buf, "3gp");
442			else if( strcmp(mime+6, "x-tivo-mpeg") == 0 )
443				strcpy(buf, "TiVo");
444			else
445				strcpy(buf, "dat");
446			break;
447		case 'i':
448			if( strcmp(mime+6, "jpeg") == 0 )
449				strcpy(buf, "jpg");
450			else if( strcmp(mime+6, "png") == 0 )
451				strcpy(buf, "png");
452			else
453				strcpy(buf, "dat");
454			break;
455		default:
456			strcpy(buf, "dat");
457			break;
458	}
459}
460
461/* Standard DLNA/UPnP filter flags */
462#define FILTER_CHILDCOUNT                        0x00000001
463#define FILTER_DC_CREATOR                        0x00000002
464#define FILTER_DC_DATE                           0x00000004
465#define FILTER_DC_DESCRIPTION                    0x00000008
466#define FILTER_DLNA_NAMESPACE                    0x00000010
467#define FILTER_REFID                             0x00000020
468#define FILTER_RES                               0x00000040
469#define FILTER_RES_BITRATE                       0x00000080
470#define FILTER_RES_DURATION                      0x00000100
471#define FILTER_RES_NRAUDIOCHANNELS               0x00000200
472#define FILTER_RES_RESOLUTION                    0x00000400
473#define FILTER_RES_SAMPLEFREQUENCY               0x00000800
474#define FILTER_RES_SIZE                          0x00001000
475#define FILTER_UPNP_ACTOR                        0x00002000
476#define FILTER_UPNP_ALBUM                        0x00004000
477#define FILTER_UPNP_ALBUMARTURI                  0x00008000
478#define FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID   0x00010000
479#define FILTER_UPNP_ARTIST                       0x00020000
480#define FILTER_UPNP_GENRE                        0x00040000
481#define FILTER_UPNP_ORIGINALTRACKNUMBER          0x00080000
482#define FILTER_UPNP_SEARCHCLASS                  0x00100000
483#define FILTER_UPNP_STORAGEUSED                  0x00200000
484/* Vendor-specific filter flags */
485#define FILTER_SEC_CAPTION_INFO_EX               0x01000000
486#define FILTER_SEC_DCM_INFO                      0x02000000
487#define FILTER_PV_SUBTITLE_FILE_TYPE             0x04000000
488#define FILTER_PV_SUBTITLE_FILE_URI              0x08000000
489#define FILTER_AV_MEDIA_CLASS                    0x10000000
490
491static u_int32_t
492set_filter_flags(char * filter, struct upnphttp *h)
493{
494	char *item, *saveptr = NULL;
495	u_int32_t flags = 0;
496
497	if( !filter || (strlen(filter) <= 1) )
498		/* Not the full 32 bits.  Skip vendor-specific stuff by default. */
499		return 0xFFFFFF;
500	if( h->reqflags & FLAG_SAMSUNG )
501		flags |= FILTER_DLNA_NAMESPACE;
502	item = strtok_r(filter, ",", &saveptr);
503	while( item != NULL )
504	{
505		if( saveptr )
506			*(item-1) = ',';
507		while( isspace(*item) )
508			item++;
509		if( strcmp(item, "@childCount") == 0 )
510		{
511			flags |= FILTER_CHILDCOUNT;
512		}
513		else if( strcmp(item, "dc:creator") == 0 )
514		{
515			flags |= FILTER_DC_CREATOR;
516		}
517		else if( strcmp(item, "dc:date") == 0 )
518		{
519			flags |= FILTER_DC_DATE;
520		}
521		else if( strcmp(item, "dc:description") == 0 )
522		{
523			flags |= FILTER_DC_DESCRIPTION;
524		}
525		else if( strcmp(item, "dlna") == 0 )
526		{
527			flags |= FILTER_DLNA_NAMESPACE;
528		}
529		else if( strcmp(item, "@refID") == 0 )
530		{
531			flags |= FILTER_REFID;
532		}
533		else if( strcmp(item, "upnp:album") == 0 )
534		{
535			flags |= FILTER_UPNP_ALBUM;
536		}
537		else if( strcmp(item, "upnp:albumArtURI") == 0 )
538		{
539			flags |= FILTER_UPNP_ALBUMARTURI;
540			if( h->reqflags & FLAG_SAMSUNG )
541				flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID;
542		}
543		else if( strcmp(item, "upnp:albumArtURI@dlna:profileID") == 0 )
544		{
545			flags |= FILTER_UPNP_ALBUMARTURI;
546			flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID;
547		}
548		else if( strcmp(item, "upnp:artist") == 0 )
549		{
550			flags |= FILTER_UPNP_ARTIST;
551		}
552		else if( strcmp(item, "upnp:actor") == 0 )
553		{
554			flags |= FILTER_UPNP_ACTOR;
555		}
556		else if( strcmp(item, "upnp:genre") == 0 )
557		{
558			flags |= FILTER_UPNP_GENRE;
559		}
560		else if( strcmp(item, "upnp:originalTrackNumber") == 0 )
561		{
562			flags |= FILTER_UPNP_ORIGINALTRACKNUMBER;
563		}
564		else if( strcmp(item, "upnp:searchClass") == 0 )
565		{
566			flags |= FILTER_UPNP_SEARCHCLASS;
567		}
568		else if( strcmp(item, "upnp:storageUsed") == 0 )
569		{
570			flags |= FILTER_UPNP_STORAGEUSED;
571		}
572		else if( strcmp(item, "res") == 0 )
573		{
574			flags |= FILTER_RES;
575		}
576		else if( (strcmp(item, "res@bitrate") == 0) ||
577		         (strcmp(item, "@bitrate") == 0) ||
578		         ((strcmp(item, "bitrate") == 0) && (flags & FILTER_RES)) )
579		{
580			flags |= FILTER_RES;
581			flags |= FILTER_RES_BITRATE;
582		}
583		else if( (strcmp(item, "res@duration") == 0) ||
584		         (strcmp(item, "@duration") == 0) ||
585		         ((strcmp(item, "duration") == 0) && (flags & FILTER_RES)) )
586		{
587			flags |= FILTER_RES;
588			flags |= FILTER_RES_DURATION;
589		}
590		else if( (strcmp(item, "res@nrAudioChannels") == 0) ||
591		         (strcmp(item, "@nrAudioChannels") == 0) ||
592		         ((strcmp(item, "nrAudioChannels") == 0) && (flags & FILTER_RES)) )
593		{
594			flags |= FILTER_RES;
595			flags |= FILTER_RES_NRAUDIOCHANNELS;
596		}
597		else if( (strcmp(item, "res@resolution") == 0) ||
598		         (strcmp(item, "@resolution") == 0) ||
599		         ((strcmp(item, "resolution") == 0) && (flags & FILTER_RES)) )
600		{
601			flags |= FILTER_RES;
602			flags |= FILTER_RES_RESOLUTION;
603		}
604		else if( (strcmp(item, "res@sampleFrequency") == 0) ||
605		         (strcmp(item, "@sampleFrequency") == 0) ||
606		         ((strcmp(item, "sampleFrequency") == 0) && (flags & FILTER_RES)) )
607		{
608			flags |= FILTER_RES;
609			flags |= FILTER_RES_SAMPLEFREQUENCY;
610		}
611		else if( (strcmp(item, "res@size") == 0) ||
612		         (strcmp(item, "@size") == 0) ||
613		         (strcmp(item, "size") == 0) )
614		{
615			flags |= FILTER_RES;
616			flags |= FILTER_RES_SIZE;
617		}
618		else if( strcmp(item, "sec:CaptionInfoEx") == 0 )
619		{
620			flags |= FILTER_SEC_CAPTION_INFO_EX;
621		}
622		else if( strcmp(item, "sec:dcmInfo") == 0 )
623		{
624			flags |= FILTER_SEC_DCM_INFO;
625		}
626		else if( strcmp(item, "res@pv:subtitleFileType") == 0 )
627		{
628			flags |= FILTER_PV_SUBTITLE_FILE_TYPE;
629		}
630		else if( strcmp(item, "res@pv:subtitleFileUri") == 0 )
631		{
632			flags |= FILTER_PV_SUBTITLE_FILE_URI;
633		}
634		else if( strcmp(item, "av:mediaClass") == 0 )
635		{
636			flags |= FILTER_AV_MEDIA_CLASS;
637		}
638		item = strtok_r(NULL, ",", &saveptr);
639	}
640
641	return flags;
642}
643
644int
645check_sort_criteria(char *sortCriteria)
646{
647	char *item, *saveptr;
648	int i, ret, reverse, title_sorted = 0;
649	int ret_value = 0;
650
651	item = strtok_r(sortCriteria, ",", &saveptr);
652
653	for( i=0; item != NULL; i++ )
654	{
655		reverse=0;
656
657		if( *item == '+' )
658		{
659			item++;
660		}
661		else if( *item == '-' )
662		{
663			reverse = 1;
664			item++;
665		}
666		else
667		{
668			DPRINTF(E_DEBUG, L_HTTP, "No order specified [%s]\n", item);
669			ret_value = -1;
670			goto unhandled_order;
671		}
672		if( strcasecmp(item, "upnp:class") == 0 )
673		{
674		}
675		else if( strcasecmp(item, "dc:title") == 0 )
676		{
677		}
678		else if( strcasecmp(item, "dc:date") == 0 )
679		{
680		}
681		else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 )
682		{
683		}
684		else if( strcasecmp(item, "upnp:album") == 0 )
685		{
686		}
687		else
688		{
689			DPRINTF(E_DEBUG, L_HTTP, "Unhandled SortCriteria [%s]\n", item);
690			ret_value = -1;
691printf("unhandled sort Criteria\n");
692			goto unhandled_order;
693		}
694
695		unhandled_order:
696    if(ret_value==-1)
697    	return ret_value;
698		item = strtok_r(NULL, ",", &saveptr);
699	}
700
701	return ret_value;
702}
703
704
705char *
706parse_sort_criteria(char *sortCriteria, int *error)
707{
708	char *order = NULL;
709	char *item, *saveptr;
710	int i, ret, reverse, title_sorted = 0;
711	struct string_s str;
712	*error = 0;
713
714	if( !sortCriteria )
715		return NULL;
716
717	if( (item = strtok_r(sortCriteria, ",", &saveptr)) )
718	{
719		order = malloc(4096);
720		str.data = order;
721		str.size = 4096;
722		str.off = 0;
723		strcatf(&str, "order by  ");
724	}
725	for( i=0; item != NULL; i++ )
726	{
727		reverse=0;
728		if( i )
729			strcatf(&str, ", ");
730		if( *item == '+' )
731		{
732			item++;
733		}
734		else if( *item == '-' )
735		{
736			reverse = 1;
737			item++;
738		}
739		else
740		{
741			DPRINTF(E_ERROR, L_HTTP, "No order specified [%s]\n", item);
742			*error = -1;
743			goto unhandled_order;
744		}
745		if( strcasecmp(item, "upnp:class") == 0 )
746		{
747			strcatf(&str, "o.CLASS");
748		}
749		else if( strcasecmp(item, "dc:title") == 0 )
750		{
751			strcatf(&str, "d.TITLE");
752			title_sorted = 1;
753		}
754		else if( strcasecmp(item, "dc:date") == 0 )
755		{
756			strcatf(&str, "d.DATE");
757		}
758		else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 )
759		{
760			strcatf(&str, "d.DISC, d.TRACK");
761		}
762		else if( strcasecmp(item, "upnp:album") == 0 )
763		{
764			strcatf(&str, "d.ALBUM");
765		}
766		else
767		{
768			DPRINTF(E_ERROR, L_HTTP, "Unhandled SortCriteria [%s]\n", item);
769			*error = -1;
770			if( i )
771			{
772				ret = strlen(order);
773				order[ret-2] = '\0';
774			}
775			i--;
776			goto unhandled_order;
777		}
778
779		if( reverse )
780			strcatf(&str, " DESC");
781		unhandled_order:
782		item = strtok_r(NULL, ",", &saveptr);
783	}
784	if( i <= 0 )
785	{
786		free(order);
787		return NULL;
788	}
789	/* Add a "tiebreaker" sort order */
790	if( !title_sorted )
791		strcatf(&str, ", TITLE ASC");
792
793	return order;
794}
795
796inline static void
797add_resized_res(int srcw, int srch, int reqw, int reqh, char *dlna_pn,
798                char *detailID, struct Response *args)
799{
800	int dstw = reqw;
801	int dsth = reqh;
802
803	if( args->flags & FLAG_NO_RESIZE )
804		return;
805
806	strcatf(args->str, "&lt;res ");
807	if( args->filter & FILTER_RES_RESOLUTION )
808	{
809		dstw = reqw;
810		dsth = ((((reqw<<10)/srcw)*srch)>>10);
811		if( dsth > reqh ) {
812			dsth = reqh;
813			dstw = (((reqh<<10)/srch) * srcw>>10);
814		}
815		strcatf(args->str, "resolution=\"%dx%d\" ", dstw, dsth);
816	}
817	strcatf(args->str, "protocolInfo=\"http-get:*:image/jpeg:"
818//	                          "DLNA.ORG_PN=%s;DLNA.ORG_CI=1;DLNA.ORG_FLAGS=%08X%024X\"&gt;"
819	                          "DLNA.ORG_PN=%s\"&gt;"
820	                          "http://%s:%d/Resized/%s.jpg?width=%d,height=%d"
821	                          "&lt;/res&gt;",
822//	                          dlna_pn, DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B|DLNA_FLAG_TM_I, 0,
823	                          dlna_pn,
824	                          lan_addr[args->iface].str, runtime_vars.port,
825	                          detailID, dstw, dsth);
826}
827
828inline static void
829add_res(char *size, char *duration, char *bitrate, char *sampleFrequency,
830        char *nrAudioChannels, char *resolution, char *dlna_pn, char *mime,
831        char *detailID, char *ext, struct Response *args)
832{
833	strcatf(args->str, "&lt;res ");
834	if( size && (args->filter & FILTER_RES_SIZE) ) {
835		strcatf(args->str, "size=\"%s\" ", size);
836	}
837	if( duration && (args->filter & FILTER_RES_DURATION) ) {
838		strcatf(args->str, "duration=\"%s\" ", duration);
839	}
840	if( bitrate && (args->filter & FILTER_RES_BITRATE) ) {
841		int br = atoi(bitrate);
842		if(args->flags & FLAG_MS_PFS)
843			br /= 8;
844		strcatf(args->str, "bitrate=\"%d\" ", br);
845	}
846	if( sampleFrequency && (args->filter & FILTER_RES_SAMPLEFREQUENCY) ) {
847		strcatf(args->str, "sampleFrequency=\"%s\" ", sampleFrequency);
848	}
849	if( nrAudioChannels && (args->filter & FILTER_RES_NRAUDIOCHANNELS) ) {
850		strcatf(args->str, "nrAudioChannels=\"%s\" ", nrAudioChannels);
851	}
852	if( resolution && (args->filter & FILTER_RES_RESOLUTION) ) {
853		strcatf(args->str, "resolution=\"%s\" ", resolution);
854	}
855	if( args->filter & (FILTER_PV_SUBTITLE_FILE_TYPE|FILTER_PV_SUBTITLE_FILE_URI) )
856	{
857		if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%s'", detailID) > 0 )
858		{
859			if( args->filter & FILTER_PV_SUBTITLE_FILE_TYPE )
860				strcatf(args->str, "pv:subtitleFileType=\"SRT\" ");
861			if( args->filter & FILTER_PV_SUBTITLE_FILE_URI )
862				strcatf(args->str, "pv:subtitleFileUri=\"http://%s:%d/Captions/%s.srt\" ",
863			                lan_addr[args->iface].str, runtime_vars.port, detailID);
864		}
865	}
866	strcatf(args->str, "protocolInfo=\"http-get:*:%s:%s\"&gt;"
867	                          "http://%s:%d/MediaItems/%s.%s"
868	                          "&lt;/res&gt;",
869	                          mime, dlna_pn, lan_addr[args->iface].str,
870	                          runtime_vars.port, detailID, ext);
871}
872
873#define COLUMNS "o.REF_ID, o.DETAIL_ID, o.CLASS," \
874                " d.SIZE, d.TITLE, d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST," \
875                " d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RESOLUTION," \
876                " d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.DISC "
877#define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, " COLUMNS
878
879static int
880callback(void *args, int argc, char **argv, char **azColName)
881{
882	struct Response *passed_args = (struct Response *)args;
883	char *id = argv[0], *parent = argv[1], *refID = argv[2], *detailID = argv[3], *class = argv[4], *size = argv[5], *title = argv[6],
884	     *duration = argv[7], *bitrate = argv[8], *sampleFrequency = argv[9], *artist = argv[10], *album = argv[11],
885	     *genre = argv[12], *comment = argv[13], *nrAudioChannels = argv[14], *track = argv[15], *date = argv[16], *resolution = argv[17],
886	     *tn = argv[18], *creator = argv[19], *dlna_pn = argv[20], *mime = argv[21], *album_art = argv[22];
887	char dlna_buf[128];
888	char ext[5];
889	struct string_s *str = passed_args->str;
890	int ret = 0;
891
892	/* Make sure we have at least 8KB left of allocated memory to finish the response. */
893	if( str->off > (str->size - 8192) )
894	{
895#if MAX_RESPONSE_SIZE > 0
896		if( (str->size+DEFAULT_RESP_SIZE) <= MAX_RESPONSE_SIZE )
897		{
898#endif
899			str->data = realloc(str->data, (str->size+DEFAULT_RESP_SIZE));
900			if( str->data )
901			{
902				str->size += DEFAULT_RESP_SIZE;
903				DPRINTF(E_DEBUG, L_HTTP, "UPnP SOAP response enlarged to %d. [%d results so far]\n",
904					str->size, passed_args->returned);
905			}
906			else
907			{
908				DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response was too big, and realloc failed!\n");
909				return -1;
910			}
911#if MAX_RESPONSE_SIZE > 0
912		}
913		else
914		{
915			DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response cut short, to not exceed the max response size [%lld]!\n", (long long int)MAX_RESPONSE_SIZE);
916			return -1;
917		}
918#endif
919	}
920	passed_args->returned++;
921
922	if( runtime_vars.root_container && strcmp(parent, runtime_vars.root_container) == 0 )
923		parent = "0";
924
925	if( strncmp(class, "item", 4) == 0 )
926	{
927//		uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B;
928		uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_TM_B;
929		char *alt_title = NULL;
930		/* We may need special handling for certain MIME types */
931		if( *mime == 'v' )
932		{
933			dlna_flags |= DLNA_FLAG_TM_S;
934			if( passed_args->flags & FLAG_MIME_AVI_DIVX )
935			{
936				if( strcmp(mime, "video/x-msvideo") == 0 )
937				{
938					if( creator )
939						strcpy(mime+6, "divx");
940					else
941						strcpy(mime+6, "avi");
942				}
943			}
944			else if( passed_args->flags & FLAG_MIME_AVI_AVI )
945			{
946				if( strcmp(mime, "video/x-msvideo") == 0 )
947				{
948					strcpy(mime+6, "avi");
949				}
950			}
951			else if( passed_args->client == EFreeBox && dlna_pn )
952			{
953				if( strncmp(dlna_pn, "AVC_TS", 6) == 0 ||
954				    strncmp(dlna_pn, "MPEG_TS", 7) == 0 )
955				{
956					strcpy(mime+6, "mp2t");
957				}
958			}
959			if( !(passed_args->flags & FLAG_DLNA) )
960			{
961				if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
962				{
963					strcpy(mime+6, "mpeg");
964				}
965			}
966			/* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
967			if( passed_args->flags & FLAG_SAMSUNG )
968			{
969				if( strcmp(mime+6, "x-matroska") == 0 )
970				{
971					strcpy(mime+8, "mkv");
972				}
973			}
974			/* LG hack: subtitles won't get used unless dc:title contains a dot. */
975			else if( passed_args->client == ELGDevice && (passed_args->filter & FILTER_RES) )
976			{
977				if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%s'", detailID) > 0 )
978				{
979					ret = asprintf(&alt_title, "%s.", title);
980					if( ret > 0 )
981						title = alt_title;
982					else
983						alt_title = NULL;
984				}
985			}
986		}
987		else if( *mime == 'a' )
988		{
989			dlna_flags |= DLNA_FLAG_TM_S;
990			if( strcmp(mime+6, "x-flac") == 0 )
991			{
992				if( passed_args->flags & FLAG_MIME_FLAC_FLAC )
993				{
994					strcpy(mime+6, "flac");
995				}
996			}
997			else if( strcmp(mime+6, "x-wav") == 0 )
998			{
999				if( passed_args->flags & FLAG_MIME_WAV_WAV )
1000				{
1001					strcpy(mime+6, "wav");
1002				}
1003			}
1004		}
1005		else
1006			dlna_flags |= DLNA_FLAG_TM_I;
1007
1008		if( dlna_pn )
1009			snprintf(dlna_buf, sizeof(dlna_buf), "DLNA.ORG_PN=%s;"
1010//			                                     "DLNA.ORG_OP=01;"
1011//			                                     "DLNA.ORG_CI=0;"
1012//			                                     "DLNA.ORG_FLAGS=%08X%024X",
1013//			                                     dlna_pn, dlna_flags, 0);
1014			                                     "DLNA.ORG_OP=01",
1015			                                     dlna_pn);
1016		else if( passed_args->flags & FLAG_DLNA )
1017//			snprintf(dlna_buf, sizeof(dlna_buf), "DLNA.ORG_OP=01;"
1018//			                                     "DLNA.ORG_CI=0;"
1019//			                                     "DLNA.ORG_FLAGS=%08X%024X",
1020//			                                     dlna_flags, 0);
1021			snprintf(dlna_buf, sizeof(dlna_buf), "DLNA.ORG_OP=01");
1022		else
1023			strcpy(dlna_buf, "*");
1024
1025		ret = strcatf(str, "&lt;item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent);
1026		if( refID && (passed_args->filter & FILTER_REFID) ) {
1027			ret = strcatf(str, " refID=\"%s\"", refID);
1028		}
1029		ret = strcatf(str, "&gt;"
1030		                   "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
1031		                   "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
1032		                   title, class);
1033		if( comment && (passed_args->filter & FILTER_DC_DESCRIPTION) ) {
1034			ret = strcatf(str, "&lt;dc:description&gt;%.384s&lt;/dc:description&gt;", comment);
1035		}
1036		if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
1037			ret = strcatf(str, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
1038		}
1039		if( date && (passed_args->filter & FILTER_DC_DATE) ) {
1040			ret = strcatf(str, "&lt;dc:date&gt;%s&lt;/dc:date&gt;", date);
1041		}
1042		if( passed_args->filter & FILTER_SEC_DCM_INFO ) {
1043			/* Get bookmark */
1044			ret = strcatf(str, "&lt;sec:dcmInfo&gt;CREATIONDATE=0,FOLDER=%s,BM=%d&lt;/sec:dcmInfo&gt;",
1045			              title, sql_get_int_field(db, "SELECT SEC from BOOKMARKS where ID = '%s'", detailID));
1046		}
1047		if( artist ) {
1048			if( (*mime == 'v') && (passed_args->filter & FILTER_UPNP_ACTOR) ) {
1049				ret = strcatf(str, "&lt;upnp:actor&gt;%s&lt;/upnp:actor&gt;", artist);
1050			}
1051			if( passed_args->filter & FILTER_UPNP_ARTIST ) {
1052				ret = strcatf(str, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
1053			}
1054		}
1055		if( album && (passed_args->filter & FILTER_UPNP_ALBUM) ) {
1056			ret = strcatf(str, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", album);
1057		}
1058		if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
1059			ret = strcatf(str, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
1060		}
1061		if( strncmp(id, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) {
1062			track = strrchr(id, '$')+1;
1063		}
1064		if( track && atoi(track) && (passed_args->filter & FILTER_UPNP_ORIGINALTRACKNUMBER) ) {
1065			ret = strcatf(str, "&lt;upnp:originalTrackNumber&gt;%s&lt;/upnp:originalTrackNumber&gt;", track);
1066		}
1067		if( album_art && atoi(album_art) )
1068		{
1069			/* Video and audio album art is handled differently */
1070			if( *mime == 'v' && (passed_args->filter & FILTER_RES) && !(passed_args->flags & FLAG_MS_PFS) ) {
1071				ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\"&gt;"
1072				                   "http://%s:%d/AlbumArt/%s-%s.jpg"
1073				                   "&lt;/res&gt;",
1074				                   lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
1075			} else if( passed_args->filter & FILTER_UPNP_ALBUMARTURI ) {
1076				ret = strcatf(str, "&lt;upnp:albumArtURI");
1077				if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
1078					ret = strcatf(str, " dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"");
1079				}
1080				ret = strcatf(str, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>;",
1081				                   lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
1082			}
1083		}
1084		if( (passed_args->flags & FLAG_MS_PFS) && *mime == 'i' ) {
1085			if( passed_args->client == EMediaRoom && !album )
1086				ret = strcatf(str, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", "[No Keywords]");
1087
1088			/* EVA2000 doesn't seem to handle embedded thumbnails */
1089			if( passed_args->client != ENetgearEVA2000 && tn && atoi(tn) ) {
1090				ret = strcatf(str, "&lt;upnp:albumArtURI&gt;"
1091				                   "http://%s:%d/Thumbnails/%s.jpg"
1092			        	           "&lt;/upnp:albumArtURI&gt;",
1093			                	   lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
1094			} else {
1095				ret = strcatf(str, "&lt;upnp:albumArtURI&gt;"
1096				                   "http://%s:%d/Resized/%s.jpg?width=160,height=160"
1097			        	           "&lt;/upnp:albumArtURI&gt;",
1098			                	   lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
1099			}
1100		}
1101		if( passed_args->filter & FILTER_RES ) {
1102			mime_to_ext(mime, ext);
1103			if( (passed_args->client == EFreeBox) && tn && atoi(tn) ) {
1104				ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:%s:%s\"&gt;"
1105				                   "http://%s:%d/Thumbnails/%s.jpg"
1106				                   "&lt;/res&gt;",
1107				                   mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[passed_args->iface].str,
1108				                   runtime_vars.port, detailID);
1109			}
1110			add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
1111			        resolution, dlna_buf, mime, detailID, ext, passed_args);
1112			if( (*mime == 'i') && (passed_args->client != EFreeBox) ) {
1113				int srcw = atoi(strsep(&resolution, "x"));
1114				int srch = atoi(resolution);
1115				if( !dlna_pn ) {
1116					add_resized_res(srcw, srch, 4096, 4096, "JPEG_LRG", detailID, passed_args);
1117				}
1118				if( !dlna_pn || !strncmp(dlna_pn, "JPEG_L", 6) || !strncmp(dlna_pn, "JPEG_M", 6) ) {
1119					add_resized_res(srcw, srch, 640, 480, "JPEG_SM", detailID, passed_args);
1120				}
1121				if( tn && atoi(tn) ) {
1122					ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:%s:%s\"&gt;"
1123					                   "http://%s:%d/Thumbnails/%s.jpg"
1124					                   "&lt;/res&gt;",
1125//					                   mime, "DLNA.ORG_PN=JPEG_TN;DLNA.ORG_CI=1", lan_addr[passed_args->iface].str,
1126					                   mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[passed_args->iface].str,
1127					                   runtime_vars.port, detailID);
1128				}
1129			}
1130			else if( *mime == 'v' ) {
1131				switch( passed_args->client ) {
1132				case EToshibaTV:
1133					if( dlna_pn &&
1134					    (strncmp(dlna_pn, "MPEG_TS_HD_NA", 13) == 0 ||
1135					     strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) == 0 ||
1136					     strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 ||
1137					     strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0))
1138					{
1139//						sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", "MPEG_PS_NTSC");
1140						sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01", "MPEG_PS_NTSC");
1141						add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
1142						        resolution, dlna_buf, mime, detailID, ext, passed_args);
1143					}
1144					break;
1145				case ESonyBDP:
1146					if( dlna_pn &&
1147					    (strncmp(dlna_pn, "AVC_TS", 6) == 0 ||
1148					     strncmp(dlna_pn, "MPEG_TS", 7) == 0) )
1149					{
1150						if( strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) != 0 )
1151						{
1152//							sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", "MPEG_TS_SD_NA");
1153							sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01", "MPEG_TS_SD_NA");
1154							add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
1155							        resolution, dlna_buf, mime, detailID, ext, passed_args);
1156						}
1157						if( strncmp(dlna_pn, "MPEG_TS_SD_EU", 13) != 0 )
1158						{
1159//							sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", "MPEG_TS_SD_EU");
1160							sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01", "MPEG_TS_SD_EU");
1161							add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
1162							        resolution, dlna_buf, mime, detailID, ext, passed_args);
1163						}
1164					}
1165					else if( (dlna_pn &&
1166					          (strncmp(dlna_pn, "AVC_MP4", 7) == 0 ||
1167					           strncmp(dlna_pn, "MPEG4_P2_MP4", 12) == 0)) ||
1168					         strcmp(mime+6, "x-matroska") == 0 ||
1169					         strcmp(mime+6, "x-msvideo") == 0 ||
1170					         strcmp(mime+6, "mpeg") == 0 )
1171					{
1172						strcpy(mime+6, "avi");
1173						if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_NTSC", 12) != 0 )
1174						{
1175//							sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", "MPEG_PS_NTSC");
1176							sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01", "MPEG_PS_NTSC");
1177							add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
1178						        	resolution, dlna_buf, mime, detailID, ext, passed_args);
1179						}
1180						if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_PAL", 11) != 0 )
1181						{
1182//							sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=0", "MPEG_PS_PAL");
1183							sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01", "MPEG_PS_PAL");
1184							add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
1185						        	resolution, dlna_buf, mime, detailID, ext, passed_args);
1186						}
1187					}
1188					break;
1189				case ESonyBravia:
1190					/* BRAVIA KDL-##*X### series TVs do natively support AVC/AC3 in TS, but
1191					   require profile to be renamed (applies to _T and _ISO variants also) */
1192					if( dlna_pn &&
1193					    (strncmp(dlna_pn, "AVC_TS_MP_SD_AC3", 16) == 0 ||
1194					     strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 ||
1195					     strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0))
1196					{
1197					        sprintf(dlna_buf, "DLNA.ORG_PN=AVC_TS_HD_50_AC3%s", dlna_pn + 16);
1198						add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
1199						        resolution, dlna_buf, mime, detailID, ext, passed_args);
1200					}
1201					break;
1202				case ELGDevice:
1203					if( alt_title )
1204					{
1205						ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:text/srt:*\"&gt;"
1206						                     "http://%s:%d/Captions/%s.srt"
1207						                   "&lt;/res&gt;",
1208						                   lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
1209						free(alt_title);
1210					}
1211					break;
1212				case ESamsungSeriesC:
1213				default:
1214					if( passed_args->filter & FILTER_SEC_CAPTION_INFO_EX )
1215					{
1216						if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%s'", detailID) > 0 )
1217						{
1218							ret = strcatf(str, "&lt;sec:CaptionInfoEx sec:type=\"srt\"&gt;"
1219							                     "http://%s:%d/Captions/%s.srt"
1220							                   "&lt;/sec:CaptionInfoEx&gt;",
1221							                   lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
1222						}
1223					}
1224					break;
1225				}
1226			}
1227		}
1228		ret = strcatf(str, "&lt;/item&gt;");
1229	}
1230	else if( strncmp(class, "container", 9) == 0 )
1231	{
1232		ret = strcatf(str, "&lt;container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent);
1233		if( passed_args->filter & FILTER_CHILDCOUNT )
1234		{
1235			int children;
1236			ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", id);
1237			children = (ret > 0) ? ret : 0;
1238			ret = strcatf(str, "childCount=\"%d\"", children);
1239		}
1240		/* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */
1241		if( (passed_args->requested == 1) && (strcmp(id, "0") == 0) )
1242		{
1243//			ret = strcatf(str, " searchable=\"1\"");
1244			if( passed_args->filter & FILTER_UPNP_SEARCHCLASS )
1245			{
1246				ret = strcatf(str, "&gt;"
1247				                   "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.audioItem&lt;/upnp:searchClass&gt;"
1248				                   "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.imageItem&lt;/upnp:searchClass&gt;"
1249				                   "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.videoItem&lt;/upnp:searchClass");
1250			}
1251		}
1252		ret = strcatf(str, "&gt;"
1253		                   "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
1254		                   "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
1255		                   title, class);
1256		if( (passed_args->filter & FILTER_UPNP_STORAGEUSED) && strcmp(class+10, "storageFolder") == 0 ) {
1257			ret = strcatf(str, "&lt;upnp:storageUsed&gt;%s&lt;/upnp:storageUsed&gt;", (size ? size : "-1"));
1258		}
1259		if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
1260			ret = strcatf(str, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
1261		}
1262		if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
1263			ret = strcatf(str, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
1264		}
1265		if( artist && (passed_args->filter & FILTER_UPNP_ARTIST) ) {
1266			ret = strcatf(str, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
1267		}
1268		if( album_art && atoi(album_art) && (passed_args->filter & FILTER_UPNP_ALBUMARTURI) ) {
1269			ret = strcatf(str, "&lt;upnp:albumArtURI ");
1270			if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
1271				ret = strcatf(str, "dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"");
1272			}
1273			ret = strcatf(str, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>;",
1274			                   lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
1275		}
1276		if( passed_args->filter & FILTER_AV_MEDIA_CLASS ) {
1277			char class;
1278			if( strncmp(id, MUSIC_ID, sizeof(MUSIC_ID)) == 0 )
1279				class = 'M';
1280			else if( strncmp(id, VIDEO_ID, sizeof(VIDEO_ID)) == 0 )
1281				class = 'V';
1282			else if( strncmp(id, IMAGE_ID, sizeof(IMAGE_ID)) == 0 )
1283				class = 'P';
1284			else
1285				class = 0;
1286			if( class )
1287				ret = strcatf(str, "&lt;av:mediaClass xmlns:av=\"urn:schemas-sony-com:av\"&gt;"
1288				                    "%c&lt;/av:mediaClass&gt;", class);
1289		}
1290		ret = strcatf(str, "&lt;/container&gt;");
1291	}
1292
1293	return 0;
1294}
1295
1296static void
1297BrowseContentDirectory(struct upnphttp * h, const char * action)
1298{
1299	static const char resp0[] =
1300			"<u:BrowseResponse "
1301			"xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1302			"<Result>"
1303			"&lt;DIDL-Lite"
1304			CONTENT_DIRECTORY_SCHEMAS;
1305	char *zErrMsg = NULL;
1306	char *sql, *ptr;
1307	struct Response args;
1308	struct string_s str;
1309	int totalMatches;
1310	int ret;
1311	char *ObjectID, *Filter, *BrowseFlag, *SortCriteria;
1312	char *orderBy = NULL;
1313	struct NameValueParserData data;
1314	int RequestedCount = 0;
1315	int StartingIndex = 0;
1316
1317	memset(&args, 0, sizeof(args));
1318	memset(&str, 0, sizeof(str));
1319
1320	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1321
1322	ObjectID = GetValueFromNameValueList(&data, "ObjectID");
1323	Filter = GetValueFromNameValueList(&data, "Filter");
1324	BrowseFlag = GetValueFromNameValueList(&data, "BrowseFlag");
1325	SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
1326
1327
1328  if(SortCriteria)
1329      if(check_sort_criteria(SortCriteria))
1330      {
1331		    SoapError(h, 500, "Invalid Args");
1332		    goto browse_error;
1333      }
1334
1335	if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
1336		RequestedCount = atoi(ptr);
1337	if( RequestedCount < 0 )
1338	{
1339		SoapError(h, 402, "Invalid Args");
1340		goto browse_error;
1341	}
1342	if( !RequestedCount )
1343		RequestedCount = -1;
1344	if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
1345		StartingIndex = atoi(ptr);
1346	if( StartingIndex < 0 )
1347	{
1348		SoapError(h, 402, "Invalid Args");
1349		goto browse_error;
1350	}
1351	if( !BrowseFlag || (strcmp(BrowseFlag, "BrowseDirectChildren") && strcmp(BrowseFlag, "BrowseMetadata")) )
1352	{
1353		SoapError(h, 402, "Invalid Args");
1354		goto browse_error;
1355	}
1356	if( !ObjectID && !(ObjectID = GetValueFromNameValueList(&data, "ContainerID")) )
1357	{
1358		SoapError(h, 402, "Invalid Args");
1359		goto browse_error;
1360	}
1361
1362	str.data = malloc(DEFAULT_RESP_SIZE);
1363	str.size = DEFAULT_RESP_SIZE;
1364	str.off = sprintf(str.data, "%s", resp0);
1365	/* See if we need to include DLNA namespace reference */
1366	args.iface = h->iface;
1367	args.filter = set_filter_flags(Filter, h);
1368	if( args.filter & FILTER_DLNA_NAMESPACE )
1369		ret = strcatf(&str, DLNA_NAMESPACE);
1370	if( args.filter & (FILTER_PV_SUBTITLE_FILE_TYPE|FILTER_PV_SUBTITLE_FILE_URI) )
1371		ret = strcatf(&str, PV_NAMESPACE);
1372	strcatf(&str, "&gt;\n");
1373
1374	args.returned = 0;
1375	args.requested = RequestedCount;
1376	args.client = h->req_client;
1377	args.flags = h->reqflags;
1378	args.str = &str;
1379	if( args.flags & FLAG_MS_PFS )
1380	{
1381		if( !strchr(ObjectID, '$') && (strcmp(ObjectID, "0") != 0) )
1382		{
1383			ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS"
1384			                             " where OBJECT_ID in "
1385			                             "('"MUSIC_ID"$%q', '"VIDEO_ID"$%q', '"IMAGE_ID"$%q')",
1386			                             ObjectID, ObjectID, ObjectID);
1387			if( ptr )
1388			{
1389				ObjectID = ptr;
1390				args.flags |= FLAG_FREE_OBJECT_ID;
1391			}
1392		}
1393	}
1394	DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n"
1395	                         " * ObjectID: %s\n"
1396	                         " * Count: %d\n"
1397	                         " * StartingIndex: %d\n"
1398	                         " * BrowseFlag: %s\n"
1399	                         " * Filter: %s\n"
1400	                         " * SortCriteria: %s\n",
1401				ObjectID, RequestedCount, StartingIndex,
1402	                        BrowseFlag, Filter, SortCriteria);
1403
1404	if( strcmp(ObjectID, "0") == 0 )
1405	{
1406		args.flags |= FLAG_ROOT_CONTAINER;
1407		if( runtime_vars.root_container )
1408		{
1409			if( (args.flags & FLAG_AUDIO_ONLY) && (strcmp(runtime_vars.root_container, BROWSEDIR_ID) == 0) )
1410				ObjectID = MUSIC_DIR_ID;
1411			else
1412				ObjectID = runtime_vars.root_container;
1413		}
1414		else
1415		{
1416			if( args.flags & FLAG_AUDIO_ONLY )
1417				ObjectID = MUSIC_ID;
1418		}
1419	}
1420
1421	if( strcmp(BrowseFlag+6, "Metadata") == 0 )
1422	{
1423		args.requested = 1;
1424		sql = sqlite3_mprintf("SELECT %s, " COLUMNS
1425		                      "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1426	        	              " where OBJECT_ID = '%q';",
1427		                      (args.flags & FLAG_ROOT_CONTAINER) ? "0, -1" : "o.OBJECT_ID, o.PARENT_ID",
1428		                      ObjectID);
1429		ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1430		totalMatches = args.returned;
1431	}
1432	else
1433	{
1434		ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%q'", ObjectID);
1435		totalMatches = (ret > 0) ? ret : 0;
1436
1437		ret = 0;
1438		if( SortCriteria )
1439		{
1440#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
1441			if( totalMatches < 10000 )
1442#endif
1443			orderBy = parse_sort_criteria(SortCriteria, &ret);
1444		}
1445		else
1446		{
1447			if( strncmp(ObjectID, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 )
1448			{
1449				if( strcmp(ObjectID, MUSIC_PLIST_ID) == 0 )
1450					ret = asprintf(&orderBy, "order by d.TITLE");
1451				else
1452					ret = asprintf(&orderBy, "order by length(OBJECT_ID), OBJECT_ID");
1453			}
1454			else if( args.client == ERokuSoundBridge )
1455			{
1456#ifdef __sparc__
1457				if( totalMatches < 10000 )
1458#endif
1459				ret = asprintf(&orderBy, "order by o.CLASS, d.DISC, d.TRACK, d.TITLE");
1460			}
1461			if( ret == -1 )
1462			{
1463				orderBy = NULL;
1464				ret = 0;
1465			}
1466		}
1467		/* If it's a DLNA client, return an error for bad sort criteria */
1468		if( ret < 0 && ((args.flags & FLAG_DLNA) || GETFLAG(DLNA_STRICT_MASK)) )
1469		{
1470			SoapError(h, 709, "Unsupported or invalid sort criteria");
1471			goto browse_error;
1472		}
1473
1474
1475		sql = sqlite3_mprintf( SELECT_COLUMNS
1476		                      "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1477				      " where PARENT_ID = '%q' %s limit %d, %d;",
1478				      ObjectID, orderBy, StartingIndex, RequestedCount);
1479		DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql);
1480		ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1481	}
1482	if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
1483	{
1484		DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1485		sqlite3_free(zErrMsg);
1486		SoapError(h, 709, "Unsupported or invalid sort criteria");
1487		goto browse_error;
1488	}
1489	sqlite3_free(sql);
1490	/* Does the object even exist? */
1491	if( !totalMatches )
1492	{
1493		ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'", ObjectID);
1494		if( ret <= 0 )
1495		{
1496			SoapError(h, 701, "No such object error");
1497			goto browse_error;
1498		}
1499	}
1500	ret = strcatf(&str, "&lt;/DIDL-Lite&gt;</Result>\n"
1501	                    "<NumberReturned>%u</NumberReturned>\n"
1502	                    "<TotalMatches>%u</TotalMatches>\n"
1503	                    "<UpdateID>%u</UpdateID>"
1504	                    "</u:BrowseResponse>",
1505	                    args.returned, totalMatches, updateID);
1506	BuildSendAndCloseSoapResp(h, str.data, str.off);
1507browse_error:
1508	ClearNameValueList(&data);
1509	if( args.flags & FLAG_FREE_OBJECT_ID )
1510		sqlite3_free(ObjectID);
1511	free(orderBy);
1512	free(str.data);
1513}
1514
1515static void
1516SearchContentDirectory(struct upnphttp * h, const char * action)
1517{
1518	static const char resp0[] =
1519			"<u:SearchResponse "
1520			"xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1521			"<Result>"
1522			"&lt;DIDL-Lite"
1523			CONTENT_DIRECTORY_SCHEMAS;
1524	char *zErrMsg = NULL;
1525	char *sql, *ptr;
1526	struct Response args;
1527	struct string_s str;
1528	int totalMatches;
1529	int ret;
1530	char *ContainerID, *Filter, *SearchCriteria, *SortCriteria;
1531	char *newSearchCriteria = NULL, *orderBy = NULL;
1532	char groupBy[] = "group by DETAIL_ID";
1533	struct NameValueParserData data;
1534	int RequestedCount = 0;
1535	int StartingIndex = 0;
1536
1537	memset(&args, 0, sizeof(args));
1538	memset(&str, 0, sizeof(str));
1539
1540	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1541
1542	ContainerID = GetValueFromNameValueList(&data, "ContainerID");
1543	Filter = GetValueFromNameValueList(&data, "Filter");
1544	SearchCriteria = GetValueFromNameValueList(&data, "SearchCriteria");
1545	SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
1546
1547	if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
1548		RequestedCount = atoi(ptr);
1549	if( !RequestedCount )
1550		RequestedCount = -1;
1551	if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
1552		StartingIndex = atoi(ptr);
1553	if( !ContainerID )
1554	{
1555		if( !(ContainerID = GetValueFromNameValueList(&data, "ObjectID")) )
1556		{
1557			SoapError(h, 402, "Invalid Args");
1558			goto search_error;
1559		}
1560	}
1561
1562	str.data = malloc(DEFAULT_RESP_SIZE);
1563	str.size = DEFAULT_RESP_SIZE;
1564	str.off = sprintf(str.data, "%s", resp0);
1565	/* See if we need to include DLNA namespace reference */
1566	args.iface = h->iface;
1567	args.filter = set_filter_flags(Filter, h);
1568	if( args.filter & FILTER_DLNA_NAMESPACE )
1569	{
1570		ret = strcatf(&str, DLNA_NAMESPACE);
1571	}
1572	strcatf(&str, "&gt;\n");
1573
1574	args.returned = 0;
1575	args.requested = RequestedCount;
1576	args.client = h->req_client;
1577	args.flags = h->reqflags;
1578	args.str = &str;
1579	if( args.flags & FLAG_MS_PFS )
1580	{
1581		if( !strchr(ContainerID, '$') && (strcmp(ContainerID, "0") != 0) )
1582		{
1583			ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS"
1584			                             " where OBJECT_ID in "
1585			                             "('"MUSIC_ID"$%q', '"VIDEO_ID"$%q', '"IMAGE_ID"$%q')",
1586			                             ContainerID, ContainerID, ContainerID);
1587			if( ptr )
1588			{
1589				ContainerID = ptr;
1590				args.flags |= FLAG_FREE_OBJECT_ID;
1591			}
1592		}
1593	}
1594	DPRINTF(E_DEBUG, L_HTTP, "Searching ContentDirectory:\n"
1595	                         " * ObjectID: %s\n"
1596	                         " * Count: %d\n"
1597	                         " * StartingIndex: %d\n"
1598	                         " * SearchCriteria: %s\n"
1599	                         " * Filter: %s\n"
1600	                         " * SortCriteria: %s\n",
1601				ContainerID, RequestedCount, StartingIndex,
1602	                        SearchCriteria, Filter, SortCriteria);
1603
1604	if( strcmp(ContainerID, "0") == 0 )
1605		*ContainerID = '*';
1606	else if( strcmp(ContainerID, MUSIC_ALL_ID) == 0 )
1607		groupBy[0] = '\0';
1608	if( !SearchCriteria )
1609	{
1610		newSearchCriteria = strdup("1 = 1");
1611		SearchCriteria = newSearchCriteria;
1612	}
1613	else
1614	{
1615		SearchCriteria = modifyString(SearchCriteria, "&quot;", "\"", 0);
1616		SearchCriteria = modifyString(SearchCriteria, "&apos;", "'", 0);
1617		SearchCriteria = modifyString(SearchCriteria, "&lt;", "<", 0);
1618		SearchCriteria = modifyString(SearchCriteria, "&gt;", ">", 0);
1619		SearchCriteria = modifyString(SearchCriteria, "object.", "", 0);
1620		SearchCriteria = modifyString(SearchCriteria, "derivedfrom", "like", 1);
1621		SearchCriteria = modifyString(SearchCriteria, "contains", "like", 2);
1622		SearchCriteria = modifyString(SearchCriteria, "dc:date", "d.DATE", 0);
1623		SearchCriteria = modifyString(SearchCriteria, "dc:title", "d.TITLE", 0);
1624		SearchCriteria = modifyString(SearchCriteria, "dc:creator", "d.CREATOR", 0);
1625		SearchCriteria = modifyString(SearchCriteria, "upnp:class", "o.CLASS", 0);
1626		SearchCriteria = modifyString(SearchCriteria, "upnp:actor", "d.ARTIST", 0);
1627		SearchCriteria = modifyString(SearchCriteria, "upnp:artist", "d.ARTIST", 0);
1628		SearchCriteria = modifyString(SearchCriteria, "upnp:album", "d.ALBUM", 0);
1629		SearchCriteria = modifyString(SearchCriteria, "upnp:genre", "d.GENRE", 0);
1630		SearchCriteria = modifyString(SearchCriteria, "exists true", "is not NULL", 0);
1631		SearchCriteria = modifyString(SearchCriteria, "exists false", "is NULL", 0);
1632		SearchCriteria = modifyString(SearchCriteria, "@refID", "REF_ID", 0);
1633		if( strstr(SearchCriteria, "@id") )
1634		{
1635			newSearchCriteria = strdup(SearchCriteria);
1636			SearchCriteria = newSearchCriteria = modifyString(newSearchCriteria, "@id", "OBJECT_ID", 0);
1637		}
1638		if( strstr(SearchCriteria, "res is ") )
1639		{
1640			if( !newSearchCriteria )
1641				newSearchCriteria = strdup(SearchCriteria);
1642			SearchCriteria = newSearchCriteria = modifyString(newSearchCriteria, "res is ", "MIME is ", 0);
1643		}
1644		if( strstr(SearchCriteria, "\\\"") )
1645		{
1646			if( !newSearchCriteria )
1647				newSearchCriteria = strdup(SearchCriteria);
1648			SearchCriteria = newSearchCriteria = modifyString(newSearchCriteria, "\\\"", "&amp;quot;", 0);
1649		}
1650	}
1651	DPRINTF(E_DEBUG, L_HTTP, "Translated SearchCriteria: %s\n", SearchCriteria);
1652
1653	totalMatches = sql_get_int_field(db, "SELECT (select count(distinct DETAIL_ID)"
1654	                                     " from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1655	                                     " where (OBJECT_ID glob '%q$*') and (%s))"
1656	                                     " + "
1657	                                     "(select count(*) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1658	                                     " where (OBJECT_ID = '%q') and (%s))",
1659	                                     ContainerID, SearchCriteria, ContainerID, SearchCriteria);
1660
1661
1662	if( totalMatches < 0 )
1663	{
1664		/* Must be invalid SQL, so most likely bad or unhandled search criteria. */
1665		SoapError(h, 708, "Unsupported or invalid search criteria");
1666		goto search_error;
1667	}
1668	/* Does the object even exist? */
1669	if( !totalMatches )
1670	{
1671		ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'",
1672		                        !strcmp(ContainerID, "*")?"0":ContainerID);
1673		if( ret <= 0 )
1674		{
1675			SoapError(h, 710, "No such container");
1676			goto search_error;
1677		}
1678	}
1679#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
1680	ret = 0;
1681	if( totalMatches < 10000 )
1682#endif
1683		orderBy = parse_sort_criteria(SortCriteria, &ret);
1684	/* If it's a DLNA client, return an error for bad sort criteria */
1685	if( ret < 0 && ((args.flags & FLAG_DLNA) || GETFLAG(DLNA_STRICT_MASK)) )
1686	{
1687		SoapError(h, 709, "Unsupported or invalid sort criteria");
1688		goto search_error;
1689	}
1690
1691	sql = sqlite3_mprintf( SELECT_COLUMNS
1692	                      "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1693	                      " where OBJECT_ID glob '%q$*' and (%s) %s "
1694	                      "%z %s"
1695	                      " limit %d, %d",
1696	                      ContainerID, SearchCriteria, groupBy,
1697	                      (*ContainerID == '*') ? NULL :
1698                              sqlite3_mprintf("UNION ALL " SELECT_COLUMNS
1699	                                      "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1700	                                      " where OBJECT_ID = '%q' and (%s) ", ContainerID, SearchCriteria),
1701	                      orderBy, StartingIndex, RequestedCount);
1702
1703
1704	DPRINTF(E_DEBUG, L_HTTP, "Search SQL: %s\n", sql);
1705	ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1706	if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
1707	{
1708		DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1709		sqlite3_free(zErrMsg);
1710	}
1711	sqlite3_free(sql);
1712	ret = strcatf(&str, "&lt;/DIDL-Lite&gt;</Result>\n"
1713	                    "<NumberReturned>%u</NumberReturned>\n"
1714	                    "<TotalMatches>%u</TotalMatches>\n"
1715	                    "<UpdateID>%u</UpdateID>"
1716	                    "</u:SearchResponse>",
1717	                    args.returned, totalMatches, updateID);
1718	BuildSendAndCloseSoapResp(h, str.data, str.off);
1719search_error:
1720	ClearNameValueList(&data);
1721	if( args.flags & FLAG_FREE_OBJECT_ID )
1722		sqlite3_free(ContainerID);
1723	free(orderBy);
1724	free(newSearchCriteria);
1725	free(str.data);
1726}
1727
1728/*
1729If a control point calls QueryStateVariable on a state variable that is not
1730buffered in memory within (or otherwise available from) the service,
1731the service must return a SOAP fault with an errorCode of 404 Invalid Var.
1732
1733QueryStateVariable remains useful as a limited test tool but may not be
1734part of some future versions of UPnP.
1735*/
1736static void
1737QueryStateVariable(struct upnphttp * h, const char * action)
1738{
1739	static const char resp[] =
1740        "<u:%sResponse "
1741        "xmlns:u=\"%s\">"
1742		"<return>%s</return>"
1743        "</u:%sResponse>";
1744
1745	char body[512];
1746	int bodylen;
1747	struct NameValueParserData data;
1748	const char * var_name;
1749
1750	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1751	/*var_name = GetValueFromNameValueList(&data, "QueryStateVariable"); */
1752	/*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/
1753	var_name = GetValueFromNameValueList(&data, "varName");
1754
1755	DPRINTF(E_INFO, L_HTTP, "QueryStateVariable(%.40s)\n", var_name);
1756
1757	if(!var_name)
1758	{
1759		SoapError(h, 402, "Invalid Args");
1760	}
1761	else if(strcmp(var_name, "ConnectionStatus") == 0)
1762	{
1763		bodylen = snprintf(body, sizeof(body), resp,
1764                           action, "urn:schemas-upnp-org:control-1-0",
1765		                   "Connected", action);
1766		BuildSendAndCloseSoapResp(h, body, bodylen);
1767	}
1768	else
1769	{
1770		DPRINTF(E_WARN, L_HTTP, "%s: Unknown: %s\n", action, var_name?var_name:"");
1771		SoapError(h, 404, "Invalid Var");
1772	}
1773
1774	ClearNameValueList(&data);
1775}
1776
1777static void
1778SamsungGetFeatureList(struct upnphttp * h, const char * action)
1779{
1780	static const char resp[] =
1781		"<u:X_GetFeatureListResponse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1782		"<FeatureList>"
1783		"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
1784		"&lt;Features xmlns=\"urn:schemas-upnp-org:av:avs\""
1785		" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
1786		" xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\"&gt;"
1787		"&lt;Feature name=\"samsung.com_BASICVIEW\" version=\"1\"&gt;"
1788		 "&lt;container id=\"1\" type=\"object.item.audioItem\"/&gt;"
1789		 "&lt;container id=\"2\" type=\"object.item.videoItem\"/&gt;"
1790		 "&lt;container id=\"3\" type=\"object.item.imageItem\"/&gt;"
1791		"&lt;/Feature&gt;"
1792		"</FeatureList></u:X_GetFeatureListResponse>";
1793
1794	BuildSendAndCloseSoapResp(h, resp, sizeof(resp)-1);
1795}
1796
1797static void
1798SamsungSetBookmark(struct upnphttp * h, const char * action)
1799{
1800	static const char resp[] =
1801	    "<u:X_SetBookmarkResponse"
1802	    " xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1803	    "</u:X_SetBookmarkResponse>";
1804
1805	struct NameValueParserData data;
1806	char *ObjectID, *PosSecond;
1807	int ret;
1808
1809	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1810	ObjectID = GetValueFromNameValueList(&data, "ObjectID");
1811	PosSecond = GetValueFromNameValueList(&data, "PosSecond");
1812	if( ObjectID && PosSecond )
1813	{
1814		ret = sql_exec(db, "INSERT OR REPLACE into BOOKMARKS"
1815		                   " VALUES "
1816		                   "((select DETAIL_ID from OBJECTS where OBJECT_ID = '%q'), %q)", ObjectID, PosSecond);
1817		if( ret != SQLITE_OK )
1818			DPRINTF(E_WARN, L_METADATA, "Error setting bookmark %s on ObjectID='%s'\n", PosSecond, ObjectID);
1819		BuildSendAndCloseSoapResp(h, resp, sizeof(resp)-1);
1820	}
1821	else
1822		SoapError(h, 402, "Invalid Args");
1823
1824	ClearNameValueList(&data);
1825}
1826
1827static const struct
1828{
1829	const char * methodName;
1830	void (*methodImpl)(struct upnphttp *, const char *);
1831}
1832soapMethods[] =
1833{
1834	{ "QueryStateVariable", QueryStateVariable},
1835	{ "Browse", BrowseContentDirectory},
1836	{ "Search", SearchContentDirectory},
1837	{ "GetSearchCapabilities", GetSearchCapabilities},
1838	{ "GetSortCapabilities", GetSortCapabilities},
1839	{ "GetSystemUpdateID", GetSystemUpdateID},
1840	{ "GetProtocolInfo", GetProtocolInfo},
1841	{ "GetCurrentConnectionIDs", GetCurrentConnectionIDs},
1842	{ "GetCurrentConnectionInfo", GetCurrentConnectionInfo},
1843	{ "IsAuthorized", IsAuthorizedValidated},
1844	{ "IsValidated", IsAuthorizedValidated},
1845	{ "X_GetFeatureList", SamsungGetFeatureList},
1846	{ "X_SetBookmark", SamsungSetBookmark},
1847	{ 0, 0 }
1848};
1849
1850void
1851ExecuteSoapAction(struct upnphttp * h, const char * action, int n)
1852{
1853	char * p;
1854
1855	p = strchr(action, '#');
1856	if(p)
1857	{
1858		int i = 0;
1859		int len;
1860		int methodlen;
1861		char * p2;
1862		p++;
1863		p2 = strchr(p, '"');
1864		if(p2)
1865			methodlen = p2 - p;
1866		else
1867			methodlen = n - (p - action);
1868		DPRINTF(E_DEBUG, L_HTTP, "SoapMethod: %.*s\n", methodlen, p);
1869		while(soapMethods[i].methodName)
1870		{
1871			len = strlen(soapMethods[i].methodName);
1872			if(strncmp(p, soapMethods[i].methodName, len) == 0)
1873			{
1874				soapMethods[i].methodImpl(h, soapMethods[i].methodName);
1875				return;
1876			}
1877			i++;
1878		}
1879
1880		DPRINTF(E_WARN, L_HTTP, "SoapMethod: Unknown: %.*s\n", methodlen, p);
1881	}
1882
1883	SoapError(h, 401, "Invalid Action");
1884}
1885
1886/* Standard Errors:
1887 *
1888 * errorCode errorDescription Description
1889 * --------	---------------- -----------
1890 * 401 		Invalid Action 	No action by that name at this service.
1891 * 402 		Invalid Args 	Could be any of the following: not enough in args,
1892 * 							too many in args, no in arg by that name,
1893 * 							one or more in args are of the wrong data type.
1894 * 403 		Out of Sync 	Out of synchronization.
1895 * 501 		Action Failed 	May be returned in current state of service
1896 * 							prevents invoking that action.
1897 * 600-699 	TBD 			Common action errors. Defined by UPnP Forum
1898 * 							Technical Committee.
1899 * 700-799 	TBD 			Action-specific errors for standard actions.
1900 * 							Defined by UPnP Forum working committee.
1901 * 800-899 	TBD 			Action-specific errors for non-standard actions.
1902 * 							Defined by UPnP vendor.
1903*/
1904void
1905SoapError(struct upnphttp * h, int errCode, const char * errDesc)
1906{
1907	static const char resp[] =
1908		"<s:Envelope "
1909		"xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
1910		"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1911		"<s:Body>"
1912		"<s:Fault>"
1913		"<faultcode>s:Client</faultcode>"
1914		"<faultstring>UPnPError</faultstring>"
1915		"<detail>"
1916		"<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">"
1917		"<errorCode>%d</errorCode>"
1918		"<errorDescription>%s</errorDescription>"
1919		"</UPnPError>"
1920		"</detail>"
1921		"</s:Fault>"
1922		"</s:Body>"
1923		"</s:Envelope>";
1924
1925	char body[2048];
1926	int bodylen;
1927
1928	DPRINTF(E_WARN, L_HTTP, "Returning UPnPError %d: %s\n", errCode, errDesc);
1929	bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc);
1930	BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen);
1931	SendResp_upnphttp(h);
1932	CloseSocket_upnphttp(h);
1933}
1934
1935