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 <stdlib.h> 14#include <unistd.h> 15#include <stdio.h> 16#include <string.h> 17#include <sys/types.h> 18#include <sys/socket.h> 19#include <sys/param.h> 20#include <ctype.h> 21#include "config.h" 22#include "upnphttp.h" 23#include "upnpdescgen.h" 24#include "minidlnapath.h" 25#include "upnpsoap.h" 26#include "upnpevents.h" 27 28#include <sys/types.h> 29#include <sys/stat.h> 30#include <fcntl.h> 31#include <errno.h> 32#include <sys/sendfile.h> 33#include <arpa/inet.h> 34 35#include "upnpglobalvars.h" 36#include "utils.h" 37#include "getifaddr.h" 38#include "image_utils.h" 39#include "log.h" 40#include "sql.h" 41#include <libexif/exif-loader.h> 42#ifdef TIVO_SUPPORT 43#include "tivo_utils.h" 44#include "tivo_commands.h" 45#endif 46//#define MAX_BUFFER_SIZE 4194304 // 4MB -- Too much? 47#define MAX_BUFFER_SIZE 2147483647 // 2GB -- Too much? 48 49#include "icons.c" 50 51struct upnphttp * 52New_upnphttp(int s) 53{ 54 struct upnphttp * ret; 55 if(s<0) 56 return NULL; 57 ret = (struct upnphttp *)malloc(sizeof(struct upnphttp)); 58 if(ret == NULL) 59 return NULL; 60 memset(ret, 0, sizeof(struct upnphttp)); 61 ret->socket = s; 62 return ret; 63} 64 65void 66CloseSocket_upnphttp(struct upnphttp * h) 67{ 68 if(close(h->socket) < 0) 69 { 70 DPRINTF(E_ERROR, L_HTTP, "CloseSocket_upnphttp: close(%d): %s\n", h->socket, strerror(errno)); 71 } 72 h->socket = -1; 73 h->state = 100; 74} 75 76void 77Delete_upnphttp(struct upnphttp * h) 78{ 79 if(h) 80 { 81 if(h->socket >= 0) 82 CloseSocket_upnphttp(h); 83 if(h->req_buf) 84 free(h->req_buf); 85 if(h->res_buf) 86 free(h->res_buf); 87 free(h); 88 } 89} 90 91int 92SearchClientCache(struct in_addr addr) 93{ 94 int i; 95 for( i=0; i<CLIENT_CACHE_SLOTS; i++ ) 96 { 97 if( clients[i].addr.s_addr == addr.s_addr ) 98 { 99 /* Invalidate this client cache if it's older than 1 hour */ 100 if( (time(NULL) - clients[i].age) > 3600 ) 101 { 102 unsigned char mac[6]; 103 if( get_remote_mac(addr, mac) == 0 && 104 memcmp(mac, clients[i].mac, 6) == 0 ) 105 { 106 /* Same MAC as last time when we were able to identify the client, 107 * so extend the timeout by another hour. */ 108 clients[i].age = time(NULL); 109 } 110 else 111 { 112 memset(&clients[i], 0, sizeof(struct client_cache_s)); 113 return -1; 114 } 115 } 116 DPRINTF(E_DEBUG, L_HTTP, "Client found in cache. [type %d/entry %d]\n", clients[i].type, i); 117 return i; 118 } 119 } 120 return -1; 121} 122 123/* parse HttpHeaders of the REQUEST */ 124static void 125ParseHttpHeaders(struct upnphttp * h) 126{ 127 char * line; 128 char * colon; 129 char * p; 130 int n; 131 line = h->req_buf; 132 /* TODO : check if req_buf, contentoff are ok */ 133 while(line < (h->req_buf + h->req_contentoff)) 134 { 135 colon = strchr(line, ':'); 136 if(colon) 137 { 138 if(strncasecmp(line, "Content-Length", 14)==0) 139 { 140 p = colon; 141 while(*p < '0' || *p > '9') 142 p++; 143 h->req_contentlen = atoi(p); 144 } 145 else if(strncasecmp(line, "SOAPAction", 10)==0) 146 { 147 p = colon; 148 n = 0; 149 while(*p == ':' || *p == ' ' || *p == '\t') 150 p++; 151 while(p[n]>=' ') 152 { 153 n++; 154 } 155 if((p[0] == '"' && p[n-1] == '"') 156 || (p[0] == '\'' && p[n-1] == '\'')) 157 { 158 p++; n -= 2; 159 } 160 h->req_soapAction = p; 161 h->req_soapActionLen = n; 162 } 163 else if(strncasecmp(line, "Callback", 8)==0) 164 { 165 p = colon; 166 while(*p != '<' && *p != '\r' ) 167 p++; 168 n = 0; 169 while(p[n] != '>' && p[n] != '\r' ) 170 n++; 171 h->req_Callback = p + 1; 172 h->req_CallbackLen = MAX(0, n - 1); 173 } 174 else if(strncasecmp(line, "SID", 3)==0) 175 { 176 //zqiu: fix bug for test 4.0.5 177 //Skip extra headers like "SIDHEADER: xxxxxx xxx" 178 for(p=line+3;p<colon;p++) 179 { 180 if(!isspace(*p)) 181 { 182 p = NULL; //unexpected header 183 break; 184 } 185 } 186 if(p) { 187 p = colon + 1; 188 while(isspace(*p)) 189 p++; 190 n = 0; 191 while(!isspace(p[n])) 192 n++; 193 h->req_SID = p; 194 h->req_SIDLen = n; 195 } 196 } 197 /* Timeout: Seconds-nnnn */ 198/* TIMEOUT 199Recommended. Requested duration until subscription expires, 200either number of seconds or infinite. Recommendation 201by a UPnP Forum working committee. Defined by UPnP vendor. 202 Consists of the keyword "Second-" followed (without an 203intervening space) by either an integer or the keyword "infinite". */ 204 else if(strncasecmp(line, "Timeout", 7)==0) 205 { 206 p = colon + 1; 207 while(isspace(*p)) 208 p++; 209 if(strncasecmp(p, "Second-", 7)==0) { 210 h->req_Timeout = atoi(p+7); 211 } 212 } 213 // Range: bytes=xxx-yyy 214 else if(strncasecmp(line, "Range", 5)==0) 215 { 216 p = colon + 1; 217 while(isspace(*p)) 218 p++; 219 if(strncasecmp(p, "bytes=", 6)==0) { 220 h->reqflags |= FLAG_RANGE; 221 h->req_RangeEnd = atoll(index(p+6, '-')+1); 222 h->req_RangeStart = atoll(p+6); 223 DPRINTF(E_DEBUG, L_HTTP, "Range Start-End: %lld - %lld\n", 224 h->req_RangeStart, h->req_RangeEnd?h->req_RangeEnd:-1); 225 } 226 } 227 else if(strncasecmp(line, "Host", 4)==0) 228 { 229 h->reqflags |= FLAG_HOST; 230 } 231 else if(strncasecmp(line, "User-Agent", 10)==0) 232 { 233 /* Skip client detection if we already detected it. */ 234 if( h->req_client ) 235 goto next_header; 236 p = colon + 1; 237 while(isspace(*p)) 238 p++; 239 if(strncasecmp(p, "Xbox/", 5)==0) 240 { 241 h->req_client = EXbox; 242 h->reqflags |= FLAG_MIME_AVI_AVI; 243 } 244 else if(strncmp(p, "PLAYSTATION", 11)==0) 245 { 246 h->req_client = EPS3; 247 h->reqflags |= FLAG_DLNA; 248 h->reqflags |= FLAG_MIME_AVI_DIVX; 249 } 250 else if(strncmp(p, "SamsungWiselinkPro", 18)==0 || 251 strncmp(p, "SEC_HHP_", 8)==0) 252 { 253 h->req_client = ESamsungTV; 254 h->reqflags |= FLAG_DLNA; 255 h->reqflags |= FLAG_NO_RESIZE; 256 //h->reqflags |= FLAG_MIME_AVI_DIVX; 257 } 258 else if(strstrc(p, "bridgeCo-DMP/3", '\r')) 259 { 260 h->req_client = EDenonReceiver; 261 h->reqflags |= FLAG_DLNA; 262 } 263 else if(strstrc(p, "fbxupnpav/", '\r')) 264 { 265 h->req_client = EFreeBox; 266 } 267 else if(strncmp(p, "SMP8634", 7)==0) 268 { 269 h->req_client = EPopcornHour; 270 h->reqflags |= FLAG_MIME_FLAC_FLAC; 271 } 272 else if(strstrc(p, "DLNADOC/1.50", '\r')) 273 { 274 h->req_client = EStandardDLNA150; 275 h->reqflags |= FLAG_DLNA; 276 h->reqflags |= FLAG_MIME_AVI_AVI; 277 } 278 } 279 else if(strncasecmp(line, "X-AV-Client-Info", 16)==0) 280 { 281 /* Skip client detection if we already detected it. */ 282 if( h->req_client ) 283 goto next_header; 284 p = colon + 1; 285 while(isspace(*p)) 286 p++; 287 if(strstr(p, "PLAYSTATION 3")) 288 { 289 h->req_client = EPS3; 290 h->reqflags |= FLAG_DLNA; 291 h->reqflags |= FLAG_MIME_AVI_DIVX; 292 } 293 } 294 else if(strncasecmp(line, "Transfer-Encoding", 17)==0) 295 { 296 p = colon + 1; 297 while(isspace(*p)) 298 p++; 299 if(strncasecmp(p, "chunked", 7)==0) 300 { 301 h->reqflags |= FLAG_CHUNKED; 302 } 303 } 304 else if(strncasecmp(line, "getcontentFeatures.dlna.org", 27)==0) 305 { 306 p = colon + 1; 307 while(isspace(*p)) 308 p++; 309 if( (*p != '1') || !isspace(p[1]) ) 310 h->reqflags |= FLAG_INVALID_REQ; 311 } 312 else if(strncasecmp(line, "TimeSeekRange.dlna.org", 22)==0) 313 { 314 h->reqflags |= FLAG_TIMESEEK; 315 } 316 else if(strncasecmp(line, "PlaySpeed.dlna.org", 18)==0) 317 { 318 h->reqflags |= FLAG_PLAYSPEED; 319 } 320 else if(strncasecmp(line, "realTimeInfo.dlna.org", 21)==0) 321 { 322 h->reqflags |= FLAG_REALTIMEINFO; 323 } 324 else if(strncasecmp(line, "transferMode.dlna.org", 21)==0) 325 { 326 p = colon + 1; 327 while(isspace(*p)) 328 p++; 329 if(strncasecmp(p, "Streaming", 9)==0) 330 { 331 h->reqflags |= FLAG_XFERSTREAMING; 332 } 333 if(strncasecmp(p, "Interactive", 11)==0) 334 { 335 h->reqflags |= FLAG_XFERINTERACTIVE; 336 } 337 if(strncasecmp(p, "Background", 10)==0) 338 { 339 h->reqflags |= FLAG_XFERBACKGROUND; 340 } 341 } 342 else if(strncasecmp(line, "getCaptionInfo.sec", 18)==0) 343 { 344 h->reqflags |= FLAG_CAPTION; 345 } 346 } 347next_header: 348 while(!(line[0] == '\r' && line[1] == '\n')) 349 line++; 350 line += 2; 351 } 352 if( h->reqflags & FLAG_CHUNKED ) 353 { 354 char *endptr; 355 h->req_chunklen = -1; 356 if( h->req_buflen <= h->req_contentoff ) 357 return; 358 while( (line < (h->req_buf + h->req_buflen)) && 359 (h->req_chunklen = strtol(line, &endptr, 16)) && 360 (endptr != line) ) 361 { 362 while(!(endptr[0] == '\r' && endptr[1] == '\n')) 363 { 364 endptr++; 365 } 366 line = endptr+h->req_chunklen+2; 367 } 368 369 if( endptr == line ) 370 { 371 h->req_chunklen = -1; 372 return; 373 } 374 } 375 /* If the client type wasn't found, search the cache. 376 * This is done because a lot of clients like to send a 377 * different User-Agent with different types of requests. */ 378 n = SearchClientCache(h->clientaddr); 379 if( h->req_client ) 380 { 381 /* Add this client to the cache if it's not there already. */ 382 if( n < 0 ) 383 { 384 for( n=0; n<CLIENT_CACHE_SLOTS; n++ ) 385 { 386 if( clients[n].addr.s_addr ) 387 continue; 388 get_remote_mac(h->clientaddr, clients[n].mac); 389 clients[n].addr = h->clientaddr; 390 DPRINTF(E_DEBUG, L_HTTP, "Added client [%d/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n", 391 h->req_client, inet_ntoa(clients[n].addr), 392 clients[n].mac[0], clients[n].mac[1], clients[n].mac[2], 393 clients[n].mac[3], clients[n].mac[4], clients[n].mac[5], n); 394 break; 395 } 396 } 397 else if( (n < EStandardDLNA150) && (h->req_client == EStandardDLNA150) ) 398 { 399 /* If we know the client and our new detection is generic, use our cached info */ 400 h->reqflags |= clients[n].flags; 401 h->req_client = clients[n].type; 402 return; 403 } 404 clients[n].type = h->req_client; 405 clients[n].flags = h->reqflags & 0xFFF00000; 406 clients[n].age = time(NULL); 407 } 408 else if( n >= 0 ) 409 { 410 h->reqflags |= clients[n].flags; 411 h->req_client = clients[n].type; 412 } 413} 414 415/* very minimalistic 400 error message */ 416static void 417Send400(struct upnphttp * h) 418{ 419 static const char body400[] = 420 "<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>" 421 "<BODY><H1>Bad Request</H1>The request is invalid" 422 " for this HTTP version.</BODY></HTML>\r\n"; 423 h->respflags = FLAG_HTML; 424 BuildResp2_upnphttp(h, 400, "Bad Request", 425 body400, sizeof(body400) - 1); 426 SendResp_upnphttp(h); 427 CloseSocket_upnphttp(h); 428} 429 430/* very minimalistic 404 error message */ 431static void 432Send404(struct upnphttp * h) 433{ 434 static const char body404[] = 435 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>" 436 "<BODY><H1>Not Found</H1>The requested URL was not found" 437 " on this server.</BODY></HTML>\r\n"; 438 h->respflags = FLAG_HTML; 439 BuildResp2_upnphttp(h, 404, "Not Found", 440 body404, sizeof(body404) - 1); 441 SendResp_upnphttp(h); 442 CloseSocket_upnphttp(h); 443} 444 445/* very minimalistic 406 error message */ 446static void 447Send406(struct upnphttp * h) 448{ 449 static const char body406[] = 450 "<HTML><HEAD><TITLE>406 Not Acceptable</TITLE></HEAD>" 451 "<BODY><H1>Not Acceptable</H1>An unsupported operation" 452 " was requested.</BODY></HTML>\r\n"; 453 h->respflags = FLAG_HTML; 454 BuildResp2_upnphttp(h, 406, "Not Acceptable", 455 body406, sizeof(body406) - 1); 456 SendResp_upnphttp(h); 457 CloseSocket_upnphttp(h); 458} 459 460/* very minimalistic 416 error message */ 461static void 462Send416(struct upnphttp * h) 463{ 464 static const char body416[] = 465 "<HTML><HEAD><TITLE>416 Requested Range Not Satisfiable</TITLE></HEAD>" 466 "<BODY><H1>Requested Range Not Satisfiable</H1>The requested range" 467 " was outside the file's size.</BODY></HTML>\r\n"; 468 h->respflags = FLAG_HTML; 469 BuildResp2_upnphttp(h, 416, "Requested Range Not Satisfiable", 470 body416, sizeof(body416) - 1); 471 SendResp_upnphttp(h); 472 CloseSocket_upnphttp(h); 473} 474 475/* very minimalistic 500 error message */ 476static void 477Send500(struct upnphttp * h) 478{ 479 static const char body500[] = 480 "<HTML><HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>" 481 "<BODY><H1>Internal Server Error</H1>Server encountered " 482 "and Internal Error.</BODY></HTML>\r\n"; 483 h->respflags = FLAG_HTML; 484 BuildResp2_upnphttp(h, 500, "Internal Server Errror", 485 body500, sizeof(body500) - 1); 486 SendResp_upnphttp(h); 487 CloseSocket_upnphttp(h); 488} 489 490/* very minimalistic 501 error message */ 491void 492Send501(struct upnphttp * h) 493{ 494 static const char body501[] = 495 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>" 496 "<BODY><H1>Not Implemented</H1>The HTTP Method " 497 "is not implemented by this server.</BODY></HTML>\r\n"; 498 h->respflags = FLAG_HTML; 499 BuildResp2_upnphttp(h, 501, "Not Implemented", 500 body501, sizeof(body501) - 1); 501 SendResp_upnphttp(h); 502 CloseSocket_upnphttp(h); 503} 504 505static const char * 506findendheaders(const char * s, int len) 507{ 508 while(len-->0) 509 { 510 if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n') 511 return s; 512 s++; 513 } 514 return NULL; 515} 516 517/* Sends the description generated by the parameter */ 518static void 519sendXMLdesc(struct upnphttp * h, char * (f)(int *)) 520{ 521 char * desc; 522 int len; 523 desc = f(&len); 524 if(!desc) 525 { 526 DPRINTF(E_ERROR, L_HTTP, "Failed to generate XML description\n"); 527 Send500(h); 528 free(desc); 529 return; 530 } 531 else 532 { 533 BuildResp_upnphttp(h, desc, len); 534 } 535 SendResp_upnphttp(h); 536 CloseSocket_upnphttp(h); 537 free(desc); 538} 539 540/* ProcessHTTPPOST_upnphttp() 541 * executes the SOAP query if it is possible */ 542static void 543ProcessHTTPPOST_upnphttp(struct upnphttp * h) 544{ 545 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) 546 { 547 if(h->req_soapAction) 548 { 549 /* we can process the request */ 550 DPRINTF(E_DEBUG, L_HTTP, "SOAPAction: %.*s\n", h->req_soapActionLen, h->req_soapAction); 551 ExecuteSoapAction(h, 552 h->req_soapAction, 553 h->req_soapActionLen); 554 } 555 else 556 { 557 static const char err400str[] = 558 "<html><body>Bad request</body></html>"; 559 DPRINTF(E_WARN, L_HTTP, "No SOAPAction in HTTP headers"); 560 h->respflags = FLAG_HTML; 561 BuildResp2_upnphttp(h, 400, "Bad Request", 562 err400str, sizeof(err400str) - 1); 563 SendResp_upnphttp(h); 564 CloseSocket_upnphttp(h); 565 } 566 } 567 else 568 { 569 /* waiting for remaining data */ 570 h->state = 1; 571 } 572} 573 574static void 575ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path) 576{ 577 const char * sid; 578 DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPSubscribe %s\n", path); 579 DPRINTF(E_DEBUG, L_HTTP, "Callback '%.*s' Timeout=%d\n", 580 h->req_CallbackLen, h->req_Callback, h->req_Timeout); 581 DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID); 582 if(!h->req_Callback && !h->req_SID) { 583 /* Missing or invalid CALLBACK : 412 Precondition Failed. 584 * If CALLBACK header is missing or does not contain a valid HTTP URL, 585 * the publisher must respond with HTTP error 412 Precondition Failed*/ 586 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); 587 SendResp_upnphttp(h); 588 CloseSocket_upnphttp(h); 589 } else { 590 /* - add to the subscriber list 591 * - respond HTTP/x.x 200 OK 592 * - Send the initial event message */ 593/* Server:, SID:; Timeout: Second-(xx|infinite) */ 594 if(h->req_Callback) { 595 sid = upnpevents_addSubscriber(path, h->req_Callback, 596 h->req_CallbackLen, h->req_Timeout); 597 h->respflags = FLAG_TIMEOUT; 598 if(sid) { 599 DPRINTF(E_DEBUG, L_HTTP, "generated sid=%s\n", sid); 600 h->respflags |= FLAG_SID; 601 h->req_SID = sid; 602 h->req_SIDLen = strlen(sid); 603 } 604 BuildResp_upnphttp(h, 0, 0); 605 } else { 606 /* subscription renew */ 607 /* Invalid SID 608412 Precondition Failed. If a SID does not correspond to a known, 609un-expired subscription, the publisher must respond 610with HTTP error 412 Precondition Failed. */ 611 if(renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0) { 612 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); 613 } else { 614 /* A DLNA device must enforce a 5 minute timeout */ 615 h->respflags = FLAG_TIMEOUT; 616 h->req_Timeout = 300; 617 h->respflags |= FLAG_SID; 618 BuildResp_upnphttp(h, 0, 0); 619 } 620 } 621 SendResp_upnphttp(h); 622 CloseSocket_upnphttp(h); 623 } 624} 625 626static void 627ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path) 628{ 629 DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPUnSubscribe %s\n", path); 630 DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID); 631 /* Remove from the list */ 632 if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0) { 633 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0); 634 } else { 635 BuildResp_upnphttp(h, 0, 0); 636 } 637 SendResp_upnphttp(h); 638 CloseSocket_upnphttp(h); 639} 640 641/* Parse and process Http Query 642 * called once all the HTTP headers have been received. */ 643static void 644ProcessHttpQuery_upnphttp(struct upnphttp * h) 645{ 646 char HttpCommand[16]; 647 char HttpUrl[512]; 648 char * HttpVer; 649 char * p; 650 int i; 651 p = h->req_buf; 652 if(!p) 653 return; 654 for(i = 0; i<15 && *p != ' ' && *p != '\r'; i++) 655 HttpCommand[i] = *(p++); 656 HttpCommand[i] = '\0'; 657 while(*p==' ') 658 p++; 659 if(strncmp(p, "http://", 7) == 0) 660 { 661 p = p+7; 662 while(*p!='/') 663 p++; 664 } 665 for(i = 0; i<511 && *p != ' ' && *p != '\r'; i++) 666 HttpUrl[i] = *(p++); 667 HttpUrl[i] = '\0'; 668 while(*p==' ') 669 p++; 670 HttpVer = h->HttpVer; 671 for(i = 0; i<15 && *p != '\r'; i++) 672 HttpVer[i] = *(p++); 673 HttpVer[i] = '\0'; 674 /*DPRINTF(E_INFO, L_HTTP, "HTTP REQUEST : %s %s (%s)\n", 675 HttpCommand, HttpUrl, HttpVer);*/ 676 ParseHttpHeaders(h); 677 678 /* see if we need to wait for remaining data */ 679 if( (h->reqflags & FLAG_CHUNKED) ) 680 { 681 if( h->req_chunklen ) 682 { 683 h->state = 2; 684 return; 685 } 686 char *chunkstart, *chunk, *endptr, *endbuf; 687 chunk = endbuf = chunkstart = h->req_buf + h->req_contentoff; 688 689 while( (h->req_chunklen = strtol(chunk, &endptr, 16)) && (endptr != chunk) ) 690 { 691 while(!(endptr[0] == '\r' && endptr[1] == '\n')) 692 { 693 endptr++; 694 } 695 endptr += 2; 696 697 memmove(endbuf, endptr, h->req_chunklen); 698 699 endbuf += h->req_chunklen; 700 chunk = endptr + h->req_chunklen; 701 } 702 h->req_contentlen = endbuf - chunkstart; 703 h->req_buflen = endbuf - h->req_buf; 704 h->state = 100; 705 } 706 707 DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf); 708 if(strcmp("POST", HttpCommand) == 0) 709 { 710 h->req_command = EPost; 711 ProcessHTTPPOST_upnphttp(h); 712 } 713 else if((strcmp("GET", HttpCommand) == 0) || (strcmp("HEAD", HttpCommand) == 0)) 714 { 715 if( ((strcmp(h->HttpVer, "HTTP/1.1")==0) && !(h->reqflags & FLAG_HOST)) || (h->reqflags & FLAG_INVALID_REQ) ) 716 { 717 DPRINTF(E_WARN, L_HTTP, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n"); 718 Send400(h); 719 return; 720 } 721 #if 1 /* 7.3.33.4 */ 722 else if( ((h->reqflags & FLAG_TIMESEEK) || (h->reqflags & FLAG_PLAYSPEED)) && 723 !(h->reqflags & FLAG_RANGE) ) 724 { 725 DPRINTF(E_WARN, L_HTTP, "DLNA %s requested, responding ERROR 406\n", 726 h->reqflags&FLAG_TIMESEEK ? "TimeSeek" : "PlaySpeed"); 727 Send406(h); 728 return; 729 } 730 #endif 731 else if(strcmp("GET", HttpCommand) == 0) 732 { 733 h->req_command = EGet; 734 } 735 else 736 { 737 h->req_command = EHead; 738 } 739 if(strcmp(ROOTDESC_PATH, HttpUrl) == 0) 740 { 741 /* If it's a Xbox360, we might need a special friendly_name to be recognized */ 742 if( (h->req_client == EXbox) && !strchr(friendly_name, ':') ) 743 { 744 strncat(friendly_name, ": 1", FRIENDLYNAME_MAX_LEN-4); 745 sendXMLdesc(h, genRootDesc); 746 friendly_name[strlen(friendly_name)-3] = '\0'; 747 } 748 else 749 { 750 sendXMLdesc(h, genRootDesc); 751 } 752 } 753 else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0) 754 { 755 sendXMLdesc(h, genContentDirectory); 756 } 757 else if(strcmp(CONNECTIONMGR_PATH, HttpUrl) == 0) 758 { 759 sendXMLdesc(h, genConnectionManager); 760 } 761 else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH, HttpUrl) == 0) 762 { 763 sendXMLdesc(h, genX_MS_MediaReceiverRegistrar); 764 } 765 else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0) 766 { 767 SendResp_dlnafile(h, HttpUrl+12); 768 CloseSocket_upnphttp(h); 769 } 770 else if(strncmp(HttpUrl, "/Thumbnails/", 12) == 0) 771 { 772 SendResp_thumbnail(h, HttpUrl+12); 773 } 774 else if(strncmp(HttpUrl, "/AlbumArt/", 10) == 0) 775 { 776 SendResp_albumArt(h, HttpUrl+10); 777 CloseSocket_upnphttp(h); 778 } 779 #ifdef TIVO_SUPPORT 780 else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0) 781 { 782 if( GETFLAG(TIVO_MASK) ) 783 { 784 if( *(HttpUrl+12) == '?' ) 785 { 786 ProcessTiVoCommand(h, HttpUrl+13); 787 } 788 else 789 { 790 printf("Invalid TiVo request! %s\n", HttpUrl+12); 791 Send404(h); 792 } 793 } 794 else 795 { 796 printf("TiVo request with out TiVo support enabled! %s\n", HttpUrl+12); 797 Send404(h); 798 } 799 } 800 #endif 801 else if(strncmp(HttpUrl, "/Resized/", 9) == 0) 802 { 803 SendResp_resizedimg(h, HttpUrl+9); 804 CloseSocket_upnphttp(h); 805 } 806 else if(strncmp(HttpUrl, "/icons/", 7) == 0) 807 { 808 SendResp_icon(h, HttpUrl+7); 809 CloseSocket_upnphttp(h); 810 } 811 else if(strncmp(HttpUrl, "/Captions/", 10) == 0) 812 { 813 SendResp_caption(h, HttpUrl+10); 814 CloseSocket_upnphttp(h); 815 } 816 else 817 { 818 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", HttpUrl); 819 Send404(h); 820 } 821 } 822 else if(strcmp("SUBSCRIBE", HttpCommand) == 0) 823 { 824 h->req_command = ESubscribe; 825 ProcessHTTPSubscribe_upnphttp(h, HttpUrl); 826 } 827 else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0) 828 { 829 h->req_command = EUnSubscribe; 830 ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl); 831 } 832 else 833 { 834 DPRINTF(E_WARN, L_HTTP, "Unsupported HTTP Command %s\n", HttpCommand); 835 Send501(h); 836 } 837} 838 839 840void 841Process_upnphttp(struct upnphttp * h) 842{ 843 char buf[2048]; 844 int n; 845 if(!h) 846 return; 847 switch(h->state) 848 { 849 case 0: 850 n = recv(h->socket, buf, 2048, 0); 851 if(n<0) 852 { 853 DPRINTF(E_ERROR, L_HTTP, "recv (state0): %s\n", strerror(errno)); 854 h->state = 100; 855 } 856 else if(n==0) 857 { 858 DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n"); 859 h->state = 100; 860 } 861 else 862 { 863 const char * endheaders; 864 /* if 1st arg of realloc() is null, 865 * realloc behaves the same as malloc() */ 866 h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen + 1); 867 memcpy(h->req_buf + h->req_buflen, buf, n); 868 h->req_buflen += n; 869 h->req_buf[h->req_buflen] = '\0'; 870 /* search for the string "\r\n\r\n" */ 871 endheaders = findendheaders(h->req_buf, h->req_buflen); 872 if(endheaders) 873 { 874 h->req_contentoff = endheaders - h->req_buf + 4; 875 h->req_contentlen = h->req_buflen - h->req_contentoff; 876 ProcessHttpQuery_upnphttp(h); 877 } 878 } 879 break; 880 case 1: 881 case 2: 882 n = recv(h->socket, buf, 2048, 0); 883 if(n<0) 884 { 885 DPRINTF(E_ERROR, L_HTTP, "recv (state%d): %s\n", h->state, strerror(errno)); 886 h->state = 100; 887 } 888 else if(n==0) 889 { 890 DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n"); 891 h->state = 100; 892 } 893 else 894 { 895 /*fwrite(buf, 1, n, stdout);*/ /* debug */ 896 h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen); 897 memcpy(h->req_buf + h->req_buflen, buf, n); 898 h->req_buflen += n; 899 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen) 900 { 901 /* Need the struct to point to the realloc'd memory locations */ 902 if( h->state == 1 ) 903 { 904 ParseHttpHeaders(h); 905 ProcessHTTPPOST_upnphttp(h); 906 } 907 else if( h->state == 2 ) 908 { 909 ProcessHttpQuery_upnphttp(h); 910 } 911 } 912 } 913 break; 914 default: 915 DPRINTF(E_WARN, L_HTTP, "Unexpected state: %d\n", h->state); 916 } 917} 918 919static const char httpresphead[] = 920 "%s %d %s\r\n" 921 "Content-Type: %s\r\n" 922 "Connection: close\r\n" 923 "Content-Length: %d\r\n" 924 "Server: " MINIDLNA_SERVER_STRING "\r\n" 925// "Accept-Ranges: bytes\r\n" 926 ; /*"\r\n";*/ 927/* 928 "<?xml version=\"1.0\"?>\n" 929 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " 930 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" 931 "<s:Body>" 932 933 "</s:Body>" 934 "</s:Envelope>"; 935*/ 936/* with response code and response message 937 * also allocate enough memory */ 938 939void 940BuildHeader_upnphttp(struct upnphttp * h, int respcode, 941 const char * respmsg, 942 int bodylen) 943{ 944 int templen; 945 if(!h->res_buf) 946 { 947 templen = sizeof(httpresphead) + 192 + bodylen; 948 h->res_buf = (char *)malloc(templen); 949 h->res_buf_alloclen = templen; 950 } 951 h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen, 952 //httpresphead, h->HttpVer, 953 httpresphead, "HTTP/1.1", 954 respcode, respmsg, 955 (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"", 956 bodylen); 957 /* Additional headers */ 958 if(h->respflags & FLAG_TIMEOUT) { 959 h->res_buflen += snprintf(h->res_buf + h->res_buflen, 960 h->res_buf_alloclen - h->res_buflen, 961 "Timeout: Second-"); 962 if(h->req_Timeout) { 963 h->res_buflen += snprintf(h->res_buf + h->res_buflen, 964 h->res_buf_alloclen - h->res_buflen, 965 "%d\r\n", h->req_Timeout); 966 } else { 967 h->res_buflen += snprintf(h->res_buf + h->res_buflen, 968 h->res_buf_alloclen - h->res_buflen, 969 "300\r\n"); 970 //JM DLNA must force to 300 - "infinite\r\n"); 971 } 972 } 973 if(h->respflags & FLAG_SID) { 974 h->res_buflen += snprintf(h->res_buf + h->res_buflen, 975 h->res_buf_alloclen - h->res_buflen, 976 "SID: %.*s\r\n", h->req_SIDLen, h->req_SID); 977 } 978#if 0 // DLNA 979 char szTime[30]; 980 time_t curtime = time(NULL); 981 strftime(szTime, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); 982 h->res_buflen += snprintf(h->res_buf + h->res_buflen, 983 h->res_buf_alloclen - h->res_buflen, 984 "Date: %s\r\n", szTime); 985 h->res_buflen += snprintf(h->res_buf + h->res_buflen, 986 h->res_buf_alloclen - h->res_buflen, 987 "contentFeatures.dlna.org: \r\n"); 988 h->res_buflen += snprintf(h->res_buf + h->res_buflen, 989 h->res_buf_alloclen - h->res_buflen, 990 "EXT:\r\n"); 991#endif 992 h->res_buf[h->res_buflen++] = '\r'; 993 h->res_buf[h->res_buflen++] = '\n'; 994 if(h->res_buf_alloclen < (h->res_buflen + bodylen)) 995 { 996 h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen)); 997 h->res_buf_alloclen = h->res_buflen + bodylen; 998 } 999} 1000 1001void 1002BuildResp2_upnphttp(struct upnphttp * h, int respcode, 1003 const char * respmsg, 1004 const char * body, int bodylen) 1005{ 1006 BuildHeader_upnphttp(h, respcode, respmsg, bodylen); 1007 if( h->req_command == EHead ) 1008 return; 1009 if(body) 1010 memcpy(h->res_buf + h->res_buflen, body, bodylen); 1011 h->res_buflen += bodylen; 1012} 1013 1014/* responding 200 OK ! */ 1015void 1016BuildResp_upnphttp(struct upnphttp * h, 1017 const char * body, int bodylen) 1018{ 1019 BuildResp2_upnphttp(h, 200, "OK", body, bodylen); 1020} 1021 1022void 1023SendResp_upnphttp(struct upnphttp * h) 1024{ 1025 int n; 1026 DPRINTF(E_DEBUG, L_HTTP, "HTTP RESPONSE: %.*s\n", h->res_buflen, h->res_buf); 1027 n = send(h->socket, h->res_buf, h->res_buflen, 0); 1028 if(n<0) 1029 { 1030 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s", strerror(errno)); 1031 } 1032 else if(n < h->res_buflen) 1033 { 1034 /* TODO : handle correctly this case */ 1035 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n", 1036 n, h->res_buflen); 1037 } 1038} 1039 1040int 1041send_data(struct upnphttp * h, char * header, size_t size, int flags) 1042{ 1043 int n; 1044 1045 n = send(h->socket, header, size, flags); 1046 if(n<0) 1047 { 1048 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno)); 1049 } 1050 else if(n < h->res_buflen) 1051 { 1052 /* TODO : handle correctly this case */ 1053 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n", 1054 n, h->res_buflen); 1055 } 1056 else 1057 { 1058 return 0; 1059 } 1060 return 1; 1061} 1062 1063/* foxconn wklin modified start, 10/13/2010, changes for workaround for sendfile() */ 1064void 1065send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset) 1066{ 1067 off_t send_size; 1068 off_t ret = -1; 1069 off_t lseek_offset = offset; 1070 1071 1072 while( offset < end_offset ) 1073 { 1074 send_size = ( ((end_offset - offset) < MAX_BUFFER_SIZE) ? (end_offset - offset + 1) : MAX_BUFFER_SIZE); 1075 ret = sendfile(h->socket, sendfd, &offset, send_size); 1076 if( ret == -1 ) 1077 { 1078 /* foxconn wklin added start, 10/08/2010 */ 1079 /* filesystem has no sendfile() support (e.g. ntfs-3g with direct-io 1080 * enabled). Try slower way */ 1081#define MAX_READ 256*1024 1082 char buffer[MAX_READ]; 1083 char *p; 1084 off_t i, j, n; 1085 int err = 0; 1086 n = send_size; 1087 if (lseek_offset>=0) { /* first time coming here */ 1088 if (lseek_offset != offset) { 1089 /* this happens when sendfile support but fails 1090 * to send after several rounds */ 1091 lseek(sendfd, offset, SEEK_SET); 1092 } else { 1093 /* no sendfile support */ 1094 lseek(sendfd, lseek_offset, SEEK_SET); 1095 } 1096 /* mark this so we won't come here again */ 1097 lseek_offset = -1; 1098 } 1099 while (n > 0 && !err) { 1100 if ((i = read(sendfd, buffer, (n>MAX_READ)?MAX_READ:n)) <= 0) { 1101 /* either error (<0) or done (==0) */ 1102 if (i < 0) 1103 err = 1; 1104 else 1105 DPRINTF(E_DEBUG, L_HTTP, "i=0\n"); 1106 break; 1107 } 1108 n -= i; 1109 p = &buffer[0]; 1110 while (i > 0) { 1111 if ((j = send(h->socket, p, i, 0)) < 0) { 1112 /* something wrong */ 1113 err = 1; 1114 break; 1115 } 1116 i -= j; 1117 p += j; 1118 } 1119 } 1120 if (err) { 1121 DPRINTF(E_DEBUG, L_HTTP, "sendfile error :: error no. %d [%s]\n", errno, strerror(errno)); 1122 if( errno != EAGAIN ) 1123 break; 1124 } else { 1125 offset += send_size; 1126 } 1127 /* foxconn wklin added end, 10/08/2010 */ 1128 } 1129 /* 1130 else 1131 { 1132 fprintf(stdout, "ret != -1, sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset); 1133 DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset); 1134 } 1135 */ 1136 } 1137} 1138/* foxconn modified end, wklin, 10/13/2010 */ 1139 1140void 1141SendResp_icon(struct upnphttp * h, char * icon) 1142{ 1143 char * header; 1144 char * data; 1145 int size, ret; 1146 char mime[12]; 1147 char date[30]; 1148 time_t curtime = time(NULL); 1149 1150 if( strcmp(icon, "sm.png") == 0 ) 1151 { 1152 DPRINTF(E_DEBUG, L_HTTP, "Sending small PNG icon\n"); 1153 data = (char *)png_sm; 1154 size = sizeof(png_sm)-1; 1155 strcpy(mime, "image/png"); 1156 } 1157 else if( strcmp(icon, "lrg.png") == 0 ) 1158 { 1159 DPRINTF(E_DEBUG, L_HTTP, "Sending large PNG icon\n"); 1160 data = (char *)png_lrg; 1161 size = sizeof(png_lrg)-1; 1162 strcpy(mime, "image/png"); 1163 } 1164 else if( strcmp(icon, "sm.jpg") == 0 ) 1165 { 1166 DPRINTF(E_DEBUG, L_HTTP, "Sending small JPEG icon\n"); 1167 data = (char *)jpeg_sm; 1168 size = sizeof(jpeg_sm)-1; 1169 strcpy(mime, "image/jpeg"); 1170 } 1171 else if( strcmp(icon, "lrg.jpg") == 0 ) 1172 { 1173 DPRINTF(E_DEBUG, L_HTTP, "Sending large JPEG icon\n"); 1174 data = (char *)jpeg_lrg; 1175 size = sizeof(jpeg_lrg)-1; 1176 strcpy(mime, "image/jpeg"); 1177 } 1178 else 1179 { 1180 DPRINTF(E_WARN, L_HTTP, "Invalid icon request: %s\n", icon); 1181 Send404(h); 1182 return; 1183 } 1184 1185 1186 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); 1187 ret = asprintf(&header, "HTTP/1.1 200 OK\r\n" 1188 "Content-Type: %s\r\n" 1189 "Content-Length: %d\r\n" 1190 "Connection: close\r\n" 1191 "Date: %s\r\n" 1192 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n", 1193 mime, size, date); 1194 1195 if( (send_data(h, header, ret, MSG_MORE) == 0) && (h->req_command != EHead) ) 1196 { 1197 send_data(h, data, size, 0); 1198 } 1199 free(header); 1200} 1201 1202void 1203SendResp_albumArt(struct upnphttp * h, char * object) 1204{ 1205 char header[1500]; 1206 char sql_buf[256]; 1207 char **result; 1208 int rows = 0; 1209 char *path; 1210 char *dash; 1211 char date[30]; 1212 time_t curtime = time(NULL); 1213 off_t offset = 0, size; 1214 int sendfh; 1215 1216 memset(header, 0, 1500); 1217 1218 if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) 1219 { 1220 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); 1221 Send406(h); 1222 return; 1223 } 1224 1225 dash = strchr(object, '-'); 1226 if( dash ) 1227 *dash = '\0'; 1228 sprintf(sql_buf, "SELECT PATH from ALBUM_ART where ID = %s", object); 1229 sql_get_table(db, sql_buf, &result, &rows, NULL); 1230 if( !rows ) 1231 { 1232 DPRINTF(E_WARN, L_HTTP, "ALBUM_ART ID %s not found, responding ERROR 404\n", object); 1233 Send404(h); 1234 goto error; 1235 } 1236 path = result[1]; 1237 DPRINTF(E_INFO, L_HTTP, "Serving album art ID: %s [%s]\n", object, path); 1238 1239 if( access(path, F_OK) == 0 ) 1240 { 1241 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); 1242 1243 sendfh = open(path, O_RDONLY); 1244 if( sendfh < 0 ) { 1245 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); 1246 goto error; 1247 } 1248 size = lseek(sendfh, 0, SEEK_END); 1249 lseek(sendfh, 0, SEEK_SET); 1250 1251 sprintf(header, "HTTP/1.1 200 OK\r\n" 1252 "Content-Type: image/jpeg\r\n" 1253 "Content-Length: %jd\r\n" 1254 "Connection: close\r\n" 1255 "Date: %s\r\n" 1256 "EXT:\r\n" 1257 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" 1258 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n" 1259 "Server: " MINIDLNA_SERVER_STRING "\r\n", 1260 size, date); 1261 1262 if( h->reqflags & FLAG_XFERBACKGROUND ) 1263 { 1264 strcat(header, "transferMode.dlna.org: Background\r\n\r\n"); 1265 } 1266 else //if( h->reqflags & FLAG_XFERINTERACTIVE ) 1267 { 1268 strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n"); 1269 } 1270 1271 1272 if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) ) 1273 { 1274 send_file(h, sendfh, offset, size); 1275 } 1276 close(sendfh); 1277 } 1278 error: 1279 sqlite3_free_table(result); 1280} 1281 1282void 1283SendResp_caption(struct upnphttp * h, char * object) 1284{ 1285 char header[1500]; 1286 char sql_buf[256]; 1287 char **result; 1288 int rows = 0; 1289 char *path; 1290 char date[30]; 1291 time_t curtime = time(NULL); 1292 off_t offset = 0, size; 1293 int sendfh, ret; 1294 1295 memset(header, 0, 1500); 1296 1297 strip_ext(object); 1298 sprintf(sql_buf, "SELECT PATH from CAPTIONS where ID = %s", object); 1299 sql_get_table(db, sql_buf, &result, &rows, NULL); 1300 if( !rows ) 1301 { 1302 DPRINTF(E_WARN, L_HTTP, "CAPTION ID %s not found, responding ERROR 404\n", object); 1303 Send404(h); 1304 goto error; 1305 } 1306 path = result[1]; 1307 DPRINTF(E_INFO, L_HTTP, "Serving caption ID: %s [%s]\n", object, path); 1308 1309 if( access(path, F_OK) != 0 ) 1310 goto error; 1311 1312 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); 1313 sendfh = open(path, O_RDONLY); 1314 if( sendfh < 0 ) { 1315 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path); 1316 goto error; 1317 } 1318 size = lseek(sendfh, 0, SEEK_END); 1319 lseek(sendfh, 0, SEEK_SET); 1320 1321 ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" 1322 "Content-Type: smi/caption\r\n" 1323 "Content-Length: %jd\r\n" 1324 "Connection: close\r\n" 1325 "Date: %s\r\n" 1326 "EXT:\r\n" 1327 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n", 1328 size, date); 1329 1330 if( (send_data(h, header, ret, MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) ) 1331 { 1332 send_file(h, sendfh, offset, size); 1333 } 1334 close(sendfh); 1335 1336 error: 1337 sqlite3_free_table(result); 1338} 1339 1340void 1341SendResp_thumbnail(struct upnphttp * h, char * object) 1342{ 1343 char header[1500]; 1344 char sql_buf[256]; 1345 char **result; 1346 int rows = 0; 1347 char *path; 1348 char date[30]; 1349 time_t curtime = time(NULL); 1350 ExifData *ed; 1351 ExifLoader *l; 1352 1353 memset(header, 0, 1500); 1354 1355 if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) 1356 { 1357 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); 1358 Send406(h); 1359 return; 1360 } 1361 1362 strip_ext(object); 1363 sprintf(sql_buf, "SELECT PATH from DETAILS where ID = '%s'", object); 1364 sql_get_table(db, sql_buf, &result, &rows, NULL); 1365 if( !rows ) 1366 { 1367 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object); 1368 Send404(h); 1369 goto error; 1370 } 1371 path = result[1]; 1372 DPRINTF(E_INFO, L_HTTP, "Serving thumbnail for ObjectId: %s [%s]\n", object, path); 1373 1374 if( access(path, F_OK) == 0 ) 1375 { 1376 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); 1377 1378 l = exif_loader_new(); 1379 exif_loader_write_file(l, path); 1380 ed = exif_loader_get_data(l); 1381 exif_loader_unref(l); 1382 1383 if( !ed || !ed->size ) 1384 { 1385 Send404(h); 1386 if( ed ) 1387 exif_data_unref(ed); 1388 goto error; 1389 } 1390 sprintf(header, "HTTP/1.1 200 OK\r\n" 1391 "Content-Type: image/jpeg\r\n" 1392 "Content-Length: %d\r\n" 1393 "Connection: close\r\n" 1394 "Date: %s\r\n" 1395 "EXT:\r\n" 1396 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" 1397 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n" 1398 "Server: " MINIDLNA_SERVER_STRING "\r\n", 1399 ed->size, date); 1400 1401 if( h->reqflags & FLAG_XFERBACKGROUND ) 1402 { 1403 strcat(header, "transferMode.dlna.org: Background\r\n\r\n"); 1404 } 1405 else //if( h->reqflags & FLAG_XFERINTERACTIVE ) 1406 { 1407 strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n"); 1408 } 1409 1410 if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) ) 1411 { 1412 send_data(h, (char *)ed->data, ed->size, 0); 1413 } 1414 exif_data_unref(ed); 1415 } 1416 CloseSocket_upnphttp(h); 1417 error: 1418 sqlite3_free_table(result); 1419} 1420 1421void 1422SendResp_resizedimg(struct upnphttp * h, char * object) 1423{ 1424 char header[1500]; 1425 char str_buf[256]; 1426 char **result; 1427 char date[30]; 1428 char dlna_pn[4]; 1429 time_t curtime = time(NULL); 1430 int width=640, height=480, dstw, dsth, rotation, size; 1431 long srcw, srch; 1432 unsigned char * data = NULL; 1433 char *path, *file_path; 1434 char *resolution, *tn; 1435 char *key, *val; 1436 char *saveptr=NULL, *item=NULL; 1437 char *pixelshape=NULL; 1438 sqlite_int64 id; 1439 int rows=0, chunked=0, ret; 1440#ifdef __sparc__ 1441 ExifData *ed; 1442 ExifLoader *l; 1443#endif 1444 image *imsrc = NULL, *imdst = NULL; 1445 int scale = 1; 1446 1447 id = strtoll(object, NULL, 10); 1448 sprintf(str_buf, "SELECT PATH, RESOLUTION, THUMBNAIL from DETAILS where ID = '%lld'", id); 1449 ret = sql_get_table(db, str_buf, &result, &rows, NULL); 1450 if( (ret != SQLITE_OK) ) 1451 { 1452 DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id); 1453 Send500(h); 1454 return; 1455 } 1456 if( !rows || (access(result[3], F_OK) != 0) ) 1457 { 1458 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object); 1459 sqlite3_free_table(result); 1460 Send404(h); 1461 return; 1462 } 1463#if USE_FORK 1464 pid_t newpid = 0; 1465 newpid = fork(); 1466 if( newpid ) 1467 goto resized_error; 1468#endif 1469 file_path = result[3]; 1470 resolution = result[4]; 1471 tn = result[5]; 1472 srcw = strtol(resolution, &saveptr, 10); 1473 srch = strtol(saveptr+1, NULL, 10); 1474 1475 path = strdup(object); 1476 if( strtok_r(path, "?", &saveptr) ) 1477 { 1478 item = strtok_r(NULL, "&,", &saveptr); 1479 } 1480 while( item != NULL ) 1481 { 1482 #ifdef TIVO_SUPPORT 1483 decodeString(item, 1); 1484 #endif 1485 val = item; 1486 key = strsep(&val, "="); 1487 DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val); 1488 if( strcasecmp(key, "width") == 0 ) 1489 { 1490 width = atoi(val); 1491 } 1492 else if( strcasecmp(key, "height") == 0 ) 1493 { 1494 height = atoi(val); 1495 } 1496 else if( strcasecmp(key, "rotation") == 0 ) 1497 { 1498 rotation = atoi(val); 1499 } 1500 else if( strcasecmp(key, "pixelshape") == 0 ) 1501 { 1502 pixelshape = val; 1503 } 1504 item = strtok_r(NULL, "&,", &saveptr); 1505 } 1506 free(path); 1507 1508 if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE ) 1509 { 1510 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with a resized image!\n"); 1511 Send406(h); 1512 goto resized_error; 1513 } 1514 1515 DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %lld [%s]\n", id, file_path); 1516 1517 /* Figure out the best destination resolution we can use */ 1518 dstw = width; 1519 dsth = ((((width<<10)/srcw)*srch)>>10); 1520 if( dsth > height ) 1521 { 1522 dsth = height; 1523 dstw = (((height<<10)/srch) * srcw>>10); 1524 } 1525 1526 if( dstw <= 640 && dsth <= 480 ) 1527 strcpy(dlna_pn, "SM"); 1528 else if( dstw <= 1024 && dsth <= 768 ) 1529 strcpy(dlna_pn, "MED"); 1530 else 1531 strcpy(dlna_pn, "LRG"); 1532 1533 DPRINTF(E_WARN, L_HTTP, "\n====================\n"); 1534 DPRINTF(E_WARN, L_HTTP, " srcw:%d, srch:%d, dstw:%d,dsth:%d \n", srcw, srch, dstw, dsth); 1535 1536 1537 if( srcw>>3 >= dstw && srch>>3 >= dsth) 1538 scale = 8; 1539 else if( srcw>>2 >= dstw && srch>>2 >= dsth ) 1540 scale = 4; 1541 else if( srcw>>1 >= dstw && srch>>1 >= dsth ) 1542 scale = 2; 1543 1544 DPRINTF(E_WARN, L_HTTP, " scale:%d\n", scale); 1545 DPRINTF(E_WARN, L_HTTP, "====================\n"); 1546 1547 1548 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); 1549 snprintf(header, sizeof(header)-100, "HTTP/1.1 200 OK\r\n" 1550 "Content-Type: image/jpeg\r\n" 1551 "Connection: close\r\n" 1552 "Date: %s\r\n" 1553 "EXT:\r\n" 1554 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" 1555 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_%s;DLNA.ORG_CI=1\r\n" 1556 "Server: " MINIDLNA_SERVER_STRING "\r\n", 1557 date, dlna_pn); 1558 if( h->reqflags & FLAG_XFERINTERACTIVE ) 1559 { 1560 strcat(header, "transferMode.dlna.org: Interactive\r\n"); 1561 } 1562 else if( h->reqflags & FLAG_XFERBACKGROUND ) 1563 { 1564 strcat(header, "transferMode.dlna.org: Background\r\n"); 1565 } 1566 1567 /* Resizing from a thumbnail is much faster than from a large image */ 1568#ifdef __sparc__ 1569 if( dstw <= 160 && dsth <= 120 && atoi(tn) ) 1570 { 1571 l = exif_loader_new(); 1572 exif_loader_write_file(l, file_path); 1573 ed = exif_loader_get_data(l); 1574 exif_loader_unref(l); 1575 1576 if( !ed || !ed->size ) 1577 { 1578 if( ed ) 1579 exif_data_unref(ed); 1580 DPRINTF(E_WARN, L_HTTP, "Unable to access image thumbnail!\n"); 1581 Send500(h); 1582 goto resized_error; 1583 } 1584 imsrc = image_new_from_jpeg(NULL, 0, (char *)ed->data, ed->size, 1); 1585 exif_data_unref(ed); 1586 } 1587 else 1588#endif 1589 if( strcmp(h->HttpVer, "HTTP/1.0") == 0 ) 1590 { 1591 imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale); 1592 } 1593 else 1594 { 1595 chunked = 1; 1596 strcat(header, "Transfer-Encoding: chunked\r\n\r\n"); 1597 } 1598 1599 if( !chunked ) 1600 { 1601 if( !imsrc ) 1602 { 1603 DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path); 1604 Send500(h); 1605 goto resized_error; 1606 } 1607 1608 imdst = image_resize(imsrc, dstw, dsth); 1609 data = image_save_to_jpeg_buf(imdst, &size); 1610 1611 sprintf(str_buf, "Content-Length: %d\r\n\r\n", size); 1612 strcat(header, str_buf); 1613 } 1614 1615 if( (send_data(h, header, strlen(header), 0) == 0) && (h->req_command != EHead) ) 1616 { 1617 if( chunked ) 1618 { 1619 imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale); 1620 if( !imsrc ) 1621 { 1622 DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path); 1623 Send500(h); 1624 goto resized_error; 1625 } 1626 imdst = image_resize(imsrc, dstw, dsth); 1627 data = image_save_to_jpeg_buf(imdst, &size); 1628 1629 ret = sprintf(str_buf, "%x\r\n", size); 1630 send_data(h, str_buf, ret, MSG_MORE); 1631 send_data(h, (char *)data, size, MSG_MORE); 1632 send_data(h, "\r\n0\r\n\r\n", 7, 0); 1633 } 1634 else 1635 { 1636 send_data(h, (char *)data, size, 0); 1637 } 1638 } 1639 DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path); 1640 if( imsrc ) 1641 image_free(imsrc); 1642 if( imdst ) 1643 image_free(imdst); 1644 resized_error: 1645 sqlite3_free_table(result); 1646#if USE_FORK 1647 if( !newpid ) 1648 _exit(0); 1649#endif 1650} 1651 1652void 1653SendResp_dlnafile(struct upnphttp * h, char * object) 1654{ 1655 char header[1500]; 1656 char hdr_buf[512]; 1657 char sql_buf[256]; 1658 char **result; 1659 int rows, ret; 1660 char date[30]; 1661 time_t curtime = time(NULL); 1662 off_t total, offset, size; 1663 sqlite_int64 id; 1664 int sendfh; 1665 static struct { sqlite_int64 id; char path[PATH_MAX]; char mime[32]; char dlna[64]; } last_file = { 0 }; 1666#if USE_FORK 1667 pid_t newpid = 0; 1668#endif 1669 1670 id = strtoll(object, NULL, 10); 1671 if( id != last_file.id ) 1672 { 1673 sprintf(sql_buf, "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", id); 1674 ret = sql_get_table(db, sql_buf, &result, &rows, NULL); 1675 if( (ret != SQLITE_OK) ) 1676 { 1677 DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id); 1678 Send500(h); 1679 return; 1680 } 1681 if( !rows ) 1682 { 1683 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object); 1684 sqlite3_free_table(result); 1685 Send404(h); 1686 return; 1687 } 1688 /* Cache the result */ 1689 last_file.id = id; 1690 strncpy(last_file.path, result[3], sizeof(last_file.path)-1); 1691 if( result[4] ) 1692 { 1693 strncpy(last_file.mime, result[4], sizeof(last_file.mime)-1); 1694 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */ 1695 if( h->req_client == ESamsungTV ) 1696 { 1697 if( strcmp(last_file.mime+6, "x-matroska") == 0 ) 1698 strcpy(last_file.mime+8, "mkv"); 1699 } 1700 } 1701 else 1702 { 1703 last_file.mime[0] = '\0'; 1704 } 1705 if( result[5] ) 1706 snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s", result[5]); 1707 else if( h->reqflags & FLAG_DLNA ) 1708 strcpy(last_file.dlna, dlna_no_conv); 1709 else 1710 last_file.dlna[0] = '\0'; 1711 sqlite3_free_table(result); 1712 } 1713#if USE_FORK 1714 newpid = fork(); 1715 if( newpid ) 1716 return; 1717#if 0 1718 {/* Foxconn, add by MJ., 2010.12.28 */ 1719 extern int sudp, shttpl; 1720 extern int snotify[MAX_LAN_ADDR]; 1721 int i ; 1722 1723 if(sudp > 0) close(sudp); 1724 if(shttpl > 0) close(shttpl); 1725 for(i=0; i<MAX_LAN_ADDR; i++) 1726 if(snotify[i] > 0) 1727 close(snotify[i]); 1728 }/* Foxconn, end by MJ., 2010.12.28 */ 1729#endif 1730#endif 1731 1732 DPRINTF(E_INFO, L_HTTP, "Serving DetailID: %lld [%s]\n", id, last_file.path); 1733 1734 if( h->reqflags & FLAG_XFERSTREAMING ) 1735 { 1736 if( strncmp(last_file.mime, "image", 5) == 0 ) 1737 { 1738 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n"); 1739 Send406(h); 1740 goto error; 1741 } 1742 } 1743 else if( h->reqflags & FLAG_XFERINTERACTIVE ) 1744 { 1745 if( h->reqflags & FLAG_REALTIMEINFO ) 1746 { 1747 DPRINTF(E_WARN, L_HTTP, "Bad realTimeInfo flag with Interactive request!\n"); 1748 Send400(h); 1749 goto error; 1750 } 1751 if( strncmp(last_file.mime, "image", 5) != 0 ) 1752 { 1753 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Interactive without an image!\n"); 1754 /* Samsung TVs (well, at least the A950) do this for some reason, 1755 * and I don't see them fixing this bug any time soon. */ 1756 if( h->req_client != ESamsungTV || GETFLAG(DLNA_STRICT_MASK) ) 1757 { 1758 Send406(h); 1759 goto error; 1760 } 1761 } 1762 } 1763 1764 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime)); 1765 offset = h->req_RangeStart; 1766 sendfh = open(last_file.path, O_RDONLY); 1767 if( sendfh < 0 ) { 1768 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", last_file.path); 1769 goto error; 1770 } 1771 size = lseek(sendfh, 0, SEEK_END); 1772 lseek(sendfh, 0, SEEK_SET); 1773 1774 sprintf(header, "HTTP/1.1 20%c OK\r\n" 1775 "Content-Type: %s\r\n", (h->reqflags & FLAG_RANGE ? '6' : '0'), last_file.mime); 1776 if( h->reqflags & FLAG_RANGE ) 1777 { 1778 if( !h->req_RangeEnd ) 1779 h->req_RangeEnd = size; 1780 if( (h->req_RangeStart > h->req_RangeEnd) || (h->req_RangeStart < 0) ) 1781 { 1782 DPRINTF(E_WARN, L_HTTP, "Specified range was invalid!\n"); 1783 Send400(h); 1784 close(sendfh); 1785 goto error; 1786 } 1787 if( h->req_RangeEnd > size ) 1788 { 1789 DPRINTF(E_WARN, L_HTTP, "Specified range was outside file boundaries!\n"); 1790 Send416(h); 1791 close(sendfh); 1792 goto error; 1793 } 1794 1795 if( h->req_RangeEnd < size ) 1796 { 1797 total = h->req_RangeEnd - h->req_RangeStart + 1; 1798 sprintf(hdr_buf, "Content-Length: %jd\r\n" 1799 "Content-Range: bytes %jd-%jd/%jd\r\n", 1800 total, h->req_RangeStart, h->req_RangeEnd, size); 1801 } 1802 else 1803 { 1804 h->req_RangeEnd = size; 1805 total = size - h->req_RangeStart; 1806 sprintf(hdr_buf, "Content-Length: %jd\r\n" 1807 "Content-Range: bytes %jd-%jd/%jd\r\n", 1808 total, h->req_RangeStart, size-1, size); 1809 } 1810 } 1811 else 1812 { 1813 h->req_RangeEnd = size; 1814 total = size; 1815 sprintf(hdr_buf, "Content-Length: %jd\r\n", total); 1816 } 1817 strcat(header, hdr_buf); 1818 1819 if( h->reqflags & FLAG_XFERSTREAMING ) 1820 { 1821 strcat(header, "transferMode.dlna.org: Streaming\r\n"); 1822 } 1823 else if( h->reqflags & FLAG_XFERBACKGROUND ) 1824 { 1825 if( strncmp(last_file.mime, "image", 5) == 0 ) 1826 strcat(header, "transferMode.dlna.org: Background\r\n"); 1827 } 1828 else //if( h->reqflags & FLAG_XFERINTERACTIVE ) 1829 { 1830 if( (strncmp(last_file.mime, "video", 5) == 0) || 1831 (strncmp(last_file.mime, "audio", 5) == 0) ) 1832 { 1833 strcat(header, "transferMode.dlna.org: Streaming\r\n"); 1834 } 1835 else 1836 { 1837 strcat(header, "transferMode.dlna.org: Interactive\r\n"); 1838 } 1839 } 1840 1841 if( h->reqflags & FLAG_CAPTION ) 1842 { 1843 sprintf(sql_buf, "SELECT 1 from CAPTIONS where ID = '%lld'", id); 1844 ret = sql_get_table(db, sql_buf, &result, &rows, NULL); 1845 if( ret == SQLITE_OK ) 1846 { 1847 if( rows ) 1848 { 1849 sprintf(hdr_buf, "CaptionInfo.sec: http://%s:%d/Captions/%lld.srt\r\n", 1850 lan_addr[0].str, runtime_vars.port, id); 1851 strcat(header, hdr_buf); 1852 } 1853 sqlite3_free_table(result); 1854 } 1855 } 1856 1857 sprintf(hdr_buf, "Accept-Ranges: bytes\r\n" 1858 "Connection: close\r\n" 1859 "Date: %s\r\n" 1860 "EXT:\r\n" 1861 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n" 1862 "contentFeatures.dlna.org: %s\r\n" 1863 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n", 1864 date, last_file.dlna); 1865 strcat(header, hdr_buf); 1866 1867 if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) ) 1868 { 1869 send_file(h, sendfh, offset, h->req_RangeEnd); 1870 } 1871 close(sendfh); 1872 1873 error: 1874#if USE_FORK 1875 if( !newpid ) 1876 _exit(0); 1877#endif 1878 return; 1879} 1880