Reinstate dropbear 0.54 update
[tomato.git] / release / src / router / minidlna / upnpsoap.c
blob2b2e8af07dec32463a25ac0d9405f6346c4b3c93
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:date,"
192 "dc:title,"
193 "upnp:album,"
194 "upnp:actor,"
195 "upnp:artist,"
196 "upnp:class,"
197 "upnp:genre,"
198 "@refID"
199 "</SearchCaps>"
200 "</u:%sResponse>";
202 char body[512];
203 int bodylen;
205 bodylen = snprintf(body, sizeof(body), resp,
206 action, "urn:schemas-upnp-org:service:ContentDirectory:1",
207 action);
208 BuildSendAndCloseSoapResp(h, body, bodylen);
211 static void
212 GetCurrentConnectionIDs(struct upnphttp * h, const char * action)
214 /* TODO: Use real data. - JM */
215 static const char resp[] =
216 "<u:%sResponse "
217 "xmlns:u=\"%s\">"
218 "<ConnectionIDs>0</ConnectionIDs>"
219 "</u:%sResponse>";
221 char body[512];
222 int bodylen;
224 bodylen = snprintf(body, sizeof(body), resp,
225 action, "urn:schemas-upnp-org:service:ConnectionManager:1",
226 action);
227 BuildSendAndCloseSoapResp(h, body, bodylen);
230 static void
231 GetCurrentConnectionInfo(struct upnphttp * h, const char * action)
233 /* TODO: Use real data. - JM */
234 static const char resp[] =
235 "<u:%sResponse "
236 "xmlns:u=\"%s\">"
237 "<RcsID>-1</RcsID>"
238 "<AVTransportID>-1</AVTransportID>"
239 "<ProtocolInfo></ProtocolInfo>"
240 "<PeerConnectionManager></PeerConnectionManager>"
241 "<PeerConnectionID>-1</PeerConnectionID>"
242 "<Direction>Output</Direction>"
243 "<Status>Unknown</Status>"
244 "</u:%sResponse>";
246 char body[sizeof(resp)+128];
247 int bodylen;
249 bodylen = snprintf(body, sizeof(body), resp,
250 action, "urn:schemas-upnp-org:service:ConnectionManager:1",
251 action);
252 BuildSendAndCloseSoapResp(h, body, bodylen);
255 static void
256 mime_to_ext(const char * mime, char * buf)
258 switch( *mime )
260 /* Audio extensions */
261 case 'a':
262 if( strcmp(mime+6, "mpeg") == 0 )
263 strcpy(buf, "mp3");
264 else if( strcmp(mime+6, "mp4") == 0 )
265 strcpy(buf, "m4a");
266 else if( strcmp(mime+6, "x-ms-wma") == 0 )
267 strcpy(buf, "wma");
268 else if( strcmp(mime+6, "x-flac") == 0 )
269 strcpy(buf, "flac");
270 else if( strcmp(mime+6, "flac") == 0 )
271 strcpy(buf, "flac");
272 else if( strcmp(mime+6, "x-wav") == 0 )
273 strcpy(buf, "wav");
274 else if( strncmp(mime+6, "L16", 3) == 0 )
275 strcpy(buf, "pcm");
276 else if( strcmp(mime+6, "3gpp") == 0 )
277 strcpy(buf, "3gp");
278 else if( strcmp(mime, "application/ogg") == 0 )
279 strcpy(buf, "ogg");
280 else
281 strcpy(buf, "dat");
282 break;
283 case 'v':
284 if( strcmp(mime+6, "avi") == 0 )
285 strcpy(buf, "avi");
286 else if( strcmp(mime+6, "divx") == 0 )
287 strcpy(buf, "avi");
288 else if( strcmp(mime+6, "x-msvideo") == 0 )
289 strcpy(buf, "avi");
290 else if( strcmp(mime+6, "mpeg") == 0 )
291 strcpy(buf, "mpg");
292 else if( strcmp(mime+6, "mp4") == 0 )
293 strcpy(buf, "mp4");
294 else if( strcmp(mime+6, "x-ms-wmv") == 0 )
295 strcpy(buf, "wmv");
296 else if( strcmp(mime+6, "x-matroska") == 0 )
297 strcpy(buf, "mkv");
298 else if( strcmp(mime+6, "x-mkv") == 0 )
299 strcpy(buf, "mkv");
300 else if( strcmp(mime+6, "x-flv") == 0 )
301 strcpy(buf, "flv");
302 else if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
303 strcpy(buf, "mpg");
304 else if( strcmp(mime+6, "quicktime") == 0 )
305 strcpy(buf, "mov");
306 else if( strcmp(mime+6, "3gpp") == 0 )
307 strcpy(buf, "3gp");
308 else if( strcmp(mime+6, "x-tivo-mpeg") == 0 )
309 strcpy(buf, "TiVo");
310 else
311 strcpy(buf, "dat");
312 break;
313 case 'i':
314 if( strcmp(mime+6, "jpeg") == 0 )
315 strcpy(buf, "jpg");
316 else if( strcmp(mime+6, "png") == 0 )
317 strcpy(buf, "png");
318 else
319 strcpy(buf, "dat");
320 break;
321 default:
322 strcpy(buf, "dat");
323 break;
327 #define FILTER_CHILDCOUNT 0x00000001
328 #define FILTER_DC_CREATOR 0x00000002
329 #define FILTER_DC_DATE 0x00000004
330 #define FILTER_DC_DESCRIPTION 0x00000008
331 #define FILTER_DLNA_NAMESPACE 0x00000010
332 #define FILTER_REFID 0x00000020
333 #define FILTER_RES 0x00000040
334 #define FILTER_RES_BITRATE 0x00000080
335 #define FILTER_RES_DURATION 0x00000100
336 #define FILTER_RES_NRAUDIOCHANNELS 0x00000200
337 #define FILTER_RES_RESOLUTION 0x00000400
338 #define FILTER_RES_SAMPLEFREQUENCY 0x00000800
339 #define FILTER_RES_SIZE 0x00001000
340 #define FILTER_UPNP_ACTOR 0x00002000
341 #define FILTER_UPNP_ALBUM 0x00004000
342 #define FILTER_UPNP_ALBUMARTURI 0x00008000
343 #define FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID 0x00010000
344 #define FILTER_UPNP_ARTIST 0x00020000
345 #define FILTER_UPNP_GENRE 0x00040000
346 #define FILTER_UPNP_ORIGINALTRACKNUMBER 0x00080000
347 #define FILTER_UPNP_SEARCHCLASS 0x00100000
348 #define FILTER_SEC 0x00200000
349 #define FILTER_SEC_CAPTION_INFO 0x00400000
350 #define FILTER_SEC_CAPTION_INFO_EX 0x00800000
352 static u_int32_t
353 set_filter_flags(char * filter, struct upnphttp *h)
355 char *item, *saveptr = NULL;
356 u_int32_t flags = 0;
358 if( !filter || (strlen(filter) <= 1) )
359 return 0xFFFFFFFF;
360 if( h->reqflags & FLAG_SAMSUNG )
361 flags |= FILTER_DLNA_NAMESPACE;
362 item = strtok_r(filter, ",", &saveptr);
363 while( item != NULL )
365 if( saveptr )
366 *(item-1) = ',';
367 while( isspace(*item) )
368 item++;
369 if( strcmp(item, "@childCount") == 0 )
371 flags |= FILTER_CHILDCOUNT;
373 else if( strcmp(item, "dc:creator") == 0 )
375 flags |= FILTER_DC_CREATOR;
377 else if( strcmp(item, "dc:date") == 0 )
379 flags |= FILTER_DC_DATE;
381 else if( strcmp(item, "dc:description") == 0 )
383 flags |= FILTER_DC_DESCRIPTION;
385 else if( strcmp(item, "dlna") == 0 )
387 flags |= FILTER_DLNA_NAMESPACE;
389 else if( strcmp(item, "@refID") == 0 )
391 flags |= FILTER_REFID;
393 else if( strcmp(item, "upnp:album") == 0 )
395 flags |= FILTER_UPNP_ALBUM;
397 else if( strcmp(item, "upnp:albumArtURI") == 0 )
399 flags |= FILTER_UPNP_ALBUMARTURI;
400 if( h->reqflags & FLAG_SAMSUNG )
401 flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID;
403 else if( strcmp(item, "upnp:albumArtURI@dlna:profileID") == 0 )
405 flags |= FILTER_UPNP_ALBUMARTURI;
406 flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID;
408 else if( strcmp(item, "upnp:artist") == 0 )
410 flags |= FILTER_UPNP_ARTIST;
412 else if( strcmp(item, "upnp:actor") == 0 )
414 flags |= FILTER_UPNP_ACTOR;
416 else if( strcmp(item, "upnp:genre") == 0 )
418 flags |= FILTER_UPNP_GENRE;
420 else if( strcmp(item, "upnp:originalTrackNumber") == 0 )
422 flags |= FILTER_UPNP_ORIGINALTRACKNUMBER;
424 else if( strcmp(item, "upnp:searchClass") == 0 )
426 flags |= FILTER_UPNP_SEARCHCLASS;
428 else if( strcmp(item, "res") == 0 )
430 flags |= FILTER_RES;
432 else if( (strcmp(item, "res@bitrate") == 0) ||
433 (strcmp(item, "@bitrate") == 0) ||
434 ((strcmp(item, "bitrate") == 0) && (flags & FILTER_RES)) )
436 flags |= FILTER_RES;
437 flags |= FILTER_RES_BITRATE;
439 else if( (strcmp(item, "res@duration") == 0) ||
440 (strcmp(item, "@duration") == 0) ||
441 ((strcmp(item, "duration") == 0) && (flags & FILTER_RES)) )
443 flags |= FILTER_RES;
444 flags |= FILTER_RES_DURATION;
446 else if( (strcmp(item, "res@nrAudioChannels") == 0) ||
447 (strcmp(item, "@nrAudioChannels") == 0) ||
448 ((strcmp(item, "nrAudioChannels") == 0) && (flags & FILTER_RES)) )
450 flags |= FILTER_RES;
451 flags |= FILTER_RES_NRAUDIOCHANNELS;
453 else if( (strcmp(item, "res@resolution") == 0) ||
454 (strcmp(item, "@resolution") == 0) ||
455 ((strcmp(item, "resolution") == 0) && (flags & FILTER_RES)) )
457 flags |= FILTER_RES;
458 flags |= FILTER_RES_RESOLUTION;
460 else if( (strcmp(item, "res@sampleFrequency") == 0) ||
461 (strcmp(item, "@sampleFrequency") == 0) ||
462 ((strcmp(item, "sampleFrequency") == 0) && (flags & FILTER_RES)) )
464 flags |= FILTER_RES;
465 flags |= FILTER_RES_SAMPLEFREQUENCY;
467 else if( (strcmp(item, "res@size") == 0) ||
468 (strcmp(item, "@size") == 0) ||
469 (strcmp(item, "size") == 0) )
471 flags |= FILTER_RES;
472 flags |= FILTER_RES_SIZE;
474 else if( strcmp(item, "sec:CaptionInfo") == 0)
476 flags |= FILTER_SEC;
477 flags |= FILTER_SEC_CAPTION_INFO;
479 else if( strcmp(item, "sec:CaptionInfoEx") == 0)
481 flags |= FILTER_SEC;
482 flags |= FILTER_SEC_CAPTION_INFO_EX;
484 item = strtok_r(NULL, ",", &saveptr);
487 return flags;
490 char *
491 parse_sort_criteria(char * sortCriteria, int * error)
493 char *order = NULL;
494 char *item, *saveptr;
495 int i, ret, reverse, title_sorted = 0;
496 *error = 0;
498 if( !sortCriteria )
499 return NULL;
501 if( (item = strtok_r(sortCriteria, ",", &saveptr)) )
503 order = malloc(4096);
504 strcpy(order, "order by ");
506 for( i=0; item != NULL; i++ )
508 reverse=0;
509 if( i )
510 strcat(order, ", ");
511 if( *item == '+' )
513 item++;
515 else if( *item == '-' )
517 reverse = 1;
518 item++;
520 if( strcasecmp(item, "upnp:class") == 0 )
522 strcat(order, "o.CLASS");
524 else if( strcasecmp(item, "dc:title") == 0 )
526 strcat(order, "d.TITLE");
527 title_sorted = 1;
529 else if( strcasecmp(item, "dc:date") == 0 )
531 strcat(order, "d.DATE");
533 else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 )
535 strcat(order, "d.DISC, d.TRACK");
537 else
539 printf("Unhandled SortCriteria [%s]\n", item);
540 *error = 1;
541 if( i )
543 ret = strlen(order);
544 order[ret-2] = '\0';
546 i--;
547 goto unhandled_order;
550 if( reverse )
551 strcat(order, " DESC");
552 unhandled_order:
553 item = strtok_r(NULL, ",", &saveptr);
555 if( i <= 0 )
557 free(order);
558 return NULL;
560 /* Add a "tiebreaker" sort order */
561 if( !title_sorted )
562 strcat(order, ", TITLE ASC");
564 return order;
567 inline static void
568 add_resized_res(int srcw, int srch, int reqw, int reqh, char *dlna_pn,
569 char *detailID, struct Response *args)
571 int dstw = reqw;
572 int dsth = reqh;
574 if( args->flags & FLAG_NO_RESIZE )
575 return;
577 strcatf(args->str, "&lt;res ");
578 if( args->filter & FILTER_RES_RESOLUTION )
580 dstw = reqw;
581 dsth = ((((reqw<<10)/srcw)*srch)>>10);
582 if( dsth > reqh ) {
583 dsth = reqh;
584 dstw = (((reqh<<10)/srch) * srcw>>10);
586 strcatf(args->str, "resolution=\"%dx%d\" ", dstw, dsth);
588 strcatf(args->str, "protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=%s;DLNA.ORG_CI=1\"&gt;"
589 "http://%s:%d/Resized/%s.jpg?width=%d,height=%d"
590 "&lt;/res&gt;",
591 dlna_pn, lan_addr[args->iface].str, runtime_vars.port,
592 detailID, dstw, dsth);
595 inline static void
596 add_res(char *size, char *duration, char *bitrate, char *sampleFrequency,
597 char *nrAudioChannels, char *resolution, char *dlna_pn, char *mime,
598 char *detailID, char *ext, struct Response *args)
600 strcatf(args->str, "&lt;res ");
601 if( size && (args->filter & FILTER_RES_SIZE) ) {
602 strcatf(args->str, "size=\"%s\" ", size);
604 if( duration && (args->filter & FILTER_RES_DURATION) ) {
605 strcatf(args->str, "duration=\"%s\" ", duration);
607 if( bitrate && (args->filter & FILTER_RES_BITRATE) ) {
608 int br = atoi(bitrate);
609 if(args->flags & FLAG_MS_PFS)
610 br /= 8;
611 strcatf(args->str, "bitrate=\"%d\" ", br);
613 if( sampleFrequency && (args->filter & FILTER_RES_SAMPLEFREQUENCY) ) {
614 strcatf(args->str, "sampleFrequency=\"%s\" ", sampleFrequency);
616 if( nrAudioChannels && (args->filter & FILTER_RES_NRAUDIOCHANNELS) ) {
617 strcatf(args->str, "nrAudioChannels=\"%s\" ", nrAudioChannels);
619 if( resolution && (args->filter & FILTER_RES_RESOLUTION) ) {
620 strcatf(args->str, "resolution=\"%s\" ", resolution);
622 strcatf(args->str, "protocolInfo=\"http-get:*:%s:%s\"&gt;"
623 "http://%s:%d/MediaItems/%s.%s"
624 "&lt;/res&gt;",
625 mime, dlna_pn, lan_addr[args->iface].str,
626 runtime_vars.port, detailID, ext);
629 #define COLUMNS "o.REF_ID, o.DETAIL_ID, o.CLASS," \
630 " d.SIZE, d.TITLE, d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST," \
631 " d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RESOLUTION," \
632 " d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.DISC "
633 #define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, " COLUMNS
635 static int
636 callback(void *args, int argc, char **argv, char **azColName)
638 struct Response *passed_args = (struct Response *)args;
639 char *id = argv[0], *parent = argv[1], *refID = argv[2], *detailID = argv[3], *class = argv[4], *size = argv[5], *title = argv[6],
640 *duration = argv[7], *bitrate = argv[8], *sampleFrequency = argv[9], *artist = argv[10], *album = argv[11],
641 *genre = argv[12], *comment = argv[13], *nrAudioChannels = argv[14], *track = argv[15], *date = argv[16], *resolution = argv[17],
642 *tn = argv[18], *creator = argv[19], *dlna_pn = argv[20], *mime = argv[21], *album_art = argv[22];
643 char dlna_buf[96];
644 char ext[5];
645 struct string_s *str = passed_args->str;
646 int ret = 0;
648 /* Make sure we have at least 8KB left of allocated memory to finish the response. */
649 if( str->off > (str->size - 8192) )
651 #if MAX_RESPONSE_SIZE > 0
652 if( (str->size+DEFAULT_RESP_SIZE) <= MAX_RESPONSE_SIZE )
654 #endif
655 str->data = realloc(str->data, (str->size+DEFAULT_RESP_SIZE));
656 if( str->data )
658 str->size += DEFAULT_RESP_SIZE;
659 DPRINTF(E_DEBUG, L_HTTP, "UPnP SOAP response enlarged to %d. [%d results so far]\n",
660 str->size, passed_args->returned);
662 else
664 DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response was too big, and realloc failed!\n");
665 return -1;
667 #if MAX_RESPONSE_SIZE > 0
669 else
671 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);
672 return -1;
674 #endif
676 passed_args->returned++;
678 if( dlna_pn )
679 sprintf(dlna_buf, "DLNA.ORG_PN=%s", dlna_pn);
680 else if( passed_args->flags & FLAG_DLNA )
681 strcpy(dlna_buf, dlna_no_conv);
682 else
683 strcpy(dlna_buf, "*");
685 if( runtime_vars.root_container && strcmp(parent, runtime_vars.root_container) == 0 )
686 parent = "0";
688 if( strncmp(class, "item", 4) == 0 )
690 /* We may need special handling for certain MIME types */
691 if( *mime == 'v' )
693 if( passed_args->flags & FLAG_MIME_AVI_DIVX )
695 if( strcmp(mime, "video/x-msvideo") == 0 )
697 if( creator )
698 strcpy(mime+6, "divx");
699 else
700 strcpy(mime+6, "avi");
703 else if( passed_args->flags & FLAG_MIME_AVI_AVI )
705 if( strcmp(mime, "video/x-msvideo") == 0 )
707 strcpy(mime+6, "avi");
710 else if( passed_args->client == EFreeBox && dlna_pn )
712 if( strncmp(dlna_pn, "AVC_TS", 6) == 0 ||
713 strncmp(dlna_pn, "MPEG_TS", 7) == 0 )
715 strcpy(mime+6, "mp2t");
718 if( !(passed_args->flags & FLAG_DLNA) )
720 if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
722 strcpy(mime+6, "mpeg");
725 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
726 if( passed_args->flags & FLAG_SAMSUNG )
728 if( strcmp(mime+6, "x-matroska") == 0 )
730 strcpy(mime+8, "mkv");
734 else if( *mime == 'a' )
736 if( strcmp(mime+6, "x-flac") == 0 )
738 if( passed_args->flags & FLAG_MIME_FLAC_FLAC )
740 strcpy(mime+6, "flac");
743 else if( strcmp(mime+6, "x-wav") == 0 )
745 if( passed_args->flags & FLAG_MIME_WAV_WAV )
747 strcpy(mime+6, "wav");
752 ret = strcatf(str, "&lt;item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent);
753 if( refID && (passed_args->filter & FILTER_REFID) ) {
754 ret = strcatf(str, " refID=\"%s\"", refID);
756 ret = strcatf(str, "&gt;"
757 "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
758 "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
759 title, class);
760 if( comment && (passed_args->filter & FILTER_DC_DESCRIPTION) ) {
761 ret = strcatf(str, "&lt;dc:description&gt;%.384s&lt;/dc:description&gt;", comment);
763 if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
764 ret = strcatf(str, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
766 if( date && (passed_args->filter & FILTER_DC_DATE) ) {
767 ret = strcatf(str, "&lt;dc:date&gt;%s&lt;/dc:date&gt;", date);
769 if( (passed_args->flags & FLAG_SAMSUNG) && (passed_args->filter & FILTER_SEC_CAPTION_INFO_EX) ) {
770 /* Get bookmark */
771 ret = strcatf(str, "&lt;sec:dcmInfo&gt;CREATIONDATE=0,FOLDER=%s,BM=%d&lt;/sec:dcmInfo&gt;",
772 title, sql_get_int_field(db, "SELECT SEC from BOOKMARKS where ID = '%s'", detailID));
774 if( artist ) {
775 if( (*mime == 'v') && (passed_args->filter & FILTER_UPNP_ACTOR) ) {
776 ret = strcatf(str, "&lt;upnp:actor&gt;%s&lt;/upnp:actor&gt;", artist);
778 if( passed_args->filter & FILTER_UPNP_ARTIST ) {
779 ret = strcatf(str, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
782 if( album && (passed_args->filter & FILTER_UPNP_ALBUM) ) {
783 ret = strcatf(str, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", album);
785 if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
786 ret = strcatf(str, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
788 if( strncmp(id, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) {
789 track = strrchr(id, '$')+1;
791 if( track && atoi(track) && (passed_args->filter & FILTER_UPNP_ORIGINALTRACKNUMBER) ) {
792 ret = strcatf(str, "&lt;upnp:originalTrackNumber&gt;%s&lt;/upnp:originalTrackNumber&gt;", track);
794 if( album_art && atoi(album_art) )
796 /* Video and audio album art is handled differently */
797 if( *mime == 'v' && (passed_args->filter & FILTER_RES) && !(passed_args->flags & FLAG_MS_PFS) ) {
798 ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\"&gt;"
799 "http://%s:%d/AlbumArt/%s-%s.jpg"
800 "&lt;/res&gt;",
801 lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
802 } else if( passed_args->filter & FILTER_UPNP_ALBUMARTURI ) {
803 ret = strcatf(str, "&lt;upnp:albumArtURI");
804 if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
805 ret = strcatf(str, " dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"");
807 ret = strcatf(str, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg&lt;/upnp:albumArtURI&gt;",
808 lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
811 if( (passed_args->flags & FLAG_MS_PFS) && *mime == 'i' ) {
812 if( passed_args->client == EMediaRoom && !album )
813 ret = strcatf(str, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", "[No Keywords]");
815 /* EVA2000 doesn't seem to handle embedded thumbnails */
816 if( passed_args->client != ENetgearEVA2000 && tn && atoi(tn) ) {
817 ret = strcatf(str, "&lt;upnp:albumArtURI&gt;"
818 "http://%s:%d/Thumbnails/%s.jpg"
819 "&lt;/upnp:albumArtURI&gt;",
820 lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
821 } else {
822 ret = strcatf(str, "&lt;upnp:albumArtURI&gt;"
823 "http://%s:%d/Resized/%s.jpg?width=160,height=160"
824 "&lt;/upnp:albumArtURI&gt;",
825 lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
828 if( passed_args->filter & FILTER_RES ) {
829 mime_to_ext(mime, ext);
830 if( (passed_args->client == EFreeBox) && tn && atoi(tn) ) {
831 ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:%s:%s\"&gt;"
832 "http://%s:%d/Thumbnails/%s.jpg"
833 "&lt;/res&gt;",
834 mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[passed_args->iface].str,
835 runtime_vars.port, detailID);
837 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
838 resolution, dlna_buf, mime, detailID, ext, passed_args);
839 if( (*mime == 'i') && (passed_args->client != EFreeBox) ) {
840 int srcw = atoi(strsep(&resolution, "x"));
841 int srch = atoi(resolution);
842 if( !dlna_pn ) {
843 add_resized_res(srcw, srch, 4096, 4096, "JPEG_LRG", detailID, passed_args);
845 if( !dlna_pn || !strncmp(dlna_pn, "JPEG_L", 6) || !strncmp(dlna_pn, "JPEG_M", 6) ) {
846 add_resized_res(srcw, srch, 640, 480, "JPEG_SM", detailID, passed_args);
848 if( tn && atoi(tn) ) {
849 ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:%s:%s\"&gt;"
850 "http://%s:%d/Thumbnails/%s.jpg"
851 "&lt;/res&gt;",
852 mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[passed_args->iface].str,
853 runtime_vars.port, detailID);
856 else if( *mime == 'v' ) {
857 switch( passed_args->client ) {
858 case EToshibaTV:
859 if( dlna_pn &&
860 (strncmp(dlna_pn, "MPEG_TS_HD_NA", 13) == 0 ||
861 strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) == 0 ||
862 strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 ||
863 strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0))
865 sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
866 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
867 resolution, dlna_buf, mime, detailID, ext, passed_args);
869 break;
870 case ESonyBDP:
871 if( dlna_pn &&
872 (strncmp(dlna_pn, "AVC_TS", 6) == 0 ||
873 strncmp(dlna_pn, "MPEG_TS", 7) == 0) )
875 if( strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) != 0 )
877 sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
878 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
879 resolution, dlna_buf, mime, detailID, ext, passed_args);
881 if( strncmp(dlna_pn, "MPEG_TS_SD_EU", 13) != 0 )
883 sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
884 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
885 resolution, dlna_buf, mime, detailID, ext, passed_args);
888 else if( (dlna_pn &&
889 (strncmp(dlna_pn, "AVC_MP4", 7) == 0 ||
890 strncmp(dlna_pn, "MPEG4_P2_MP4", 12) == 0)) ||
891 strcmp(mime+6, "x-matroska") == 0 ||
892 strcmp(mime+6, "x-msvideo") == 0 ||
893 strcmp(mime+6, "mpeg") == 0 )
895 strcpy(mime+6, "avi");
896 if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_NTSC", 12) != 0 )
898 sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
899 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
900 resolution, dlna_buf, mime, detailID, ext, passed_args);
902 if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_PAL", 11) != 0 )
904 sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
905 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
906 resolution, dlna_buf, mime, detailID, ext, passed_args);
909 break;
910 case ESonyBravia:
911 /* BRAVIA KDL-##*X### series TVs do natively support AVC/AC3 in TS, but
912 require profile to be renamed (applies to _T and _ISO variants also) */
913 if( dlna_pn &&
914 (strncmp(dlna_pn, "AVC_TS_MP_SD_AC3", 16) == 0 ||
915 strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 ||
916 strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0))
918 sprintf(dlna_buf, "DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
919 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
920 resolution, dlna_buf, mime, detailID, ext, passed_args);
922 break;
923 case ELGDevice:
924 if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%s'", detailID) > 0 )
926 ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:text/srt:*\"&gt;"
927 "http://%s:%d/Captions/%s.srt"
928 "&lt;/res&gt;",
929 lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
931 break;
932 default:
933 break;
937 ret = strcatf(str, "&lt;/item&gt;");
939 else if( strncmp(class, "container", 9) == 0 )
941 ret = strcatf(str, "&lt;container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent);
942 if( passed_args->filter & FILTER_CHILDCOUNT )
944 int children;
945 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", id);
946 children = (ret > 0) ? ret : 0;
947 ret = strcatf(str, "childCount=\"%d\"", children);
949 /* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */
950 if( (passed_args->requested == 1) && (strcmp(id, "0") == 0) )
952 if( passed_args->filter & FILTER_UPNP_SEARCHCLASS )
954 ret = strcatf(str, "&gt;"
955 "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.audioItem&lt;/upnp:searchClass&gt;"
956 "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.imageItem&lt;/upnp:searchClass&gt;"
957 "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.videoItem&lt;/upnp:searchClass");
960 ret = strcatf(str, "&gt;"
961 "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
962 "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
963 title, class);
964 if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
965 ret = strcatf(str, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
967 if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
968 ret = strcatf(str, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
970 if( artist && (passed_args->filter & FILTER_UPNP_ARTIST) ) {
971 ret = strcatf(str, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
973 if( album_art && atoi(album_art) && (passed_args->filter & FILTER_UPNP_ALBUMARTURI) ) {
974 ret = strcatf(str, "&lt;upnp:albumArtURI ");
975 if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
976 ret = strcatf(str, "dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"");
978 ret = strcatf(str, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg&lt;/upnp:albumArtURI&gt;",
979 lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
981 ret = strcatf(str, "&lt;/container&gt;");
984 return 0;
987 static void
988 BrowseContentDirectory(struct upnphttp * h, const char * action)
990 static const char resp0[] =
991 "<u:BrowseResponse "
992 "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
993 "<Result>"
994 "&lt;DIDL-Lite"
995 CONTENT_DIRECTORY_SCHEMAS;
996 char *zErrMsg = NULL;
997 char *sql, *ptr;
998 struct Response args;
999 struct string_s str;
1000 int totalMatches;
1001 int ret;
1002 char *ObjectID, *Filter, *BrowseFlag, *SortCriteria;
1003 char *orderBy = NULL;
1004 struct NameValueParserData data;
1005 int RequestedCount = 0;
1006 int StartingIndex = 0;
1008 memset(&args, 0, sizeof(args));
1009 memset(&str, 0, sizeof(str));
1011 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1013 ObjectID = GetValueFromNameValueList(&data, "ObjectID");
1014 Filter = GetValueFromNameValueList(&data, "Filter");
1015 BrowseFlag = GetValueFromNameValueList(&data, "BrowseFlag");
1016 SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
1018 if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
1019 RequestedCount = atoi(ptr);
1020 if( !RequestedCount )
1021 RequestedCount = -1;
1022 if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
1023 StartingIndex = atoi(ptr);
1024 if( !BrowseFlag || (strcmp(BrowseFlag, "BrowseDirectChildren") && strcmp(BrowseFlag, "BrowseMetadata")) )
1026 SoapError(h, 402, "Invalid Args");
1027 goto browse_error;
1029 if( !ObjectID && !(ObjectID = GetValueFromNameValueList(&data, "ContainerID")) )
1031 SoapError(h, 701, "No such object error");
1032 goto browse_error;
1035 str.data = malloc(DEFAULT_RESP_SIZE);
1036 str.size = DEFAULT_RESP_SIZE;
1037 str.off = sprintf(str.data, "%s", resp0);
1038 /* See if we need to include DLNA namespace reference */
1039 args.iface = h->iface;
1040 args.filter = set_filter_flags(Filter, h);
1041 if( args.filter & FILTER_DLNA_NAMESPACE )
1043 ret = strcatf(&str, DLNA_NAMESPACE);
1045 strcatf(&str, "&gt;\n");
1047 args.returned = 0;
1048 args.requested = RequestedCount;
1049 args.client = h->req_client;
1050 args.flags = h->reqflags;
1051 args.str = &str;
1052 if( args.flags & FLAG_MS_PFS )
1054 if( !strchr(ObjectID, '$') && (strcmp(ObjectID, "0") != 0) )
1056 ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS"
1057 " where OBJECT_ID in "
1058 "('"MUSIC_ID"$%s', '"VIDEO_ID"$%s', '"IMAGE_ID"$%s')",
1059 ObjectID, ObjectID, ObjectID);
1060 if( ptr )
1062 ObjectID = ptr;
1063 args.flags |= FLAG_FREE_OBJECT_ID;
1067 DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n"
1068 " * ObjectID: %s\n"
1069 " * Count: %d\n"
1070 " * StartingIndex: %d\n"
1071 " * BrowseFlag: %s\n"
1072 " * Filter: %s\n"
1073 " * SortCriteria: %s\n",
1074 ObjectID, RequestedCount, StartingIndex,
1075 BrowseFlag, Filter, SortCriteria);
1077 if( strcmp(ObjectID, "0") == 0 )
1079 args.flags |= FLAG_ROOT_CONTAINER;
1080 if( runtime_vars.root_container )
1082 if( (args.flags & FLAG_AUDIO_ONLY) && (strcmp(runtime_vars.root_container, BROWSEDIR_ID) == 0) )
1083 ObjectID = MUSIC_DIR_ID;
1084 else
1085 ObjectID = runtime_vars.root_container;
1087 else
1089 if( args.flags & FLAG_AUDIO_ONLY )
1090 ObjectID = MUSIC_ID;
1094 if( strcmp(BrowseFlag+6, "Metadata") == 0 )
1096 args.requested = 1;
1097 sql = sqlite3_mprintf("SELECT %s, " COLUMNS
1098 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1099 " where OBJECT_ID = '%s';",
1100 (args.flags & FLAG_ROOT_CONTAINER) ? "0, -1" : "o.OBJECT_ID, o.PARENT_ID",
1101 ObjectID);
1102 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1103 totalMatches = args.returned;
1105 else
1107 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", ObjectID);
1108 totalMatches = (ret > 0) ? ret : 0;
1109 ret = 0;
1110 if( SortCriteria )
1112 #ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
1113 if( totalMatches < 10000 )
1114 #endif
1115 orderBy = parse_sort_criteria(SortCriteria, &ret);
1117 else
1119 if( strncmp(ObjectID, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 )
1121 if( strcmp(ObjectID, MUSIC_PLIST_ID) == 0 )
1122 asprintf(&orderBy, "order by d.TITLE");
1123 else
1124 asprintf(&orderBy, "order by length(OBJECT_ID), OBJECT_ID");
1126 else if( args.client == ERokuSoundBridge )
1128 #ifdef __sparc__
1129 if( totalMatches < 10000 )
1130 #endif
1131 asprintf(&orderBy, "order by o.CLASS, d.DISC, d.TRACK, d.TITLE");
1134 /* If it's a DLNA client, return an error for bad sort criteria */
1135 if( (args.flags & FLAG_DLNA) && ret )
1137 SoapError(h, 709, "Unsupported or invalid sort criteria");
1138 goto browse_error;
1141 sql = sqlite3_mprintf( SELECT_COLUMNS
1142 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1143 " where PARENT_ID = '%s' %s limit %d, %d;",
1144 ObjectID, orderBy, StartingIndex, RequestedCount);
1145 DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql);
1146 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1148 sqlite3_free(sql);
1149 if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
1151 DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1152 sqlite3_free(zErrMsg);
1154 /* Does the object even exist? */
1155 if( !totalMatches )
1157 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", ObjectID);
1158 if( ret <= 0 )
1160 SoapError(h, 701, "No such object error");
1161 goto browse_error;
1164 ret = strcatf(&str, "&lt;/DIDL-Lite&gt;</Result>\n"
1165 "<NumberReturned>%u</NumberReturned>\n"
1166 "<TotalMatches>%u</TotalMatches>\n"
1167 "<UpdateID>%u</UpdateID>"
1168 "</u:BrowseResponse>",
1169 args.returned, totalMatches, updateID);
1170 BuildSendAndCloseSoapResp(h, str.data, str.off);
1171 browse_error:
1172 ClearNameValueList(&data);
1173 if( args.flags & FLAG_FREE_OBJECT_ID )
1174 sqlite3_free(ObjectID);
1175 free(orderBy);
1176 free(str.data);
1179 static void
1180 SearchContentDirectory(struct upnphttp * h, const char * action)
1182 static const char resp0[] =
1183 "<u:SearchResponse "
1184 "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1185 "<Result>"
1186 "&lt;DIDL-Lite"
1187 CONTENT_DIRECTORY_SCHEMAS;
1188 char *zErrMsg = NULL;
1189 char *sql, *ptr;
1190 struct Response args;
1191 struct string_s str;
1192 int totalMatches;
1193 int ret;
1194 char *ContainerID, *Filter, *SearchCriteria, *SortCriteria;
1195 char *newSearchCriteria = NULL, *orderBy = NULL;
1196 char groupBy[] = "group by DETAIL_ID";
1197 struct NameValueParserData data;
1198 int RequestedCount = 0;
1199 int StartingIndex = 0;
1201 memset(&args, 0, sizeof(args));
1202 memset(&str, 0, sizeof(str));
1204 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1206 ContainerID = GetValueFromNameValueList(&data, "ContainerID");
1207 Filter = GetValueFromNameValueList(&data, "Filter");
1208 SearchCriteria = GetValueFromNameValueList(&data, "SearchCriteria");
1209 SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
1211 if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
1212 RequestedCount = atoi(ptr);
1213 if( !RequestedCount )
1214 RequestedCount = -1;
1215 if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
1216 StartingIndex = atoi(ptr);
1217 if( !ContainerID )
1219 if( !(ContainerID = GetValueFromNameValueList(&data, "ObjectID")) )
1221 SoapError(h, 701, "No such object error");
1222 goto search_error;
1226 str.data = malloc(DEFAULT_RESP_SIZE);
1227 str.size = DEFAULT_RESP_SIZE;
1228 str.off = sprintf(str.data, "%s", resp0);
1229 /* See if we need to include DLNA namespace reference */
1230 args.iface = h->iface;
1231 args.filter = set_filter_flags(Filter, h);
1232 if( args.filter & FILTER_DLNA_NAMESPACE )
1234 ret = strcatf(&str, DLNA_NAMESPACE);
1236 strcatf(&str, "&gt;\n");
1238 args.returned = 0;
1239 args.requested = RequestedCount;
1240 args.client = h->req_client;
1241 args.flags = h->reqflags;
1242 args.str = &str;
1243 if( args.flags & FLAG_MS_PFS )
1245 if( !strchr(ContainerID, '$') && (strcmp(ContainerID, "0") != 0) )
1247 ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS"
1248 " where OBJECT_ID in "
1249 "('"MUSIC_ID"$%s', '"VIDEO_ID"$%s', '"IMAGE_ID"$%s')",
1250 ContainerID, ContainerID, ContainerID);
1251 if( ptr )
1253 ContainerID = ptr;
1254 args.flags |= FLAG_FREE_OBJECT_ID;
1257 #if 0 // Looks like the 360 already does this
1258 /* Sort by track number for some containers */
1259 if( orderBy &&
1260 ((strncmp(ContainerID, MUSIC_GENRE_ID, 3) == 0) ||
1261 (strncmp(ContainerID, MUSIC_ARTIST_ID, 3) == 0) ||
1262 (strncmp(ContainerID, MUSIC_ALBUM_ID, 3) == 0)) )
1264 DPRINTF(E_DEBUG, L_HTTP, "Old sort order: %s\n", orderBy);
1265 sprintf(str_buf, "d.TRACK, ");
1266 memmove(orderBy+18, orderBy+9, strlen(orderBy)+1);
1267 memmove(orderBy+9, &str_buf, 9);
1268 DPRINTF(E_DEBUG, L_HTTP, "New sort order: %s\n", orderBy);
1270 #endif
1272 DPRINTF(E_DEBUG, L_HTTP, "Searching ContentDirectory:\n"
1273 " * ObjectID: %s\n"
1274 " * Count: %d\n"
1275 " * StartingIndex: %d\n"
1276 " * SearchCriteria: %s\n"
1277 " * Filter: %s\n"
1278 " * SortCriteria: %s\n",
1279 ContainerID, RequestedCount, StartingIndex,
1280 SearchCriteria, Filter, SortCriteria);
1282 if( strcmp(ContainerID, "0") == 0 )
1283 *ContainerID = '*';
1284 else if( strcmp(ContainerID, MUSIC_ALL_ID) == 0 )
1285 groupBy[0] = '\0';
1286 if( !SearchCriteria )
1288 newSearchCriteria = strdup("1 = 1");
1289 SearchCriteria = newSearchCriteria;
1291 else
1293 SearchCriteria = modifyString(SearchCriteria, "&quot;", "\"", 0);
1294 SearchCriteria = modifyString(SearchCriteria, "&apos;", "'", 0);
1295 SearchCriteria = modifyString(SearchCriteria, "&lt;", "<", 0);
1296 SearchCriteria = modifyString(SearchCriteria, "&gt;", ">", 0);
1297 SearchCriteria = modifyString(SearchCriteria, "\\\"", "\"\"", 0);
1298 SearchCriteria = modifyString(SearchCriteria, "object.", "", 0);
1299 SearchCriteria = modifyString(SearchCriteria, "derivedfrom", "like", 1);
1300 SearchCriteria = modifyString(SearchCriteria, "contains", "like", 2);
1301 SearchCriteria = modifyString(SearchCriteria, "dc:date", "d.DATE", 0);
1302 SearchCriteria = modifyString(SearchCriteria, "dc:title", "d.TITLE", 0);
1303 SearchCriteria = modifyString(SearchCriteria, "dc:creator", "d.CREATOR", 0);
1304 SearchCriteria = modifyString(SearchCriteria, "upnp:class", "o.CLASS", 0);
1305 SearchCriteria = modifyString(SearchCriteria, "upnp:actor", "d.ARTIST", 0);
1306 SearchCriteria = modifyString(SearchCriteria, "upnp:artist", "d.ARTIST", 0);
1307 SearchCriteria = modifyString(SearchCriteria, "upnp:album", "d.ALBUM", 0);
1308 SearchCriteria = modifyString(SearchCriteria, "upnp:genre", "d.GENRE", 0);
1309 SearchCriteria = modifyString(SearchCriteria, "exists true", "is not NULL", 0);
1310 SearchCriteria = modifyString(SearchCriteria, "exists false", "is NULL", 0);
1311 SearchCriteria = modifyString(SearchCriteria, "@refID", "REF_ID", 0);
1312 if( strstr(SearchCriteria, "@id") )
1314 newSearchCriteria = strdup(SearchCriteria);
1315 SearchCriteria = newSearchCriteria = modifyString(newSearchCriteria, "@id", "OBJECT_ID", 0);
1317 if( strstr(SearchCriteria, "res is ") )
1319 if( !newSearchCriteria )
1320 newSearchCriteria = strdup(SearchCriteria);
1321 SearchCriteria = newSearchCriteria = modifyString(newSearchCriteria, "res is ", "MIME is ", 0);
1323 #if 0 // Does 360 need this?
1324 if( strstr(SearchCriteria, "&amp;") )
1326 if( newSearchCriteria )
1327 newSearchCriteria = modifyString(newSearchCriteria, "&amp;", "&amp;amp;", 0);
1328 else
1329 newSearchCriteria = modifyString(strdup(SearchCriteria), "&amp;", "&amp;amp;", 0);
1330 SearchCriteria = newSearchCriteria;
1332 #endif
1334 DPRINTF(E_DEBUG, L_HTTP, "Translated SearchCriteria: %s\n", SearchCriteria);
1336 totalMatches = sql_get_int_field(db, "SELECT (select count(distinct DETAIL_ID)"
1337 " from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1338 " where (OBJECT_ID glob '%s$*') and (%s))"
1339 " + "
1340 "(select count(*) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1341 " where (OBJECT_ID = '%s') and (%s))",
1342 ContainerID, SearchCriteria, ContainerID, SearchCriteria);
1343 if( totalMatches < 0 )
1345 /* Must be invalid SQL, so most likely bad or unhandled search criteria. */
1346 SoapError(h, 708, "Unsupported or invalid search criteria");
1347 goto search_error;
1349 /* Does the object even exist? */
1350 if( !totalMatches )
1352 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'",
1353 !strcmp(ContainerID, "*")?"0":ContainerID);
1354 if( ret <= 0 )
1356 SoapError(h, 710, "No such container");
1357 goto search_error;
1360 #ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
1361 ret = 0;
1362 if( totalMatches < 10000 )
1363 #endif
1364 orderBy = parse_sort_criteria(SortCriteria, &ret);
1365 /* If it's a DLNA client, return an error for bad sort criteria */
1366 if( (args.flags & FLAG_DLNA) && ret )
1368 SoapError(h, 709, "Unsupported or invalid sort criteria");
1369 goto search_error;
1372 sql = sqlite3_mprintf( SELECT_COLUMNS
1373 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1374 " where OBJECT_ID glob '%s$*' and (%s) %s "
1375 "%z %s"
1376 " limit %d, %d",
1377 ContainerID, SearchCriteria, groupBy,
1378 (*ContainerID == '*') ? NULL :
1379 sqlite3_mprintf("UNION ALL " SELECT_COLUMNS
1380 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1381 " where OBJECT_ID = '%s' and (%s) ", ContainerID, SearchCriteria),
1382 orderBy, StartingIndex, RequestedCount);
1383 DPRINTF(E_DEBUG, L_HTTP, "Search SQL: %s\n", sql);
1384 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1385 if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
1387 DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1388 sqlite3_free(zErrMsg);
1390 sqlite3_free(sql);
1391 ret = strcatf(&str, "&lt;/DIDL-Lite&gt;</Result>\n"
1392 "<NumberReturned>%u</NumberReturned>\n"
1393 "<TotalMatches>%u</TotalMatches>\n"
1394 "<UpdateID>%u</UpdateID>"
1395 "</u:SearchResponse>",
1396 args.returned, totalMatches, updateID);
1397 BuildSendAndCloseSoapResp(h, str.data, str.off);
1398 search_error:
1399 ClearNameValueList(&data);
1400 if( args.flags & FLAG_FREE_OBJECT_ID )
1401 sqlite3_free(ContainerID);
1402 free(orderBy);
1403 free(newSearchCriteria);
1404 free(str.data);
1408 If a control point calls QueryStateVariable on a state variable that is not
1409 buffered in memory within (or otherwise available from) the service,
1410 the service must return a SOAP fault with an errorCode of 404 Invalid Var.
1412 QueryStateVariable remains useful as a limited test tool but may not be
1413 part of some future versions of UPnP.
1415 static void
1416 QueryStateVariable(struct upnphttp * h, const char * action)
1418 static const char resp[] =
1419 "<u:%sResponse "
1420 "xmlns:u=\"%s\">"
1421 "<return>%s</return>"
1422 "</u:%sResponse>";
1424 char body[512];
1425 int bodylen;
1426 struct NameValueParserData data;
1427 const char * var_name;
1429 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1430 /*var_name = GetValueFromNameValueList(&data, "QueryStateVariable"); */
1431 /*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/
1432 var_name = GetValueFromNameValueList(&data, "varName");
1434 DPRINTF(E_INFO, L_HTTP, "QueryStateVariable(%.40s)\n", var_name);
1436 if(!var_name)
1438 SoapError(h, 402, "Invalid Args");
1440 else if(strcmp(var_name, "ConnectionStatus") == 0)
1442 bodylen = snprintf(body, sizeof(body), resp,
1443 action, "urn:schemas-upnp-org:control-1-0",
1444 "Connected", action);
1445 BuildSendAndCloseSoapResp(h, body, bodylen);
1447 else
1449 DPRINTF(E_WARN, L_HTTP, "%s: Unknown: %s\n", action, var_name?var_name:"");
1450 SoapError(h, 404, "Invalid Var");
1453 ClearNameValueList(&data);
1456 static void
1457 SamsungGetFeatureList(struct upnphttp * h, const char * action)
1459 static const char resp[] =
1460 "<u:X_GetFeatureListResponse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1461 "<FeatureList>"
1462 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
1463 "&lt;Features xmlns=\"urn:schemas-upnp-org:av:avs\""
1464 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
1465 " xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\"&gt;"
1466 "&lt;Feature name=\"samsung.com_BASICVIEW\" version=\"1\"&gt;"
1467 "&lt;container id=\"1\" type=\"object.item.audioItem\"/&gt;"
1468 "&lt;container id=\"2\" type=\"object.item.videoItem\"/&gt;"
1469 "&lt;container id=\"3\" type=\"object.item.imageItem\"/&gt;"
1470 "&lt;/Feature&gt;"
1471 "</FeatureList></u:X_GetFeatureListResponse>";
1473 BuildSendAndCloseSoapResp(h, resp, sizeof(resp)-1);
1476 static void
1477 SamsungSetBookmark(struct upnphttp * h, const char * action)
1479 static const char resp[] =
1480 "<u:X_SetBookmarkResponse"
1481 " xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1482 "</u:X_SetBookmarkResponse>";
1484 struct NameValueParserData data;
1485 char *ObjectID, *PosSecond;
1486 int ret;
1488 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1489 ObjectID = GetValueFromNameValueList(&data, "ObjectID");
1490 PosSecond = GetValueFromNameValueList(&data, "PosSecond");
1491 if( ObjectID && PosSecond )
1493 ret = sql_exec(db, "INSERT OR REPLACE into BOOKMARKS"
1494 " VALUES "
1495 "((select DETAIL_ID from OBJECTS where OBJECT_ID = '%s'), %s)", ObjectID, PosSecond);
1496 if( ret != SQLITE_OK )
1497 DPRINTF(E_WARN, L_METADATA, "Error setting bookmark %s on ObjectID='%s'\n", PosSecond, ObjectID);
1498 BuildSendAndCloseSoapResp(h, resp, sizeof(resp)-1);
1500 else
1501 SoapError(h, 402, "Invalid Args");
1503 ClearNameValueList(&data);
1506 static const struct
1508 const char * methodName;
1509 void (*methodImpl)(struct upnphttp *, const char *);
1511 soapMethods[] =
1513 { "QueryStateVariable", QueryStateVariable},
1514 { "Browse", BrowseContentDirectory},
1515 { "Search", SearchContentDirectory},
1516 { "GetSearchCapabilities", GetSearchCapabilities},
1517 { "GetSortCapabilities", GetSortCapabilities},
1518 { "GetSystemUpdateID", GetSystemUpdateID},
1519 { "GetProtocolInfo", GetProtocolInfo},
1520 { "GetCurrentConnectionIDs", GetCurrentConnectionIDs},
1521 { "GetCurrentConnectionInfo", GetCurrentConnectionInfo},
1522 { "IsAuthorized", IsAuthorizedValidated},
1523 { "IsValidated", IsAuthorizedValidated},
1524 { "X_GetFeatureList", SamsungGetFeatureList},
1525 { "X_SetBookmark", SamsungSetBookmark},
1526 { 0, 0 }
1529 void
1530 ExecuteSoapAction(struct upnphttp * h, const char * action, int n)
1532 char * p;
1534 p = strchr(action, '#');
1535 if(p)
1537 int i = 0;
1538 int len;
1539 int methodlen;
1540 char * p2;
1541 p++;
1542 p2 = strchr(p, '"');
1543 if(p2)
1544 methodlen = p2 - p;
1545 else
1546 methodlen = n - (p - action);
1547 DPRINTF(E_DEBUG, L_HTTP, "SoapMethod: %.*s\n", methodlen, p);
1548 while(soapMethods[i].methodName)
1550 len = strlen(soapMethods[i].methodName);
1551 if(strncmp(p, soapMethods[i].methodName, len) == 0)
1553 soapMethods[i].methodImpl(h, soapMethods[i].methodName);
1554 return;
1556 i++;
1559 DPRINTF(E_WARN, L_HTTP, "SoapMethod: Unknown: %.*s\n", methodlen, p);
1562 SoapError(h, 401, "Invalid Action");
1565 /* Standard Errors:
1567 * errorCode errorDescription Description
1568 * -------- ---------------- -----------
1569 * 401 Invalid Action No action by that name at this service.
1570 * 402 Invalid Args Could be any of the following: not enough in args,
1571 * too many in args, no in arg by that name,
1572 * one or more in args are of the wrong data type.
1573 * 403 Out of Sync Out of synchronization.
1574 * 501 Action Failed May be returned in current state of service
1575 * prevents invoking that action.
1576 * 600-699 TBD Common action errors. Defined by UPnP Forum
1577 * Technical Committee.
1578 * 700-799 TBD Action-specific errors for standard actions.
1579 * Defined by UPnP Forum working committee.
1580 * 800-899 TBD Action-specific errors for non-standard actions.
1581 * Defined by UPnP vendor.
1583 void
1584 SoapError(struct upnphttp * h, int errCode, const char * errDesc)
1586 static const char resp[] =
1587 "<s:Envelope "
1588 "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
1589 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1590 "<s:Body>"
1591 "<s:Fault>"
1592 "<faultcode>s:Client</faultcode>"
1593 "<faultstring>UPnPError</faultstring>"
1594 "<detail>"
1595 "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">"
1596 "<errorCode>%d</errorCode>"
1597 "<errorDescription>%s</errorDescription>"
1598 "</UPnPError>"
1599 "</detail>"
1600 "</s:Fault>"
1601 "</s:Body>"
1602 "</s:Envelope>";
1604 char body[2048];
1605 int bodylen;
1607 DPRINTF(E_WARN, L_HTTP, "Returning UPnPError %d: %s\n", errCode, errDesc);
1608 bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc);
1609 BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen);
1610 SendResp_upnphttp(h);
1611 CloseSocket_upnphttp(h);