source: titan/minidlna-1.0.22/upnpsoap.c @ 13567

Last change on this file since 13567 was 13567, checked in by obi, 11 years ago

[titan] add minidlna-1.0.22 first step

File size: 52.2 KB
Line 
1/* MiniDLNA project
2 *
3 * http://sourceforge.net/projects/minidlna/
4 *
5 * MiniDLNA media server
6 * Copyright (C) 2008-2009  Justin Maggard
7 *
8 * This file is part of MiniDLNA.
9 *
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.
13 *
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.
18 *
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/>.
21 *
22 * Portions of the code from the MiniUPnP project:
23 *
24 * Copyright (c) 2006-2007, Thomas Bernard
25 * All rights reserved.
26 *
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.
36 *
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.
48 */
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#include <sys/socket.h>
53#include <unistd.h>
54#include <dirent.h>
55#include <sys/stat.h>
56#include <sys/types.h>
57#include <arpa/inet.h>
58#include <netinet/in.h>
59#include <netdb.h>
60#include <ctype.h>
61
62#include "config.h"
63#include "upnpglobalvars.h"
64#include "utils.h"
65#include "upnphttp.h"
66#include "upnpsoap.h"
67#include "upnpreplyparse.h"
68#include "getifaddr.h"
69#include "scanner.h"
70#include "sql.h"
71#include "log.h"
72
73static void
74BuildSendAndCloseSoapResp(struct upnphttp * h,
75                          const char * body, int bodylen)
76{
77        static const char beforebody[] =
78                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
79                "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
80                "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
81                "<s:Body>";
82
83        static const char afterbody[] =
84                "</s:Body>"
85                "</s:Envelope>\r\n";
86
87        BuildHeader_upnphttp(h, 200, "OK",  sizeof(beforebody) - 1
88                + sizeof(afterbody) - 1 + bodylen );
89
90        memcpy(h->res_buf + h->res_buflen, beforebody, sizeof(beforebody) - 1);
91        h->res_buflen += sizeof(beforebody) - 1;
92
93        memcpy(h->res_buf + h->res_buflen, body, bodylen);
94        h->res_buflen += bodylen;
95
96        memcpy(h->res_buf + h->res_buflen, afterbody, sizeof(afterbody) - 1);
97        h->res_buflen += sizeof(afterbody) - 1;
98
99        SendResp_upnphttp(h);
100        CloseSocket_upnphttp(h);
101}
102
103static void
104GetSystemUpdateID(struct upnphttp * h, const char * action)
105{
106        static const char resp[] =
107                "<u:%sResponse "
108                "xmlns:u=\"%s\">"
109                "<Id>%d</Id>"
110                "</u:%sResponse>";
111
112        char body[512];
113        int bodylen;
114
115        bodylen = snprintf(body, sizeof(body), resp,
116                action, "urn:schemas-upnp-org:service:ContentDirectory:1",
117                updateID, action);
118        BuildSendAndCloseSoapResp(h, body, bodylen);
119}
120
121static void
122IsAuthorizedValidated(struct upnphttp * h, const char * action)
123{
124        static const char resp[] =
125                "<u:%sResponse "
126                "xmlns:u=\"%s\">"
127                "<Result>%d</Result>"
128                "</u:%sResponse>";
129
130        char body[512];
131        int bodylen;
132
133        bodylen = snprintf(body, sizeof(body), resp,
134                action, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
135                1, action);     
136        BuildSendAndCloseSoapResp(h, body, bodylen);
137}
138
139static void
140GetProtocolInfo(struct upnphttp * h, const char * action)
141{
142        static const char resp[] =
143                "<u:%sResponse "
144                "xmlns:u=\"%s\">"
145                "<Source>"
146                RESOURCE_PROTOCOL_INFO_VALUES
147                "</Source>"
148                "<Sink></Sink>"
149                "</u:%sResponse>";
150
151        char * body;
152        int bodylen;
153
154        bodylen = asprintf(&body, resp,
155                action, "urn:schemas-upnp-org:service:ConnectionManager:1",
156                action);       
157        BuildSendAndCloseSoapResp(h, body, bodylen);
158        free(body);
159}
160
161static void
162GetSortCapabilities(struct upnphttp * h, const char * action)
163{
164        static const char resp[] =
165                "<u:%sResponse "
166                "xmlns:u=\"%s\">"
167                "<SortCaps>"
168                  "dc:title,"
169                  "dc:date,"
170                  "upnp:class,"
171                  "upnp:originalTrackNumber"
172                "</SortCaps>"
173                "</u:%sResponse>";
174
175        char body[512];
176        int bodylen;
177
178        bodylen = snprintf(body, sizeof(body), resp,
179                action, "urn:schemas-upnp-org:service:ContentDirectory:1",
180                action);       
181        BuildSendAndCloseSoapResp(h, body, bodylen);
182}
183
184static void
185GetSearchCapabilities(struct upnphttp * h, const char * action)
186{
187        static const char resp[] =
188                "<u:%sResponse xmlns:u=\"%s\">"
189                "<SearchCaps>"
190                  "dc:creator,"
191                  "dc:date,"
192                  "dc:title,"
193                  "upnp:album,"
194                  "upnp:actor,"
195                  "upnp:artist,"
196                  "upnp:class,"
197                  "upnp:genre,"
198                  "@refID"
199                "</SearchCaps>"
200                "</u:%sResponse>";
201
202        char body[512];
203        int bodylen;
204
205        bodylen = snprintf(body, sizeof(body), resp,
206                action, "urn:schemas-upnp-org:service:ContentDirectory:1",
207                action);       
208        BuildSendAndCloseSoapResp(h, body, bodylen);
209}
210
211static void
212GetCurrentConnectionIDs(struct upnphttp * h, const char * action)
213{
214        /* TODO: Use real data. - JM */
215        static const char resp[] =
216                "<u:%sResponse "
217                "xmlns:u=\"%s\">"
218                "<ConnectionIDs>0</ConnectionIDs>"
219                "</u:%sResponse>";
220
221        char body[512];
222        int bodylen;
223
224        bodylen = snprintf(body, sizeof(body), resp,
225                action, "urn:schemas-upnp-org:service:ConnectionManager:1",
226                action);       
227        BuildSendAndCloseSoapResp(h, body, bodylen);
228}
229
230static void
231GetCurrentConnectionInfo(struct upnphttp * h, const char * action)
232{
233        /* TODO: Use real data. - JM */
234        static const char resp[] =
235                "<u:%sResponse "
236                "xmlns:u=\"%s\">"
237                "<RcsID>-1</RcsID>"
238                "<AVTransportID>-1</AVTransportID>"
239                "<ProtocolInfo></ProtocolInfo>"
240                "<PeerConnectionManager></PeerConnectionManager>"
241                "<PeerConnectionID>-1</PeerConnectionID>"
242                "<Direction>Output</Direction>"
243                "<Status>Unknown</Status>"
244                "</u:%sResponse>";
245
246        char body[sizeof(resp)+128];
247        int bodylen;
248
249        bodylen = snprintf(body, sizeof(body), resp,
250                action, "urn:schemas-upnp-org:service:ConnectionManager:1",
251                action);       
252        BuildSendAndCloseSoapResp(h, body, bodylen);
253}
254
255static void
256mime_to_ext(const char * mime, char * buf)
257{
258        switch( *mime )
259        {
260                /* Audio extensions */
261                case 'a':
262                        if( strcmp(mime+6, "mpeg") == 0 )
263                                strcpy(buf, "mp3");
264                        else if( strcmp(mime+6, "mp4") == 0 )
265                                strcpy(buf, "m4a");
266                        else if( strcmp(mime+6, "x-ms-wma") == 0 )
267                                strcpy(buf, "wma");
268                        else if( strcmp(mime+6, "x-flac") == 0 )
269                                strcpy(buf, "flac");
270                        else if( strcmp(mime+6, "flac") == 0 )
271                                strcpy(buf, "flac");
272                        else if( strcmp(mime+6, "x-wav") == 0 )
273                                strcpy(buf, "wav");
274                        else if( strncmp(mime+6, "L16", 3) == 0 )
275                                strcpy(buf, "pcm");
276                        else if( strcmp(mime+6, "3gpp") == 0 )
277                                strcpy(buf, "3gp");
278                        else if( strcmp(mime, "application/ogg") == 0 )
279                                strcpy(buf, "ogg");
280                        else
281                                strcpy(buf, "dat");
282                        break;
283                case 'v':
284                        if( strcmp(mime+6, "avi") == 0 )
285                                strcpy(buf, "avi");
286                        else if( strcmp(mime+6, "divx") == 0 )
287                                strcpy(buf, "avi");
288                        else if( strcmp(mime+6, "x-msvideo") == 0 )
289                                strcpy(buf, "avi");
290                        else if( strcmp(mime+6, "mpeg") == 0 )
291                                strcpy(buf, "mpg");
292                        else if( strcmp(mime+6, "mp4") == 0 )
293                                strcpy(buf, "mp4");
294                        else if( strcmp(mime+6, "x-ms-wmv") == 0 )
295                                strcpy(buf, "wmv");
296                        else if( strcmp(mime+6, "x-matroska") == 0 )
297                                strcpy(buf, "mkv");
298                        else if( strcmp(mime+6, "x-mkv") == 0 )
299                                strcpy(buf, "mkv");
300                        else if( strcmp(mime+6, "x-flv") == 0 )
301                                strcpy(buf, "flv");
302                        else if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
303                                strcpy(buf, "mpg");
304                        else if( strcmp(mime+6, "quicktime") == 0 )
305                                strcpy(buf, "mov");
306                        else if( strcmp(mime+6, "3gpp") == 0 )
307                                strcpy(buf, "3gp");
308                        else if( strcmp(mime+6, "x-tivo-mpeg") == 0 )
309                                strcpy(buf, "TiVo");
310                        else
311                                strcpy(buf, "dat");
312                        break;
313                case 'i':
314                        if( strcmp(mime+6, "jpeg") == 0 )
315                                strcpy(buf, "jpg");
316                        else if( strcmp(mime+6, "png") == 0 )
317                                strcpy(buf, "png");
318                        else
319                                strcpy(buf, "dat");
320                        break;
321                default:
322                        strcpy(buf, "dat");
323                        break;
324        }
325}
326
327#define FILTER_CHILDCOUNT                        0x00000001
328#define FILTER_DC_CREATOR                        0x00000002
329#define FILTER_DC_DATE                           0x00000004
330#define FILTER_DC_DESCRIPTION                    0x00000008
331#define FILTER_DLNA_NAMESPACE                    0x00000010
332#define FILTER_REFID                             0x00000020
333#define FILTER_RES                               0x00000040
334#define FILTER_RES_BITRATE                       0x00000080
335#define FILTER_RES_DURATION                      0x00000100
336#define FILTER_RES_NRAUDIOCHANNELS               0x00000200
337#define FILTER_RES_RESOLUTION                    0x00000400
338#define FILTER_RES_SAMPLEFREQUENCY               0x00000800
339#define FILTER_RES_SIZE                          0x00001000
340#define FILTER_UPNP_ACTOR                        0x00002000
341#define FILTER_UPNP_ALBUM                        0x00004000
342#define FILTER_UPNP_ALBUMARTURI                  0x00008000
343#define FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID   0x00010000
344#define FILTER_UPNP_ARTIST                       0x00020000
345#define FILTER_UPNP_GENRE                        0x00040000
346#define FILTER_UPNP_ORIGINALTRACKNUMBER          0x00080000
347#define FILTER_UPNP_SEARCHCLASS                  0x00100000
348#define FILTER_SEC                               0x00200000
349#define FILTER_SEC_CAPTION_INFO                  0x00400000
350#define FILTER_SEC_CAPTION_INFO_EX               0x00800000
351
352static u_int32_t
353set_filter_flags(char * filter, struct upnphttp *h)
354{
355        char *item, *saveptr = NULL;
356        u_int32_t flags = 0;
357
358        if( !filter || (strlen(filter) <= 1) )
359                return 0xFFFFFFFF;
360        if( h->reqflags & FLAG_SAMSUNG )
361                flags |= FILTER_DLNA_NAMESPACE;
362        item = strtok_r(filter, ",", &saveptr);
363        while( item != NULL )
364        {
365                if( saveptr )
366                        *(item-1) = ',';
367                while( isspace(*item) )
368                        item++;
369                if( strcmp(item, "@childCount") == 0 )
370                {
371                        flags |= FILTER_CHILDCOUNT;
372                }
373                else if( strcmp(item, "dc:creator") == 0 )
374                {
375                        flags |= FILTER_DC_CREATOR;
376                }
377                else if( strcmp(item, "dc:date") == 0 )
378                {
379                        flags |= FILTER_DC_DATE;
380                }
381                else if( strcmp(item, "dc:description") == 0 )
382                {
383                        flags |= FILTER_DC_DESCRIPTION;
384                }
385                else if( strcmp(item, "dlna") == 0 )
386                {
387                        flags |= FILTER_DLNA_NAMESPACE;
388                }
389                else if( strcmp(item, "@refID") == 0 )
390                {
391                        flags |= FILTER_REFID;
392                }
393                else if( strcmp(item, "upnp:album") == 0 )
394                {
395                        flags |= FILTER_UPNP_ALBUM;
396                }
397                else if( strcmp(item, "upnp:albumArtURI") == 0 )
398                {
399                        flags |= FILTER_UPNP_ALBUMARTURI;
400                        if( h->reqflags & FLAG_SAMSUNG )
401                                flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID;
402                }
403                else if( strcmp(item, "upnp:albumArtURI@dlna:profileID") == 0 )
404                {
405                        flags |= FILTER_UPNP_ALBUMARTURI;
406                        flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID;
407                }
408                else if( strcmp(item, "upnp:artist") == 0 )
409                {
410                        flags |= FILTER_UPNP_ARTIST;
411                }
412                else if( strcmp(item, "upnp:actor") == 0 )
413                {
414                        flags |= FILTER_UPNP_ACTOR;
415                }
416                else if( strcmp(item, "upnp:genre") == 0 )
417                {
418                        flags |= FILTER_UPNP_GENRE;
419                }
420                else if( strcmp(item, "upnp:originalTrackNumber") == 0 )
421                {
422                        flags |= FILTER_UPNP_ORIGINALTRACKNUMBER;
423                }
424                else if( strcmp(item, "upnp:searchClass") == 0 )
425                {
426                        flags |= FILTER_UPNP_SEARCHCLASS;
427                }
428                else if( strcmp(item, "res") == 0 )
429                {
430                        flags |= FILTER_RES;
431                }
432                else if( (strcmp(item, "res@bitrate") == 0) ||
433                         (strcmp(item, "@bitrate") == 0) ||
434                         ((strcmp(item, "bitrate") == 0) && (flags & FILTER_RES)) )
435                {
436                        flags |= FILTER_RES;
437                        flags |= FILTER_RES_BITRATE;
438                }
439                else if( (strcmp(item, "res@duration") == 0) ||
440                         (strcmp(item, "@duration") == 0) ||
441                         ((strcmp(item, "duration") == 0) && (flags & FILTER_RES)) )
442                {
443                        flags |= FILTER_RES;
444                        flags |= FILTER_RES_DURATION;
445                }
446                else if( (strcmp(item, "res@nrAudioChannels") == 0) ||
447                         (strcmp(item, "@nrAudioChannels") == 0) ||
448                         ((strcmp(item, "nrAudioChannels") == 0) && (flags & FILTER_RES)) )
449                {
450                        flags |= FILTER_RES;
451                        flags |= FILTER_RES_NRAUDIOCHANNELS;
452                }
453                else if( (strcmp(item, "res@resolution") == 0) ||
454                         (strcmp(item, "@resolution") == 0) ||
455                         ((strcmp(item, "resolution") == 0) && (flags & FILTER_RES)) )
456                {
457                        flags |= FILTER_RES;
458                        flags |= FILTER_RES_RESOLUTION;
459                }
460                else if( (strcmp(item, "res@sampleFrequency") == 0) ||
461                         (strcmp(item, "@sampleFrequency") == 0) ||
462                         ((strcmp(item, "sampleFrequency") == 0) && (flags & FILTER_RES)) )
463                {
464                        flags |= FILTER_RES;
465                        flags |= FILTER_RES_SAMPLEFREQUENCY;
466                }
467                else if( (strcmp(item, "res@size") == 0) ||
468                         (strcmp(item, "@size") == 0) ||
469                         (strcmp(item, "size") == 0) )
470                {
471                        flags |= FILTER_RES;
472                        flags |= FILTER_RES_SIZE;
473                }
474                else if( strcmp(item, "sec:CaptionInfo") == 0)
475                {
476                        flags |= FILTER_SEC;
477                        flags |= FILTER_SEC_CAPTION_INFO;
478                }
479                else if( strcmp(item, "sec:CaptionInfoEx") == 0)
480                {
481                        flags |= FILTER_SEC;
482                        flags |= FILTER_SEC_CAPTION_INFO_EX;
483                }
484                item = strtok_r(NULL, ",", &saveptr);
485        }
486
487        return flags;
488}
489
490char *
491parse_sort_criteria(char * sortCriteria, int * error)
492{
493        char *order = NULL;
494        char *item, *saveptr;
495        int i, ret, reverse, title_sorted = 0;
496        *error = 0;
497
498        if( !sortCriteria )
499                return NULL;
500
501        if( (item = strtok_r(sortCriteria, ",", &saveptr)) )
502        {
503                order = malloc(4096);
504                strcpy(order, "order by ");
505        }
506        for( i=0; item != NULL; i++ )
507        {
508                reverse=0;
509                if( i )
510                        strcat(order, ", ");
511                if( *item == '+' )
512                {
513                        item++;
514                }
515                else if( *item == '-' )
516                {
517                        reverse = 1;
518                        item++;
519                }
520                if( strcasecmp(item, "upnp:class") == 0 )
521                {
522                        strcat(order, "o.CLASS");
523                }
524                else if( strcasecmp(item, "dc:title") == 0 )
525                {
526                        strcat(order, "d.TITLE");
527                        title_sorted = 1;
528                }
529                else if( strcasecmp(item, "dc:date") == 0 )
530                {
531                        strcat(order, "d.DATE");
532                }
533                else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 )
534                {
535                        strcat(order, "d.DISC, d.TRACK");
536                }
537                else
538                {
539                        printf("Unhandled SortCriteria [%s]\n", item);
540                        *error = 1;
541                        if( i )
542                        {
543                                ret = strlen(order);
544                                order[ret-2] = '\0';
545                        }
546                        i--;
547                        goto unhandled_order;
548                }
549
550                if( reverse )
551                        strcat(order, " DESC");
552                unhandled_order:
553                item = strtok_r(NULL, ",", &saveptr);
554        }
555        if( i <= 0 )
556        {
557                free(order);
558                return NULL;
559        }
560        /* Add a "tiebreaker" sort order */
561        if( !title_sorted )
562                strcat(order, ", TITLE ASC");
563
564        return order;
565}
566
567inline static void
568add_resized_res(int srcw, int srch, int reqw, int reqh, char *dlna_pn,
569                char *detailID, struct Response *args)
570{
571        int dstw = reqw;
572        int dsth = reqh;
573
574        if( args->flags & FLAG_NO_RESIZE )
575                return;
576
577        strcatf(args->str, "&lt;res ");
578        if( args->filter & FILTER_RES_RESOLUTION )
579        {
580                dstw = reqw;
581                dsth = ((((reqw<<10)/srcw)*srch)>>10);
582                if( dsth > reqh ) {
583                        dsth = reqh;
584                        dstw = (((reqh<<10)/srch) * srcw>>10);
585                }
586                strcatf(args->str, "resolution=\"%dx%d\" ", dstw, dsth);
587        }
588        strcatf(args->str, "protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=%s;DLNA.ORG_CI=1\"&gt;"
589                                  "http://%s:%d/Resized/%s.jpg?width=%d,height=%d"
590                                  "&lt;/res&gt;",
591                                  dlna_pn, lan_addr[args->iface].str, runtime_vars.port,
592                                  detailID, dstw, dsth);
593}
594
595inline static void
596add_res(char *size, char *duration, char *bitrate, char *sampleFrequency,
597        char *nrAudioChannels, char *resolution, char *dlna_pn, char *mime,
598        char *detailID, char *ext, struct Response *args)
599{
600        strcatf(args->str, "&lt;res ");
601        if( size && (args->filter & FILTER_RES_SIZE) ) {
602                strcatf(args->str, "size=\"%s\" ", size);
603        }
604        if( duration && (args->filter & FILTER_RES_DURATION) ) {
605                strcatf(args->str, "duration=\"%s\" ", duration);
606        }
607        if( bitrate && (args->filter & FILTER_RES_BITRATE) ) {
608                int br = atoi(bitrate);
609                if(args->flags & FLAG_MS_PFS)
610                        br /= 8;
611                strcatf(args->str, "bitrate=\"%d\" ", br);
612        }
613        if( sampleFrequency && (args->filter & FILTER_RES_SAMPLEFREQUENCY) ) {
614                strcatf(args->str, "sampleFrequency=\"%s\" ", sampleFrequency);
615        }
616        if( nrAudioChannels && (args->filter & FILTER_RES_NRAUDIOCHANNELS) ) {
617                strcatf(args->str, "nrAudioChannels=\"%s\" ", nrAudioChannels);
618        }
619        if( resolution && (args->filter & FILTER_RES_RESOLUTION) ) {
620                strcatf(args->str, "resolution=\"%s\" ", resolution);
621        }
622        strcatf(args->str, "protocolInfo=\"http-get:*:%s:%s\"&gt;"
623                                  "http://%s:%d/MediaItems/%s.%s"
624                                  "&lt;/res&gt;",
625                                  mime, dlna_pn, lan_addr[args->iface].str,
626                                  runtime_vars.port, detailID, ext);
627}
628
629#define COLUMNS "o.REF_ID, o.DETAIL_ID, o.CLASS," \
630                " d.SIZE, d.TITLE, d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST," \
631                " d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RESOLUTION," \
632                " d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.DISC "
633#define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, " COLUMNS
634
635static int
636callback(void *args, int argc, char **argv, char **azColName)
637{
638        struct Response *passed_args = (struct Response *)args;
639        char *id = argv[0], *parent = argv[1], *refID = argv[2], *detailID = argv[3], *class = argv[4], *size = argv[5], *title = argv[6],
640             *duration = argv[7], *bitrate = argv[8], *sampleFrequency = argv[9], *artist = argv[10], *album = argv[11],
641             *genre = argv[12], *comment = argv[13], *nrAudioChannels = argv[14], *track = argv[15], *date = argv[16], *resolution = argv[17],
642             *tn = argv[18], *creator = argv[19], *dlna_pn = argv[20], *mime = argv[21], *album_art = argv[22];
643        char dlna_buf[96];
644        char ext[5];
645        struct string_s *str = passed_args->str;
646        int ret = 0;
647
648        /* Make sure we have at least 8KB left of allocated memory to finish the response. */
649        if( str->off > (str->size - 8192) )
650        {
651#if MAX_RESPONSE_SIZE > 0
652                if( (str->size+DEFAULT_RESP_SIZE) <= MAX_RESPONSE_SIZE )
653                {
654#endif
655                        str->data = realloc(str->data, (str->size+DEFAULT_RESP_SIZE));
656                        if( str->data )
657                        {
658                                str->size += DEFAULT_RESP_SIZE;
659                                DPRINTF(E_DEBUG, L_HTTP, "UPnP SOAP response enlarged to %d. [%d results so far]\n",
660                                        str->size, passed_args->returned);
661                        }
662                        else
663                        {
664                                DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response was too big, and realloc failed!\n");
665                                return -1;
666                        }
667#if MAX_RESPONSE_SIZE > 0
668                }
669                else
670                {
671                        DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response cut short, to not exceed the max response size [%lld]!\n", (long long int)MAX_RESPONSE_SIZE);
672                        return -1;
673                }
674#endif
675        }
676        passed_args->returned++;
677
678        if( dlna_pn )
679                sprintf(dlna_buf, "DLNA.ORG_PN=%s", dlna_pn);
680        else if( passed_args->flags & FLAG_DLNA )
681                strcpy(dlna_buf, dlna_no_conv);
682        else
683                strcpy(dlna_buf, "*");
684
685        if( runtime_vars.root_container && strcmp(parent, runtime_vars.root_container) == 0 )
686                parent = "0";
687
688        if( strncmp(class, "item", 4) == 0 )
689        {
690                /* We may need special handling for certain MIME types */
691                if( *mime == 'v' )
692                {
693                        if( passed_args->flags & FLAG_MIME_AVI_DIVX )
694                        {
695                                if( strcmp(mime, "video/x-msvideo") == 0 )
696                                {
697                                        if( creator )
698                                                strcpy(mime+6, "divx");
699                                        else
700                                                strcpy(mime+6, "avi");
701                                }
702                        }
703                        else if( passed_args->flags & FLAG_MIME_AVI_AVI )
704                        {
705                                if( strcmp(mime, "video/x-msvideo") == 0 )
706                                {
707                                        strcpy(mime+6, "avi");
708                                }
709                        }
710                        else if( passed_args->client == EFreeBox && dlna_pn )
711                        {
712                                if( strncmp(dlna_pn, "AVC_TS", 6) == 0 ||
713                                    strncmp(dlna_pn, "MPEG_TS", 7) == 0 )
714                                {
715                                        strcpy(mime+6, "mp2t");
716                                }
717                        }
718                        if( !(passed_args->flags & FLAG_DLNA) )
719                        {
720                                if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
721                                {
722                                        strcpy(mime+6, "mpeg");
723                                }
724                        }
725                        /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
726                        if( passed_args->flags & FLAG_SAMSUNG )
727                        {
728                                if( strcmp(mime+6, "x-matroska") == 0 )
729                                {
730                                        strcpy(mime+8, "mkv");
731                                }
732                        }
733                }
734                else if( *mime == 'a' )
735                {
736                        if( strcmp(mime+6, "x-flac") == 0 )
737                        {
738                                if( passed_args->flags & FLAG_MIME_FLAC_FLAC )
739                                {
740                                        strcpy(mime+6, "flac");
741                                }
742                        }
743                        else if( strcmp(mime+6, "x-wav") == 0 )
744                        {
745                                if( passed_args->flags & FLAG_MIME_WAV_WAV )
746                                {
747                                        strcpy(mime+6, "wav");
748                                }
749                        }
750                }
751
752                ret = strcatf(str, "&lt;item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent);
753                if( refID && (passed_args->filter & FILTER_REFID) ) {
754                        ret = strcatf(str, " refID=\"%s\"", refID);
755                }
756                ret = strcatf(str, "&gt;"
757                                   "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
758                                   "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
759                                   title, class);
760                if( comment && (passed_args->filter & FILTER_DC_DESCRIPTION) ) {
761                        ret = strcatf(str, "&lt;dc:description&gt;%.384s&lt;/dc:description&gt;", comment);
762                }
763                if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
764                        ret = strcatf(str, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
765                }
766                if( date && (passed_args->filter & FILTER_DC_DATE) ) {
767                        ret = strcatf(str, "&lt;dc:date&gt;%s&lt;/dc:date&gt;", date);
768                }
769                if( (passed_args->flags & FLAG_SAMSUNG) && (passed_args->filter & FILTER_SEC_CAPTION_INFO_EX) ) {
770                        /* Get bookmark */
771                        ret = strcatf(str, "&lt;sec:dcmInfo&gt;CREATIONDATE=0,FOLDER=%s,BM=%d&lt;/sec:dcmInfo&gt;",
772                                      title, sql_get_int_field(db, "SELECT SEC from BOOKMARKS where ID = '%s'", detailID));
773                }
774                if( artist ) {
775                        if( (*mime == 'v') && (passed_args->filter & FILTER_UPNP_ACTOR) ) {
776                                ret = strcatf(str, "&lt;upnp:actor&gt;%s&lt;/upnp:actor&gt;", artist);
777                        }
778                        if( passed_args->filter & FILTER_UPNP_ARTIST ) {
779                                ret = strcatf(str, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
780                        }
781                }
782                if( album && (passed_args->filter & FILTER_UPNP_ALBUM) ) {
783                        ret = strcatf(str, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", album);
784                }
785                if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
786                        ret = strcatf(str, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
787                }
788                if( strncmp(id, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) {
789                        track = strrchr(id, '$')+1;
790                }
791                if( track && atoi(track) && (passed_args->filter & FILTER_UPNP_ORIGINALTRACKNUMBER) ) {
792                        ret = strcatf(str, "&lt;upnp:originalTrackNumber&gt;%s&lt;/upnp:originalTrackNumber&gt;", track);
793                }
794                if( album_art && atoi(album_art) )
795                {
796                        /* Video and audio album art is handled differently */
797                        if( *mime == 'v' && (passed_args->filter & FILTER_RES) && !(passed_args->flags & FLAG_MS_PFS) ) {
798                                ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\"&gt;"
799                                                   "http://%s:%d/AlbumArt/%s-%s.jpg"
800                                                   "&lt;/res&gt;",
801                                                   lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
802                        } else if( passed_args->filter & FILTER_UPNP_ALBUMARTURI ) {
803                                ret = strcatf(str, "&lt;upnp:albumArtURI");
804                                if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
805                                        ret = strcatf(str, " dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"");
806                                }
807                                ret = strcatf(str, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg&lt;/upnp:albumArtURI&gt;",
808                                                   lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
809                        }
810                }
811                if( (passed_args->flags & FLAG_MS_PFS) && *mime == 'i' ) {
812                        if( passed_args->client == EMediaRoom && !album )
813                                ret = strcatf(str, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", "[No Keywords]");
814
815                        /* EVA2000 doesn't seem to handle embedded thumbnails */
816                        if( passed_args->client != ENetgearEVA2000 && tn && atoi(tn) ) {
817                                ret = strcatf(str, "&lt;upnp:albumArtURI&gt;"
818                                                   "http://%s:%d/Thumbnails/%s.jpg"
819                                                   "&lt;/upnp:albumArtURI&gt;",
820                                                   lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
821                        } else {
822                                ret = strcatf(str, "&lt;upnp:albumArtURI&gt;"
823                                                   "http://%s:%d/Resized/%s.jpg?width=160,height=160"
824                                                   "&lt;/upnp:albumArtURI&gt;",
825                                                   lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
826                        }
827                }
828                if( passed_args->filter & FILTER_RES ) {
829                        mime_to_ext(mime, ext);
830                        if( (passed_args->client == EFreeBox) && tn && atoi(tn) ) {
831                                ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:%s:%s\"&gt;"
832                                                   "http://%s:%d/Thumbnails/%s.jpg"
833                                                   "&lt;/res&gt;",
834                                                   mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[passed_args->iface].str,
835                                                   runtime_vars.port, detailID);
836                        }
837                        add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
838                                resolution, dlna_buf, mime, detailID, ext, passed_args);
839                        if( (*mime == 'i') && (passed_args->client != EFreeBox) ) {
840                                int srcw = atoi(strsep(&resolution, "x"));
841                                int srch = atoi(resolution);
842                                if( !dlna_pn ) {
843                                        add_resized_res(srcw, srch, 4096, 4096, "JPEG_LRG", detailID, passed_args);
844                                }
845                                if( !dlna_pn || !strncmp(dlna_pn, "JPEG_L", 6) || !strncmp(dlna_pn, "JPEG_M", 6) ) {
846                                        add_resized_res(srcw, srch, 640, 480, "JPEG_SM", detailID, passed_args);
847                                }
848                                if( tn && atoi(tn) ) {
849                                        ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:%s:%s\"&gt;"
850                                                           "http://%s:%d/Thumbnails/%s.jpg"
851                                                           "&lt;/res&gt;",
852                                                           mime, "DLNA.ORG_PN=JPEG_TN", lan_addr[passed_args->iface].str,
853                                                           runtime_vars.port, detailID);
854                                }
855                        }
856                        else if( *mime == 'v' ) {
857                                switch( passed_args->client ) {
858                                case EToshibaTV:
859                                        if( dlna_pn &&
860                                            (strncmp(dlna_pn, "MPEG_TS_HD_NA", 13) == 0 ||
861                                             strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) == 0 ||
862                                             strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 ||
863                                             strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0))
864                                        {
865                                                sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
866                                                add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
867                                                        resolution, dlna_buf, mime, detailID, ext, passed_args);
868                                        }
869                                        break;
870                                case ESonyBDP:
871                                        if( dlna_pn &&
872                                            (strncmp(dlna_pn, "AVC_TS", 6) == 0 ||
873                                             strncmp(dlna_pn, "MPEG_TS", 7) == 0) )
874                                        {
875                                                if( strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) != 0 )
876                                                {
877                                                        sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_TS_SD_NA;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
878                                                        add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
879                                                                resolution, dlna_buf, mime, detailID, ext, passed_args);
880                                                }
881                                                if( strncmp(dlna_pn, "MPEG_TS_SD_EU", 13) != 0 )
882                                                {
883                                                        sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_TS_SD_EU;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
884                                                        add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
885                                                                resolution, dlna_buf, mime, detailID, ext, passed_args);
886                                                }
887                                        }
888                                        else if( (dlna_pn &&
889                                                  (strncmp(dlna_pn, "AVC_MP4", 7) == 0 ||
890                                                   strncmp(dlna_pn, "MPEG4_P2_MP4", 12) == 0)) ||
891                                                 strcmp(mime+6, "x-matroska") == 0 ||
892                                                 strcmp(mime+6, "x-msvideo") == 0 ||
893                                                 strcmp(mime+6, "mpeg") == 0 )
894                                        {
895                                                strcpy(mime+6, "avi");
896                                                if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_NTSC", 12) != 0 )
897                                                {
898                                                        sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_PS_NTSC;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
899                                                        add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
900                                                                resolution, dlna_buf, mime, detailID, ext, passed_args);
901                                                }
902                                                if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_PAL", 11) != 0 )
903                                                {
904                                                        sprintf(dlna_buf, "DLNA.ORG_PN=MPEG_PS_PAL;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
905                                                        add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
906                                                                resolution, dlna_buf, mime, detailID, ext, passed_args);
907                                                }
908                                        }
909                                        break;
910                                case ESonyBravia:
911                                        /* BRAVIA KDL-##*X### series TVs do natively support AVC/AC3 in TS, but
912                                           require profile to be renamed (applies to _T and _ISO variants also) */
913                                        if( dlna_pn &&
914                                            (strncmp(dlna_pn, "AVC_TS_MP_SD_AC3", 16) == 0 ||
915                                             strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 ||
916                                             strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0))
917                                        {
918                                                sprintf(dlna_buf, "DLNA.ORG_PN=AVC_TS_HD_50_AC3;DLNA.ORG_OP=01;DLNA.ORG_CI=0");
919                                                add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
920                                                        resolution, dlna_buf, mime, detailID, ext, passed_args);
921                                        }
922                                        break;
923                                case ELGDevice:
924                                        if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%s'", detailID) > 0 )
925                                        {
926                                                ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:text/srt:*\"&gt;"
927                                                                     "http://%s:%d/Captions/%s.srt"
928                                                                   "&lt;/res&gt;",
929                                                                   lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
930                                        }
931                                        break;
932                                default:
933                                        break;
934                                }
935                        }
936                }
937                ret = strcatf(str, "&lt;/item&gt;");
938        }
939        else if( strncmp(class, "container", 9) == 0 )
940        {
941                ret = strcatf(str, "&lt;container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent);
942                if( passed_args->filter & FILTER_CHILDCOUNT )
943                {
944                        int children;
945                        ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", id);
946                        children = (ret > 0) ? ret : 0;
947                        ret = strcatf(str, "childCount=\"%d\"", children);
948                }
949                /* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */
950                if( (passed_args->requested == 1) && (strcmp(id, "0") == 0) )
951                {
952                        if( passed_args->filter & FILTER_UPNP_SEARCHCLASS )
953                        {
954                                ret = strcatf(str, "&gt;"
955                                                   "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.audioItem&lt;/upnp:searchClass&gt;"
956                                                   "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.imageItem&lt;/upnp:searchClass&gt;"
957                                                   "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.videoItem&lt;/upnp:searchClass");
958                        }
959                }
960                ret = strcatf(str, "&gt;"
961                                   "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
962                                   "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
963                                   title, class);
964                if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
965                        ret = strcatf(str, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
966                }
967                if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
968                        ret = strcatf(str, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
969                }
970                if( artist && (passed_args->filter & FILTER_UPNP_ARTIST) ) {
971                        ret = strcatf(str, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
972                }
973                if( album_art && atoi(album_art) && (passed_args->filter & FILTER_UPNP_ALBUMARTURI) ) {
974                        ret = strcatf(str, "&lt;upnp:albumArtURI ");
975                        if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
976                                ret = strcatf(str, "dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"");
977                        }
978                        ret = strcatf(str, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg&lt;/upnp:albumArtURI&gt;",
979                                           lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
980                }
981                ret = strcatf(str, "&lt;/container&gt;");
982        }
983
984        return 0;
985}
986
987static void
988BrowseContentDirectory(struct upnphttp * h, const char * action)
989{
990        static const char resp0[] =
991                        "<u:BrowseResponse "
992                        "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
993                        "<Result>"
994                        "&lt;DIDL-Lite"
995                        CONTENT_DIRECTORY_SCHEMAS;
996        char *zErrMsg = NULL;
997        char *sql, *ptr;
998        struct Response args;
999        struct string_s str;
1000        int totalMatches;
1001        int ret;
1002        char *ObjectID, *Filter, *BrowseFlag, *SortCriteria;
1003        char *orderBy = NULL;
1004        struct NameValueParserData data;
1005        int RequestedCount = 0;
1006        int StartingIndex = 0;
1007
1008        memset(&args, 0, sizeof(args));
1009        memset(&str, 0, sizeof(str));
1010
1011        ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1012
1013        ObjectID = GetValueFromNameValueList(&data, "ObjectID");
1014        Filter = GetValueFromNameValueList(&data, "Filter");
1015        BrowseFlag = GetValueFromNameValueList(&data, "BrowseFlag");
1016        SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
1017
1018        if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
1019                RequestedCount = atoi(ptr);
1020        if( !RequestedCount )
1021                RequestedCount = -1;
1022        if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
1023                StartingIndex = atoi(ptr);
1024        if( !BrowseFlag || (strcmp(BrowseFlag, "BrowseDirectChildren") && strcmp(BrowseFlag, "BrowseMetadata")) )
1025        {
1026                SoapError(h, 402, "Invalid Args");
1027                goto browse_error;
1028        }
1029        if( !ObjectID && !(ObjectID = GetValueFromNameValueList(&data, "ContainerID")) )
1030        {
1031                SoapError(h, 701, "No such object error");
1032                goto browse_error;
1033        }
1034
1035        str.data = malloc(DEFAULT_RESP_SIZE);
1036        str.size = DEFAULT_RESP_SIZE;
1037        str.off = sprintf(str.data, "%s", resp0);
1038        /* See if we need to include DLNA namespace reference */
1039        args.iface = h->iface;
1040        args.filter = set_filter_flags(Filter, h);
1041        if( args.filter & FILTER_DLNA_NAMESPACE )
1042        {
1043                ret = strcatf(&str, DLNA_NAMESPACE);
1044        }
1045        strcatf(&str, "&gt;\n");
1046
1047        args.returned = 0;
1048        args.requested = RequestedCount;
1049        args.client = h->req_client;
1050        args.flags = h->reqflags;
1051        args.str = &str;
1052        if( args.flags & FLAG_MS_PFS )
1053        {
1054                if( !strchr(ObjectID, '$') && (strcmp(ObjectID, "0") != 0) )
1055                {
1056                        ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS"
1057                                                     " where OBJECT_ID in "
1058                                                     "('"MUSIC_ID"$%s', '"VIDEO_ID"$%s', '"IMAGE_ID"$%s')",
1059                                                     ObjectID, ObjectID, ObjectID);
1060                        if( ptr )
1061                        {
1062                                ObjectID = ptr;
1063                                args.flags |= FLAG_FREE_OBJECT_ID;
1064                        }
1065                }
1066        }
1067        DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n"
1068                                 " * ObjectID: %s\n"
1069                                 " * Count: %d\n"
1070                                 " * StartingIndex: %d\n"
1071                                 " * BrowseFlag: %s\n"
1072                                 " * Filter: %s\n"
1073                                 " * SortCriteria: %s\n",
1074                                ObjectID, RequestedCount, StartingIndex,
1075                                BrowseFlag, Filter, SortCriteria);
1076
1077        if( strcmp(ObjectID, "0") == 0 )
1078        {
1079                args.flags |= FLAG_ROOT_CONTAINER;
1080                if( runtime_vars.root_container )
1081                {
1082                        if( (args.flags & FLAG_AUDIO_ONLY) && (strcmp(runtime_vars.root_container, BROWSEDIR_ID) == 0) )
1083                                ObjectID = MUSIC_DIR_ID;
1084                        else
1085                                ObjectID = runtime_vars.root_container;
1086                }
1087                else
1088                {
1089                        if( args.flags & FLAG_AUDIO_ONLY )
1090                                ObjectID = MUSIC_ID;
1091                }
1092        }
1093
1094        if( strcmp(BrowseFlag+6, "Metadata") == 0 )
1095        {
1096                args.requested = 1;
1097                sql = sqlite3_mprintf("SELECT %s, " COLUMNS
1098                                      "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1099                                      " where OBJECT_ID = '%s';",
1100                                      (args.flags & FLAG_ROOT_CONTAINER) ? "0, -1" : "o.OBJECT_ID, o.PARENT_ID",
1101                                      ObjectID);
1102                ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1103                totalMatches = args.returned;
1104        }
1105        else
1106        {
1107                ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", ObjectID);
1108                totalMatches = (ret > 0) ? ret : 0;
1109                ret = 0;
1110                if( SortCriteria )
1111                {
1112#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
1113                        if( totalMatches < 10000 )
1114#endif
1115                        orderBy = parse_sort_criteria(SortCriteria, &ret);
1116                }
1117                else
1118                {
1119                        if( strncmp(ObjectID, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 )
1120                        {
1121                                if( strcmp(ObjectID, MUSIC_PLIST_ID) == 0 )
1122                                        asprintf(&orderBy, "order by d.TITLE");
1123                                else
1124                                        asprintf(&orderBy, "order by length(OBJECT_ID), OBJECT_ID");
1125                        }
1126                        else if( args.client == ERokuSoundBridge )
1127                        {
1128#ifdef __sparc__
1129                                if( totalMatches < 10000 )
1130#endif
1131                                asprintf(&orderBy, "order by o.CLASS, d.DISC, d.TRACK, d.TITLE");
1132                        }
1133                }
1134                /* If it's a DLNA client, return an error for bad sort criteria */
1135                if( (args.flags & FLAG_DLNA) && ret )
1136                {
1137                        SoapError(h, 709, "Unsupported or invalid sort criteria");
1138                        goto browse_error;
1139                }
1140
1141                sql = sqlite3_mprintf( SELECT_COLUMNS
1142                                      "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1143                                      " where PARENT_ID = '%s' %s limit %d, %d;",
1144                                      ObjectID, orderBy, StartingIndex, RequestedCount);
1145                DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql);
1146                ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1147        }
1148        sqlite3_free(sql);
1149        if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
1150        {
1151                DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1152                sqlite3_free(zErrMsg);
1153        }
1154        /* Does the object even exist? */
1155        if( !totalMatches )
1156        {
1157                ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", ObjectID);
1158                if( ret <= 0 )
1159                {
1160                        SoapError(h, 701, "No such object error");
1161                        goto browse_error;
1162                }
1163        }
1164        ret = strcatf(&str, "&lt;/DIDL-Lite&gt;</Result>\n"
1165                            "<NumberReturned>%u</NumberReturned>\n"
1166                            "<TotalMatches>%u</TotalMatches>\n"
1167                            "<UpdateID>%u</UpdateID>"
1168                            "</u:BrowseResponse>",
1169                            args.returned, totalMatches, updateID);
1170        BuildSendAndCloseSoapResp(h, str.data, str.off);
1171browse_error:
1172        ClearNameValueList(&data);
1173        if( args.flags & FLAG_FREE_OBJECT_ID )
1174                sqlite3_free(ObjectID);
1175        free(orderBy);
1176        free(str.data);
1177}
1178
1179static void
1180SearchContentDirectory(struct upnphttp * h, const char * action)
1181{
1182        static const char resp0[] =
1183                        "<u:SearchResponse "
1184                        "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1185                        "<Result>"
1186                        "&lt;DIDL-Lite"
1187                        CONTENT_DIRECTORY_SCHEMAS;
1188        char *zErrMsg = NULL;
1189        char *sql, *ptr;
1190        struct Response args;
1191        struct string_s str;
1192        int totalMatches;
1193        int ret;
1194        char *ContainerID, *Filter, *SearchCriteria, *SortCriteria;
1195        char *newSearchCriteria = NULL, *orderBy = NULL;
1196        char groupBy[] = "group by DETAIL_ID";
1197        struct NameValueParserData data;
1198        int RequestedCount = 0;
1199        int StartingIndex = 0;
1200
1201        memset(&args, 0, sizeof(args));
1202        memset(&str, 0, sizeof(str));
1203
1204        ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1205
1206        ContainerID = GetValueFromNameValueList(&data, "ContainerID");
1207        Filter = GetValueFromNameValueList(&data, "Filter");
1208        SearchCriteria = GetValueFromNameValueList(&data, "SearchCriteria");
1209        SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
1210
1211        if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
1212                RequestedCount = atoi(ptr);
1213        if( !RequestedCount )
1214                RequestedCount = -1;
1215        if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
1216                StartingIndex = atoi(ptr);
1217        if( !ContainerID )
1218        {
1219                if( !(ContainerID = GetValueFromNameValueList(&data, "ObjectID")) )
1220                {
1221                        SoapError(h, 701, "No such object error");
1222                        goto search_error;
1223                }
1224        }
1225
1226        str.data = malloc(DEFAULT_RESP_SIZE);
1227        str.size = DEFAULT_RESP_SIZE;
1228        str.off = sprintf(str.data, "%s", resp0);
1229        /* See if we need to include DLNA namespace reference */
1230        args.iface = h->iface;
1231        args.filter = set_filter_flags(Filter, h);
1232        if( args.filter & FILTER_DLNA_NAMESPACE )
1233        {
1234                ret = strcatf(&str, DLNA_NAMESPACE);
1235        }
1236        strcatf(&str, "&gt;\n");
1237
1238        args.returned = 0;
1239        args.requested = RequestedCount;
1240        args.client = h->req_client;
1241        args.flags = h->reqflags;
1242        args.str = &str;
1243        if( args.flags & FLAG_MS_PFS )
1244        {
1245                if( !strchr(ContainerID, '$') && (strcmp(ContainerID, "0") != 0) )
1246                {
1247                        ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS"
1248                                                     " where OBJECT_ID in "
1249                                                     "('"MUSIC_ID"$%s', '"VIDEO_ID"$%s', '"IMAGE_ID"$%s')",
1250                                                     ContainerID, ContainerID, ContainerID);
1251                        if( ptr )
1252                        {
1253                                ContainerID = ptr;
1254                                args.flags |= FLAG_FREE_OBJECT_ID;
1255                        }
1256                }
1257                #if 0 // Looks like the 360 already does this
1258                /* Sort by track number for some containers */
1259                if( orderBy &&
1260                    ((strncmp(ContainerID, MUSIC_GENRE_ID, 3) == 0) ||
1261                     (strncmp(ContainerID, MUSIC_ARTIST_ID, 3) == 0) ||
1262                     (strncmp(ContainerID, MUSIC_ALBUM_ID, 3) == 0)) )
1263                {
1264                        DPRINTF(E_DEBUG, L_HTTP, "Old sort order: %s\n", orderBy);
1265                        sprintf(str_buf, "d.TRACK, ");
1266                        memmove(orderBy+18, orderBy+9, strlen(orderBy)+1);
1267                        memmove(orderBy+9, &str_buf, 9);
1268                        DPRINTF(E_DEBUG, L_HTTP, "New sort order: %s\n", orderBy);
1269                }
1270                #endif
1271        }
1272        DPRINTF(E_DEBUG, L_HTTP, "Searching ContentDirectory:\n"
1273                                 " * ObjectID: %s\n"
1274                                 " * Count: %d\n"
1275                                 " * StartingIndex: %d\n"
1276                                 " * SearchCriteria: %s\n"
1277                                 " * Filter: %s\n"
1278                                 " * SortCriteria: %s\n",
1279                                ContainerID, RequestedCount, StartingIndex,
1280                                SearchCriteria, Filter, SortCriteria);
1281
1282        if( strcmp(ContainerID, "0") == 0 )
1283                *ContainerID = '*';
1284        else if( strcmp(ContainerID, MUSIC_ALL_ID) == 0 )
1285                groupBy[0] = '\0';
1286        if( !SearchCriteria )
1287        {
1288                newSearchCriteria = strdup("1 = 1");
1289                SearchCriteria = newSearchCriteria;
1290        }
1291        else
1292        {
1293                SearchCriteria = modifyString(SearchCriteria, "&quot;", "\"", 0);
1294                SearchCriteria = modifyString(SearchCriteria, "&apos;", "'", 0);
1295                SearchCriteria = modifyString(SearchCriteria, "&lt;", "<", 0);
1296                SearchCriteria = modifyString(SearchCriteria, "&gt;", ">", 0);
1297                SearchCriteria = modifyString(SearchCriteria, "\\\"", "\"\"", 0);
1298                SearchCriteria = modifyString(SearchCriteria, "object.", "", 0);
1299                SearchCriteria = modifyString(SearchCriteria, "derivedfrom", "like", 1);
1300                SearchCriteria = modifyString(SearchCriteria, "contains", "like", 2);
1301                SearchCriteria = modifyString(SearchCriteria, "dc:date", "d.DATE", 0);
1302                SearchCriteria = modifyString(SearchCriteria, "dc:title", "d.TITLE", 0);
1303                SearchCriteria = modifyString(SearchCriteria, "dc:creator", "d.CREATOR", 0);
1304                SearchCriteria = modifyString(SearchCriteria, "upnp:class", "o.CLASS", 0);
1305                SearchCriteria = modifyString(SearchCriteria, "upnp:actor", "d.ARTIST", 0);
1306                SearchCriteria = modifyString(SearchCriteria, "upnp:artist", "d.ARTIST", 0);
1307                SearchCriteria = modifyString(SearchCriteria, "upnp:album", "d.ALBUM", 0);
1308                SearchCriteria = modifyString(SearchCriteria, "upnp:genre", "d.GENRE", 0);
1309                SearchCriteria = modifyString(SearchCriteria, "exists true", "is not NULL", 0);
1310                SearchCriteria = modifyString(SearchCriteria, "exists false", "is NULL", 0);
1311                SearchCriteria = modifyString(SearchCriteria, "@refID", "REF_ID", 0);
1312                if( strstr(SearchCriteria, "@id") )
1313                {
1314                        newSearchCriteria = strdup(SearchCriteria);
1315                        SearchCriteria = newSearchCriteria = modifyString(newSearchCriteria, "@id", "OBJECT_ID", 0);
1316                }
1317                if( strstr(SearchCriteria, "res is ") )
1318                {
1319                        if( !newSearchCriteria )
1320                                newSearchCriteria = strdup(SearchCriteria);
1321                        SearchCriteria = newSearchCriteria = modifyString(newSearchCriteria, "res is ", "MIME is ", 0);
1322                }
1323                #if 0 // Does 360 need this?
1324                if( strstr(SearchCriteria, "&amp;") )
1325                {
1326                        if( newSearchCriteria )
1327                                newSearchCriteria = modifyString(newSearchCriteria, "&amp;", "&amp;amp;", 0);
1328                        else
1329                                newSearchCriteria = modifyString(strdup(SearchCriteria), "&amp;", "&amp;amp;", 0);
1330                        SearchCriteria = newSearchCriteria;
1331                }
1332                #endif
1333        }
1334        DPRINTF(E_DEBUG, L_HTTP, "Translated SearchCriteria: %s\n", SearchCriteria);
1335
1336        totalMatches = sql_get_int_field(db, "SELECT (select count(distinct DETAIL_ID)"
1337                                             " from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1338                                             " where (OBJECT_ID glob '%s$*') and (%s))"
1339                                             " + "
1340                                             "(select count(*) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1341                                             " where (OBJECT_ID = '%s') and (%s))",
1342                                             ContainerID, SearchCriteria, ContainerID, SearchCriteria);
1343        if( totalMatches < 0 )
1344        {
1345                /* Must be invalid SQL, so most likely bad or unhandled search criteria. */
1346                SoapError(h, 708, "Unsupported or invalid search criteria");
1347                goto search_error;
1348        }
1349        /* Does the object even exist? */
1350        if( !totalMatches )
1351        {
1352                ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'",
1353                                        !strcmp(ContainerID, "*")?"0":ContainerID);
1354                if( ret <= 0 )
1355                {
1356                        SoapError(h, 710, "No such container");
1357                        goto search_error;
1358                }
1359        }
1360#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
1361        ret = 0;
1362        if( totalMatches < 10000 )
1363#endif
1364                orderBy = parse_sort_criteria(SortCriteria, &ret);
1365        /* If it's a DLNA client, return an error for bad sort criteria */
1366        if( (args.flags & FLAG_DLNA) && ret )
1367        {
1368                SoapError(h, 709, "Unsupported or invalid sort criteria");
1369                goto search_error;
1370        }
1371
1372        sql = sqlite3_mprintf( SELECT_COLUMNS
1373                              "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1374                              " where OBJECT_ID glob '%s$*' and (%s) %s "
1375                              "%z %s"
1376                              " limit %d, %d",
1377                              ContainerID, SearchCriteria, groupBy,
1378                              (*ContainerID == '*') ? NULL :
1379                              sqlite3_mprintf("UNION ALL " SELECT_COLUMNS
1380                                              "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1381                                              " where OBJECT_ID = '%s' and (%s) ", ContainerID, SearchCriteria),
1382                              orderBy, StartingIndex, RequestedCount);
1383        DPRINTF(E_DEBUG, L_HTTP, "Search SQL: %s\n", sql);
1384        ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1385        if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
1386        {
1387                DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1388                sqlite3_free(zErrMsg);
1389        }
1390        sqlite3_free(sql);
1391        ret = strcatf(&str, "&lt;/DIDL-Lite&gt;</Result>\n"
1392                            "<NumberReturned>%u</NumberReturned>\n"
1393                            "<TotalMatches>%u</TotalMatches>\n"
1394                            "<UpdateID>%u</UpdateID>"
1395                            "</u:SearchResponse>",
1396                            args.returned, totalMatches, updateID);
1397        BuildSendAndCloseSoapResp(h, str.data, str.off);
1398search_error:
1399        ClearNameValueList(&data);
1400        if( args.flags & FLAG_FREE_OBJECT_ID )
1401                sqlite3_free(ContainerID);
1402        free(orderBy);
1403        free(newSearchCriteria);
1404        free(str.data);
1405}
1406
1407/*
1408If a control point calls QueryStateVariable on a state variable that is not
1409buffered in memory within (or otherwise available from) the service,
1410the service must return a SOAP fault with an errorCode of 404 Invalid Var.
1411
1412QueryStateVariable remains useful as a limited test tool but may not be
1413part of some future versions of UPnP.
1414*/
1415static void
1416QueryStateVariable(struct upnphttp * h, const char * action)
1417{
1418        static const char resp[] =
1419        "<u:%sResponse "
1420        "xmlns:u=\"%s\">"
1421                "<return>%s</return>"
1422        "</u:%sResponse>";
1423
1424        char body[512];
1425        int bodylen;
1426        struct NameValueParserData data;
1427        const char * var_name;
1428
1429        ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1430        /*var_name = GetValueFromNameValueList(&data, "QueryStateVariable"); */
1431        /*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/
1432        var_name = GetValueFromNameValueList(&data, "varName");
1433
1434        DPRINTF(E_INFO, L_HTTP, "QueryStateVariable(%.40s)\n", var_name);
1435
1436        if(!var_name)
1437        {
1438                SoapError(h, 402, "Invalid Args");
1439        }
1440        else if(strcmp(var_name, "ConnectionStatus") == 0)
1441        {       
1442                bodylen = snprintf(body, sizeof(body), resp,
1443                           action, "urn:schemas-upnp-org:control-1-0",
1444                                   "Connected", action);
1445                BuildSendAndCloseSoapResp(h, body, bodylen);
1446        }
1447        else
1448        {
1449                DPRINTF(E_WARN, L_HTTP, "%s: Unknown: %s\n", action, var_name?var_name:"");
1450                SoapError(h, 404, "Invalid Var");
1451        }
1452
1453        ClearNameValueList(&data);     
1454}
1455
1456static void
1457SamsungGetFeatureList(struct upnphttp * h, const char * action)
1458{
1459        static const char resp[] =
1460                "<u:X_GetFeatureListResponse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1461                "<FeatureList>"
1462                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
1463                "&lt;Features xmlns=\"urn:schemas-upnp-org:av:avs\""
1464                " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
1465                " xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\"&gt;"
1466                "&lt;Feature name=\"samsung.com_BASICVIEW\" version=\"1\"&gt;"
1467                 "&lt;container id=\"1\" type=\"object.item.audioItem\"/&gt;"
1468                 "&lt;container id=\"2\" type=\"object.item.videoItem\"/&gt;"
1469                 "&lt;container id=\"3\" type=\"object.item.imageItem\"/&gt;"
1470                "&lt;/Feature&gt;"
1471                "</FeatureList></u:X_GetFeatureListResponse>";
1472
1473        BuildSendAndCloseSoapResp(h, resp, sizeof(resp)-1);
1474}
1475
1476static void
1477SamsungSetBookmark(struct upnphttp * h, const char * action)
1478{
1479        static const char resp[] =
1480            "<u:X_SetBookmarkResponse"
1481            " xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1482            "</u:X_SetBookmarkResponse>";
1483
1484        struct NameValueParserData data;
1485        char *ObjectID, *PosSecond;
1486        int ret;
1487
1488        ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data);
1489        ObjectID = GetValueFromNameValueList(&data, "ObjectID");
1490        PosSecond = GetValueFromNameValueList(&data, "PosSecond");
1491        if( ObjectID && PosSecond )
1492        {
1493                ret = sql_exec(db, "INSERT OR REPLACE into BOOKMARKS"
1494                                   " VALUES "
1495                                   "((select DETAIL_ID from OBJECTS where OBJECT_ID = '%s'), %s)", ObjectID, PosSecond);
1496                if( ret != SQLITE_OK )
1497                        DPRINTF(E_WARN, L_METADATA, "Error setting bookmark %s on ObjectID='%s'\n", PosSecond, ObjectID);
1498                BuildSendAndCloseSoapResp(h, resp, sizeof(resp)-1);
1499        }
1500        else
1501                SoapError(h, 402, "Invalid Args");
1502
1503        ClearNameValueList(&data);     
1504}
1505
1506static const struct
1507{
1508        const char * methodName;
1509        void (*methodImpl)(struct upnphttp *, const char *);
1510}
1511soapMethods[] =
1512{
1513        { "QueryStateVariable", QueryStateVariable},
1514        { "Browse", BrowseContentDirectory},
1515        { "Search", SearchContentDirectory},
1516        { "GetSearchCapabilities", GetSearchCapabilities},
1517        { "GetSortCapabilities", GetSortCapabilities},
1518        { "GetSystemUpdateID", GetSystemUpdateID},
1519        { "GetProtocolInfo", GetProtocolInfo},
1520        { "GetCurrentConnectionIDs", GetCurrentConnectionIDs},
1521        { "GetCurrentConnectionInfo", GetCurrentConnectionInfo},
1522        { "IsAuthorized", IsAuthorizedValidated},
1523        { "IsValidated", IsAuthorizedValidated},
1524        { "X_GetFeatureList", SamsungGetFeatureList},
1525        { "X_SetBookmark", SamsungSetBookmark},
1526        { 0, 0 }
1527};
1528
1529void
1530ExecuteSoapAction(struct upnphttp * h, const char * action, int n)
1531{
1532        char * p;
1533
1534        p = strchr(action, '#');
1535        if(p)
1536        {
1537                int i = 0;
1538                int len;
1539                int methodlen;
1540                char * p2;
1541                p++;
1542                p2 = strchr(p, '"');
1543                if(p2)
1544                        methodlen = p2 - p;
1545                else
1546                        methodlen = n - (p - action);
1547                DPRINTF(E_DEBUG, L_HTTP, "SoapMethod: %.*s\n", methodlen, p);
1548                while(soapMethods[i].methodName)
1549                {
1550                        len = strlen(soapMethods[i].methodName);
1551                        if(strncmp(p, soapMethods[i].methodName, len) == 0)
1552                        {
1553                                soapMethods[i].methodImpl(h, soapMethods[i].methodName);
1554                                return;
1555                        }
1556                        i++;
1557                }
1558
1559                DPRINTF(E_WARN, L_HTTP, "SoapMethod: Unknown: %.*s\n", methodlen, p);
1560        }
1561
1562        SoapError(h, 401, "Invalid Action");
1563}
1564
1565/* Standard Errors:
1566 *
1567 * errorCode errorDescription Description
1568 * --------     ---------------- -----------
1569 * 401          Invalid Action  No action by that name at this service.
1570 * 402          Invalid Args    Could be any of the following: not enough in args,
1571 *                                                      too many in args, no in arg by that name,
1572 *                                                      one or more in args are of the wrong data type.
1573 * 403          Out of Sync     Out of synchronization.
1574 * 501          Action Failed   May be returned in current state of service
1575 *                                                      prevents invoking that action.
1576 * 600-699      TBD                     Common action errors. Defined by UPnP Forum
1577 *                                                      Technical Committee.
1578 * 700-799      TBD                     Action-specific errors for standard actions.
1579 *                                                      Defined by UPnP Forum working committee.
1580 * 800-899      TBD                     Action-specific errors for non-standard actions.
1581 *                                                      Defined by UPnP vendor.
1582*/
1583void
1584SoapError(struct upnphttp * h, int errCode, const char * errDesc)
1585{
1586        static const char resp[] =
1587                "<s:Envelope "
1588                "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
1589                "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1590                "<s:Body>"
1591                "<s:Fault>"
1592                "<faultcode>s:Client</faultcode>"
1593                "<faultstring>UPnPError</faultstring>"
1594                "<detail>"
1595                "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">"
1596                "<errorCode>%d</errorCode>"
1597                "<errorDescription>%s</errorDescription>"
1598                "</UPnPError>"
1599                "</detail>"
1600                "</s:Fault>"
1601                "</s:Body>"
1602                "</s:Envelope>";
1603
1604        char body[2048];
1605        int bodylen;
1606
1607        DPRINTF(E_WARN, L_HTTP, "Returning UPnPError %d: %s\n", errCode, errDesc);
1608        bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc);
1609        BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen);
1610        SendResp_upnphttp(h);
1611        CloseSocket_upnphttp(h);
1612}
1613
Note: See TracBrowser for help on using the repository browser.