1/* MiniDLNA project
2 * http://minidlna.sourceforge.net/
3 * (c) 2008-2009 Justin Maggard
4 *
5 * This software is subject to the conditions detailed
6 * in the LICENCE file provided within the distribution
7 *
8 * Portions of the code from the MiniUPnP Project
9 * (c) Thomas Bernard licensed under BSD revised license
10 * detailed in the LICENSE.miniupnpd file provided within
11 * the distribution.
12 */
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <sys/socket.h>
17#include <unistd.h>
18#include <dirent.h>
19#include <sys/stat.h>
20#include <sys/types.h>
21#include <arpa/inet.h>
22#include <netinet/in.h>
23#include <netdb.h>
24#include <ctype.h>
25
26#include "config.h"
27#include "upnpglobalvars.h"
28#include "upnphttp.h"
29#include "upnpsoap.h"
30#include "upnpreplyparse.h"
31#include "getifaddr.h"
32
33#include "scanner.h"
34#include "utils.h"
35#include "sql.h"
36#include "log.h"
37
38static void
39BuildSendAndCloseSoapResp(struct upnphttp * h,
40                          const char * body, int bodylen)
41{
42	static const char beforebody[] =
43		"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
44		"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
45		"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
46		"<s:Body>";
47
48	static const char afterbody[] =
49		"</s:Body>"
50		"</s:Envelope>\r\n";
51
52	BuildHeader_upnphttp(h, 200, "OK",  sizeof(beforebody) - 1
53		+ sizeof(afterbody) - 1 + bodylen );
54
55	memcpy(h->res_buf + h->res_buflen, beforebody, sizeof(beforebody) - 1);
56	h->res_buflen += sizeof(beforebody) - 1;
57
58	memcpy(h->res_buf + h->res_buflen, body, bodylen);
59	h->res_buflen += bodylen;
60
61	memcpy(h->res_buf + h->res_buflen, afterbody, sizeof(afterbody) - 1);
62	h->res_buflen += sizeof(afterbody) - 1;
63
64	SendResp_upnphttp(h);
65	CloseSocket_upnphttp(h);
66}
67
68static void
69GetSystemUpdateID(struct upnphttp * h, const char * action)
70{
71	static const char resp[] =
72		"<u:%sResponse "
73		"xmlns:u=\"%s\">"
74		"<Id>%d</Id>"
75		"</u:%sResponse>";
76
77	char body[512];
78	int bodylen;
79
80	bodylen = snprintf(body, sizeof(body), resp,
81		action, "urn:schemas-upnp-org:service:ContentDirectory:1",
82		updateID, action);
83	BuildSendAndCloseSoapResp(h, body, bodylen);
84}
85
86static void
87IsAuthorizedValidated(struct upnphttp * h, const char * action)
88{
89	static const char resp[] =
90		"<u:%sResponse "
91		"xmlns:u=\"%s\">"
92		"<Result>%d</Result>"
93		"</u:%sResponse>";
94
95	char body[512];
96	int bodylen;
97
98	bodylen = snprintf(body, sizeof(body), resp,
99		action, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
100		1, action);
101	BuildSendAndCloseSoapResp(h, body, bodylen);
102}
103
104static void
105GetProtocolInfo(struct upnphttp * h, const char * action)
106{
107	static const char resp[] =
108		"<u:%sResponse "
109		"xmlns:u=\"%s\">"
110		"<Source>"
111		RESOURCE_PROTOCOL_INFO_VALUES
112		"</Source>"
113		"<Sink></Sink>"
114		"</u:%sResponse>";
115
116	char * body;
117	int bodylen;
118
119	bodylen = asprintf(&body, resp,
120		action, "urn:schemas-upnp-org:service:ConnectionManager:1",
121		action);
122	BuildSendAndCloseSoapResp(h, body, bodylen);
123	free(body);
124}
125
126static void
127GetSortCapabilities(struct upnphttp * h, const char * action)
128{
129	static const char resp[] =
130		"<u:%sResponse "
131		"xmlns:u=\"%s\">"
132		"<SortCaps>"
133                  "dc:title,"
134                  "dc:date,"
135		  "upnp:class,"
136                  "upnp:originalTrackNumber"
137		"</SortCaps>"
138		"</u:%sResponse>";
139
140	char body[512];
141	int bodylen;
142
143	bodylen = snprintf(body, sizeof(body), resp,
144		action, "urn:schemas-upnp-org:service:ContentDirectory:1",
145		action);
146	BuildSendAndCloseSoapResp(h, body, bodylen);
147}
148
149static void
150GetSearchCapabilities(struct upnphttp * h, const char * action)
151{
152	static const char resp[] =
153		"<u:%sResponse "
154		"xmlns:u=\"%s\">"
155		"<SearchCaps>dc:title,dc:creator,upnp:class,upnp:artist,upnp:album,@refID</SearchCaps>"
156		"</u:%sResponse>";
157
158	char body[512];
159	int bodylen;
160
161	bodylen = snprintf(body, sizeof(body), resp,
162		action, "urn:schemas-upnp-org:service:ContentDirectory:1",
163		action);
164	BuildSendAndCloseSoapResp(h, body, bodylen);
165}
166
167static void
168GetCurrentConnectionIDs(struct upnphttp * h, const char * action)
169{
170	/* TODO: Use real data. - JM */
171	static const char resp[] =
172		"<u:%sResponse "
173		"xmlns:u=\"%s\">"
174		"<ConnectionIDs>0</ConnectionIDs>"
175		"</u:%sResponse>";
176
177	char body[512];
178	int bodylen;
179
180	bodylen = snprintf(body, sizeof(body), resp,
181		action, "urn:schemas-upnp-org:service:ConnectionManager:1",
182		action);
183	BuildSendAndCloseSoapResp(h, body, bodylen);
184}
185
186static void
187GetCurrentConnectionInfo(struct upnphttp * h, const char * action)
188{
189	/* TODO: Use real data. - JM */
190	static const char resp[] =
191		"<u:%sResponse "
192		"xmlns:u=\"%s\">"
193		"<RcsID>-1</RcsID>"
194		"<AVTransportID>-1</AVTransportID>"
195		"<ProtocolInfo></ProtocolInfo>"
196		"<PeerConnectionManager></PeerConnectionManager>"
197		"<PeerConnectionID>-1</PeerConnectionID>"
198		"<Direction>Output</Direction>"
199		"<Status>Unknown</Status>"
200		"</u:%sResponse>";
201
202	char body[sizeof(resp)+128];
203	int bodylen;
204
205	bodylen = snprintf(body, sizeof(body), resp,
206		action, "urn:schemas-upnp-org:service:ConnectionManager:1",
207		action);
208	BuildSendAndCloseSoapResp(h, body, bodylen);
209}
210
211static void
212mime_to_ext(const char * mime, char * buf)
213{
214	switch( *mime )
215	{
216		/* Audio extensions */
217		case 'a':
218			if( strcmp(mime+6, "mpeg") == 0 )
219				strcpy(buf, "mp3");
220			else if( strcmp(mime+6, "mp4") == 0 )
221				strcpy(buf, "m4a");
222			else if( strcmp(mime+6, "x-ms-wma") == 0 )
223				strcpy(buf, "wma");
224			else if( strcmp(mime+6, "x-flac") == 0 )
225				strcpy(buf, "flac");
226			else if( strcmp(mime+6, "flac") == 0 )
227				strcpy(buf, "flac");
228			else if( strcmp(mime+6, "x-wav") == 0 )
229				strcpy(buf, "wav");
230			else if( strncmp(mime+6, "L16", 3) == 0 )
231				strcpy(buf, "pcm");
232			else
233				strcpy(buf, "dat");
234			break;
235		case 'v':
236			if( strcmp(mime+6, "avi") == 0 )
237				strcpy(buf, "avi");
238			else if( strcmp(mime+6, "divx") == 0 )
239				strcpy(buf, "avi");
240			else if( strcmp(mime+6, "x-msvideo") == 0 )
241				strcpy(buf, "avi");
242			else if( strcmp(mime+6, "mpeg") == 0 )
243				strcpy(buf, "mpg");
244			else if( strcmp(mime+6, "mp4") == 0 )
245				strcpy(buf, "mp4");
246			else if( strcmp(mime+6, "x-ms-wmv") == 0 )
247				strcpy(buf, "wmv");
248			else if( strcmp(mime+6, "x-matroska") == 0 )
249				strcpy(buf, "mkv");
250			else if( strcmp(mime+6, "x-mkv") == 0 )
251				strcpy(buf, "mkv");
252			else if( strcmp(mime+6, "x-flv") == 0 )
253				strcpy(buf, "flv");
254			else if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
255				strcpy(buf, "mpg");
256			else if( strcmp(mime+6, "quicktime") == 0 )
257				strcpy(buf, "mov");
258			else if( strcmp(mime+6, "3gpp") == 0 )
259				strcpy(buf, "3gp");
260			else if( strcmp(mime+6, "x-tivo-mpeg") == 0 )
261				strcpy(buf, "TiVo");
262			else
263				strcpy(buf, "dat");
264			break;
265		case 'i':
266			if( strcmp(mime+6, "jpeg") == 0 )
267				strcpy(buf, "jpg");
268			else if( strcmp(mime+6, "png") == 0 )
269				strcpy(buf, "png");
270			else
271				strcpy(buf, "dat");
272			break;
273		default:
274			strcpy(buf, "dat");
275			break;
276	}
277}
278
279#define FILTER_CHILDCOUNT                        0x00000001
280#define FILTER_DC_CREATOR                        0x00000002
281#define FILTER_DC_DATE                           0x00000004
282#define FILTER_DC_DESCRIPTION                    0x00000008
283#define FILTER_DLNA_NAMESPACE                    0x00000010
284#define FILTER_REFID                             0x00000020
285#define FILTER_RES                               0x00000040
286#define FILTER_RES_BITRATE                       0x00000080
287#define FILTER_RES_DURATION                      0x00000100
288#define FILTER_RES_NRAUDIOCHANNELS               0x00000200
289#define FILTER_RES_RESOLUTION                    0x00000400
290#define FILTER_RES_SAMPLEFREQUENCY               0x00000800
291#define FILTER_RES_SIZE                          0x00001000
292#define FILTER_UPNP_ALBUM                        0x00002000
293#define FILTER_UPNP_ALBUMARTURI                  0x00004000
294#define FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID   0x00008000
295#define FILTER_UPNP_ARTIST                       0x00010000
296#define FILTER_UPNP_GENRE                        0x00020000
297#define FILTER_UPNP_ORIGINALTRACKNUMBER          0x00040000
298#define FILTER_UPNP_SEARCHCLASS                  0x00080000
299
300static u_int32_t
301set_filter_flags(char * filter)
302{
303	char *item, *saveptr = NULL;
304	u_int32_t flags = 0;
305
306	if( !filter || (strlen(filter) <= 1) )
307		return 0xFFFFFFFF;
308	item = strtok_r(filter, ",", &saveptr);
309	while( item != NULL )
310	{
311		if( saveptr )
312			*(item-1) = ',';
313		while( isspace(*item) )
314			item++;
315		if( strcmp(item, "@childCount") == 0 )
316		{
317			flags |= FILTER_CHILDCOUNT;
318		}
319		else if( strcmp(item, "dc:creator") == 0 )
320		{
321			flags |= FILTER_DC_CREATOR;
322		}
323		else if( strcmp(item, "dc:date") == 0 )
324		{
325			flags |= FILTER_DC_DATE;
326		}
327		else if( strcmp(item, "dc:description") == 0 )
328		{
329			flags |= FILTER_DC_DESCRIPTION;
330		}
331		else if( strcmp(item, "dlna") == 0 )
332		{
333			flags |= FILTER_DLNA_NAMESPACE;
334		}
335		else if( strcmp(item, "@refID") == 0 )
336		{
337			flags |= FILTER_REFID;
338		}
339		else if( strcmp(item, "upnp:album") == 0 )
340		{
341			flags |= FILTER_UPNP_ALBUM;
342		}
343		else if( strcmp(item, "upnp:albumArtURI") == 0 )
344		{
345			flags |= FILTER_UPNP_ALBUMARTURI;
346		}
347		else if( strcmp(item, "upnp:albumArtURI@dlna:profileID") == 0 )
348		{
349			flags |= FILTER_UPNP_ALBUMARTURI;
350			flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID;
351		}
352		else if( strcmp(item, "upnp:artist") == 0 )
353		{
354			flags |= FILTER_UPNP_ARTIST;
355		}
356		else if( strcmp(item, "upnp:genre") == 0 )
357		{
358			flags |= FILTER_UPNP_GENRE;
359		}
360		else if( strcmp(item, "upnp:originalTrackNumber") == 0 )
361		{
362			flags |= FILTER_UPNP_ORIGINALTRACKNUMBER;
363		}
364		else if( strcmp(item, "upnp:searchClass") == 0 )
365		{
366			flags |= FILTER_UPNP_SEARCHCLASS;
367		}
368		else if( strcmp(item, "res") == 0 )
369		{
370			flags |= FILTER_RES;
371		}
372		else if( (strcmp(item, "res@bitrate") == 0) ||
373		         (strcmp(item, "@bitrate") == 0) ||
374		         ((strcmp(item, "bitrate") == 0) && (flags & FILTER_RES)) )
375		{
376			flags |= FILTER_RES;
377			flags |= FILTER_RES_BITRATE;
378		}
379		else if( (strcmp(item, "res@duration") == 0) ||
380		         (strcmp(item, "@duration") == 0) ||
381		         ((strcmp(item, "duration") == 0) && (flags & FILTER_RES)) )
382		{
383			flags |= FILTER_RES;
384			flags |= FILTER_RES_DURATION;
385		}
386		else if( (strcmp(item, "res@nrAudioChannels") == 0) ||
387		         (strcmp(item, "@nrAudioChannels") == 0) ||
388		         ((strcmp(item, "nrAudioChannels") == 0) && (flags & FILTER_RES)) )
389		{
390			flags |= FILTER_RES;
391			flags |= FILTER_RES_NRAUDIOCHANNELS;
392		}
393		else if( (strcmp(item, "res@resolution") == 0) ||
394		         (strcmp(item, "@resolution") == 0) ||
395		         ((strcmp(item, "resolution") == 0) && (flags & FILTER_RES)) )
396		{
397			flags |= FILTER_RES;
398			flags |= FILTER_RES_RESOLUTION;
399		}
400		else if( (strcmp(item, "res@sampleFrequency") == 0) ||
401		         (strcmp(item, "@sampleFrequency") == 0) ||
402		         ((strcmp(item, "sampleFrequency") == 0) && (flags & FILTER_RES)) )
403		{
404			flags |= FILTER_RES;
405			flags |= FILTER_RES_SAMPLEFREQUENCY;
406		}
407		else if( (strcmp(item, "res@size") == 0) ||
408		         (strcmp(item, "@size") == 0) ||
409		         (strcmp(item, "size") == 0) )
410		{
411			flags |= FILTER_RES;
412			flags |= FILTER_RES_SIZE;
413		}
414		item = strtok_r(NULL, ",", &saveptr);
415	}
416
417	return flags;
418}
419
420char *
421parse_sort_criteria(char * sortCriteria, int * error)
422{
423	char *order = NULL;
424	char *item, *saveptr;
425	int i, ret, reverse, title_sorted = 0;
426	*error = 0;
427
428	if( !sortCriteria )
429		return NULL;
430
431	if( (item = strtok_r(sortCriteria, ",", &saveptr)) )
432	{
433		order = malloc(4096);
434		strcpy(order, "order by ");
435	}
436	for( i=0; item != NULL; i++ )
437	{
438		reverse=0;
439		if( i )
440			strcat(order, ", ");
441		if( *item == '+' )
442		{
443			item++;
444		}
445		else if( *item == '-' )
446		{
447			reverse = 1;
448			item++;
449		}
450		if( strcasecmp(item, "upnp:class") == 0 )
451		{
452			strcat(order, "o.CLASS");
453		}
454		else if( strcasecmp(item, "dc:title") == 0 )
455		{
456			strcat(order, "d.TITLE");
457			title_sorted = 1;
458		}
459		else if( strcasecmp(item, "dc:date") == 0 )
460		{
461			strcat(order, "d.DATE");
462		}
463		else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 )
464		{
465			strcat(order, "d.DISC, d.TRACK");
466		}
467		else
468		{
469			printf("Unhandled SortCriteria [%s]\n", item);
470			*error = 1;
471			if( i )
472			{
473				ret = strlen(order);
474				order[ret-2] = '\0';
475			}
476			i--;
477			goto unhandled_order;
478		}
479
480		if( reverse )
481			strcat(order, " DESC");
482		unhandled_order:
483		item = strtok_r(NULL, ",", &saveptr);
484	}
485	if( i <= 0 )
486	{
487		free(order);
488		return NULL;
489	}
490	/* Add a "tiebreaker" sort order */
491	if( !title_sorted )
492		strcat(order, ", TITLE ASC");
493
494	return order;
495}
496
497static void add_resized_res(int srcw, int srch, int reqw, int reqh, char *dlna_pn, char *detailID, struct Response *passed_args)
498{
499	int ret;
500	int dstw = reqw;
501	int dsth = reqh;
502	char str_buf[256];
503
504
505	if( passed_args->flags & FLAG_NO_RESIZE )
506	{
507		return;
508	}
509
510	ret = sprintf(str_buf, "&lt;res ");
511	memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
512	passed_args->size += ret;
513	if( passed_args->filter & FILTER_RES_RESOLUTION )
514	{
515		dstw = reqw;
516		dsth = ((((reqw<<10)/srcw)*srch)>>10);
517		if( dsth > reqh ) {
518			dsth = reqh;
519			dstw = (((reqh<<10)/srch) * srcw>>10);
520		}
521		ret = sprintf(str_buf, "resolution=\"%dx%d\" ", dstw, dsth);
522		memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
523		passed_args->size += ret;
524	}
525	ret = sprintf(str_buf, "protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=%s;DLNA.ORG_CI=1\"&gt;"
526	                       "http://%s:%d/Resized/%s.jpg?width=%d,height=%d"
527	                       "&lt;/res&gt;",
528	                       dlna_pn, lan_addr[0].str, runtime_vars.port,
529	                       detailID, dstw, dsth);
530	memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
531	passed_args->size += ret;
532}
533
534#define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, o.REF_ID, o.DETAIL_ID, o.CLASS," \
535                       " d.SIZE, d.TITLE, d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST," \
536                       " d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RESOLUTION," \
537                       " d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.DISC "
538
539static int
540callback(void *args, int argc, char **argv, char **azColName)
541{
542	struct Response *passed_args = (struct Response *)args;
543	char *id = argv[0], *parent = argv[1], *refID = argv[2], *detailID = argv[3], *class = argv[4], *size = argv[5], *title = argv[6],
544	     *duration = argv[7], *bitrate = argv[8], *sampleFrequency = argv[9], *artist = argv[10], *album = argv[11],
545	     *genre = argv[12], *comment = argv[13], *nrAudioChannels = argv[14], *track = argv[15], *date = argv[16], *resolution = argv[17],
546	     *tn = argv[18], *creator = argv[19], *dlna_pn = argv[20], *mime = argv[21], *album_art = argv[22];
547	char dlna_buf[96];
548	char ext[5];
549	char str_buf[512];
550	int children, ret = 0;
551
552	/* Make sure we have at least 4KB left of allocated memory to finish the response. */
553	if( passed_args->size > (passed_args->alloced - 4096) )
554	{
555#if MAX_RESPONSE_SIZE > 0
556		if( (passed_args->alloced+1048576) <= MAX_RESPONSE_SIZE )
557		{
558#endif
559			passed_args->resp = realloc(passed_args->resp, (passed_args->alloced+1048576));
560			if( passed_args->resp )
561			{
562				passed_args->alloced += 1048576;
563				DPRINTF(E_DEBUG, L_HTTP, "HUGE RESPONSE ALERT: UPnP SOAP response had to be enlarged to %d. [%d results so far]\n", passed_args->alloced, passed_args->returned);
564			}
565			else
566			{
567				DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response was too big, and realloc failed!\n");
568				return -1;
569			}
570#if MAX_RESPONSE_SIZE > 0
571		}
572		else
573		{
574			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);
575			return -1;
576		}
577#endif
578	}
579	passed_args->returned++;
580
581	if( dlna_pn )
582		sprintf(dlna_buf, "DLNA.ORG_PN=%s", dlna_pn);
583	else if( passed_args->flags & FLAG_DLNA )
584		strcpy(dlna_buf, dlna_no_conv);
585	else
586		strcpy(dlna_buf, "*");
587
588	if( strncmp(class, "item", 4) == 0 )
589	{
590		/* We may need special handling for certain MIME types */
591		if( *mime == 'v' )
592		{
593			if( passed_args->flags & FLAG_MIME_AVI_DIVX )
594			{
595				if( strcmp(mime, "video/x-msvideo") == 0 )
596				{
597					if( creator )
598						strcpy(mime+6, "divx");
599					else
600						strcpy(mime+6, "avi");
601				}
602			}
603			else if( passed_args->flags & FLAG_MIME_AVI_AVI )
604			{
605				if( strcmp(mime, "video/x-msvideo") == 0 )
606				{
607					strcpy(mime+6, "avi");
608				}
609			}
610			if( !(passed_args->flags & FLAG_DLNA) )
611			{
612				if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
613				{
614					strcpy(mime+6, "mpeg");
615				}
616			}
617			/* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
618			if( passed_args->client == ESamsungTV )
619			{
620				if( strcmp(mime+6, "x-matroska") == 0 )
621				{
622					strcpy(mime+8, "mkv");
623				}
624			}
625		}
626		else if( *mime == 'a' )
627		{
628			if( strcmp(mime+6, "x-flac") == 0 )
629			{
630				if( passed_args->flags & FLAG_MIME_FLAC_FLAC )
631				{
632					strcpy(mime+6, "flac");
633				}
634			}
635		}
636
637		ret = snprintf(str_buf, 512, "&lt;item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent);
638		memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
639		passed_args->size += ret;
640		if( refID && (passed_args->filter & FILTER_REFID) ) {
641			ret = sprintf(str_buf, " refID=\"%s\"", refID);
642			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
643			passed_args->size += ret;
644		}
645		ret = snprintf(str_buf, 512, "&gt;"
646		                             "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
647		                             "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
648		                             title, class);
649		memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
650		passed_args->size += ret;
651		if( comment && (passed_args->filter & FILTER_DC_DESCRIPTION) ) {
652			ret = snprintf(str_buf, 512, "&lt;dc:description&gt;%.384s&lt;/dc:description&gt;", comment);
653			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
654			passed_args->size += ret;
655		}
656		if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
657			ret = snprintf(str_buf, 512, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
658			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
659			passed_args->size += ret;
660		}
661		if( date && (passed_args->filter & FILTER_DC_DATE) ) {
662			ret = snprintf(str_buf, 512, "&lt;dc:date&gt;%s&lt;/dc:date&gt;", date);
663			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
664			passed_args->size += ret;
665		}
666		if( artist && (passed_args->filter & FILTER_UPNP_ARTIST) ) {
667			ret = snprintf(str_buf, 512, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
668			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
669			passed_args->size += ret;
670		}
671		if( album && (passed_args->filter & FILTER_UPNP_ALBUM) ) {
672			ret = snprintf(str_buf, 512, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", album);
673			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
674			passed_args->size += ret;
675		}
676		if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
677			ret = snprintf(str_buf, 512, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
678			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
679			passed_args->size += ret;
680		}
681		if( strncmp(id, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) {
682			track = strrchr(id, '$')+1;
683		}
684		if( track && atoi(track) && (passed_args->filter & FILTER_UPNP_ORIGINALTRACKNUMBER) ) {
685			ret = sprintf(str_buf, "&lt;upnp:originalTrackNumber&gt;%s&lt;/upnp:originalTrackNumber&gt;", track);
686			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
687			passed_args->size += ret;
688		}
689		if( album_art && atoi(album_art) )
690		{
691			/* Video and audio album art is handled differently */
692			if( *mime == 'v' && (passed_args->filter & FILTER_RES) && (passed_args->client != EXbox) ) {
693				ret = sprintf(str_buf, "&lt;res protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\"&gt;"
694				                       "http://%s:%d/AlbumArt/%s-%s.jpg"
695				                       "&lt;/res&gt;",
696				                       lan_addr[0].str, runtime_vars.port, album_art, detailID);
697				memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
698				passed_args->size += ret;
699			}
700			else if( passed_args->filter & FILTER_UPNP_ALBUMARTURI ) {
701				ret = sprintf(str_buf, "&lt;upnp:albumArtURI ");
702				memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
703				passed_args->size += ret;
704				if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
705					ret = sprintf(str_buf, "dlna:profileID=\"%s\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"", "JPEG_TN");
706					memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
707					passed_args->size += ret;
708				}
709				ret = sprintf(str_buf, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>;",
710						 lan_addr[0].str, runtime_vars.port, album_art, detailID);
711				memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
712				passed_args->size += ret;
713			}
714		}
715		if( passed_args->filter & FILTER_RES ) {
716			mime_to_ext(mime, ext);
717			if( (passed_args->client == EFreeBox) && tn && atoi(tn) ) {
718				ret = sprintf(str_buf, "&lt;res protocolInfo=\"http-get:*:%s:%s\"&gt;"
719				                       "http://%s:%d/Thumbnails/%s.jpg"
720				                       "&lt;/res&gt;",
721				                       mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, runtime_vars.port, detailID);
722				memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
723				passed_args->size += ret;
724			}
725			ret = sprintf(str_buf, "&lt;res ");
726			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
727			passed_args->size += ret;
728			if( size && (passed_args->filter & FILTER_RES_SIZE) ) {
729				ret = sprintf(str_buf, "size=\"%s\" ", size);
730				memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
731				passed_args->size += ret;
732			}
733			if( duration && (passed_args->filter & FILTER_RES_DURATION) ) {
734				ret = sprintf(str_buf, "duration=\"%s\" ", duration);
735				memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
736				passed_args->size += ret;
737			}
738			if( bitrate && (passed_args->filter & FILTER_RES_BITRATE) ) {
739				if( passed_args->client == EXbox )
740					ret = sprintf(str_buf, "bitrate=\"%d\" ", atoi(bitrate)/1024);
741				else
742					ret = sprintf(str_buf, "bitrate=\"%s\" ", bitrate);
743				memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
744				passed_args->size += ret;
745			}
746			if( sampleFrequency && (passed_args->filter & FILTER_RES_SAMPLEFREQUENCY) ) {
747				ret = sprintf(str_buf, "sampleFrequency=\"%s\" ", sampleFrequency);
748				memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
749				passed_args->size += ret;
750			}
751			if( nrAudioChannels && (passed_args->filter & FILTER_RES_NRAUDIOCHANNELS) ) {
752				ret = sprintf(str_buf, "nrAudioChannels=\"%s\" ", nrAudioChannels);
753				memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
754				passed_args->size += ret;
755			}
756			if( resolution && (passed_args->filter & FILTER_RES_RESOLUTION) ) {
757				ret = sprintf(str_buf, "resolution=\"%s\" ", resolution);
758				memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
759				passed_args->size += ret;
760			}
761			ret = sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\"&gt;"
762			                       "http://%s:%d/MediaItems/%s.%s"
763			                       "&lt;/res&gt;",
764			                       mime, dlna_buf, lan_addr[0].str, runtime_vars.port, detailID, ext);
765			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
766			passed_args->size += ret;
767			if( (*mime == 'i') && (passed_args->client != EFreeBox) ) {
768#if 1 //JPEG_RESIZE
769				int srcw = atoi(strsep(&resolution, "x"));
770				int srch = atoi(resolution);
771				if( !dlna_pn ) {
772					add_resized_res(srcw, srch, 4096, 4096, "JPEG_LRG", detailID, passed_args);
773				}
774				if( !dlna_pn || !strncmp(dlna_pn, "JPEG_L", 6) || !strncmp(dlna_pn, "JPEG_M", 6) ) {
775					add_resized_res(srcw, srch, 640, 480, "JPEG_SM", detailID, passed_args);
776				}
777#endif
778				if( tn && atoi(tn) ) {
779					ret = sprintf(str_buf, "&lt;res protocolInfo=\"http-get:*:%s:%s\"&gt;"
780					                       "http://%s:%d/Thumbnails/%s.jpg"
781					                       "&lt;/res&gt;",
782					                       mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, runtime_vars.port, detailID);
783					memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
784					passed_args->size += ret;
785				}
786			}
787		}
788		ret = sprintf(str_buf, "&lt;/item&gt;");
789	}
790	else if( strncmp(class, "container", 9) == 0 )
791	{
792		ret = sprintf(str_buf, "&lt;container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent);
793		memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
794		passed_args->size += ret;
795		if( passed_args->filter & FILTER_CHILDCOUNT )
796		{
797			ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", id);
798			children = (ret > 0) ? ret : 0;
799			ret = sprintf(str_buf, "childCount=\"%d\"", children);
800			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
801			passed_args->size += ret;
802		}
803		/* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */
804		if( (passed_args->requested == 1) && (strcmp(id, "0") == 0) )
805		{
806			if( passed_args->filter & FILTER_UPNP_SEARCHCLASS )
807			{
808				ret = sprintf(str_buf, "&gt;"
809				                       "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.audioItem&lt;/upnp:searchClass&gt;"
810				                       "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.imageItem&lt;/upnp:searchClass&gt;"
811				                       "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.videoItem&lt;/upnp:searchClass");
812				memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
813				passed_args->size += ret;
814			}
815		}
816		ret = snprintf(str_buf, 512, "&gt;"
817		                             "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
818		                             "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
819		                             title, class);
820		memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
821		passed_args->size += ret;
822		if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
823			ret = snprintf(str_buf, 512, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
824			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
825			passed_args->size += ret;
826		}
827		if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
828			ret = snprintf(str_buf, 512, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
829			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
830			passed_args->size += ret;
831		}
832		if( artist && (passed_args->filter & FILTER_UPNP_ARTIST) ) {
833			ret = snprintf(str_buf, 512, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
834			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
835			passed_args->size += ret;
836		}
837		if( album_art && atoi(album_art) && (passed_args->filter & FILTER_UPNP_ALBUMARTURI) ) {
838			ret = sprintf(str_buf, "&lt;upnp:albumArtURI ");
839			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
840			passed_args->size += ret;
841			if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
842				ret = sprintf(str_buf, "dlna:profileID=\"%s\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"", "JPEG_TN");
843				memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
844				passed_args->size += ret;
845			}
846			ret = sprintf(str_buf, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>;",
847					 lan_addr[0].str, runtime_vars.port, album_art, detailID);
848			memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
849			passed_args->size += ret;
850		}
851		ret = sprintf(str_buf, "&lt;/container&gt;");
852	}
853	memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
854	passed_args->size += ret;
855
856	return 0;
857}
858
859static void
860BrowseContentDirectory(struct upnphttp * h, const char * action)
861{
862	static const char resp0[] =
863			"<u:BrowseResponse "
864			"xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
865			"<Result>"
866			"&lt;DIDL-Lite"
867			CONTENT_DIRECTORY_SCHEMAS;
868
869	char *resp = malloc(1048576);
870	char str_buf[512];
871	char *zErrMsg = 0;
872	char *sql, *ptr;
873	int ret;
874	struct Response args;
875	int totalMatches;
876	struct NameValueParserData data;
877	*resp = '\0';
878
879	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
880	char * ObjectId = GetValueFromNameValueList(&data, "ObjectID");
881	char * Filter = GetValueFromNameValueList(&data, "Filter");
882	char * BrowseFlag = GetValueFromNameValueList(&data, "BrowseFlag");
883	char * SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
884	char * orderBy = NULL;
885	int RequestedCount = 0;
886	int StartingIndex = 0;
887	if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
888		RequestedCount = atoi(ptr);
889	if( !RequestedCount )
890		RequestedCount = -1;
891	if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
892		StartingIndex = atoi(ptr);
893	if( !BrowseFlag || (strcmp(BrowseFlag, "BrowseDirectChildren") && strcmp(BrowseFlag, "BrowseMetadata")) )
894	{
895		SoapError(h, 402, "Invalid Args");
896		if( h->req_client == EXbox )
897			ObjectId = malloc(1);
898		goto browse_error;
899	}
900	if( !ObjectId )
901	{
902		if( !(ObjectId = GetValueFromNameValueList(&data, "ContainerID")) )
903		{
904			SoapError(h, 701, "No such object error");
905			if( h->req_client == EXbox )
906				ObjectId = malloc(1);
907			goto browse_error;
908		}
909	}
910	memset(&args, 0, sizeof(args));
911
912	args.alloced = 1048576;
913	args.resp = resp;
914	args.size = sprintf(resp, "%s", resp0);
915	/* See if we need to include DLNA namespace reference */
916	args.filter = set_filter_flags(Filter);
917	if( args.filter & FILTER_DLNA_NAMESPACE )
918	{
919		ret = sprintf(str_buf, DLNA_NAMESPACE);
920		memcpy(resp+args.size, &str_buf, ret+1);
921		args.size += ret;
922	}
923	ret = sprintf(str_buf, "&gt;\n");
924	memcpy(resp+args.size, &str_buf, ret+1);
925	args.size += ret;
926
927	args.returned = 0;
928	args.requested = RequestedCount;
929	args.client = h->req_client;
930	args.flags = h->reqflags;
931	if( h->req_client == EXbox )
932	{
933		if( strcmp(ObjectId, "16") == 0 )
934			ObjectId = strdup(IMAGE_DIR_ID);
935		else if( strcmp(ObjectId, "15") == 0 )
936			ObjectId = strdup(VIDEO_DIR_ID);
937		else
938			ObjectId = strdup(ObjectId);
939	}
940	DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n"
941	                         " * ObjectID: %s\n"
942	                         " * Count: %d\n"
943	                         " * StartingIndex: %d\n"
944	                         " * BrowseFlag: %s\n"
945	                         " * Filter: %s\n"
946	                         " * SortCriteria: %s\n",
947				ObjectId, RequestedCount, StartingIndex,
948	                        BrowseFlag, Filter, SortCriteria);
949
950	if( strcmp(BrowseFlag+6, "Metadata") == 0 )
951	{
952		args.requested = 1;
953		sql = sqlite3_mprintf( SELECT_COLUMNS
954		                      "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
955		                      " where OBJECT_ID = '%s';"
956		                      , ObjectId);
957		ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
958		totalMatches = args.returned;
959	}
960	else
961	{
962		ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", ObjectId);
963		totalMatches = (ret > 0) ? ret : 0;
964		ret = 0;
965		if( SortCriteria )
966		{
967#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
968			if( totalMatches < 10000 )
969#endif
970			orderBy = parse_sort_criteria(SortCriteria, &ret);
971		}
972		else
973		{
974			if( strncmp(ObjectId, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 )
975			{
976				if( strcmp(ObjectId, MUSIC_PLIST_ID) == 0 )
977					asprintf(&orderBy, "order by d.TITLE");
978				else
979					asprintf(&orderBy, "order by length(OBJECT_ID), OBJECT_ID");
980			}
981		}
982		/* If it's a DLNA client, return an error for bad sort criteria */
983		if( (args.flags & FLAG_DLNA) && ret )
984		{
985			SoapError(h, 709, "Unsupported or invalid sort criteria");
986			goto browse_error;
987		}
988
989		sql = sqlite3_mprintf( SELECT_COLUMNS
990		                      "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
991				      " where PARENT_ID = '%s' %s limit %d, %d;",
992				      ObjectId, orderBy, StartingIndex, RequestedCount);
993		DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql);
994		ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
995	}
996	sqlite3_free(sql);
997	if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
998	{
999		DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1000		sqlite3_free(zErrMsg);
1001	}
1002	/* Does the object even exist? */
1003	if( !totalMatches )
1004	{
1005		ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", ObjectId);
1006		if( ret <= 0 )
1007		{
1008			SoapError(h, 701, "No such object error");
1009			goto browse_error;
1010		}
1011	}
1012	ret = snprintf(str_buf, sizeof(str_buf), "&lt;/DIDL-Lite&gt;</Result>\n"
1013	                                         "<NumberReturned>%u</NumberReturned>\n"
1014	                                         "<TotalMatches>%u</TotalMatches>\n"
1015	                                         "<UpdateID>%u</UpdateID>"
1016	                                         "</u:BrowseResponse>",
1017	                                         args.returned, totalMatches, updateID);
1018	memcpy(resp+args.size, &str_buf, ret+1);
1019	args.size += ret;
1020	BuildSendAndCloseSoapResp(h, resp, args.size);
1021browse_error:
1022	ClearNameValueList(&data);
1023	if( orderBy )
1024		free(orderBy);
1025	free(resp);
1026	if( h->req_client == EXbox )
1027	{
1028		free(ObjectId);
1029	}
1030}
1031
1032static void
1033SearchContentDirectory(struct upnphttp * h, const char * action)
1034{
1035	static const char resp0[] =
1036			"<u:SearchResponse "
1037			"xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1038			"<Result>"
1039			"&lt;DIDL-Lite"
1040			CONTENT_DIRECTORY_SCHEMAS;
1041
1042	char *resp = malloc(1048576);
1043	char *zErrMsg = 0;
1044	char *sql, *ptr;
1045	char **result;
1046	char str_buf[4096];
1047	int ret;
1048	struct Response args;
1049	int totalMatches = 0;
1050	*resp = '\0';
1051
1052	struct NameValueParserData data;
1053	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1054	char * ContainerID = GetValueFromNameValueList(&data, "ContainerID");
1055	char * Filter = GetValueFromNameValueList(&data, "Filter");
1056	char * SearchCriteria = GetValueFromNameValueList(&data, "SearchCriteria");
1057	char * SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
1058	char * newSearchCriteria = NULL;
1059	char * orderBy = NULL;
1060	char groupBy[] = "group by DETAIL_ID";
1061	int RequestedCount = 0;
1062	int StartingIndex = 0;
1063	if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
1064		RequestedCount = atoi(ptr);
1065	if( !RequestedCount )
1066		RequestedCount = -1;
1067	if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
1068		StartingIndex = atoi(ptr);
1069	if( !ContainerID )
1070	{
1071		SoapError(h, 701, "No such object error");
1072		if( h->req_client == EXbox )
1073			ContainerID = malloc(1);
1074		goto search_error;
1075	}
1076	memset(&args, 0, sizeof(args));
1077
1078	args.alloced = 1048576;
1079	args.resp = resp;
1080	args.size = sprintf(resp, "%s", resp0);
1081	/* See if we need to include DLNA namespace reference */
1082	args.filter = set_filter_flags(Filter);
1083	if( args.filter & FILTER_DLNA_NAMESPACE )
1084	{
1085		ret = sprintf(str_buf, DLNA_NAMESPACE);
1086		memcpy(resp+args.size, &str_buf, ret+1);
1087		args.size += ret;
1088	}
1089	ret = sprintf(str_buf, "&gt;\n");
1090	memcpy(resp+args.size, &str_buf, ret+1);
1091	args.size += ret;
1092
1093	args.returned = 0;
1094	args.requested = RequestedCount;
1095	args.client = h->req_client;
1096	args.flags = h->reqflags;
1097	if( h->req_client == EXbox )
1098	{
1099		if( strcmp(ContainerID, "4") == 0 )
1100			ContainerID = strdup("1$4");
1101		else if( strcmp(ContainerID, "5") == 0 )
1102			ContainerID = strdup("1$5");
1103		else if( strcmp(ContainerID, "6") == 0 )
1104			ContainerID = strdup("1$6");
1105		else if( strcmp(ContainerID, "7") == 0 )
1106			ContainerID = strdup("1$7");
1107		else if( strcmp(ContainerID, "F") == 0 )
1108			ContainerID = strdup(MUSIC_PLIST_ID);
1109		else
1110			ContainerID = strdup(ContainerID);
1111		#if 0 // Looks like the 360 already does this
1112		/* Sort by track number for some containers */
1113		if( orderBy &&
1114		    ((strncmp(ContainerID, "1$5", 3) == 0) ||
1115		     (strncmp(ContainerID, "1$6", 3) == 0) ||
1116		     (strncmp(ContainerID, "1$7", 3) == 0)) )
1117		{
1118			DPRINTF(E_DEBUG, L_HTTP, "Old sort order: %s\n", orderBy);
1119			sprintf(str_buf, "d.TRACK, ");
1120			memmove(orderBy+18, orderBy+9, strlen(orderBy)+1);
1121			memmove(orderBy+9, &str_buf, 9);
1122			DPRINTF(E_DEBUG, L_HTTP, "New sort order: %s\n", orderBy);
1123		}
1124		#endif
1125	}
1126	DPRINTF(E_DEBUG, L_HTTP, "Searching ContentDirectory:\n"
1127	                         " * ObjectID: %s\n"
1128	                         " * Count: %d\n"
1129	                         " * StartingIndex: %d\n"
1130	                         " * SearchCriteria: %s\n"
1131	                         " * Filter: %s\n"
1132	                         " * SortCriteria: %s\n",
1133				ContainerID, RequestedCount, StartingIndex,
1134	                        SearchCriteria, Filter, SortCriteria);
1135
1136	if( strcmp(ContainerID, "0") == 0 )
1137		*ContainerID = '*';
1138	else if( strcmp(ContainerID, "1$4") == 0 )
1139		groupBy[0] = '\0';
1140	if( !SearchCriteria )
1141	{
1142		asprintf(&newSearchCriteria, "1 = 1");
1143		SearchCriteria = newSearchCriteria;
1144	}
1145	else
1146	{
1147		SearchCriteria = modifyString(SearchCriteria, "&quot;", "\"", 0);
1148		SearchCriteria = modifyString(SearchCriteria, "&apos;", "'", 0);
1149		SearchCriteria = modifyString(SearchCriteria, "object.", "", 0);
1150		SearchCriteria = modifyString(SearchCriteria, "derivedfrom", "like", 1);
1151		SearchCriteria = modifyString(SearchCriteria, "contains", "like", 2);
1152		SearchCriteria = modifyString(SearchCriteria, "dc:title", "d.TITLE", 0);
1153		SearchCriteria = modifyString(SearchCriteria, "dc:creator", "d.CREATOR", 0);
1154		SearchCriteria = modifyString(SearchCriteria, "upnp:class", "o.CLASS", 0);
1155		SearchCriteria = modifyString(SearchCriteria, "upnp:artist", "d.ARTIST", 0);
1156		SearchCriteria = modifyString(SearchCriteria, "upnp:album", "d.ALBUM", 0);
1157		SearchCriteria = modifyString(SearchCriteria, "exists true", "is not NULL", 0);
1158		SearchCriteria = modifyString(SearchCriteria, "exists false", "is NULL", 0);
1159		SearchCriteria = modifyString(SearchCriteria, "@refID", "REF_ID", 0);
1160		if( strstr(SearchCriteria, "@id") )
1161		{
1162			newSearchCriteria = modifyString(strdup(SearchCriteria), "@id", "OBJECT_ID", 0);
1163			SearchCriteria = newSearchCriteria;
1164		}
1165		if( strstr(SearchCriteria, "res is ") )
1166		{
1167			if( newSearchCriteria )
1168				newSearchCriteria = modifyString(newSearchCriteria, "res is ", "MIME is ", 0);
1169			else
1170				newSearchCriteria = modifyString(strdup(SearchCriteria), "res is ", "MIME is ", 0);
1171			SearchCriteria = newSearchCriteria;
1172		}
1173		#if 0 // Does 360 need this?
1174		if( strstr(SearchCriteria, "&amp;") )
1175		{
1176			if( newSearchCriteria )
1177				newSearchCriteria = modifyString(newSearchCriteria, "&amp;", "&amp;amp;", 0);
1178			else
1179				newSearchCriteria = modifyString(strdup(SearchCriteria), "&amp;", "&amp;amp;", 0);
1180			SearchCriteria = newSearchCriteria;
1181		}
1182		#endif
1183	}
1184	DPRINTF(E_DEBUG, L_HTTP, "Translated SearchCriteria: %s\n", SearchCriteria);
1185
1186	sprintf(str_buf, "SELECT (select count(distinct DETAIL_ID) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1187	                 " where (OBJECT_ID glob '%s$*') and (%s))"
1188	                 " + "
1189	                 "(select count(*) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1190	                 " where (OBJECT_ID = '%s') and (%s))",
1191	                 ContainerID, SearchCriteria, ContainerID, SearchCriteria);
1192	//DEBUG DPRINTF(E_DEBUG, L_HTTP, "Count SQL: %s\n", sql);
1193	ret = sql_get_table(db, str_buf, &result, NULL, NULL);
1194	if( ret == SQLITE_OK )
1195	{
1196		totalMatches = atoi(result[1]);
1197		sqlite3_free_table(result);
1198	}
1199	else
1200	{
1201		/* Must be invalid SQL, so most likely bad or unhandled search criteria. */
1202		SoapError(h, 708, "Unsupported or invalid search criteria");
1203		goto search_error;
1204	}
1205	/* Does the object even exist? */
1206	if( !totalMatches )
1207	{
1208		ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'",
1209		                        !strcmp(ContainerID, "*")?"0":ContainerID);
1210		if( ret <= 0 )
1211		{
1212			SoapError(h, 710, "No such container");
1213			goto search_error;
1214		}
1215	}
1216#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
1217	ret = 0;
1218	if( totalMatches < 10000 )
1219#endif
1220		orderBy = parse_sort_criteria(SortCriteria, &ret);
1221	/* If it's a DLNA client, return an error for bad sort criteria */
1222	if( (args.flags & FLAG_DLNA) && ret )
1223	{
1224		SoapError(h, 709, "Unsupported or invalid sort criteria");
1225		goto search_error;
1226	}
1227
1228	sql = sqlite3_mprintf( SELECT_COLUMNS
1229	                      "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1230	                      " where OBJECT_ID glob '%s$*' and (%s) %s "
1231	                      "%z %s"
1232	                      " limit %d, %d",
1233	                      ContainerID, SearchCriteria, groupBy,
1234	                      (*ContainerID == '*') ? NULL :
1235                              sqlite3_mprintf("UNION ALL " SELECT_COLUMNS
1236	                                      "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1237	                                      " where OBJECT_ID = '%s' and (%s) ", ContainerID, SearchCriteria),
1238	                      orderBy, StartingIndex, RequestedCount);
1239	DPRINTF(E_DEBUG, L_HTTP, "Search SQL: %s\n", sql);
1240	ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1241	if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
1242	{
1243		DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1244		sqlite3_free(zErrMsg);
1245	}
1246	sqlite3_free(sql);
1247	strcat(resp, str_buf);
1248	ret = snprintf(str_buf, sizeof(str_buf), "&lt;/DIDL-Lite&gt;</Result>\n"
1249	                                         "<NumberReturned>%u</NumberReturned>\n"
1250	                                         "<TotalMatches>%u</TotalMatches>\n"
1251	                                         "<UpdateID>%u</UpdateID>"
1252	                                         "</u:SearchResponse>",
1253	                                         args.returned, totalMatches, updateID);
1254	memcpy(resp+args.size, &str_buf, ret+1);
1255	args.size += ret;
1256	BuildSendAndCloseSoapResp(h, resp, args.size);
1257search_error:
1258	ClearNameValueList(&data);
1259	if( orderBy )
1260		free(orderBy);
1261	if( newSearchCriteria )
1262		free(newSearchCriteria);
1263	free(resp);
1264	if( h->req_client == EXbox )
1265	{
1266		free(ContainerID);
1267	}
1268}
1269
1270/*
1271If a control point calls QueryStateVariable on a state variable that is not
1272buffered in memory within (or otherwise available from) the service,
1273the service must return a SOAP fault with an errorCode of 404 Invalid Var.
1274
1275QueryStateVariable remains useful as a limited test tool but may not be
1276part of some future versions of UPnP.
1277*/
1278static void
1279QueryStateVariable(struct upnphttp * h, const char * action)
1280{
1281	static const char resp[] =
1282        "<u:%sResponse "
1283        "xmlns:u=\"%s\">"
1284		"<return>%s</return>"
1285        "</u:%sResponse>";
1286
1287	char body[512];
1288	int bodylen;
1289	struct NameValueParserData data;
1290	const char * var_name;
1291
1292	ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1293	/*var_name = GetValueFromNameValueList(&data, "QueryStateVariable"); */
1294	/*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/
1295	var_name = GetValueFromNameValueList(&data, "varName");
1296
1297	DPRINTF(E_INFO, L_HTTP, "QueryStateVariable(%.40s)\n", var_name);
1298
1299	if(!var_name)
1300	{
1301		SoapError(h, 402, "Invalid Args");
1302	}
1303	else if(strcmp(var_name, "ConnectionStatus") == 0)
1304	{
1305		bodylen = snprintf(body, sizeof(body), resp,
1306                           action, "urn:schemas-upnp-org:control-1-0",
1307		                   "Connected", action);
1308		BuildSendAndCloseSoapResp(h, body, bodylen);
1309	}
1310	else
1311	{
1312		DPRINTF(E_WARN, L_HTTP, "%s: Unknown: %s\n", action, var_name?var_name:"");
1313		SoapError(h, 404, "Invalid Var");
1314	}
1315
1316	ClearNameValueList(&data);
1317}
1318
1319static const struct
1320{
1321	const char * methodName;
1322	void (*methodImpl)(struct upnphttp *, const char *);
1323}
1324soapMethods[] =
1325{
1326	{ "QueryStateVariable", QueryStateVariable},
1327	{ "Browse", BrowseContentDirectory},
1328	{ "Search", SearchContentDirectory},
1329	{ "GetSearchCapabilities", GetSearchCapabilities},
1330	{ "GetSortCapabilities", GetSortCapabilities},
1331	{ "GetSystemUpdateID", GetSystemUpdateID},
1332	{ "GetProtocolInfo", GetProtocolInfo},
1333	{ "GetCurrentConnectionIDs", GetCurrentConnectionIDs},
1334	{ "GetCurrentConnectionInfo", GetCurrentConnectionInfo},
1335	{ "IsAuthorized", IsAuthorizedValidated},
1336	{ "IsValidated", IsAuthorizedValidated},
1337	{ 0, 0 }
1338};
1339
1340void
1341ExecuteSoapAction(struct upnphttp * h, const char * action, int n)
1342{
1343	char * p;
1344	char * p2;
1345	int i, len, methodlen;
1346
1347	i = 0;
1348	p = strchr(action, '#');
1349
1350	if(p)
1351	{
1352		p++;
1353		p2 = strchr(p, '"');
1354		if(p2)
1355			methodlen = p2 - p;
1356		else
1357			methodlen = n - (p - action);
1358		DPRINTF(E_DEBUG, L_HTTP, "SoapMethod: %.*s\n", methodlen, p);
1359		while(soapMethods[i].methodName)
1360		{
1361			len = strlen(soapMethods[i].methodName);
1362			if(strncmp(p, soapMethods[i].methodName, len) == 0)
1363			{
1364				soapMethods[i].methodImpl(h, soapMethods[i].methodName);
1365				return;
1366			}
1367			i++;
1368		}
1369
1370		DPRINTF(E_WARN, L_HTTP, "SoapMethod: Unknown: %.*s\n", methodlen, p);
1371	}
1372
1373	SoapError(h, 401, "Invalid Action");
1374}
1375
1376/* Standard Errors:
1377 *
1378 * errorCode errorDescription Description
1379 * --------	---------------- -----------
1380 * 401 		Invalid Action 	No action by that name at this service.
1381 * 402 		Invalid Args 	Could be any of the following: not enough in args,
1382 * 							too many in args, no in arg by that name,
1383 * 							one or more in args are of the wrong data type.
1384 * 403 		Out of Sync 	Out of synchronization.
1385 * 501 		Action Failed 	May be returned in current state of service
1386 * 							prevents invoking that action.
1387 * 600-699 	TBD 			Common action errors. Defined by UPnP Forum
1388 * 							Technical Committee.
1389 * 700-799 	TBD 			Action-specific errors for standard actions.
1390 * 							Defined by UPnP Forum working committee.
1391 * 800-899 	TBD 			Action-specific errors for non-standard actions.
1392 * 							Defined by UPnP vendor.
1393*/
1394void
1395SoapError(struct upnphttp * h, int errCode, const char * errDesc)
1396{
1397	static const char resp[] =
1398		"<s:Envelope "
1399		"xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
1400		"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1401		"<s:Body>"
1402		"<s:Fault>"
1403		"<faultcode>s:Client</faultcode>"
1404		"<faultstring>UPnPError</faultstring>"
1405		"<detail>"
1406		"<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">"
1407		"<errorCode>%d</errorCode>"
1408		"<errorDescription>%s</errorDescription>"
1409		"</UPnPError>"
1410		"</detail>"
1411		"</s:Fault>"
1412		"</s:Body>"
1413		"</s:Envelope>";
1414
1415	char body[2048];
1416	int bodylen;
1417
1418	DPRINTF(E_WARN, L_HTTP, "Returning UPnPError %d: %s\n", errCode, errDesc);
1419	bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc);
1420	BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen);
1421	SendResp_upnphttp(h);
1422	CloseSocket_upnphttp(h);
1423}
1424
1425