minidlna support now Samsung TV C550/C650 (thx amir909)
[tomato.git] / release / src / router / minidlna / upnpsoap.c
blob77627c3e56608f0fdc9ba4fcb09e2e54fbcd2070
1 /* MiniDLNA project
3 * http://sourceforge.net/projects/minidlna/
5 * MiniDLNA media server
6 * Copyright (C) 2008-2009 Justin Maggard
8 * This file is part of MiniDLNA.
10 * MiniDLNA is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
12 * published by the Free Software Foundation.
14 * MiniDLNA is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
22 * Portions of the code from the MiniUPnP project:
24 * Copyright (c) 2006-2007, Thomas Bernard
25 * All rights reserved.
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions are met:
29 * * Redistributions of source code must retain the above copyright
30 * notice, this list of conditions and the following disclaimer.
31 * * Redistributions in binary form must reproduce the above copyright
32 * notice, this list of conditions and the following disclaimer in the
33 * documentation and/or other materials provided with the distribution.
34 * * The name of the author may not be used to endorse or promote products
35 * derived from this software without specific prior written permission.
37 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
38 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
39 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
40 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
41 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
43 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
44 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
45 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
46 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
47 * POSSIBILITY OF SUCH DAMAGE.
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <sys/socket.h>
53 #include <unistd.h>
54 #include <dirent.h>
55 #include <sys/stat.h>
56 #include <sys/types.h>
57 #include <arpa/inet.h>
58 #include <netinet/in.h>
59 #include <netdb.h>
60 #include <ctype.h>
62 #include "config.h"
63 #include "upnpglobalvars.h"
64 #include "utils.h"
65 #include "upnphttp.h"
66 #include "upnpsoap.h"
67 #include "upnpreplyparse.h"
68 #include "getifaddr.h"
69 #include "scanner.h"
70 #include "sql.h"
71 #include "log.h"
73 static void
74 BuildSendAndCloseSoapResp(struct upnphttp * h,
75 const char * body, int bodylen)
77 static const char beforebody[] =
78 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
79 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
80 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
81 "<s:Body>";
83 static const char afterbody[] =
84 "</s:Body>"
85 "</s:Envelope>\r\n";
87 BuildHeader_upnphttp(h, 200, "OK", sizeof(beforebody) - 1
88 + sizeof(afterbody) - 1 + bodylen );
90 memcpy(h->res_buf + h->res_buflen, beforebody, sizeof(beforebody) - 1);
91 h->res_buflen += sizeof(beforebody) - 1;
93 memcpy(h->res_buf + h->res_buflen, body, bodylen);
94 h->res_buflen += bodylen;
96 memcpy(h->res_buf + h->res_buflen, afterbody, sizeof(afterbody) - 1);
97 h->res_buflen += sizeof(afterbody) - 1;
99 SendResp_upnphttp(h);
100 CloseSocket_upnphttp(h);
103 static void
104 GetSystemUpdateID(struct upnphttp * h, const char * action)
106 static const char resp[] =
107 "<u:%sResponse "
108 "xmlns:u=\"%s\">"
109 "<Id>%d</Id>"
110 "</u:%sResponse>";
112 char body[512];
113 int bodylen;
115 bodylen = snprintf(body, sizeof(body), resp,
116 action, "urn:schemas-upnp-org:service:ContentDirectory:1",
117 updateID, action);
118 BuildSendAndCloseSoapResp(h, body, bodylen);
121 static void
122 IsAuthorizedValidated(struct upnphttp * h, const char * action)
124 static const char resp[] =
125 "<u:%sResponse "
126 "xmlns:u=\"%s\">"
127 "<Result>%d</Result>"
128 "</u:%sResponse>";
130 char body[512];
131 int bodylen;
133 bodylen = snprintf(body, sizeof(body), resp,
134 action, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
135 1, action);
136 BuildSendAndCloseSoapResp(h, body, bodylen);
139 static void
140 GetProtocolInfo(struct upnphttp * h, const char * action)
142 static const char resp[] =
143 "<u:%sResponse "
144 "xmlns:u=\"%s\">"
145 "<Source>"
146 RESOURCE_PROTOCOL_INFO_VALUES
147 "</Source>"
148 "<Sink></Sink>"
149 "</u:%sResponse>";
151 char * body;
152 int bodylen;
154 bodylen = asprintf(&body, resp,
155 action, "urn:schemas-upnp-org:service:ConnectionManager:1",
156 action);
157 BuildSendAndCloseSoapResp(h, body, bodylen);
158 free(body);
161 static void
162 GetSortCapabilities(struct upnphttp * h, const char * action)
164 static const char resp[] =
165 "<u:%sResponse "
166 "xmlns:u=\"%s\">"
167 "<SortCaps>"
168 "dc:title,"
169 "dc:date,"
170 "upnp:class,"
171 "upnp:originalTrackNumber"
172 "</SortCaps>"
173 "</u:%sResponse>";
175 char body[512];
176 int bodylen;
178 bodylen = snprintf(body, sizeof(body), resp,
179 action, "urn:schemas-upnp-org:service:ContentDirectory:1",
180 action);
181 BuildSendAndCloseSoapResp(h, body, bodylen);
184 static void
185 GetSearchCapabilities(struct upnphttp * h, const char * action)
187 static const char resp[] =
188 "<u:%sResponse xmlns:u=\"%s\">"
189 "<SearchCaps>"
190 "dc:creator,"
191 "dc:title,"
192 "upnp:album,"
193 "upnp:actor,"
194 "upnp:artist,"
195 "upnp:class,"
196 "upnp:genre,"
197 "@refID"
198 "</SearchCaps>"
199 "</u:%sResponse>";
201 char body[512];
202 int bodylen;
204 bodylen = snprintf(body, sizeof(body), resp,
205 action, "urn:schemas-upnp-org:service:ContentDirectory:1",
206 action);
207 BuildSendAndCloseSoapResp(h, body, bodylen);
210 static void
211 GetCurrentConnectionIDs(struct upnphttp * h, const char * action)
213 /* TODO: Use real data. - JM */
214 static const char resp[] =
215 "<u:%sResponse "
216 "xmlns:u=\"%s\">"
217 "<ConnectionIDs>0</ConnectionIDs>"
218 "</u:%sResponse>";
220 char body[512];
221 int bodylen;
223 bodylen = snprintf(body, sizeof(body), resp,
224 action, "urn:schemas-upnp-org:service:ConnectionManager:1",
225 action);
226 BuildSendAndCloseSoapResp(h, body, bodylen);
229 static void
230 GetCurrentConnectionInfo(struct upnphttp * h, const char * action)
232 /* TODO: Use real data. - JM */
233 static const char resp[] =
234 "<u:%sResponse "
235 "xmlns:u=\"%s\">"
236 "<RcsID>-1</RcsID>"
237 "<AVTransportID>-1</AVTransportID>"
238 "<ProtocolInfo></ProtocolInfo>"
239 "<PeerConnectionManager></PeerConnectionManager>"
240 "<PeerConnectionID>-1</PeerConnectionID>"
241 "<Direction>Output</Direction>"
242 "<Status>Unknown</Status>"
243 "</u:%sResponse>";
245 char body[sizeof(resp)+128];
246 int bodylen;
248 bodylen = snprintf(body, sizeof(body), resp,
249 action, "urn:schemas-upnp-org:service:ConnectionManager:1",
250 action);
251 BuildSendAndCloseSoapResp(h, body, bodylen);
254 static void
255 mime_to_ext(const char * mime, char * buf)
257 switch( *mime )
259 /* Audio extensions */
260 case 'a':
261 if( strcmp(mime+6, "mpeg") == 0 )
262 strcpy(buf, "mp3");
263 else if( strcmp(mime+6, "mp4") == 0 )
264 strcpy(buf, "m4a");
265 else if( strcmp(mime+6, "x-ms-wma") == 0 )
266 strcpy(buf, "wma");
267 else if( strcmp(mime+6, "x-flac") == 0 )
268 strcpy(buf, "flac");
269 else if( strcmp(mime+6, "flac") == 0 )
270 strcpy(buf, "flac");
271 else if( strcmp(mime+6, "x-wav") == 0 )
272 strcpy(buf, "wav");
273 else if( strncmp(mime+6, "L16", 3) == 0 )
274 strcpy(buf, "pcm");
275 else if( strcmp(mime+6, "3gpp") == 0 )
276 strcpy(buf, "3gp");
277 else if( strcmp(mime, "application/ogg") == 0 )
278 strcpy(buf, "ogg");
279 else
280 strcpy(buf, "dat");
281 break;
282 case 'v':
283 if( strcmp(mime+6, "avi") == 0 )
284 strcpy(buf, "avi");
285 else if( strcmp(mime+6, "divx") == 0 )
286 strcpy(buf, "avi");
287 else if( strcmp(mime+6, "x-msvideo") == 0 )
288 strcpy(buf, "avi");
289 else if( strcmp(mime+6, "mpeg") == 0 )
290 strcpy(buf, "mpg");
291 else if( strcmp(mime+6, "mp4") == 0 )
292 strcpy(buf, "mp4");
293 else if( strcmp(mime+6, "x-ms-wmv") == 0 )
294 strcpy(buf, "wmv");
295 else if( strcmp(mime+6, "x-matroska") == 0 )
296 strcpy(buf, "mkv");
297 else if( strcmp(mime+6, "x-mkv") == 0 )
298 strcpy(buf, "mkv");
299 else if( strcmp(mime+6, "x-flv") == 0 )
300 strcpy(buf, "flv");
301 else if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
302 strcpy(buf, "mpg");
303 else if( strcmp(mime+6, "quicktime") == 0 )
304 strcpy(buf, "mov");
305 else if( strcmp(mime+6, "3gpp") == 0 )
306 strcpy(buf, "3gp");
307 else if( strcmp(mime+6, "x-tivo-mpeg") == 0 )
308 strcpy(buf, "TiVo");
309 else
310 strcpy(buf, "dat");
311 break;
312 case 'i':
313 if( strcmp(mime+6, "jpeg") == 0 )
314 strcpy(buf, "jpg");
315 else if( strcmp(mime+6, "png") == 0 )
316 strcpy(buf, "png");
317 else
318 strcpy(buf, "dat");
319 break;
320 default:
321 strcpy(buf, "dat");
322 break;
326 #define FILTER_CHILDCOUNT 0x00000001
327 #define FILTER_DC_CREATOR 0x00000002
328 #define FILTER_DC_DATE 0x00000004
329 #define FILTER_DC_DESCRIPTION 0x00000008
330 #define FILTER_DLNA_NAMESPACE 0x00000010
331 #define FILTER_REFID 0x00000020
332 #define FILTER_RES 0x00000040
333 #define FILTER_RES_BITRATE 0x00000080
334 #define FILTER_RES_DURATION 0x00000100
335 #define FILTER_RES_NRAUDIOCHANNELS 0x00000200
336 #define FILTER_RES_RESOLUTION 0x00000400
337 #define FILTER_RES_SAMPLEFREQUENCY 0x00000800
338 #define FILTER_RES_SIZE 0x00001000
339 #define FILTER_UPNP_ACTOR 0x00002000
340 #define FILTER_UPNP_ALBUM 0x00004000
341 #define FILTER_UPNP_ALBUMARTURI 0x00008000
342 #define FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID 0x00010000
343 #define FILTER_UPNP_ARTIST 0x00020000
344 #define FILTER_UPNP_GENRE 0x00040000
345 #define FILTER_UPNP_ORIGINALTRACKNUMBER 0x00080000
346 #define FILTER_UPNP_SEARCHCLASS 0x00100000
347 #define FILTER_SEC 0x00200000
348 #define FILTER_SEC_CAPTION_INFO 0x00400000
349 #define FILTER_SEC_CAPTION_INFO_EX 0x00800000
351 static u_int32_t
352 set_filter_flags(char * filter, struct upnphttp *h)
354 char *item, *saveptr = NULL;
355 u_int32_t flags = 0;
357 if( !filter || (strlen(filter) <= 1) )
358 return 0xFFFFFFFF;
359 if( h->reqflags & FLAG_SAMSUNG )
360 flags |= FILTER_DLNA_NAMESPACE;
361 item = strtok_r(filter, ",", &saveptr);
362 while( item != NULL )
364 if( saveptr )
365 *(item-1) = ',';
366 while( isspace(*item) )
367 item++;
368 if( strcmp(item, "@childCount") == 0 )
370 flags |= FILTER_CHILDCOUNT;
372 else if( strcmp(item, "dc:creator") == 0 )
374 flags |= FILTER_DC_CREATOR;
376 else if( strcmp(item, "dc:date") == 0 )
378 flags |= FILTER_DC_DATE;
380 else if( strcmp(item, "dc:description") == 0 )
382 flags |= FILTER_DC_DESCRIPTION;
384 else if( strcmp(item, "dlna") == 0 )
386 flags |= FILTER_DLNA_NAMESPACE;
388 else if( strcmp(item, "@refID") == 0 )
390 flags |= FILTER_REFID;
392 else if( strcmp(item, "upnp:album") == 0 )
394 flags |= FILTER_UPNP_ALBUM;
396 else if( strcmp(item, "upnp:albumArtURI") == 0 )
398 flags |= FILTER_UPNP_ALBUMARTURI;
399 if( h->reqflags & FLAG_SAMSUNG )
400 flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID;
402 else if( strcmp(item, "upnp:albumArtURI@dlna:profileID") == 0 )
404 flags |= FILTER_UPNP_ALBUMARTURI;
405 flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID;
407 else if( strcmp(item, "upnp:artist") == 0 )
409 flags |= FILTER_UPNP_ARTIST;
411 else if( strcmp(item, "upnp:actor") == 0 )
413 flags |= FILTER_UPNP_ACTOR;
415 else if( strcmp(item, "upnp:genre") == 0 )
417 flags |= FILTER_UPNP_GENRE;
419 else if( strcmp(item, "upnp:originalTrackNumber") == 0 )
421 flags |= FILTER_UPNP_ORIGINALTRACKNUMBER;
423 else if( strcmp(item, "upnp:searchClass") == 0 )
425 flags |= FILTER_UPNP_SEARCHCLASS;
427 else if( strcmp(item, "res") == 0 )
429 flags |= FILTER_RES;
431 else if( (strcmp(item, "res@bitrate") == 0) ||
432 (strcmp(item, "@bitrate") == 0) ||
433 ((strcmp(item, "bitrate") == 0) && (flags & FILTER_RES)) )
435 flags |= FILTER_RES;
436 flags |= FILTER_RES_BITRATE;
438 else if( (strcmp(item, "res@duration") == 0) ||
439 (strcmp(item, "@duration") == 0) ||
440 ((strcmp(item, "duration") == 0) && (flags & FILTER_RES)) )
442 flags |= FILTER_RES;
443 flags |= FILTER_RES_DURATION;
445 else if( (strcmp(item, "res@nrAudioChannels") == 0) ||
446 (strcmp(item, "@nrAudioChannels") == 0) ||
447 ((strcmp(item, "nrAudioChannels") == 0) && (flags & FILTER_RES)) )
449 flags |= FILTER_RES;
450 flags |= FILTER_RES_NRAUDIOCHANNELS;
452 else if( (strcmp(item, "res@resolution") == 0) ||
453 (strcmp(item, "@resolution") == 0) ||
454 ((strcmp(item, "resolution") == 0) && (flags & FILTER_RES)) )
456 flags |= FILTER_RES;
457 flags |= FILTER_RES_RESOLUTION;
459 else if( (strcmp(item, "res@sampleFrequency") == 0) ||
460 (strcmp(item, "@sampleFrequency") == 0) ||
461 ((strcmp(item, "sampleFrequency") == 0) && (flags & FILTER_RES)) )
463 flags |= FILTER_RES;
464 flags |= FILTER_RES_SAMPLEFREQUENCY;
466 else if( (strcmp(item, "res@size") == 0) ||
467 (strcmp(item, "@size") == 0) ||
468 (strcmp(item, "size") == 0) )
470 flags |= FILTER_RES;
471 flags |= FILTER_RES_SIZE;
473 else if( strcmp(item, "sec:CaptionInfo") == 0)
475 flags |= FILTER_SEC;
476 flags |= FILTER_SEC_CAPTION_INFO;
478 else if( strcmp(item, "sec:CaptionInfoEx") == 0)
480 flags |= FILTER_SEC;
481 flags |= FILTER_SEC_CAPTION_INFO_EX;
483 item = strtok_r(NULL, ",", &saveptr);
486 return flags;
489 char *
490 parse_sort_criteria(char * sortCriteria, int * error)
492 char *order = NULL;
493 char *item, *saveptr;
494 int i, ret, reverse, title_sorted = 0;
495 *error = 0;
497 if( !sortCriteria )
498 return NULL;
500 if( (item = strtok_r(sortCriteria, ",", &saveptr)) )
502 order = malloc(4096);
503 strcpy(order, "order by ");
505 for( i=0; item != NULL; i++ )
507 reverse=0;
508 if( i )
509 strcat(order, ", ");
510 if( *item == '+' )
512 item++;
514 else if( *item == '-' )
516 reverse = 1;
517 item++;
519 if( strcasecmp(item, "upnp:class") == 0 )
521 strcat(order, "o.CLASS");
523 else if( strcasecmp(item, "dc:title") == 0 )
525 strcat(order, "d.TITLE");
526 title_sorted = 1;
528 else if( strcasecmp(item, "dc:date") == 0 )
530 strcat(order, "d.DATE");
532 else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 )
534 strcat(order, "d.DISC, d.TRACK");
536 else
538 printf("Unhandled SortCriteria [%s]\n", item);
539 *error = 1;
540 if( i )
542 ret = strlen(order);
543 order[ret-2] = '\0';
545 i--;
546 goto unhandled_order;
549 if( reverse )
550 strcat(order, " DESC");
551 unhandled_order:
552 item = strtok_r(NULL, ",", &saveptr);
554 if( i <= 0 )
556 free(order);
557 return NULL;
559 /* Add a "tiebreaker" sort order */
560 if( !title_sorted )
561 strcat(order, ", TITLE ASC");
563 return order;
566 inline static void
567 add_resized_res(int srcw, int srch, int reqw, int reqh, char *dlna_pn,
568 char *detailID, struct Response *args)
570 int dstw = reqw;
571 int dsth = reqh;
573 if( args->flags & FLAG_NO_RESIZE )
574 return;
576 strcatf(args->str, "&lt;res ");
577 if( args->filter & FILTER_RES_RESOLUTION )
579 dstw = reqw;
580 dsth = ((((reqw<<10)/srcw)*srch)>>10);
581 if( dsth > reqh ) {
582 dsth = reqh;
583 dstw = (((reqh<<10)/srch) * srcw>>10);
585 strcatf(args->str, "resolution=\"%dx%d\" ", dstw, dsth);
587 strcatf(args->str, "protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=%s;DLNA.ORG_CI=1\"&gt;"
588 "http://%s:%d/Resized/%s.jpg?width=%d,height=%d"
589 "&lt;/res&gt;",
590 dlna_pn, lan_addr[args->iface].str, runtime_vars.port,
591 detailID, dstw, dsth);
594 inline static void
595 add_res(char *size, char *duration, char *bitrate, char *sampleFrequency,
596 char *nrAudioChannels, char *resolution, char *dlna_pn, char *mime,
597 char *detailID, char *ext, struct Response *args)
599 strcatf(args->str, "&lt;res ");
600 if( size && (args->filter & FILTER_RES_SIZE) ) {
601 strcatf(args->str, "size=\"%s\" ", size);
603 if( duration && (args->filter & FILTER_RES_DURATION) ) {
604 strcatf(args->str, "duration=\"%s\" ", duration);
606 if( bitrate && (args->filter & FILTER_RES_BITRATE) ) {
607 strcatf(args->str, "bitrate=\"%s\" ", bitrate);
609 if( sampleFrequency && (args->filter & FILTER_RES_SAMPLEFREQUENCY) ) {
610 strcatf(args->str, "sampleFrequency=\"%s\" ", sampleFrequency);
612 if( nrAudioChannels && (args->filter & FILTER_RES_NRAUDIOCHANNELS) ) {
613 strcatf(args->str, "nrAudioChannels=\"%s\" ", nrAudioChannels);
615 if( resolution && (args->filter & FILTER_RES_RESOLUTION) ) {
616 strcatf(args->str, "resolution=\"%s\" ", resolution);
618 strcatf(args->str, "protocolInfo=\"http-get:*:%s:%s\"&gt;"
619 "http://%s:%d/MediaItems/%s.%s"
620 "&lt;/res&gt;",
621 mime, dlna_pn, lan_addr[args->iface].str,
622 runtime_vars.port, detailID, ext);
625 #define COLUMNS "o.REF_ID, o.DETAIL_ID, o.CLASS," \
626 " d.SIZE, d.TITLE, d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST," \
627 " d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RESOLUTION," \
628 " d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.DISC "
629 #define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, " COLUMNS
631 static int
632 callback(void *args, int argc, char **argv, char **azColName)
634 struct Response *passed_args = (struct Response *)args;
635 char *id = argv[0], *parent = argv[1], *refID = argv[2], *detailID = argv[3], *class = argv[4], *size = argv[5], *title = argv[6],
636 *duration = argv[7], *bitrate = argv[8], *sampleFrequency = argv[9], *artist = argv[10], *album = argv[11],
637 *genre = argv[12], *comment = argv[13], *nrAudioChannels = argv[14], *track = argv[15], *date = argv[16], *resolution = argv[17],
638 *tn = argv[18], *creator = argv[19], *dlna_pn = argv[20], *mime = argv[21], *album_art = argv[22];
639 char dlna_buf[96];
640 char ext[5];
641 struct string_s *str = passed_args->str;
642 int ret = 0;
644 /* Make sure we have at least 8KB left of allocated memory to finish the response. */
645 if( str->off > (str->size - 8192) )
647 #if MAX_RESPONSE_SIZE > 0
648 if( (str->size+DEFAULT_RESP_SIZE) <= MAX_RESPONSE_SIZE )
650 #endif
651 str->data = realloc(str->data, (str->size+DEFAULT_RESP_SIZE));
652 if( str->data )
654 str->size += DEFAULT_RESP_SIZE;
655 DPRINTF(E_DEBUG, L_HTTP, "UPnP SOAP response enlarged to %d. [%d results so far]\n",
656 str->size, passed_args->returned);
658 else
660 DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response was too big, and realloc failed!\n");
661 return -1;
663 #if MAX_RESPONSE_SIZE > 0
665 else
667 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);
668 return -1;
670 #endif
672 passed_args->returned++;
674 if( dlna_pn )
675 sprintf(dlna_buf, "DLNA.ORG_PN=%s", dlna_pn);
676 else if( passed_args->flags & FLAG_DLNA )
677 strcpy(dlna_buf, dlna_no_conv);
678 else
679 strcpy(dlna_buf, "*");
681 if( runtime_vars.root_container && strcmp(parent, runtime_vars.root_container) == 0 )
682 parent = "0";
684 if( strncmp(class, "item", 4) == 0 )
686 /* We may need special handling for certain MIME types */
687 if( *mime == 'v' )
689 if( passed_args->flags & FLAG_MIME_AVI_DIVX )
691 if( strcmp(mime, "video/x-msvideo") == 0 )
693 if( creator )
694 strcpy(mime+6, "divx");
695 else
696 strcpy(mime+6, "avi");
699 else if( passed_args->flags & FLAG_MIME_AVI_AVI )
701 if( strcmp(mime, "video/x-msvideo") == 0 )
703 strcpy(mime+6, "avi");
706 else if( passed_args->client == EFreeBox && dlna_pn )
708 if( strncmp(dlna_pn, "AVC_TS", 6) == 0 ||
709 strncmp(dlna_pn, "MPEG_TS", 7) == 0 )
711 strcpy(mime+6, "mp2t");
714 if( !(passed_args->flags & FLAG_DLNA) )
716 if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
718 strcpy(mime+6, "mpeg");
721 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
722 if( passed_args->flags & FLAG_SAMSUNG )
724 if( strcmp(mime+6, "x-matroska") == 0 )
726 strcpy(mime+8, "mkv");
730 else if( *mime == 'a' )
732 if( strcmp(mime+6, "x-flac") == 0 )
734 if( passed_args->flags & FLAG_MIME_FLAC_FLAC )
736 strcpy(mime+6, "flac");
741 ret = strcatf(str, "&lt;item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent);
742 if( refID && (passed_args->filter & FILTER_REFID) ) {
743 ret = strcatf(str, " refID=\"%s\"", refID);
745 ret = strcatf(str, "&gt;"
746 "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
747 "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
748 title, class);
749 if( comment && (passed_args->filter & FILTER_DC_DESCRIPTION) ) {
750 ret = strcatf(str, "&lt;dc:description&gt;%.384s&lt;/dc:description&gt;", comment);
752 if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
753 ret = strcatf(str, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
755 if( date && (passed_args->filter & FILTER_DC_DATE) ) {
756 ret = strcatf(str, "&lt;dc:date&gt;%s&lt;/dc:date&gt;", date);
758 if( passed_args->filter & FILTER_SEC_CAPTION_INFO_EX) {
759 /* Get bookmark */
760 ret = strcatf(str, "&lt;sec:dcmInfo&gt;CREATIONDATE=0,FOLDER=%s,BM=%d&lt;/sec:dcmInfo&gt;",
761 title, sql_get_int_field(db, "SELECT SEC from BOOKMARKS where ID = '%s'", detailID));
763 if( artist ) {
764 if( (*mime == 'v') && (passed_args->filter & FILTER_UPNP_ACTOR) ) {
765 ret = strcatf(str, "&lt;upnp:actor&gt;%s&lt;/upnp:actor&gt;", artist);
767 if( passed_args->filter & FILTER_UPNP_ARTIST ) {
768 ret = strcatf(str, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
771 if( album && (passed_args->filter & FILTER_UPNP_ALBUM) ) {
772 ret = strcatf(str, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", album);
774 if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
775 ret = strcatf(str, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
777 if( strncmp(id, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) {
778 track = strrchr(id, '$')+1;
780 if( track && atoi(track) && (passed_args->filter & FILTER_UPNP_ORIGINALTRACKNUMBER) ) {
781 ret = strcatf(str, "&lt;upnp:originalTrackNumber&gt;%s&lt;/upnp:originalTrackNumber&gt;", track);
783 if( album_art && atoi(album_art) )
785 /* Video and audio album art is handled differently */
786 if( *mime == 'v' && (passed_args->filter & FILTER_RES) && !(passed_args->flags & FLAG_MS_PFS) ) {
787 ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\"&gt;"
788 "http://%s:%d/AlbumArt/%s-%s.jpg"
789 "&lt;/res&gt;",
790 lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
791 } else if( passed_args->filter & FILTER_UPNP_ALBUMARTURI ) {
792 ret = strcatf(str, "&lt;upnp:albumArtURI");
793 if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
794 ret = strcatf(str, " dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"");
796 ret = strcatf(str, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg&lt;/upnp:albumArtURI&gt;",
797 lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
800 if( (passed_args->flags & FLAG_MS_PFS) && *mime == 'i' ) {
801 if( passed_args->client == EMediaRoom && !album )
802 ret = strcatf(str, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", "[No Keywords]");
804 /* EVA2000 doesn't seem to handle embedded thumbnails */
805 if( passed_args->client != ENetgearEVA2000 && tn && atoi(tn) ) {
806 ret = strcatf(str, "&lt;upnp:albumArtURI&gt;"
807 "http://%s:%d/Thumbnails/%s.jpg"
808 "&lt;/upnp:albumArtURI&gt;",
809 lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
810 } else {
811 ret = strcatf(str, "&lt;upnp:albumArtURI&gt;"
812 "http://%s:%d/Resized/%s.jpg?width=160,height=160"
813 "&lt;/upnp:albumArtURI&gt;",
814 lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
817 if( passed_args->filter & FILTER_RES ) {
818 mime_to_ext(mime, ext);
819 if( (passed_args->client == EFreeBox) && tn && atoi(tn) ) {
820 ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:%s:%s\"&gt;"
821 "http://%s:%d/Thumbnails/%s.jpg"
822 "&lt;/res&gt;",
823 mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[passed_args->iface].str,
824 runtime_vars.port, detailID);
826 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
827 resolution, dlna_buf, mime, detailID, ext, passed_args);
828 if( (*mime == 'i') && (passed_args->client != EFreeBox) ) {
829 int srcw = atoi(strsep(&resolution, "x"));
830 int srch = atoi(resolution);
831 if( !dlna_pn ) {
832 add_resized_res(srcw, srch, 4096, 4096, "JPEG_LRG", detailID, passed_args);
834 if( !dlna_pn || !strncmp(dlna_pn, "JPEG_L", 6) || !strncmp(dlna_pn, "JPEG_M", 6) ) {
835 add_resized_res(srcw, srch, 640, 480, "JPEG_SM", detailID, passed_args);
837 if( tn && atoi(tn) ) {
838 ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:%s:%s\"&gt;"
839 "http://%s:%d/Thumbnails/%s.jpg"
840 "&lt;/res&gt;",
841 mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[passed_args->iface].str,
842 runtime_vars.port, detailID);
845 else if( *mime == 'v' ) {
846 switch( passed_args->client ) {
847 case EToshibaTV:
848 if( dlna_pn &&
849 (strncmp(dlna_pn, "MPEG_TS_HD_NA", 13) == 0 ||
850 strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) == 0 ||
851 strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 ||
852 strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0))
854 sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
855 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
856 resolution, dlna_buf, mime, detailID, ext, passed_args);
858 break;
859 case ESonyBDP:
860 if( dlna_pn &&
861 (strncmp(dlna_pn, "AVC_TS", 6) == 0 ||
862 strncmp(dlna_pn, "MPEG_TS", 7) == 0) )
864 if( strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) != 0 )
866 sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
867 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
868 resolution, dlna_buf, mime, detailID, ext, passed_args);
870 if( strncmp(dlna_pn, "MPEG_TS_SD_EU", 13) != 0 )
872 sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
873 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
874 resolution, dlna_buf, mime, detailID, ext, passed_args);
877 else if( (dlna_pn &&
878 (strncmp(dlna_pn, "AVC_MP4", 7) == 0 ||
879 strncmp(dlna_pn, "MPEG4_P2_MP4", 12) == 0)) ||
880 strcmp(mime+6, "x-matroska") == 0 ||
881 strcmp(mime+6, "x-msvideo") == 0 ||
882 strcmp(mime+6, "mpeg") == 0 )
884 strcpy(mime+6, "avi");
885 if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_NTSC", 12) != 0 )
887 sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
888 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
889 resolution, dlna_buf, mime, detailID, ext, passed_args);
891 if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_PAL", 11) != 0 )
893 sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
894 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
895 resolution, dlna_buf, mime, detailID, ext, passed_args);
898 break;
899 case ESonyBravia:
900 /* BRAVIA KDL-##*X### series TVs do natively support AVC/AC3 in TS, but
901 require profile to be renamed (applies to _T and _ISO variants also) */
902 if( dlna_pn &&
903 (strncmp(dlna_pn, "AVC_TS_MP_SD_AC3", 16) == 0 ||
904 strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 ||
905 strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0))
907 sprintf(dlna_buf, "DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
908 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
909 resolution, dlna_buf, mime, detailID, ext, passed_args);
911 break;
912 case ELGDevice:
913 if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%s'", detailID) > 0 )
915 ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:text/srt:*\"&gt;"
916 "http://%s:%d/Captions/%s.srt"
917 "&lt;/res&gt;",
918 lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
920 break;
921 default:
922 break;
926 ret = strcatf(str, "&lt;/item&gt;");
928 else if( strncmp(class, "container", 9) == 0 )
930 ret = strcatf(str, "&lt;container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent);
931 if( passed_args->filter & FILTER_CHILDCOUNT )
933 int children;
934 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", id);
935 children = (ret > 0) ? ret : 0;
936 ret = strcatf(str, "childCount=\"%d\"", children);
938 /* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */
939 if( (passed_args->requested == 1) && (strcmp(id, "0") == 0) )
941 if( passed_args->filter & FILTER_UPNP_SEARCHCLASS )
943 ret = strcatf(str, "&gt;"
944 "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.audioItem&lt;/upnp:searchClass&gt;"
945 "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.imageItem&lt;/upnp:searchClass&gt;"
946 "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.videoItem&lt;/upnp:searchClass");
949 ret = strcatf(str, "&gt;"
950 "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
951 "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
952 title, class);
953 if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
954 ret = strcatf(str, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
956 if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
957 ret = strcatf(str, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
959 if( artist && (passed_args->filter & FILTER_UPNP_ARTIST) ) {
960 ret = strcatf(str, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
962 if( album_art && atoi(album_art) && (passed_args->filter & FILTER_UPNP_ALBUMARTURI) ) {
963 ret = strcatf(str, "&lt;upnp:albumArtURI ");
964 if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
965 ret = strcatf(str, "dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"");
967 ret = strcatf(str, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg&lt;/upnp:albumArtURI&gt;",
968 lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
970 ret = strcatf(str, "&lt;/container&gt;");
973 return 0;
976 static void
977 BrowseContentDirectory(struct upnphttp * h, const char * action)
979 static const char resp0[] =
980 "<u:BrowseResponse "
981 "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
982 "<Result>"
983 "&lt;DIDL-Lite"
984 CONTENT_DIRECTORY_SCHEMAS;
985 char *zErrMsg = 0;
986 char *sql, *ptr;
987 int ret;
988 struct Response args;
989 struct string_s str;
990 int totalMatches;
991 struct NameValueParserData data;
993 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
994 char * ObjectID = GetValueFromNameValueList(&data, "ObjectID");
995 char * Filter = GetValueFromNameValueList(&data, "Filter");
996 char * BrowseFlag = GetValueFromNameValueList(&data, "BrowseFlag");
997 char * SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
998 char * orderBy = NULL;
999 int RequestedCount = 0;
1000 int StartingIndex = 0;
1001 if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
1002 RequestedCount = atoi(ptr);
1003 if( !RequestedCount )
1004 RequestedCount = -1;
1005 if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
1006 StartingIndex = atoi(ptr);
1007 if( !BrowseFlag || (strcmp(BrowseFlag, "BrowseDirectChildren") && strcmp(BrowseFlag, "BrowseMetadata")) )
1009 SoapError(h, 402, "Invalid Args");
1010 goto browse_error;
1012 if( !ObjectID && !(ObjectID = GetValueFromNameValueList(&data, "ContainerID")) )
1014 SoapError(h, 701, "No such object error");
1015 goto browse_error;
1017 memset(&args, 0, sizeof(args));
1018 memset(&str, 0, sizeof(str));
1020 str.data = malloc(DEFAULT_RESP_SIZE);
1021 str.size = DEFAULT_RESP_SIZE;
1022 str.off = sprintf(str.data, "%s", resp0);
1023 /* See if we need to include DLNA namespace reference */
1024 args.iface = h->iface;
1025 args.filter = set_filter_flags(Filter, h);
1026 if( args.filter & FILTER_DLNA_NAMESPACE )
1028 ret = strcatf(&str, DLNA_NAMESPACE);
1030 strcatf(&str, "&gt;\n");
1032 args.returned = 0;
1033 args.requested = RequestedCount;
1034 args.client = h->req_client;
1035 args.flags = h->reqflags;
1036 args.str = &str;
1037 if( args.flags & FLAG_MS_PFS )
1039 if( !strchr(ObjectID, '$') && (strcmp(ObjectID, "0") != 0) )
1041 ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS"
1042 " where OBJECT_ID in "
1043 "('"MUSIC_ID"$%s', '"VIDEO_ID"$%s', '"IMAGE_ID"$%s')",
1044 ObjectID, ObjectID, ObjectID);
1045 if( ptr )
1047 ObjectID = ptr;
1048 args.flags |= FLAG_FREE_OBJECT_ID;
1052 DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n"
1053 " * ObjectID: %s\n"
1054 " * Count: %d\n"
1055 " * StartingIndex: %d\n"
1056 " * BrowseFlag: %s\n"
1057 " * Filter: %s\n"
1058 " * SortCriteria: %s\n",
1059 ObjectID, RequestedCount, StartingIndex,
1060 BrowseFlag, Filter, SortCriteria);
1062 if( strcmp(ObjectID, "0") == 0 )
1064 args.flags |= FLAG_ROOT_CONTAINER;
1065 if( runtime_vars.root_container )
1067 if( (args.flags & FLAG_AUDIO_ONLY) && (strcmp(runtime_vars.root_container, BROWSEDIR_ID) == 0) )
1068 ObjectID = MUSIC_DIR_ID;
1069 else
1070 ObjectID = runtime_vars.root_container;
1072 else
1074 if( args.flags & FLAG_AUDIO_ONLY )
1075 ObjectID = MUSIC_ID;
1079 if( strcmp(BrowseFlag+6, "Metadata") == 0 )
1081 args.requested = 1;
1082 sql = sqlite3_mprintf("SELECT %s, " COLUMNS
1083 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1084 " where OBJECT_ID = '%s';",
1085 (args.flags & FLAG_ROOT_CONTAINER) ? "0, -1" : "o.OBJECT_ID, o.PARENT_ID",
1086 ObjectID);
1087 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1088 totalMatches = args.returned;
1090 else
1092 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", ObjectID);
1093 totalMatches = (ret > 0) ? ret : 0;
1094 ret = 0;
1095 if( SortCriteria )
1097 #ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
1098 if( totalMatches < 10000 )
1099 #endif
1100 orderBy = parse_sort_criteria(SortCriteria, &ret);
1102 else
1104 if( strncmp(ObjectID, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 )
1106 if( strcmp(ObjectID, MUSIC_PLIST_ID) == 0 )
1107 asprintf(&orderBy, "order by d.TITLE");
1108 else
1109 asprintf(&orderBy, "order by length(OBJECT_ID), OBJECT_ID");
1111 else if( args.client == ERokuSoundBridge )
1113 #ifdef __sparc__
1114 if( totalMatches < 10000 )
1115 #endif
1116 asprintf(&orderBy, "order by o.CLASS, d.DISC, d.TRACK, d.TITLE");
1119 /* If it's a DLNA client, return an error for bad sort criteria */
1120 if( (args.flags & FLAG_DLNA) && ret )
1122 SoapError(h, 709, "Unsupported or invalid sort criteria");
1123 goto browse_error;
1126 sql = sqlite3_mprintf( SELECT_COLUMNS
1127 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1128 " where PARENT_ID = '%s' %s limit %d, %d;",
1129 ObjectID, orderBy, StartingIndex, RequestedCount);
1130 DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql);
1131 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1133 sqlite3_free(sql);
1134 if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
1136 DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1137 sqlite3_free(zErrMsg);
1139 /* Does the object even exist? */
1140 if( !totalMatches )
1142 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", ObjectID);
1143 if( ret <= 0 )
1145 SoapError(h, 701, "No such object error");
1146 goto browse_error;
1149 ret = strcatf(&str, "&lt;/DIDL-Lite&gt;</Result>\n"
1150 "<NumberReturned>%u</NumberReturned>\n"
1151 "<TotalMatches>%u</TotalMatches>\n"
1152 "<UpdateID>%u</UpdateID>"
1153 "</u:BrowseResponse>",
1154 args.returned, totalMatches, updateID);
1155 BuildSendAndCloseSoapResp(h, str.data, str.off);
1156 browse_error:
1157 ClearNameValueList(&data);
1158 if( args.flags & FLAG_FREE_OBJECT_ID )
1159 sqlite3_free(ObjectID);
1160 free(orderBy);
1161 free(str.data);
1164 static void
1165 SearchContentDirectory(struct upnphttp * h, const char * action)
1167 static const char resp0[] =
1168 "<u:SearchResponse "
1169 "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1170 "<Result>"
1171 "&lt;DIDL-Lite"
1172 CONTENT_DIRECTORY_SCHEMAS;
1173 char *zErrMsg = 0;
1174 char *sql, *ptr;
1175 char **result;
1176 struct Response args;
1177 struct string_s str;
1178 int totalMatches = 0;
1179 int ret;
1181 struct NameValueParserData data;
1182 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1183 char * ContainerID = GetValueFromNameValueList(&data, "ContainerID");
1184 char * Filter = GetValueFromNameValueList(&data, "Filter");
1185 char * SearchCriteria = GetValueFromNameValueList(&data, "SearchCriteria");
1186 char * SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
1187 char * newSearchCriteria = NULL;
1188 char * orderBy = NULL;
1189 char groupBy[] = "group by DETAIL_ID";
1190 int RequestedCount = 0;
1191 int StartingIndex = 0;
1192 if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
1193 RequestedCount = atoi(ptr);
1194 if( !RequestedCount )
1195 RequestedCount = -1;
1196 if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
1197 StartingIndex = atoi(ptr);
1198 if( !ContainerID )
1200 if( !(ContainerID = GetValueFromNameValueList(&data, "ObjectID")) )
1202 SoapError(h, 701, "No such object error");
1203 goto search_error;
1206 memset(&args, 0, sizeof(args));
1207 memset(&str, 0, sizeof(str));
1209 str.data = malloc(DEFAULT_RESP_SIZE);
1210 str.size = DEFAULT_RESP_SIZE;
1211 str.off = sprintf(str.data, "%s", resp0);
1212 /* See if we need to include DLNA namespace reference */
1213 args.iface = h->iface;
1214 args.filter = set_filter_flags(Filter, h);
1215 if( args.filter & FILTER_DLNA_NAMESPACE )
1217 ret = strcatf(&str, DLNA_NAMESPACE);
1219 strcatf(&str, "&gt;\n");
1221 args.returned = 0;
1222 args.requested = RequestedCount;
1223 args.client = h->req_client;
1224 args.flags = h->reqflags;
1225 args.str = &str;
1226 if( args.flags & FLAG_MS_PFS )
1228 if( !strchr(ContainerID, '$') && (strcmp(ContainerID, "0") != 0) )
1230 ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS"
1231 " where OBJECT_ID in "
1232 "('"MUSIC_ID"$%s', '"VIDEO_ID"$%s', '"IMAGE_ID"$%s')",
1233 ContainerID, ContainerID, ContainerID);
1234 if( ptr )
1236 ContainerID = ptr;
1237 args.flags |= FLAG_FREE_OBJECT_ID;
1240 #if 0 // Looks like the 360 already does this
1241 /* Sort by track number for some containers */
1242 if( orderBy &&
1243 ((strncmp(ContainerID, MUSIC_GENRE_ID, 3) == 0) ||
1244 (strncmp(ContainerID, MUSIC_ARTIST_ID, 3) == 0) ||
1245 (strncmp(ContainerID, MUSIC_ALBUM_ID, 3) == 0)) )
1247 DPRINTF(E_DEBUG, L_HTTP, "Old sort order: %s\n", orderBy);
1248 sprintf(str_buf, "d.TRACK, ");
1249 memmove(orderBy+18, orderBy+9, strlen(orderBy)+1);
1250 memmove(orderBy+9, &str_buf, 9);
1251 DPRINTF(E_DEBUG, L_HTTP, "New sort order: %s\n", orderBy);
1253 #endif
1255 DPRINTF(E_DEBUG, L_HTTP, "Searching ContentDirectory:\n"
1256 " * ObjectID: %s\n"
1257 " * Count: %d\n"
1258 " * StartingIndex: %d\n"
1259 " * SearchCriteria: %s\n"
1260 " * Filter: %s\n"
1261 " * SortCriteria: %s\n",
1262 ContainerID, RequestedCount, StartingIndex,
1263 SearchCriteria, Filter, SortCriteria);
1265 if( strcmp(ContainerID, "0") == 0 )
1266 *ContainerID = '*';
1267 else if( strcmp(ContainerID, MUSIC_ALL_ID) == 0 )
1268 groupBy[0] = '\0';
1269 if( !SearchCriteria )
1271 newSearchCriteria = strdup("1 = 1");
1272 SearchCriteria = newSearchCriteria;
1274 else
1276 SearchCriteria = modifyString(SearchCriteria, "&quot;", "\"", 0);
1277 SearchCriteria = modifyString(SearchCriteria, "&apos;", "'", 0);
1278 SearchCriteria = modifyString(SearchCriteria, "\\\"", "\"\"", 0);
1279 SearchCriteria = modifyString(SearchCriteria, "object.", "", 0);
1280 SearchCriteria = modifyString(SearchCriteria, "derivedfrom", "like", 1);
1281 SearchCriteria = modifyString(SearchCriteria, "contains", "like", 2);
1282 SearchCriteria = modifyString(SearchCriteria, "dc:title", "d.TITLE", 0);
1283 SearchCriteria = modifyString(SearchCriteria, "dc:creator", "d.CREATOR", 0);
1284 SearchCriteria = modifyString(SearchCriteria, "upnp:class", "o.CLASS", 0);
1285 SearchCriteria = modifyString(SearchCriteria, "upnp:actor", "d.ARTIST", 0);
1286 SearchCriteria = modifyString(SearchCriteria, "upnp:artist", "d.ARTIST", 0);
1287 SearchCriteria = modifyString(SearchCriteria, "upnp:album", "d.ALBUM", 0);
1288 SearchCriteria = modifyString(SearchCriteria, "upnp:genre", "d.GENRE", 0);
1289 SearchCriteria = modifyString(SearchCriteria, "exists true", "is not NULL", 0);
1290 SearchCriteria = modifyString(SearchCriteria, "exists false", "is NULL", 0);
1291 SearchCriteria = modifyString(SearchCriteria, "@refID", "REF_ID", 0);
1292 if( strstr(SearchCriteria, "@id") )
1294 newSearchCriteria = strdup(SearchCriteria);
1295 SearchCriteria = newSearchCriteria = modifyString(newSearchCriteria, "@id", "OBJECT_ID", 0);
1297 if( strstr(SearchCriteria, "res is ") )
1299 if( !newSearchCriteria )
1300 newSearchCriteria = strdup(SearchCriteria);
1301 SearchCriteria = newSearchCriteria = modifyString(newSearchCriteria, "res is ", "MIME is ", 0);
1303 #if 0 // Does 360 need this?
1304 if( strstr(SearchCriteria, "&amp;") )
1306 if( newSearchCriteria )
1307 newSearchCriteria = modifyString(newSearchCriteria, "&amp;", "&amp;amp;", 0);
1308 else
1309 newSearchCriteria = modifyString(strdup(SearchCriteria), "&amp;", "&amp;amp;", 0);
1310 SearchCriteria = newSearchCriteria;
1312 #endif
1314 DPRINTF(E_DEBUG, L_HTTP, "Translated SearchCriteria: %s\n", SearchCriteria);
1316 sql = sqlite3_mprintf("SELECT (select count(distinct DETAIL_ID)"
1317 " from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1318 " where (OBJECT_ID glob '%s$*') and (%s))"
1319 " + "
1320 "(select count(*) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1321 " where (OBJECT_ID = '%s') and (%s))",
1322 ContainerID, SearchCriteria, ContainerID, SearchCriteria);
1323 //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Count SQL: %s\n", sql);
1324 ret = sql_get_table(db, sql, &result, NULL, NULL);
1325 sqlite3_free(sql);
1326 if( ret == SQLITE_OK )
1328 totalMatches = atoi(result[1]);
1329 sqlite3_free_table(result);
1331 else
1333 /* Must be invalid SQL, so most likely bad or unhandled search criteria. */
1334 SoapError(h, 708, "Unsupported or invalid search criteria");
1335 goto search_error;
1337 /* Does the object even exist? */
1338 if( !totalMatches )
1340 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'",
1341 !strcmp(ContainerID, "*")?"0":ContainerID);
1342 if( ret <= 0 )
1344 SoapError(h, 710, "No such container");
1345 goto search_error;
1348 #ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
1349 ret = 0;
1350 if( totalMatches < 10000 )
1351 #endif
1352 orderBy = parse_sort_criteria(SortCriteria, &ret);
1353 /* If it's a DLNA client, return an error for bad sort criteria */
1354 if( (args.flags & FLAG_DLNA) && ret )
1356 SoapError(h, 709, "Unsupported or invalid sort criteria");
1357 goto search_error;
1360 sql = sqlite3_mprintf( SELECT_COLUMNS
1361 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1362 " where OBJECT_ID glob '%s$*' and (%s) %s "
1363 "%z %s"
1364 " limit %d, %d",
1365 ContainerID, SearchCriteria, groupBy,
1366 (*ContainerID == '*') ? NULL :
1367 sqlite3_mprintf("UNION ALL " SELECT_COLUMNS
1368 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1369 " where OBJECT_ID = '%s' and (%s) ", ContainerID, SearchCriteria),
1370 orderBy, StartingIndex, RequestedCount);
1371 DPRINTF(E_DEBUG, L_HTTP, "Search SQL: %s\n", sql);
1372 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1373 if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
1375 DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1376 sqlite3_free(zErrMsg);
1378 sqlite3_free(sql);
1379 ret = strcatf(&str, "&lt;/DIDL-Lite&gt;</Result>\n"
1380 "<NumberReturned>%u</NumberReturned>\n"
1381 "<TotalMatches>%u</TotalMatches>\n"
1382 "<UpdateID>%u</UpdateID>"
1383 "</u:SearchResponse>",
1384 args.returned, totalMatches, updateID);
1385 BuildSendAndCloseSoapResp(h, str.data, str.off);
1386 search_error:
1387 ClearNameValueList(&data);
1388 if( args.flags & FLAG_FREE_OBJECT_ID )
1389 sqlite3_free(ContainerID);
1390 free(orderBy);
1391 free(newSearchCriteria);
1392 free(str.data);
1396 If a control point calls QueryStateVariable on a state variable that is not
1397 buffered in memory within (or otherwise available from) the service,
1398 the service must return a SOAP fault with an errorCode of 404 Invalid Var.
1400 QueryStateVariable remains useful as a limited test tool but may not be
1401 part of some future versions of UPnP.
1403 static void
1404 QueryStateVariable(struct upnphttp * h, const char * action)
1406 static const char resp[] =
1407 "<u:%sResponse "
1408 "xmlns:u=\"%s\">"
1409 "<return>%s</return>"
1410 "</u:%sResponse>";
1412 char body[512];
1413 int bodylen;
1414 struct NameValueParserData data;
1415 const char * var_name;
1417 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1418 /*var_name = GetValueFromNameValueList(&data, "QueryStateVariable"); */
1419 /*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/
1420 var_name = GetValueFromNameValueList(&data, "varName");
1422 DPRINTF(E_INFO, L_HTTP, "QueryStateVariable(%.40s)\n", var_name);
1424 if(!var_name)
1426 SoapError(h, 402, "Invalid Args");
1428 else if(strcmp(var_name, "ConnectionStatus") == 0)
1430 bodylen = snprintf(body, sizeof(body), resp,
1431 action, "urn:schemas-upnp-org:control-1-0",
1432 "Connected", action);
1433 BuildSendAndCloseSoapResp(h, body, bodylen);
1435 else
1437 DPRINTF(E_WARN, L_HTTP, "%s: Unknown: %s\n", action, var_name?var_name:"");
1438 SoapError(h, 404, "Invalid Var");
1441 ClearNameValueList(&data);
1444 static void
1445 SamsungGetFeatureList(struct upnphttp * h, const char * action)
1447 static const char resp[] =
1448 "<u:X_GetFeatureListResponse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1449 "<FeatureList>"
1450 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
1451 "&lt;Features xmlns=\"urn:schemas-upnp-org:av:avs\""
1452 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
1453 " xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\"&gt;"
1454 "&lt;Feature name=\"samsung.com_BASICVIEW\" version=\"1\"&gt;"
1455 "&lt;container id=\"1\" type=\"object.item.audioItem\"/&gt;"
1456 "&lt;container id=\"2\" type=\"object.item.videoItem\"/&gt;"
1457 "&lt;container id=\"3\" type=\"object.item.imageItem\"/&gt;"
1458 "&lt;/Feature&gt;"
1459 "</FeatureList></u:X_GetFeatureListResponse>";
1461 BuildSendAndCloseSoapResp(h, resp, sizeof(resp)-1);
1464 static void
1465 SamsungSetBookmark(struct upnphttp * h, const char * action)
1467 static const char resp[] =
1468 "<u:X_SetBookmarkResponse"
1469 " xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1470 "</u:X_SetBookmarkResponse>";
1472 struct NameValueParserData data;
1473 char *ObjectID, *PosSecond;
1474 int ret;
1476 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1477 ObjectID = GetValueFromNameValueList(&data, "ObjectID");
1478 PosSecond = GetValueFromNameValueList(&data, "PosSecond");
1479 if( ObjectID && PosSecond )
1481 ret = sql_exec(db, "INSERT OR REPLACE into BOOKMARKS"
1482 " VALUES "
1483 "((select DETAIL_ID from OBJECTS where OBJECT_ID = '%s'), %s)", ObjectID, PosSecond);
1484 if( ret != SQLITE_OK )
1485 DPRINTF(E_WARN, L_METADATA, "Error setting bookmark %s on ObjectID='%s'\n", PosSecond, ObjectID);
1486 BuildSendAndCloseSoapResp(h, resp, sizeof(resp)-1);
1488 else
1489 SoapError(h, 402, "Invalid Args");
1491 ClearNameValueList(&data);
1494 static const struct
1496 const char * methodName;
1497 void (*methodImpl)(struct upnphttp *, const char *);
1499 soapMethods[] =
1501 { "QueryStateVariable", QueryStateVariable},
1502 { "Browse", BrowseContentDirectory},
1503 { "Search", SearchContentDirectory},
1504 { "GetSearchCapabilities", GetSearchCapabilities},
1505 { "GetSortCapabilities", GetSortCapabilities},
1506 { "GetSystemUpdateID", GetSystemUpdateID},
1507 { "GetProtocolInfo", GetProtocolInfo},
1508 { "GetCurrentConnectionIDs", GetCurrentConnectionIDs},
1509 { "GetCurrentConnectionInfo", GetCurrentConnectionInfo},
1510 { "IsAuthorized", IsAuthorizedValidated},
1511 { "IsValidated", IsAuthorizedValidated},
1512 { "X_GetFeatureList", SamsungGetFeatureList},
1513 { "X_SetBookmark", SamsungSetBookmark},
1514 { 0, 0 }
1517 void
1518 ExecuteSoapAction(struct upnphttp * h, const char * action, int n)
1520 char * p;
1522 p = strchr(action, '#');
1523 if(p)
1525 int i = 0;
1526 int len;
1527 int methodlen;
1528 char * p2;
1529 p++;
1530 p2 = strchr(p, '"');
1531 if(p2)
1532 methodlen = p2 - p;
1533 else
1534 methodlen = n - (p - action);
1535 DPRINTF(E_DEBUG, L_HTTP, "SoapMethod: %.*s\n", methodlen, p);
1536 while(soapMethods[i].methodName)
1538 len = strlen(soapMethods[i].methodName);
1539 if(strncmp(p, soapMethods[i].methodName, len) == 0)
1541 soapMethods[i].methodImpl(h, soapMethods[i].methodName);
1542 return;
1544 i++;
1547 DPRINTF(E_WARN, L_HTTP, "SoapMethod: Unknown: %.*s\n", methodlen, p);
1550 SoapError(h, 401, "Invalid Action");
1553 /* Standard Errors:
1555 * errorCode errorDescription Description
1556 * -------- ---------------- -----------
1557 * 401 Invalid Action No action by that name at this service.
1558 * 402 Invalid Args Could be any of the following: not enough in args,
1559 * too many in args, no in arg by that name,
1560 * one or more in args are of the wrong data type.
1561 * 403 Out of Sync Out of synchronization.
1562 * 501 Action Failed May be returned in current state of service
1563 * prevents invoking that action.
1564 * 600-699 TBD Common action errors. Defined by UPnP Forum
1565 * Technical Committee.
1566 * 700-799 TBD Action-specific errors for standard actions.
1567 * Defined by UPnP Forum working committee.
1568 * 800-899 TBD Action-specific errors for non-standard actions.
1569 * Defined by UPnP vendor.
1571 void
1572 SoapError(struct upnphttp * h, int errCode, const char * errDesc)
1574 static const char resp[] =
1575 "<s:Envelope "
1576 "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
1577 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1578 "<s:Body>"
1579 "<s:Fault>"
1580 "<faultcode>s:Client</faultcode>"
1581 "<faultstring>UPnPError</faultstring>"
1582 "<detail>"
1583 "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">"
1584 "<errorCode>%d</errorCode>"
1585 "<errorDescription>%s</errorDescription>"
1586 "</UPnPError>"
1587 "</detail>"
1588 "</s:Fault>"
1589 "</s:Body>"
1590 "</s:Envelope>";
1592 char body[2048];
1593 int bodylen;
1595 DPRINTF(E_WARN, L_HTTP, "Returning UPnPError %d: %s\n", errCode, errDesc);
1596 bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc);
1597 BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen);
1598 SendResp_upnphttp(h);
1599 CloseSocket_upnphttp(h);