minidlna: update to 1.1.5
[tomato.git] / release / src / router / minidlna / upnpsoap.c
blob80663182bed88d6bbe1d4361177003062559bd81
1 /* MiniDLNA project
3 * http://sourceforge.net/projects/minidlna/
5 * MiniDLNA media server
6 * Copyright (C) 2008-2009 Justin Maggard
8 * This file is part of MiniDLNA.
10 * MiniDLNA is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
12 * published by the Free Software Foundation.
14 * MiniDLNA is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
22 * Portions of the code from the MiniUPnP project:
24 * Copyright (c) 2006-2007, Thomas Bernard
25 * All rights reserved.
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions are met:
29 * * Redistributions of source code must retain the above copyright
30 * notice, this list of conditions and the following disclaimer.
31 * * Redistributions in binary form must reproduce the above copyright
32 * notice, this list of conditions and the following disclaimer in the
33 * documentation and/or other materials provided with the distribution.
34 * * The name of the author may not be used to endorse or promote products
35 * derived from this software without specific prior written permission.
37 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
38 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
39 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
40 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
41 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
43 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
44 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
45 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
46 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
47 * POSSIBILITY OF SUCH DAMAGE.
49 #include "config.h"
51 #include <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <sys/socket.h>
55 #include <unistd.h>
56 #include <dirent.h>
57 #include <sys/stat.h>
58 #include <sys/types.h>
59 #include <arpa/inet.h>
60 #include <netinet/in.h>
61 #include <netdb.h>
62 #include <ctype.h>
64 #include "upnpglobalvars.h"
65 #include "utils.h"
66 #include "upnphttp.h"
67 #include "upnpsoap.h"
68 #include "containers.h"
69 #include "upnpreplyparse.h"
70 #include "getifaddr.h"
71 #include "scanner.h"
72 #include "sql.h"
73 #include "log.h"
75 #ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */
76 # define __SORT_LIMIT if( totalMatches < 10000 )
77 #else
78 # define __SORT_LIMIT
79 #endif
81 /* Standard Errors:
83 * errorCode errorDescription Description
84 * -------- ---------------- -----------
85 * 401 Invalid Action No action by that name at this service.
86 * 402 Invalid Args Could be any of the following: not enough in args,
87 * too many in args, no in arg by that name,
88 * one or more in args are of the wrong data type.
89 * 403 Out of Sync Out of synchronization.
90 * 501 Action Failed May be returned in current state of service
91 * prevents invoking that action.
92 * 600-699 TBD Common action errors. Defined by UPnP Forum
93 * Technical Committee.
94 * 700-799 TBD Action-specific errors for standard actions.
95 * Defined by UPnP Forum working committee.
96 * 800-899 TBD Action-specific errors for non-standard actions.
97 * Defined by UPnP vendor.
99 static void
100 SoapError(struct upnphttp * h, int errCode, const char * errDesc)
102 static const char resp[] =
103 "<s:Envelope "
104 "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
105 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
106 "<s:Body>"
107 "<s:Fault>"
108 "<faultcode>s:Client</faultcode>"
109 "<faultstring>UPnPError</faultstring>"
110 "<detail>"
111 "<UPnPError xmlns=\"urn:schemas-upnp-org:control-1-0\">"
112 "<errorCode>%d</errorCode>"
113 "<errorDescription>%s</errorDescription>"
114 "</UPnPError>"
115 "</detail>"
116 "</s:Fault>"
117 "</s:Body>"
118 "</s:Envelope>";
120 char body[2048];
121 int bodylen;
123 DPRINTF(E_WARN, L_HTTP, "Returning UPnPError %d: %s\n", errCode, errDesc);
124 bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc);
125 BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen);
126 SendResp_upnphttp(h);
127 CloseSocket_upnphttp(h);
130 static void
131 BuildSendAndCloseSoapResp(struct upnphttp * h,
132 const char * body, int bodylen)
134 static const char beforebody[] =
135 "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
136 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
137 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
138 "<s:Body>";
140 static const char afterbody[] =
141 "</s:Body>"
142 "</s:Envelope>\r\n";
144 if (!body || bodylen < 0)
146 Send500(h);
147 return;
150 BuildHeader_upnphttp(h, 200, "OK", sizeof(beforebody) - 1
151 + sizeof(afterbody) - 1 + bodylen );
153 memcpy(h->res_buf + h->res_buflen, beforebody, sizeof(beforebody) - 1);
154 h->res_buflen += sizeof(beforebody) - 1;
156 memcpy(h->res_buf + h->res_buflen, body, bodylen);
157 h->res_buflen += bodylen;
159 memcpy(h->res_buf + h->res_buflen, afterbody, sizeof(afterbody) - 1);
160 h->res_buflen += sizeof(afterbody) - 1;
162 SendResp_upnphttp(h);
163 CloseSocket_upnphttp(h);
166 static void
167 GetSystemUpdateID(struct upnphttp * h, const char * action)
169 static const char resp[] =
170 "<u:%sResponse "
171 "xmlns:u=\"%s\">"
172 "<Id>%d</Id>"
173 "</u:%sResponse>";
175 char body[512];
176 int bodylen;
178 bodylen = snprintf(body, sizeof(body), resp,
179 action, "urn:schemas-upnp-org:service:ContentDirectory:1",
180 updateID, action);
181 BuildSendAndCloseSoapResp(h, body, bodylen);
184 static void
185 IsAuthorizedValidated(struct upnphttp * h, const char * action)
187 static const char resp[] =
188 "<u:%sResponse "
189 "xmlns:u=\"%s\">"
190 "<Result>%d</Result>"
191 "</u:%sResponse>";
193 char body[512];
194 struct NameValueParserData data;
195 const char * id;
197 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, XML_STORE_EMPTY_FL);
198 id = GetValueFromNameValueList(&data, "DeviceID");
199 if(id)
201 int bodylen;
202 bodylen = snprintf(body, sizeof(body), resp,
203 action, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
204 1, action);
205 BuildSendAndCloseSoapResp(h, body, bodylen);
207 else
208 SoapError(h, 402, "Invalid Args");
210 ClearNameValueList(&data);
213 static void
214 RegisterDevice(struct upnphttp * h, const char * action)
216 static const char resp[] =
217 "<u:%sResponse "
218 "xmlns:u=\"%s\">"
219 "<RegistrationRespMsg>%s</RegistrationRespMsg>"
220 "</u:%sResponse>";
222 char body[512];
223 int bodylen;
225 bodylen = snprintf(body, sizeof(body), resp,
226 action, "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
227 uuidvalue, action);
228 BuildSendAndCloseSoapResp(h, body, bodylen);
231 static void
232 GetProtocolInfo(struct upnphttp * h, const char * action)
234 static const char resp[] =
235 "<u:%sResponse "
236 "xmlns:u=\"%s\">"
237 "<Source>"
238 RESOURCE_PROTOCOL_INFO_VALUES
239 "</Source>"
240 "<Sink></Sink>"
241 "</u:%sResponse>";
243 char * body;
244 int bodylen;
246 bodylen = asprintf(&body, resp,
247 action, "urn:schemas-upnp-org:service:ConnectionManager:1",
248 action);
249 BuildSendAndCloseSoapResp(h, body, bodylen);
250 free(body);
253 static void
254 GetSortCapabilities(struct upnphttp * h, const char * action)
256 static const char resp[] =
257 "<u:%sResponse "
258 "xmlns:u=\"%s\">"
259 "<SortCaps>"
260 "dc:title,"
261 "dc:date,"
262 "upnp:class,"
263 "upnp:album,"
264 "upnp:originalTrackNumber"
265 "</SortCaps>"
266 "</u:%sResponse>";
268 char body[512];
269 int bodylen;
271 bodylen = snprintf(body, sizeof(body), resp,
272 action, "urn:schemas-upnp-org:service:ContentDirectory:1",
273 action);
274 BuildSendAndCloseSoapResp(h, body, bodylen);
277 static void
278 GetSearchCapabilities(struct upnphttp * h, const char * action)
280 static const char resp[] =
281 "<u:%sResponse xmlns:u=\"%s\">"
282 "<SearchCaps>"
283 "dc:creator,"
284 "dc:date,"
285 "dc:title,"
286 "upnp:album,"
287 "upnp:actor,"
288 "upnp:artist,"
289 "upnp:class,"
290 "upnp:genre,"
291 "@id,"
292 "@parentID,"
293 "@refID"
294 "</SearchCaps>"
295 "</u:%sResponse>";
297 char body[512];
298 int bodylen;
300 bodylen = snprintf(body, sizeof(body), resp,
301 action, "urn:schemas-upnp-org:service:ContentDirectory:1",
302 action);
303 BuildSendAndCloseSoapResp(h, body, bodylen);
306 static void
307 GetCurrentConnectionIDs(struct upnphttp * h, const char * action)
309 /* TODO: Use real data. - JM */
310 static const char resp[] =
311 "<u:%sResponse "
312 "xmlns:u=\"%s\">"
313 "<ConnectionIDs>0</ConnectionIDs>"
314 "</u:%sResponse>";
316 char body[512];
317 int bodylen;
319 bodylen = snprintf(body, sizeof(body), resp,
320 action, "urn:schemas-upnp-org:service:ConnectionManager:1",
321 action);
322 BuildSendAndCloseSoapResp(h, body, bodylen);
325 static void
326 GetCurrentConnectionInfo(struct upnphttp * h, const char * action)
328 /* TODO: Use real data. - JM */
329 static const char resp[] =
330 "<u:%sResponse "
331 "xmlns:u=\"%s\">"
332 "<RcsID>-1</RcsID>"
333 "<AVTransportID>-1</AVTransportID>"
334 "<ProtocolInfo></ProtocolInfo>"
335 "<PeerConnectionManager></PeerConnectionManager>"
336 "<PeerConnectionID>-1</PeerConnectionID>"
337 "<Direction>Output</Direction>"
338 "<Status>Unknown</Status>"
339 "</u:%sResponse>";
341 char body[sizeof(resp)+128];
342 struct NameValueParserData data;
343 const char *id_str;
344 int id;
345 char *endptr = NULL;
347 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, XML_STORE_EMPTY_FL);
348 id_str = GetValueFromNameValueList(&data, "ConnectionID");
349 DPRINTF(E_INFO, L_HTTP, "GetCurrentConnectionInfo(%s)\n", id_str);
350 if(id_str)
351 id = strtol(id_str, &endptr, 10);
352 if (!id_str || endptr == id_str)
354 SoapError(h, 402, "Invalid Args");
356 else if(id != 0)
358 SoapError(h, 701, "No such object error");
360 else
362 int bodylen;
363 bodylen = snprintf(body, sizeof(body), resp,
364 action, "urn:schemas-upnp-org:service:ConnectionManager:1",
365 action);
366 BuildSendAndCloseSoapResp(h, body, bodylen);
368 ClearNameValueList(&data);
371 /* Standard DLNA/UPnP filter flags */
372 #define FILTER_CHILDCOUNT 0x00000001
373 #define FILTER_DC_CREATOR 0x00000002
374 #define FILTER_DC_DATE 0x00000004
375 #define FILTER_DC_DESCRIPTION 0x00000008
376 #define FILTER_DLNA_NAMESPACE 0x00000010
377 #define FILTER_REFID 0x00000020
378 #define FILTER_RES 0x00000040
379 #define FILTER_RES_BITRATE 0x00000080
380 #define FILTER_RES_DURATION 0x00000100
381 #define FILTER_RES_NRAUDIOCHANNELS 0x00000200
382 #define FILTER_RES_RESOLUTION 0x00000400
383 #define FILTER_RES_SAMPLEFREQUENCY 0x00000800
384 #define FILTER_RES_SIZE 0x00001000
385 #define FILTER_SEARCHABLE 0x00002000
386 #define FILTER_UPNP_ACTOR 0x00004000
387 #define FILTER_UPNP_ALBUM 0x00008000
388 #define FILTER_UPNP_ALBUMARTURI 0x00010000
389 #define FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID 0x00020000
390 #define FILTER_UPNP_ARTIST 0x00040000
391 #define FILTER_UPNP_GENRE 0x00080000
392 #define FILTER_UPNP_ORIGINALTRACKNUMBER 0x00100000
393 #define FILTER_UPNP_SEARCHCLASS 0x00200000
394 #define FILTER_UPNP_STORAGEUSED 0x00400000
395 /* Vendor-specific filter flags */
396 #define FILTER_SEC_CAPTION_INFO_EX 0x01000000
397 #define FILTER_SEC_DCM_INFO 0x02000000
398 #define FILTER_PV_SUBTITLE_FILE_TYPE 0x04000000
399 #define FILTER_PV_SUBTITLE_FILE_URI 0x08000000
400 #define FILTER_PV_SUBTITLE 0x0C000000
401 #define FILTER_AV_MEDIA_CLASS 0x10000000
403 static uint32_t
404 set_filter_flags(char *filter, struct upnphttp *h)
406 char *item, *saveptr = NULL;
407 uint32_t flags = 0;
408 int samsung = h->req_client && (h->req_client->type->flags & FLAG_SAMSUNG);
410 if( !filter || (strlen(filter) <= 1) ) {
411 /* Not the full 32 bits. Skip vendor-specific stuff by default. */
412 flags = 0xFFFFFF;
413 if (samsung)
414 flags |= FILTER_SEC_CAPTION_INFO_EX | FILTER_SEC_DCM_INFO;
416 if (flags)
417 return flags;
419 if( samsung )
420 flags |= FILTER_DLNA_NAMESPACE;
421 item = strtok_r(filter, ",", &saveptr);
422 while( item != NULL )
424 if( saveptr )
425 *(item-1) = ',';
426 while( isspace(*item) )
427 item++;
428 if( strcmp(item, "@childCount") == 0 )
430 flags |= FILTER_CHILDCOUNT;
432 else if( strcmp(item, "@searchable") == 0 )
434 flags |= FILTER_SEARCHABLE;
436 else if( strcmp(item, "dc:creator") == 0 )
438 flags |= FILTER_DC_CREATOR;
440 else if( strcmp(item, "dc:date") == 0 )
442 flags |= FILTER_DC_DATE;
444 else if( strcmp(item, "dc:description") == 0 )
446 flags |= FILTER_DC_DESCRIPTION;
448 else if( strcmp(item, "dlna") == 0 )
450 flags |= FILTER_DLNA_NAMESPACE;
452 else if( strcmp(item, "@refID") == 0 )
454 flags |= FILTER_REFID;
456 else if( strcmp(item, "upnp:album") == 0 )
458 flags |= FILTER_UPNP_ALBUM;
460 else if( strcmp(item, "upnp:albumArtURI") == 0 )
462 flags |= FILTER_UPNP_ALBUMARTURI;
463 if( samsung )
464 flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID;
466 else if( strcmp(item, "upnp:albumArtURI@dlna:profileID") == 0 )
468 flags |= FILTER_UPNP_ALBUMARTURI;
469 flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID;
471 else if( strcmp(item, "upnp:artist") == 0 )
473 flags |= FILTER_UPNP_ARTIST;
475 else if( strcmp(item, "upnp:actor") == 0 )
477 flags |= FILTER_UPNP_ACTOR;
479 else if( strcmp(item, "upnp:genre") == 0 )
481 flags |= FILTER_UPNP_GENRE;
483 else if( strcmp(item, "upnp:originalTrackNumber") == 0 )
485 flags |= FILTER_UPNP_ORIGINALTRACKNUMBER;
487 else if( strcmp(item, "upnp:searchClass") == 0 )
489 flags |= FILTER_UPNP_SEARCHCLASS;
491 else if( strcmp(item, "upnp:storageUsed") == 0 )
493 flags |= FILTER_UPNP_STORAGEUSED;
495 else if( strcmp(item, "res") == 0 )
497 flags |= FILTER_RES;
499 else if( (strcmp(item, "res@bitrate") == 0) ||
500 (strcmp(item, "@bitrate") == 0) ||
501 ((strcmp(item, "bitrate") == 0) && (flags & FILTER_RES)) )
503 flags |= FILTER_RES;
504 flags |= FILTER_RES_BITRATE;
506 else if( (strcmp(item, "res@duration") == 0) ||
507 (strcmp(item, "@duration") == 0) ||
508 ((strcmp(item, "duration") == 0) && (flags & FILTER_RES)) )
510 flags |= FILTER_RES;
511 flags |= FILTER_RES_DURATION;
513 else if( (strcmp(item, "res@nrAudioChannels") == 0) ||
514 (strcmp(item, "@nrAudioChannels") == 0) ||
515 ((strcmp(item, "nrAudioChannels") == 0) && (flags & FILTER_RES)) )
517 flags |= FILTER_RES;
518 flags |= FILTER_RES_NRAUDIOCHANNELS;
520 else if( (strcmp(item, "res@resolution") == 0) ||
521 (strcmp(item, "@resolution") == 0) ||
522 ((strcmp(item, "resolution") == 0) && (flags & FILTER_RES)) )
524 flags |= FILTER_RES;
525 flags |= FILTER_RES_RESOLUTION;
527 else if( (strcmp(item, "res@sampleFrequency") == 0) ||
528 (strcmp(item, "@sampleFrequency") == 0) ||
529 ((strcmp(item, "sampleFrequency") == 0) && (flags & FILTER_RES)) )
531 flags |= FILTER_RES;
532 flags |= FILTER_RES_SAMPLEFREQUENCY;
534 else if( (strcmp(item, "res@size") == 0) ||
535 (strcmp(item, "@size") == 0) ||
536 (strcmp(item, "size") == 0) )
538 flags |= FILTER_RES;
539 flags |= FILTER_RES_SIZE;
541 else if( strcmp(item, "sec:CaptionInfoEx") == 0 )
543 flags |= FILTER_SEC_CAPTION_INFO_EX;
545 else if( strcmp(item, "sec:dcmInfo") == 0 )
547 flags |= FILTER_SEC_DCM_INFO;
549 else if( strcmp(item, "res@pv:subtitleFileType") == 0 )
551 flags |= FILTER_PV_SUBTITLE_FILE_TYPE;
553 else if( strcmp(item, "res@pv:subtitleFileUri") == 0 )
555 flags |= FILTER_PV_SUBTITLE_FILE_URI;
557 else if( strcmp(item, "av:mediaClass") == 0 )
559 flags |= FILTER_AV_MEDIA_CLASS;
561 item = strtok_r(NULL, ",", &saveptr);
564 return flags;
567 static char *
568 parse_sort_criteria(char *sortCriteria, int *error)
570 char *order = NULL;
571 char *item, *saveptr;
572 int i, ret, reverse, title_sorted = 0;
573 struct string_s str;
574 *error = 0;
576 if( force_sort_criteria )
577 sortCriteria = strdup(force_sort_criteria);
578 if( !sortCriteria )
579 return NULL;
581 if( (item = strtok_r(sortCriteria, ",", &saveptr)) )
583 order = malloc(4096);
584 str.data = order;
585 str.size = 4096;
586 str.off = 0;
587 strcatf(&str, "order by ");
589 for( i = 0; item != NULL; i++ )
591 reverse=0;
592 if( i )
593 strcatf(&str, ", ");
594 if( *item == '+' )
596 item++;
598 else if( *item == '-' )
600 reverse = 1;
601 item++;
603 else
605 DPRINTF(E_ERROR, L_HTTP, "No order specified [%s]\n", item);
606 goto bad_direction;
608 if( strcasecmp(item, "upnp:class") == 0 )
610 strcatf(&str, "o.CLASS");
612 else if( strcasecmp(item, "dc:title") == 0 )
614 strcatf(&str, "d.TITLE");
615 title_sorted = 1;
617 else if( strcasecmp(item, "dc:date") == 0 )
619 strcatf(&str, "d.DATE");
621 else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 )
623 strcatf(&str, "d.DISC, d.TRACK");
625 else if( strcasecmp(item, "upnp:album") == 0 )
627 strcatf(&str, "d.ALBUM");
629 else
631 DPRINTF(E_ERROR, L_HTTP, "Unhandled SortCriteria [%s]\n", item);
632 bad_direction:
633 *error = -1;
634 if( i )
636 ret = strlen(order);
637 order[ret-2] = '\0';
639 i--;
640 goto unhandled_order;
643 if( reverse )
644 strcatf(&str, " DESC");
645 unhandled_order:
646 item = strtok_r(NULL, ",", &saveptr);
648 if( i <= 0 )
650 free(order);
651 if( force_sort_criteria )
652 free(sortCriteria);
653 return NULL;
655 /* Add a "tiebreaker" sort order */
656 if( !title_sorted )
657 strcatf(&str, ", TITLE ASC");
659 if( force_sort_criteria )
660 free(sortCriteria);
662 return order;
665 inline static void
666 add_resized_res(int srcw, int srch, int reqw, int reqh, char *dlna_pn,
667 char *detailID, struct Response *args)
669 int dstw = reqw;
670 int dsth = reqh;
672 if( (args->flags & FLAG_NO_RESIZE) && reqw > 160 && reqh > 160 )
673 return;
675 strcatf(args->str, "&lt;res ");
676 if( args->filter & FILTER_RES_RESOLUTION )
678 dstw = reqw;
679 dsth = ((((reqw<<10)/srcw)*srch)>>10);
680 if( dsth > reqh ) {
681 dsth = reqh;
682 dstw = (((reqh<<10)/srch) * srcw>>10);
684 strcatf(args->str, "resolution=\"%dx%d\" ", dstw, dsth);
686 strcatf(args->str, "protocolInfo=\"http-get:*:image/jpeg:"
687 "DLNA.ORG_PN=%s;DLNA.ORG_CI=1;DLNA.ORG_FLAGS=%08X%024X\"&gt;"
688 "http://%s:%d/Resized/%s.jpg?width=%d,height=%d"
689 "&lt;/res&gt;",
690 dlna_pn, DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B|DLNA_FLAG_TM_I, 0,
691 lan_addr[args->iface].str, runtime_vars.port,
692 detailID, dstw, dsth);
695 inline static void
696 add_res(char *size, char *duration, char *bitrate, char *sampleFrequency,
697 char *nrAudioChannels, char *resolution, char *dlna_pn, char *mime,
698 char *detailID, const char *ext, struct Response *args)
700 strcatf(args->str, "&lt;res ");
701 if( size && (args->filter & FILTER_RES_SIZE) ) {
702 strcatf(args->str, "size=\"%s\" ", size);
704 if( duration && (args->filter & FILTER_RES_DURATION) ) {
705 strcatf(args->str, "duration=\"%s\" ", duration);
707 if( bitrate && (args->filter & FILTER_RES_BITRATE) ) {
708 int br = atoi(bitrate);
709 if(args->flags & FLAG_MS_PFS)
710 br /= 8;
711 strcatf(args->str, "bitrate=\"%d\" ", br);
713 if( sampleFrequency && (args->filter & FILTER_RES_SAMPLEFREQUENCY) ) {
714 strcatf(args->str, "sampleFrequency=\"%s\" ", sampleFrequency);
716 if( nrAudioChannels && (args->filter & FILTER_RES_NRAUDIOCHANNELS) ) {
717 strcatf(args->str, "nrAudioChannels=\"%s\" ", nrAudioChannels);
719 if( resolution && (args->filter & FILTER_RES_RESOLUTION) ) {
720 strcatf(args->str, "resolution=\"%s\" ", resolution);
722 if( args->filter & FILTER_PV_SUBTITLE )
724 if( args->flags & FLAG_HAS_CAPTIONS )
726 if( args->filter & FILTER_PV_SUBTITLE_FILE_TYPE )
727 strcatf(args->str, "pv:subtitleFileType=\"SRT\" ");
728 if( args->filter & FILTER_PV_SUBTITLE_FILE_URI )
729 strcatf(args->str, "pv:subtitleFileUri=\"http://%s:%d/Captions/%s.srt\" ",
730 lan_addr[args->iface].str, runtime_vars.port, detailID);
733 strcatf(args->str, "protocolInfo=\"http-get:*:%s:%s\"&gt;"
734 "http://%s:%d/MediaItems/%s.%s"
735 "&lt;/res&gt;",
736 mime, dlna_pn, lan_addr[args->iface].str,
737 runtime_vars.port, detailID, ext);
740 static int
741 get_child_count(const char *object, struct magic_container_s *magic)
743 int ret;
745 if (magic && magic->child_count)
746 ret = sql_get_int_field(db, "SELECT count(*) from %s", magic->child_count);
747 else if (magic && magic->objectid && *(magic->objectid))
748 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", *(magic->objectid));
749 else
750 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", object);
752 return (ret > 0) ? ret : 0;
755 static int
756 object_exists(const char *object)
758 int ret;
759 ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'",
760 strcmp(object, "*") == 0 ? "0" : object);
761 return (ret > 0);
764 #define COLUMNS "o.DETAIL_ID, o.CLASS," \
765 " d.SIZE, d.TITLE, d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST," \
766 " d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RESOLUTION," \
767 " d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.ROTATION, d.DISC "
768 #define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, o.REF_ID, " COLUMNS
770 #define NON_ZERO(x) (x && atoi(x))
771 #define IS_ZERO(x) (!x || !atoi(x))
773 static int
774 callback(void *args, int argc, char **argv, char **azColName)
776 struct Response *passed_args = (struct Response *)args;
777 char *id = argv[0], *parent = argv[1], *refID = argv[2], *detailID = argv[3], *class = argv[4], *size = argv[5], *title = argv[6],
778 *duration = argv[7], *bitrate = argv[8], *sampleFrequency = argv[9], *artist = argv[10], *album = argv[11],
779 *genre = argv[12], *comment = argv[13], *nrAudioChannels = argv[14], *track = argv[15], *date = argv[16], *resolution = argv[17],
780 *tn = argv[18], *creator = argv[19], *dlna_pn = argv[20], *mime = argv[21], *album_art = argv[22], *rotate = argv[23];
781 char dlna_buf[128];
782 const char *ext;
783 struct string_s *str = passed_args->str;
784 int ret = 0;
786 /* Make sure we have at least 8KB left of allocated memory to finish the response. */
787 if( str->off > (str->size - 8192) )
789 #if MAX_RESPONSE_SIZE > 0
790 if( (str->size+DEFAULT_RESP_SIZE) <= MAX_RESPONSE_SIZE )
792 #endif
793 str->data = realloc(str->data, (str->size+DEFAULT_RESP_SIZE));
794 if( str->data )
796 str->size += DEFAULT_RESP_SIZE;
797 DPRINTF(E_DEBUG, L_HTTP, "UPnP SOAP response enlarged to %lu. [%d results so far]\n",
798 (unsigned long)str->size, passed_args->returned);
800 else
802 DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response was too big, and realloc failed!\n");
803 return -1;
805 #if MAX_RESPONSE_SIZE > 0
807 else
809 DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response cut short, to not exceed the max response size [%lld]!\n", (long long int)MAX_RESPONSE_SIZE);
810 return -1;
812 #endif
814 passed_args->returned++;
815 passed_args->flags &= ~RESPONSE_FLAGS;
817 if( strncmp(class, "item", 4) == 0 )
819 uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B;
820 char *alt_title = NULL;
821 /* We may need special handling for certain MIME types */
822 if( *mime == 'v' )
824 dlna_flags |= DLNA_FLAG_TM_S;
825 if( passed_args->flags & FLAG_MIME_AVI_DIVX )
827 if( strcmp(mime, "video/x-msvideo") == 0 )
829 if( creator )
830 strcpy(mime+6, "divx");
831 else
832 strcpy(mime+6, "avi");
835 else if( passed_args->flags & FLAG_MIME_AVI_AVI )
837 if( strcmp(mime, "video/x-msvideo") == 0 )
839 strcpy(mime+6, "avi");
842 else if( passed_args->client == EFreeBox && dlna_pn )
844 if( strncmp(dlna_pn, "AVC_TS", 6) == 0 ||
845 strncmp(dlna_pn, "MPEG_TS", 7) == 0 )
847 strcpy(mime+6, "mp2t");
850 if( !(passed_args->flags & FLAG_DLNA) )
852 if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
854 strcpy(mime+6, "mpeg");
857 if( (passed_args->flags & FLAG_CAPTION_RES) ||
858 (passed_args->filter & (FILTER_SEC_CAPTION_INFO_EX|FILTER_PV_SUBTITLE)) )
860 if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%s'", detailID) > 0 )
861 passed_args->flags |= FLAG_HAS_CAPTIONS;
863 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
864 if( passed_args->flags & FLAG_SAMSUNG )
866 if( strcmp(mime+6, "x-matroska") == 0 )
868 strcpy(mime+8, "mkv");
871 /* LG hack: subtitles won't get used unless dc:title contains a dot. */
872 else if( passed_args->client == ELGDevice && (passed_args->flags & FLAG_HAS_CAPTIONS) )
874 ret = asprintf(&alt_title, "%s.", title);
875 if( ret > 0 )
876 title = alt_title;
877 else
878 alt_title = NULL;
880 /* Asus OPlay reboots with titles longer than 23 characters with some file types. */
881 else if( passed_args->client == EAsusOPlay && (passed_args->flags & FLAG_HAS_CAPTIONS) )
883 if( strlen(title) > 23 )
884 title[23] = '\0';
887 else if( *mime == 'a' )
889 dlna_flags |= DLNA_FLAG_TM_S;
890 if( strcmp(mime+6, "x-flac") == 0 )
892 if( passed_args->flags & FLAG_MIME_FLAC_FLAC )
894 strcpy(mime+6, "flac");
897 else if( strcmp(mime+6, "x-wav") == 0 )
899 if( passed_args->flags & FLAG_MIME_WAV_WAV )
901 strcpy(mime+6, "wav");
905 else
906 dlna_flags |= DLNA_FLAG_TM_I;
908 if( dlna_pn )
909 snprintf(dlna_buf, sizeof(dlna_buf), "DLNA.ORG_PN=%s;"
910 "DLNA.ORG_OP=01;"
911 "DLNA.ORG_CI=0;"
912 "DLNA.ORG_FLAGS=%08X%024X",
913 dlna_pn, dlna_flags, 0);
914 else if( passed_args->flags & FLAG_DLNA )
915 snprintf(dlna_buf, sizeof(dlna_buf), "DLNA.ORG_OP=01;"
916 "DLNA.ORG_CI=0;"
917 "DLNA.ORG_FLAGS=%08X%024X",
918 dlna_flags, 0);
919 else
920 strcpy(dlna_buf, "*");
922 ret = strcatf(str, "&lt;item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent);
923 if( refID && (passed_args->filter & FILTER_REFID) ) {
924 ret = strcatf(str, " refID=\"%s\"", refID);
926 ret = strcatf(str, "&gt;"
927 "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
928 "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
929 title, class);
930 if( comment && (passed_args->filter & FILTER_DC_DESCRIPTION) ) {
931 ret = strcatf(str, "&lt;dc:description&gt;%.384s&lt;/dc:description&gt;", comment);
933 if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
934 ret = strcatf(str, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
936 if( date && (passed_args->filter & FILTER_DC_DATE) ) {
937 ret = strcatf(str, "&lt;dc:date&gt;%s&lt;/dc:date&gt;", date);
939 if( passed_args->filter & FILTER_SEC_DCM_INFO ) {
940 /* Get bookmark */
941 ret = strcatf(str, "&lt;sec:dcmInfo&gt;CREATIONDATE=0,FOLDER=%s,BM=%d&lt;/sec:dcmInfo&gt;",
942 title, sql_get_int_field(db, "SELECT SEC from BOOKMARKS where ID = '%s'", detailID));
944 if( artist ) {
945 if( (*mime == 'v') && (passed_args->filter & FILTER_UPNP_ACTOR) ) {
946 ret = strcatf(str, "&lt;upnp:actor&gt;%s&lt;/upnp:actor&gt;", artist);
948 if( passed_args->filter & FILTER_UPNP_ARTIST ) {
949 ret = strcatf(str, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
952 if( album && (passed_args->filter & FILTER_UPNP_ALBUM) ) {
953 ret = strcatf(str, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", album);
955 if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
956 ret = strcatf(str, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
958 if( strncmp(id, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) {
959 track = strrchr(id, '$')+1;
961 if( NON_ZERO(track) && (passed_args->filter & FILTER_UPNP_ORIGINALTRACKNUMBER) ) {
962 ret = strcatf(str, "&lt;upnp:originalTrackNumber&gt;%s&lt;/upnp:originalTrackNumber&gt;", track);
964 if( passed_args->filter & FILTER_RES ) {
965 ext = mime_to_ext(mime);
966 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
967 resolution, dlna_buf, mime, detailID, ext, passed_args);
968 if( *mime == 'i' ) {
969 int srcw, srch;
970 if( resolution && (sscanf(resolution, "%6dx%6d", &srcw, &srch) == 2) )
972 if( srcw > 4096 || srch > 4096 )
973 add_resized_res(srcw, srch, 4096, 4096, "JPEG_LRG", detailID, passed_args);
974 if( srcw > 1024 || srch > 768 )
975 add_resized_res(srcw, srch, 1024, 768, "JPEG_MED", detailID, passed_args);
976 if( srcw > 640 || srch > 480 )
977 add_resized_res(srcw, srch, 640, 480, "JPEG_SM", detailID, passed_args);
979 if( !(passed_args->flags & FLAG_RESIZE_THUMBS) && NON_ZERO(tn) && IS_ZERO(rotate) ) {
980 ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:%s:%s\"&gt;"
981 "http://%s:%d/Thumbnails/%s.jpg"
982 "&lt;/res&gt;",
983 mime, "DLNA.ORG_PN=JPEG_TN;DLNA.ORG_CI=1", lan_addr[passed_args->iface].str,
984 runtime_vars.port, detailID);
986 else
987 add_resized_res(srcw, srch, 160, 160, "JPEG_TN", detailID, passed_args);
989 else if( *mime == 'v' ) {
990 switch( passed_args->client ) {
991 case EToshibaTV:
992 if( dlna_pn &&
993 (strncmp(dlna_pn, "MPEG_TS_HD_NA", 13) == 0 ||
994 strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) == 0 ||
995 strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 ||
996 strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0))
998 sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_PS_NTSC");
999 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
1000 resolution, dlna_buf, mime, detailID, ext, passed_args);
1002 break;
1003 case ESonyBDP:
1004 if( dlna_pn &&
1005 (strncmp(dlna_pn, "AVC_TS", 6) == 0 ||
1006 strncmp(dlna_pn, "MPEG_TS", 7) == 0) )
1008 if( strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) != 0 )
1010 sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_TS_SD_NA");
1011 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
1012 resolution, dlna_buf, mime, detailID, ext, passed_args);
1014 if( strncmp(dlna_pn, "MPEG_TS_SD_EU", 13) != 0 )
1016 sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_TS_SD_EU");
1017 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
1018 resolution, dlna_buf, mime, detailID, ext, passed_args);
1021 else if( (dlna_pn &&
1022 (strncmp(dlna_pn, "AVC_MP4", 7) == 0 ||
1023 strncmp(dlna_pn, "MPEG4_P2_MP4", 12) == 0)) ||
1024 strcmp(mime+6, "x-matroska") == 0 ||
1025 strcmp(mime+6, "x-msvideo") == 0 ||
1026 strcmp(mime+6, "mpeg") == 0 )
1028 strcpy(mime+6, "avi");
1029 if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_NTSC", 12) != 0 )
1031 sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_PS_NTSC");
1032 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
1033 resolution, dlna_buf, mime, detailID, ext, passed_args);
1035 if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_PAL", 11) != 0 )
1037 sprintf(dlna_buf, "DLNA.ORG_PN=%s;DLNA.ORG_OP=01;DLNA.ORG_CI=1", "MPEG_PS_PAL");
1038 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
1039 resolution, dlna_buf, mime, detailID, ext, passed_args);
1042 break;
1043 case ESonyBravia:
1044 /* BRAVIA KDL-##*X### series TVs do natively support AVC/AC3 in TS, but
1045 require profile to be renamed (applies to _T and _ISO variants also) */
1046 if( dlna_pn &&
1047 (strncmp(dlna_pn, "AVC_TS_MP_SD_AC3", 16) == 0 ||
1048 strncmp(dlna_pn, "AVC_TS_MP_HD_AC3", 16) == 0 ||
1049 strncmp(dlna_pn, "AVC_TS_HP_HD_AC3", 16) == 0))
1051 sprintf(dlna_buf, "DLNA.ORG_PN=AVC_TS_HD_50_AC3%s", dlna_pn + 16);
1052 add_res(size, duration, bitrate, sampleFrequency, nrAudioChannels,
1053 resolution, dlna_buf, mime, detailID, ext, passed_args);
1055 break;
1056 case ESamsungSeriesCDE:
1057 case ELGDevice:
1058 case EAsusOPlay:
1059 default:
1060 if( passed_args->flags & FLAG_HAS_CAPTIONS )
1062 if( passed_args->flags & FLAG_CAPTION_RES )
1063 ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:text/srt:*\"&gt;"
1064 "http://%s:%d/Captions/%s.srt"
1065 "&lt;/res&gt;",
1066 lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
1067 else if( passed_args->filter & FILTER_SEC_CAPTION_INFO_EX )
1068 ret = strcatf(str, "&lt;sec:CaptionInfoEx sec:type=\"srt\"&gt;"
1069 "http://%s:%d/Captions/%s.srt"
1070 "&lt;/sec:CaptionInfoEx&gt;",
1071 lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
1073 free(alt_title);
1074 break;
1078 if( NON_ZERO(album_art) )
1080 /* Video and audio album art is handled differently */
1081 if( *mime == 'v' && (passed_args->filter & FILTER_RES) && !(passed_args->flags & FLAG_MS_PFS) ) {
1082 ret = strcatf(str, "&lt;res protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\"&gt;"
1083 "http://%s:%d/AlbumArt/%s-%s.jpg"
1084 "&lt;/res&gt;",
1085 lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
1086 } else if( passed_args->filter & FILTER_UPNP_ALBUMARTURI ) {
1087 ret = strcatf(str, "&lt;upnp:albumArtURI");
1088 if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
1089 ret = strcatf(str, " dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"");
1091 ret = strcatf(str, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg&lt;/upnp:albumArtURI&gt;",
1092 lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
1095 if( (passed_args->flags & FLAG_MS_PFS) && *mime == 'i' ) {
1096 if( passed_args->client == EMediaRoom && !album )
1097 ret = strcatf(str, "&lt;upnp:album&gt;%s&lt;/upnp:album&gt;", "[No Keywords]");
1099 /* EVA2000 doesn't seem to handle embedded thumbnails */
1100 if( !(passed_args->flags & FLAG_RESIZE_THUMBS) && NON_ZERO(tn) && IS_ZERO(rotate) ) {
1101 ret = strcatf(str, "&lt;upnp:albumArtURI&gt;"
1102 "http://%s:%d/Thumbnails/%s.jpg"
1103 "&lt;/upnp:albumArtURI&gt;",
1104 lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
1105 } else {
1106 ret = strcatf(str, "&lt;upnp:albumArtURI&gt;"
1107 "http://%s:%d/Resized/%s.jpg?width=160,height=160"
1108 "&lt;/upnp:albumArtURI&gt;",
1109 lan_addr[passed_args->iface].str, runtime_vars.port, detailID);
1112 ret = strcatf(str, "&lt;/item&gt;");
1114 else if( strncmp(class, "container", 9) == 0 )
1116 ret = strcatf(str, "&lt;container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent);
1117 if( passed_args->filter & FILTER_SEARCHABLE ) {
1118 ret = strcatf(str, "searchable=\"%d\" ", check_magic_container(id, passed_args->flags) ? 0 : 1);
1120 if( passed_args->filter & FILTER_CHILDCOUNT ) {
1121 ret = strcatf(str, "childCount=\"%d\"", get_child_count(id, check_magic_container(id, passed_args->flags)));
1123 /* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */
1124 if( passed_args->requested == 1 && strcmp(id, "0") == 0 && (passed_args->filter & FILTER_UPNP_SEARCHCLASS) ) {
1125 ret = strcatf(str, "&gt;"
1126 "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.audioItem&lt;/upnp:searchClass&gt;"
1127 "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.imageItem&lt;/upnp:searchClass&gt;"
1128 "&lt;upnp:searchClass includeDerived=\"1\"&gt;object.item.videoItem&lt;/upnp:searchClass");
1130 ret = strcatf(str, "&gt;"
1131 "&lt;dc:title&gt;%s&lt;/dc:title&gt;"
1132 "&lt;upnp:class&gt;object.%s&lt;/upnp:class&gt;",
1133 title, class);
1134 if( (passed_args->filter & FILTER_UPNP_STORAGEUSED) || strcmp(class+10, "storageFolder") == 0 ) {
1135 /* TODO: Implement real folder size tracking */
1136 ret = strcatf(str, "&lt;upnp:storageUsed&gt;%s&lt;/upnp:storageUsed&gt;", (size ? size : "-1"));
1138 if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
1139 ret = strcatf(str, "&lt;dc:creator&gt;%s&lt;/dc:creator&gt;", creator);
1141 if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
1142 ret = strcatf(str, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&gt;", genre);
1144 if( artist && (passed_args->filter & FILTER_UPNP_ARTIST) ) {
1145 ret = strcatf(str, "&lt;upnp:artist&gt;%s&lt;/upnp:artist&gt;", artist);
1147 if( NON_ZERO(album_art) && (passed_args->filter & FILTER_UPNP_ALBUMARTURI) ) {
1148 ret = strcatf(str, "&lt;upnp:albumArtURI ");
1149 if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) {
1150 ret = strcatf(str, "dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"");
1152 ret = strcatf(str, "&gt;http://%s:%d/AlbumArt/%s-%s.jpg&lt;/upnp:albumArtURI&gt;",
1153 lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID);
1155 if( passed_args->filter & FILTER_AV_MEDIA_CLASS ) {
1156 char class;
1157 if( strncmp(id, MUSIC_ID, sizeof(MUSIC_ID)) == 0 )
1158 class = 'M';
1159 else if( strncmp(id, VIDEO_ID, sizeof(VIDEO_ID)) == 0 )
1160 class = 'V';
1161 else if( strncmp(id, IMAGE_ID, sizeof(IMAGE_ID)) == 0 )
1162 class = 'P';
1163 else
1164 class = 0;
1165 if( class )
1166 ret = strcatf(str, "&lt;av:mediaClass xmlns:av=\"urn:schemas-sony-com:av\"&gt;"
1167 "%c&lt;/av:mediaClass&gt;", class);
1169 ret = strcatf(str, "&lt;/container&gt;");
1172 return 0;
1175 static void
1176 BrowseContentDirectory(struct upnphttp * h, const char * action)
1178 static const char resp0[] =
1179 "<u:BrowseResponse "
1180 "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1181 "<Result>"
1182 "&lt;DIDL-Lite"
1183 CONTENT_DIRECTORY_SCHEMAS;
1184 struct magic_container_s *magic;
1185 char *zErrMsg = NULL;
1186 char *sql, *ptr;
1187 struct Response args;
1188 struct string_s str;
1189 int totalMatches = 0;
1190 int ret;
1191 const char *ObjectID, *BrowseFlag;
1192 char *Filter, *SortCriteria;
1193 const char *objectid_sql = "o.OBJECT_ID";
1194 const char *parentid_sql = "o.PARENT_ID";
1195 const char *refid_sql = "o.REF_ID";
1196 char where[256] = "";
1197 char *orderBy = NULL;
1198 struct NameValueParserData data;
1199 int RequestedCount = 0;
1200 int StartingIndex = 0;
1202 memset(&args, 0, sizeof(args));
1203 memset(&str, 0, sizeof(str));
1205 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, 0);
1207 ObjectID = GetValueFromNameValueList(&data, "ObjectID");
1208 Filter = GetValueFromNameValueList(&data, "Filter");
1209 BrowseFlag = GetValueFromNameValueList(&data, "BrowseFlag");
1210 SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
1212 if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
1213 RequestedCount = atoi(ptr);
1214 if( RequestedCount < 0 )
1216 SoapError(h, 402, "Invalid Args");
1217 goto browse_error;
1219 if( !RequestedCount )
1220 RequestedCount = -1;
1221 if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
1222 StartingIndex = atoi(ptr);
1223 if( StartingIndex < 0 )
1225 SoapError(h, 402, "Invalid Args");
1226 goto browse_error;
1228 if( !BrowseFlag || (strcmp(BrowseFlag, "BrowseDirectChildren") && strcmp(BrowseFlag, "BrowseMetadata")) )
1230 SoapError(h, 402, "Invalid Args");
1231 goto browse_error;
1233 if( !ObjectID && !(ObjectID = GetValueFromNameValueList(&data, "ContainerID")) )
1235 SoapError(h, 402, "Invalid Args");
1236 goto browse_error;
1239 str.data = malloc(DEFAULT_RESP_SIZE);
1240 str.size = DEFAULT_RESP_SIZE;
1241 str.off = sprintf(str.data, "%s", resp0);
1242 /* See if we need to include DLNA namespace reference */
1243 args.iface = h->iface;
1244 args.filter = set_filter_flags(Filter, h);
1245 if( args.filter & FILTER_DLNA_NAMESPACE )
1246 ret = strcatf(&str, DLNA_NAMESPACE);
1247 if( args.filter & FILTER_PV_SUBTITLE )
1248 ret = strcatf(&str, PV_NAMESPACE);
1249 strcatf(&str, "&gt;\n");
1251 args.returned = 0;
1252 args.requested = RequestedCount;
1253 args.client = h->req_client ? h->req_client->type->type : 0;
1254 args.flags = h->req_client ? h->req_client->type->flags : 0;
1255 args.str = &str;
1256 DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n"
1257 " * ObjectID: %s\n"
1258 " * Count: %d\n"
1259 " * StartingIndex: %d\n"
1260 " * BrowseFlag: %s\n"
1261 " * Filter: %s\n"
1262 " * SortCriteria: %s\n",
1263 ObjectID, RequestedCount, StartingIndex,
1264 BrowseFlag, Filter, SortCriteria);
1266 if( strcmp(BrowseFlag+6, "Metadata") == 0 )
1268 const char *id = ObjectID;
1269 args.requested = 1;
1270 magic = in_magic_container(ObjectID, args.flags, &id);
1271 if (magic)
1273 if (magic->objectid_sql && strcmp(id, ObjectID) != 0)
1274 objectid_sql = magic->objectid_sql;
1275 if (magic->parentid_sql && strcmp(id, ObjectID) != 0)
1276 parentid_sql = magic->parentid_sql;
1277 if (magic->refid_sql)
1278 refid_sql = magic->refid_sql;
1280 sql = sqlite3_mprintf("SELECT %s, %s, %s, " COLUMNS
1281 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1282 " where OBJECT_ID = '%q';",
1283 objectid_sql, parentid_sql, refid_sql, id);
1284 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1285 totalMatches = args.returned;
1287 else
1289 magic = check_magic_container(ObjectID, args.flags);
1290 if (magic)
1292 if (magic->objectid && *(magic->objectid))
1293 ObjectID = *(magic->objectid);
1294 if (magic->objectid_sql)
1295 objectid_sql = magic->objectid_sql;
1296 if (magic->parentid_sql)
1297 parentid_sql = magic->parentid_sql;
1298 if (magic->refid_sql)
1299 refid_sql = magic->refid_sql;
1300 if (magic->where)
1301 strncpyt(where, magic->where, sizeof(where));
1302 if (magic->orderby && !GETFLAG(DLNA_STRICT_MASK))
1303 orderBy = strdup(magic->orderby);
1304 if (magic->max_count > 0)
1306 int limit = MAX(magic->max_count - StartingIndex, 0);
1307 ret = get_child_count(ObjectID, magic);
1308 totalMatches = MIN(ret, limit);
1309 if (RequestedCount > limit || RequestedCount < 0)
1310 RequestedCount = limit;
1313 if (!where[0])
1314 sqlite3_snprintf(sizeof(where), where, "PARENT_ID = '%q'", ObjectID);
1316 if (!totalMatches)
1317 totalMatches = get_child_count(ObjectID, magic);
1318 ret = 0;
1319 if (SortCriteria && !orderBy)
1321 __SORT_LIMIT
1322 orderBy = parse_sort_criteria(SortCriteria, &ret);
1324 else if (!orderBy)
1326 if( strncmp(ObjectID, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 )
1328 if( strcmp(ObjectID, MUSIC_PLIST_ID) == 0 )
1329 ret = xasprintf(&orderBy, "order by d.TITLE");
1330 else
1331 ret = xasprintf(&orderBy, "order by length(OBJECT_ID), OBJECT_ID");
1333 else if( args.flags & FLAG_FORCE_SORT )
1335 __SORT_LIMIT
1336 ret = xasprintf(&orderBy, "order by o.CLASS, d.DISC, d.TRACK, d.TITLE");
1338 /* LG TV ordering bug */
1339 else if( args.client == ELGDevice )
1340 ret = xasprintf(&orderBy, "order by o.CLASS, d.TITLE");
1341 else
1342 orderBy = parse_sort_criteria(SortCriteria, &ret);
1343 if( ret == -1 )
1345 free(orderBy);
1346 orderBy = NULL;
1347 ret = 0;
1350 /* If it's a DLNA client, return an error for bad sort criteria */
1351 if( ret < 0 && ((args.flags & FLAG_DLNA) || GETFLAG(DLNA_STRICT_MASK)) )
1353 SoapError(h, 709, "Unsupported or invalid sort criteria");
1354 goto browse_error;
1357 sql = sqlite3_mprintf("SELECT %s, %s, %s, " COLUMNS
1358 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1359 " where %s %s limit %d, %d;",
1360 objectid_sql, parentid_sql, refid_sql,
1361 where, THISORNUL(orderBy), StartingIndex, RequestedCount);
1362 DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql);
1363 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1365 if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
1367 DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1368 sqlite3_free(zErrMsg);
1369 SoapError(h, 709, "Unsupported or invalid sort criteria");
1370 goto browse_error;
1372 sqlite3_free(sql);
1373 /* Does the object even exist? */
1374 if( !totalMatches )
1376 if( !object_exists(ObjectID) )
1378 SoapError(h, 701, "No such object error");
1379 goto browse_error;
1382 ret = strcatf(&str, "&lt;/DIDL-Lite&gt;</Result>\n"
1383 "<NumberReturned>%u</NumberReturned>\n"
1384 "<TotalMatches>%u</TotalMatches>\n"
1385 "<UpdateID>%u</UpdateID>"
1386 "</u:BrowseResponse>",
1387 args.returned, totalMatches, updateID);
1388 BuildSendAndCloseSoapResp(h, str.data, str.off);
1389 browse_error:
1390 ClearNameValueList(&data);
1391 free(orderBy);
1392 free(str.data);
1395 static inline void
1396 charcat(struct string_s *str, char c)
1398 if (str->size <= str->off)
1400 str->data[str->size-1] = '\0';
1401 return;
1403 str->data[str->off] = c;
1404 str->off += 1;
1407 static inline char *
1408 parse_search_criteria(const char *str, char *sep)
1410 struct string_s criteria;
1411 int len;
1412 int literal = 0, like = 0;
1413 const char *s;
1415 if (!str)
1416 return strdup("1 = 1");
1418 len = strlen(str) + 32;
1419 criteria.data = malloc(len);
1420 criteria.size = len;
1421 criteria.off = 0;
1423 s = str;
1425 while (isspace(*s))
1426 s++;
1428 while (*s)
1430 if (literal)
1432 switch (*s) {
1433 case '&':
1434 if (strncmp(s, "&quot;", 6) == 0)
1435 s += 5;
1436 else if (strncmp(s, "&apos;", 6) == 0)
1438 strcatf(&criteria, "'");
1439 s += 6;
1440 continue;
1442 else
1443 break;
1444 case '"':
1445 literal = 0;
1446 if (like)
1448 charcat(&criteria, '%');
1449 like--;
1451 charcat(&criteria, '"');
1452 break;
1453 case '\\':
1454 if (strncmp(s, "\\&quot;", 7) == 0)
1456 strcatf(&criteria, "&amp;quot;");
1457 s += 7;
1458 continue;
1460 break;
1461 case 'o':
1462 if (strncmp(s, "object.", 7) == 0)
1463 s += 7;
1464 else if (strncmp(s, "object\"", 7) == 0 ||
1465 strncmp(s, "object&quot;", 12) == 0)
1467 s += 6;
1468 continue;
1470 default:
1471 charcat(&criteria, *s);
1472 break;
1475 else
1477 switch (*s) {
1478 case '\\':
1479 if (strncmp(s, "\\&quot;", 7) == 0)
1481 strcatf(&criteria, "&amp;quot;");
1482 s += 7;
1483 continue;
1485 else
1486 charcat(&criteria, *s);
1487 break;
1488 case '"':
1489 literal = 1;
1490 charcat(&criteria, *s);
1491 if (like == 2)
1493 charcat(&criteria, '%');
1494 like--;
1496 break;
1497 case '&':
1498 if (strncmp(s, "&quot;", 6) == 0)
1500 literal = 1;
1501 strcatf(&criteria, "\"");
1502 if (like == 2)
1504 charcat(&criteria, '%');
1505 like--;
1507 s += 5;
1509 else if (strncmp(s, "&apos;", 6) == 0)
1511 strcatf(&criteria, "'");
1512 s += 5;
1514 else if (strncmp(s, "&lt;", 4) == 0)
1516 strcatf(&criteria, "<");
1517 s += 3;
1519 else if (strncmp(s, "&gt;", 4) == 0)
1521 strcatf(&criteria, ">");
1522 s += 3;
1524 else
1525 charcat(&criteria, *s);
1526 break;
1527 case '@':
1528 if (strncmp(s, "@refID", 6) == 0)
1530 strcatf(&criteria, "REF_ID");
1531 s += 6;
1532 continue;
1534 else if (strncmp(s, "@id", 3) == 0)
1536 strcatf(&criteria, "OBJECT_ID");
1537 s += 3;
1538 continue;
1540 else if (strncmp(s, "@parentID", 9) == 0)
1542 strcatf(&criteria, "PARENT_ID");
1543 s += 9;
1544 strcpy(sep, "*");
1545 continue;
1547 else
1548 charcat(&criteria, *s);
1549 break;
1550 case 'c':
1551 if (strncmp(s, "contains", 8) == 0)
1553 strcatf(&criteria, "like");
1554 s += 8;
1555 like = 2;
1556 continue;
1558 else
1559 charcat(&criteria, *s);
1560 break;
1561 case 'd':
1562 if (strncmp(s, "derivedfrom", 11) == 0)
1564 strcatf(&criteria, "like");
1565 s += 11;
1566 like = 1;
1567 continue;
1569 else if (strncmp(s, "dc:date", 7) == 0)
1571 strcatf(&criteria, "d.DATE");
1572 s += 7;
1573 continue;
1575 else if (strncmp(s, "dc:title", 8) == 0)
1577 strcatf(&criteria, "d.TITLE");
1578 s += 8;
1579 continue;
1581 else if (strncmp(s, "dc:creator", 10) == 0)
1583 strcatf(&criteria, "d.CREATOR");
1584 s += 10;
1585 continue;
1587 else
1588 charcat(&criteria, *s);
1589 break;
1590 case 'e':
1591 if (strncmp(s, "exists", 6) == 0)
1593 s += 6;
1594 while (isspace(*s))
1595 s++;
1596 if (strncmp(s, "true", 4) == 0)
1598 strcatf(&criteria, "is not NULL");
1599 s += 3;
1601 else if (strncmp(s, "false", 5) == 0)
1603 strcatf(&criteria, "is NULL");
1604 s += 4;
1607 else
1608 charcat(&criteria, *s);
1609 break;
1610 case 'u':
1611 if (strncmp(s, "upnp:class", 10) == 0)
1613 strcatf(&criteria, "o.CLASS");
1614 s += 10;
1615 continue;
1617 else if (strncmp(s, "upnp:actor", 10) == 0)
1619 strcatf(&criteria, "d.ARTIST");
1620 s += 10;
1621 continue;
1623 else if (strncmp(s, "upnp:artist", 11) == 0)
1625 strcatf(&criteria, "d.ARTIST");
1626 s += 11;
1627 continue;
1629 else if (strncmp(s, "upnp:album", 10) == 0)
1631 strcatf(&criteria, "d.ALBUM");
1632 s += 10;
1633 continue;
1635 else if (strncmp(s, "upnp:genre", 10) == 0)
1637 strcatf(&criteria, "d.GENRE");
1638 s += 10;
1639 continue;
1641 else
1642 charcat(&criteria, *s);
1643 break;
1644 case '(':
1645 if (s > str && !isspace(s[-1]))
1646 charcat(&criteria, ' ');
1647 charcat(&criteria, *s);
1648 break;
1649 case ')':
1650 charcat(&criteria, *s);
1651 if (!isspace(s[1]))
1652 charcat(&criteria, ' ');
1653 break;
1654 default:
1655 charcat(&criteria, *s);
1656 break;
1659 s++;
1661 charcat(&criteria, '\0');
1663 return criteria.data;
1666 static void
1667 SearchContentDirectory(struct upnphttp * h, const char * action)
1669 static const char resp0[] =
1670 "<u:SearchResponse "
1671 "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1672 "<Result>"
1673 "&lt;DIDL-Lite"
1674 CONTENT_DIRECTORY_SCHEMAS;
1675 struct magic_container_s *magic;
1676 char *zErrMsg = NULL;
1677 char *sql, *ptr;
1678 struct Response args;
1679 struct string_s str;
1680 int totalMatches;
1681 int ret;
1682 const char *ContainerID;
1683 char *Filter, *SearchCriteria, *SortCriteria;
1684 char *orderBy = NULL, *where = NULL, sep[] = "$*";
1685 char groupBy[] = "group by DETAIL_ID";
1686 struct NameValueParserData data;
1687 int RequestedCount = 0;
1688 int StartingIndex = 0;
1690 memset(&args, 0, sizeof(args));
1691 memset(&str, 0, sizeof(str));
1693 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, 0);
1695 ContainerID = GetValueFromNameValueList(&data, "ContainerID");
1696 Filter = GetValueFromNameValueList(&data, "Filter");
1697 SearchCriteria = GetValueFromNameValueList(&data, "SearchCriteria");
1698 SortCriteria = GetValueFromNameValueList(&data, "SortCriteria");
1700 if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) )
1701 RequestedCount = atoi(ptr);
1702 if( !RequestedCount )
1703 RequestedCount = -1;
1704 if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) )
1705 StartingIndex = atoi(ptr);
1706 if( !ContainerID )
1708 if( !(ContainerID = GetValueFromNameValueList(&data, "ObjectID")) )
1710 SoapError(h, 402, "Invalid Args");
1711 goto search_error;
1715 str.data = malloc(DEFAULT_RESP_SIZE);
1716 str.size = DEFAULT_RESP_SIZE;
1717 str.off = sprintf(str.data, "%s", resp0);
1718 /* See if we need to include DLNA namespace reference */
1719 args.iface = h->iface;
1720 args.filter = set_filter_flags(Filter, h);
1721 if( args.filter & FILTER_DLNA_NAMESPACE )
1723 ret = strcatf(&str, DLNA_NAMESPACE);
1725 strcatf(&str, "&gt;\n");
1727 args.returned = 0;
1728 args.requested = RequestedCount;
1729 args.client = h->req_client ? h->req_client->type->type : 0;
1730 args.flags = h->req_client ? h->req_client->type->flags : 0;
1731 args.str = &str;
1732 DPRINTF(E_DEBUG, L_HTTP, "Searching ContentDirectory:\n"
1733 " * ObjectID: %s\n"
1734 " * Count: %d\n"
1735 " * StartingIndex: %d\n"
1736 " * SearchCriteria: %s\n"
1737 " * Filter: %s\n"
1738 " * SortCriteria: %s\n",
1739 ContainerID, RequestedCount, StartingIndex,
1740 SearchCriteria, Filter, SortCriteria);
1742 magic = check_magic_container(ContainerID, args.flags);
1743 if (magic && magic->objectid && *(magic->objectid))
1744 ContainerID = *(magic->objectid);
1746 if( strcmp(ContainerID, "0") == 0 )
1747 ContainerID = "*";
1749 if( strcmp(ContainerID, MUSIC_ALL_ID) == 0 ||
1750 GETFLAG(DLNA_STRICT_MASK) )
1751 groupBy[0] = '\0';
1753 where = parse_search_criteria(SearchCriteria, sep);
1754 DPRINTF(E_DEBUG, L_HTTP, "Translated SearchCriteria: %s\n", where);
1756 totalMatches = sql_get_int_field(db, "SELECT (select count(distinct DETAIL_ID)"
1757 " from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1758 " where (OBJECT_ID glob '%q%s') and (%s))"
1759 " + "
1760 "(select count(*) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
1761 " where (OBJECT_ID = '%q') and (%s))",
1762 ContainerID, sep, where, ContainerID, where);
1763 if( totalMatches < 0 )
1765 /* Must be invalid SQL, so most likely bad or unhandled search criteria. */
1766 SoapError(h, 708, "Unsupported or invalid search criteria");
1767 goto search_error;
1769 /* Does the object even exist? */
1770 if( !totalMatches )
1772 if( !object_exists(ContainerID) )
1774 SoapError(h, 710, "No such container");
1775 goto search_error;
1778 ret = 0;
1779 __SORT_LIMIT
1780 orderBy = parse_sort_criteria(SortCriteria, &ret);
1781 /* If it's a DLNA client, return an error for bad sort criteria */
1782 if( ret < 0 && ((args.flags & FLAG_DLNA) || GETFLAG(DLNA_STRICT_MASK)) )
1784 SoapError(h, 709, "Unsupported or invalid sort criteria");
1785 goto search_error;
1788 sql = sqlite3_mprintf( SELECT_COLUMNS
1789 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1790 " where OBJECT_ID glob '%q%s' and (%s) %s "
1791 "%z %s"
1792 " limit %d, %d",
1793 ContainerID, sep, where, groupBy,
1794 (*ContainerID == '*') ? NULL :
1795 sqlite3_mprintf("UNION ALL " SELECT_COLUMNS
1796 "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
1797 " where OBJECT_ID = '%q' and (%s) ", ContainerID, where),
1798 orderBy, StartingIndex, RequestedCount);
1799 DPRINTF(E_DEBUG, L_HTTP, "Search SQL: %s\n", sql);
1800 ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
1801 if( (ret != SQLITE_OK) && (zErrMsg != NULL) )
1803 DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql);
1804 sqlite3_free(zErrMsg);
1806 sqlite3_free(sql);
1807 ret = strcatf(&str, "&lt;/DIDL-Lite&gt;</Result>\n"
1808 "<NumberReturned>%u</NumberReturned>\n"
1809 "<TotalMatches>%u</TotalMatches>\n"
1810 "<UpdateID>%u</UpdateID>"
1811 "</u:SearchResponse>",
1812 args.returned, totalMatches, updateID);
1813 BuildSendAndCloseSoapResp(h, str.data, str.off);
1814 search_error:
1815 ClearNameValueList(&data);
1816 free(orderBy);
1817 free(where);
1818 free(str.data);
1822 If a control point calls QueryStateVariable on a state variable that is not
1823 buffered in memory within (or otherwise available from) the service,
1824 the service must return a SOAP fault with an errorCode of 404 Invalid Var.
1826 QueryStateVariable remains useful as a limited test tool but may not be
1827 part of some future versions of UPnP.
1829 static void
1830 QueryStateVariable(struct upnphttp * h, const char * action)
1832 static const char resp[] =
1833 "<u:%sResponse "
1834 "xmlns:u=\"%s\">"
1835 "<return>%s</return>"
1836 "</u:%sResponse>";
1838 char body[512];
1839 struct NameValueParserData data;
1840 const char * var_name;
1842 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, 0);
1843 /*var_name = GetValueFromNameValueList(&data, "QueryStateVariable"); */
1844 /*var_name = GetValueFromNameValueListIgnoreNS(&data, "varName");*/
1845 var_name = GetValueFromNameValueList(&data, "varName");
1847 DPRINTF(E_INFO, L_HTTP, "QueryStateVariable(%.40s)\n", var_name);
1849 if(!var_name)
1851 SoapError(h, 402, "Invalid Args");
1853 else if(strcmp(var_name, "ConnectionStatus") == 0)
1855 int bodylen;
1856 bodylen = snprintf(body, sizeof(body), resp,
1857 action, "urn:schemas-upnp-org:control-1-0",
1858 "Connected", action);
1859 BuildSendAndCloseSoapResp(h, body, bodylen);
1861 else
1863 DPRINTF(E_WARN, L_HTTP, "%s: Unknown: %s\n", action, THISORNUL(var_name));
1864 SoapError(h, 404, "Invalid Var");
1867 ClearNameValueList(&data);
1870 static void
1871 SamsungGetFeatureList(struct upnphttp * h, const char * action)
1873 static const char resp[] =
1874 "<u:X_GetFeatureListResponse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1875 "<FeatureList>"
1876 "&lt;Features xmlns=\"urn:schemas-upnp-org:av:avs\""
1877 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
1878 " xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\"&gt;"
1879 "&lt;Feature name=\"samsung.com_BASICVIEW\" version=\"1\"&gt;"
1880 "&lt;container id=\"%s\" type=\"object.item.audioItem\"/&gt;"
1881 "&lt;container id=\"%s\" type=\"object.item.videoItem\"/&gt;"
1882 "&lt;container id=\"%s\" type=\"object.item.imageItem\"/&gt;"
1883 "&lt;/Feature&gt;"
1884 "&lt;/Features&gt;"
1885 "</FeatureList></u:X_GetFeatureListResponse>";
1886 const char *audio = MUSIC_ID;
1887 const char *video = VIDEO_ID;
1888 const char *image = IMAGE_ID;
1889 char body[1024];
1890 int len;
1892 if (runtime_vars.root_container)
1894 if (strcmp(runtime_vars.root_container, BROWSEDIR_ID) == 0)
1896 audio = MUSIC_DIR_ID;
1897 video = VIDEO_DIR_ID;
1898 image = IMAGE_DIR_ID;
1900 else
1902 audio = runtime_vars.root_container;
1903 video = runtime_vars.root_container;
1904 image = runtime_vars.root_container;
1908 len = snprintf(body, sizeof(body), resp, audio, video, image);
1910 BuildSendAndCloseSoapResp(h, body, len);
1913 static void
1914 SamsungSetBookmark(struct upnphttp * h, const char * action)
1916 static const char resp[] =
1917 "<u:X_SetBookmarkResponse"
1918 " xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">"
1919 "</u:X_SetBookmarkResponse>";
1921 struct NameValueParserData data;
1922 char *ObjectID, *PosSecond;
1924 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, 0);
1925 ObjectID = GetValueFromNameValueList(&data, "ObjectID");
1926 PosSecond = GetValueFromNameValueList(&data, "PosSecond");
1927 if( ObjectID && PosSecond )
1929 int ret;
1930 ret = sql_exec(db, "INSERT OR REPLACE into BOOKMARKS"
1931 " VALUES "
1932 "((select DETAIL_ID from OBJECTS where OBJECT_ID = '%q'), %q)", ObjectID, PosSecond);
1933 if( ret != SQLITE_OK )
1934 DPRINTF(E_WARN, L_METADATA, "Error setting bookmark %s on ObjectID='%s'\n", PosSecond, ObjectID);
1935 BuildSendAndCloseSoapResp(h, resp, sizeof(resp)-1);
1937 else
1938 SoapError(h, 402, "Invalid Args");
1940 ClearNameValueList(&data);
1943 static const struct
1945 const char * methodName;
1946 void (*methodImpl)(struct upnphttp *, const char *);
1948 soapMethods[] =
1950 { "QueryStateVariable", QueryStateVariable},
1951 { "Browse", BrowseContentDirectory},
1952 { "Search", SearchContentDirectory},
1953 { "GetSearchCapabilities", GetSearchCapabilities},
1954 { "GetSortCapabilities", GetSortCapabilities},
1955 { "GetSystemUpdateID", GetSystemUpdateID},
1956 { "GetProtocolInfo", GetProtocolInfo},
1957 { "GetCurrentConnectionIDs", GetCurrentConnectionIDs},
1958 { "GetCurrentConnectionInfo", GetCurrentConnectionInfo},
1959 { "IsAuthorized", IsAuthorizedValidated},
1960 { "IsValidated", IsAuthorizedValidated},
1961 { "RegisterDevice", RegisterDevice},
1962 { "X_GetFeatureList", SamsungGetFeatureList},
1963 { "X_SetBookmark", SamsungSetBookmark},
1964 { 0, 0 }
1967 void
1968 ExecuteSoapAction(struct upnphttp * h, const char * action, int n)
1970 char * p;
1972 p = strchr(action, '#');
1973 if(p)
1975 int i = 0;
1976 int len;
1977 int methodlen;
1978 char * p2;
1979 p++;
1980 p2 = strchr(p, '"');
1981 if(p2)
1982 methodlen = p2 - p;
1983 else
1984 methodlen = n - (p - action);
1985 DPRINTF(E_DEBUG, L_HTTP, "SoapMethod: %.*s\n", methodlen, p);
1986 while(soapMethods[i].methodName)
1988 len = strlen(soapMethods[i].methodName);
1989 if(strncmp(p, soapMethods[i].methodName, len) == 0)
1991 soapMethods[i].methodImpl(h, soapMethods[i].methodName);
1992 return;
1994 i++;
1997 DPRINTF(E_WARN, L_HTTP, "SoapMethod: Unknown: %.*s\n", methodlen, p);
2000 SoapError(h, 401, "Invalid Action");