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.
52 #include <sys/socket.h>
56 #include <sys/types.h>
57 #include <arpa/inet.h>
58 #include <netinet/in.h>
63 #include "upnpglobalvars.h"
67 #include "upnpreplyparse.h"
68 #include "getifaddr.h"
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/\">"
83 static const char afterbody
[] =
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;
100 CloseSocket_upnphttp(h
);
104 GetSystemUpdateID(struct upnphttp
* h
, const char * action
)
106 static const char resp
[] =
115 bodylen
= snprintf(body
, sizeof(body
), resp
,
116 action
, "urn:schemas-upnp-org:service:ContentDirectory:1",
118 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
122 IsAuthorizedValidated(struct upnphttp
* h
, const char * action
)
124 static const char resp
[] =
127 "<Result>%d</Result>"
133 bodylen
= snprintf(body
, sizeof(body
), resp
,
134 action
, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
136 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
140 GetProtocolInfo(struct upnphttp
* h
, const char * action
)
142 static const char resp
[] =
146 RESOURCE_PROTOCOL_INFO_VALUES
154 bodylen
= asprintf(&body
, resp
,
155 action
, "urn:schemas-upnp-org:service:ConnectionManager:1",
157 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
162 GetSortCapabilities(struct upnphttp
* h
, const char * action
)
164 static const char resp
[] =
171 "upnp:originalTrackNumber"
178 bodylen
= snprintf(body
, sizeof(body
), resp
,
179 action
, "urn:schemas-upnp-org:service:ContentDirectory:1",
181 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
185 GetSearchCapabilities(struct upnphttp
* h
, const char * action
)
187 static const char resp
[] =
188 "<u:%sResponse xmlns:u=\"%s\">"
205 bodylen
= snprintf(body
, sizeof(body
), resp
,
206 action
, "urn:schemas-upnp-org:service:ContentDirectory:1",
208 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
212 GetCurrentConnectionIDs(struct upnphttp
* h
, const char * action
)
214 /* TODO: Use real data. - JM */
215 static const char resp
[] =
218 "<ConnectionIDs>0</ConnectionIDs>"
224 bodylen
= snprintf(body
, sizeof(body
), resp
,
225 action
, "urn:schemas-upnp-org:service:ConnectionManager:1",
227 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
231 GetCurrentConnectionInfo(struct upnphttp
* h
, const char * action
)
233 /* TODO: Use real data. - JM */
234 static const char resp
[] =
238 "<AVTransportID>-1</AVTransportID>"
239 "<ProtocolInfo></ProtocolInfo>"
240 "<PeerConnectionManager></PeerConnectionManager>"
241 "<PeerConnectionID>-1</PeerConnectionID>"
242 "<Direction>Output</Direction>"
243 "<Status>Unknown</Status>"
246 char body
[sizeof(resp
)+128];
249 bodylen
= snprintf(body
, sizeof(body
), resp
,
250 action
, "urn:schemas-upnp-org:service:ConnectionManager:1",
252 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
256 mime_to_ext(const char * mime
, char * buf
)
260 /* Audio extensions */
262 if( strcmp(mime
+6, "mpeg") == 0 )
264 else if( strcmp(mime
+6, "mp4") == 0 )
266 else if( strcmp(mime
+6, "x-ms-wma") == 0 )
268 else if( strcmp(mime
+6, "x-flac") == 0 )
270 else if( strcmp(mime
+6, "flac") == 0 )
272 else if( strcmp(mime
+6, "x-wav") == 0 )
274 else if( strncmp(mime
+6, "L16", 3) == 0 )
276 else if( strcmp(mime
+6, "3gpp") == 0 )
278 else if( strcmp(mime
, "application/ogg") == 0 )
284 if( strcmp(mime
+6, "avi") == 0 )
286 else if( strcmp(mime
+6, "divx") == 0 )
288 else if( strcmp(mime
+6, "x-msvideo") == 0 )
290 else if( strcmp(mime
+6, "mpeg") == 0 )
292 else if( strcmp(mime
+6, "mp4") == 0 )
294 else if( strcmp(mime
+6, "x-ms-wmv") == 0 )
296 else if( strcmp(mime
+6, "x-matroska") == 0 )
298 else if( strcmp(mime
+6, "x-mkv") == 0 )
300 else if( strcmp(mime
+6, "x-flv") == 0 )
302 else if( strcmp(mime
+6, "vnd.dlna.mpeg-tts") == 0 )
304 else if( strcmp(mime
+6, "quicktime") == 0 )
306 else if( strcmp(mime
+6, "3gpp") == 0 )
308 else if( strcmp(mime
+6, "x-tivo-mpeg") == 0 )
314 if( strcmp(mime
+6, "jpeg") == 0 )
316 else if( strcmp(mime
+6, "png") == 0 )
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
353 set_filter_flags(char * filter
, struct upnphttp
*h
)
355 char *item
, *saveptr
= NULL
;
358 if( !filter
|| (strlen(filter
) <= 1) )
360 if( h
->reqflags
& FLAG_SAMSUNG
)
361 flags
|= FILTER_DLNA_NAMESPACE
;
362 item
= strtok_r(filter
, ",", &saveptr
);
363 while( item
!= NULL
)
367 while( isspace(*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 )
432 else if( (strcmp(item
, "res@bitrate") == 0) ||
433 (strcmp(item
, "@bitrate") == 0) ||
434 ((strcmp(item
, "bitrate") == 0) && (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
)) )
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
)) )
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
)) )
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
)) )
465 flags
|= FILTER_RES_SAMPLEFREQUENCY
;
467 else if( (strcmp(item
, "res@size") == 0) ||
468 (strcmp(item
, "@size") == 0) ||
469 (strcmp(item
, "size") == 0) )
472 flags
|= FILTER_RES_SIZE
;
474 else if( strcmp(item
, "sec:CaptionInfo") == 0)
477 flags
|= FILTER_SEC_CAPTION_INFO
;
479 else if( strcmp(item
, "sec:CaptionInfoEx") == 0)
482 flags
|= FILTER_SEC_CAPTION_INFO_EX
;
484 item
= strtok_r(NULL
, ",", &saveptr
);
491 parse_sort_criteria(char * sortCriteria
, int * error
)
494 char *item
, *saveptr
;
495 int i
, ret
, reverse
, title_sorted
= 0;
501 if( (item
= strtok_r(sortCriteria
, ",", &saveptr
)) )
503 order
= malloc(4096);
504 strcpy(order
, "order by ");
506 for( i
=0; item
!= NULL
; i
++ )
515 else if( *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");
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");
539 printf("Unhandled SortCriteria [%s]\n", item
);
547 goto unhandled_order
;
551 strcat(order
, " DESC");
553 item
= strtok_r(NULL
, ",", &saveptr
);
560 /* Add a "tiebreaker" sort order */
562 strcat(order
, ", TITLE ASC");
568 add_resized_res(int srcw
, int srch
, int reqw
, int reqh
, char *dlna_pn
,
569 char *detailID
, struct Response
*args
)
574 if( args
->flags
& FLAG_NO_RESIZE
)
577 strcatf(args
->str
, "<res ");
578 if( args
->filter
& FILTER_RES_RESOLUTION
)
581 dsth
= ((((reqw
<<10)/srcw
)*srch
)>>10);
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\">"
589 "http://%s:%d/Resized/%s.jpg?width=%d,height=%d"
591 dlna_pn
, lan_addr
[args
->iface
].str
, runtime_vars
.port
,
592 detailID
, dstw
, dsth
);
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
, "<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
)
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\">"
623 "http://%s:%d/MediaItems/%s.%s"
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
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];
645 struct string_s
*str
= passed_args
->str
;
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
)
655 str
->data
= realloc(str
->data
, (str
->size
+DEFAULT_RESP_SIZE
));
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
);
664 DPRINTF(E_ERROR
, L_HTTP
, "UPnP SOAP response was too big, and realloc failed!\n");
667 #if MAX_RESPONSE_SIZE > 0
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
);
676 passed_args
->returned
++;
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
);
683 strcpy(dlna_buf
, "*");
685 if( runtime_vars
.root_container
&& strcmp(parent
, runtime_vars
.root_container
) == 0 )
688 if( strncmp(class, "item", 4) == 0 )
690 /* We may need special handling for certain MIME types */
693 if( passed_args
->flags
& FLAG_MIME_AVI_DIVX
)
695 if( strcmp(mime
, "video/x-msvideo") == 0 )
698 strcpy(mime
+6, "divx");
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
, "<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
, ">"
757 "<dc:title>%s</dc:title>"
758 "<upnp:class>object.%s</upnp:class>",
760 if( comment
&& (passed_args
->filter
& FILTER_DC_DESCRIPTION
) ) {
761 ret
= strcatf(str
, "<dc:description>%.384s</dc:description>", comment
);
763 if( creator
&& (passed_args
->filter
& FILTER_DC_CREATOR
) ) {
764 ret
= strcatf(str
, "<dc:creator>%s</dc:creator>", creator
);
766 if( date
&& (passed_args
->filter
& FILTER_DC_DATE
) ) {
767 ret
= strcatf(str
, "<dc:date>%s</dc:date>", date
);
769 if( (passed_args
->flags
& FLAG_SAMSUNG
) && (passed_args
->filter
& FILTER_SEC_CAPTION_INFO_EX
) ) {
771 ret
= strcatf(str
, "<sec:dcmInfo>CREATIONDATE=0,FOLDER=%s,BM=%d</sec:dcmInfo>",
772 title
, sql_get_int_field(db
, "SELECT SEC from BOOKMARKS where ID = '%s'", detailID
));
775 if( (*mime
== 'v') && (passed_args
->filter
& FILTER_UPNP_ACTOR
) ) {
776 ret
= strcatf(str
, "<upnp:actor>%s</upnp:actor>", artist
);
778 if( passed_args
->filter
& FILTER_UPNP_ARTIST
) {
779 ret
= strcatf(str
, "<upnp:artist>%s</upnp:artist>", artist
);
782 if( album
&& (passed_args
->filter
& FILTER_UPNP_ALBUM
) ) {
783 ret
= strcatf(str
, "<upnp:album>%s</upnp:album>", album
);
785 if( genre
&& (passed_args
->filter
& FILTER_UPNP_GENRE
) ) {
786 ret
= strcatf(str
, "<upnp:genre>%s</upnp:genre>", 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
, "<upnp:originalTrackNumber>%s</upnp:originalTrackNumber>", 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
, "<res protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\">"
799 "http://%s:%d/AlbumArt/%s-%s.jpg"
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
, "<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
, ">http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>",
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
, "<upnp:album>%s</upnp:album>", "[No Keywords]");
815 /* EVA2000 doesn't seem to handle embedded thumbnails */
816 if( passed_args
->client
!= ENetgearEVA2000
&& tn
&& atoi(tn
) ) {
817 ret
= strcatf(str
, "<upnp:albumArtURI>"
818 "http://%s:%d/Thumbnails/%s.jpg"
819 "</upnp:albumArtURI>",
820 lan_addr
[passed_args
->iface
].str
, runtime_vars
.port
, detailID
);
822 ret
= strcatf(str
, "<upnp:albumArtURI>"
823 "http://%s:%d/Resized/%s.jpg?width=160,height=160"
824 "</upnp:albumArtURI>",
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
, "<res protocolInfo=\"http-get:*:%s:%s\">"
832 "http://%s:%d/Thumbnails/%s.jpg"
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
);
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
, "<res protocolInfo=\"http-get:*:%s:%s\">"
850 "http://%s:%d/Thumbnails/%s.jpg"
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
) {
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
);
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
);
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
);
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) */
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
);
924 if( sql_get_int_field(db
, "SELECT ID from CAPTIONS where ID = '%s'", detailID
) > 0 )
926 ret
= strcatf(str
, "<res protocolInfo=\"http-get:*:text/srt:*\">"
927 "http://%s:%d/Captions/%s.srt"
929 lan_addr
[passed_args
->iface
].str
, runtime_vars
.port
, detailID
);
937 ret
= strcatf(str
, "</item>");
939 else if( strncmp(class, "container", 9) == 0 )
941 ret
= strcatf(str
, "<container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id
, parent
);
942 if( passed_args
->filter
& FILTER_CHILDCOUNT
)
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
, ">"
955 "<upnp:searchClass includeDerived=\"1\">object.item.audioItem</upnp:searchClass>"
956 "<upnp:searchClass includeDerived=\"1\">object.item.imageItem</upnp:searchClass>"
957 "<upnp:searchClass includeDerived=\"1\">object.item.videoItem</upnp:searchClass");
960 ret
= strcatf(str
, ">"
961 "<dc:title>%s</dc:title>"
962 "<upnp:class>object.%s</upnp:class>",
964 if( creator
&& (passed_args
->filter
& FILTER_DC_CREATOR
) ) {
965 ret
= strcatf(str
, "<dc:creator>%s</dc:creator>", creator
);
967 if( genre
&& (passed_args
->filter
& FILTER_UPNP_GENRE
) ) {
968 ret
= strcatf(str
, "<upnp:genre>%s</upnp:genre>", genre
);
970 if( artist
&& (passed_args
->filter
& FILTER_UPNP_ARTIST
) ) {
971 ret
= strcatf(str
, "<upnp:artist>%s</upnp:artist>", artist
);
973 if( album_art
&& atoi(album_art
) && (passed_args
->filter
& FILTER_UPNP_ALBUMARTURI
) ) {
974 ret
= strcatf(str
, "<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
, ">http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>",
979 lan_addr
[passed_args
->iface
].str
, runtime_vars
.port
, album_art
, detailID
);
981 ret
= strcatf(str
, "</container>");
988 BrowseContentDirectory(struct upnphttp
* h
, const char * action
)
990 static const char resp0
[] =
992 "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
995 CONTENT_DIRECTORY_SCHEMAS
;
996 char *zErrMsg
= NULL
;
998 struct Response args
;
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");
1029 if( !ObjectID
&& !(ObjectID
= GetValueFromNameValueList(&data
, "ContainerID")) )
1031 SoapError(h
, 701, "No such object 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
, ">\n");
1048 args
.requested
= RequestedCount
;
1049 args
.client
= h
->req_client
;
1050 args
.flags
= h
->reqflags
;
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
);
1063 args
.flags
|= FLAG_FREE_OBJECT_ID
;
1067 DPRINTF(E_DEBUG
, L_HTTP
, "Browsing ContentDirectory:\n"
1070 " * StartingIndex: %d\n"
1071 " * BrowseFlag: %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
;
1085 ObjectID
= runtime_vars
.root_container
;
1089 if( args
.flags
& FLAG_AUDIO_ONLY
)
1090 ObjectID
= MUSIC_ID
;
1094 if( strcmp(BrowseFlag
+6, "Metadata") == 0 )
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",
1102 ret
= sqlite3_exec(db
, sql
, callback
, (void *) &args
, &zErrMsg
);
1103 totalMatches
= args
.returned
;
1107 ret
= sql_get_int_field(db
, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", ObjectID
);
1108 totalMatches
= (ret
> 0) ? ret
: 0;
1112 #ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
1113 if( totalMatches
< 10000 )
1115 orderBy
= parse_sort_criteria(SortCriteria
, &ret
);
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");
1124 asprintf(&orderBy
, "order by length(OBJECT_ID), OBJECT_ID");
1126 else if( args
.client
== ERokuSoundBridge
)
1129 if( totalMatches
< 10000 )
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");
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
);
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? */
1157 ret
= sql_get_int_field(db
, "SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", ObjectID
);
1160 SoapError(h
, 701, "No such object error");
1164 ret
= strcatf(&str
, "</DIDL-Lite></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
);
1172 ClearNameValueList(&data
);
1173 if( args
.flags
& FLAG_FREE_OBJECT_ID
)
1174 sqlite3_free(ObjectID
);
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\">"
1187 CONTENT_DIRECTORY_SCHEMAS
;
1188 char *zErrMsg
= NULL
;
1190 struct Response args
;
1191 struct string_s str
;
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
);
1219 if( !(ContainerID
= GetValueFromNameValueList(&data
, "ObjectID")) )
1221 SoapError(h
, 701, "No such object 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
, ">\n");
1239 args
.requested
= RequestedCount
;
1240 args
.client
= h
->req_client
;
1241 args
.flags
= h
->reqflags
;
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
);
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 */
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
);
1272 DPRINTF(E_DEBUG
, L_HTTP
, "Searching ContentDirectory:\n"
1275 " * StartingIndex: %d\n"
1276 " * SearchCriteria: %s\n"
1278 " * SortCriteria: %s\n",
1279 ContainerID
, RequestedCount
, StartingIndex
,
1280 SearchCriteria
, Filter
, SortCriteria
);
1282 if( strcmp(ContainerID
, "0") == 0 )
1284 else if( strcmp(ContainerID
, MUSIC_ALL_ID
) == 0 )
1286 if( !SearchCriteria
)
1288 newSearchCriteria
= strdup("1 = 1");
1289 SearchCriteria
= newSearchCriteria
;
1293 SearchCriteria
= modifyString(SearchCriteria
, """, "\"", 0);
1294 SearchCriteria
= modifyString(SearchCriteria
, "'", "'", 0);
1295 SearchCriteria
= modifyString(SearchCriteria
, "<", "<", 0);
1296 SearchCriteria
= modifyString(SearchCriteria
, ">", ">", 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
, "&") )
1326 if( newSearchCriteria
)
1327 newSearchCriteria
= modifyString(newSearchCriteria
, "&", "&amp;", 0);
1329 newSearchCriteria
= modifyString(strdup(SearchCriteria
), "&", "&amp;", 0);
1330 SearchCriteria
= newSearchCriteria
;
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))"
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");
1349 /* Does the object even exist? */
1352 ret
= sql_get_int_field(db
, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'",
1353 !strcmp(ContainerID
, "*")?"0":ContainerID
);
1356 SoapError(h
, 710, "No such container");
1360 #ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
1362 if( totalMatches
< 10000 )
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");
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 "
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
);
1391 ret
= strcatf(&str
, "</DIDL-Lite></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
);
1399 ClearNameValueList(&data
);
1400 if( args
.flags
& FLAG_FREE_OBJECT_ID
)
1401 sqlite3_free(ContainerID
);
1403 free(newSearchCriteria
);
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.
1416 QueryStateVariable(struct upnphttp
* h
, const char * action
)
1418 static const char resp
[] =
1421 "<return>%s</return>"
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
);
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
);
1449 DPRINTF(E_WARN
, L_HTTP
, "%s: Unknown: %s\n", action
, var_name
?var_name
:"");
1450 SoapError(h
, 404, "Invalid Var");
1453 ClearNameValueList(&data
);
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\">"
1462 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
1463 "<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\">"
1466 "<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">"
1467 "<container id=\"1\" type=\"object.item.audioItem\"/>"
1468 "<container id=\"2\" type=\"object.item.videoItem\"/>"
1469 "<container id=\"3\" type=\"object.item.imageItem\"/>"
1471 "</FeatureList></u:X_GetFeatureListResponse>";
1473 BuildSendAndCloseSoapResp(h
, resp
, sizeof(resp
)-1);
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
;
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"
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);
1501 SoapError(h
, 402, "Invalid Args");
1503 ClearNameValueList(&data
);
1508 const char * methodName
;
1509 void (*methodImpl
)(struct upnphttp
*, const char *);
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
},
1530 ExecuteSoapAction(struct upnphttp
* h
, const char * action
, int n
)
1534 p
= strchr(action
, '#');
1542 p2
= strchr(p
, '"');
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
);
1559 DPRINTF(E_WARN
, L_HTTP
, "SoapMethod: Unknown: %.*s\n", methodlen
, p
);
1562 SoapError(h
, 401, "Invalid Action");
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.
1584 SoapError(struct upnphttp
* h
, int errCode
, const char * errDesc
)
1586 static const char resp
[] =
1588 "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
1589 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1592 "<faultcode>s:Client</faultcode>"
1593 "<faultstring>UPnPError</faultstring>"
1595 "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">"
1596 "<errorCode>%d</errorCode>"
1597 "<errorDescription>%s</errorDescription>"
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
);