minidlna support now Samsung TV C550/C650 (thx amir909)
[tomato.git] / release / src / router / minidlna / upnphttp.c
blob2940b85c6d3b7f21336f8b6d72099ce0566d6329
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 free(h->req_buf);
121 free(h->res_buf);
122 free(h);
127 SearchClientCache(struct in_addr addr, int quiet)
129 int i;
130 for( i=0; i<CLIENT_CACHE_SLOTS; i++ )
132 if( clients[i].addr.s_addr == addr.s_addr )
134 /* Invalidate this client cache if it's older than 1 hour */
135 if( (time(NULL) - clients[i].age) > 3600 )
137 unsigned char mac[6];
138 if( get_remote_mac(addr, mac) == 0 &&
139 memcmp(mac, clients[i].mac, 6) == 0 )
141 /* Same MAC as last time when we were able to identify the client,
142 * so extend the timeout by another hour. */
143 clients[i].age = time(NULL);
145 else
147 memset(&clients[i], 0, sizeof(struct client_cache_s));
148 return -1;
151 if( !quiet )
152 DPRINTF(E_DEBUG, L_HTTP, "Client found in cache. [type %d/entry %d]\n",
153 clients[i].type, i);
154 return i;
157 return -1;
160 /* parse HttpHeaders of the REQUEST */
161 static void
162 ParseHttpHeaders(struct upnphttp * h)
164 char * line;
165 char * colon;
166 char * p;
167 int n;
168 line = h->req_buf;
169 /* TODO : check if req_buf, contentoff are ok */
170 while(line < (h->req_buf + h->req_contentoff))
172 colon = strchr(line, ':');
173 if(colon)
175 if(strncasecmp(line, "Content-Length", 14)==0)
177 p = colon;
178 while(*p < '0' || *p > '9')
179 p++;
180 h->req_contentlen = atoi(p);
182 else if(strncasecmp(line, "SOAPAction", 10)==0)
184 p = colon;
185 n = 0;
186 while(*p == ':' || *p == ' ' || *p == '\t')
187 p++;
188 while(p[n]>=' ')
190 n++;
192 if((p[0] == '"' && p[n-1] == '"')
193 || (p[0] == '\'' && p[n-1] == '\''))
195 p++; n -= 2;
197 h->req_soapAction = p;
198 h->req_soapActionLen = n;
200 else if(strncasecmp(line, "Callback", 8)==0)
202 p = colon;
203 while(*p != '<' && *p != '\r' )
204 p++;
205 n = 0;
206 while(p[n] != '>' && p[n] != '\r' )
207 n++;
208 h->req_Callback = p + 1;
209 h->req_CallbackLen = MAX(0, n - 1);
211 else if(strncasecmp(line, "SID", 3)==0)
213 //zqiu: fix bug for test 4.0.5
214 //Skip extra headers like "SIDHEADER: xxxxxx xxx"
215 for(p=line+3;p<colon;p++)
217 if(!isspace(*p))
219 p = NULL; //unexpected header
220 break;
223 if(p) {
224 p = colon + 1;
225 while(isspace(*p))
226 p++;
227 n = 0;
228 while(!isspace(p[n]))
229 n++;
230 h->req_SID = p;
231 h->req_SIDLen = n;
234 /* Timeout: Seconds-nnnn */
235 /* TIMEOUT
236 Recommended. Requested duration until subscription expires,
237 either number of seconds or infinite. Recommendation
238 by a UPnP Forum working committee. Defined by UPnP vendor.
239 Consists of the keyword "Second-" followed (without an
240 intervening space) by either an integer or the keyword "infinite". */
241 else if(strncasecmp(line, "Timeout", 7)==0)
243 p = colon + 1;
244 while(isspace(*p))
245 p++;
246 if(strncasecmp(p, "Second-", 7)==0) {
247 h->req_Timeout = atoi(p+7);
250 // Range: bytes=xxx-yyy
251 else if(strncasecmp(line, "Range", 5)==0)
253 p = colon + 1;
254 while(isspace(*p))
255 p++;
256 if(strncasecmp(p, "bytes=", 6)==0) {
257 h->reqflags |= FLAG_RANGE;
258 h->req_RangeStart = strtoll(p+6, &colon, 10);
259 h->req_RangeEnd = colon ? atoll(colon+1) : 0;
260 DPRINTF(E_DEBUG, L_HTTP, "Range Start-End: %lld - %lld\n",
261 h->req_RangeStart, h->req_RangeEnd?h->req_RangeEnd:-1);
264 else if(strncasecmp(line, "Host", 4)==0)
266 int i;
267 h->reqflags |= FLAG_HOST;
268 p = colon + 1;
269 while(isspace(*p))
270 p++;
271 for(n = 0; n<n_lan_addr; n++)
273 for(i=0; lan_addr[n].str[i]; i++)
275 if(lan_addr[n].str[i] != p[i])
276 break;
278 if(!lan_addr[n].str[i])
280 h->iface = n;
281 break;
285 else if(strncasecmp(line, "User-Agent", 10)==0)
287 /* Skip client detection if we already detected it. */
288 if( h->req_client )
289 goto next_header;
290 p = colon + 1;
291 while(isspace(*p))
292 p++;
293 if(strncasecmp(p, "Xbox/", 5)==0)
295 h->req_client = EXbox;
296 h->reqflags |= FLAG_MIME_AVI_AVI;
297 h->reqflags |= FLAG_MS_PFS;
299 else if(strncmp(p, "PLAYSTATION", 11)==0)
301 h->req_client = EPS3;
302 h->reqflags |= FLAG_DLNA;
303 h->reqflags |= FLAG_MIME_AVI_DIVX;
305 else if(strncmp(p, "SEC_HHP_", 8)==0)
307 h->req_client = ESamsungTV;
308 h->reqflags |= FLAG_SAMSUNG;
309 h->reqflags |= FLAG_DLNA;
310 h->reqflags |= FLAG_NO_RESIZE;
312 else if(strncmp(p, "SamsungWiselinkPro", 18)==0 ||
313 strncmp(p, "SEC_HHP_TV", 10)==0)
315 h->req_client = ESamsungSeriesA;
316 h->reqflags |= FLAG_SAMSUNG;
317 h->reqflags |= FLAG_DLNA;
318 h->reqflags |= FLAG_NO_RESIZE;
320 else if(strstrc(p, "bridgeCo-DMP/3", '\r'))
322 h->req_client = EDenonReceiver;
323 h->reqflags |= FLAG_DLNA;
325 else if(strstrc(p, "fbxupnpav/", '\r'))
327 h->req_client = EFreeBox;
329 else if(strncmp(p, "SMP8634", 7)==0)
331 h->req_client = EPopcornHour;
332 h->reqflags |= FLAG_MIME_FLAC_FLAC;
334 else if(strstrc(p, "Microsoft-IPTV-Client", '\r'))
336 h->req_client = EMediaRoom;
337 h->reqflags |= FLAG_MS_PFS;
339 else if(strstrc(p, "LGE_DLNA_SDK", '\r'))
341 h->req_client = ELGDevice;
342 h->reqflags |= FLAG_DLNA;
344 else if(strncmp(p, "Verismo,", 8)==0)
346 h->req_client = ENetgearEVA2000;
347 h->reqflags |= FLAG_MS_PFS;
349 else if(strstrc(p, "UPnP/1.0 DLNADOC/1.50 Intel_SDK_for_UPnP_devices/1.2", '\r'))
351 h->req_client = EToshibaTV;
352 h->reqflags |= FLAG_DLNA;
354 else if(strstrc(p, "DLNADOC/1.50", '\r'))
356 h->req_client = EStandardDLNA150;
357 h->reqflags |= FLAG_DLNA;
358 h->reqflags |= FLAG_MIME_AVI_AVI;
361 else if(strncasecmp(line, "X-AV-Client-Info", 16)==0)
363 /* Skip client detection if we already detected it. */
364 if( h->req_client && h->req_client < EStandardDLNA150 )
365 goto next_header;
366 p = colon + 1;
367 while(isspace(*p))
368 p++;
369 if(strstrc(p, "PLAYSTATION 3", '\r'))
371 h->req_client = EPS3;
372 h->reqflags |= FLAG_DLNA;
373 h->reqflags |= FLAG_MIME_AVI_DIVX;
375 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Blu-ray Disc Player"; mv="2.0" */
376 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BLU-RAY HOME THEATRE SYSTEM"; mv="2.0"; */
377 /* Sony SMP-100 needs the same treatment as their BDP-S370 */
378 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Media Player"; mv="2.0" */
379 else if(strstrc(p, "Blu-ray Disc Player", '\r') ||
380 strstrc(p, "BLU-RAY HOME THEATRE SYSTEM", '\r') ||
381 strstrc(p, "Media Player", '\r'))
383 h->req_client = ESonyBDP;
384 h->reqflags |= FLAG_DLNA;
386 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BRAVIA KDL-40EX503"; mv="1.7"; */
387 /* X-AV-Client-Info: av=5.0; hn=""; cn="Sony Corporation"; mn="INTERNET TV NSX-40GT 1"; mv="0.1"; */
388 else if(strstrc(p, "BRAVIA", '\r') ||
389 strstrc(p, "INTERNET TV", '\r'))
391 h->req_client = ESonyBravia;
392 h->reqflags |= FLAG_DLNA;
395 else if(strncasecmp(line, "Transfer-Encoding", 17)==0)
397 p = colon + 1;
398 while(isspace(*p))
399 p++;
400 if(strncasecmp(p, "chunked", 7)==0)
402 h->reqflags |= FLAG_CHUNKED;
405 else if(strncasecmp(line, "getcontentFeatures.dlna.org", 27)==0)
407 p = colon + 1;
408 while(isspace(*p))
409 p++;
410 if( (*p != '1') || !isspace(p[1]) )
411 h->reqflags |= FLAG_INVALID_REQ;
413 else if(strncasecmp(line, "TimeSeekRange.dlna.org", 22)==0)
415 h->reqflags |= FLAG_TIMESEEK;
417 else if(strncasecmp(line, "PlaySpeed.dlna.org", 18)==0)
419 h->reqflags |= FLAG_PLAYSPEED;
421 else if(strncasecmp(line, "realTimeInfo.dlna.org", 21)==0)
423 h->reqflags |= FLAG_REALTIMEINFO;
425 else if(strncasecmp(line, "transferMode.dlna.org", 21)==0)
427 p = colon + 1;
428 while(isspace(*p))
429 p++;
430 if(strncasecmp(p, "Streaming", 9)==0)
432 h->reqflags |= FLAG_XFERSTREAMING;
434 if(strncasecmp(p, "Interactive", 11)==0)
436 h->reqflags |= FLAG_XFERINTERACTIVE;
438 if(strncasecmp(p, "Background", 10)==0)
440 h->reqflags |= FLAG_XFERBACKGROUND;
443 else if(strncasecmp(line, "getCaptionInfo.sec", 18)==0)
445 h->reqflags |= FLAG_CAPTION;
448 next_header:
449 while(!(line[0] == '\r' && line[1] == '\n'))
450 line++;
451 line += 2;
453 if( h->reqflags & FLAG_CHUNKED )
455 char *endptr;
456 h->req_chunklen = -1;
457 if( h->req_buflen <= h->req_contentoff )
458 return;
459 while( (line < (h->req_buf + h->req_buflen)) &&
460 (h->req_chunklen = strtol(line, &endptr, 16)) &&
461 (endptr != line) )
463 while(!(endptr[0] == '\r' && endptr[1] == '\n'))
465 endptr++;
467 line = endptr+h->req_chunklen+2;
470 if( endptr == line )
472 h->req_chunklen = -1;
473 return;
476 /* If the client type wasn't found, search the cache.
477 * This is done because a lot of clients like to send a
478 * different User-Agent with different types of requests. */
479 n = SearchClientCache(h->clientaddr, 0);
480 if( h->req_client )
482 /* Add this client to the cache if it's not there already. */
483 if( n < 0 )
485 for( n=0; n<CLIENT_CACHE_SLOTS; n++ )
487 if( clients[n].addr.s_addr )
488 continue;
489 get_remote_mac(h->clientaddr, clients[n].mac);
490 clients[n].addr = h->clientaddr;
491 DPRINTF(E_DEBUG, L_HTTP, "Added client [%d/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n",
492 h->req_client, inet_ntoa(clients[n].addr),
493 clients[n].mac[0], clients[n].mac[1], clients[n].mac[2],
494 clients[n].mac[3], clients[n].mac[4], clients[n].mac[5], n);
495 break;
498 else if( (clients[n].type < EStandardDLNA150 && h->req_client == EStandardDLNA150) ||
499 (clients[n].type == ESamsungSeriesB && h->req_client == ESamsungSeriesA) )
501 /* If we know the client and our new detection is generic, use our cached info */
502 /* If we detected a Samsung Series B earlier, don't overwrite it with Series A info */
503 h->reqflags |= clients[n].flags;
504 h->req_client = clients[n].type;
505 return;
507 clients[n].type = h->req_client;
508 clients[n].flags = h->reqflags & 0xFFF00000;
509 clients[n].age = time(NULL);
511 else if( n >= 0 )
513 h->reqflags |= clients[n].flags;
514 h->req_client = clients[n].type;
518 /* very minimalistic 400 error message */
519 static void
520 Send400(struct upnphttp * h)
522 static const char body400[] =
523 "<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>"
524 "<BODY><H1>Bad Request</H1>The request is invalid"
525 " for this HTTP version.</BODY></HTML>\r\n";
526 h->respflags = FLAG_HTML;
527 BuildResp2_upnphttp(h, 400, "Bad Request",
528 body400, sizeof(body400) - 1);
529 SendResp_upnphttp(h);
530 CloseSocket_upnphttp(h);
533 /* very minimalistic 404 error message */
534 static void
535 Send404(struct upnphttp * h)
537 static const char body404[] =
538 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
539 "<BODY><H1>Not Found</H1>The requested URL was not found"
540 " on this server.</BODY></HTML>\r\n";
541 h->respflags = FLAG_HTML;
542 BuildResp2_upnphttp(h, 404, "Not Found",
543 body404, sizeof(body404) - 1);
544 SendResp_upnphttp(h);
545 CloseSocket_upnphttp(h);
548 /* very minimalistic 406 error message */
549 static void
550 Send406(struct upnphttp * h)
552 static const char body406[] =
553 "<HTML><HEAD><TITLE>406 Not Acceptable</TITLE></HEAD>"
554 "<BODY><H1>Not Acceptable</H1>An unsupported operation"
555 " was requested.</BODY></HTML>\r\n";
556 h->respflags = FLAG_HTML;
557 BuildResp2_upnphttp(h, 406, "Not Acceptable",
558 body406, sizeof(body406) - 1);
559 SendResp_upnphttp(h);
560 CloseSocket_upnphttp(h);
563 /* very minimalistic 416 error message */
564 static void
565 Send416(struct upnphttp * h)
567 static const char body416[] =
568 "<HTML><HEAD><TITLE>416 Requested Range Not Satisfiable</TITLE></HEAD>"
569 "<BODY><H1>Requested Range Not Satisfiable</H1>The requested range"
570 " was outside the file's size.</BODY></HTML>\r\n";
571 h->respflags = FLAG_HTML;
572 BuildResp2_upnphttp(h, 416, "Requested Range Not Satisfiable",
573 body416, sizeof(body416) - 1);
574 SendResp_upnphttp(h);
575 CloseSocket_upnphttp(h);
578 /* very minimalistic 500 error message */
579 void
580 Send500(struct upnphttp * h)
582 static const char body500[] =
583 "<HTML><HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>"
584 "<BODY><H1>Internal Server Error</H1>Server encountered "
585 "and Internal Error.</BODY></HTML>\r\n";
586 h->respflags = FLAG_HTML;
587 BuildResp2_upnphttp(h, 500, "Internal Server Errror",
588 body500, sizeof(body500) - 1);
589 SendResp_upnphttp(h);
590 CloseSocket_upnphttp(h);
593 /* very minimalistic 501 error message */
594 void
595 Send501(struct upnphttp * h)
597 static const char body501[] =
598 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
599 "<BODY><H1>Not Implemented</H1>The HTTP Method "
600 "is not implemented by this server.</BODY></HTML>\r\n";
601 h->respflags = FLAG_HTML;
602 BuildResp2_upnphttp(h, 501, "Not Implemented",
603 body501, sizeof(body501) - 1);
604 SendResp_upnphttp(h);
605 CloseSocket_upnphttp(h);
608 static const char *
609 findendheaders(const char * s, int len)
611 while(len-- > 0)
613 if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n')
614 return s;
615 s++;
617 return NULL;
620 /* Sends the description generated by the parameter */
621 static void
622 sendXMLdesc(struct upnphttp * h, char * (f)(int *))
624 char * desc;
625 int len;
626 desc = f(&len);
627 if(!desc)
629 DPRINTF(E_ERROR, L_HTTP, "Failed to generate XML description\n");
630 Send500(h);
631 return;
633 BuildResp_upnphttp(h, desc, len);
634 SendResp_upnphttp(h);
635 CloseSocket_upnphttp(h);
636 free(desc);
639 /* ProcessHTTPPOST_upnphttp()
640 * executes the SOAP query if it is possible */
641 static void
642 ProcessHTTPPOST_upnphttp(struct upnphttp * h)
644 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
646 if(h->req_soapAction)
648 /* we can process the request */
649 DPRINTF(E_DEBUG, L_HTTP, "SOAPAction: %.*s\n", h->req_soapActionLen, h->req_soapAction);
650 ExecuteSoapAction(h,
651 h->req_soapAction,
652 h->req_soapActionLen);
654 else
656 static const char err400str[] =
657 "<html><body>Bad request</body></html>";
658 DPRINTF(E_WARN, L_HTTP, "No SOAPAction in HTTP headers\n");
659 h->respflags = FLAG_HTML;
660 BuildResp2_upnphttp(h, 400, "Bad Request",
661 err400str, sizeof(err400str) - 1);
662 SendResp_upnphttp(h);
663 CloseSocket_upnphttp(h);
666 else
668 /* waiting for remaining data */
669 h->state = 1;
673 static void
674 ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path)
676 const char * sid;
677 DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPSubscribe %s\n", path);
678 DPRINTF(E_DEBUG, L_HTTP, "Callback '%.*s' Timeout=%d\n",
679 h->req_CallbackLen, h->req_Callback, h->req_Timeout);
680 DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID);
681 if(!h->req_Callback && !h->req_SID) {
682 /* Missing or invalid CALLBACK : 412 Precondition Failed.
683 * If CALLBACK header is missing or does not contain a valid HTTP URL,
684 * the publisher must respond with HTTP error 412 Precondition Failed*/
685 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
686 SendResp_upnphttp(h);
687 CloseSocket_upnphttp(h);
688 } else {
689 /* - add to the subscriber list
690 * - respond HTTP/x.x 200 OK
691 * - Send the initial event message */
692 /* Server:, SID:; Timeout: Second-(xx|infinite) */
693 if(h->req_Callback) {
694 sid = upnpevents_addSubscriber(path, h->req_Callback,
695 h->req_CallbackLen, h->req_Timeout);
696 h->respflags = FLAG_TIMEOUT;
697 if(sid) {
698 DPRINTF(E_DEBUG, L_HTTP, "generated sid=%s\n", sid);
699 h->respflags |= FLAG_SID;
700 h->req_SID = sid;
701 h->req_SIDLen = strlen(sid);
703 BuildResp_upnphttp(h, 0, 0);
704 } else {
705 /* subscription renew */
706 /* Invalid SID
707 412 Precondition Failed. If a SID does not correspond to a known,
708 un-expired subscription, the publisher must respond
709 with HTTP error 412 Precondition Failed. */
710 if(renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0) {
711 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
712 } else {
713 /* A DLNA device must enforce a 5 minute timeout */
714 h->respflags = FLAG_TIMEOUT;
715 h->req_Timeout = 300;
716 h->respflags |= FLAG_SID;
717 BuildResp_upnphttp(h, 0, 0);
720 SendResp_upnphttp(h);
721 CloseSocket_upnphttp(h);
725 static void
726 ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path)
728 DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPUnSubscribe %s\n", path);
729 DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID);
730 /* Remove from the list */
731 if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0) {
732 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
733 } else {
734 BuildResp_upnphttp(h, 0, 0);
736 SendResp_upnphttp(h);
737 CloseSocket_upnphttp(h);
740 /* Parse and process Http Query
741 * called once all the HTTP headers have been received. */
742 static void
743 ProcessHttpQuery_upnphttp(struct upnphttp * h)
745 char HttpCommand[16];
746 char HttpUrl[512];
747 char * HttpVer;
748 char * p;
749 int i;
750 p = h->req_buf;
751 if(!p)
752 return;
753 for(i = 0; i<15 && *p != ' ' && *p != '\r'; i++)
754 HttpCommand[i] = *(p++);
755 HttpCommand[i] = '\0';
756 while(*p==' ')
757 p++;
758 if(strncmp(p, "http://", 7) == 0)
760 p = p+7;
761 while(*p!='/')
762 p++;
764 for(i = 0; i<511 && *p != ' ' && *p != '\r'; i++)
765 HttpUrl[i] = *(p++);
766 HttpUrl[i] = '\0';
767 while(*p==' ')
768 p++;
769 HttpVer = h->HttpVer;
770 for(i = 0; i<15 && *p != '\r'; i++)
771 HttpVer[i] = *(p++);
772 HttpVer[i] = '\0';
773 /*DPRINTF(E_INFO, L_HTTP, "HTTP REQUEST : %s %s (%s)\n",
774 HttpCommand, HttpUrl, HttpVer);*/
775 ParseHttpHeaders(h);
777 /* see if we need to wait for remaining data */
778 if( (h->reqflags & FLAG_CHUNKED) )
780 if( h->req_chunklen )
782 h->state = 2;
783 return;
785 char *chunkstart, *chunk, *endptr, *endbuf;
786 chunk = endbuf = chunkstart = h->req_buf + h->req_contentoff;
788 while( (h->req_chunklen = strtol(chunk, &endptr, 16)) && (endptr != chunk) )
790 while(!(endptr[0] == '\r' && endptr[1] == '\n'))
792 endptr++;
794 endptr += 2;
796 memmove(endbuf, endptr, h->req_chunklen);
798 endbuf += h->req_chunklen;
799 chunk = endptr + h->req_chunklen;
801 h->req_contentlen = endbuf - chunkstart;
802 h->req_buflen = endbuf - h->req_buf;
803 h->state = 100;
806 DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf);
807 if(strcmp("POST", HttpCommand) == 0)
809 h->req_command = EPost;
810 ProcessHTTPPOST_upnphttp(h);
812 else if((strcmp("GET", HttpCommand) == 0) || (strcmp("HEAD", HttpCommand) == 0))
814 if( ((strcmp(h->HttpVer, "HTTP/1.1")==0) && !(h->reqflags & FLAG_HOST)) || (h->reqflags & FLAG_INVALID_REQ) )
816 DPRINTF(E_WARN, L_HTTP, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n");
817 Send400(h);
818 return;
820 #if 1 /* 7.3.33.4 */
821 else if( ((h->reqflags & FLAG_TIMESEEK) || (h->reqflags & FLAG_PLAYSPEED)) &&
822 !(h->reqflags & FLAG_RANGE) )
824 DPRINTF(E_WARN, L_HTTP, "DLNA %s requested, responding ERROR 406\n",
825 h->reqflags&FLAG_TIMESEEK ? "TimeSeek" : "PlaySpeed");
826 Send406(h);
827 return;
829 #endif
830 else if(strcmp("GET", HttpCommand) == 0)
832 h->req_command = EGet;
834 else
836 h->req_command = EHead;
838 if(strcmp(ROOTDESC_PATH, HttpUrl) == 0)
840 /* If it's a Xbox360, we might need a special friendly_name to be recognized */
841 if( (h->req_client == EXbox) && !strchr(friendly_name, ':') )
843 i = strlen(friendly_name);
844 snprintf(friendly_name+i, FRIENDLYNAME_MAX_LEN-i, ": 1");
845 sendXMLdesc(h, genRootDesc);
846 friendly_name[i] = '\0';
848 else
850 sendXMLdesc(h, genRootDesc);
853 else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0)
855 sendXMLdesc(h, genContentDirectory);
857 else if(strcmp(CONNECTIONMGR_PATH, HttpUrl) == 0)
859 sendXMLdesc(h, genConnectionManager);
861 else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH, HttpUrl) == 0)
863 sendXMLdesc(h, genX_MS_MediaReceiverRegistrar);
865 else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0)
867 SendResp_dlnafile(h, HttpUrl+12);
868 CloseSocket_upnphttp(h);
870 else if(strncmp(HttpUrl, "/Thumbnails/", 12) == 0)
872 SendResp_thumbnail(h, HttpUrl+12);
874 else if(strncmp(HttpUrl, "/AlbumArt/", 10) == 0)
876 SendResp_albumArt(h, HttpUrl+10);
877 CloseSocket_upnphttp(h);
879 #ifdef TIVO_SUPPORT
880 else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0)
882 if( GETFLAG(TIVO_MASK) )
884 if( *(HttpUrl+12) == '?' )
886 ProcessTiVoCommand(h, HttpUrl+13);
888 else
890 DPRINTF(E_WARN, L_HTTP, "Invalid TiVo request! %s\n", HttpUrl+12);
891 Send404(h);
894 else
896 DPRINTF(E_WARN, L_HTTP, "TiVo request with out TiVo support enabled! %s\n",
897 HttpUrl+12);
898 Send404(h);
901 #endif
902 else if(strncmp(HttpUrl, "/Resized/", 9) == 0)
904 SendResp_resizedimg(h, HttpUrl+9);
905 CloseSocket_upnphttp(h);
907 else if(strncmp(HttpUrl, "/icons/", 7) == 0)
909 SendResp_icon(h, HttpUrl+7);
910 CloseSocket_upnphttp(h);
912 else if(strncmp(HttpUrl, "/Captions/", 10) == 0)
914 SendResp_caption(h, HttpUrl+10);
915 CloseSocket_upnphttp(h);
917 else
919 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", HttpUrl);
920 Send404(h);
923 else if(strcmp("SUBSCRIBE", HttpCommand) == 0)
925 h->req_command = ESubscribe;
926 ProcessHTTPSubscribe_upnphttp(h, HttpUrl);
928 else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0)
930 h->req_command = EUnSubscribe;
931 ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl);
933 else
935 DPRINTF(E_WARN, L_HTTP, "Unsupported HTTP Command %s\n", HttpCommand);
936 Send501(h);
941 void
942 Process_upnphttp(struct upnphttp * h)
944 char buf[2048];
945 int n;
946 if(!h)
947 return;
948 switch(h->state)
950 case 0:
951 n = recv(h->socket, buf, 2048, 0);
952 if(n<0)
954 DPRINTF(E_ERROR, L_HTTP, "recv (state0): %s\n", strerror(errno));
955 h->state = 100;
957 else if(n==0)
959 DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n");
960 h->state = 100;
962 else
964 const char * endheaders;
965 /* if 1st arg of realloc() is null,
966 * realloc behaves the same as malloc() */
967 h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen + 1);
968 memcpy(h->req_buf + h->req_buflen, buf, n);
969 h->req_buflen += n;
970 h->req_buf[h->req_buflen] = '\0';
971 /* search for the string "\r\n\r\n" */
972 endheaders = findendheaders(h->req_buf, h->req_buflen);
973 if(endheaders)
975 h->req_contentoff = endheaders - h->req_buf + 4;
976 h->req_contentlen = h->req_buflen - h->req_contentoff;
977 ProcessHttpQuery_upnphttp(h);
980 break;
981 case 1:
982 case 2:
983 n = recv(h->socket, buf, 2048, 0);
984 if(n<0)
986 DPRINTF(E_ERROR, L_HTTP, "recv (state%d): %s\n", h->state, strerror(errno));
987 h->state = 100;
989 else if(n==0)
991 DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n");
992 h->state = 100;
994 else
996 /*fwrite(buf, 1, n, stdout);*/ /* debug */
997 h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen);
998 memcpy(h->req_buf + h->req_buflen, buf, n);
999 h->req_buflen += n;
1000 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
1002 /* Need the struct to point to the realloc'd memory locations */
1003 if( h->state == 1 )
1005 ParseHttpHeaders(h);
1006 ProcessHTTPPOST_upnphttp(h);
1008 else if( h->state == 2 )
1010 ProcessHttpQuery_upnphttp(h);
1014 break;
1015 default:
1016 DPRINTF(E_WARN, L_HTTP, "Unexpected state: %d\n", h->state);
1020 static const char httpresphead[] =
1021 "%s %d %s\r\n"
1022 "Content-Type: %s\r\n"
1023 "Connection: close\r\n"
1024 "Content-Length: %d\r\n"
1025 "Server: " MINIDLNA_SERVER_STRING "\r\n"
1026 // "Accept-Ranges: bytes\r\n"
1027 ; /*"\r\n";*/
1029 "<?xml version=\"1.0\"?>\n"
1030 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
1031 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1032 "<s:Body>"
1034 "</s:Body>"
1035 "</s:Envelope>";
1037 /* with response code and response message
1038 * also allocate enough memory */
1040 void
1041 BuildHeader_upnphttp(struct upnphttp * h, int respcode,
1042 const char * respmsg,
1043 int bodylen)
1045 int templen;
1046 if(!h->res_buf)
1048 templen = sizeof(httpresphead) + 192 + bodylen;
1049 h->res_buf = (char *)malloc(templen);
1050 h->res_buf_alloclen = templen;
1052 h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen,
1053 //httpresphead, h->HttpVer,
1054 httpresphead, "HTTP/1.1",
1055 respcode, respmsg,
1056 (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"",
1057 bodylen);
1058 /* Additional headers */
1059 if(h->respflags & FLAG_TIMEOUT) {
1060 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1061 h->res_buf_alloclen - h->res_buflen,
1062 "Timeout: Second-");
1063 if(h->req_Timeout) {
1064 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1065 h->res_buf_alloclen - h->res_buflen,
1066 "%d\r\n", h->req_Timeout);
1067 } else {
1068 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1069 h->res_buf_alloclen - h->res_buflen,
1070 "300\r\n");
1071 //JM DLNA must force to 300 - "infinite\r\n");
1074 if(h->respflags & FLAG_SID) {
1075 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1076 h->res_buf_alloclen - h->res_buflen,
1077 "SID: %.*s\r\n", h->req_SIDLen, h->req_SID);
1079 #if 0 // DLNA
1080 char szTime[30];
1081 time_t curtime = time(NULL);
1082 strftime(szTime, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1083 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1084 h->res_buf_alloclen - h->res_buflen,
1085 "Date: %s\r\n", szTime);
1086 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1087 h->res_buf_alloclen - h->res_buflen,
1088 "contentFeatures.dlna.org: \r\n");
1089 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1090 h->res_buf_alloclen - h->res_buflen,
1091 "EXT:\r\n");
1092 #endif
1093 h->res_buf[h->res_buflen++] = '\r';
1094 h->res_buf[h->res_buflen++] = '\n';
1095 if(h->res_buf_alloclen < (h->res_buflen + bodylen))
1097 h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen));
1098 h->res_buf_alloclen = h->res_buflen + bodylen;
1102 void
1103 BuildResp2_upnphttp(struct upnphttp * h, int respcode,
1104 const char * respmsg,
1105 const char * body, int bodylen)
1107 BuildHeader_upnphttp(h, respcode, respmsg, bodylen);
1108 if( h->req_command == EHead )
1109 return;
1110 if(body)
1111 memcpy(h->res_buf + h->res_buflen, body, bodylen);
1112 h->res_buflen += bodylen;
1115 /* responding 200 OK ! */
1116 void
1117 BuildResp_upnphttp(struct upnphttp * h,
1118 const char * body, int bodylen)
1120 BuildResp2_upnphttp(h, 200, "OK", body, bodylen);
1123 void
1124 SendResp_upnphttp(struct upnphttp * h)
1126 int n;
1127 DPRINTF(E_DEBUG, L_HTTP, "HTTP RESPONSE: %.*s\n", h->res_buflen, h->res_buf);
1128 n = send(h->socket, h->res_buf, h->res_buflen, 0);
1129 if(n<0)
1131 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno));
1133 else if(n < h->res_buflen)
1135 /* TODO : handle correctly this case */
1136 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
1137 n, h->res_buflen);
1142 send_data(struct upnphttp * h, char * header, size_t size, int flags)
1144 int n;
1146 n = send(h->socket, header, size, flags);
1147 if(n<0)
1149 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno));
1151 else if(n < h->res_buflen)
1153 /* TODO : handle correctly this case */
1154 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
1155 n, h->res_buflen);
1157 else
1159 return 0;
1161 return 1;
1164 void
1165 send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset)
1167 off_t send_size;
1168 off_t ret;
1169 char *buf = NULL;
1170 int try_sendfile = 1;
1172 while( offset < end_offset )
1174 if( try_sendfile )
1176 send_size = ( ((end_offset - offset) < MAX_BUFFER_SIZE) ? (end_offset - offset + 1) : MAX_BUFFER_SIZE);
1177 ret = sendfile(h->socket, sendfd, &offset, send_size);
1178 if( ret == -1 )
1180 DPRINTF(E_DEBUG, L_HTTP, "sendfile error :: error no. %d [%s]\n", errno, strerror(errno));
1181 /* If sendfile isn't supported on the filesystem, don't bother trying to use it again. */
1182 if( errno == EOVERFLOW || errno == EINVAL )
1183 try_sendfile = 0;
1184 else if( errno != EAGAIN )
1185 break;
1187 else
1189 //DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset);
1190 continue;
1193 /* Fall back to regular I/O */
1194 if( !buf )
1195 buf = malloc(MIN_BUFFER_SIZE);
1196 send_size = ( ((end_offset - offset) < MIN_BUFFER_SIZE) ? (end_offset - offset + 1) : MIN_BUFFER_SIZE);
1197 lseek(sendfd, offset, SEEK_SET);
1198 ret = read(sendfd, buf, send_size);
1199 if( ret == -1 ) {
1200 DPRINTF(E_DEBUG, L_HTTP, "read error :: error no. %d [%s]\n", errno, strerror(errno));
1201 if( errno != EAGAIN )
1202 break;
1204 ret = write(h->socket, buf, ret);
1205 if( ret == -1 ) {
1206 DPRINTF(E_DEBUG, L_HTTP, "write error :: error no. %d [%s]\n", errno, strerror(errno));
1207 if( errno != EAGAIN )
1208 break;
1210 offset+=ret;
1212 free(buf);
1215 void
1216 SendResp_icon(struct upnphttp * h, char * icon)
1218 char header[512];
1219 char mime[12] = "image/";
1220 char date[30];
1221 char *data;
1222 int size, ret;
1223 time_t curtime = time(NULL);
1225 if( strcmp(icon, "sm.png") == 0 )
1227 DPRINTF(E_DEBUG, L_HTTP, "Sending small PNG icon\n");
1228 data = (char *)png_sm;
1229 size = sizeof(png_sm)-1;
1230 strcpy(mime+6, "png");
1232 else if( strcmp(icon, "lrg.png") == 0 )
1234 DPRINTF(E_DEBUG, L_HTTP, "Sending large PNG icon\n");
1235 data = (char *)png_lrg;
1236 size = sizeof(png_lrg)-1;
1237 strcpy(mime+6, "png");
1239 else if( strcmp(icon, "sm.jpg") == 0 )
1241 DPRINTF(E_DEBUG, L_HTTP, "Sending small JPEG icon\n");
1242 data = (char *)jpeg_sm;
1243 size = sizeof(jpeg_sm)-1;
1244 strcpy(mime+6, "jpeg");
1246 else if( strcmp(icon, "lrg.jpg") == 0 )
1248 DPRINTF(E_DEBUG, L_HTTP, "Sending large JPEG icon\n");
1249 data = (char *)jpeg_lrg;
1250 size = sizeof(jpeg_lrg)-1;
1251 strcpy(mime+6, "jpeg");
1253 else
1255 DPRINTF(E_WARN, L_HTTP, "Invalid icon request: %s\n", icon);
1256 Send404(h);
1257 return;
1260 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1261 ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n"
1262 "Content-Type: %s\r\n"
1263 "Content-Length: %d\r\n"
1264 "Connection: close\r\n"
1265 "Date: %s\r\n"
1266 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
1267 mime, size, date);
1269 if( send_data(h, header, ret, MSG_MORE) == 0 )
1271 if( h->req_command != EHead )
1272 send_data(h, data, size, 0);
1276 void
1277 SendResp_albumArt(struct upnphttp * h, char * object)
1279 char header[512];
1280 char *path;
1281 char *dash;
1282 char date[30];
1283 time_t curtime = time(NULL);
1284 off_t size;
1285 int fd;
1286 int ret;
1288 if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE )
1290 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1291 Send406(h);
1292 return;
1295 dash = strchr(object, '-');
1296 if( dash )
1297 *dash = '\0';
1299 path = sql_get_text_field(db, "SELECT PATH from ALBUM_ART where ID = '%s'", object);
1300 if( !path )
1302 DPRINTF(E_WARN, L_HTTP, "ALBUM_ART ID %s not found, responding ERROR 404\n", object);
1303 Send404(h);
1304 return;
1306 DPRINTF(E_INFO, L_HTTP, "Serving album art ID: %s [%s]\n", object, path);
1308 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1310 fd = open(path, O_RDONLY);
1311 if( fd < 0 ) {
1312 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path);
1313 sqlite3_free(path);
1314 Send404(h);
1315 return;
1317 sqlite3_free(path);
1318 size = lseek(fd, 0, SEEK_END);
1319 lseek(fd, 0, SEEK_SET);
1321 ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n"
1322 "Content-Type: image/jpeg\r\n"
1323 "Content-Length: %jd\r\n"
1324 "Connection: close\r\n"
1325 "Date: %s\r\n"
1326 "EXT:\r\n"
1327 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1328 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
1329 "Server: " MINIDLNA_SERVER_STRING "\r\n"
1330 "transferMode.dlna.org: %s\r\n\r\n",
1331 (intmax_t)size, date,
1332 (h->reqflags & FLAG_XFERBACKGROUND) ? "Background" : "Interactive");
1334 if( send_data(h, header, ret, MSG_MORE) == 0 )
1336 if( h->req_command != EHead )
1337 send_file(h, fd, 0, size-1);
1339 close(fd);
1342 void
1343 SendResp_caption(struct upnphttp * h, char * object)
1345 char header[512];
1346 char *path;
1347 char date[30];
1348 time_t curtime = time(NULL);
1349 off_t size;
1350 int fd, ret;
1352 strip_ext(object);
1353 path = sql_get_text_field(db, "SELECT PATH from CAPTIONS where ID = %s", object);
1354 if( !path )
1356 DPRINTF(E_WARN, L_HTTP, "CAPTION ID %s not found, responding ERROR 404\n", object);
1357 Send404(h);
1358 return;
1360 DPRINTF(E_INFO, L_HTTP, "Serving caption ID: %s [%s]\n", object, path);
1362 fd = open(path, O_RDONLY);
1363 if( fd < 0 ) {
1364 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path);
1365 sqlite3_free(path);
1366 Send404(h);
1367 return;
1369 sqlite3_free(path);
1370 size = lseek(fd, 0, SEEK_END);
1371 lseek(fd, 0, SEEK_SET);
1372 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1374 ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n"
1375 "Content-Type: smi/caption\r\n"
1376 "Content-Length: %jd\r\n"
1377 "Connection: close\r\n"
1378 "Date: %s\r\n"
1379 "EXT:\r\n"
1380 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
1381 (intmax_t)size, date);
1383 if( send_data(h, header, ret, MSG_MORE) == 0 )
1385 if( h->req_command != EHead )
1386 send_file(h, fd, 0, size-1);
1388 close(fd);
1391 void
1392 SendResp_thumbnail(struct upnphttp * h, char * object)
1394 char header[512];
1395 char *path;
1396 char date[30];
1397 time_t curtime = time(NULL);
1398 int ret;
1399 ExifData *ed;
1400 ExifLoader *l;
1402 if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE )
1404 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1405 Send406(h);
1406 return;
1409 strip_ext(object);
1410 path = sql_get_text_field(db, "SELECT PATH from DETAILS where ID = '%s'", object);
1411 if( !path )
1413 DPRINTF(E_WARN, L_HTTP, "DETAIL ID %s not found, responding ERROR 404\n", object);
1414 Send404(h);
1415 return;
1417 DPRINTF(E_INFO, L_HTTP, "Serving thumbnail for ObjectId: %s [%s]\n", object, path);
1419 if( access(path, F_OK) != 0 )
1421 DPRINTF(E_ERROR, L_HTTP, "Error accessing %s\n", path);
1422 sqlite3_free(path);
1423 return;
1425 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1427 l = exif_loader_new();
1428 exif_loader_write_file(l, path);
1429 ed = exif_loader_get_data(l);
1430 exif_loader_unref(l);
1431 sqlite3_free(path);
1433 if( !ed || !ed->size )
1435 Send404(h);
1436 if( ed )
1437 exif_data_unref(ed);
1438 return;
1440 ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n"
1441 "Content-Type: image/jpeg\r\n"
1442 "Content-Length: %d\r\n"
1443 "Connection: close\r\n"
1444 "Date: %s\r\n"
1445 "EXT:\r\n"
1446 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1447 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
1448 "Server: " MINIDLNA_SERVER_STRING "\r\n"
1449 "transferMode.dlna.org: %s\r\n\r\n",
1450 ed->size, date,
1451 (h->reqflags & FLAG_XFERBACKGROUND) ? "Background" : "Interactive");
1453 if( send_data(h, header, ret, MSG_MORE) == 0 )
1455 if( h->req_command != EHead )
1456 send_data(h, (char *)ed->data, ed->size, 0);
1458 exif_data_unref(ed);
1459 CloseSocket_upnphttp(h);
1462 void
1463 SendResp_resizedimg(struct upnphttp * h, char * object)
1465 char header[512];
1466 char str_buf[256];
1467 struct string_s str;
1468 char **result;
1469 char date[30];
1470 char dlna_pn[4];
1471 time_t curtime = time(NULL);
1472 int width=640, height=480, dstw, dsth, size;
1473 long srcw, srch;
1474 unsigned char * data = NULL;
1475 char *path, *file_path;
1476 char *resolution;
1477 char *key, *val;
1478 char *saveptr=NULL, *item=NULL;
1479 /* Not implemented yet *
1480 char *pixelshape=NULL;
1481 int rotation; */
1482 sqlite_int64 id;
1483 int rows=0, chunked, ret;
1484 #ifdef __sparc__
1485 char *tn;
1486 ExifData *ed;
1487 ExifLoader *l;
1488 #endif
1489 image_s *imsrc = NULL, *imdst = NULL;
1490 int scale = 1;
1492 id = strtoll(object, NULL, 10);
1493 sprintf(str_buf, "SELECT PATH, RESOLUTION, THUMBNAIL from DETAILS where ID = '%lld'", id);
1494 ret = sql_get_table(db, str_buf, &result, &rows, NULL);
1495 if( (ret != SQLITE_OK) )
1497 DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id);
1498 Send500(h);
1499 return;
1501 if( !rows || (access(result[3], F_OK) != 0) )
1503 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
1504 sqlite3_free_table(result);
1505 Send404(h);
1506 return;
1508 #if USE_FORK
1509 pid_t newpid = 0;
1510 newpid = fork();
1511 if( newpid )
1512 goto resized_error;
1513 #endif
1514 file_path = result[3];
1515 resolution = result[4];
1516 srcw = strtol(resolution, &saveptr, 10);
1517 srch = strtol(saveptr+1, NULL, 10);
1519 path = strdup(object);
1520 if( strtok_r(path, "?", &saveptr) )
1522 item = strtok_r(NULL, "&,", &saveptr);
1524 while( item != NULL )
1526 #ifdef TIVO_SUPPORT
1527 decodeString(item, 1);
1528 #endif
1529 val = item;
1530 key = strsep(&val, "=");
1531 DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val);
1532 if( strcasecmp(key, "width") == 0 )
1534 width = atoi(val);
1536 else if( strcasecmp(key, "height") == 0 )
1538 height = atoi(val);
1540 /* Not implemented yet *
1541 else if( strcasecmp(key, "rotation") == 0 )
1543 rotation = atoi(val);
1545 else if( strcasecmp(key, "pixelshape") == 0 )
1547 pixelshape = val;
1548 } */
1549 item = strtok_r(NULL, "&,", &saveptr);
1551 free(path);
1553 if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE )
1555 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with a resized image!\n");
1556 Send406(h);
1557 goto resized_error;
1560 DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %lld [%s]\n", id, file_path);
1562 /* Figure out the best destination resolution we can use */
1563 dstw = width;
1564 dsth = ((((width<<10)/srcw)*srch)>>10);
1565 if( dsth > height )
1567 dsth = height;
1568 dstw = (((height<<10)/srch) * srcw>>10);
1571 if( dstw <= 640 && dsth <= 480 )
1572 strcpy(dlna_pn, "SM");
1573 else if( dstw <= 1024 && dsth <= 768 )
1574 strcpy(dlna_pn, "MED");
1575 else
1576 strcpy(dlna_pn, "LRG");
1578 if( srcw>>3 >= dstw && srch>>3 >= dsth)
1579 scale = 8;
1580 else if( srcw>>2 >= dstw && srch>>2 >= dsth )
1581 scale = 4;
1582 else if( srcw>>1 >= dstw && srch>>1 >= dsth )
1583 scale = 2;
1585 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1587 str.data = header;
1588 str.size = sizeof(header);
1589 str.off = 0;
1591 strcatf(&str, "HTTP/1.1 200 OK\r\n"
1592 "Content-Type: image/jpeg\r\n"
1593 "Connection: close\r\n"
1594 "Date: %s\r\n"
1595 "EXT:\r\n"
1596 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1597 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_%s;DLNA.ORG_CI=1\r\n"
1598 "Server: " MINIDLNA_SERVER_STRING "\r\n",
1599 date, dlna_pn);
1600 if( h->reqflags & FLAG_XFERINTERACTIVE )
1602 strcatf(&str, "transferMode.dlna.org: Interactive\r\n");
1604 else if( h->reqflags & FLAG_XFERBACKGROUND )
1606 strcatf(&str, "transferMode.dlna.org: Background\r\n");
1609 /* Resizing from a thumbnail is much faster than from a large image */
1610 #ifdef __sparc__
1611 tn = result[5];
1612 if( dstw <= 160 && dsth <= 120 && atoi(tn) )
1614 l = exif_loader_new();
1615 exif_loader_write_file(l, file_path);
1616 ed = exif_loader_get_data(l);
1617 exif_loader_unref(l);
1619 if( !ed || !ed->size )
1621 if( ed )
1622 exif_data_unref(ed);
1623 DPRINTF(E_WARN, L_HTTP, "Unable to access image thumbnail!\n");
1624 Send500(h);
1625 goto resized_error;
1627 imsrc = image_new_from_jpeg(NULL, 0, (char *)ed->data, ed->size, 1);
1628 exif_data_unref(ed);
1630 else
1631 #endif
1632 if( strcmp(h->HttpVer, "HTTP/1.0") == 0 )
1634 chunked = 0;
1635 imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale);
1637 else
1639 chunked = 1;
1640 strcatf(&str, "Transfer-Encoding: chunked\r\n\r\n");
1643 if( !chunked )
1645 if( !imsrc )
1647 DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path);
1648 Send500(h);
1649 goto resized_error;
1652 imdst = image_resize(imsrc, dstw, dsth);
1653 data = image_save_to_jpeg_buf(imdst, &size);
1655 strcatf(&str, "Content-Length: %d\r\n\r\n", size);
1658 if( (send_data(h, str.data, str.off, 0) == 0) && (h->req_command != EHead) )
1660 if( chunked )
1662 imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale);
1663 if( !imsrc )
1665 DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path);
1666 Send500(h);
1667 goto resized_error;
1669 imdst = image_resize(imsrc, dstw, dsth);
1670 data = image_save_to_jpeg_buf(imdst, &size);
1672 ret = sprintf(str_buf, "%x\r\n", size);
1673 send_data(h, str_buf, ret, MSG_MORE);
1674 send_data(h, (char *)data, size, MSG_MORE);
1675 send_data(h, "\r\n0\r\n\r\n", 7, 0);
1677 else
1679 send_data(h, (char *)data, size, 0);
1682 DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path);
1683 if( imsrc )
1684 image_free(imsrc);
1685 if( imdst )
1686 image_free(imdst);
1687 resized_error:
1688 sqlite3_free_table(result);
1689 #if USE_FORK
1690 if( !newpid )
1691 _exit(0);
1692 #endif
1695 void
1696 SendResp_dlnafile(struct upnphttp * h, char * object)
1698 char header[1024];
1699 struct string_s str;
1700 char sql_buf[256];
1701 char **result;
1702 int rows, ret;
1703 char date[30];
1704 time_t curtime = time(NULL);
1705 off_t total, offset, size;
1706 sqlite_int64 id;
1707 int sendfh;
1708 static struct { sqlite_int64 id;
1709 enum client_types client;
1710 char path[PATH_MAX];
1711 char mime[32];
1712 char dlna[96];
1713 } last_file = { 0, 0 };
1714 #if USE_FORK
1715 pid_t newpid = 0;
1716 #endif
1718 id = strtoll(object, NULL, 10);
1719 if( id != last_file.id || h->req_client != last_file.client )
1721 sprintf(sql_buf, "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", id);
1722 ret = sql_get_table(db, sql_buf, &result, &rows, NULL);
1723 if( (ret != SQLITE_OK) )
1725 DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id);
1726 Send500(h);
1727 return;
1729 if( !rows )
1731 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
1732 sqlite3_free_table(result);
1733 Send404(h);
1734 return;
1736 /* Cache the result */
1737 last_file.id = id;
1738 last_file.client = h->req_client;
1739 strncpy(last_file.path, result[3], sizeof(last_file.path)-1);
1740 if( result[4] )
1742 strncpy(last_file.mime, result[4], sizeof(last_file.mime)-1);
1743 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
1744 if( h->reqflags & FLAG_SAMSUNG )
1746 if( strcmp(last_file.mime+6, "x-matroska") == 0 )
1747 strcpy(last_file.mime+8, "mkv");
1748 /* Samsung TV's such as the A750 can natively support many
1749 Xvid/DivX AVI's however, the DLNA server needs the
1750 mime type to say video/mpeg */
1751 else if( h->req_client == ESamsungSeriesA &&
1752 strcmp(last_file.mime+6, "x-msvideo") == 0 )
1753 strcpy(last_file.mime+6, "mpeg");
1755 /* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */
1756 else if( h->req_client == ESonyBDP )
1758 if( strcmp(last_file.mime+6, "x-matroska") == 0 ||
1759 strcmp(last_file.mime+6, "mpeg") == 0 )
1760 strcpy(last_file.mime+6, "divx");
1763 else
1765 last_file.mime[0] = '\0';
1767 if( result[5] )
1768 snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s", result[5]);
1769 else if( h->reqflags & FLAG_DLNA )
1770 strcpy(last_file.dlna, dlna_no_conv);
1771 else
1772 last_file.dlna[0] = '\0';
1773 sqlite3_free_table(result);
1775 #if USE_FORK
1776 newpid = fork();
1777 if( newpid )
1778 return;
1779 #endif
1781 DPRINTF(E_INFO, L_HTTP, "Serving DetailID: %lld [%s]\n", id, last_file.path);
1783 if( h->reqflags & FLAG_XFERSTREAMING )
1785 if( strncmp(last_file.mime, "image", 5) == 0 )
1787 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1788 Send406(h);
1789 goto error;
1792 else if( h->reqflags & FLAG_XFERINTERACTIVE )
1794 if( h->reqflags & FLAG_REALTIMEINFO )
1796 DPRINTF(E_WARN, L_HTTP, "Bad realTimeInfo flag with Interactive request!\n");
1797 Send400(h);
1798 goto error;
1800 if( strncmp(last_file.mime, "image", 5) != 0 )
1802 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Interactive without an image!\n");
1803 /* Samsung TVs (well, at least the A950) do this for some reason,
1804 * and I don't see them fixing this bug any time soon. */
1805 if( !(h->reqflags & FLAG_SAMSUNG) || GETFLAG(DLNA_STRICT_MASK) )
1807 Send406(h);
1808 goto error;
1813 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1814 offset = h->req_RangeStart;
1815 sendfh = open(last_file.path, O_RDONLY);
1816 if( sendfh < 0 ) {
1817 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", last_file.path);
1818 Send404(h);
1819 goto error;
1821 size = lseek(sendfh, 0, SEEK_END);
1822 lseek(sendfh, 0, SEEK_SET);
1824 str.data = header;
1825 str.size = sizeof(header);
1826 str.off = 0;
1828 strcatf(&str, "HTTP/1.1 20%c OK\r\n"
1829 "Content-Type: %s\r\n",
1830 (h->reqflags & FLAG_RANGE ? '6' : '0'),
1831 last_file.mime);
1832 if( h->reqflags & FLAG_RANGE )
1834 if( !h->req_RangeEnd || h->req_RangeEnd == size )
1836 h->req_RangeEnd = size - 1;
1838 if( (h->req_RangeStart > h->req_RangeEnd) || (h->req_RangeStart < 0) )
1840 DPRINTF(E_WARN, L_HTTP, "Specified range was invalid!\n");
1841 Send400(h);
1842 close(sendfh);
1843 goto error;
1845 if( h->req_RangeEnd >= size )
1847 DPRINTF(E_WARN, L_HTTP, "Specified range was outside file boundaries!\n");
1848 Send416(h);
1849 close(sendfh);
1850 goto error;
1853 total = h->req_RangeEnd - h->req_RangeStart + 1;
1854 strcatf(&str, "Content-Length: %jd\r\n"
1855 "Content-Range: bytes %jd-%jd/%jd\r\n",
1856 (intmax_t)total, (intmax_t)h->req_RangeStart,
1857 (intmax_t)h->req_RangeEnd, (intmax_t)size);
1859 else
1861 h->req_RangeEnd = size - 1;
1862 total = size;
1863 strcatf(&str, "Content-Length: %jd\r\n", (intmax_t)total);
1866 if( h->reqflags & FLAG_XFERSTREAMING )
1868 strcatf(&str, "transferMode.dlna.org: Streaming\r\n");
1870 else if( h->reqflags & FLAG_XFERBACKGROUND )
1872 if( strncmp(last_file.mime, "image", 5) == 0 )
1873 strcatf(&str, "transferMode.dlna.org: Background\r\n");
1875 else //if( h->reqflags & FLAG_XFERINTERACTIVE )
1877 if( (strncmp(last_file.mime, "video", 5) == 0) ||
1878 (strncmp(last_file.mime, "audio", 5) == 0) )
1880 strcatf(&str, "transferMode.dlna.org: Streaming\r\n");
1882 else
1884 strcatf(&str, "transferMode.dlna.org: Interactive\r\n");
1888 if( h->reqflags & FLAG_CAPTION )
1890 if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%lld'", id) > 0 )
1892 strcatf(&str, "CaptionInfo.sec: http://%s:%d/Captions/%lld.srt\r\n",
1893 lan_addr[h->iface].str, runtime_vars.port, id);
1897 strcatf(&str, "Accept-Ranges: bytes\r\n"
1898 "Connection: close\r\n"
1899 "Date: %s\r\n"
1900 "EXT:\r\n"
1901 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1902 "contentFeatures.dlna.org: %s\r\n"
1903 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
1904 date, last_file.dlna);
1906 //DEBUG DPRINTF(E_DEBUG, L_HTTP, "RESPONSE: %s\n", str.data);
1907 if( send_data(h, str.data, str.off, MSG_MORE) == 0 )
1909 if( h->req_command != EHead )
1910 send_file(h, sendfh, offset, h->req_RangeEnd);
1912 close(sendfh);
1914 error:
1915 #if USE_FORK
1916 if( !newpid )
1917 _exit(0);
1918 #endif
1919 return;