MiniDLNA 1.0.19 cvs 2011-03-15
[tomato.git] / release / src / router / minidlna / upnpsoap.c
blob0afca85dec10848dfc058cbf2923e330b009b41f
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 "upnphttp.h"
65 #include "upnpsoap.h"
66 #include "upnpreplyparse.h"
67 #include "getifaddr.h"
69 #include "scanner.h"
70 #include "utils.h"
71 #include "sql.h"
72 #include "log.h"
74 static void
75 BuildSendAndCloseSoapResp(struct upnphttp * h,
76 const char * body, int bodylen)
78 static const char beforebody[] =
79 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
80 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
81 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
82 "<s:Body>";
84 static const char afterbody[] =
85 "</s:Body>"
86 "</s:Envelope>\r\n";
88 BuildHeader_upnphttp(h, 200, "OK", sizeof(beforebody) - 1
89 + sizeof(afterbody) - 1 + bodylen );
91 memcpy(h->res_buf + h->res_buflen, beforebody, sizeof(beforebody) - 1);
92 h->res_buflen += sizeof(beforebody) - 1;
94 memcpy(h->res_buf + h->res_buflen, body, bodylen);
95 h->res_buflen += bodylen;
97 memcpy(h->res_buf + h->res_buflen, afterbody, sizeof(afterbody) - 1);
98 h->res_buflen += sizeof(afterbody) - 1;
100 SendResp_upnphttp(h);
101 CloseSocket_upnphttp(h);
104 static void
105 GetSystemUpdateID(struct upnphttp * h, const char * action)
107 static const char resp[] =
108 "<u:%sResponse "
109 "xmlns:u=\"%s\">"
110 "<Id>%d</Id>"
111 "</u:%sResponse>";
113 char body[512];
114 int bodylen;
116 bodylen = snprintf(body, sizeof(body), resp,
117 action, "urn:schemas-upnp-org:service:ContentDirectory:1",
118 updateID, action);
119 BuildSendAndCloseSoapResp(h, body, bodylen);
122 static void
123 IsAuthorizedValidated(struct upnphttp * h, const char * action)
125 static const char resp[] =
126 "<u:%sResponse "
127 "xmlns:u=\"%s\">"
128 "<Result>%d</Result>"
129 "</u:%sResponse>";
131 char body[512];
132 int bodylen;
134 bodylen = snprintf(body, sizeof(body), resp,
135 action, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
136 1, action);
137 BuildSendAndCloseSoapResp(h, body, bodylen);
140 static void
141 GetProtocolInfo(struct upnphttp * h, const char * action)
143 static const char resp[] =
144 "<u:%sResponse "
145 "xmlns:u=\"%s\">"
146 "<Source>"
147 RESOURCE_PROTOCOL_INFO_VALUES
148 "</Source>"
149 "<Sink></Sink>"
150 "</u:%sResponse>";
152 char * body;
153 int bodylen;
155 bodylen = asprintf(&body, resp,
156 action, "urn:schemas-upnp-org:service:ConnectionManager:1",
157 action);
158 BuildSendAndCloseSoapResp(h, body, bodylen);
159 free(body);
162 static void
163 GetSortCapabilities(struct upnphttp * h, const char * action)
165 static const char resp[] =
166 "<u:%sResponse "
167 "xmlns:u=\"%s\">"
168 "<SortCaps>"
169 "dc:title,"
170 "dc:date,"
171 "upnp:class,"
172 "upnp:originalTrackNumber"
173 "</SortCaps>"
174 "</u:%sResponse>";
176 char body[512];
177 int bodylen;
179 bodylen = snprintf(body, sizeof(body), resp,
180 action, "urn:schemas-upnp-org:service:ContentDirectory:1",
181 action);
182 BuildSendAndCloseSoapResp(h, body, bodylen);
185 static void
186 GetSearchCapabilities(struct upnphttp * h, const char * action)
188 static const char resp[] =
189 "<u:%sResponse xmlns:u=\"%s\">"
190 "<SearchCaps>"
191 "dc:creator,"
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
349 static u_int32_t
350 set_filter_flags(char * filter, enum client_types client)
352 char *item, *saveptr = NULL;
353 u_int32_t flags = 0;
355 if( !filter || (strlen(filter) <= 1) )
356 return 0xFFFFFFFF;
357 if( client == ESamsungTV )
358 flags |= FILTER_DLNA_NAMESPACE;
359 item = strtok_r(filter, ",", &saveptr);
360 while( item != NULL )
362 if( saveptr )
363 *(item-1) = ',';
364 while( isspace(*item) )
365 item++;
366 if( strcmp(item, "@childCount") == 0 )
368 flags |= FILTER_CHILDCOUNT;
370 else if( strcmp(item, "dc:creator") == 0 )
372 flags |= FILTER_DC_CREATOR;
374 else if( strcmp(item, "dc:date") == 0 )
376 flags |= FILTER_DC_DATE;
378 else if( strcmp(item, "dc:description") == 0 )
380 flags |= FILTER_DC_DESCRIPTION;
382 else if( strcmp(item, "dlna") == 0 )
384 flags |= FILTER_DLNA_NAMESPACE;
386 else if( strcmp(item, "@refID") == 0 )
388 flags |= FILTER_REFID;
390 else if( strcmp(item, "upnp:album") == 0 )
392 flags |= FILTER_UPNP_ALBUM;
394 else if( strcmp(item, "upnp:albumArtURI") == 0 )
396 flags |= FILTER_UPNP_ALBUMARTURI;
397 if( client == ESamsungTV )
398 flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID;
400 else if( strcmp(item, "upnp:albumArtURI@dlna:profileID") == 0 )
402 flags |= FILTER_UPNP_ALBUMARTURI;
403 flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID;
405 else if( strcmp(item, "upnp:artist") == 0 )
407 flags |= FILTER_UPNP_ARTIST;
409 else if( strcmp(item, "upnp:actor") == 0 )
411 flags |= FILTER_UPNP_ACTOR;
413 else if( strcmp(item, "upnp:genre") == 0 )
415 flags |= FILTER_UPNP_GENRE;
417 else if( strcmp(item, "upnp:originalTrackNumber") == 0 )
419 flags |= FILTER_UPNP_ORIGINALTRACKNUMBER;
421 else if( strcmp(item, "upnp:searchClass") == 0 )
423 flags |= FILTER_UPNP_SEARCHCLASS;
425 else if( strcmp(item, "res") == 0 )
427 flags |= FILTER_RES;
429 else if( (strcmp(item, "res@bitrate") == 0) ||
430 (strcmp(item, "@bitrate") == 0) ||
431 ((strcmp(item, "bitrate") == 0) && (flags & FILTER_RES)) )
433 flags |= FILTER_RES;
434 flags |= FILTER_RES_BITRATE;
436 else if( (strcmp(item, "res@duration") == 0) ||
437 (strcmp(item, "@duration") == 0) ||
438 ((strcmp(item, "duration") == 0) && (flags & FILTER_RES)) )
440 flags |= FILTER_RES;
441 flags |= FILTER_RES_DURATION;
443 else if( (strcmp(item, "res@nrAudioChannels") == 0) ||
444 (strcmp(item, "@nrAudioChannels") == 0) ||
445 ((strcmp(item, "nrAudioChannels") == 0) && (flags & FILTER_RES)) )
447 flags |= FILTER_RES;
448 flags |= FILTER_RES_NRAUDIOCHANNELS;
450 else if( (strcmp(item, "res@resolution") == 0) ||
451 (strcmp(item, "@resolution") == 0) ||
452 ((strcmp(item, "resolution") == 0) && (flags & FILTER_RES)) )
454 flags |= FILTER_RES;
455 flags |= FILTER_RES_RESOLUTION;
457 else if( (strcmp(item, "res@sampleFrequency") == 0) ||
458 (strcmp(item, "@sampleFrequency") == 0) ||
459 ((strcmp(item, "sampleFrequency") == 0) && (flags & FILTER_RES)) )
461 flags |= FILTER_RES;
462 flags |= FILTER_RES_SAMPLEFREQUENCY;
464 else if( (strcmp(item, "res@size") == 0) ||
465 (strcmp(item, "@size") == 0) ||
466 (strcmp(item, "size") == 0) )
468 flags |= FILTER_RES;
469 flags |= FILTER_RES_SIZE;
471 item = strtok_r(NULL, ",", &saveptr);
474 return flags;
477 char *
478 parse_sort_criteria(char * sortCriteria, int * error)
480 char *order = NULL;
481 char *item, *saveptr;
482 int i, ret, reverse, title_sorted = 0;
483 *error = 0;
485 if( !sortCriteria )
486 return NULL;
488 if( (item = strtok_r(sortCriteria, ",", &saveptr)) )
490 order = malloc(4096);
491 strcpy(order, "order by ");
493 for( i=0; item != NULL; i++ )
495 reverse=0;
496 if( i )
497 strcat(order, ", ");
498 if( *item == '+' )
500 item++;
502 else if( *item == '-' )
504 reverse = 1;
505 item++;
507 if( strcasecmp(item, "upnp:class") == 0 )
509 strcat(order, "o.CLASS");
511 else if( strcasecmp(item, "dc:title") == 0 )
513 strcat(order, "d.TITLE");
514 title_sorted = 1;
516 else if( strcasecmp(item, "dc:date") == 0 )
518 strcat(order, "d.DATE");
520 else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 )
522 strcat(order, "d.DISC, d.TRACK");
524 else
526 printf("Unhandled SortCriteria [%s]\n", item);
527 *error = 1;
528 if( i )
530 ret = strlen(order);
531 order[ret-2] = '\0';
533 i--;
534 goto unhandled_order;
537 if( reverse )
538 strcat(order, " DESC");
539 unhandled_order:
540 item = strtok_r(NULL, ",", &saveptr);
542 if( i <= 0 )
544 free(order);
545 return NULL;
547 /* Add a "tiebreaker" sort order */
548 if( !title_sorted )
549 strcat(order, ", TITLE ASC");
551 return order;
554 static void add_resized_res(int srcw, int srch, int reqw, int reqh, char *dlna_pn, char *detailID, struct Response *passed_args)
556 int ret;
557 int dstw = reqw;
558 int dsth = reqh;
559 char str_buf[256];
562 if( passed_args->flags & FLAG_NO_RESIZE )
564 return;
567 ret = sprintf(str_buf, "&lt;res ");
568 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
569 passed_args->size += ret;
570 if( passed_args->filter & FILTER_RES_RESOLUTION )
572 dstw = reqw;
573 dsth = ((((reqw<<10)/srcw)*srch)>>10);
574 if( dsth > reqh ) {
575 dsth = reqh;
576 dstw = (((reqh<<10)/srch) * srcw>>10);
578 ret = sprintf(str_buf, "resolution=\"%dx%d\" ", dstw, dsth);
579 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
580 passed_args->size += ret;
582 ret = sprintf(str_buf, "protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=%s;DLNA.ORG_CI=1\"&gt;"
583 "http://%s:%d/Resized/%s.jpg?width=%d,height=%d"
584 "&lt;/res&gt;",
585 dlna_pn, lan_addr[0].str, runtime_vars.port,
586 detailID, dstw, dsth);
587 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
588 passed_args->size += ret;
591 #define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, o.REF_ID, o.DETAIL_ID, o.CLASS," \
592 " d.SIZE, d.TITLE, d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST," \
593 " d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RESOLUTION," \
594 " d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.DISC "
596 static int
597 callback(void *args, int argc, char **argv, char **azColName)
599 struct Response *passed_args = (struct Response *)args;
600 char *id = argv[0], *parent = argv[1], *refID = argv[2], *detailID = argv[3], *class = argv[4], *size = argv[5], *title = argv[6],
601 *duration = argv[7], *bitrate = argv[8], *sampleFrequency = argv[9], *artist = argv[10], *album = argv[11],
602 *genre = argv[12], *comment = argv[13], *nrAudioChannels = argv[14], *track = argv[15], *date = argv[16], *resolution = argv[17],
603 *tn = argv[18], *creator = argv[19], *dlna_pn = argv[20], *mime = argv[21], *album_art = argv[22];
604 char dlna_buf[96];
605 char ext[5];
606 char str_buf[512];
607 int children, ret = 0;
609 /* Make sure we have at least 4KB left of allocated memory to finish the response. */
610 if( passed_args->size > (passed_args->alloced - 4096) )
612 #if MAX_RESPONSE_SIZE > 0
613 if( (passed_args->alloced+1048576) <= MAX_RESPONSE_SIZE )
615 #endif
616 passed_args->resp = realloc(passed_args->resp, (passed_args->alloced+1048576));
617 if( passed_args->resp )
619 passed_args->alloced += 1048576;
620 DPRINTF(E_DEBUG, L_HTTP, "HUGE RESPONSE ALERT: UPnP SOAP response had to be enlarged to %d. [%d results so far]\n", passed_args->alloced, passed_args->returned);
622 else
624 DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response was too big, and realloc failed!\n");
625 return -1;
627 #if MAX_RESPONSE_SIZE > 0
629 else
631 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);
632 return -1;
634 #endif
636 passed_args->returned++;
638 if( dlna_pn )
639 sprintf(dlna_buf, "DLNA.ORG_PN=%s", dlna_pn);
640 else if( passed_args->flags & FLAG_DLNA )
641 strcpy(dlna_buf, dlna_no_conv);
642 else
643 strcpy(dlna_buf, "*");
645 if( strncmp(class, "item", 4) == 0 )
647 /* We may need special handling for certain MIME types */
648 if( *mime == 'v' )
650 if( passed_args->flags & FLAG_MIME_AVI_DIVX )
652 if( strcmp(mime, "video/x-msvideo") == 0 )
654 if( creator )
655 strcpy(mime+6, "divx");
656 else
657 strcpy(mime+6, "avi");
660 else if( passed_args->flags & FLAG_MIME_AVI_AVI )
662 if( strcmp(mime, "video/x-msvideo") == 0 )
664 strcpy(mime+6, "avi");
667 if( !(passed_args->flags & FLAG_DLNA) )
669 if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
671 strcpy(mime+6, "mpeg");
674 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
675 if( passed_args->client == ESamsungTV )
677 if( strcmp(mime+6, "x-matroska") == 0 )
679 strcpy(mime+8, "mkv");
682 else if( passed_args->client == ESonyBDP || passed_args->client == ESonyBravia )
684 if( passed_args->client == ESonyBDP &&
685 (strcmp(mime+6, "x-matroska") == 0 ||
686 strcmp(mime+6, "mpeg") == 0) )
688 strcpy(mime+6, "divx");
690 /* BRAVIA KDL-##*X### series TVs do natively support AVC/AC3 in TS, but
691 require profile to be renamed (applies to _T and _ISO variants also) */
692 modifyString(dlna_pn, "AVC_TS_MP_SD_AC3", "AVC_TS_HD_50_AC3", 0);
693 modifyString(dlna_pn, "AVC_TS_MP_HD_AC3", "AVC_TS_HD_50_AC3", 0);
696 else if( *mime == 'a' )
698 if( strcmp(mime+6, "x-flac") == 0 )
700 if( passed_args->flags & FLAG_MIME_FLAC_FLAC )
702 strcpy(mime+6, "flac");
707 ret = snprintf(str_buf, 512, "&lt;item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent);
708 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
709 passed_args->size += ret;
710 if( refID && (passed_args->filter & FILTER_REFID) ) {
711 ret = sprintf(str_buf, " refID=\"%s\"", refID);
712 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
713 passed_args->size += ret;
715 ret = snprintf(str_buf, 512, "&gt;"
716 "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
717 "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
718 title, class);
719 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
720 passed_args->size += ret;
721 if( comment && (passed_args->filter & FILTER_DC_DESCRIPTION) ) {
722 ret = snprintf(str_buf, 512, "&lt;dc:description&gt;%.384s&lt;/dc:description&gt;", comment);
723 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
724 passed_args->size += ret;
726 if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
727 ret = snprintf(str_buf, 512, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
728 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
729 passed_args->size += ret;
731 if( date && (passed_args->filter & FILTER_DC_DATE) ) {
732 ret = snprintf(str_buf, 512, "&lt;dc:date&gt;%s&lt;/dc:date&gt;", date);
733 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
734 passed_args->size += ret;
736 if( artist ) {
737 if( (*mime == 'a') && (passed_args->filter & FILTER_UPNP_ARTIST) ) {
738 ret = snprintf(str_buf, 512, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
739 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
740 passed_args->size += ret;
742 else if( (*mime == 'v') && (passed_args->filter & FILTER_UPNP_ACTOR) ) {
743 ret = snprintf(str_buf, 512, "&lt;upnp:actor&gt;%s&lt;/upnp:actor&gt;", artist);
744 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
745 passed_args->size += ret;
748 if( album && (passed_args->filter & FILTER_UPNP_ALBUM) ) {
749 ret = snprintf(str_buf, 512, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", album);
750 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
751 passed_args->size += ret;
753 if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
754 ret = snprintf(str_buf, 512, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
755 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
756 passed_args->size += ret;
758 if( strncmp(id, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) {
759 track = strrchr(id, '$')+1;
761 if( track && atoi(track) && (passed_args->filter & FILTER_UPNP_ORIGINALTRACKNUMBER) ) {
762 ret = sprintf(str_buf, "&lt;upnp:originalTrackNumber&gt;%s&lt;/upnp:originalTrackNumber&gt;", track);
763 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
764 passed_args->size += ret;
766 if( album_art && atoi(album_art) )
768 /* Video and audio album art is handled differently */
769 if( *mime == 'v' && (passed_args->filter & FILTER_RES) && !(passed_args->flags & FLAG_MS_PFS) ) {
770 ret = sprintf(str_buf, "&lt;res protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\"&gt;"
771 "http://%s:%d/AlbumArt/%s-%s.jpg"
772 "&lt;/res&gt;",
773 lan_addr[0].str, runtime_vars.port, album_art, detailID);
774 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
775 passed_args->size += ret;
776 } else if( passed_args->filter & FILTER_UPNP_ALBUMARTURI ) {
777 ret = sprintf(str_buf, "&lt;upnp:albumArtURI");
778 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
779 passed_args->size += ret;
780 if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
781 ret = sprintf(str_buf, " dlna:profileID=\"%s\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"", "JPEG_TN");
782 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
783 passed_args->size += ret;
785 ret = sprintf(str_buf, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg&lt;/upnp:albumArtURI&gt;",
786 lan_addr[0].str, runtime_vars.port, album_art, detailID);
787 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
788 passed_args->size += ret;
791 #ifdef PFS_HACK
792 if( (passed_args->flags & FLAG_MS_PFS) && *mime == 'i' ) {
793 ret = snprintf(str_buf, 512, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", "[No Keywords]");
794 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
795 passed_args->size += ret;
797 if( tn && atoi(tn) ) {
798 ret = snprintf(str_buf, 512, "&lt;upnp:albumArtURI&gt;"
799 "http://%s:%d/Thumbnails/%s.jpg"
800 "&lt;/upnp:albumArtURI&gt;",
801 lan_addr[0].str, runtime_vars.port, detailID);
802 } else {
803 ret = snprintf(str_buf, 512, "&lt;upnp:albumArtURI&gt;"
804 "http://%s:%d/Resized/%s.jpg?width=160,height=160"
805 "&lt;/upnp:albumArtURI&gt;",
806 lan_addr[0].str, runtime_vars.port, detailID);
808 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
809 passed_args->size += ret;
811 #endif
812 if( passed_args->filter & FILTER_RES ) {
813 mime_to_ext(mime, ext);
814 if( (passed_args->client == EFreeBox) && tn && atoi(tn) ) {
815 ret = sprintf(str_buf, "&lt;res protocolInfo=\"http-get:*:%s:%s\"&gt;"
816 "http://%s:%d/Thumbnails/%s.jpg"
817 "&lt;/res&gt;",
818 mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, runtime_vars.port, detailID);
819 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
820 passed_args->size += ret;
822 ret = sprintf(str_buf, "&lt;res ");
823 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
824 passed_args->size += ret;
825 if( size && (passed_args->filter & FILTER_RES_SIZE) ) {
826 ret = sprintf(str_buf, "size=\"%s\" ", size);
827 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
828 passed_args->size += ret;
830 if( duration && (passed_args->filter & FILTER_RES_DURATION) ) {
831 ret = sprintf(str_buf, "duration=\"%s\" ", duration);
832 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
833 passed_args->size += ret;
835 if( bitrate && (passed_args->filter & FILTER_RES_BITRATE) ) {
836 if( passed_args->flags & FLAG_MS_PFS )
837 ret = sprintf(str_buf, "bitrate=\"%d\" ", atoi(bitrate)/1024);
838 else
839 ret = sprintf(str_buf, "bitrate=\"%s\" ", bitrate);
840 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
841 passed_args->size += ret;
843 if( sampleFrequency && (passed_args->filter & FILTER_RES_SAMPLEFREQUENCY) ) {
844 ret = sprintf(str_buf, "sampleFrequency=\"%s\" ", sampleFrequency);
845 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
846 passed_args->size += ret;
848 if( nrAudioChannels && (passed_args->filter & FILTER_RES_NRAUDIOCHANNELS) ) {
849 ret = sprintf(str_buf, "nrAudioChannels=\"%s\" ", nrAudioChannels);
850 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
851 passed_args->size += ret;
853 if( resolution && (passed_args->filter & FILTER_RES_RESOLUTION) ) {
854 ret = sprintf(str_buf, "resolution=\"%s\" ", resolution);
855 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
856 passed_args->size += ret;
858 ret = sprintf(str_buf, "protocolInfo=\"http-get:*:%s:%s\"&gt;"
859 "http://%s:%d/MediaItems/%s.%s"
860 "&lt;/res&gt;",
861 mime, dlna_buf, lan_addr[0].str, runtime_vars.port, detailID, ext);
862 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
863 passed_args->size += ret;
864 if( (*mime == 'i') && (passed_args->client != EFreeBox) ) {
865 #if 1 //JPEG_RESIZE
866 int srcw = atoi(strsep(&resolution, "x"));
867 int srch = atoi(resolution);
868 if( !dlna_pn ) {
869 add_resized_res(srcw, srch, 4096, 4096, "JPEG_LRG", detailID, passed_args);
871 if( !dlna_pn || !strncmp(dlna_pn, "JPEG_L", 6) || !strncmp(dlna_pn, "JPEG_M", 6) ) {
872 add_resized_res(srcw, srch, 640, 480, "JPEG_SM", detailID, passed_args);
874 #endif
875 if( tn && atoi(tn) ) {
876 ret = sprintf(str_buf, "&lt;res protocolInfo=\"http-get:*:%s:%s\"&gt;"
877 "http://%s:%d/Thumbnails/%s.jpg"
878 "&lt;/res&gt;",
879 mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[0].str, runtime_vars.port, detailID);
880 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
881 passed_args->size += ret;
885 ret = sprintf(str_buf, "&lt;/item&gt;");
887 else if( strncmp(class, "container", 9) == 0 )
889 ret = sprintf(str_buf, "&lt;container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent);
890 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
891 passed_args->size += ret;
892 if( passed_args->filter & FILTER_CHILDCOUNT )
894 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", id);
895 children = (ret > 0) ? ret : 0;
896 ret = sprintf(str_buf, "childCount=\"%d\"", children);
897 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
898 passed_args->size += ret;
900 /* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */
901 if( (passed_args->requested == 1) && (strcmp(id, "0") == 0) )
903 if( passed_args->filter & FILTER_UPNP_SEARCHCLASS )
905 ret = sprintf(str_buf, "&gt;"
906 "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.audioItem&lt;/upnp:searchClass&gt;"
907 "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.imageItem&lt;/upnp:searchClass&gt;"
908 "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.videoItem&lt;/upnp:searchClass");
909 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
910 passed_args->size += ret;
913 ret = snprintf(str_buf, 512, "&gt;"
914 "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
915 "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
916 title, class);
917 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
918 passed_args->size += ret;
919 if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
920 ret = snprintf(str_buf, 512, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
921 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
922 passed_args->size += ret;
924 if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
925 ret = snprintf(str_buf, 512, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
926 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
927 passed_args->size += ret;
929 if( artist && (passed_args->filter & FILTER_UPNP_ARTIST) ) {
930 ret = snprintf(str_buf, 512, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
931 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
932 passed_args->size += ret;
934 if( album_art && atoi(album_art) && (passed_args->filter & FILTER_UPNP_ALBUMARTURI) ) {
935 ret = sprintf(str_buf, "&lt;upnp:albumArtURI ");
936 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
937 passed_args->size += ret;
938 if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
939 ret = sprintf(str_buf, "dlna:profileID=\"%s\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"", "JPEG_TN");
940 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
941 passed_args->size += ret;
943 ret = sprintf(str_buf, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg&lt;/upnp:albumArtURI&gt;",
944 lan_addr[0].str, runtime_vars.port, album_art, detailID);
945 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
946 passed_args->size += ret;
948 ret = sprintf(str_buf, "&lt;/container&gt;");
950 memcpy(passed_args->resp+passed_args->size, &str_buf, ret+1);
951 passed_args->size += ret;
953 return 0;
956 static void
957 BrowseContentDirectory(struct upnphttp * h, const char * action)
959 static const char resp0[] =
960 "<u:BrowseResponse "
961 "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
962 "<Result>"
963 "&lt;DIDL-Lite"
964 CONTENT_DIRECTORY_SCHEMAS;
966 char *resp = malloc(1048576);
967 char str_buf[512];
968 char *zErrMsg = 0;
969 char *sql, *ptr;
970 int ret;
971 struct Response args;
972 int totalMatches;
973 struct NameValueParserData data;
974 *resp = '\0';
976 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
977 char * ObjectId = GetValueFromNameValueList(&data, "ObjectID");
978 char * Filter = GetValueFromNameValueList(&data, "Filter");
979 char * BrowseFlag = GetValueFromNameValueList(&data, "BrowseFlag");
980 char * SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
981 char * orderBy = NULL;
982 int RequestedCount = 0;
983 int StartingIndex = 0;
984 if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
985 RequestedCount = atoi(ptr);
986 if( !RequestedCount )
987 RequestedCount = -1;
988 if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
989 StartingIndex = atoi(ptr);
990 if( !BrowseFlag || (strcmp(BrowseFlag, "BrowseDirectChildren") && strcmp(BrowseFlag, "BrowseMetadata")) )
992 SoapError(h, 402, "Invalid Args");
993 if( h->reqflags & FLAG_MS_PFS )
994 ObjectId = sqlite3_malloc(1);
995 goto browse_error;
997 if( !ObjectId && !(ObjectId = GetValueFromNameValueList(&data, "ContainerID")) )
999 SoapError(h, 701, "No such object error");
1000 if( h->reqflags & FLAG_MS_PFS )
1001 ObjectId = sqlite3_malloc(1);
1002 goto browse_error;
1004 memset(&args, 0, sizeof(args));
1006 args.alloced = 1048576;
1007 args.resp = resp;
1008 args.size = sprintf(resp, "%s", resp0);
1009 /* See if we need to include DLNA namespace reference */
1010 args.filter = set_filter_flags(Filter, h->req_client);
1011 if( args.filter & FILTER_DLNA_NAMESPACE )
1013 ret = sprintf(str_buf, DLNA_NAMESPACE);
1014 memcpy(resp+args.size, &str_buf, ret+1);
1015 args.size += ret;
1017 ret = sprintf(str_buf, "&gt;\n");
1018 memcpy(resp+args.size, &str_buf, ret+1);
1019 args.size += ret;
1021 args.returned = 0;
1022 args.requested = RequestedCount;
1023 args.client = h->req_client;
1024 args.flags = h->reqflags;
1025 if( h->reqflags & FLAG_MS_PFS )
1027 if( strchr(ObjectId, '$') || (strcmp(ObjectId, "0") == 0) )
1029 ObjectId = sqlite3_mprintf("%s", ObjectId);
1031 else
1033 ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS"
1034 " where OBJECT_ID in "
1035 "('"MUSIC_ID"$%s', '"VIDEO_ID"$%s', '"IMAGE_ID"$%s')",
1036 ObjectId, ObjectId, ObjectId);
1037 if( ptr )
1038 ObjectId = ptr;
1039 else
1040 ObjectId = sqlite3_mprintf("%s", ObjectId);
1043 DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n"
1044 " * ObjectID: %s\n"
1045 " * Count: %d\n"
1046 " * StartingIndex: %d\n"
1047 " * BrowseFlag: %s\n"
1048 " * Filter: %s\n"
1049 " * SortCriteria: %s\n",
1050 ObjectId, RequestedCount, StartingIndex,
1051 BrowseFlag, Filter, SortCriteria);
1053 if( strcmp(BrowseFlag+6, "Metadata") == 0 )
1055 args.requested = 1;
1056 sql = sqlite3_mprintf( SELECT_COLUMNS
1057 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1058 " where OBJECT_ID = '%s';"
1059 , ObjectId);
1060 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1061 totalMatches = args.returned;
1063 else
1065 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", ObjectId);
1066 totalMatches = (ret > 0) ? ret : 0;
1067 ret = 0;
1068 if( SortCriteria )
1070 #ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
1071 if( totalMatches < 10000 )
1072 #endif
1073 orderBy = parse_sort_criteria(SortCriteria, &ret);
1075 else
1077 if( strncmp(ObjectId, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 )
1079 if( strcmp(ObjectId, MUSIC_PLIST_ID) == 0 )
1080 asprintf(&orderBy, "order by d.TITLE");
1081 else
1082 asprintf(&orderBy, "order by length(OBJECT_ID), OBJECT_ID");
1085 /* If it's a DLNA client, return an error for bad sort criteria */
1086 if( (args.flags & FLAG_DLNA) && ret )
1088 SoapError(h, 709, "Unsupported or invalid sort criteria");
1089 goto browse_error;
1092 sql = sqlite3_mprintf( SELECT_COLUMNS
1093 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1094 " where PARENT_ID = '%s' %s limit %d, %d;",
1095 ObjectId, orderBy, StartingIndex, RequestedCount);
1096 DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql);
1097 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1099 sqlite3_free(sql);
1100 if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
1102 DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1103 sqlite3_free(zErrMsg);
1105 /* Does the object even exist? */
1106 if( !totalMatches )
1108 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", ObjectId);
1109 if( ret <= 0 )
1111 SoapError(h, 701, "No such object error");
1112 goto browse_error;
1115 ret = snprintf(str_buf, sizeof(str_buf), "&lt;/DIDL-Lite&gt;</Result>\n"
1116 "<NumberReturned>%u</NumberReturned>\n"
1117 "<TotalMatches>%u</TotalMatches>\n"
1118 "<UpdateID>%u</UpdateID>"
1119 "</u:BrowseResponse>",
1120 args.returned, totalMatches, updateID);
1121 memcpy(resp+args.size, &str_buf, ret+1);
1122 args.size += ret;
1123 BuildSendAndCloseSoapResp(h, resp, args.size);
1124 browse_error:
1125 ClearNameValueList(&data);
1126 if( orderBy )
1127 free(orderBy);
1128 free(resp);
1129 if( h->reqflags & FLAG_MS_PFS )
1131 sqlite3_free(ObjectId);
1135 static void
1136 SearchContentDirectory(struct upnphttp * h, const char * action)
1138 static const char resp0[] =
1139 "<u:SearchResponse "
1140 "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1141 "<Result>"
1142 "&lt;DIDL-Lite"
1143 CONTENT_DIRECTORY_SCHEMAS;
1145 char *resp = malloc(1048576);
1146 char *zErrMsg = 0;
1147 char *sql, *ptr;
1148 char **result;
1149 char str_buf[4096];
1150 int ret;
1151 struct Response args;
1152 int totalMatches = 0;
1153 *resp = '\0';
1155 struct NameValueParserData data;
1156 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1157 char * ContainerID = GetValueFromNameValueList(&data, "ContainerID");
1158 char * Filter = GetValueFromNameValueList(&data, "Filter");
1159 char * SearchCriteria = GetValueFromNameValueList(&data, "SearchCriteria");
1160 char * SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
1161 char * newSearchCriteria = NULL;
1162 char * orderBy = NULL;
1163 char groupBy[] = "group by DETAIL_ID";
1164 int RequestedCount = 0;
1165 int StartingIndex = 0;
1166 if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
1167 RequestedCount = atoi(ptr);
1168 if( !RequestedCount )
1169 RequestedCount = -1;
1170 if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
1171 StartingIndex = atoi(ptr);
1172 if( !ContainerID )
1174 if( !(ContainerID = GetValueFromNameValueList(&data, "ObjectID")) )
1176 SoapError(h, 701, "No such object error");
1177 if( h->reqflags & FLAG_MS_PFS )
1178 ContainerID = sqlite3_malloc(1);
1179 goto search_error;
1182 memset(&args, 0, sizeof(args));
1184 args.alloced = 1048576;
1185 args.resp = resp;
1186 args.size = sprintf(resp, "%s", resp0);
1187 /* See if we need to include DLNA namespace reference */
1188 args.filter = set_filter_flags(Filter, h->req_client);
1189 if( args.filter & FILTER_DLNA_NAMESPACE )
1191 ret = sprintf(str_buf, DLNA_NAMESPACE);
1192 memcpy(resp+args.size, &str_buf, ret+1);
1193 args.size += ret;
1195 ret = sprintf(str_buf, "&gt;\n");
1196 memcpy(resp+args.size, &str_buf, ret+1);
1197 args.size += ret;
1199 args.returned = 0;
1200 args.requested = RequestedCount;
1201 args.client = h->req_client;
1202 args.flags = h->reqflags;
1203 if( h->reqflags & FLAG_MS_PFS )
1205 if( strchr(ContainerID, '$') || (strcmp(ContainerID, "0") == 0) )
1207 ContainerID = sqlite3_mprintf("%s", ContainerID);
1209 else
1211 ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS"
1212 " where OBJECT_ID in "
1213 "('"MUSIC_ID"$%s', '"VIDEO_ID"$%s', '"IMAGE_ID"$%s')",
1214 ContainerID, ContainerID, ContainerID);
1215 if( ptr )
1216 ContainerID = ptr;
1217 else
1218 ContainerID = sqlite3_mprintf("%s", ContainerID);
1220 #if 0 // Looks like the 360 already does this
1221 /* Sort by track number for some containers */
1222 if( orderBy &&
1223 ((strncmp(ContainerID, MUSIC_GENRE_ID, 3) == 0) ||
1224 (strncmp(ContainerID, MUSIC_ARTIST_ID, 3) == 0) ||
1225 (strncmp(ContainerID, MUSIC_ALBUM_ID, 3) == 0)) )
1227 DPRINTF(E_DEBUG, L_HTTP, "Old sort order: %s\n", orderBy);
1228 sprintf(str_buf, "d.TRACK, ");
1229 memmove(orderBy+18, orderBy+9, strlen(orderBy)+1);
1230 memmove(orderBy+9, &str_buf, 9);
1231 DPRINTF(E_DEBUG, L_HTTP, "New sort order: %s\n", orderBy);
1233 #endif
1235 DPRINTF(E_DEBUG, L_HTTP, "Searching ContentDirectory:\n"
1236 " * ObjectID: %s\n"
1237 " * Count: %d\n"
1238 " * StartingIndex: %d\n"
1239 " * SearchCriteria: %s\n"
1240 " * Filter: %s\n"
1241 " * SortCriteria: %s\n",
1242 ContainerID, RequestedCount, StartingIndex,
1243 SearchCriteria, Filter, SortCriteria);
1245 if( strcmp(ContainerID, "0") == 0 )
1246 *ContainerID = '*';
1247 else if( strcmp(ContainerID, MUSIC_ALL_ID) == 0 )
1248 groupBy[0] = '\0';
1249 if( !SearchCriteria )
1251 asprintf(&newSearchCriteria, "1 = 1");
1252 SearchCriteria = newSearchCriteria;
1254 else
1256 SearchCriteria = modifyString(SearchCriteria, "&quot;", "\"", 0);
1257 SearchCriteria = modifyString(SearchCriteria, "&apos;", "'", 0);
1258 SearchCriteria = modifyString(SearchCriteria, "object.", "", 0);
1259 SearchCriteria = modifyString(SearchCriteria, "derivedfrom", "like", 1);
1260 SearchCriteria = modifyString(SearchCriteria, "contains", "like", 2);
1261 SearchCriteria = modifyString(SearchCriteria, "dc:title", "d.TITLE", 0);
1262 SearchCriteria = modifyString(SearchCriteria, "dc:creator", "d.CREATOR", 0);
1263 SearchCriteria = modifyString(SearchCriteria, "upnp:class", "o.CLASS", 0);
1264 SearchCriteria = modifyString(SearchCriteria, "upnp:actor", "d.ARTIST", 0);
1265 SearchCriteria = modifyString(SearchCriteria, "upnp:artist", "d.ARTIST", 0);
1266 SearchCriteria = modifyString(SearchCriteria, "upnp:album", "d.ALBUM", 0);
1267 SearchCriteria = modifyString(SearchCriteria, "upnp:genre", "d.GENRE", 0);
1268 SearchCriteria = modifyString(SearchCriteria, "exists true", "is not NULL", 0);
1269 SearchCriteria = modifyString(SearchCriteria, "exists false", "is NULL", 0);
1270 SearchCriteria = modifyString(SearchCriteria, "@refID", "REF_ID", 0);
1271 if( strstr(SearchCriteria, "@id") )
1273 newSearchCriteria = modifyString(strdup(SearchCriteria), "@id", "OBJECT_ID", 0);
1274 SearchCriteria = newSearchCriteria;
1276 if( strstr(SearchCriteria, "res is ") )
1278 if( newSearchCriteria )
1279 newSearchCriteria = modifyString(newSearchCriteria, "res is ", "MIME is ", 0);
1280 else
1281 newSearchCriteria = modifyString(strdup(SearchCriteria), "res is ", "MIME is ", 0);
1282 SearchCriteria = newSearchCriteria;
1284 #if 0 // Does 360 need this?
1285 if( strstr(SearchCriteria, "&amp;") )
1287 if( newSearchCriteria )
1288 newSearchCriteria = modifyString(newSearchCriteria, "&amp;", "&amp;amp;", 0);
1289 else
1290 newSearchCriteria = modifyString(strdup(SearchCriteria), "&amp;", "&amp;amp;", 0);
1291 SearchCriteria = newSearchCriteria;
1293 #endif
1295 DPRINTF(E_DEBUG, L_HTTP, "Translated SearchCriteria: %s\n", SearchCriteria);
1297 sprintf(str_buf, "SELECT (select count(distinct DETAIL_ID) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1298 " where (OBJECT_ID glob '%s$*') and (%s))"
1299 " + "
1300 "(select count(*) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1301 " where (OBJECT_ID = '%s') and (%s))",
1302 ContainerID, SearchCriteria, ContainerID, SearchCriteria);
1303 //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Count SQL: %s\n", sql);
1304 ret = sql_get_table(db, str_buf, &result, NULL, NULL);
1305 if( ret == SQLITE_OK )
1307 totalMatches = atoi(result[1]);
1308 sqlite3_free_table(result);
1310 else
1312 /* Must be invalid SQL, so most likely bad or unhandled search criteria. */
1313 SoapError(h, 708, "Unsupported or invalid search criteria");
1314 goto search_error;
1316 /* Does the object even exist? */
1317 if( !totalMatches )
1319 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'",
1320 !strcmp(ContainerID, "*")?"0":ContainerID);
1321 if( ret <= 0 )
1323 SoapError(h, 710, "No such container");
1324 goto search_error;
1327 #ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
1328 ret = 0;
1329 if( totalMatches < 10000 )
1330 #endif
1331 orderBy = parse_sort_criteria(SortCriteria, &ret);
1332 /* If it's a DLNA client, return an error for bad sort criteria */
1333 if( (args.flags & FLAG_DLNA) && ret )
1335 SoapError(h, 709, "Unsupported or invalid sort criteria");
1336 goto search_error;
1339 sql = sqlite3_mprintf( SELECT_COLUMNS
1340 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1341 " where OBJECT_ID glob '%s$*' and (%s) %s "
1342 "%z %s"
1343 " limit %d, %d",
1344 ContainerID, SearchCriteria, groupBy,
1345 (*ContainerID == '*') ? NULL :
1346 sqlite3_mprintf("UNION ALL " SELECT_COLUMNS
1347 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1348 " where OBJECT_ID = '%s' and (%s) ", ContainerID, SearchCriteria),
1349 orderBy, StartingIndex, RequestedCount);
1350 DPRINTF(E_DEBUG, L_HTTP, "Search SQL: %s\n", sql);
1351 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1352 if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
1354 DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1355 sqlite3_free(zErrMsg);
1357 sqlite3_free(sql);
1358 strcat(resp, str_buf);
1359 ret = snprintf(str_buf, sizeof(str_buf), "&lt;/DIDL-Lite&gt;</Result>\n"
1360 "<NumberReturned>%u</NumberReturned>\n"
1361 "<TotalMatches>%u</TotalMatches>\n"
1362 "<UpdateID>%u</UpdateID>"
1363 "</u:SearchResponse>",
1364 args.returned, totalMatches, updateID);
1365 memcpy(resp+args.size, &str_buf, ret+1);
1366 args.size += ret;
1367 BuildSendAndCloseSoapResp(h, resp, args.size);
1368 search_error:
1369 ClearNameValueList(&data);
1370 if( orderBy )
1371 free(orderBy);
1372 if( newSearchCriteria )
1373 free(newSearchCriteria);
1374 free(resp);
1375 if( h->reqflags & FLAG_MS_PFS )
1377 sqlite3_free(ContainerID);
1382 If a control point calls QueryStateVariable on a state variable that is not
1383 buffered in memory within (or otherwise available from) the service,
1384 the service must return a SOAP fault with an errorCode of 404 Invalid Var.
1386 QueryStateVariable remains useful as a limited test tool but may not be
1387 part of some future versions of UPnP.
1389 static void
1390 QueryStateVariable(struct upnphttp * h, const char * action)
1392 static const char resp[] =
1393 "<u:%sResponse "
1394 "xmlns:u=\"%s\">"
1395 "<return>%s</return>"
1396 "</u:%sResponse>";
1398 char body[512];
1399 int bodylen;
1400 struct NameValueParserData data;
1401 const char * var_name;
1403 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1404 /*var_name = GetValueFromNameValueList(&data, "QueryStateVariable"); */
1405 /*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/
1406 var_name = GetValueFromNameValueList(&data, "varName");
1408 DPRINTF(E_INFO, L_HTTP, "QueryStateVariable(%.40s)\n", var_name);
1410 if(!var_name)
1412 SoapError(h, 402, "Invalid Args");
1414 else if(strcmp(var_name, "ConnectionStatus") == 0)
1416 bodylen = snprintf(body, sizeof(body), resp,
1417 action, "urn:schemas-upnp-org:control-1-0",
1418 "Connected", action);
1419 BuildSendAndCloseSoapResp(h, body, bodylen);
1421 else
1423 DPRINTF(E_WARN, L_HTTP, "%s: Unknown: %s\n", action, var_name?var_name:"");
1424 SoapError(h, 404, "Invalid Var");
1427 ClearNameValueList(&data);
1430 static void
1431 SamsungGetFeatureList(struct upnphttp * h, const char * action)
1433 static const char resp[] =
1434 "<u:X_GetFeatureListResponse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1435 "<FeatureList>"
1436 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
1437 "&lt;Features xmlns=\"urn:schemas-upnp-org:av:avs\""
1438 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
1439 " xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\"&gt;"
1440 "&lt;Feature name=\"samsung.com_BASICVIEW\" version=\"1\"&gt;"
1441 "&lt;container id=\"1\" type=\"object.item.audioItem\"/&gt;"
1442 "&lt;container id=\"2\" type=\"object.item.videoItem\"/&gt;"
1443 "&lt;container id=\"3\" type=\"object.item.imageItem\"/&gt;"
1444 "&lt;/Feature&gt;"
1445 "</FeatureList></u:X_GetFeatureListResponse>";
1447 BuildSendAndCloseSoapResp(h, resp, sizeof(resp));
1450 static const struct
1452 const char * methodName;
1453 void (*methodImpl)(struct upnphttp *, const char *);
1455 soapMethods[] =
1457 { "QueryStateVariable", QueryStateVariable},
1458 { "Browse", BrowseContentDirectory},
1459 { "Search", SearchContentDirectory},
1460 { "GetSearchCapabilities", GetSearchCapabilities},
1461 { "GetSortCapabilities", GetSortCapabilities},
1462 { "GetSystemUpdateID", GetSystemUpdateID},
1463 { "GetProtocolInfo", GetProtocolInfo},
1464 { "GetCurrentConnectionIDs", GetCurrentConnectionIDs},
1465 { "GetCurrentConnectionInfo", GetCurrentConnectionInfo},
1466 { "IsAuthorized", IsAuthorizedValidated},
1467 { "IsValidated", IsAuthorizedValidated},
1468 { "X_GetFeatureList", SamsungGetFeatureList},
1469 { 0, 0 }
1472 void
1473 ExecuteSoapAction(struct upnphttp * h, const char * action, int n)
1475 char * p;
1476 char * p2;
1477 int i, len, methodlen;
1479 i = 0;
1480 p = strchr(action, '#');
1482 if(p)
1484 p++;
1485 p2 = strchr(p, '"');
1486 if(p2)
1487 methodlen = p2 - p;
1488 else
1489 methodlen = n - (p - action);
1490 DPRINTF(E_DEBUG, L_HTTP, "SoapMethod: %.*s\n", methodlen, p);
1491 while(soapMethods[i].methodName)
1493 len = strlen(soapMethods[i].methodName);
1494 if(strncmp(p, soapMethods[i].methodName, len) == 0)
1496 soapMethods[i].methodImpl(h, soapMethods[i].methodName);
1497 return;
1499 i++;
1502 DPRINTF(E_WARN, L_HTTP, "SoapMethod: Unknown: %.*s\n", methodlen, p);
1505 SoapError(h, 401, "Invalid Action");
1508 /* Standard Errors:
1510 * errorCode errorDescription Description
1511 * -------- ---------------- -----------
1512 * 401 Invalid Action No action by that name at this service.
1513 * 402 Invalid Args Could be any of the following: not enough in args,
1514 * too many in args, no in arg by that name,
1515 * one or more in args are of the wrong data type.
1516 * 403 Out of Sync Out of synchronization.
1517 * 501 Action Failed May be returned in current state of service
1518 * prevents invoking that action.
1519 * 600-699 TBD Common action errors. Defined by UPnP Forum
1520 * Technical Committee.
1521 * 700-799 TBD Action-specific errors for standard actions.
1522 * Defined by UPnP Forum working committee.
1523 * 800-899 TBD Action-specific errors for non-standard actions.
1524 * Defined by UPnP vendor.
1526 void
1527 SoapError(struct upnphttp * h, int errCode, const char * errDesc)
1529 static const char resp[] =
1530 "<s:Envelope "
1531 "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
1532 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1533 "<s:Body>"
1534 "<s:Fault>"
1535 "<faultcode>s:Client</faultcode>"
1536 "<faultstring>UPnPError</faultstring>"
1537 "<detail>"
1538 "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">"
1539 "<errorCode>%d</errorCode>"
1540 "<errorDescription>%s</errorDescription>"
1541 "</UPnPError>"
1542 "</detail>"
1543 "</s:Fault>"
1544 "</s:Body>"
1545 "</s:Envelope>";
1547 char body[2048];
1548 int bodylen;
1550 DPRINTF(E_WARN, L_HTTP, "Returning UPnPError %d: %s\n", errCode, errDesc);
1551 bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc);
1552 BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen);
1553 SendResp_upnphttp(h);
1554 CloseSocket_upnphttp(h);