upgrade minidlna from 1.0.19 to 1.0.19.1
[tomato.git] / release / src / router / minidlna / upnphttp.c
blobf2000dae61ab54eb7fcf11b67e72ae03fe85fd45
1 /* MiniDLNA project
3 * http://sourceforge.net/projects/minidlna/
5 * MiniDLNA media server
6 * Copyright (C) 2008-2009 Justin Maggard
8 * This file is part of MiniDLNA.
10 * MiniDLNA is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2 as
12 * published by the Free Software Foundation.
14 * MiniDLNA is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
22 * Portions of the code from the MiniUPnP project:
24 * Copyright (c) 2006-2007, Thomas Bernard
25 * All rights reserved.
27 * Redistribution and use in source and binary forms, with or without
28 * modification, are permitted provided that the following conditions are met:
29 * * Redistributions of source code must retain the above copyright
30 * notice, this list of conditions and the following disclaimer.
31 * * Redistributions in binary form must reproduce the above copyright
32 * notice, this list of conditions and the following disclaimer in the
33 * documentation and/or other materials provided with the distribution.
34 * * The name of the author may not be used to endorse or promote products
35 * derived from this software without specific prior written permission.
37 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
38 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
39 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
40 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
41 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
43 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
44 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
45 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
46 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
47 * POSSIBILITY OF SUCH DAMAGE.
49 #include <stdlib.h>
50 #include <unistd.h>
51 #include <stdio.h>
52 #include <string.h>
53 #include <sys/types.h>
54 #include <sys/socket.h>
55 #include <sys/param.h>
56 #include <ctype.h>
57 #include "config.h"
58 #include "upnphttp.h"
59 #include "upnpdescgen.h"
60 #include "minidlnapath.h"
61 #include "upnpsoap.h"
62 #include "upnpevents.h"
64 #include <sys/types.h>
65 #include <sys/stat.h>
66 #include <fcntl.h>
67 #include <errno.h>
68 #include <sys/sendfile.h>
69 #include <arpa/inet.h>
71 #include "upnpglobalvars.h"
72 #include "utils.h"
73 #include "getifaddr.h"
74 #include "image_utils.h"
75 #include "log.h"
76 #include "sql.h"
77 #include <libexif/exif-loader.h>
78 #ifdef TIVO_SUPPORT
79 #include "tivo_utils.h"
80 #include "tivo_commands.h"
81 #endif
82 #define MAX_BUFFER_SIZE 4194304 // 4MB -- Too much?
83 //#define MAX_BUFFER_SIZE 2147483647 // 2GB -- Too much?
84 #define MIN_BUFFER_SIZE 65536
86 #include "icons.c"
88 struct upnphttp *
89 New_upnphttp(int s)
91 struct upnphttp * ret;
92 if(s<0)
93 return NULL;
94 ret = (struct upnphttp *)malloc(sizeof(struct upnphttp));
95 if(ret == NULL)
96 return NULL;
97 memset(ret, 0, sizeof(struct upnphttp));
98 ret->socket = s;
99 return ret;
102 void
103 CloseSocket_upnphttp(struct upnphttp * h)
105 if(close(h->socket) < 0)
107 DPRINTF(E_ERROR, L_HTTP, "CloseSocket_upnphttp: close(%d): %s\n", h->socket, strerror(errno));
109 h->socket = -1;
110 h->state = 100;
113 void
114 Delete_upnphttp(struct upnphttp * h)
116 if(h)
118 if(h->socket >= 0)
119 CloseSocket_upnphttp(h);
120 if(h->req_buf)
121 free(h->req_buf);
122 if(h->res_buf)
123 free(h->res_buf);
124 free(h);
129 SearchClientCache(struct in_addr addr, int quiet)
131 int i;
132 for( i=0; i<CLIENT_CACHE_SLOTS; i++ )
134 if( clients[i].addr.s_addr == addr.s_addr )
136 /* Invalidate this client cache if it's older than 1 hour */
137 if( (time(NULL) - clients[i].age) > 3600 )
139 unsigned char mac[6];
140 if( get_remote_mac(addr, mac) == 0 &&
141 memcmp(mac, clients[i].mac, 6) == 0 )
143 /* Same MAC as last time when we were able to identify the client,
144 * so extend the timeout by another hour. */
145 clients[i].age = time(NULL);
147 else
149 memset(&clients[i], 0, sizeof(struct client_cache_s));
150 return -1;
153 if( !quiet )
154 DPRINTF(E_DEBUG, L_HTTP, "Client found in cache. [type %d/entry %d]\n",
155 clients[i].type, i);
156 return i;
159 return -1;
162 /* parse HttpHeaders of the REQUEST */
163 static void
164 ParseHttpHeaders(struct upnphttp * h)
166 char * line;
167 char * colon;
168 char * p;
169 int n;
170 line = h->req_buf;
171 /* TODO : check if req_buf, contentoff are ok */
172 while(line < (h->req_buf + h->req_contentoff))
174 colon = strchr(line, ':');
175 if(colon)
177 if(strncasecmp(line, "Content-Length", 14)==0)
179 p = colon;
180 while(*p < '0' || *p > '9')
181 p++;
182 h->req_contentlen = atoi(p);
184 else if(strncasecmp(line, "SOAPAction", 10)==0)
186 p = colon;
187 n = 0;
188 while(*p == ':' || *p == ' ' || *p == '\t')
189 p++;
190 while(p[n]>=' ')
192 n++;
194 if((p[0] == '"' && p[n-1] == '"')
195 || (p[0] == '\'' && p[n-1] == '\''))
197 p++; n -= 2;
199 h->req_soapAction = p;
200 h->req_soapActionLen = n;
202 else if(strncasecmp(line, "Callback", 8)==0)
204 p = colon;
205 while(*p != '<' && *p != '\r' )
206 p++;
207 n = 0;
208 while(p[n] != '>' && p[n] != '\r' )
209 n++;
210 h->req_Callback = p + 1;
211 h->req_CallbackLen = MAX(0, n - 1);
213 else if(strncasecmp(line, "SID", 3)==0)
215 //zqiu: fix bug for test 4.0.5
216 //Skip extra headers like "SIDHEADER: xxxxxx xxx"
217 for(p=line+3;p<colon;p++)
219 if(!isspace(*p))
221 p = NULL; //unexpected header
222 break;
225 if(p) {
226 p = colon + 1;
227 while(isspace(*p))
228 p++;
229 n = 0;
230 while(!isspace(p[n]))
231 n++;
232 h->req_SID = p;
233 h->req_SIDLen = n;
236 /* Timeout: Seconds-nnnn */
237 /* TIMEOUT
238 Recommended. Requested duration until subscription expires,
239 either number of seconds or infinite. Recommendation
240 by a UPnP Forum working committee. Defined by UPnP vendor.
241 Consists of the keyword "Second-" followed (without an
242 intervening space) by either an integer or the keyword "infinite". */
243 else if(strncasecmp(line, "Timeout", 7)==0)
245 p = colon + 1;
246 while(isspace(*p))
247 p++;
248 if(strncasecmp(p, "Second-", 7)==0) {
249 h->req_Timeout = atoi(p+7);
252 // Range: bytes=xxx-yyy
253 else if(strncasecmp(line, "Range", 5)==0)
255 p = colon + 1;
256 while(isspace(*p))
257 p++;
258 if(strncasecmp(p, "bytes=", 6)==0) {
259 h->reqflags |= FLAG_RANGE;
260 h->req_RangeEnd = atoll(index(p+6, '-')+1);
261 h->req_RangeStart = atoll(p+6);
262 DPRINTF(E_DEBUG, L_HTTP, "Range Start-End: %lld - %lld\n",
263 h->req_RangeStart, h->req_RangeEnd?h->req_RangeEnd:-1);
266 else if(strncasecmp(line, "Host", 4)==0)
268 h->reqflags |= FLAG_HOST;
270 else if(strncasecmp(line, "User-Agent", 10)==0)
272 /* Skip client detection if we already detected it. */
273 if( h->req_client )
274 goto next_header;
275 p = colon + 1;
276 while(isspace(*p))
277 p++;
278 if(strncasecmp(p, "Xbox/", 5)==0)
280 h->req_client = EXbox;
281 h->reqflags |= FLAG_MIME_AVI_AVI;
282 h->reqflags |= FLAG_MS_PFS;
284 else if(strncmp(p, "PLAYSTATION", 11)==0)
286 h->req_client = EPS3;
287 h->reqflags |= FLAG_DLNA;
288 h->reqflags |= FLAG_MIME_AVI_DIVX;
290 else if(strncmp(p, "SamsungWiselinkPro", 18)==0 ||
291 strncmp(p, "SEC_HHP_", 8)==0)
293 h->req_client = ESamsungTV;
294 h->reqflags |= FLAG_DLNA;
295 h->reqflags |= FLAG_NO_RESIZE;
296 //h->reqflags |= FLAG_MIME_AVI_DIVX;
298 else if(strstrc(p, "bridgeCo-DMP/3", '\r'))
300 h->req_client = EDenonReceiver;
301 h->reqflags |= FLAG_DLNA;
303 else if(strstrc(p, "fbxupnpav/", '\r'))
305 h->req_client = EFreeBox;
307 else if(strncmp(p, "SMP8634", 7)==0)
309 h->req_client = EPopcornHour;
310 h->reqflags |= FLAG_MIME_FLAC_FLAC;
312 else if(strstrc(p, "Microsoft-IPTV-Client", '\r'))
314 h->req_client = EMediaRoom;
315 h->reqflags |= FLAG_MS_PFS;
317 else if(strstrc(p, "UPnP/1.0 DLNADOC/1.50 Intel_SDK_for_UPnP_devices/1.2", '\r'))
319 h->req_client = EToshibaTV;
320 h->reqflags |= FLAG_DLNA;
322 else if(strstrc(p, "DLNADOC/1.50", '\r'))
324 h->req_client = EStandardDLNA150;
325 h->reqflags |= FLAG_DLNA;
326 h->reqflags |= FLAG_MIME_AVI_AVI;
329 else if(strncasecmp(line, "X-AV-Client-Info", 16)==0)
331 /* Skip client detection if we already detected it. */
332 if( h->req_client && h->req_client < EStandardDLNA150 )
333 goto next_header;
334 p = colon + 1;
335 while(isspace(*p))
336 p++;
337 if(strstrc(p, "PLAYSTATION 3", '\r'))
339 h->req_client = EPS3;
340 h->reqflags |= FLAG_DLNA;
341 h->reqflags |= FLAG_MIME_AVI_DIVX;
343 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Blu-ray Disc Player"; mv="2.0" */
344 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BLU-RAY HOME THEATRE SYSTEM"; mv="2.0"; */
345 /* Sony SMP-100 needs the same treatment as their BDP-S370 */
346 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Media Player"; mv="2.0" */
347 else if(strstrc(p, "Blu-ray Disc Player", '\r') ||
348 strstrc(p, "BLU-RAY HOME THEATRE SYSTEM", '\r') ||
349 strstrc(p, "Media Player", '\r'))
351 h->req_client = ESonyBDP;
352 h->reqflags |= FLAG_DLNA;
354 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BRAVIA KDL-40EX503"; mv="1.7"; */
355 /* X-AV-Client-Info: av=5.0; hn=""; cn="Sony Corporation"; mn="INTERNET TV NSX-40GT 1"; mv="0.1"; */
356 else if(strstrc(p, "BRAVIA", '\r') ||
357 strstrc(p, "INTERNET TV", '\r'))
359 h->req_client = ESonyBravia;
360 h->reqflags |= FLAG_DLNA;
363 else if(strncasecmp(line, "Transfer-Encoding", 17)==0)
365 p = colon + 1;
366 while(isspace(*p))
367 p++;
368 if(strncasecmp(p, "chunked", 7)==0)
370 h->reqflags |= FLAG_CHUNKED;
373 else if(strncasecmp(line, "getcontentFeatures.dlna.org", 27)==0)
375 p = colon + 1;
376 while(isspace(*p))
377 p++;
378 if( (*p != '1') || !isspace(p[1]) )
379 h->reqflags |= FLAG_INVALID_REQ;
381 else if(strncasecmp(line, "TimeSeekRange.dlna.org", 22)==0)
383 h->reqflags |= FLAG_TIMESEEK;
385 else if(strncasecmp(line, "PlaySpeed.dlna.org", 18)==0)
387 h->reqflags |= FLAG_PLAYSPEED;
389 else if(strncasecmp(line, "realTimeInfo.dlna.org", 21)==0)
391 h->reqflags |= FLAG_REALTIMEINFO;
393 else if(strncasecmp(line, "transferMode.dlna.org", 21)==0)
395 p = colon + 1;
396 while(isspace(*p))
397 p++;
398 if(strncasecmp(p, "Streaming", 9)==0)
400 h->reqflags |= FLAG_XFERSTREAMING;
402 if(strncasecmp(p, "Interactive", 11)==0)
404 h->reqflags |= FLAG_XFERINTERACTIVE;
406 if(strncasecmp(p, "Background", 10)==0)
408 h->reqflags |= FLAG_XFERBACKGROUND;
411 else if(strncasecmp(line, "getCaptionInfo.sec", 18)==0)
413 h->reqflags |= FLAG_CAPTION;
416 next_header:
417 while(!(line[0] == '\r' && line[1] == '\n'))
418 line++;
419 line += 2;
421 if( h->reqflags & FLAG_CHUNKED )
423 char *endptr;
424 h->req_chunklen = -1;
425 if( h->req_buflen <= h->req_contentoff )
426 return;
427 while( (line < (h->req_buf + h->req_buflen)) &&
428 (h->req_chunklen = strtol(line, &endptr, 16)) &&
429 (endptr != line) )
431 while(!(endptr[0] == '\r' && endptr[1] == '\n'))
433 endptr++;
435 line = endptr+h->req_chunklen+2;
438 if( endptr == line )
440 h->req_chunklen = -1;
441 return;
444 /* If the client type wasn't found, search the cache.
445 * This is done because a lot of clients like to send a
446 * different User-Agent with different types of requests. */
447 n = SearchClientCache(h->clientaddr, 0);
448 if( h->req_client )
450 /* Add this client to the cache if it's not there already. */
451 if( n < 0 )
453 for( n=0; n<CLIENT_CACHE_SLOTS; n++ )
455 if( clients[n].addr.s_addr )
456 continue;
457 get_remote_mac(h->clientaddr, clients[n].mac);
458 clients[n].addr = h->clientaddr;
459 DPRINTF(E_DEBUG, L_HTTP, "Added client [%d/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n",
460 h->req_client, inet_ntoa(clients[n].addr),
461 clients[n].mac[0], clients[n].mac[1], clients[n].mac[2],
462 clients[n].mac[3], clients[n].mac[4], clients[n].mac[5], n);
463 break;
466 else if( (clients[n].type < EStandardDLNA150) && (h->req_client == EStandardDLNA150) )
468 /* If we know the client and our new detection is generic, use our cached info */
469 h->reqflags |= clients[n].flags;
470 h->req_client = clients[n].type;
471 return;
473 clients[n].type = h->req_client;
474 clients[n].flags = h->reqflags & 0xFFF00000;
475 clients[n].age = time(NULL);
477 else if( n >= 0 )
479 h->reqflags |= clients[n].flags;
480 h->req_client = clients[n].type;
484 /* very minimalistic 400 error message */
485 static void
486 Send400(struct upnphttp * h)
488 static const char body400[] =
489 "<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>"
490 "<BODY><H1>Bad Request</H1>The request is invalid"
491 " for this HTTP version.</BODY></HTML>\r\n";
492 h->respflags = FLAG_HTML;
493 BuildResp2_upnphttp(h, 400, "Bad Request",
494 body400, sizeof(body400) - 1);
495 SendResp_upnphttp(h);
496 CloseSocket_upnphttp(h);
499 /* very minimalistic 404 error message */
500 static void
501 Send404(struct upnphttp * h)
503 static const char body404[] =
504 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
505 "<BODY><H1>Not Found</H1>The requested URL was not found"
506 " on this server.</BODY></HTML>\r\n";
507 h->respflags = FLAG_HTML;
508 BuildResp2_upnphttp(h, 404, "Not Found",
509 body404, sizeof(body404) - 1);
510 SendResp_upnphttp(h);
511 CloseSocket_upnphttp(h);
514 /* very minimalistic 406 error message */
515 static void
516 Send406(struct upnphttp * h)
518 static const char body406[] =
519 "<HTML><HEAD><TITLE>406 Not Acceptable</TITLE></HEAD>"
520 "<BODY><H1>Not Acceptable</H1>An unsupported operation"
521 " was requested.</BODY></HTML>\r\n";
522 h->respflags = FLAG_HTML;
523 BuildResp2_upnphttp(h, 406, "Not Acceptable",
524 body406, sizeof(body406) - 1);
525 SendResp_upnphttp(h);
526 CloseSocket_upnphttp(h);
529 /* very minimalistic 416 error message */
530 static void
531 Send416(struct upnphttp * h)
533 static const char body416[] =
534 "<HTML><HEAD><TITLE>416 Requested Range Not Satisfiable</TITLE></HEAD>"
535 "<BODY><H1>Requested Range Not Satisfiable</H1>The requested range"
536 " was outside the file's size.</BODY></HTML>\r\n";
537 h->respflags = FLAG_HTML;
538 BuildResp2_upnphttp(h, 416, "Requested Range Not Satisfiable",
539 body416, sizeof(body416) - 1);
540 SendResp_upnphttp(h);
541 CloseSocket_upnphttp(h);
544 /* very minimalistic 500 error message */
545 static void
546 Send500(struct upnphttp * h)
548 static const char body500[] =
549 "<HTML><HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>"
550 "<BODY><H1>Internal Server Error</H1>Server encountered "
551 "and Internal Error.</BODY></HTML>\r\n";
552 h->respflags = FLAG_HTML;
553 BuildResp2_upnphttp(h, 500, "Internal Server Errror",
554 body500, sizeof(body500) - 1);
555 SendResp_upnphttp(h);
556 CloseSocket_upnphttp(h);
559 /* very minimalistic 501 error message */
560 void
561 Send501(struct upnphttp * h)
563 static const char body501[] =
564 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
565 "<BODY><H1>Not Implemented</H1>The HTTP Method "
566 "is not implemented by this server.</BODY></HTML>\r\n";
567 h->respflags = FLAG_HTML;
568 BuildResp2_upnphttp(h, 501, "Not Implemented",
569 body501, sizeof(body501) - 1);
570 SendResp_upnphttp(h);
571 CloseSocket_upnphttp(h);
574 static const char *
575 findendheaders(const char * s, int len)
577 while(len-- > 0)
579 if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n')
580 return s;
581 s++;
583 return NULL;
586 /* Sends the description generated by the parameter */
587 static void
588 sendXMLdesc(struct upnphttp * h, char * (f)(int *))
590 char * desc;
591 int len;
592 desc = f(&len);
593 if(!desc)
595 DPRINTF(E_ERROR, L_HTTP, "Failed to generate XML description\n");
596 Send500(h);
597 return;
599 BuildResp_upnphttp(h, desc, len);
600 SendResp_upnphttp(h);
601 CloseSocket_upnphttp(h);
602 free(desc);
605 /* ProcessHTTPPOST_upnphttp()
606 * executes the SOAP query if it is possible */
607 static void
608 ProcessHTTPPOST_upnphttp(struct upnphttp * h)
610 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
612 if(h->req_soapAction)
614 /* we can process the request */
615 DPRINTF(E_DEBUG, L_HTTP, "SOAPAction: %.*s\n", h->req_soapActionLen, h->req_soapAction);
616 ExecuteSoapAction(h,
617 h->req_soapAction,
618 h->req_soapActionLen);
620 else
622 static const char err400str[] =
623 "<html><body>Bad request</body></html>";
624 DPRINTF(E_WARN, L_HTTP, "No SOAPAction in HTTP headers\n");
625 h->respflags = FLAG_HTML;
626 BuildResp2_upnphttp(h, 400, "Bad Request",
627 err400str, sizeof(err400str) - 1);
628 SendResp_upnphttp(h);
629 CloseSocket_upnphttp(h);
632 else
634 /* waiting for remaining data */
635 h->state = 1;
639 static void
640 ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path)
642 const char * sid;
643 DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPSubscribe %s\n", path);
644 DPRINTF(E_DEBUG, L_HTTP, "Callback '%.*s' Timeout=%d\n",
645 h->req_CallbackLen, h->req_Callback, h->req_Timeout);
646 DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID);
647 if(!h->req_Callback && !h->req_SID) {
648 /* Missing or invalid CALLBACK : 412 Precondition Failed.
649 * If CALLBACK header is missing or does not contain a valid HTTP URL,
650 * the publisher must respond with HTTP error 412 Precondition Failed*/
651 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
652 SendResp_upnphttp(h);
653 CloseSocket_upnphttp(h);
654 } else {
655 /* - add to the subscriber list
656 * - respond HTTP/x.x 200 OK
657 * - Send the initial event message */
658 /* Server:, SID:; Timeout: Second-(xx|infinite) */
659 if(h->req_Callback) {
660 sid = upnpevents_addSubscriber(path, h->req_Callback,
661 h->req_CallbackLen, h->req_Timeout);
662 h->respflags = FLAG_TIMEOUT;
663 if(sid) {
664 DPRINTF(E_DEBUG, L_HTTP, "generated sid=%s\n", sid);
665 h->respflags |= FLAG_SID;
666 h->req_SID = sid;
667 h->req_SIDLen = strlen(sid);
669 BuildResp_upnphttp(h, 0, 0);
670 } else {
671 /* subscription renew */
672 /* Invalid SID
673 412 Precondition Failed. If a SID does not correspond to a known,
674 un-expired subscription, the publisher must respond
675 with HTTP error 412 Precondition Failed. */
676 if(renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0) {
677 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
678 } else {
679 /* A DLNA device must enforce a 5 minute timeout */
680 h->respflags = FLAG_TIMEOUT;
681 h->req_Timeout = 300;
682 h->respflags |= FLAG_SID;
683 BuildResp_upnphttp(h, 0, 0);
686 SendResp_upnphttp(h);
687 CloseSocket_upnphttp(h);
691 static void
692 ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path)
694 DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPUnSubscribe %s\n", path);
695 DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID);
696 /* Remove from the list */
697 if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0) {
698 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
699 } else {
700 BuildResp_upnphttp(h, 0, 0);
702 SendResp_upnphttp(h);
703 CloseSocket_upnphttp(h);
706 /* Parse and process Http Query
707 * called once all the HTTP headers have been received. */
708 static void
709 ProcessHttpQuery_upnphttp(struct upnphttp * h)
711 char HttpCommand[16];
712 char HttpUrl[512];
713 char * HttpVer;
714 char * p;
715 int i;
716 p = h->req_buf;
717 if(!p)
718 return;
719 for(i = 0; i<15 && *p != ' ' && *p != '\r'; i++)
720 HttpCommand[i] = *(p++);
721 HttpCommand[i] = '\0';
722 while(*p==' ')
723 p++;
724 if(strncmp(p, "http://", 7) == 0)
726 p = p+7;
727 while(*p!='/')
728 p++;
730 for(i = 0; i<511 && *p != ' ' && *p != '\r'; i++)
731 HttpUrl[i] = *(p++);
732 HttpUrl[i] = '\0';
733 while(*p==' ')
734 p++;
735 HttpVer = h->HttpVer;
736 for(i = 0; i<15 && *p != '\r'; i++)
737 HttpVer[i] = *(p++);
738 HttpVer[i] = '\0';
739 /*DPRINTF(E_INFO, L_HTTP, "HTTP REQUEST : %s %s (%s)\n",
740 HttpCommand, HttpUrl, HttpVer);*/
741 ParseHttpHeaders(h);
743 /* see if we need to wait for remaining data */
744 if( (h->reqflags & FLAG_CHUNKED) )
746 if( h->req_chunklen )
748 h->state = 2;
749 return;
751 char *chunkstart, *chunk, *endptr, *endbuf;
752 chunk = endbuf = chunkstart = h->req_buf + h->req_contentoff;
754 while( (h->req_chunklen = strtol(chunk, &endptr, 16)) && (endptr != chunk) )
756 while(!(endptr[0] == '\r' && endptr[1] == '\n'))
758 endptr++;
760 endptr += 2;
762 memmove(endbuf, endptr, h->req_chunklen);
764 endbuf += h->req_chunklen;
765 chunk = endptr + h->req_chunklen;
767 h->req_contentlen = endbuf - chunkstart;
768 h->req_buflen = endbuf - h->req_buf;
769 h->state = 100;
772 DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf);
773 if(strcmp("POST", HttpCommand) == 0)
775 h->req_command = EPost;
776 ProcessHTTPPOST_upnphttp(h);
778 else if((strcmp("GET", HttpCommand) == 0) || (strcmp("HEAD", HttpCommand) == 0))
780 if( ((strcmp(h->HttpVer, "HTTP/1.1")==0) && !(h->reqflags & FLAG_HOST)) || (h->reqflags & FLAG_INVALID_REQ) )
782 DPRINTF(E_WARN, L_HTTP, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n");
783 Send400(h);
784 return;
786 #if 1 /* 7.3.33.4 */
787 else if( ((h->reqflags & FLAG_TIMESEEK) || (h->reqflags & FLAG_PLAYSPEED)) &&
788 !(h->reqflags & FLAG_RANGE) )
790 DPRINTF(E_WARN, L_HTTP, "DLNA %s requested, responding ERROR 406\n",
791 h->reqflags&FLAG_TIMESEEK ? "TimeSeek" : "PlaySpeed");
792 Send406(h);
793 return;
795 #endif
796 else if(strcmp("GET", HttpCommand) == 0)
798 h->req_command = EGet;
800 else
802 h->req_command = EHead;
804 if(strcmp(ROOTDESC_PATH, HttpUrl) == 0)
806 /* If it's a Xbox360, we might need a special friendly_name to be recognized */
807 if( (h->req_client == EXbox) && !strchr(friendly_name, ':') )
809 i = strlen(friendly_name);
810 snprintf(friendly_name+i, FRIENDLYNAME_MAX_LEN-i, ": 1");
811 sendXMLdesc(h, genRootDesc);
812 friendly_name[i] = '\0';
814 else
816 sendXMLdesc(h, genRootDesc);
819 else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0)
821 sendXMLdesc(h, genContentDirectory);
823 else if(strcmp(CONNECTIONMGR_PATH, HttpUrl) == 0)
825 sendXMLdesc(h, genConnectionManager);
827 else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH, HttpUrl) == 0)
829 sendXMLdesc(h, genX_MS_MediaReceiverRegistrar);
831 else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0)
833 SendResp_dlnafile(h, HttpUrl+12);
834 CloseSocket_upnphttp(h);
836 else if(strncmp(HttpUrl, "/Thumbnails/", 12) == 0)
838 SendResp_thumbnail(h, HttpUrl+12);
840 else if(strncmp(HttpUrl, "/AlbumArt/", 10) == 0)
842 SendResp_albumArt(h, HttpUrl+10);
843 CloseSocket_upnphttp(h);
845 #ifdef TIVO_SUPPORT
846 else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0)
848 if( GETFLAG(TIVO_MASK) )
850 if( *(HttpUrl+12) == '?' )
852 ProcessTiVoCommand(h, HttpUrl+13);
854 else
856 printf("Invalid TiVo request! %s\n", HttpUrl+12);
857 Send404(h);
860 else
862 printf("TiVo request with out TiVo support enabled! %s\n", HttpUrl+12);
863 Send404(h);
866 #endif
867 else if(strncmp(HttpUrl, "/Resized/", 9) == 0)
869 SendResp_resizedimg(h, HttpUrl+9);
870 CloseSocket_upnphttp(h);
872 else if(strncmp(HttpUrl, "/icons/", 7) == 0)
874 SendResp_icon(h, HttpUrl+7);
875 CloseSocket_upnphttp(h);
877 else if(strncmp(HttpUrl, "/Captions/", 10) == 0)
879 SendResp_caption(h, HttpUrl+10);
880 CloseSocket_upnphttp(h);
882 else
884 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", HttpUrl);
885 Send404(h);
888 else if(strcmp("SUBSCRIBE", HttpCommand) == 0)
890 h->req_command = ESubscribe;
891 ProcessHTTPSubscribe_upnphttp(h, HttpUrl);
893 else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0)
895 h->req_command = EUnSubscribe;
896 ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl);
898 else
900 DPRINTF(E_WARN, L_HTTP, "Unsupported HTTP Command %s\n", HttpCommand);
901 Send501(h);
906 void
907 Process_upnphttp(struct upnphttp * h)
909 char buf[2048];
910 int n;
911 if(!h)
912 return;
913 switch(h->state)
915 case 0:
916 n = recv(h->socket, buf, 2048, 0);
917 if(n<0)
919 DPRINTF(E_ERROR, L_HTTP, "recv (state0): %s\n", strerror(errno));
920 h->state = 100;
922 else if(n==0)
924 DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n");
925 h->state = 100;
927 else
929 const char * endheaders;
930 /* if 1st arg of realloc() is null,
931 * realloc behaves the same as malloc() */
932 h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen + 1);
933 memcpy(h->req_buf + h->req_buflen, buf, n);
934 h->req_buflen += n;
935 h->req_buf[h->req_buflen] = '\0';
936 /* search for the string "\r\n\r\n" */
937 endheaders = findendheaders(h->req_buf, h->req_buflen);
938 if(endheaders)
940 h->req_contentoff = endheaders - h->req_buf + 4;
941 h->req_contentlen = h->req_buflen - h->req_contentoff;
942 ProcessHttpQuery_upnphttp(h);
945 break;
946 case 1:
947 case 2:
948 n = recv(h->socket, buf, 2048, 0);
949 if(n<0)
951 DPRINTF(E_ERROR, L_HTTP, "recv (state%d): %s\n", h->state, strerror(errno));
952 h->state = 100;
954 else if(n==0)
956 DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n");
957 h->state = 100;
959 else
961 /*fwrite(buf, 1, n, stdout);*/ /* debug */
962 h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen);
963 memcpy(h->req_buf + h->req_buflen, buf, n);
964 h->req_buflen += n;
965 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
967 /* Need the struct to point to the realloc'd memory locations */
968 if( h->state == 1 )
970 ParseHttpHeaders(h);
971 ProcessHTTPPOST_upnphttp(h);
973 else if( h->state == 2 )
975 ProcessHttpQuery_upnphttp(h);
979 break;
980 default:
981 DPRINTF(E_WARN, L_HTTP, "Unexpected state: %d\n", h->state);
985 static const char httpresphead[] =
986 "%s %d %s\r\n"
987 "Content-Type: %s\r\n"
988 "Connection: close\r\n"
989 "Content-Length: %d\r\n"
990 "Server: " MINIDLNA_SERVER_STRING "\r\n"
991 // "Accept-Ranges: bytes\r\n"
992 ; /*"\r\n";*/
994 "<?xml version=\"1.0\"?>\n"
995 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
996 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
997 "<s:Body>"
999 "</s:Body>"
1000 "</s:Envelope>";
1002 /* with response code and response message
1003 * also allocate enough memory */
1005 void
1006 BuildHeader_upnphttp(struct upnphttp * h, int respcode,
1007 const char * respmsg,
1008 int bodylen)
1010 int templen;
1011 if(!h->res_buf)
1013 templen = sizeof(httpresphead) + 192 + bodylen;
1014 h->res_buf = (char *)malloc(templen);
1015 h->res_buf_alloclen = templen;
1017 h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen,
1018 //httpresphead, h->HttpVer,
1019 httpresphead, "HTTP/1.1",
1020 respcode, respmsg,
1021 (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"",
1022 bodylen);
1023 /* Additional headers */
1024 if(h->respflags & FLAG_TIMEOUT) {
1025 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1026 h->res_buf_alloclen - h->res_buflen,
1027 "Timeout: Second-");
1028 if(h->req_Timeout) {
1029 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1030 h->res_buf_alloclen - h->res_buflen,
1031 "%d\r\n", h->req_Timeout);
1032 } else {
1033 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1034 h->res_buf_alloclen - h->res_buflen,
1035 "300\r\n");
1036 //JM DLNA must force to 300 - "infinite\r\n");
1039 if(h->respflags & FLAG_SID) {
1040 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1041 h->res_buf_alloclen - h->res_buflen,
1042 "SID: %.*s\r\n", h->req_SIDLen, h->req_SID);
1044 #if 0 // DLNA
1045 char szTime[30];
1046 time_t curtime = time(NULL);
1047 strftime(szTime, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1048 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1049 h->res_buf_alloclen - h->res_buflen,
1050 "Date: %s\r\n", szTime);
1051 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1052 h->res_buf_alloclen - h->res_buflen,
1053 "contentFeatures.dlna.org: \r\n");
1054 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1055 h->res_buf_alloclen - h->res_buflen,
1056 "EXT:\r\n");
1057 #endif
1058 h->res_buf[h->res_buflen++] = '\r';
1059 h->res_buf[h->res_buflen++] = '\n';
1060 if(h->res_buf_alloclen < (h->res_buflen + bodylen))
1062 h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen));
1063 h->res_buf_alloclen = h->res_buflen + bodylen;
1067 void
1068 BuildResp2_upnphttp(struct upnphttp * h, int respcode,
1069 const char * respmsg,
1070 const char * body, int bodylen)
1072 BuildHeader_upnphttp(h, respcode, respmsg, bodylen);
1073 if( h->req_command == EHead )
1074 return;
1075 if(body)
1076 memcpy(h->res_buf + h->res_buflen, body, bodylen);
1077 h->res_buflen += bodylen;
1080 /* responding 200 OK ! */
1081 void
1082 BuildResp_upnphttp(struct upnphttp * h,
1083 const char * body, int bodylen)
1085 BuildResp2_upnphttp(h, 200, "OK", body, bodylen);
1088 void
1089 SendResp_upnphttp(struct upnphttp * h)
1091 int n;
1092 DPRINTF(E_DEBUG, L_HTTP, "HTTP RESPONSE: %.*s\n", h->res_buflen, h->res_buf);
1093 n = send(h->socket, h->res_buf, h->res_buflen, 0);
1094 if(n<0)
1096 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno));
1098 else if(n < h->res_buflen)
1100 /* TODO : handle correctly this case */
1101 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
1102 n, h->res_buflen);
1107 send_data(struct upnphttp * h, char * header, size_t size, int flags)
1109 int n;
1111 n = send(h->socket, header, size, flags);
1112 if(n<0)
1114 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno));
1116 else if(n < h->res_buflen)
1118 /* TODO : handle correctly this case */
1119 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
1120 n, h->res_buflen);
1122 else
1124 return 0;
1126 return 1;
1129 void
1130 send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset)
1132 off_t send_size;
1133 off_t ret;
1134 char *buf = NULL;
1135 int try_sendfile = 1;
1137 while( offset < end_offset )
1139 if( try_sendfile )
1141 send_size = ( ((end_offset - offset) < MAX_BUFFER_SIZE) ? (end_offset - offset + 1) : MAX_BUFFER_SIZE);
1142 ret = sendfile(h->socket, sendfd, &offset, send_size);
1143 if( ret == -1 )
1145 DPRINTF(E_DEBUG, L_HTTP, "sendfile error :: error no. %d [%s]\n", errno, strerror(errno));
1146 /* If sendfile isn't supported on the filesystem, don't bother trying to use it again. */
1147 if( errno == EOVERFLOW || errno == EINVAL )
1148 try_sendfile = 0;
1149 else if( errno != EAGAIN )
1150 break;
1152 else
1154 //DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset);
1155 continue;
1158 /* Fall back to regular I/O */
1159 if( !buf )
1160 buf = malloc(MIN_BUFFER_SIZE);
1161 send_size = ( ((end_offset - offset) < MIN_BUFFER_SIZE) ? (end_offset - offset + 1) : MIN_BUFFER_SIZE);
1162 lseek(sendfd, offset, SEEK_SET);
1163 ret = read(sendfd, buf, send_size);
1164 if( ret == -1 ) {
1165 DPRINTF(E_DEBUG, L_HTTP, "read error :: error no. %d [%s]\n", errno, strerror(errno));
1166 if( errno != EAGAIN )
1167 break;
1169 ret = write(h->socket, buf, ret);
1170 if( ret == -1 ) {
1171 DPRINTF(E_DEBUG, L_HTTP, "write error :: error no. %d [%s]\n", errno, strerror(errno));
1172 if( errno != EAGAIN )
1173 break;
1175 offset+=ret;
1177 if( buf )
1178 free(buf);
1181 void
1182 SendResp_icon(struct upnphttp * h, char * icon)
1184 char * header;
1185 char * data;
1186 int size, ret;
1187 char mime[12];
1188 char date[30];
1189 time_t curtime = time(NULL);
1191 if( strcmp(icon, "sm.png") == 0 )
1193 DPRINTF(E_DEBUG, L_HTTP, "Sending small PNG icon\n");
1194 data = (char *)png_sm;
1195 size = sizeof(png_sm)-1;
1196 strcpy(mime, "image/png");
1198 else if( strcmp(icon, "lrg.png") == 0 )
1200 DPRINTF(E_DEBUG, L_HTTP, "Sending large PNG icon\n");
1201 data = (char *)png_lrg;
1202 size = sizeof(png_lrg)-1;
1203 strcpy(mime, "image/png");
1205 else if( strcmp(icon, "sm.jpg") == 0 )
1207 DPRINTF(E_DEBUG, L_HTTP, "Sending small JPEG icon\n");
1208 data = (char *)jpeg_sm;
1209 size = sizeof(jpeg_sm)-1;
1210 strcpy(mime, "image/jpeg");
1212 else if( strcmp(icon, "lrg.jpg") == 0 )
1214 DPRINTF(E_DEBUG, L_HTTP, "Sending large JPEG icon\n");
1215 data = (char *)jpeg_lrg;
1216 size = sizeof(jpeg_lrg)-1;
1217 strcpy(mime, "image/jpeg");
1219 else
1221 DPRINTF(E_WARN, L_HTTP, "Invalid icon request: %s\n", icon);
1222 Send404(h);
1223 return;
1227 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1228 ret = asprintf(&header, "HTTP/1.1 200 OK\r\n"
1229 "Content-Type: %s\r\n"
1230 "Content-Length: %d\r\n"
1231 "Connection: close\r\n"
1232 "Date: %s\r\n"
1233 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
1234 mime, size, date);
1236 if( (send_data(h, header, ret, MSG_MORE) == 0) && (h->req_command != EHead) )
1238 send_data(h, data, size, 0);
1240 free(header);
1243 void
1244 SendResp_albumArt(struct upnphttp * h, char * object)
1246 char header[1500];
1247 char sql_buf[256];
1248 char **result;
1249 int rows = 0;
1250 char *path;
1251 char *dash;
1252 char date[30];
1253 time_t curtime = time(NULL);
1254 off_t offset = 0, size;
1255 int sendfh;
1257 memset(header, 0, 1500);
1259 if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE )
1261 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1262 Send406(h);
1263 return;
1266 dash = strchr(object, '-');
1267 if( dash )
1268 *dash = '\0';
1269 sprintf(sql_buf, "SELECT PATH from ALBUM_ART where ID = %s", object);
1270 sql_get_table(db, sql_buf, &result, &rows, NULL);
1271 if( !rows )
1273 DPRINTF(E_WARN, L_HTTP, "ALBUM_ART ID %s not found, responding ERROR 404\n", object);
1274 Send404(h);
1275 goto error;
1277 path = result[1];
1278 DPRINTF(E_INFO, L_HTTP, "Serving album art ID: %s [%s]\n", object, path);
1280 if( access(path, F_OK) == 0 )
1282 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1284 sendfh = open(path, O_RDONLY);
1285 if( sendfh < 0 ) {
1286 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path);
1287 goto error;
1289 size = lseek(sendfh, 0, SEEK_END);
1290 lseek(sendfh, 0, SEEK_SET);
1292 sprintf(header, "HTTP/1.1 200 OK\r\n"
1293 "Content-Type: image/jpeg\r\n"
1294 "Content-Length: %jd\r\n"
1295 "Connection: close\r\n"
1296 "Date: %s\r\n"
1297 "EXT:\r\n"
1298 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1299 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
1300 "Server: " MINIDLNA_SERVER_STRING "\r\n",
1301 size, date);
1303 if( h->reqflags & FLAG_XFERBACKGROUND )
1305 strcat(header, "transferMode.dlna.org: Background\r\n\r\n");
1307 else //if( h->reqflags & FLAG_XFERINTERACTIVE )
1309 strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n");
1313 if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) )
1315 send_file(h, sendfh, offset, size);
1317 close(sendfh);
1319 error:
1320 sqlite3_free_table(result);
1323 void
1324 SendResp_caption(struct upnphttp * h, char * object)
1326 char header[1500];
1327 char sql_buf[256];
1328 char **result;
1329 int rows = 0;
1330 char *path;
1331 char date[30];
1332 time_t curtime = time(NULL);
1333 off_t offset = 0, size;
1334 int sendfh, ret;
1336 memset(header, 0, 1500);
1338 strip_ext(object);
1339 sprintf(sql_buf, "SELECT PATH from CAPTIONS where ID = %s", object);
1340 sql_get_table(db, sql_buf, &result, &rows, NULL);
1341 if( !rows )
1343 DPRINTF(E_WARN, L_HTTP, "CAPTION ID %s not found, responding ERROR 404\n", object);
1344 Send404(h);
1345 goto error;
1347 path = result[1];
1348 DPRINTF(E_INFO, L_HTTP, "Serving caption ID: %s [%s]\n", object, path);
1350 if( access(path, F_OK) != 0 )
1351 goto error;
1353 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1354 sendfh = open(path, O_RDONLY);
1355 if( sendfh < 0 ) {
1356 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path);
1357 goto error;
1359 size = lseek(sendfh, 0, SEEK_END);
1360 lseek(sendfh, 0, SEEK_SET);
1362 ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n"
1363 "Content-Type: smi/caption\r\n"
1364 "Content-Length: %jd\r\n"
1365 "Connection: close\r\n"
1366 "Date: %s\r\n"
1367 "EXT:\r\n"
1368 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
1369 size, date);
1371 if( (send_data(h, header, ret, MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) )
1373 send_file(h, sendfh, offset, size);
1375 close(sendfh);
1377 error:
1378 sqlite3_free_table(result);
1381 void
1382 SendResp_thumbnail(struct upnphttp * h, char * object)
1384 char header[1500];
1385 char sql_buf[256];
1386 char **result;
1387 int rows = 0;
1388 char *path;
1389 char date[30];
1390 time_t curtime = time(NULL);
1391 ExifData *ed;
1392 ExifLoader *l;
1394 memset(header, 0, 1500);
1396 if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE )
1398 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1399 Send406(h);
1400 return;
1403 strip_ext(object);
1404 sprintf(sql_buf, "SELECT PATH from DETAILS where ID = '%s'", object);
1405 sql_get_table(db, sql_buf, &result, &rows, NULL);
1406 if( !rows )
1408 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
1409 Send404(h);
1410 goto error;
1412 path = result[1];
1413 DPRINTF(E_INFO, L_HTTP, "Serving thumbnail for ObjectId: %s [%s]\n", object, path);
1415 if( access(path, F_OK) == 0 )
1417 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1419 l = exif_loader_new();
1420 exif_loader_write_file(l, path);
1421 ed = exif_loader_get_data(l);
1422 exif_loader_unref(l);
1424 if( !ed || !ed->size )
1426 Send404(h);
1427 if( ed )
1428 exif_data_unref(ed);
1429 goto error;
1431 sprintf(header, "HTTP/1.1 200 OK\r\n"
1432 "Content-Type: image/jpeg\r\n"
1433 "Content-Length: %d\r\n"
1434 "Connection: close\r\n"
1435 "Date: %s\r\n"
1436 "EXT:\r\n"
1437 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1438 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
1439 "Server: " MINIDLNA_SERVER_STRING "\r\n",
1440 ed->size, date);
1442 if( h->reqflags & FLAG_XFERBACKGROUND )
1444 strcat(header, "transferMode.dlna.org: Background\r\n\r\n");
1446 else //if( h->reqflags & FLAG_XFERINTERACTIVE )
1448 strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n");
1451 if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) )
1453 send_data(h, (char *)ed->data, ed->size, 0);
1455 exif_data_unref(ed);
1457 CloseSocket_upnphttp(h);
1458 error:
1459 sqlite3_free_table(result);
1462 void
1463 SendResp_resizedimg(struct upnphttp * h, char * object)
1465 char header[1500];
1466 char str_buf[256];
1467 char **result;
1468 char date[30];
1469 char dlna_pn[4];
1470 time_t curtime = time(NULL);
1471 int width=640, height=480, dstw, dsth, rotation, size;
1472 long srcw, srch;
1473 unsigned char * data = NULL;
1474 char *path, *file_path;
1475 char *resolution, *tn;
1476 char *key, *val;
1477 char *saveptr=NULL, *item=NULL;
1478 char *pixelshape=NULL;
1479 sqlite_int64 id;
1480 int rows=0, chunked=0, ret;
1481 #ifdef __sparc__
1482 ExifData *ed;
1483 ExifLoader *l;
1484 #endif
1485 image *imsrc = NULL, *imdst = NULL;
1486 int scale = 1;
1488 id = strtoll(object, NULL, 10);
1489 sprintf(str_buf, "SELECT PATH, RESOLUTION, THUMBNAIL from DETAILS where ID = '%lld'", id);
1490 ret = sql_get_table(db, str_buf, &result, &rows, NULL);
1491 if( (ret != SQLITE_OK) )
1493 DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id);
1494 Send500(h);
1495 return;
1497 if( !rows || (access(result[3], F_OK) != 0) )
1499 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
1500 sqlite3_free_table(result);
1501 Send404(h);
1502 return;
1504 #if USE_FORK
1505 pid_t newpid = 0;
1506 newpid = fork();
1507 if( newpid )
1508 goto resized_error;
1509 #endif
1510 file_path = result[3];
1511 resolution = result[4];
1512 tn = result[5];
1513 srcw = strtol(resolution, &saveptr, 10);
1514 srch = strtol(saveptr+1, NULL, 10);
1516 path = strdup(object);
1517 if( strtok_r(path, "?", &saveptr) )
1519 item = strtok_r(NULL, "&,", &saveptr);
1521 while( item != NULL )
1523 #ifdef TIVO_SUPPORT
1524 decodeString(item, 1);
1525 #endif
1526 val = item;
1527 key = strsep(&val, "=");
1528 DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val);
1529 if( strcasecmp(key, "width") == 0 )
1531 width = atoi(val);
1533 else if( strcasecmp(key, "height") == 0 )
1535 height = atoi(val);
1537 else if( strcasecmp(key, "rotation") == 0 )
1539 rotation = atoi(val);
1541 else if( strcasecmp(key, "pixelshape") == 0 )
1543 pixelshape = val;
1545 item = strtok_r(NULL, "&,", &saveptr);
1547 free(path);
1549 if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE )
1551 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with a resized image!\n");
1552 Send406(h);
1553 goto resized_error;
1556 DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %lld [%s]\n", id, file_path);
1558 /* Figure out the best destination resolution we can use */
1559 dstw = width;
1560 dsth = ((((width<<10)/srcw)*srch)>>10);
1561 if( dsth > height )
1563 dsth = height;
1564 dstw = (((height<<10)/srch) * srcw>>10);
1567 if( dstw <= 640 && dsth <= 480 )
1568 strcpy(dlna_pn, "SM");
1569 else if( dstw <= 1024 && dsth <= 768 )
1570 strcpy(dlna_pn, "MED");
1571 else
1572 strcpy(dlna_pn, "LRG");
1574 if( srcw>>3 >= dstw && srch>>3 >= dsth)
1575 scale = 8;
1576 else if( srcw>>2 >= dstw && srch>>2 >= dsth )
1577 scale = 4;
1578 else if( srcw>>1 >= dstw && srch>>1 >= dsth )
1579 scale = 2;
1581 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1582 snprintf(header, sizeof(header)-100, "HTTP/1.1 200 OK\r\n"
1583 "Content-Type: image/jpeg\r\n"
1584 "Connection: close\r\n"
1585 "Date: %s\r\n"
1586 "EXT:\r\n"
1587 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1588 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_%s;DLNA.ORG_CI=1\r\n"
1589 "Server: " MINIDLNA_SERVER_STRING "\r\n",
1590 date, dlna_pn);
1591 if( h->reqflags & FLAG_XFERINTERACTIVE )
1593 strcat(header, "transferMode.dlna.org: Interactive\r\n");
1595 else if( h->reqflags & FLAG_XFERBACKGROUND )
1597 strcat(header, "transferMode.dlna.org: Background\r\n");
1600 /* Resizing from a thumbnail is much faster than from a large image */
1601 #ifdef __sparc__
1602 if( dstw <= 160 && dsth <= 120 && atoi(tn) )
1604 l = exif_loader_new();
1605 exif_loader_write_file(l, file_path);
1606 ed = exif_loader_get_data(l);
1607 exif_loader_unref(l);
1609 if( !ed || !ed->size )
1611 if( ed )
1612 exif_data_unref(ed);
1613 DPRINTF(E_WARN, L_HTTP, "Unable to access image thumbnail!\n");
1614 Send500(h);
1615 goto resized_error;
1617 imsrc = image_new_from_jpeg(NULL, 0, (char *)ed->data, ed->size, 1);
1618 exif_data_unref(ed);
1620 else
1621 #endif
1622 if( strcmp(h->HttpVer, "HTTP/1.0") == 0 )
1624 imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale);
1626 else
1628 chunked = 1;
1629 strcat(header, "Transfer-Encoding: chunked\r\n\r\n");
1632 if( !chunked )
1634 if( !imsrc )
1636 DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path);
1637 Send500(h);
1638 goto resized_error;
1641 imdst = image_resize(imsrc, dstw, dsth);
1642 data = image_save_to_jpeg_buf(imdst, &size);
1644 sprintf(str_buf, "Content-Length: %d\r\n\r\n", size);
1645 strcat(header, str_buf);
1648 if( (send_data(h, header, strlen(header), 0) == 0) && (h->req_command != EHead) )
1650 if( chunked )
1652 imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale);
1653 if( !imsrc )
1655 DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path);
1656 Send500(h);
1657 goto resized_error;
1659 imdst = image_resize(imsrc, dstw, dsth);
1660 data = image_save_to_jpeg_buf(imdst, &size);
1662 ret = sprintf(str_buf, "%x\r\n", size);
1663 send_data(h, str_buf, ret, MSG_MORE);
1664 send_data(h, (char *)data, size, MSG_MORE);
1665 send_data(h, "\r\n0\r\n\r\n", 7, 0);
1667 else
1669 send_data(h, (char *)data, size, 0);
1672 DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path);
1673 if( imsrc )
1674 image_free(imsrc);
1675 if( imdst )
1676 image_free(imdst);
1677 resized_error:
1678 sqlite3_free_table(result);
1679 #if USE_FORK
1680 if( !newpid )
1681 _exit(0);
1682 #endif
1685 void
1686 SendResp_dlnafile(struct upnphttp * h, char * object)
1688 char header[1500];
1689 char hdr_buf[512];
1690 char sql_buf[256];
1691 char **result;
1692 int rows, ret;
1693 char date[30];
1694 time_t curtime = time(NULL);
1695 off_t total, offset, size;
1696 sqlite_int64 id;
1697 int sendfh;
1698 static struct { sqlite_int64 id; char path[PATH_MAX]; char mime[32]; char dlna[64]; } last_file = { 0 };
1699 #if USE_FORK
1700 pid_t newpid = 0;
1701 #endif
1703 id = strtoll(object, NULL, 10);
1704 if( id != last_file.id )
1706 sprintf(sql_buf, "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", id);
1707 ret = sql_get_table(db, sql_buf, &result, &rows, NULL);
1708 if( (ret != SQLITE_OK) )
1710 DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id);
1711 Send500(h);
1712 return;
1714 if( !rows )
1716 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
1717 sqlite3_free_table(result);
1718 Send404(h);
1719 return;
1721 /* Cache the result */
1722 last_file.id = id;
1723 strncpy(last_file.path, result[3], sizeof(last_file.path)-1);
1724 if( result[4] )
1726 strncpy(last_file.mime, result[4], sizeof(last_file.mime)-1);
1727 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
1728 if( h->req_client == ESamsungTV )
1730 if( strcmp(last_file.mime+6, "x-matroska") == 0 )
1731 strcpy(last_file.mime+8, "mkv");
1733 /* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */
1734 else if( h->req_client == ESonyBDP )
1736 if( strcmp(last_file.mime+6, "x-matroska") == 0 ||
1737 strcmp(last_file.mime+6, "mpeg") == 0 )
1738 strcpy(last_file.mime+6, "divx");
1741 else
1743 last_file.mime[0] = '\0';
1745 if( result[5] )
1746 snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s", result[5]);
1747 else if( h->reqflags & FLAG_DLNA )
1748 strcpy(last_file.dlna, dlna_no_conv);
1749 else
1750 last_file.dlna[0] = '\0';
1751 sqlite3_free_table(result);
1753 #if USE_FORK
1754 newpid = fork();
1755 if( newpid )
1756 return;
1757 #endif
1759 DPRINTF(E_INFO, L_HTTP, "Serving DetailID: %lld [%s]\n", id, last_file.path);
1761 if( h->reqflags & FLAG_XFERSTREAMING )
1763 if( strncmp(last_file.mime, "image", 5) == 0 )
1765 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1766 Send406(h);
1767 goto error;
1770 else if( h->reqflags & FLAG_XFERINTERACTIVE )
1772 if( h->reqflags & FLAG_REALTIMEINFO )
1774 DPRINTF(E_WARN, L_HTTP, "Bad realTimeInfo flag with Interactive request!\n");
1775 Send400(h);
1776 goto error;
1778 if( strncmp(last_file.mime, "image", 5) != 0 )
1780 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Interactive without an image!\n");
1781 /* Samsung TVs (well, at least the A950) do this for some reason,
1782 * and I don't see them fixing this bug any time soon. */
1783 if( h->req_client != ESamsungTV || GETFLAG(DLNA_STRICT_MASK) )
1785 Send406(h);
1786 goto error;
1791 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1792 offset = h->req_RangeStart;
1793 sendfh = open(last_file.path, O_RDONLY);
1794 if( sendfh < 0 ) {
1795 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", last_file.path);
1796 goto error;
1798 size = lseek(sendfh, 0, SEEK_END);
1799 lseek(sendfh, 0, SEEK_SET);
1801 sprintf(header, "HTTP/1.1 20%c OK\r\n"
1802 "Content-Type: %s\r\n", (h->reqflags & FLAG_RANGE ? '6' : '0'), last_file.mime);
1803 if( h->reqflags & FLAG_RANGE )
1805 if( !h->req_RangeEnd )
1806 h->req_RangeEnd = size;
1807 if( (h->req_RangeStart > h->req_RangeEnd) || (h->req_RangeStart < 0) )
1809 DPRINTF(E_WARN, L_HTTP, "Specified range was invalid!\n");
1810 Send400(h);
1811 close(sendfh);
1812 goto error;
1814 if( h->req_RangeEnd > size )
1816 DPRINTF(E_WARN, L_HTTP, "Specified range was outside file boundaries!\n");
1817 Send416(h);
1818 close(sendfh);
1819 goto error;
1822 if( h->req_RangeEnd < size )
1824 total = h->req_RangeEnd - h->req_RangeStart + 1;
1825 sprintf(hdr_buf, "Content-Length: %jd\r\n"
1826 "Content-Range: bytes %jd-%jd/%jd\r\n",
1827 total, h->req_RangeStart, h->req_RangeEnd, size);
1829 else
1831 h->req_RangeEnd = size;
1832 total = size - h->req_RangeStart;
1833 sprintf(hdr_buf, "Content-Length: %jd\r\n"
1834 "Content-Range: bytes %jd-%jd/%jd\r\n",
1835 total, h->req_RangeStart, size-1, size);
1838 else
1840 h->req_RangeEnd = size;
1841 total = size;
1842 sprintf(hdr_buf, "Content-Length: %jd\r\n", total);
1844 strcat(header, hdr_buf);
1846 if( h->reqflags & FLAG_XFERSTREAMING )
1848 strcat(header, "transferMode.dlna.org: Streaming\r\n");
1850 else if( h->reqflags & FLAG_XFERBACKGROUND )
1852 if( strncmp(last_file.mime, "image", 5) == 0 )
1853 strcat(header, "transferMode.dlna.org: Background\r\n");
1855 else //if( h->reqflags & FLAG_XFERINTERACTIVE )
1857 if( (strncmp(last_file.mime, "video", 5) == 0) ||
1858 (strncmp(last_file.mime, "audio", 5) == 0) )
1860 strcat(header, "transferMode.dlna.org: Streaming\r\n");
1862 else
1864 strcat(header, "transferMode.dlna.org: Interactive\r\n");
1868 if( h->reqflags & FLAG_CAPTION )
1870 if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%lld'", id) > 0 )
1872 sprintf(hdr_buf, "CaptionInfo.sec: http://%s:%d/Captions/%lld.srt\r\n",
1873 lan_addr[0].str, runtime_vars.port, id);
1874 strcat(header, hdr_buf);
1878 sprintf(hdr_buf, "Accept-Ranges: bytes\r\n"
1879 "Connection: close\r\n"
1880 "Date: %s\r\n"
1881 "EXT:\r\n"
1882 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1883 "contentFeatures.dlna.org: %s\r\n"
1884 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
1885 date, last_file.dlna);
1886 strcat(header, hdr_buf);
1888 if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) )
1890 send_file(h, sendfh, offset, h->req_RangeEnd);
1892 close(sendfh);
1894 error:
1895 #if USE_FORK
1896 if( !newpid )
1897 _exit(0);
1898 #endif
1899 return;