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