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 "config.h" 50 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> 63 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" 74 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 80 81/* Standard Errors: 82 * 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. 98*/ 99static void 100SoapError(struct upnphttp * h, int errCode, const char * errDesc) 101{ 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>"; 119 120 char body[2048]; 121 int bodylen; 122 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); 128} 129 130static void 131BuildSendAndCloseSoapResp(struct upnphttp * h, 132 const char * body, int bodylen) 133{ 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>"; 139 140 static const char afterbody[] = 141 "</s:Body>" 142 "</s:Envelope>\r\n"; 143 144 if (!body || bodylen < 0) 145 { 146 Send500(h); 147 return; 148 } 149 150 BuildHeader_upnphttp(h, 200, "OK", sizeof(beforebody) - 1 151 + sizeof(afterbody) - 1 + bodylen ); 152 153 memcpy(h->res_buf + h->res_buflen, beforebody, sizeof(beforebody) - 1); 154 h->res_buflen += sizeof(beforebody) - 1; 155 156 memcpy(h->res_buf + h->res_buflen, body, bodylen); 157 h->res_buflen += bodylen; 158 159 memcpy(h->res_buf + h->res_buflen, afterbody, sizeof(afterbody) - 1); 160 h->res_buflen += sizeof(afterbody) - 1; 161 162 SendResp_upnphttp(h); 163 CloseSocket_upnphttp(h); 164} 165 166static void 167GetSystemUpdateID(struct upnphttp * h, const char * action) 168{ 169 static const char resp[] = 170 "<u:%sResponse " 171 "xmlns:u=\"%s\">" 172 "<Id>%d</Id>" 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 updateID, action); 181 BuildSendAndCloseSoapResp(h, body, bodylen); 182} 183 184static void 185IsAuthorizedValidated(struct upnphttp * h, const char * action) 186{ 187 static const char resp[] = 188 "<u:%sResponse " 189 "xmlns:u=\"%s\">" 190 "<Result>%d</Result>" 191 "</u:%sResponse>"; 192 193 char body[512]; 194 struct NameValueParserData data; 195 const char * id; 196 197 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, XML_STORE_EMPTY_FL); 198 id = GetValueFromNameValueList(&data, "DeviceID"); 199 if(id) 200 { 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); 206 } 207 else 208 SoapError(h, 402, "Invalid Args"); 209 210 ClearNameValueList(&data); 211} 212 213static void 214RegisterDevice(struct upnphttp * h, const char * action) 215{ 216 static const char resp[] = 217 "<u:%sResponse " 218 "xmlns:u=\"%s\">" 219 "<RegistrationRespMsg>%s</RegistrationRespMsg>" 220 "</u:%sResponse>"; 221 222 char body[512]; 223 int bodylen; 224 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); 229} 230 231static void 232GetProtocolInfo(struct upnphttp * h, const char * action) 233{ 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>"; 242 243 char * body; 244 int bodylen; 245 246 bodylen = asprintf(&body, resp, 247 action, "urn:schemas-upnp-org:service:ConnectionManager:1", 248 action); 249 BuildSendAndCloseSoapResp(h, body, bodylen); 250 free(body); 251} 252 253static void 254GetSortCapabilities(struct upnphttp * h, const char * action) 255{ 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>"; 267 268 char body[512]; 269 int bodylen; 270 271 bodylen = snprintf(body, sizeof(body), resp, 272 action, "urn:schemas-upnp-org:service:ContentDirectory:1", 273 action); 274 BuildSendAndCloseSoapResp(h, body, bodylen); 275} 276 277static void 278GetSearchCapabilities(struct upnphttp * h, const char * action) 279{ 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>"; 296 297 char body[512]; 298 int bodylen; 299 300 bodylen = snprintf(body, sizeof(body), resp, 301 action, "urn:schemas-upnp-org:service:ContentDirectory:1", 302 action); 303 BuildSendAndCloseSoapResp(h, body, bodylen); 304} 305 306static void 307GetCurrentConnectionIDs(struct upnphttp * h, const char * action) 308{ 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>"; 315 316 char body[512]; 317 int bodylen; 318 319 bodylen = snprintf(body, sizeof(body), resp, 320 action, "urn:schemas-upnp-org:service:ConnectionManager:1", 321 action); 322 BuildSendAndCloseSoapResp(h, body, bodylen); 323} 324 325static void 326GetCurrentConnectionInfo(struct upnphttp * h, const char * action) 327{ 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>"; 340 341 char body[sizeof(resp)+128]; 342 struct NameValueParserData data; 343 const char *id_str; 344 int id; 345 char *endptr = NULL; 346 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) 353 { 354 SoapError(h, 402, "Invalid Args"); 355 } 356 else if(id != 0) 357 { 358 SoapError(h, 701, "No such object error"); 359 } 360 else 361 { 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); 367 } 368 ClearNameValueList(&data); 369} 370 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 402 403static uint32_t 404set_filter_flags(char *filter, struct upnphttp *h) 405{ 406 char *item, *saveptr = NULL; 407 uint32_t flags = 0; 408 int samsung = h->req_client && (h->req_client->type->flags & FLAG_SAMSUNG); 409 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; 415 } 416 if (flags) 417 return flags; 418 419 if( samsung ) 420 flags |= FILTER_DLNA_NAMESPACE; 421 item = strtok_r(filter, ",", &saveptr); 422 while( item != NULL ) 423 { 424 if( saveptr ) 425 *(item-1) = ','; 426 while( isspace(*item) ) 427 item++; 428 if( strcmp(item, "@childCount") == 0 ) 429 { 430 flags |= FILTER_CHILDCOUNT; 431 } 432 else if( strcmp(item, "@searchable") == 0 ) 433 { 434 flags |= FILTER_SEARCHABLE; 435 } 436 else if( strcmp(item, "dc:creator") == 0 ) 437 { 438 flags |= FILTER_DC_CREATOR; 439 } 440 else if( strcmp(item, "dc:date") == 0 ) 441 { 442 flags |= FILTER_DC_DATE; 443 } 444 else if( strcmp(item, "dc:description") == 0 ) 445 { 446 flags |= FILTER_DC_DESCRIPTION; 447 } 448 else if( strcmp(item, "dlna") == 0 ) 449 { 450 flags |= FILTER_DLNA_NAMESPACE; 451 } 452 else if( strcmp(item, "@refID") == 0 ) 453 { 454 flags |= FILTER_REFID; 455 } 456 else if( strcmp(item, "upnp:album") == 0 ) 457 { 458 flags |= FILTER_UPNP_ALBUM; 459 } 460 else if( strcmp(item, "upnp:albumArtURI") == 0 ) 461 { 462 flags |= FILTER_UPNP_ALBUMARTURI; 463 if( samsung ) 464 flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID; 465 } 466 else if( strcmp(item, "upnp:albumArtURI@dlna:profileID") == 0 ) 467 { 468 flags |= FILTER_UPNP_ALBUMARTURI; 469 flags |= FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID; 470 } 471 else if( strcmp(item, "upnp:artist") == 0 ) 472 { 473 flags |= FILTER_UPNP_ARTIST; 474 } 475 else if( strcmp(item, "upnp:actor") == 0 ) 476 { 477 flags |= FILTER_UPNP_ACTOR; 478 } 479 else if( strcmp(item, "upnp:genre") == 0 ) 480 { 481 flags |= FILTER_UPNP_GENRE; 482 } 483 else if( strcmp(item, "upnp:originalTrackNumber") == 0 ) 484 { 485 flags |= FILTER_UPNP_ORIGINALTRACKNUMBER; 486 } 487 else if( strcmp(item, "upnp:searchClass") == 0 ) 488 { 489 flags |= FILTER_UPNP_SEARCHCLASS; 490 } 491 else if( strcmp(item, "upnp:storageUsed") == 0 ) 492 { 493 flags |= FILTER_UPNP_STORAGEUSED; 494 } 495 else if( strcmp(item, "res") == 0 ) 496 { 497 flags |= FILTER_RES; 498 } 499 else if( (strcmp(item, "res@bitrate") == 0) || 500 (strcmp(item, "@bitrate") == 0) || 501 ((strcmp(item, "bitrate") == 0) && (flags & FILTER_RES)) ) 502 { 503 flags |= FILTER_RES; 504 flags |= FILTER_RES_BITRATE; 505 } 506 else if( (strcmp(item, "res@duration") == 0) || 507 (strcmp(item, "@duration") == 0) || 508 ((strcmp(item, "duration") == 0) && (flags & FILTER_RES)) ) 509 { 510 flags |= FILTER_RES; 511 flags |= FILTER_RES_DURATION; 512 } 513 else if( (strcmp(item, "res@nrAudioChannels") == 0) || 514 (strcmp(item, "@nrAudioChannels") == 0) || 515 ((strcmp(item, "nrAudioChannels") == 0) && (flags & FILTER_RES)) ) 516 { 517 flags |= FILTER_RES; 518 flags |= FILTER_RES_NRAUDIOCHANNELS; 519 } 520 else if( (strcmp(item, "res@resolution") == 0) || 521 (strcmp(item, "@resolution") == 0) || 522 ((strcmp(item, "resolution") == 0) && (flags & FILTER_RES)) ) 523 { 524 flags |= FILTER_RES; 525 flags |= FILTER_RES_RESOLUTION; 526 } 527 else if( (strcmp(item, "res@sampleFrequency") == 0) || 528 (strcmp(item, "@sampleFrequency") == 0) || 529 ((strcmp(item, "sampleFrequency") == 0) && (flags & FILTER_RES)) ) 530 { 531 flags |= FILTER_RES; 532 flags |= FILTER_RES_SAMPLEFREQUENCY; 533 } 534 else if( (strcmp(item, "res@size") == 0) || 535 (strcmp(item, "@size") == 0) || 536 (strcmp(item, "size") == 0) ) 537 { 538 flags |= FILTER_RES; 539 flags |= FILTER_RES_SIZE; 540 } 541 else if( strcmp(item, "sec:CaptionInfoEx") == 0 ) 542 { 543 flags |= FILTER_SEC_CAPTION_INFO_EX; 544 } 545 else if( strcmp(item, "sec:dcmInfo") == 0 ) 546 { 547 flags |= FILTER_SEC_DCM_INFO; 548 } 549 else if( strcmp(item, "res@pv:subtitleFileType") == 0 ) 550 { 551 flags |= FILTER_PV_SUBTITLE_FILE_TYPE; 552 } 553 else if( strcmp(item, "res@pv:subtitleFileUri") == 0 ) 554 { 555 flags |= FILTER_PV_SUBTITLE_FILE_URI; 556 } 557 else if( strcmp(item, "av:mediaClass") == 0 ) 558 { 559 flags |= FILTER_AV_MEDIA_CLASS; 560 } 561 item = strtok_r(NULL, ",", &saveptr); 562 } 563 564 return flags; 565} 566 567static char * 568parse_sort_criteria(char *sortCriteria, int *error) 569{ 570 char *order = NULL; 571 char *item, *saveptr; 572 int i, ret, reverse, title_sorted = 0; 573 struct string_s str; 574 *error = 0; 575 576 if( force_sort_criteria ) 577 sortCriteria = strdup(force_sort_criteria); 578 if( !sortCriteria ) 579 return NULL; 580 581 if( (item = strtok_r(sortCriteria, ",", &saveptr)) ) 582 { 583 order = malloc(4096); 584 str.data = order; 585 str.size = 4096; 586 str.off = 0; 587 strcatf(&str, "order by "); 588 } 589 for( i = 0; item != NULL; i++ ) 590 { 591 reverse=0; 592 if( i ) 593 strcatf(&str, ", "); 594 if( *item == '+' ) 595 { 596 item++; 597 } 598 else if( *item == '-' ) 599 { 600 reverse = 1; 601 item++; 602 } 603 else 604 { 605 DPRINTF(E_ERROR, L_HTTP, "No order specified [%s]\n", item); 606 goto bad_direction; 607 } 608 if( strcasecmp(item, "upnp:class") == 0 ) 609 { 610 strcatf(&str, "o.CLASS"); 611 } 612 else if( strcasecmp(item, "dc:title") == 0 ) 613 { 614 strcatf(&str, "d.TITLE"); 615 title_sorted = 1; 616 } 617 else if( strcasecmp(item, "dc:date") == 0 ) 618 { 619 strcatf(&str, "d.DATE"); 620 } 621 else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 ) 622 { 623 strcatf(&str, "d.DISC, d.TRACK"); 624 } 625 else if( strcasecmp(item, "upnp:album") == 0 ) 626 { 627 strcatf(&str, "d.ALBUM"); 628 } 629 else 630 { 631 DPRINTF(E_ERROR, L_HTTP, "Unhandled SortCriteria [%s]\n", item); 632 bad_direction: 633 *error = -1; 634 if( i ) 635 { 636 ret = strlen(order); 637 order[ret-2] = '\0'; 638 } 639 i--; 640 goto unhandled_order; 641 } 642 643 if( reverse ) 644 strcatf(&str, " DESC"); 645 unhandled_order: 646 item = strtok_r(NULL, ",", &saveptr); 647 } 648 if( i <= 0 ) 649 { 650 free(order); 651 if( force_sort_criteria ) 652 free(sortCriteria); 653 return NULL; 654 } 655 /* Add a "tiebreaker" sort order */ 656 if( !title_sorted ) 657 strcatf(&str, ", TITLE ASC"); 658 659 if( force_sort_criteria ) 660 free(sortCriteria); 661 662 return order; 663} 664 665inline static void 666add_resized_res(int srcw, int srch, int reqw, int reqh, char *dlna_pn, 667 char *detailID, struct Response *args) 668{ 669 int dstw = reqw; 670 int dsth = reqh; 671 672 if( (args->flags & FLAG_NO_RESIZE) && reqw > 160 && reqh > 160 ) 673 return; 674 675 strcatf(args->str, "<res "); 676 if( args->filter & FILTER_RES_RESOLUTION ) 677 { 678 dstw = reqw; 679 dsth = ((((reqw<<10)/srcw)*srch)>>10); 680 if( dsth > reqh ) { 681 dsth = reqh; 682 dstw = (((reqh<<10)/srch) * srcw>>10); 683 } 684 strcatf(args->str, "resolution=\"%dx%d\" ", dstw, dsth); 685 } 686 strcatf(args->str, "protocolInfo=\"http-get:*:image/jpeg:" 687 "DLNA.ORG_PN=%s;DLNA.ORG_CI=1;DLNA.ORG_FLAGS=%08X%024X\">" 688 "http://%s:%d/Resized/%s.jpg?width=%d,height=%d" 689 "</res>", 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); 693} 694 695inline static void 696add_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) 699{ 700 strcatf(args->str, "<res "); 701 if( size && (args->filter & FILTER_RES_SIZE) ) { 702 strcatf(args->str, "size=\"%s\" ", size); 703 } 704 if( duration && (args->filter & FILTER_RES_DURATION) ) { 705 strcatf(args->str, "duration=\"%s\" ", duration); 706 } 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); 712 } 713 if( sampleFrequency && (args->filter & FILTER_RES_SAMPLEFREQUENCY) ) { 714 strcatf(args->str, "sampleFrequency=\"%s\" ", sampleFrequency); 715 } 716 if( nrAudioChannels && (args->filter & FILTER_RES_NRAUDIOCHANNELS) ) { 717 strcatf(args->str, "nrAudioChannels=\"%s\" ", nrAudioChannels); 718 } 719 if( resolution && (args->filter & FILTER_RES_RESOLUTION) ) { 720 strcatf(args->str, "resolution=\"%s\" ", resolution); 721 } 722 if( args->filter & FILTER_PV_SUBTITLE ) 723 { 724 if( args->flags & FLAG_HAS_CAPTIONS ) 725 { 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); 731 } 732 } 733 strcatf(args->str, "protocolInfo=\"http-get:*:%s:%s\">" 734 "http://%s:%d/MediaItems/%s.%s" 735 "</res>", 736 mime, dlna_pn, lan_addr[args->iface].str, 737 runtime_vars.port, detailID, ext); 738} 739 740static int 741get_child_count(const char *object, struct magic_container_s *magic) 742{ 743 int ret; 744 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); 751 752 return (ret > 0) ? ret : 0; 753} 754 755static int 756object_exists(const char *object) 757{ 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); 762} 763 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 769 770#define NON_ZERO(x) (x && atoi(x)) 771#define IS_ZERO(x) (!x || !atoi(x)) 772 773static int 774callback(void *args, int argc, char **argv, char **azColName) 775{ 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; 785 786 /* Make sure we have at least 8KB left of allocated memory to finish the response. */ 787 if( str->off > (str->size - 8192) ) 788 { 789#if MAX_RESPONSE_SIZE > 0 790 if( (str->size+DEFAULT_RESP_SIZE) <= MAX_RESPONSE_SIZE ) 791 { 792#endif 793 str->data = realloc(str->data, (str->size+DEFAULT_RESP_SIZE)); 794 if( str->data ) 795 { 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); 799 } 800 else 801 { 802 DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response was too big, and realloc failed!\n"); 803 return -1; 804 } 805#if MAX_RESPONSE_SIZE > 0 806 } 807 else 808 { 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; 811 } 812#endif 813 } 814 passed_args->returned++; 815 passed_args->flags &= ~RESPONSE_FLAGS; 816 817 if( strncmp(class, "item", 4) == 0 ) 818 { 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' ) 823 { 824 dlna_flags |= DLNA_FLAG_TM_S; 825 if( passed_args->flags & FLAG_MIME_AVI_DIVX ) 826 { 827 if( strcmp(mime, "video/x-msvideo") == 0 ) 828 { 829 if( creator ) 830 strcpy(mime+6, "divx"); 831 else 832 strcpy(mime+6, "avi"); 833 } 834 } 835 else if( passed_args->flags & FLAG_MIME_AVI_AVI ) 836 { 837 if( strcmp(mime, "video/x-msvideo") == 0 ) 838 { 839 strcpy(mime+6, "avi"); 840 } 841 } 842 else if( passed_args->client == EFreeBox && dlna_pn ) 843 { 844 if( strncmp(dlna_pn, "AVC_TS", 6) == 0 || 845 strncmp(dlna_pn, "MPEG_TS", 7) == 0 ) 846 { 847 strcpy(mime+6, "mp2t"); 848 } 849 } 850 if( !(passed_args->flags & FLAG_DLNA) ) 851 { 852 if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 ) 853 { 854 strcpy(mime+6, "mpeg"); 855 } 856 } 857 if( (passed_args->flags & FLAG_CAPTION_RES) || 858 (passed_args->filter & (FILTER_SEC_CAPTION_INFO_EX|FILTER_PV_SUBTITLE)) ) 859 { 860 if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%s'", detailID) > 0 ) 861 passed_args->flags |= FLAG_HAS_CAPTIONS; 862 } 863 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */ 864 if( passed_args->flags & FLAG_SAMSUNG ) 865 { 866 if( strcmp(mime+6, "x-matroska") == 0 ) 867 { 868 strcpy(mime+8, "mkv"); 869 } 870 } 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) ) 873 { 874 ret = asprintf(&alt_title, "%s.", title); 875 if( ret > 0 ) 876 title = alt_title; 877 else 878 alt_title = NULL; 879 } 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) ) 882 { 883 if( strlen(title) > 23 ) 884 title[23] = '\0'; 885 } 886 } 887 else if( *mime == 'a' ) 888 { 889 dlna_flags |= DLNA_FLAG_TM_S; 890 if( strcmp(mime+6, "x-flac") == 0 ) 891 { 892 if( passed_args->flags & FLAG_MIME_FLAC_FLAC ) 893 { 894 strcpy(mime+6, "flac"); 895 } 896 } 897 else if( strcmp(mime+6, "x-wav") == 0 ) 898 { 899 if( passed_args->flags & FLAG_MIME_WAV_WAV ) 900 { 901 strcpy(mime+6, "wav"); 902 } 903 } 904 } 905 else 906 dlna_flags |= DLNA_FLAG_TM_I; 907 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, "*"); 921 922 ret = strcatf(str, "<item id=\"%s\" parentID=\"%s\" restricted=\"1\"", id, parent); 923 if( refID && (passed_args->filter & FILTER_REFID) ) { 924 ret = strcatf(str, " refID=\"%s\"", refID); 925 } 926 ret = strcatf(str, ">" 927 "<dc:title>%s</dc:title>" 928 "<upnp:class>object.%s</upnp:class>", 929 title, class); 930 if( comment && (passed_args->filter & FILTER_DC_DESCRIPTION) ) { 931 ret = strcatf(str, "<dc:description>%.384s</dc:description>", comment); 932 } 933 if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) { 934 ret = strcatf(str, "<dc:creator>%s</dc:creator>", creator); 935 } 936 if( date && (passed_args->filter & FILTER_DC_DATE) ) { 937 ret = strcatf(str, "<dc:date>%s</dc:date>", date); 938 } 939 if( passed_args->filter & FILTER_SEC_DCM_INFO ) { 940 /* Get bookmark */ 941 ret = strcatf(str, "<sec:dcmInfo>CREATIONDATE=0,FOLDER=%s,BM=%d</sec:dcmInfo>", 942 title, sql_get_int_field(db, "SELECT SEC from BOOKMARKS where ID = '%s'", detailID)); 943 } 944 if( artist ) { 945 if( (*mime == 'v') && (passed_args->filter & FILTER_UPNP_ACTOR) ) { 946 ret = strcatf(str, "<upnp:actor>%s</upnp:actor>", artist); 947 } 948 if( passed_args->filter & FILTER_UPNP_ARTIST ) { 949 ret = strcatf(str, "<upnp:artist>%s</upnp:artist>", artist); 950 } 951 } 952 if( album && (passed_args->filter & FILTER_UPNP_ALBUM) ) { 953 ret = strcatf(str, "<upnp:album>%s</upnp:album>", album); 954 } 955 if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) { 956 ret = strcatf(str, "<upnp:genre>%s</upnp:genre>", genre); 957 } 958 if( strncmp(id, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) { 959 track = strrchr(id, '$')+1; 960 } 961 if( NON_ZERO(track) && (passed_args->filter & FILTER_UPNP_ORIGINALTRACKNUMBER) ) { 962 ret = strcatf(str, "<upnp:originalTrackNumber>%s</upnp:originalTrackNumber>", track); 963 } 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) ) 971 { 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); 978 } 979 if( !(passed_args->flags & FLAG_RESIZE_THUMBS) && NON_ZERO(tn) && IS_ZERO(rotate) ) { 980 ret = strcatf(str, "<res protocolInfo=\"http-get:*:%s:%s\">" 981 "http://%s:%d/Thumbnails/%s.jpg" 982 "</res>", 983 mime, "DLNA.ORG_PN=JPEG_TN;DLNA.ORG_CI=1", lan_addr[passed_args->iface].str, 984 runtime_vars.port, detailID); 985 } 986 else 987 add_resized_res(srcw, srch, 160, 160, "JPEG_TN", detailID, passed_args); 988 } 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)) 997 { 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); 1001 } 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) ) 1007 { 1008 if( strncmp(dlna_pn, "MPEG_TS_SD_NA", 13) != 0 ) 1009 { 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); 1013 } 1014 if( strncmp(dlna_pn, "MPEG_TS_SD_EU", 13) != 0 ) 1015 { 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); 1019 } 1020 } 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 ) 1027 { 1028 strcpy(mime+6, "avi"); 1029 if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_NTSC", 12) != 0 ) 1030 { 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); 1034 } 1035 if( !dlna_pn || strncmp(dlna_pn, "MPEG_PS_PAL", 11) != 0 ) 1036 { 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); 1040 } 1041 } 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)) 1050 { 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); 1054 } 1055 break; 1056 case ESamsungSeriesCDE: 1057 case ELGDevice: 1058 case EAsusOPlay: 1059 default: 1060 if( passed_args->flags & FLAG_HAS_CAPTIONS ) 1061 { 1062 if( passed_args->flags & FLAG_CAPTION_RES ) 1063 ret = strcatf(str, "<res protocolInfo=\"http-get:*:text/srt:*\">" 1064 "http://%s:%d/Captions/%s.srt" 1065 "</res>", 1066 lan_addr[passed_args->iface].str, runtime_vars.port, detailID); 1067 else if( passed_args->filter & FILTER_SEC_CAPTION_INFO_EX ) 1068 ret = strcatf(str, "<sec:CaptionInfoEx sec:type=\"srt\">" 1069 "http://%s:%d/Captions/%s.srt" 1070 "</sec:CaptionInfoEx>", 1071 lan_addr[passed_args->iface].str, runtime_vars.port, detailID); 1072 } 1073 free(alt_title); 1074 break; 1075 } 1076 } 1077 } 1078 if( NON_ZERO(album_art) ) 1079 { 1080 /* Video and audio album art is handled differently */ 1081 if( *mime == 'v' && (passed_args->filter & FILTER_RES) && !(passed_args->flags & FLAG_MS_PFS) ) { 1082 ret = strcatf(str, "<res protocolInfo=\"http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\">" 1083 "http://%s:%d/AlbumArt/%s-%s.jpg" 1084 "</res>", 1085 lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID); 1086 } else if( passed_args->filter & FILTER_UPNP_ALBUMARTURI ) { 1087 ret = strcatf(str, "<upnp:albumArtURI"); 1088 if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) { 1089 ret = strcatf(str, " dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\""); 1090 } 1091 ret = strcatf(str, ">http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>", 1092 lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID); 1093 } 1094 } 1095 if( (passed_args->flags & FLAG_MS_PFS) && *mime == 'i' ) { 1096 if( passed_args->client == EMediaRoom && !album ) 1097 ret = strcatf(str, "<upnp:album>%s</upnp:album>", "[No Keywords]"); 1098 1099 /* EVA2000 doesn't seem to handle embedded thumbnails */ 1100 if( !(passed_args->flags & FLAG_RESIZE_THUMBS) && NON_ZERO(tn) && IS_ZERO(rotate) ) { 1101 ret = strcatf(str, "<upnp:albumArtURI>" 1102 "http://%s:%d/Thumbnails/%s.jpg" 1103 "</upnp:albumArtURI>", 1104 lan_addr[passed_args->iface].str, runtime_vars.port, detailID); 1105 } else { 1106 ret = strcatf(str, "<upnp:albumArtURI>" 1107 "http://%s:%d/Resized/%s.jpg?width=160,height=160" 1108 "</upnp:albumArtURI>", 1109 lan_addr[passed_args->iface].str, runtime_vars.port, detailID); 1110 } 1111 } 1112 ret = strcatf(str, "</item>"); 1113 } 1114 else if( strncmp(class, "container", 9) == 0 ) 1115 { 1116 ret = strcatf(str, "<container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent); 1117 if( passed_args->filter & FILTER_SEARCHABLE ) { 1118 ret = strcatf(str, "searchable=\"%d\" ", check_magic_container(id, passed_args->flags) ? 0 : 1); 1119 } 1120 if( passed_args->filter & FILTER_CHILDCOUNT ) { 1121 ret = strcatf(str, "childCount=\"%d\"", get_child_count(id, check_magic_container(id, passed_args->flags))); 1122 } 1123 /* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */ 1124 if( passed_args->requested == 1 && strcmp(id, "0") == 0 && (passed_args->filter & FILTER_UPNP_SEARCHCLASS) ) { 1125 ret = strcatf(str, ">" 1126 "<upnp:searchClass includeDerived=\"1\">object.item.audioItem</upnp:searchClass>" 1127 "<upnp:searchClass includeDerived=\"1\">object.item.imageItem</upnp:searchClass>" 1128 "<upnp:searchClass includeDerived=\"1\">object.item.videoItem</upnp:searchClass"); 1129 } 1130 ret = strcatf(str, ">" 1131 "<dc:title>%s</dc:title>" 1132 "<upnp:class>object.%s</upnp:class>", 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, "<upnp:storageUsed>%s</upnp:storageUsed>", (size ? size : "-1")); 1137 } 1138 if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) { 1139 ret = strcatf(str, "<dc:creator>%s</dc:creator>", creator); 1140 } 1141 if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) { 1142 ret = strcatf(str, "<upnp:genre>%s</upnp:genre>", genre); 1143 } 1144 if( artist && (passed_args->filter & FILTER_UPNP_ARTIST) ) { 1145 ret = strcatf(str, "<upnp:artist>%s</upnp:artist>", artist); 1146 } 1147 if( NON_ZERO(album_art) && (passed_args->filter & FILTER_UPNP_ALBUMARTURI) ) { 1148 ret = strcatf(str, "<upnp:albumArtURI "); 1149 if( passed_args->filter & FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID ) { 1150 ret = strcatf(str, "dlna:profileID=\"JPEG_TN\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\""); 1151 } 1152 ret = strcatf(str, ">http://%s:%d/AlbumArt/%s-%s.jpg</upnp:albumArtURI>", 1153 lan_addr[passed_args->iface].str, runtime_vars.port, album_art, detailID); 1154 } 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, "<av:mediaClass xmlns:av=\"urn:schemas-sony-com:av\">" 1167 "%c</av:mediaClass>", class); 1168 } 1169 ret = strcatf(str, "</container>"); 1170 } 1171 1172 return 0; 1173} 1174 1175static void 1176BrowseContentDirectory(struct upnphttp * h, const char * action) 1177{ 1178 static const char resp0[] = 1179 "<u:BrowseResponse " 1180 "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">" 1181 "<Result>" 1182 "<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; 1201 1202 memset(&args, 0, sizeof(args)); 1203 memset(&str, 0, sizeof(str)); 1204 1205 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, 0); 1206 1207 ObjectID = GetValueFromNameValueList(&data, "ObjectID"); 1208 Filter = GetValueFromNameValueList(&data, "Filter"); 1209 BrowseFlag = GetValueFromNameValueList(&data, "BrowseFlag"); 1210 SortCriteria = GetValueFromNameValueList(&data, "SortCriteria"); 1211 1212 if( (ptr = GetValueFromNameValueList(&data, "RequestedCount")) ) 1213 RequestedCount = atoi(ptr); 1214 if( RequestedCount < 0 ) 1215 { 1216 SoapError(h, 402, "Invalid Args"); 1217 goto browse_error; 1218 } 1219 if( !RequestedCount ) 1220 RequestedCount = -1; 1221 if( (ptr = GetValueFromNameValueList(&data, "StartingIndex")) ) 1222 StartingIndex = atoi(ptr); 1223 if( StartingIndex < 0 ) 1224 { 1225 SoapError(h, 402, "Invalid Args"); 1226 goto browse_error; 1227 } 1228 if( !BrowseFlag || (strcmp(BrowseFlag, "BrowseDirectChildren") && strcmp(BrowseFlag, "BrowseMetadata")) ) 1229 { 1230 SoapError(h, 402, "Invalid Args"); 1231 goto browse_error; 1232 } 1233 if( !ObjectID && !(ObjectID = GetValueFromNameValueList(&data, "ContainerID")) ) 1234 { 1235 SoapError(h, 402, "Invalid Args"); 1236 goto browse_error; 1237 } 1238 1239 str.data = malloc(DEFAULT_RESP_SIZE); 1240 str.size = DEFAULT_RESP_SIZE; 1241 str.off = sprintf(str.data, "%s", resp0); 1242 /* See if we need to include DLNA namespace reference */ 1243 args.iface = h->iface; 1244 args.filter = set_filter_flags(Filter, h); 1245 if( args.filter & FILTER_DLNA_NAMESPACE ) 1246 ret = strcatf(&str, DLNA_NAMESPACE); 1247 if( args.filter & FILTER_PV_SUBTITLE ) 1248 ret = strcatf(&str, PV_NAMESPACE); 1249 strcatf(&str, ">\n"); 1250 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); 1265 1266 if( strcmp(BrowseFlag+6, "Metadata") == 0 ) 1267 { 1268 const char *id = ObjectID; 1269 args.requested = 1; 1270 magic = in_magic_container(ObjectID, args.flags, &id); 1271 if (magic) 1272 { 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; 1279 } 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; 1286 } 1287 else 1288 { 1289 magic = check_magic_container(ObjectID, args.flags); 1290 if (magic) 1291 { 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) 1305 { 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; 1311 } 1312 } 1313 if (!where[0]) 1314 sqlite3_snprintf(sizeof(where), where, "PARENT_ID = '%q'", ObjectID); 1315 1316 if (!totalMatches) 1317 totalMatches = get_child_count(ObjectID, magic); 1318 ret = 0; 1319 if (SortCriteria && !orderBy) 1320 { 1321 __SORT_LIMIT 1322 orderBy = parse_sort_criteria(SortCriteria, &ret); 1323 } 1324 else if (!orderBy) 1325 { 1326 if( strncmp(ObjectID, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) 1327 { 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"); 1332 } 1333 else if( args.flags & FLAG_FORCE_SORT ) 1334 { 1335 __SORT_LIMIT 1336 ret = xasprintf(&orderBy, "order by o.CLASS, d.DISC, d.TRACK, d.TITLE"); 1337 } 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 ) 1344 { 1345 free(orderBy); 1346 orderBy = NULL; 1347 ret = 0; 1348 } 1349 } 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)) ) 1352 { 1353 SoapError(h, 709, "Unsupported or invalid sort criteria"); 1354 goto browse_error; 1355 } 1356 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); 1364 } 1365 if( (ret != SQLITE_OK) && (zErrMsg != NULL) ) 1366 { 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; 1371 } 1372 sqlite3_free(sql); 1373 /* Does the object even exist? */ 1374 if( !totalMatches ) 1375 { 1376 if( !object_exists(ObjectID) ) 1377 { 1378 SoapError(h, 701, "No such object error"); 1379 goto browse_error; 1380 } 1381 } 1382 ret = strcatf(&str, "</DIDL-Lite></Result>\n" 1383 "<NumberReturned>%u</NumberReturned>\n" 1384 "<TotalMatches>%u</TotalMatches>\n" 1385 "<UpdateID>%u</UpdateID>" 1386 "</u:BrowseResponse>", 1387 args.returned, totalMatches, updateID); 1388 BuildSendAndCloseSoapResp(h, str.data, str.off); 1389browse_error: 1390 ClearNameValueList(&data); 1391 free(orderBy); 1392 free(str.data); 1393} 1394 1395static inline void 1396charcat(struct string_s *str, char c) 1397{ 1398 if (str->size <= str->off) 1399 { 1400 str->data[str->size-1] = '\0'; 1401 return; 1402 } 1403 str->data[str->off] = c; 1404 str->off += 1; 1405} 1406 1407static inline char * 1408parse_search_criteria(const char *str, char *sep) 1409{ 1410 struct string_s criteria; 1411 int len; 1412 int literal = 0, like = 0; 1413 const char *s; 1414 1415 if (!str) 1416 return strdup("1 = 1"); 1417 1418 len = strlen(str) + 32; 1419 criteria.data = malloc(len); 1420 criteria.size = len; 1421 criteria.off = 0; 1422 1423 s = str; 1424 1425 while (isspace(*s)) 1426 s++; 1427 1428 while (*s) 1429 { 1430 if (literal) 1431 { 1432 switch (*s) { 1433 case '&': 1434 if (strncmp(s, """, 6) == 0) 1435 s += 5; 1436 else if (strncmp(s, "'", 6) == 0) 1437 { 1438 strcatf(&criteria, "'"); 1439 s += 6; 1440 continue; 1441 } 1442 else 1443 break; 1444 case '"': 1445 literal = 0; 1446 if (like) 1447 { 1448 charcat(&criteria, '%'); 1449 like--; 1450 } 1451 charcat(&criteria, '"'); 1452 break; 1453 case '\\': 1454 if (strncmp(s, "\\"", 7) == 0) 1455 { 1456 strcatf(&criteria, "&quot;"); 1457 s += 7; 1458 continue; 1459 } 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"", 12) == 0) 1466 { 1467 s += 6; 1468 continue; 1469 } 1470 default: 1471 charcat(&criteria, *s); 1472 break; 1473 } 1474 } 1475 else 1476 { 1477 switch (*s) { 1478 case '\\': 1479 if (strncmp(s, "\\"", 7) == 0) 1480 { 1481 strcatf(&criteria, "&quot;"); 1482 s += 7; 1483 continue; 1484 } 1485 else 1486 charcat(&criteria, *s); 1487 break; 1488 case '"': 1489 literal = 1; 1490 charcat(&criteria, *s); 1491 if (like == 2) 1492 { 1493 charcat(&criteria, '%'); 1494 like--; 1495 } 1496 break; 1497 case '&': 1498 if (strncmp(s, """, 6) == 0) 1499 { 1500 literal = 1; 1501 strcatf(&criteria, "\""); 1502 if (like == 2) 1503 { 1504 charcat(&criteria, '%'); 1505 like--; 1506 } 1507 s += 5; 1508 } 1509 else if (strncmp(s, "'", 6) == 0) 1510 { 1511 strcatf(&criteria, "'"); 1512 s += 5; 1513 } 1514 else if (strncmp(s, "<", 4) == 0) 1515 { 1516 strcatf(&criteria, "<"); 1517 s += 3; 1518 } 1519 else if (strncmp(s, ">", 4) == 0) 1520 { 1521 strcatf(&criteria, ">"); 1522 s += 3; 1523 } 1524 else 1525 charcat(&criteria, *s); 1526 break; 1527 case '@': 1528 if (strncmp(s, "@refID", 6) == 0) 1529 { 1530 strcatf(&criteria, "REF_ID"); 1531 s += 6; 1532 continue; 1533 } 1534 else if (strncmp(s, "@id", 3) == 0) 1535 { 1536 strcatf(&criteria, "OBJECT_ID"); 1537 s += 3; 1538 continue; 1539 } 1540 else if (strncmp(s, "@parentID", 9) == 0) 1541 { 1542 strcatf(&criteria, "PARENT_ID"); 1543 s += 9; 1544 strcpy(sep, "*"); 1545 continue; 1546 } 1547 else 1548 charcat(&criteria, *s); 1549 break; 1550 case 'c': 1551 if (strncmp(s, "contains", 8) == 0) 1552 { 1553 strcatf(&criteria, "like"); 1554 s += 8; 1555 like = 2; 1556 continue; 1557 } 1558 else 1559 charcat(&criteria, *s); 1560 break; 1561 case 'd': 1562 if (strncmp(s, "derivedfrom", 11) == 0) 1563 { 1564 strcatf(&criteria, "like"); 1565 s += 11; 1566 like = 1; 1567 continue; 1568 } 1569 else if (strncmp(s, "dc:date", 7) == 0) 1570 { 1571 strcatf(&criteria, "d.DATE"); 1572 s += 7; 1573 continue; 1574 } 1575 else if (strncmp(s, "dc:title", 8) == 0) 1576 { 1577 strcatf(&criteria, "d.TITLE"); 1578 s += 8; 1579 continue; 1580 } 1581 else if (strncmp(s, "dc:creator", 10) == 0) 1582 { 1583 strcatf(&criteria, "d.CREATOR"); 1584 s += 10; 1585 continue; 1586 } 1587 else 1588 charcat(&criteria, *s); 1589 break; 1590 case 'e': 1591 if (strncmp(s, "exists", 6) == 0) 1592 { 1593 s += 6; 1594 while (isspace(*s)) 1595 s++; 1596 if (strncmp(s, "true", 4) == 0) 1597 { 1598 strcatf(&criteria, "is not NULL"); 1599 s += 3; 1600 } 1601 else if (strncmp(s, "false", 5) == 0) 1602 { 1603 strcatf(&criteria, "is NULL"); 1604 s += 4; 1605 } 1606 } 1607 else 1608 charcat(&criteria, *s); 1609 break; 1610 case 'u': 1611 if (strncmp(s, "upnp:class", 10) == 0) 1612 { 1613 strcatf(&criteria, "o.CLASS"); 1614 s += 10; 1615 continue; 1616 } 1617 else if (strncmp(s, "upnp:actor", 10) == 0) 1618 { 1619 strcatf(&criteria, "d.ARTIST"); 1620 s += 10; 1621 continue; 1622 } 1623 else if (strncmp(s, "upnp:artist", 11) == 0) 1624 { 1625 strcatf(&criteria, "d.ARTIST"); 1626 s += 11; 1627 continue; 1628 } 1629 else if (strncmp(s, "upnp:album", 10) == 0) 1630 { 1631 strcatf(&criteria, "d.ALBUM"); 1632 s += 10; 1633 continue; 1634 } 1635 else if (strncmp(s, "upnp:genre", 10) == 0) 1636 { 1637 strcatf(&criteria, "d.GENRE"); 1638 s += 10; 1639 continue; 1640 } 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; 1657 } 1658 } 1659 s++; 1660 } 1661 charcat(&criteria, '\0'); 1662 1663 return criteria.data; 1664} 1665 1666static void 1667SearchContentDirectory(struct upnphttp * h, const char * action) 1668{ 1669 static const char resp0[] = 1670 "<u:SearchResponse " 1671 "xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">" 1672 "<Result>" 1673 "<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; 1689 1690 memset(&args, 0, sizeof(args)); 1691 memset(&str, 0, sizeof(str)); 1692 1693 ParseNameValue(h->req_buf + h->req_contentoff, h->req_contentlen, &data, 0); 1694 1695 ContainerID = GetValueFromNameValueList(&data, "ContainerID"); 1696 Filter = GetValueFromNameValueList(&data, "Filter"); 1697 SearchCriteria = GetValueFromNameValueList(&data, "SearchCriteria"); 1698 SortCriteria = GetValueFromNameValueList(&data, "SortCriteria"); 1699 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 ) 1707 { 1708 if( !(ContainerID = GetValueFromNameValueList(&data, "ObjectID")) ) 1709 { 1710 SoapError(h, 402, "Invalid Args"); 1711 goto search_error; 1712 } 1713 } 1714 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 ) 1722 { 1723 ret = strcatf(&str, DLNA_NAMESPACE); 1724 } 1725 strcatf(&str, ">\n"); 1726 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); 1741 1742 magic = check_magic_container(ContainerID, args.flags); 1743 if (magic && magic->objectid && *(magic->objectid)) 1744 ContainerID = *(magic->objectid); 1745 1746 if( strcmp(ContainerID, "0") == 0 ) 1747 ContainerID = "*"; 1748 1749 if( strcmp(ContainerID, MUSIC_ALL_ID) == 0 || 1750 GETFLAG(DLNA_STRICT_MASK) ) 1751 groupBy[0] = '\0'; 1752 1753 where = parse_search_criteria(SearchCriteria, sep); 1754 DPRINTF(E_DEBUG, L_HTTP, "Translated SearchCriteria: %s\n", where); 1755 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 ) 1764 { 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; 1768 } 1769 /* Does the object even exist? */ 1770 if( !totalMatches ) 1771 { 1772 if( !object_exists(ContainerID) ) 1773 { 1774 SoapError(h, 710, "No such container"); 1775 goto search_error; 1776 } 1777 } 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)) ) 1783 { 1784 SoapError(h, 709, "Unsupported or invalid sort criteria"); 1785 goto search_error; 1786 } 1787 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) ) 1802 { 1803 DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql); 1804 sqlite3_free(zErrMsg); 1805 } 1806 sqlite3_free(sql); 1807 ret = strcatf(&str, "</DIDL-Lite></Result>\n" 1808 "<NumberReturned>%u</NumberReturned>\n" 1809 "<TotalMatches>%u</TotalMatches>\n" 1810 "<UpdateID>%u</UpdateID>" 1811 "</u:SearchResponse>", 1812 args.returned, totalMatches, updateID); 1813 BuildSendAndCloseSoapResp(h, str.data, str.off); 1814search_error: 1815 ClearNameValueList(&data); 1816 free(orderBy); 1817 free(where); 1818 free(str.data); 1819} 1820 1821/* 1822If a control point calls QueryStateVariable on a state variable that is not 1823buffered in memory within (or otherwise available from) the service, 1824the service must return a SOAP fault with an errorCode of 404 Invalid Var. 1825 1826QueryStateVariable remains useful as a limited test tool but may not be 1827part of some future versions of UPnP. 1828*/ 1829static void 1830QueryStateVariable(struct upnphttp * h, const char * action) 1831{ 1832 static const char resp[] = 1833 "<u:%sResponse " 1834 "xmlns:u=\"%s\">" 1835 "<return>%s</return>" 1836 "</u:%sResponse>"; 1837 1838 char body[512]; 1839 struct NameValueParserData data; 1840 const char * var_name; 1841 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"); 1846 1847 DPRINTF(E_INFO, L_HTTP, "QueryStateVariable(%.40s)\n", var_name); 1848 1849 if(!var_name) 1850 { 1851 SoapError(h, 402, "Invalid Args"); 1852 } 1853 else if(strcmp(var_name, "ConnectionStatus") == 0) 1854 { 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); 1860 } 1861 else 1862 { 1863 DPRINTF(E_WARN, L_HTTP, "%s: Unknown: %s\n", action, THISORNUL(var_name)); 1864 SoapError(h, 404, "Invalid Var"); 1865 } 1866 1867 ClearNameValueList(&data); 1868} 1869 1870static void 1871SamsungGetFeatureList(struct upnphttp * h, const char * action) 1872{ 1873 static const char resp[] = 1874 "<u:X_GetFeatureListResponse xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">" 1875 "<FeatureList>" 1876 "<Features xmlns=\"urn:schemas-upnp-org:av:avs\"" 1877 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" 1878 " xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\">" 1879 "<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">" 1880 "<container id=\"%s\" type=\"object.item.audioItem\"/>" 1881 "<container id=\"%s\" type=\"object.item.videoItem\"/>" 1882 "<container id=\"%s\" type=\"object.item.imageItem\"/>" 1883 "</Feature>" 1884 "</Features>" 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; 1891 1892 if (runtime_vars.root_container) 1893 { 1894 if (strcmp(runtime_vars.root_container, BROWSEDIR_ID) == 0) 1895 { 1896 audio = MUSIC_DIR_ID; 1897 video = VIDEO_DIR_ID; 1898 image = IMAGE_DIR_ID; 1899 } 1900 else 1901 { 1902 audio = runtime_vars.root_container; 1903 video = runtime_vars.root_container; 1904 image = runtime_vars.root_container; 1905 } 1906 } 1907 1908 len = snprintf(body, sizeof(body), resp, audio, video, image); 1909 1910 BuildSendAndCloseSoapResp(h, body, len); 1911} 1912 1913static void 1914SamsungSetBookmark(struct upnphttp * h, const char * action) 1915{ 1916 static const char resp[] = 1917 "<u:X_SetBookmarkResponse" 1918 " xmlns:u=\"urn:schemas-upnp-org:service:ContentDirectory:1\">" 1919 "</u:X_SetBookmarkResponse>"; 1920 1921 struct NameValueParserData data; 1922 char *ObjectID, *PosSecond; 1923 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 ) 1928 { 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); 1936 } 1937 else 1938 SoapError(h, 402, "Invalid Args"); 1939 1940 ClearNameValueList(&data); 1941} 1942 1943static const struct 1944{ 1945 const char * methodName; 1946 void (*methodImpl)(struct upnphttp *, const char *); 1947} 1948soapMethods[] = 1949{ 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 } 1965}; 1966 1967void 1968ExecuteSoapAction(struct upnphttp * h, const char * action, int n) 1969{ 1970 char * p; 1971 1972 p = strchr(action, '#'); 1973 if(p) 1974 { 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) 1987 { 1988 len = strlen(soapMethods[i].methodName); 1989 if(strncmp(p, soapMethods[i].methodName, len) == 0) 1990 { 1991 soapMethods[i].methodImpl(h, soapMethods[i].methodName); 1992 return; 1993 } 1994 i++; 1995 } 1996 1997 DPRINTF(E_WARN, L_HTTP, "SoapMethod: Unknown: %.*s\n", methodlen, p); 1998 } 1999 2000 SoapError(h, 401, "Invalid Action"); 2001} 2002 2003