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