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.
54 #include <sys/socket.h>
58 #include <sys/types.h>
59 #include <arpa/inet.h>
60 #include <netinet/in.h>
64 #include "upnpglobalvars.h"
68 #include "containers.h"
69 #include "upnpreplyparse.h"
70 #include "getifaddr.h"
75 #ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
76 # define __SORT_LIMIT if( totalMatches < 10000 )
83 * errorCode errorDescription Description
84 * -------- ---------------- -----------
85 * 401 Invalid Action No action by that name at this service.
86 * 402 Invalid Args Could be any of the following: not enough in args,
87 * too many in args, no in arg by that name,
88 * one or more in args are of the wrong data type.
89 * 403 Out of Sync Out of synchronization.
90 * 501 Action Failed May be returned in current state of service
91 * prevents invoking that action.
92 * 600-699 TBD Common action errors. Defined by UPnP Forum
93 * Technical Committee.
94 * 700-799 TBD Action-specific errors for standard actions.
95 * Defined by UPnP Forum working committee.
96 * 800-899 TBD Action-specific errors for non-standard actions.
97 * Defined by UPnP vendor.
100 SoapError(struct upnphttp
* h
, int errCode
, const char * errDesc
)
102 static const char resp
[] =
104 "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
105 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
108 "<faultcode>s:Client</faultcode>"
109 "<faultstring>UPnPError</faultstring>"
111 "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">"
112 "<errorCode>%d</errorCode>"
113 "<errorDescription>%s</errorDescription>"
123 DPRINTF(E_WARN
, L_HTTP
, "Returning UPnPError %d: %s\n", errCode
, errDesc
);
124 bodylen
= snprintf(body
, sizeof(body
), resp
, errCode
, errDesc
);
125 BuildResp2_upnphttp(h
, 500, "Internal Server Error", body
, bodylen
);
126 SendResp_upnphttp(h
);
127 CloseSocket_upnphttp(h
);
131 BuildSendAndCloseSoapResp(struct upnphttp
* h
,
132 const char * body
, int bodylen
)
134 static const char beforebody
[] =
135 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
136 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
137 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
140 static const char afterbody
[] =
144 if (!body
|| bodylen
< 0)
150 BuildHeader_upnphttp(h
, 200, "OK", sizeof(beforebody
) - 1
151 + sizeof(afterbody
) - 1 + bodylen
);
153 memcpy(h
->res_buf
+ h
->res_buflen
, beforebody
, sizeof(beforebody
) - 1);
154 h
->res_buflen
+= sizeof(beforebody
) - 1;
156 memcpy(h
->res_buf
+ h
->res_buflen
, body
, bodylen
);
157 h
->res_buflen
+= bodylen
;
159 memcpy(h
->res_buf
+ h
->res_buflen
, afterbody
, sizeof(afterbody
) - 1);
160 h
->res_buflen
+= sizeof(afterbody
) - 1;
162 SendResp_upnphttp(h
);
163 CloseSocket_upnphttp(h
);
167 GetSystemUpdateID(struct upnphttp
* h
, const char * action
)
169 static const char resp
[] =
178 bodylen
= snprintf(body
, sizeof(body
), resp
,
179 action
, "urn:schemas-upnp-org:service:ContentDirectory:1",
181 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
185 IsAuthorizedValidated(struct upnphttp
* h
, const char * action
)
187 static const char resp
[] =
190 "<Result>%d</Result>"
194 struct NameValueParserData data
;
197 ParseNameValue(h
->req_buf
+ h
->req_contentoff
, h
->req_contentlen
, &data
, XML_STORE_EMPTY_FL
);
198 id
= GetValueFromNameValueList(&data
, "DeviceID");
202 bodylen
= snprintf(body
, sizeof(body
), resp
,
203 action
, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
205 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
208 SoapError(h
, 402, "Invalid Args");
210 ClearNameValueList(&data
);
214 RegisterDevice(struct upnphttp
* h
, const char * action
)
216 static const char resp
[] =
219 "<RegistrationRespMsg>%s</RegistrationRespMsg>"
225 bodylen
= snprintf(body
, sizeof(body
), resp
,
226 action
, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
228 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
232 GetProtocolInfo(struct upnphttp
* h
, const char * action
)
234 static const char resp
[] =
238 RESOURCE_PROTOCOL_INFO_VALUES
246 bodylen
= asprintf(&body
, resp
,
247 action
, "urn:schemas-upnp-org:service:ConnectionManager:1",
249 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
254 GetSortCapabilities(struct upnphttp
* h
, const char * action
)
256 static const char resp
[] =
264 "upnp:originalTrackNumber"
271 bodylen
= snprintf(body
, sizeof(body
), resp
,
272 action
, "urn:schemas-upnp-org:service:ContentDirectory:1",
274 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
278 GetSearchCapabilities(struct upnphttp
* h
, const char * action
)
280 static const char resp
[] =
281 "<u:%sResponse xmlns:u=\"%s\">"
300 bodylen
= snprintf(body
, sizeof(body
), resp
,
301 action
, "urn:schemas-upnp-org:service:ContentDirectory:1",
303 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
307 GetCurrentConnectionIDs(struct upnphttp
* h
, const char * action
)
309 /* TODO: Use real data. - JM */
310 static const char resp
[] =
313 "<ConnectionIDs>0</ConnectionIDs>"
319 bodylen
= snprintf(body
, sizeof(body
), resp
,
320 action
, "urn:schemas-upnp-org:service:ConnectionManager:1",
322 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
326 GetCurrentConnectionInfo(struct upnphttp
* h
, const char * action
)
328 /* TODO: Use real data. - JM */
329 static const char resp
[] =
333 "<AVTransportID>-1</AVTransportID>"
334 "<ProtocolInfo></ProtocolInfo>"
335 "<PeerConnectionManager></PeerConnectionManager>"
336 "<PeerConnectionID>-1</PeerConnectionID>"
337 "<Direction>Output</Direction>"
338 "<Status>Unknown</Status>"
341 char body
[sizeof(resp
)+128];
342 struct NameValueParserData data
;
347 ParseNameValue(h
->req_buf
+ h
->req_contentoff
, h
->req_contentlen
, &data
, XML_STORE_EMPTY_FL
);
348 id_str
= GetValueFromNameValueList(&data
, "ConnectionID");
349 DPRINTF(E_INFO
, L_HTTP
, "GetCurrentConnectionInfo(%s)\n", id_str
);
351 id
= strtol(id_str
, &endptr
, 10);
352 if (!id_str
|| endptr
== id_str
)
354 SoapError(h
, 402, "Invalid Args");
358 SoapError(h
, 701, "No such object error");
363 bodylen
= snprintf(body
, sizeof(body
), resp
,
364 action
, "urn:schemas-upnp-org:service:ConnectionManager:1",
366 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
368 ClearNameValueList(&data
);
371 /* Standard DLNA/UPnP filter flags */
372 #define FILTER_CHILDCOUNT 0x00000001
373 #define FILTER_DC_CREATOR 0x00000002
374 #define FILTER_DC_DATE 0x00000004
375 #define FILTER_DC_DESCRIPTION 0x00000008
376 #define FILTER_DLNA_NAMESPACE 0x00000010
377 #define FILTER_REFID 0x00000020
378 #define FILTER_RES 0x00000040
379 #define FILTER_RES_BITRATE 0x00000080
380 #define FILTER_RES_DURATION 0x00000100
381 #define FILTER_RES_NRAUDIOCHANNELS 0x00000200
382 #define FILTER_RES_RESOLUTION 0x00000400
383 #define FILTER_RES_SAMPLEFREQUENCY 0x00000800
384 #define FILTER_RES_SIZE 0x00001000
385 #define FILTER_SEARCHABLE 0x00002000
386 #define FILTER_UPNP_ACTOR 0x00004000
387 #define FILTER_UPNP_ALBUM 0x00008000
388 #define FILTER_UPNP_ALBUMARTURI 0x00010000
389 #define FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID 0x00020000
390 #define FILTER_UPNP_ARTIST 0x00040000
391 #define FILTER_UPNP_GENRE 0x00080000
392 #define FILTER_UPNP_ORIGINALTRACKNUMBER 0x00100000
393 #define FILTER_UPNP_SEARCHCLASS 0x00200000
394 #define FILTER_UPNP_STORAGEUSED 0x00400000
395 /* Vendor-specific filter flags */
396 #define FILTER_SEC_CAPTION_INFO_EX 0x01000000
397 #define FILTER_SEC_DCM_INFO 0x02000000
398 #define FILTER_PV_SUBTITLE_FILE_TYPE 0x04000000
399 #define FILTER_PV_SUBTITLE_FILE_URI 0x08000000
400 #define FILTER_PV_SUBTITLE 0x0C000000
401 #define FILTER_AV_MEDIA_CLASS 0x10000000
404 set_filter_flags(char *filter
, struct upnphttp
*h
)
406 char *item
, *saveptr
= NULL
;
408 int samsung
= h
->req_client
&& (h
->req_client
->type
->flags
& FLAG_SAMSUNG
);
410 if( !filter
|| (strlen(filter
) <= 1) ) {
411 /* Not the full 32 bits. Skip vendor-specific stuff by default. */
414 flags
|= FILTER_SEC_CAPTION_INFO_EX
| FILTER_SEC_DCM_INFO
;
420 flags
|= FILTER_DLNA_NAMESPACE
;
421 item
= strtok_r(filter
, ",", &saveptr
);
422 while( item
!= NULL
)
426 while( isspace(*item
) )
428 if( strcmp(item
, "@childCount") == 0 )
430 flags
|= FILTER_CHILDCOUNT
;
432 else if( strcmp(item
, "@searchable") == 0 )
434 flags
|= FILTER_SEARCHABLE
;
436 else if( strcmp(item
, "dc:creator") == 0 )
438 flags
|= FILTER_DC_CREATOR
;
440 else if( strcmp(item
, "dc:date") == 0 )
442 flags
|= FILTER_DC_DATE
;
444 else if( strcmp(item
, "dc:description") == 0 )
446 flags
|= FILTER_DC_DESCRIPTION
;
448 else if( strcmp(item
, "dlna") == 0 )
450 flags
|= FILTER_DLNA_NAMESPACE
;
452 else if( strcmp(item
, "@refID") == 0 )
454 flags
|= FILTER_REFID
;
456 else if( strcmp(item
, "upnp:album") == 0 )
458 flags
|= FILTER_UPNP_ALBUM
;
460 else if( strcmp(item
, "upnp:albumArtURI") == 0 )
462 flags
|= FILTER_UPNP_ALBUMARTURI
;
464 flags
|= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID
;
466 else if( strcmp(item
, "upnp:albumArtURI@dlna:profileID") == 0 )
468 flags
|= FILTER_UPNP_ALBUMARTURI
;
469 flags
|= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID
;
471 else if( strcmp(item
, "upnp:artist") == 0 )
473 flags
|= FILTER_UPNP_ARTIST
;
475 else if( strcmp(item
, "upnp:actor") == 0 )
477 flags
|= FILTER_UPNP_ACTOR
;
479 else if( strcmp(item
, "upnp:genre") == 0 )
481 flags
|= FILTER_UPNP_GENRE
;
483 else if( strcmp(item
, "upnp:originalTrackNumber") == 0 )
485 flags
|= FILTER_UPNP_ORIGINALTRACKNUMBER
;
487 else if( strcmp(item
, "upnp:searchClass") == 0 )
489 flags
|= FILTER_UPNP_SEARCHCLASS
;
491 else if( strcmp(item
, "upnp:storageUsed") == 0 )
493 flags
|= FILTER_UPNP_STORAGEUSED
;
495 else if( strcmp(item
, "res") == 0 )
499 else if( (strcmp(item
, "res@bitrate") == 0) ||
500 (strcmp(item
, "@bitrate") == 0) ||
501 ((strcmp(item
, "bitrate") == 0) && (flags
& FILTER_RES
)) )
504 flags
|= FILTER_RES_BITRATE
;
506 else if( (strcmp(item
, "res@duration") == 0) ||
507 (strcmp(item
, "@duration") == 0) ||
508 ((strcmp(item
, "duration") == 0) && (flags
& FILTER_RES
)) )
511 flags
|= FILTER_RES_DURATION
;
513 else if( (strcmp(item
, "res@nrAudioChannels") == 0) ||
514 (strcmp(item
, "@nrAudioChannels") == 0) ||
515 ((strcmp(item
, "nrAudioChannels") == 0) && (flags
& FILTER_RES
)) )
518 flags
|= FILTER_RES_NRAUDIOCHANNELS
;
520 else if( (strcmp(item
, "res@resolution") == 0) ||
521 (strcmp(item
, "@resolution") == 0) ||
522 ((strcmp(item
, "resolution") == 0) && (flags
& FILTER_RES
)) )
525 flags
|= FILTER_RES_RESOLUTION
;
527 else if( (strcmp(item
, "res@sampleFrequency") == 0) ||
528 (strcmp(item
, "@sampleFrequency") == 0) ||
529 ((strcmp(item
, "sampleFrequency") == 0) && (flags
& FILTER_RES
)) )
532 flags
|= FILTER_RES_SAMPLEFREQUENCY
;
534 else if( (strcmp(item
, "res@size") == 0) ||
535 (strcmp(item
, "@size") == 0) ||
536 (strcmp(item
, "size") == 0) )
539 flags
|= FILTER_RES_SIZE
;
541 else if( strcmp(item
, "sec:CaptionInfoEx") == 0 )
543 flags
|= FILTER_SEC_CAPTION_INFO_EX
;
545 else if( strcmp(item
, "sec:dcmInfo") == 0 )
547 flags
|= FILTER_SEC_DCM_INFO
;
549 else if( strcmp(item
, "res@pv:subtitleFileType") == 0 )
551 flags
|= FILTER_PV_SUBTITLE_FILE_TYPE
;
553 else if( strcmp(item
, "res@pv:subtitleFileUri") == 0 )
555 flags
|= FILTER_PV_SUBTITLE_FILE_URI
;
557 else if( strcmp(item
, "av:mediaClass") == 0 )
559 flags
|= FILTER_AV_MEDIA_CLASS
;
561 item
= strtok_r(NULL
, ",", &saveptr
);
568 parse_sort_criteria(char *sortCriteria
, int *error
)
571 char *item
, *saveptr
;
572 int i
, ret
, reverse
, title_sorted
= 0;
576 if( force_sort_criteria
)
577 sortCriteria
= strdup(force_sort_criteria
);
581 if( (item
= strtok_r(sortCriteria
, ",", &saveptr
)) )
583 order
= malloc(4096);
587 strcatf(&str
, "order by ");
589 for( i
= 0; item
!= NULL
; i
++ )
598 else if( *item
== '-' )
605 DPRINTF(E_ERROR
, L_HTTP
, "No order specified [%s]\n", item
);
608 if( strcasecmp(item
, "upnp:class") == 0 )
610 strcatf(&str
, "o.CLASS");
612 else if( strcasecmp(item
, "dc:title") == 0 )
614 strcatf(&str
, "d.TITLE");
617 else if( strcasecmp(item
, "dc:date") == 0 )
619 strcatf(&str
, "d.DATE");
621 else if( strcasecmp(item
, "upnp:originalTrackNumber") == 0 )
623 strcatf(&str
, "d.DISC, d.TRACK");
625 else if( strcasecmp(item
, "upnp:album") == 0 )
627 strcatf(&str
, "d.ALBUM");
631 DPRINTF(E_ERROR
, L_HTTP
, "Unhandled SortCriteria [%s]\n", item
);
640 goto unhandled_order
;
644 strcatf(&str
, " DESC");
646 item
= strtok_r(NULL
, ",", &saveptr
);
651 if( force_sort_criteria
)
655 /* Add a "tiebreaker" sort order */
657 strcatf(&str
, ", TITLE ASC");
659 if( force_sort_criteria
)
666 add_resized_res(int srcw
, int srch
, int reqw
, int reqh
, char *dlna_pn
,
667 char *detailID
, struct Response
*args
)
672 if( (args
->flags
& FLAG_NO_RESIZE
) && reqw
> 160 && reqh
> 160 )
675 strcatf(args
->str
, "<res ");
676 if( args
->filter
& FILTER_RES_RESOLUTION
)
679 dsth
= ((((reqw
<<10)/srcw
)*srch
)>>10);
682 dstw
= (((reqh
<<10)/srch
) * srcw
>>10);
684 strcatf(args
->str
, "resolution=\"%dx%d\" ", dstw
, dsth
);
686 strcatf(args
->str
, "protocolInfo=\"http-get:*:image/jpeg:"
687 "DLNA.ORG_PN=%s;DLNA.ORG_CI=1;DLNA.ORG_FLAGS=%08X%024X\">"
688 "http://%s:%d/Resized/%s.jpg?width=%d,height=%d"
690 dlna_pn
, DLNA_FLAG_DLNA_V1_5
|DLNA_FLAG_HTTP_STALLING
|DLNA_FLAG_TM_B
|DLNA_FLAG_TM_I
, 0,
691 lan_addr
[args
->iface
].str
, runtime_vars
.port
,
692 detailID
, dstw
, dsth
);
696 add_res(char *size
, char *duration
, char *bitrate
, char *sampleFrequency
,
697 char *nrAudioChannels
, char *resolution
, char *dlna_pn
, char *mime
,
698 char *detailID
, const char *ext
, struct Response
*args
)
700 strcatf(args
->str
, "<res ");
701 if( size
&& (args
->filter
& FILTER_RES_SIZE
) ) {
702 strcatf(args
->str
, "size=\"%s\" ", size
);
704 if( duration
&& (args
->filter
& FILTER_RES_DURATION
) ) {
705 strcatf(args
->str
, "duration=\"%s\" ", duration
);
707 if( bitrate
&& (args
->filter
& FILTER_RES_BITRATE
) ) {
708 int br
= atoi(bitrate
);
709 if(args
->flags
& FLAG_MS_PFS
)
711 strcatf(args
->str
, "bitrate=\"%d\" ", br
);
713 if( sampleFrequency
&& (args
->filter
& FILTER_RES_SAMPLEFREQUENCY
) ) {
714 strcatf(args
->str
, "sampleFrequency=\"%s\" ", sampleFrequency
);
716 if( nrAudioChannels
&& (args
->filter
& FILTER_RES_NRAUDIOCHANNELS
) ) {
717 strcatf(args
->str
, "nrAudioChannels=\"%s\" ", nrAudioChannels
);
719 if( resolution
&& (args
->filter
& FILTER_RES_RESOLUTION
) ) {
720 strcatf(args
->str
, "resolution=\"%s\" ", resolution
);
722 if( args
->filter
& FILTER_PV_SUBTITLE
)
724 if( args
->flags
& FLAG_HAS_CAPTIONS
)
726 if( args
->filter
& FILTER_PV_SUBTITLE_FILE_TYPE
)
727 strcatf(args
->str
, "pv:subtitleFileType=\"SRT\" ");
728 if( args
->filter
& FILTER_PV_SUBTITLE_FILE_URI
)
729 strcatf(args
->str
, "pv:subtitleFileUri=\"http://%s:%d/Captions/%s.srt\" ",
730 lan_addr
[args
->iface
].str
, runtime_vars
.port
, detailID
);
733 strcatf(args
->str
, "protocolInfo=\"http-get:*:%s:%s\">"
734 "http://%s:%d/MediaItems/%s.%s"
736 mime
, dlna_pn
, lan_addr
[args
->iface
].str
,
737 runtime_vars
.port
, detailID
, ext
);
741 get_child_count(const char *object
, struct magic_container_s
*magic
)
745 if (magic
&& magic
->child_count
)
746 ret
= sql_get_int_field(db
, "SELECT count(*) from %s", magic
->child_count
);
747 else if (magic
&& magic
->objectid
&& *(magic
->objectid
))
748 ret
= sql_get_int_field(db
, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", *(magic
->objectid
));
750 ret
= sql_get_int_field(db
, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", object
);
752 return (ret
> 0) ? ret
: 0;
756 object_exists(const char *object
)
759 ret
= sql_get_int_field(db
, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'",
760 strcmp(object
, "*") == 0 ? "0" : object
);
764 #define COLUMNS "o.DETAIL_ID, o.CLASS," \
765 " d.SIZE, d.TITLE, d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST," \
766 " d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RESOLUTION," \
767 " d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.ROTATION, d.DISC "
768 #define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, o.REF_ID, " COLUMNS
770 #define NON_ZERO(x) (x && atoi(x))
771 #define IS_ZERO(x) (!x || !atoi(x))
774 callback(void *args
, int argc
, char **argv
, char **azColName
)
776 struct Response
*passed_args
= (struct Response
*)args
;
777 char *id
= argv
[0], *parent
= argv
[1], *refID
= argv
[2], *detailID
= argv
[3], *class = argv
[4], *size
= argv
[5], *title
= argv
[6],
778 *duration
= argv
[7], *bitrate
= argv
[8], *sampleFrequency
= argv
[9], *artist
= argv
[10], *album
= argv
[11],
779 *genre
= argv
[12], *comment
= argv
[13], *nrAudioChannels
= argv
[14], *track
= argv
[15], *date
= argv
[16], *resolution
= argv
[17],
780 *tn
= argv
[18], *creator
= argv
[19], *dlna_pn
= argv
[20], *mime
= argv
[21], *album_art
= argv
[22], *rotate
= argv
[23];
783 struct string_s
*str
= passed_args
->str
;
786 /* Make sure we have at least 8KB left of allocated memory to finish the response. */
787 if( str
->off
> (str
->size
- 8192) )
789 #if MAX_RESPONSE_SIZE > 0
790 if( (str
->size
+DEFAULT_RESP_SIZE
) <= MAX_RESPONSE_SIZE
)
793 str
->data
= realloc(str
->data
, (str
->size
+DEFAULT_RESP_SIZE
));
796 str
->size
+= DEFAULT_RESP_SIZE
;
797 DPRINTF(E_DEBUG
, L_HTTP
, "UPnP SOAP response enlarged to %lu. [%d results so far]\n",
798 (unsigned long)str
->size
, passed_args
->returned
);
802 DPRINTF(E_ERROR
, L_HTTP
, "UPnP SOAP response was too big, and realloc failed!\n");
805 #if MAX_RESPONSE_SIZE > 0
809 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
);
814 passed_args
->returned
++;
815 passed_args
->flags
&= ~RESPONSE_FLAGS
;
817 if( strncmp(class, "item", 4) == 0 )
819 uint32_t dlna_flags
= DLNA_FLAG_DLNA_V1_5
|DLNA_FLAG_HTTP_STALLING
|DLNA_FLAG_TM_B
;
820 char *alt_title
= NULL
;
821 /* We may need special handling for certain MIME types */
824 dlna_flags
|= DLNA_FLAG_TM_S
;
825 if( passed_args
->flags
& FLAG_MIME_AVI_DIVX
)
827 if( strcmp(mime
, "video/x-msvideo") == 0 )
830 strcpy(mime
+6, "divx");
832 strcpy(mime
+6, "avi");
835 else if( passed_args
->flags
& FLAG_MIME_AVI_AVI
)
837 if( strcmp(mime
, "video/x-msvideo") == 0 )
839 strcpy(mime
+6, "avi");
842 else if( passed_args
->client
== EFreeBox
&& dlna_pn
)
844 if( strncmp(dlna_pn
, "AVC_TS", 6) == 0 ||
845 strncmp(dlna_pn
, "MPEG_TS", 7) == 0 )
847 strcpy(mime
+6, "mp2t");
850 if( !(passed_args
->flags
& FLAG_DLNA
) )
852 if( strcmp(mime
+6, "vnd.dlna.mpeg-tts") == 0 )
854 strcpy(mime
+6, "mpeg");
857 if( (passed_args
->flags
& FLAG_CAPTION_RES
) ||
858 (passed_args
->filter
& (FILTER_SEC_CAPTION_INFO_EX
|FILTER_PV_SUBTITLE
)) )
860 if( sql_get_int_field(db
, "SELECT ID from CAPTIONS where ID = '%s'", detailID
) > 0 )
861 passed_args
->flags
|= FLAG_HAS_CAPTIONS
;
863 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
864 if( passed_args
->flags
& FLAG_SAMSUNG
)
866 if( strcmp(mime
+6, "x-matroska") == 0 )
868 strcpy(mime
+8, "mkv");
871 /* LG hack: subtitles won't get used unless dc:title contains a dot. */
872 else if( passed_args
->client
== ELGDevice
&& (passed_args
->flags
& FLAG_HAS_CAPTIONS
) )
874 ret
= asprintf(&alt_title
, "%s.", title
);
880 /* Asus OPlay reboots with titles longer than 23 characters with some file types. */
881 else if( passed_args
->client
== EAsusOPlay
&& (passed_args
->flags
& FLAG_HAS_CAPTIONS
) )
883 if( strlen(title
) > 23 )
887 else if( *mime
== 'a' )
889 dlna_flags
|= DLNA_FLAG_TM_S
;
890 if( strcmp(mime
+6, "x-flac") == 0 )
892 if( passed_args
->flags
& FLAG_MIME_FLAC_FLAC
)
894 strcpy(mime
+6, "flac");
897 else if( strcmp(mime
+6, "x-wav") == 0 )
899 if( passed_args
->flags
& FLAG_MIME_WAV_WAV
)
901 strcpy(mime
+6, "wav");
906 dlna_flags
|= DLNA_FLAG_TM_I
;
909 snprintf(dlna_buf
, sizeof(dlna_buf
), "DLNA.ORG_PN=%s;"
912 "DLNA.ORG_FLAGS=%08X%024X",
913 dlna_pn
, dlna_flags
, 0);
914 else if( passed_args
->flags
& FLAG_DLNA
)
915 snprintf(dlna_buf
, sizeof(dlna_buf
), "DLNA.ORG_OP=01;"
917 "DLNA.ORG_FLAGS=%08X%024X",
920 strcpy(dlna_buf
, "*");
922 ret
= strcatf(str
, "<item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id
, parent
);
923 if( refID
&& (passed_args
->filter
& FILTER_REFID
) ) {
924 ret
= strcatf(str
, " refID=\"%s\"", refID
);
926 ret
= strcatf(str
, ">"
927 "<dc:title>%s</dc:title>"
928 "<upnp:class>object.%s</upnp:class>",
930 if( comment
&& (passed_args
->filter
& FILTER_DC_DESCRIPTION
) ) {
931 ret
= strcatf(str
, "<dc:description>%.384s</dc:description>", comment
);
933 if( creator
&& (passed_args
->filter
& FILTER_DC_CREATOR
) ) {
934 ret
= strcatf(str
, "<dc:creator>%s</dc:creator>", creator
);
936 if( date
&& (passed_args
->filter
& FILTER_DC_DATE
) ) {
937 ret
= strcatf(str
, "<dc:date>%s</dc:date>", date
);
939 if( passed_args
->filter
& FILTER_SEC_DCM_INFO
) {
941 ret
= strcatf(str
, "<sec:dcmInfo>CREATIONDATE=0,FOLDER=%s,BM=%d</sec:dcmInfo>",
942 title
, sql_get_int_field(db
, "SELECT SEC from BOOKMARKS where ID = '%s'", detailID
));
945 if( (*mime
== 'v') && (passed_args
->filter
& FILTER_UPNP_ACTOR
) ) {
946 ret
= strcatf(str
, "<upnp:actor>%s</upnp:actor>", artist
);
948 if( passed_args
->filter
& FILTER_UPNP_ARTIST
) {
949 ret
= strcatf(str
, "<upnp:artist>%s</upnp:artist>", artist
);
952 if( album
&& (passed_args
->filter
& FILTER_UPNP_ALBUM
) ) {
953 ret
= strcatf(str
, "<upnp:album>%s</upnp:album>", album
);
955 if( genre
&& (passed_args
->filter
& FILTER_UPNP_GENRE
) ) {
956 ret
= strcatf(str
, "<upnp:genre>%s</upnp:genre>", genre
);
958 if( strncmp(id
, MUSIC_PLIST_ID
, strlen(MUSIC_PLIST_ID
)) == 0 ) {
959 track
= strrchr(id
, '$')+1;
961 if( NON_ZERO(track
) && (passed_args
->filter
& FILTER_UPNP_ORIGINALTRACKNUMBER
) ) {
962 ret
= strcatf(str
, "<upnp:originalTrackNumber>%s</upnp:originalTrackNumber>", track
);
964 if( passed_args
->filter
& FILTER_RES
) {
965 ext
= mime_to_ext(mime
);
966 add_res(size
, duration
, bitrate
, sampleFrequency
, nrAudioChannels
,
967 resolution
, dlna_buf
, mime
, detailID
, ext
, passed_args
);
970 if( resolution
&& (sscanf(resolution
, "%6dx%6d", &srcw
, &srch
) == 2) )
972 if( srcw
> 4096 || srch
> 4096 )
973 add_resized_res(srcw
, srch
, 4096, 4096, "JPEG_LRG", detailID
, passed_args
);
974 if( srcw
> 1024 || srch
> 768 )
975 add_resized_res(srcw
, srch
, 1024, 768, "JPEG_MED", detailID
, passed_args
);
976 if( srcw
> 640 || srch
> 480 )
977 add_resized_res(srcw
, srch
, 640, 480, "JPEG_SM", detailID
, passed_args
);
979 if( !(passed_args
->flags
& FLAG_RESIZE_THUMBS
) && NON_ZERO(tn
) && IS_ZERO(rotate
) ) {
980 ret
= strcatf(str
, "<res protocolInfo=\"http-get:*:%s:%s\">"
981 "http://%s:%d/Thumbnails/%s.jpg"
983 mime
, "DLNA.ORG_PN=JPEG_TN;DLNA.ORG_CI=1", lan_addr
[passed_args
->iface
].str
,
984 runtime_vars
.port
, detailID
);
987 add_resized_res(srcw
, srch
, 160, 160, "JPEG_TN", detailID
, passed_args
);
989 else if( *mime
== 'v' ) {
990 switch( passed_args
->client
) {
993 (strncmp(dlna_pn
, "MPEG_TS_HD_NA", 13) == 0 ||
994 strncmp(dlna_pn
, "MPEG_TS_SD_NA", 13) == 0 ||
995 strncmp(dlna_pn
, "AVC_TS_MP_HD_AC3", 16) == 0 ||
996 strncmp(dlna_pn
, "AVC_TS_HP_HD_AC3", 16) == 0))
998 sprintf(dlna_buf
, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_PS_NTSC");
999 add_res(size
, duration
, bitrate
, sampleFrequency
, nrAudioChannels
,
1000 resolution
, dlna_buf
, mime
, detailID
, ext
, passed_args
);
1005 (strncmp(dlna_pn
, "AVC_TS", 6) == 0 ||
1006 strncmp(dlna_pn
, "MPEG_TS", 7) == 0) )
1008 if( strncmp(dlna_pn
, "MPEG_TS_SD_NA", 13) != 0 )
1010 sprintf(dlna_buf
, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_TS_SD_NA");
1011 add_res(size
, duration
, bitrate
, sampleFrequency
, nrAudioChannels
,
1012 resolution
, dlna_buf
, mime
, detailID
, ext
, passed_args
);
1014 if( strncmp(dlna_pn
, "MPEG_TS_SD_EU", 13) != 0 )
1016 sprintf(dlna_buf
, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_TS_SD_EU");
1017 add_res(size
, duration
, bitrate
, sampleFrequency
, nrAudioChannels
,
1018 resolution
, dlna_buf
, mime
, detailID
, ext
, passed_args
);
1021 else if( (dlna_pn
&&
1022 (strncmp(dlna_pn
, "AVC_MP4", 7) == 0 ||
1023 strncmp(dlna_pn
, "MPEG4_P2_MP4", 12) == 0)) ||
1024 strcmp(mime
+6, "x-matroska") == 0 ||
1025 strcmp(mime
+6, "x-msvideo") == 0 ||
1026 strcmp(mime
+6, "mpeg") == 0 )
1028 strcpy(mime
+6, "avi");
1029 if( !dlna_pn
|| strncmp(dlna_pn
, "MPEG_PS_NTSC", 12) != 0 )
1031 sprintf(dlna_buf
, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_PS_NTSC");
1032 add_res(size
, duration
, bitrate
, sampleFrequency
, nrAudioChannels
,
1033 resolution
, dlna_buf
, mime
, detailID
, ext
, passed_args
);
1035 if( !dlna_pn
|| strncmp(dlna_pn
, "MPEG_PS_PAL", 11) != 0 )
1037 sprintf(dlna_buf
, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_PS_PAL");
1038 add_res(size
, duration
, bitrate
, sampleFrequency
, nrAudioChannels
,
1039 resolution
, dlna_buf
, mime
, detailID
, ext
, passed_args
);
1044 /* BRAVIA KDL-##*X### series TVs do natively support AVC/AC3 in TS, but
1045 require profile to be renamed (applies to _T and _ISO variants also) */
1047 (strncmp(dlna_pn
, "AVC_TS_MP_SD_AC3", 16) == 0 ||
1048 strncmp(dlna_pn
, "AVC_TS_MP_HD_AC3", 16) == 0 ||
1049 strncmp(dlna_pn
, "AVC_TS_HP_HD_AC3", 16) == 0))
1051 sprintf(dlna_buf
, "DLNA.ORG_PN=AVC_TS_HD_50_AC3%s", dlna_pn
+ 16);
1052 add_res(size
, duration
, bitrate
, sampleFrequency
, nrAudioChannels
,
1053 resolution
, dlna_buf
, mime
, detailID
, ext
, passed_args
);
1056 case ESamsungSeriesCDE
:
1060 if( passed_args
->flags
& FLAG_HAS_CAPTIONS
)
1062 if( passed_args
->flags
& FLAG_CAPTION_RES
)
1063 ret
= strcatf(str
, "<res protocolInfo=\"http-get:*:text/srt:*\">"
1064 "http://%s:%d/Captions/%s.srt"
1066 lan_addr
[passed_args
->iface
].str
, runtime_vars
.port
, detailID
);
1067 else if( passed_args
->filter
& FILTER_SEC_CAPTION_INFO_EX
)
1068 ret
= strcatf(str
, "<sec:CaptionInfoEx sec:type=\"srt\">"
1069 "http://%s:%d/Captions/%s.srt"
1070 "</sec:CaptionInfoEx>",
1071 lan_addr
[passed_args
->iface
].str
, runtime_vars
.port
, detailID
);
1078 if( NON_ZERO(album_art
) )
1080 /* Video and audio album art is handled differently */
1081 if( *mime
== 'v' && (passed_args
->filter
& FILTER_RES
) && !(passed_args
->flags
& FLAG_MS_PFS
) ) {
1082 ret
= strcatf(str
, "<res protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\">"
1083 "http://%s:%d/AlbumArt/%s-%s.jpg"
1085 lan_addr
[passed_args
->iface
].str
, runtime_vars
.port
, album_art
, detailID
);
1086 } else if( passed_args
->filter
& FILTER_UPNP_ALBUMARTURI
) {
1087 ret
= strcatf(str
, "<upnp:albumArtURI");
1088 if( passed_args
->filter
& FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID
) {
1089 ret
= strcatf(str
, " dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"");
1091 ret
= strcatf(str
, ">http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>",
1092 lan_addr
[passed_args
->iface
].str
, runtime_vars
.port
, album_art
, detailID
);
1095 if( (passed_args
->flags
& FLAG_MS_PFS
) && *mime
== 'i' ) {
1096 if( passed_args
->client
== EMediaRoom
&& !album
)
1097 ret
= strcatf(str
, "<upnp:album>%s</upnp:album>", "[No Keywords]");
1099 /* EVA2000 doesn't seem to handle embedded thumbnails */
1100 if( !(passed_args
->flags
& FLAG_RESIZE_THUMBS
) && NON_ZERO(tn
) && IS_ZERO(rotate
) ) {
1101 ret
= strcatf(str
, "<upnp:albumArtURI>"
1102 "http://%s:%d/Thumbnails/%s.jpg"
1103 "</upnp:albumArtURI>",
1104 lan_addr
[passed_args
->iface
].str
, runtime_vars
.port
, detailID
);
1106 ret
= strcatf(str
, "<upnp:albumArtURI>"
1107 "http://%s:%d/Resized/%s.jpg?width=160,height=160"
1108 "</upnp:albumArtURI>",
1109 lan_addr
[passed_args
->iface
].str
, runtime_vars
.port
, detailID
);
1112 ret
= strcatf(str
, "</item>");
1114 else if( strncmp(class, "container", 9) == 0 )
1116 ret
= strcatf(str
, "<container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id
, parent
);
1117 if( passed_args
->filter
& FILTER_SEARCHABLE
) {
1118 ret
= strcatf(str
, "searchable=\"%d\" ", check_magic_container(id
, passed_args
->flags
) ? 0 : 1);
1120 if( passed_args
->filter
& FILTER_CHILDCOUNT
) {
1121 ret
= strcatf(str
, "childCount=\"%d\"", get_child_count(id
, check_magic_container(id
, passed_args
->flags
)));
1123 /* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */
1124 if( passed_args
->requested
== 1 && strcmp(id
, "0") == 0 && (passed_args
->filter
& FILTER_UPNP_SEARCHCLASS
) ) {
1125 ret
= strcatf(str
, ">"
1126 "<upnp:searchClass includeDerived=\"1\">object.item.audioItem</upnp:searchClass>"
1127 "<upnp:searchClass includeDerived=\"1\">object.item.imageItem</upnp:searchClass>"
1128 "<upnp:searchClass includeDerived=\"1\">object.item.videoItem</upnp:searchClass");
1130 ret
= strcatf(str
, ">"
1131 "<dc:title>%s</dc:title>"
1132 "<upnp:class>object.%s</upnp:class>",
1134 if( (passed_args
->filter
& FILTER_UPNP_STORAGEUSED
) || strcmp(class+10, "storageFolder") == 0 ) {
1135 /* TODO: Implement real folder size tracking */
1136 ret
= strcatf(str
, "<upnp:storageUsed>%s</upnp:storageUsed>", (size
? size
: "-1"));
1138 if( creator
&& (passed_args
->filter
& FILTER_DC_CREATOR
) ) {
1139 ret
= strcatf(str
, "<dc:creator>%s</dc:creator>", creator
);
1141 if( genre
&& (passed_args
->filter
& FILTER_UPNP_GENRE
) ) {
1142 ret
= strcatf(str
, "<upnp:genre>%s</upnp:genre>", genre
);
1144 if( artist
&& (passed_args
->filter
& FILTER_UPNP_ARTIST
) ) {
1145 ret
= strcatf(str
, "<upnp:artist>%s</upnp:artist>", artist
);
1147 if( NON_ZERO(album_art
) && (passed_args
->filter
& FILTER_UPNP_ALBUMARTURI
) ) {
1148 ret
= strcatf(str
, "<upnp:albumArtURI ");
1149 if( passed_args
->filter
& FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID
) {
1150 ret
= strcatf(str
, "dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"");
1152 ret
= strcatf(str
, ">http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>",
1153 lan_addr
[passed_args
->iface
].str
, runtime_vars
.port
, album_art
, detailID
);
1155 if( passed_args
->filter
& FILTER_AV_MEDIA_CLASS
) {
1157 if( strncmp(id
, MUSIC_ID
, sizeof(MUSIC_ID
)) == 0 )
1159 else if( strncmp(id
, VIDEO_ID
, sizeof(VIDEO_ID
)) == 0 )
1161 else if( strncmp(id
, IMAGE_ID
, sizeof(IMAGE_ID
)) == 0 )
1166 ret
= strcatf(str
, "<av:mediaClass xmlns:av=\"urn:schemas-sony-com:av\">"
1167 "%c</av:mediaClass>", class);
1169 ret
= strcatf(str
, "</container>");
1176 BrowseContentDirectory(struct upnphttp
* h
, const char * action
)
1178 static const char resp0
[] =
1179 "<u:BrowseResponse "
1180 "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1183 CONTENT_DIRECTORY_SCHEMAS
;
1184 struct magic_container_s
*magic
;
1185 char *zErrMsg
= NULL
;
1187 struct Response args
;
1188 struct string_s str
;
1189 int totalMatches
= 0;
1191 const char *ObjectID
, *BrowseFlag
;
1192 char *Filter
, *SortCriteria
;
1193 const char *objectid_sql
= "o.OBJECT_ID";
1194 const char *parentid_sql
= "o.PARENT_ID";
1195 const char *refid_sql
= "o.REF_ID";
1196 char where
[256] = "";
1197 char *orderBy
= NULL
;
1198 struct NameValueParserData data
;
1199 int RequestedCount
= 0;
1200 int StartingIndex
= 0;
1202 memset(&args
, 0, sizeof(args
));
1203 memset(&str
, 0, sizeof(str
));
1205 ParseNameValue(h
->req_buf
+ h
->req_contentoff
, h
->req_contentlen
, &data
, 0);
1207 ObjectID
= GetValueFromNameValueList(&data
, "ObjectID");
1208 Filter
= GetValueFromNameValueList(&data
, "Filter");
1209 BrowseFlag
= GetValueFromNameValueList(&data
, "BrowseFlag");
1210 SortCriteria
= GetValueFromNameValueList(&data
, "SortCriteria");
1212 if( (ptr
= GetValueFromNameValueList(&data
, "RequestedCount")) )
1213 RequestedCount
= atoi(ptr
);
1214 if( RequestedCount
< 0 )
1216 SoapError(h
, 402, "Invalid Args");
1219 if( !RequestedCount
)
1220 RequestedCount
= -1;
1221 if( (ptr
= GetValueFromNameValueList(&data
, "StartingIndex")) )
1222 StartingIndex
= atoi(ptr
);
1223 if( StartingIndex
< 0 )
1225 SoapError(h
, 402, "Invalid Args");
1228 if( !BrowseFlag
|| (strcmp(BrowseFlag
, "BrowseDirectChildren") && strcmp(BrowseFlag
, "BrowseMetadata")) )
1230 SoapError(h
, 402, "Invalid Args");
1233 if( !ObjectID
&& !(ObjectID
= GetValueFromNameValueList(&data
, "ContainerID")) )
1235 SoapError(h
, 402, "Invalid Args");
1239 str
.data
= malloc(DEFAULT_RESP_SIZE
);
1240 str
.size
= DEFAULT_RESP_SIZE
;
1241 str
.off
= sprintf(str
.data
, "%s", resp0
);
1242 /* See if we need to include DLNA namespace reference */
1243 args
.iface
= h
->iface
;
1244 args
.filter
= set_filter_flags(Filter
, h
);
1245 if( args
.filter
& FILTER_DLNA_NAMESPACE
)
1246 ret
= strcatf(&str
, DLNA_NAMESPACE
);
1247 if( args
.filter
& FILTER_PV_SUBTITLE
)
1248 ret
= strcatf(&str
, PV_NAMESPACE
);
1249 strcatf(&str
, ">\n");
1252 args
.requested
= RequestedCount
;
1253 args
.client
= h
->req_client
? h
->req_client
->type
->type
: 0;
1254 args
.flags
= h
->req_client
? h
->req_client
->type
->flags
: 0;
1256 DPRINTF(E_DEBUG
, L_HTTP
, "Browsing ContentDirectory:\n"
1259 " * StartingIndex: %d\n"
1260 " * BrowseFlag: %s\n"
1262 " * SortCriteria: %s\n",
1263 ObjectID
, RequestedCount
, StartingIndex
,
1264 BrowseFlag
, Filter
, SortCriteria
);
1266 if( strcmp(BrowseFlag
+6, "Metadata") == 0 )
1268 const char *id
= ObjectID
;
1270 magic
= in_magic_container(ObjectID
, args
.flags
, &id
);
1273 if (magic
->objectid_sql
&& strcmp(id
, ObjectID
) != 0)
1274 objectid_sql
= magic
->objectid_sql
;
1275 if (magic
->parentid_sql
&& strcmp(id
, ObjectID
) != 0)
1276 parentid_sql
= magic
->parentid_sql
;
1277 if (magic
->refid_sql
)
1278 refid_sql
= magic
->refid_sql
;
1280 sql
= sqlite3_mprintf("SELECT %s, %s, %s, " COLUMNS
1281 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1282 " where OBJECT_ID = '%q';",
1283 objectid_sql
, parentid_sql
, refid_sql
, id
);
1284 ret
= sqlite3_exec(db
, sql
, callback
, (void *) &args
, &zErrMsg
);
1285 totalMatches
= args
.returned
;
1289 magic
= check_magic_container(ObjectID
, args
.flags
);
1292 if (magic
->objectid
&& *(magic
->objectid
))
1293 ObjectID
= *(magic
->objectid
);
1294 if (magic
->objectid_sql
)
1295 objectid_sql
= magic
->objectid_sql
;
1296 if (magic
->parentid_sql
)
1297 parentid_sql
= magic
->parentid_sql
;
1298 if (magic
->refid_sql
)
1299 refid_sql
= magic
->refid_sql
;
1301 strncpyt(where
, magic
->where
, sizeof(where
));
1302 if (magic
->orderby
&& !GETFLAG(DLNA_STRICT_MASK
))
1303 orderBy
= strdup(magic
->orderby
);
1304 if (magic
->max_count
> 0)
1306 int limit
= MAX(magic
->max_count
- StartingIndex
, 0);
1307 ret
= get_child_count(ObjectID
, magic
);
1308 totalMatches
= MIN(ret
, limit
);
1309 if (RequestedCount
> limit
|| RequestedCount
< 0)
1310 RequestedCount
= limit
;
1314 sqlite3_snprintf(sizeof(where
), where
, "PARENT_ID = '%q'", ObjectID
);
1317 totalMatches
= get_child_count(ObjectID
, magic
);
1319 if (SortCriteria
&& !orderBy
)
1322 orderBy
= parse_sort_criteria(SortCriteria
, &ret
);
1326 if( strncmp(ObjectID
, MUSIC_PLIST_ID
, strlen(MUSIC_PLIST_ID
)) == 0 )
1328 if( strcmp(ObjectID
, MUSIC_PLIST_ID
) == 0 )
1329 ret
= xasprintf(&orderBy
, "order by d.TITLE");
1331 ret
= xasprintf(&orderBy
, "order by length(OBJECT_ID), OBJECT_ID");
1333 else if( args
.flags
& FLAG_FORCE_SORT
)
1336 ret
= xasprintf(&orderBy
, "order by o.CLASS, d.DISC, d.TRACK, d.TITLE");
1338 /* LG TV ordering bug */
1339 else if( args
.client
== ELGDevice
)
1340 ret
= xasprintf(&orderBy
, "order by o.CLASS, d.TITLE");
1342 orderBy
= parse_sort_criteria(SortCriteria
, &ret
);
1350 /* If it's a DLNA client, return an error for bad sort criteria */
1351 if( ret
< 0 && ((args
.flags
& FLAG_DLNA
) || GETFLAG(DLNA_STRICT_MASK
)) )
1353 SoapError(h
, 709, "Unsupported or invalid sort criteria");
1357 sql
= sqlite3_mprintf("SELECT %s, %s, %s, " COLUMNS
1358 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1359 " where %s %s limit %d, %d;",
1360 objectid_sql
, parentid_sql
, refid_sql
,
1361 where
, THISORNUL(orderBy
), StartingIndex
, RequestedCount
);
1362 DPRINTF(E_DEBUG
, L_HTTP
, "Browse SQL: %s\n", sql
);
1363 ret
= sqlite3_exec(db
, sql
, callback
, (void *) &args
, &zErrMsg
);
1365 if( (ret
!= SQLITE_OK
) && (zErrMsg
!= NULL
) )
1367 DPRINTF(E_WARN
, L_HTTP
, "SQL error: %s\nBAD SQL: %s\n", zErrMsg
, sql
);
1368 sqlite3_free(zErrMsg
);
1369 SoapError(h
, 709, "Unsupported or invalid sort criteria");
1373 /* Does the object even exist? */
1376 if( !object_exists(ObjectID
) )
1378 SoapError(h
, 701, "No such object error");
1382 ret
= strcatf(&str
, "</DIDL-Lite></Result>\n"
1383 "<NumberReturned>%u</NumberReturned>\n"
1384 "<TotalMatches>%u</TotalMatches>\n"
1385 "<UpdateID>%u</UpdateID>"
1386 "</u:BrowseResponse>",
1387 args
.returned
, totalMatches
, updateID
);
1388 BuildSendAndCloseSoapResp(h
, str
.data
, str
.off
);
1390 ClearNameValueList(&data
);
1396 charcat(struct string_s
*str
, char c
)
1398 if (str
->size
<= str
->off
)
1400 str
->data
[str
->size
-1] = '\0';
1403 str
->data
[str
->off
] = c
;
1407 static inline char *
1408 parse_search_criteria(const char *str
, char *sep
)
1410 struct string_s criteria
;
1412 int literal
= 0, like
= 0;
1416 return strdup("1 = 1");
1418 len
= strlen(str
) + 32;
1419 criteria
.data
= malloc(len
);
1420 criteria
.size
= len
;
1434 if (strncmp(s
, """, 6) == 0)
1436 else if (strncmp(s
, "'", 6) == 0)
1438 strcatf(&criteria
, "'");
1448 charcat(&criteria
, '%');
1451 charcat(&criteria
, '"');
1454 if (strncmp(s
, "\\"", 7) == 0)
1456 strcatf(&criteria
, "&quot;");
1462 if (strncmp(s
, "object.", 7) == 0)
1464 else if (strncmp(s
, "object\"", 7) == 0 ||
1465 strncmp(s
, "object"", 12) == 0)
1471 charcat(&criteria
, *s
);
1479 if (strncmp(s
, "\\"", 7) == 0)
1481 strcatf(&criteria
, "&quot;");
1486 charcat(&criteria
, *s
);
1490 charcat(&criteria
, *s
);
1493 charcat(&criteria
, '%');
1498 if (strncmp(s
, """, 6) == 0)
1501 strcatf(&criteria
, "\"");
1504 charcat(&criteria
, '%');
1509 else if (strncmp(s
, "'", 6) == 0)
1511 strcatf(&criteria
, "'");
1514 else if (strncmp(s
, "<", 4) == 0)
1516 strcatf(&criteria
, "<");
1519 else if (strncmp(s
, ">", 4) == 0)
1521 strcatf(&criteria
, ">");
1525 charcat(&criteria
, *s
);
1528 if (strncmp(s
, "@refID", 6) == 0)
1530 strcatf(&criteria
, "REF_ID");
1534 else if (strncmp(s
, "@id", 3) == 0)
1536 strcatf(&criteria
, "OBJECT_ID");
1540 else if (strncmp(s
, "@parentID", 9) == 0)
1542 strcatf(&criteria
, "PARENT_ID");
1548 charcat(&criteria
, *s
);
1551 if (strncmp(s
, "contains", 8) == 0)
1553 strcatf(&criteria
, "like");
1559 charcat(&criteria
, *s
);
1562 if (strncmp(s
, "derivedfrom", 11) == 0)
1564 strcatf(&criteria
, "like");
1569 else if (strncmp(s
, "dc:date", 7) == 0)
1571 strcatf(&criteria
, "d.DATE");
1575 else if (strncmp(s
, "dc:title", 8) == 0)
1577 strcatf(&criteria
, "d.TITLE");
1581 else if (strncmp(s
, "dc:creator", 10) == 0)
1583 strcatf(&criteria
, "d.CREATOR");
1588 charcat(&criteria
, *s
);
1591 if (strncmp(s
, "exists", 6) == 0)
1596 if (strncmp(s
, "true", 4) == 0)
1598 strcatf(&criteria
, "is not NULL");
1601 else if (strncmp(s
, "false", 5) == 0)
1603 strcatf(&criteria
, "is NULL");
1608 charcat(&criteria
, *s
);
1611 if (strncmp(s
, "upnp:class", 10) == 0)
1613 strcatf(&criteria
, "o.CLASS");
1617 else if (strncmp(s
, "upnp:actor", 10) == 0)
1619 strcatf(&criteria
, "d.ARTIST");
1623 else if (strncmp(s
, "upnp:artist", 11) == 0)
1625 strcatf(&criteria
, "d.ARTIST");
1629 else if (strncmp(s
, "upnp:album", 10) == 0)
1631 strcatf(&criteria
, "d.ALBUM");
1635 else if (strncmp(s
, "upnp:genre", 10) == 0)
1637 strcatf(&criteria
, "d.GENRE");
1642 charcat(&criteria
, *s
);
1645 if (s
> str
&& !isspace(s
[-1]))
1646 charcat(&criteria
, ' ');
1647 charcat(&criteria
, *s
);
1650 charcat(&criteria
, *s
);
1652 charcat(&criteria
, ' ');
1655 charcat(&criteria
, *s
);
1661 charcat(&criteria
, '\0');
1663 return criteria
.data
;
1667 SearchContentDirectory(struct upnphttp
* h
, const char * action
)
1669 static const char resp0
[] =
1670 "<u:SearchResponse "
1671 "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1674 CONTENT_DIRECTORY_SCHEMAS
;
1675 struct magic_container_s
*magic
;
1676 char *zErrMsg
= NULL
;
1678 struct Response args
;
1679 struct string_s str
;
1682 const char *ContainerID
;
1683 char *Filter
, *SearchCriteria
, *SortCriteria
;
1684 char *orderBy
= NULL
, *where
= NULL
, sep
[] = "$*";
1685 char groupBy
[] = "group by DETAIL_ID";
1686 struct NameValueParserData data
;
1687 int RequestedCount
= 0;
1688 int StartingIndex
= 0;
1690 memset(&args
, 0, sizeof(args
));
1691 memset(&str
, 0, sizeof(str
));
1693 ParseNameValue(h
->req_buf
+ h
->req_contentoff
, h
->req_contentlen
, &data
, 0);
1695 ContainerID
= GetValueFromNameValueList(&data
, "ContainerID");
1696 Filter
= GetValueFromNameValueList(&data
, "Filter");
1697 SearchCriteria
= GetValueFromNameValueList(&data
, "SearchCriteria");
1698 SortCriteria
= GetValueFromNameValueList(&data
, "SortCriteria");
1700 if( (ptr
= GetValueFromNameValueList(&data
, "RequestedCount")) )
1701 RequestedCount
= atoi(ptr
);
1702 if( !RequestedCount
)
1703 RequestedCount
= -1;
1704 if( (ptr
= GetValueFromNameValueList(&data
, "StartingIndex")) )
1705 StartingIndex
= atoi(ptr
);
1708 if( !(ContainerID
= GetValueFromNameValueList(&data
, "ObjectID")) )
1710 SoapError(h
, 402, "Invalid Args");
1715 str
.data
= malloc(DEFAULT_RESP_SIZE
);
1716 str
.size
= DEFAULT_RESP_SIZE
;
1717 str
.off
= sprintf(str
.data
, "%s", resp0
);
1718 /* See if we need to include DLNA namespace reference */
1719 args
.iface
= h
->iface
;
1720 args
.filter
= set_filter_flags(Filter
, h
);
1721 if( args
.filter
& FILTER_DLNA_NAMESPACE
)
1723 ret
= strcatf(&str
, DLNA_NAMESPACE
);
1725 strcatf(&str
, ">\n");
1728 args
.requested
= RequestedCount
;
1729 args
.client
= h
->req_client
? h
->req_client
->type
->type
: 0;
1730 args
.flags
= h
->req_client
? h
->req_client
->type
->flags
: 0;
1732 DPRINTF(E_DEBUG
, L_HTTP
, "Searching ContentDirectory:\n"
1735 " * StartingIndex: %d\n"
1736 " * SearchCriteria: %s\n"
1738 " * SortCriteria: %s\n",
1739 ContainerID
, RequestedCount
, StartingIndex
,
1740 SearchCriteria
, Filter
, SortCriteria
);
1742 magic
= check_magic_container(ContainerID
, args
.flags
);
1743 if (magic
&& magic
->objectid
&& *(magic
->objectid
))
1744 ContainerID
= *(magic
->objectid
);
1746 if( strcmp(ContainerID
, "0") == 0 )
1749 if( strcmp(ContainerID
, MUSIC_ALL_ID
) == 0 ||
1750 GETFLAG(DLNA_STRICT_MASK
) )
1753 where
= parse_search_criteria(SearchCriteria
, sep
);
1754 DPRINTF(E_DEBUG
, L_HTTP
, "Translated SearchCriteria: %s\n", where
);
1756 totalMatches
= sql_get_int_field(db
, "SELECT (select count(distinct DETAIL_ID)"
1757 " from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1758 " where (OBJECT_ID glob '%q%s') and (%s))"
1760 "(select count(*) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1761 " where (OBJECT_ID = '%q') and (%s))",
1762 ContainerID
, sep
, where
, ContainerID
, where
);
1763 if( totalMatches
< 0 )
1765 /* Must be invalid SQL, so most likely bad or unhandled search criteria. */
1766 SoapError(h
, 708, "Unsupported or invalid search criteria");
1769 /* Does the object even exist? */
1772 if( !object_exists(ContainerID
) )
1774 SoapError(h
, 710, "No such container");
1780 orderBy
= parse_sort_criteria(SortCriteria
, &ret
);
1781 /* If it's a DLNA client, return an error for bad sort criteria */
1782 if( ret
< 0 && ((args
.flags
& FLAG_DLNA
) || GETFLAG(DLNA_STRICT_MASK
)) )
1784 SoapError(h
, 709, "Unsupported or invalid sort criteria");
1788 sql
= sqlite3_mprintf( SELECT_COLUMNS
1789 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1790 " where OBJECT_ID glob '%q%s' and (%s) %s "
1793 ContainerID
, sep
, where
, groupBy
,
1794 (*ContainerID
== '*') ? NULL
:
1795 sqlite3_mprintf("UNION ALL " SELECT_COLUMNS
1796 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1797 " where OBJECT_ID = '%q' and (%s) ", ContainerID
, where
),
1798 orderBy
, StartingIndex
, RequestedCount
);
1799 DPRINTF(E_DEBUG
, L_HTTP
, "Search SQL: %s\n", sql
);
1800 ret
= sqlite3_exec(db
, sql
, callback
, (void *) &args
, &zErrMsg
);
1801 if( (ret
!= SQLITE_OK
) && (zErrMsg
!= NULL
) )
1803 DPRINTF(E_WARN
, L_HTTP
, "SQL error: %s\nBAD SQL: %s\n", zErrMsg
, sql
);
1804 sqlite3_free(zErrMsg
);
1807 ret
= strcatf(&str
, "</DIDL-Lite></Result>\n"
1808 "<NumberReturned>%u</NumberReturned>\n"
1809 "<TotalMatches>%u</TotalMatches>\n"
1810 "<UpdateID>%u</UpdateID>"
1811 "</u:SearchResponse>",
1812 args
.returned
, totalMatches
, updateID
);
1813 BuildSendAndCloseSoapResp(h
, str
.data
, str
.off
);
1815 ClearNameValueList(&data
);
1822 If a control point calls QueryStateVariable on a state variable that is not
1823 buffered in memory within (or otherwise available from) the service,
1824 the service must return a SOAP fault with an errorCode of 404 Invalid Var.
1826 QueryStateVariable remains useful as a limited test tool but may not be
1827 part of some future versions of UPnP.
1830 QueryStateVariable(struct upnphttp
* h
, const char * action
)
1832 static const char resp
[] =
1835 "<return>%s</return>"
1839 struct NameValueParserData data
;
1840 const char * var_name
;
1842 ParseNameValue(h
->req_buf
+ h
->req_contentoff
, h
->req_contentlen
, &data
, 0);
1843 /*var_name = GetValueFromNameValueList(&data, "QueryStateVariable"); */
1844 /*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/
1845 var_name
= GetValueFromNameValueList(&data
, "varName");
1847 DPRINTF(E_INFO
, L_HTTP
, "QueryStateVariable(%.40s)\n", var_name
);
1851 SoapError(h
, 402, "Invalid Args");
1853 else if(strcmp(var_name
, "ConnectionStatus") == 0)
1856 bodylen
= snprintf(body
, sizeof(body
), resp
,
1857 action
, "urn:schemas-upnp-org:control-1-0",
1858 "Connected", action
);
1859 BuildSendAndCloseSoapResp(h
, body
, bodylen
);
1863 DPRINTF(E_WARN
, L_HTTP
, "%s: Unknown: %s\n", action
, THISORNUL(var_name
));
1864 SoapError(h
, 404, "Invalid Var");
1867 ClearNameValueList(&data
);
1871 SamsungGetFeatureList(struct upnphttp
* h
, const char * action
)
1873 static const char resp
[] =
1874 "<u:X_GetFeatureListResponse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1876 "<Features xmlns=\"urn:schemas-upnp-org:av:avs\""
1877 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
1878 " xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\">"
1879 "<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">"
1880 "<container id=\"%s\" type=\"object.item.audioItem\"/>"
1881 "<container id=\"%s\" type=\"object.item.videoItem\"/>"
1882 "<container id=\"%s\" type=\"object.item.imageItem\"/>"
1885 "</FeatureList></u:X_GetFeatureListResponse>";
1886 const char *audio
= MUSIC_ID
;
1887 const char *video
= VIDEO_ID
;
1888 const char *image
= IMAGE_ID
;
1892 if (runtime_vars
.root_container
)
1894 if (strcmp(runtime_vars
.root_container
, BROWSEDIR_ID
) == 0)
1896 audio
= MUSIC_DIR_ID
;
1897 video
= VIDEO_DIR_ID
;
1898 image
= IMAGE_DIR_ID
;
1902 audio
= runtime_vars
.root_container
;
1903 video
= runtime_vars
.root_container
;
1904 image
= runtime_vars
.root_container
;
1908 len
= snprintf(body
, sizeof(body
), resp
, audio
, video
, image
);
1910 BuildSendAndCloseSoapResp(h
, body
, len
);
1914 SamsungSetBookmark(struct upnphttp
* h
, const char * action
)
1916 static const char resp
[] =
1917 "<u:X_SetBookmarkResponse"
1918 " xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1919 "</u:X_SetBookmarkResponse>";
1921 struct NameValueParserData data
;
1922 char *ObjectID
, *PosSecond
;
1924 ParseNameValue(h
->req_buf
+ h
->req_contentoff
, h
->req_contentlen
, &data
, 0);
1925 ObjectID
= GetValueFromNameValueList(&data
, "ObjectID");
1926 PosSecond
= GetValueFromNameValueList(&data
, "PosSecond");
1927 if( ObjectID
&& PosSecond
)
1930 ret
= sql_exec(db
, "INSERT OR REPLACE into BOOKMARKS"
1932 "((select DETAIL_ID from OBJECTS where OBJECT_ID = '%q'), %q)", ObjectID
, PosSecond
);
1933 if( ret
!= SQLITE_OK
)
1934 DPRINTF(E_WARN
, L_METADATA
, "Error setting bookmark %s on ObjectID='%s'\n", PosSecond
, ObjectID
);
1935 BuildSendAndCloseSoapResp(h
, resp
, sizeof(resp
)-1);
1938 SoapError(h
, 402, "Invalid Args");
1940 ClearNameValueList(&data
);
1945 const char * methodName
;
1946 void (*methodImpl
)(struct upnphttp
*, const char *);
1950 { "QueryStateVariable", QueryStateVariable
},
1951 { "Browse", BrowseContentDirectory
},
1952 { "Search", SearchContentDirectory
},
1953 { "GetSearchCapabilities", GetSearchCapabilities
},
1954 { "GetSortCapabilities", GetSortCapabilities
},
1955 { "GetSystemUpdateID", GetSystemUpdateID
},
1956 { "GetProtocolInfo", GetProtocolInfo
},
1957 { "GetCurrentConnectionIDs", GetCurrentConnectionIDs
},
1958 { "GetCurrentConnectionInfo", GetCurrentConnectionInfo
},
1959 { "IsAuthorized", IsAuthorizedValidated
},
1960 { "IsValidated", IsAuthorizedValidated
},
1961 { "RegisterDevice", RegisterDevice
},
1962 { "X_GetFeatureList", SamsungGetFeatureList
},
1963 { "X_SetBookmark", SamsungSetBookmark
},
1968 ExecuteSoapAction(struct upnphttp
* h
, const char * action
, int n
)
1972 p
= strchr(action
, '#');
1980 p2
= strchr(p
, '"');
1984 methodlen
= n
- (p
- action
);
1985 DPRINTF(E_DEBUG
, L_HTTP
, "SoapMethod: %.*s\n", methodlen
, p
);
1986 while(soapMethods
[i
].methodName
)
1988 len
= strlen(soapMethods
[i
].methodName
);
1989 if(strncmp(p
, soapMethods
[i
].methodName
, len
) == 0)
1991 soapMethods
[i
].methodImpl(h
, soapMethods
[i
].methodName
);
1997 DPRINTF(E_WARN
, L_HTTP
, "SoapMethod: Unknown: %.*s\n", methodlen
, p
);
2000 SoapError(h
, 401, "Invalid Action");