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