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