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