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