MiniDLNA: cvs 2011-02-14
[tomato.git] / release / src / router / minidlna / upnphttp.c
blobecdb92559ed99b03326f20957ca05b3175f59b83
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)
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 DPRINTF(E_DEBUG, L_HTTP, "Client found in cache. [type %d/entry %d]\n", 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_RangeEnd = atoll(index(p+6, '-')+1);
259 h->req_RangeStart = atoll(p+6);
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 h->reqflags |= FLAG_HOST;
268 else if(strncasecmp(line, "User-Agent", 10)==0)
270 /* Skip client detection if we already detected it. */
271 if( h->req_client )
272 goto next_header;
273 p = colon + 1;
274 while(isspace(*p))
275 p++;
276 if(strncasecmp(p, "Xbox/", 5)==0)
278 h->req_client = EXbox;
279 h->reqflags |= FLAG_MIME_AVI_AVI;
280 h->reqflags |= FLAG_MS_PFS;
282 else if(strncmp(p, "PLAYSTATION", 11)==0)
284 h->req_client = EPS3;
285 h->reqflags |= FLAG_DLNA;
286 h->reqflags |= FLAG_MIME_AVI_DIVX;
288 else if(strncmp(p, "SamsungWiselinkPro", 18)==0 ||
289 strncmp(p, "SEC_HHP_", 8)==0)
291 h->req_client = ESamsungTV;
292 h->reqflags |= FLAG_DLNA;
293 h->reqflags |= FLAG_NO_RESIZE;
294 //h->reqflags |= FLAG_MIME_AVI_DIVX;
296 else if(strstrc(p, "bridgeCo-DMP/3", '\r'))
298 h->req_client = EDenonReceiver;
299 h->reqflags |= FLAG_DLNA;
301 else if(strstrc(p, "fbxupnpav/", '\r'))
303 h->req_client = EFreeBox;
305 else if(strncmp(p, "SMP8634", 7)==0)
307 h->req_client = EPopcornHour;
308 h->reqflags |= FLAG_MIME_FLAC_FLAC;
310 else if(strstrc(p, "Microsoft-IPTV-Client", '\r'))
312 h->req_client = EMediaRoom;
313 h->reqflags |= FLAG_MS_PFS;
315 else if(strstrc(p, "DLNADOC/1.50", '\r'))
317 h->req_client = EStandardDLNA150;
318 h->reqflags |= FLAG_DLNA;
319 h->reqflags |= FLAG_MIME_AVI_AVI;
322 else if(strncasecmp(line, "X-AV-Client-Info", 16)==0)
324 /* Skip client detection if we already detected it. */
325 if( h->req_client && h->req_client < EStandardDLNA150 )
326 goto next_header;
327 p = colon + 1;
328 while(isspace(*p))
329 p++;
330 if(strstrc(p, "PLAYSTATION 3", '\r'))
332 h->req_client = EPS3;
333 h->reqflags |= FLAG_DLNA;
334 h->reqflags |= FLAG_MIME_AVI_DIVX;
336 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Blu-ray Disc Player"; mv="2.0" */
337 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BLU-RAY HOME THEATRE SYSTEM"; mv="2.0"; */
338 /* Sony SMP-100 needs the same treatment as their BDP-S370 */
339 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Media Player"; mv="2.0" */
340 else if(strstrc(p, "Blu-ray Disc Player", '\r') ||
341 strstrc(p, "BLU-RAY HOME THEATRE SYSTEM", '\r') ||
342 strstrc(p, "Media Player", '\r'))
344 h->req_client = ESonyBDP;
345 h->reqflags |= FLAG_DLNA;
347 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BRAVIA KDL-40EX503"; mv="1.7"; */
348 else if(strstrc(p, "BRAVIA", '\r'))
350 h->req_client = ESonyBravia;
351 h->reqflags |= FLAG_DLNA;
354 else if(strncasecmp(line, "Transfer-Encoding", 17)==0)
356 p = colon + 1;
357 while(isspace(*p))
358 p++;
359 if(strncasecmp(p, "chunked", 7)==0)
361 h->reqflags |= FLAG_CHUNKED;
364 else if(strncasecmp(line, "getcontentFeatures.dlna.org", 27)==0)
366 p = colon + 1;
367 while(isspace(*p))
368 p++;
369 if( (*p != '1') || !isspace(p[1]) )
370 h->reqflags |= FLAG_INVALID_REQ;
372 else if(strncasecmp(line, "TimeSeekRange.dlna.org", 22)==0)
374 h->reqflags |= FLAG_TIMESEEK;
376 else if(strncasecmp(line, "PlaySpeed.dlna.org", 18)==0)
378 h->reqflags |= FLAG_PLAYSPEED;
380 else if(strncasecmp(line, "realTimeInfo.dlna.org", 21)==0)
382 h->reqflags |= FLAG_REALTIMEINFO;
384 else if(strncasecmp(line, "transferMode.dlna.org", 21)==0)
386 p = colon + 1;
387 while(isspace(*p))
388 p++;
389 if(strncasecmp(p, "Streaming", 9)==0)
391 h->reqflags |= FLAG_XFERSTREAMING;
393 if(strncasecmp(p, "Interactive", 11)==0)
395 h->reqflags |= FLAG_XFERINTERACTIVE;
397 if(strncasecmp(p, "Background", 10)==0)
399 h->reqflags |= FLAG_XFERBACKGROUND;
402 else if(strncasecmp(line, "getCaptionInfo.sec", 18)==0)
404 h->reqflags |= FLAG_CAPTION;
407 next_header:
408 while(!(line[0] == '\r' && line[1] == '\n'))
409 line++;
410 line += 2;
412 if( h->reqflags & FLAG_CHUNKED )
414 char *endptr;
415 h->req_chunklen = -1;
416 if( h->req_buflen <= h->req_contentoff )
417 return;
418 while( (line < (h->req_buf + h->req_buflen)) &&
419 (h->req_chunklen = strtol(line, &endptr, 16)) &&
420 (endptr != line) )
422 while(!(endptr[0] == '\r' && endptr[1] == '\n'))
424 endptr++;
426 line = endptr+h->req_chunklen+2;
429 if( endptr == line )
431 h->req_chunklen = -1;
432 return;
435 /* If the client type wasn't found, search the cache.
436 * This is done because a lot of clients like to send a
437 * different User-Agent with different types of requests. */
438 n = SearchClientCache(h->clientaddr);
439 if( h->req_client )
441 /* Add this client to the cache if it's not there already. */
442 if( n < 0 )
444 for( n=0; n<CLIENT_CACHE_SLOTS; n++ )
446 if( clients[n].addr.s_addr )
447 continue;
448 get_remote_mac(h->clientaddr, clients[n].mac);
449 clients[n].addr = h->clientaddr;
450 DPRINTF(E_DEBUG, L_HTTP, "Added client [%d/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n",
451 h->req_client, inet_ntoa(clients[n].addr),
452 clients[n].mac[0], clients[n].mac[1], clients[n].mac[2],
453 clients[n].mac[3], clients[n].mac[4], clients[n].mac[5], n);
454 break;
457 else if( (n < EStandardDLNA150) && (h->req_client == EStandardDLNA150) )
459 /* If we know the client and our new detection is generic, use our cached info */
460 h->reqflags |= clients[n].flags;
461 h->req_client = clients[n].type;
462 return;
464 clients[n].type = h->req_client;
465 clients[n].flags = h->reqflags & 0xFFF00000;
466 clients[n].age = time(NULL);
468 else if( n >= 0 )
470 h->reqflags |= clients[n].flags;
471 h->req_client = clients[n].type;
475 /* very minimalistic 400 error message */
476 static void
477 Send400(struct upnphttp * h)
479 static const char body400[] =
480 "<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>"
481 "<BODY><H1>Bad Request</H1>The request is invalid"
482 " for this HTTP version.</BODY></HTML>\r\n";
483 h->respflags = FLAG_HTML;
484 BuildResp2_upnphttp(h, 400, "Bad Request",
485 body400, sizeof(body400) - 1);
486 SendResp_upnphttp(h);
487 CloseSocket_upnphttp(h);
490 /* very minimalistic 404 error message */
491 static void
492 Send404(struct upnphttp * h)
494 static const char body404[] =
495 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
496 "<BODY><H1>Not Found</H1>The requested URL was not found"
497 " on this server.</BODY></HTML>\r\n";
498 h->respflags = FLAG_HTML;
499 BuildResp2_upnphttp(h, 404, "Not Found",
500 body404, sizeof(body404) - 1);
501 SendResp_upnphttp(h);
502 CloseSocket_upnphttp(h);
505 /* very minimalistic 406 error message */
506 static void
507 Send406(struct upnphttp * h)
509 static const char body406[] =
510 "<HTML><HEAD><TITLE>406 Not Acceptable</TITLE></HEAD>"
511 "<BODY><H1>Not Acceptable</H1>An unsupported operation"
512 " was requested.</BODY></HTML>\r\n";
513 h->respflags = FLAG_HTML;
514 BuildResp2_upnphttp(h, 406, "Not Acceptable",
515 body406, sizeof(body406) - 1);
516 SendResp_upnphttp(h);
517 CloseSocket_upnphttp(h);
520 /* very minimalistic 416 error message */
521 static void
522 Send416(struct upnphttp * h)
524 static const char body416[] =
525 "<HTML><HEAD><TITLE>416 Requested Range Not Satisfiable</TITLE></HEAD>"
526 "<BODY><H1>Requested Range Not Satisfiable</H1>The requested range"
527 " was outside the file's size.</BODY></HTML>\r\n";
528 h->respflags = FLAG_HTML;
529 BuildResp2_upnphttp(h, 416, "Requested Range Not Satisfiable",
530 body416, sizeof(body416) - 1);
531 SendResp_upnphttp(h);
532 CloseSocket_upnphttp(h);
535 /* very minimalistic 500 error message */
536 static void
537 Send500(struct upnphttp * h)
539 static const char body500[] =
540 "<HTML><HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>"
541 "<BODY><H1>Internal Server Error</H1>Server encountered "
542 "and Internal Error.</BODY></HTML>\r\n";
543 h->respflags = FLAG_HTML;
544 BuildResp2_upnphttp(h, 500, "Internal Server Errror",
545 body500, sizeof(body500) - 1);
546 SendResp_upnphttp(h);
547 CloseSocket_upnphttp(h);
550 /* very minimalistic 501 error message */
551 void
552 Send501(struct upnphttp * h)
554 static const char body501[] =
555 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
556 "<BODY><H1>Not Implemented</H1>The HTTP Method "
557 "is not implemented by this server.</BODY></HTML>\r\n";
558 h->respflags = FLAG_HTML;
559 BuildResp2_upnphttp(h, 501, "Not Implemented",
560 body501, sizeof(body501) - 1);
561 SendResp_upnphttp(h);
562 CloseSocket_upnphttp(h);
565 static const char *
566 findendheaders(const char * s, int len)
568 while(len-- > 0)
570 if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n')
571 return s;
572 s++;
574 return NULL;
577 /* Sends the description generated by the parameter */
578 static void
579 sendXMLdesc(struct upnphttp * h, char * (f)(int *))
581 char * desc;
582 int len;
583 desc = f(&len);
584 if(!desc)
586 DPRINTF(E_ERROR, L_HTTP, "Failed to generate XML description\n");
587 Send500(h);
588 return;
590 BuildResp_upnphttp(h, desc, len);
591 SendResp_upnphttp(h);
592 CloseSocket_upnphttp(h);
593 free(desc);
596 /* ProcessHTTPPOST_upnphttp()
597 * executes the SOAP query if it is possible */
598 static void
599 ProcessHTTPPOST_upnphttp(struct upnphttp * h)
601 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
603 if(h->req_soapAction)
605 /* we can process the request */
606 DPRINTF(E_DEBUG, L_HTTP, "SOAPAction: %.*s\n", h->req_soapActionLen, h->req_soapAction);
607 ExecuteSoapAction(h,
608 h->req_soapAction,
609 h->req_soapActionLen);
611 else
613 static const char err400str[] =
614 "<html><body>Bad request</body></html>";
615 DPRINTF(E_WARN, L_HTTP, "No SOAPAction in HTTP headers");
616 h->respflags = FLAG_HTML;
617 BuildResp2_upnphttp(h, 400, "Bad Request",
618 err400str, sizeof(err400str) - 1);
619 SendResp_upnphttp(h);
620 CloseSocket_upnphttp(h);
623 else
625 /* waiting for remaining data */
626 h->state = 1;
630 static void
631 ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path)
633 const char * sid;
634 DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPSubscribe %s\n", path);
635 DPRINTF(E_DEBUG, L_HTTP, "Callback '%.*s' Timeout=%d\n",
636 h->req_CallbackLen, h->req_Callback, h->req_Timeout);
637 DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID);
638 if(!h->req_Callback && !h->req_SID) {
639 /* Missing or invalid CALLBACK : 412 Precondition Failed.
640 * If CALLBACK header is missing or does not contain a valid HTTP URL,
641 * the publisher must respond with HTTP error 412 Precondition Failed*/
642 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
643 SendResp_upnphttp(h);
644 CloseSocket_upnphttp(h);
645 } else {
646 /* - add to the subscriber list
647 * - respond HTTP/x.x 200 OK
648 * - Send the initial event message */
649 /* Server:, SID:; Timeout: Second-(xx|infinite) */
650 if(h->req_Callback) {
651 sid = upnpevents_addSubscriber(path, h->req_Callback,
652 h->req_CallbackLen, h->req_Timeout);
653 h->respflags = FLAG_TIMEOUT;
654 if(sid) {
655 DPRINTF(E_DEBUG, L_HTTP, "generated sid=%s\n", sid);
656 h->respflags |= FLAG_SID;
657 h->req_SID = sid;
658 h->req_SIDLen = strlen(sid);
660 BuildResp_upnphttp(h, 0, 0);
661 } else {
662 /* subscription renew */
663 /* Invalid SID
664 412 Precondition Failed. If a SID does not correspond to a known,
665 un-expired subscription, the publisher must respond
666 with HTTP error 412 Precondition Failed. */
667 if(renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0) {
668 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
669 } else {
670 /* A DLNA device must enforce a 5 minute timeout */
671 h->respflags = FLAG_TIMEOUT;
672 h->req_Timeout = 300;
673 h->respflags |= FLAG_SID;
674 BuildResp_upnphttp(h, 0, 0);
677 SendResp_upnphttp(h);
678 CloseSocket_upnphttp(h);
682 static void
683 ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path)
685 DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPUnSubscribe %s\n", path);
686 DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID);
687 /* Remove from the list */
688 if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0) {
689 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
690 } else {
691 BuildResp_upnphttp(h, 0, 0);
693 SendResp_upnphttp(h);
694 CloseSocket_upnphttp(h);
697 /* Parse and process Http Query
698 * called once all the HTTP headers have been received. */
699 static void
700 ProcessHttpQuery_upnphttp(struct upnphttp * h)
702 char HttpCommand[16];
703 char HttpUrl[512];
704 char * HttpVer;
705 char * p;
706 int i;
707 p = h->req_buf;
708 if(!p)
709 return;
710 for(i = 0; i<15 && *p != ' ' && *p != '\r'; i++)
711 HttpCommand[i] = *(p++);
712 HttpCommand[i] = '\0';
713 while(*p==' ')
714 p++;
715 if(strncmp(p, "http://", 7) == 0)
717 p = p+7;
718 while(*p!='/')
719 p++;
721 for(i = 0; i<511 && *p != ' ' && *p != '\r'; i++)
722 HttpUrl[i] = *(p++);
723 HttpUrl[i] = '\0';
724 while(*p==' ')
725 p++;
726 HttpVer = h->HttpVer;
727 for(i = 0; i<15 && *p != '\r'; i++)
728 HttpVer[i] = *(p++);
729 HttpVer[i] = '\0';
730 /*DPRINTF(E_INFO, L_HTTP, "HTTP REQUEST : %s %s (%s)\n",
731 HttpCommand, HttpUrl, HttpVer);*/
732 ParseHttpHeaders(h);
734 /* see if we need to wait for remaining data */
735 if( (h->reqflags & FLAG_CHUNKED) )
737 if( h->req_chunklen )
739 h->state = 2;
740 return;
742 char *chunkstart, *chunk, *endptr, *endbuf;
743 chunk = endbuf = chunkstart = h->req_buf + h->req_contentoff;
745 while( (h->req_chunklen = strtol(chunk, &endptr, 16)) && (endptr != chunk) )
747 while(!(endptr[0] == '\r' && endptr[1] == '\n'))
749 endptr++;
751 endptr += 2;
753 memmove(endbuf, endptr, h->req_chunklen);
755 endbuf += h->req_chunklen;
756 chunk = endptr + h->req_chunklen;
758 h->req_contentlen = endbuf - chunkstart;
759 h->req_buflen = endbuf - h->req_buf;
760 h->state = 100;
763 DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf);
764 if(strcmp("POST", HttpCommand) == 0)
766 h->req_command = EPost;
767 ProcessHTTPPOST_upnphttp(h);
769 else if((strcmp("GET", HttpCommand) == 0) || (strcmp("HEAD", HttpCommand) == 0))
771 if( ((strcmp(h->HttpVer, "HTTP/1.1")==0) && !(h->reqflags & FLAG_HOST)) || (h->reqflags & FLAG_INVALID_REQ) )
773 DPRINTF(E_WARN, L_HTTP, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n");
774 Send400(h);
775 return;
777 #if 1 /* 7.3.33.4 */
778 else if( ((h->reqflags & FLAG_TIMESEEK) || (h->reqflags & FLAG_PLAYSPEED)) &&
779 !(h->reqflags & FLAG_RANGE) )
781 DPRINTF(E_WARN, L_HTTP, "DLNA %s requested, responding ERROR 406\n",
782 h->reqflags&FLAG_TIMESEEK ? "TimeSeek" : "PlaySpeed");
783 Send406(h);
784 return;
786 #endif
787 else if(strcmp("GET", HttpCommand) == 0)
789 h->req_command = EGet;
791 else
793 h->req_command = EHead;
795 if(strcmp(ROOTDESC_PATH, HttpUrl) == 0)
797 /* If it's a Xbox360, we might need a special friendly_name to be recognized */
798 if( (h->req_client == EXbox) && !strchr(friendly_name, ':') )
800 strncat(friendly_name, ": 1", FRIENDLYNAME_MAX_LEN-4);
801 sendXMLdesc(h, genRootDesc);
802 friendly_name[strlen(friendly_name)-3] = '\0';
804 else
806 sendXMLdesc(h, genRootDesc);
809 else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0)
811 sendXMLdesc(h, genContentDirectory);
813 else if(strcmp(CONNECTIONMGR_PATH, HttpUrl) == 0)
815 sendXMLdesc(h, genConnectionManager);
817 else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH, HttpUrl) == 0)
819 sendXMLdesc(h, genX_MS_MediaReceiverRegistrar);
821 else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0)
823 SendResp_dlnafile(h, HttpUrl+12);
824 CloseSocket_upnphttp(h);
826 else if(strncmp(HttpUrl, "/Thumbnails/", 12) == 0)
828 SendResp_thumbnail(h, HttpUrl+12);
830 else if(strncmp(HttpUrl, "/AlbumArt/", 10) == 0)
832 SendResp_albumArt(h, HttpUrl+10);
833 CloseSocket_upnphttp(h);
835 #ifdef TIVO_SUPPORT
836 else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0)
838 if( GETFLAG(TIVO_MASK) )
840 if( *(HttpUrl+12) == '?' )
842 ProcessTiVoCommand(h, HttpUrl+13);
844 else
846 printf("Invalid TiVo request! %s\n", HttpUrl+12);
847 Send404(h);
850 else
852 printf("TiVo request with out TiVo support enabled! %s\n", HttpUrl+12);
853 Send404(h);
856 #endif
857 else if(strncmp(HttpUrl, "/Resized/", 9) == 0)
859 SendResp_resizedimg(h, HttpUrl+9);
860 CloseSocket_upnphttp(h);
862 else if(strncmp(HttpUrl, "/icons/", 7) == 0)
864 SendResp_icon(h, HttpUrl+7);
865 CloseSocket_upnphttp(h);
867 else if(strncmp(HttpUrl, "/Captions/", 10) == 0)
869 SendResp_caption(h, HttpUrl+10);
870 CloseSocket_upnphttp(h);
872 else
874 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", HttpUrl);
875 Send404(h);
878 else if(strcmp("SUBSCRIBE", HttpCommand) == 0)
880 h->req_command = ESubscribe;
881 ProcessHTTPSubscribe_upnphttp(h, HttpUrl);
883 else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0)
885 h->req_command = EUnSubscribe;
886 ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl);
888 else
890 DPRINTF(E_WARN, L_HTTP, "Unsupported HTTP Command %s\n", HttpCommand);
891 Send501(h);
896 void
897 Process_upnphttp(struct upnphttp * h)
899 char buf[2048];
900 int n;
901 if(!h)
902 return;
903 switch(h->state)
905 case 0:
906 n = recv(h->socket, buf, 2048, 0);
907 if(n<0)
909 DPRINTF(E_ERROR, L_HTTP, "recv (state0): %s\n", strerror(errno));
910 h->state = 100;
912 else if(n==0)
914 DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n");
915 h->state = 100;
917 else
919 const char * endheaders;
920 /* if 1st arg of realloc() is null,
921 * realloc behaves the same as malloc() */
922 h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen + 1);
923 memcpy(h->req_buf + h->req_buflen, buf, n);
924 h->req_buflen += n;
925 h->req_buf[h->req_buflen] = '\0';
926 /* search for the string "\r\n\r\n" */
927 endheaders = findendheaders(h->req_buf, h->req_buflen);
928 if(endheaders)
930 h->req_contentoff = endheaders - h->req_buf + 4;
931 h->req_contentlen = h->req_buflen - h->req_contentoff;
932 ProcessHttpQuery_upnphttp(h);
935 break;
936 case 1:
937 case 2:
938 n = recv(h->socket, buf, 2048, 0);
939 if(n<0)
941 DPRINTF(E_ERROR, L_HTTP, "recv (state%d): %s\n", h->state, strerror(errno));
942 h->state = 100;
944 else if(n==0)
946 DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n");
947 h->state = 100;
949 else
951 /*fwrite(buf, 1, n, stdout);*/ /* debug */
952 h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen);
953 memcpy(h->req_buf + h->req_buflen, buf, n);
954 h->req_buflen += n;
955 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
957 /* Need the struct to point to the realloc'd memory locations */
958 if( h->state == 1 )
960 ParseHttpHeaders(h);
961 ProcessHTTPPOST_upnphttp(h);
963 else if( h->state == 2 )
965 ProcessHttpQuery_upnphttp(h);
969 break;
970 default:
971 DPRINTF(E_WARN, L_HTTP, "Unexpected state: %d\n", h->state);
975 static const char httpresphead[] =
976 "%s %d %s\r\n"
977 "Content-Type: %s\r\n"
978 "Connection: close\r\n"
979 "Content-Length: %d\r\n"
980 "Server: " MINIDLNA_SERVER_STRING "\r\n"
981 // "Accept-Ranges: bytes\r\n"
982 ; /*"\r\n";*/
984 "<?xml version=\"1.0\"?>\n"
985 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
986 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
987 "<s:Body>"
989 "</s:Body>"
990 "</s:Envelope>";
992 /* with response code and response message
993 * also allocate enough memory */
995 void
996 BuildHeader_upnphttp(struct upnphttp * h, int respcode,
997 const char * respmsg,
998 int bodylen)
1000 int templen;
1001 if(!h->res_buf)
1003 templen = sizeof(httpresphead) + 192 + bodylen;
1004 h->res_buf = (char *)malloc(templen);
1005 h->res_buf_alloclen = templen;
1007 h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen,
1008 //httpresphead, h->HttpVer,
1009 httpresphead, "HTTP/1.1",
1010 respcode, respmsg,
1011 (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"",
1012 bodylen);
1013 /* Additional headers */
1014 if(h->respflags & FLAG_TIMEOUT) {
1015 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1016 h->res_buf_alloclen - h->res_buflen,
1017 "Timeout: Second-");
1018 if(h->req_Timeout) {
1019 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1020 h->res_buf_alloclen - h->res_buflen,
1021 "%d\r\n", h->req_Timeout);
1022 } else {
1023 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1024 h->res_buf_alloclen - h->res_buflen,
1025 "300\r\n");
1026 //JM DLNA must force to 300 - "infinite\r\n");
1029 if(h->respflags & FLAG_SID) {
1030 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1031 h->res_buf_alloclen - h->res_buflen,
1032 "SID: %.*s\r\n", h->req_SIDLen, h->req_SID);
1034 #if 0 // DLNA
1035 char szTime[30];
1036 time_t curtime = time(NULL);
1037 strftime(szTime, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1038 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1039 h->res_buf_alloclen - h->res_buflen,
1040 "Date: %s\r\n", szTime);
1041 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1042 h->res_buf_alloclen - h->res_buflen,
1043 "contentFeatures.dlna.org: \r\n");
1044 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1045 h->res_buf_alloclen - h->res_buflen,
1046 "EXT:\r\n");
1047 #endif
1048 h->res_buf[h->res_buflen++] = '\r';
1049 h->res_buf[h->res_buflen++] = '\n';
1050 if(h->res_buf_alloclen < (h->res_buflen + bodylen))
1052 h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen));
1053 h->res_buf_alloclen = h->res_buflen + bodylen;
1057 void
1058 BuildResp2_upnphttp(struct upnphttp * h, int respcode,
1059 const char * respmsg,
1060 const char * body, int bodylen)
1062 BuildHeader_upnphttp(h, respcode, respmsg, bodylen);
1063 if( h->req_command == EHead )
1064 return;
1065 if(body)
1066 memcpy(h->res_buf + h->res_buflen, body, bodylen);
1067 h->res_buflen += bodylen;
1070 /* responding 200 OK ! */
1071 void
1072 BuildResp_upnphttp(struct upnphttp * h,
1073 const char * body, int bodylen)
1075 BuildResp2_upnphttp(h, 200, "OK", body, bodylen);
1078 void
1079 SendResp_upnphttp(struct upnphttp * h)
1081 int n;
1082 DPRINTF(E_DEBUG, L_HTTP, "HTTP RESPONSE: %.*s\n", h->res_buflen, h->res_buf);
1083 n = send(h->socket, h->res_buf, h->res_buflen, 0);
1084 if(n<0)
1086 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno));
1088 else if(n < h->res_buflen)
1090 /* TODO : handle correctly this case */
1091 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
1092 n, h->res_buflen);
1097 send_data(struct upnphttp * h, char * header, size_t size, int flags)
1099 int n;
1101 n = send(h->socket, header, size, flags);
1102 if(n<0)
1104 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno));
1106 else if(n < h->res_buflen)
1108 /* TODO : handle correctly this case */
1109 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
1110 n, h->res_buflen);
1112 else
1114 return 0;
1116 return 1;
1119 void
1120 send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset)
1122 off_t send_size;
1123 off_t ret;
1124 char *buf = NULL;
1125 int try_sendfile = 1;
1127 while( offset < end_offset )
1129 if( try_sendfile )
1131 send_size = ( ((end_offset - offset) < MAX_BUFFER_SIZE) ? (end_offset - offset + 1) : MAX_BUFFER_SIZE);
1132 ret = sendfile(h->socket, sendfd, &offset, send_size);
1133 if( ret == -1 )
1135 DPRINTF(E_DEBUG, L_HTTP, "sendfile error :: error no. %d [%s]\n", errno, strerror(errno));
1136 /* If sendfile isn't supported on the filesystem, don't bother trying to use it again. */
1137 if( errno == EOVERFLOW || errno == EINVAL )
1138 try_sendfile = 0;
1139 else if( errno != EAGAIN )
1140 break;
1142 else
1144 //DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset);
1145 continue;
1148 /* Fall back to regular I/O */
1149 if( !buf )
1150 buf = malloc(MIN_BUFFER_SIZE);
1151 send_size = ( ((end_offset - offset) < MIN_BUFFER_SIZE) ? (end_offset - offset + 1) : MIN_BUFFER_SIZE);
1152 lseek(sendfd, offset, SEEK_SET);
1153 ret = read(sendfd, buf, send_size);
1154 if( ret == -1 ) {
1155 DPRINTF(E_DEBUG, L_HTTP, "read error :: error no. %d [%s]\n", errno, strerror(errno));
1156 if( errno != EAGAIN )
1157 break;
1159 ret = write(h->socket, buf, ret);
1160 if( ret == -1 ) {
1161 DPRINTF(E_DEBUG, L_HTTP, "write error :: error no. %d [%s]\n", errno, strerror(errno));
1162 if( errno != EAGAIN )
1163 break;
1165 offset+=ret;
1167 if( buf )
1168 free(buf);
1171 void
1172 SendResp_icon(struct upnphttp * h, char * icon)
1174 char * header;
1175 char * data;
1176 int size, ret;
1177 char mime[12];
1178 char date[30];
1179 time_t curtime = time(NULL);
1181 if( strcmp(icon, "sm.png") == 0 )
1183 DPRINTF(E_DEBUG, L_HTTP, "Sending small PNG icon\n");
1184 data = (char *)png_sm;
1185 size = sizeof(png_sm)-1;
1186 strcpy(mime, "image/png");
1188 else if( strcmp(icon, "lrg.png") == 0 )
1190 DPRINTF(E_DEBUG, L_HTTP, "Sending large PNG icon\n");
1191 data = (char *)png_lrg;
1192 size = sizeof(png_lrg)-1;
1193 strcpy(mime, "image/png");
1195 else if( strcmp(icon, "sm.jpg") == 0 )
1197 DPRINTF(E_DEBUG, L_HTTP, "Sending small JPEG icon\n");
1198 data = (char *)jpeg_sm;
1199 size = sizeof(jpeg_sm)-1;
1200 strcpy(mime, "image/jpeg");
1202 else if( strcmp(icon, "lrg.jpg") == 0 )
1204 DPRINTF(E_DEBUG, L_HTTP, "Sending large JPEG icon\n");
1205 data = (char *)jpeg_lrg;
1206 size = sizeof(jpeg_lrg)-1;
1207 strcpy(mime, "image/jpeg");
1209 else
1211 DPRINTF(E_WARN, L_HTTP, "Invalid icon request: %s\n", icon);
1212 Send404(h);
1213 return;
1217 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1218 ret = asprintf(&header, "HTTP/1.1 200 OK\r\n"
1219 "Content-Type: %s\r\n"
1220 "Content-Length: %d\r\n"
1221 "Connection: close\r\n"
1222 "Date: %s\r\n"
1223 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
1224 mime, size, date);
1226 if( (send_data(h, header, ret, MSG_MORE) == 0) && (h->req_command != EHead) )
1228 send_data(h, data, size, 0);
1230 free(header);
1233 void
1234 SendResp_albumArt(struct upnphttp * h, char * object)
1236 char header[1500];
1237 char sql_buf[256];
1238 char **result;
1239 int rows = 0;
1240 char *path;
1241 char *dash;
1242 char date[30];
1243 time_t curtime = time(NULL);
1244 off_t offset = 0, size;
1245 int sendfh;
1247 memset(header, 0, 1500);
1249 if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE )
1251 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1252 Send406(h);
1253 return;
1256 dash = strchr(object, '-');
1257 if( dash )
1258 *dash = '\0';
1259 sprintf(sql_buf, "SELECT PATH from ALBUM_ART where ID = %s", object);
1260 sql_get_table(db, sql_buf, &result, &rows, NULL);
1261 if( !rows )
1263 DPRINTF(E_WARN, L_HTTP, "ALBUM_ART ID %s not found, responding ERROR 404\n", object);
1264 Send404(h);
1265 goto error;
1267 path = result[1];
1268 DPRINTF(E_INFO, L_HTTP, "Serving album art ID: %s [%s]\n", object, path);
1270 if( access(path, F_OK) == 0 )
1272 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1274 sendfh = open(path, O_RDONLY);
1275 if( sendfh < 0 ) {
1276 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path);
1277 goto error;
1279 size = lseek(sendfh, 0, SEEK_END);
1280 lseek(sendfh, 0, SEEK_SET);
1282 sprintf(header, "HTTP/1.1 200 OK\r\n"
1283 "Content-Type: image/jpeg\r\n"
1284 "Content-Length: %jd\r\n"
1285 "Connection: close\r\n"
1286 "Date: %s\r\n"
1287 "EXT:\r\n"
1288 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1289 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
1290 "Server: " MINIDLNA_SERVER_STRING "\r\n",
1291 size, date);
1293 if( h->reqflags & FLAG_XFERBACKGROUND )
1295 strcat(header, "transferMode.dlna.org: Background\r\n\r\n");
1297 else //if( h->reqflags & FLAG_XFERINTERACTIVE )
1299 strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n");
1303 if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) )
1305 send_file(h, sendfh, offset, size);
1307 close(sendfh);
1309 error:
1310 sqlite3_free_table(result);
1313 void
1314 SendResp_caption(struct upnphttp * h, char * object)
1316 char header[1500];
1317 char sql_buf[256];
1318 char **result;
1319 int rows = 0;
1320 char *path;
1321 char date[30];
1322 time_t curtime = time(NULL);
1323 off_t offset = 0, size;
1324 int sendfh, ret;
1326 memset(header, 0, 1500);
1328 strip_ext(object);
1329 sprintf(sql_buf, "SELECT PATH from CAPTIONS where ID = %s", object);
1330 sql_get_table(db, sql_buf, &result, &rows, NULL);
1331 if( !rows )
1333 DPRINTF(E_WARN, L_HTTP, "CAPTION ID %s not found, responding ERROR 404\n", object);
1334 Send404(h);
1335 goto error;
1337 path = result[1];
1338 DPRINTF(E_INFO, L_HTTP, "Serving caption ID: %s [%s]\n", object, path);
1340 if( access(path, F_OK) != 0 )
1341 goto error;
1343 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1344 sendfh = open(path, O_RDONLY);
1345 if( sendfh < 0 ) {
1346 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path);
1347 goto error;
1349 size = lseek(sendfh, 0, SEEK_END);
1350 lseek(sendfh, 0, SEEK_SET);
1352 ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n"
1353 "Content-Type: smi/caption\r\n"
1354 "Content-Length: %jd\r\n"
1355 "Connection: close\r\n"
1356 "Date: %s\r\n"
1357 "EXT:\r\n"
1358 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
1359 size, date);
1361 if( (send_data(h, header, ret, MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) )
1363 send_file(h, sendfh, offset, size);
1365 close(sendfh);
1367 error:
1368 sqlite3_free_table(result);
1371 void
1372 SendResp_thumbnail(struct upnphttp * h, char * object)
1374 char header[1500];
1375 char sql_buf[256];
1376 char **result;
1377 int rows = 0;
1378 char *path;
1379 char date[30];
1380 time_t curtime = time(NULL);
1381 ExifData *ed;
1382 ExifLoader *l;
1384 memset(header, 0, 1500);
1386 if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE )
1388 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1389 Send406(h);
1390 return;
1393 strip_ext(object);
1394 sprintf(sql_buf, "SELECT PATH from DETAILS where ID = '%s'", object);
1395 sql_get_table(db, sql_buf, &result, &rows, NULL);
1396 if( !rows )
1398 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
1399 Send404(h);
1400 goto error;
1402 path = result[1];
1403 DPRINTF(E_INFO, L_HTTP, "Serving thumbnail for ObjectId: %s [%s]\n", object, path);
1405 if( access(path, F_OK) == 0 )
1407 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1409 l = exif_loader_new();
1410 exif_loader_write_file(l, path);
1411 ed = exif_loader_get_data(l);
1412 exif_loader_unref(l);
1414 if( !ed || !ed->size )
1416 Send404(h);
1417 if( ed )
1418 exif_data_unref(ed);
1419 goto error;
1421 sprintf(header, "HTTP/1.1 200 OK\r\n"
1422 "Content-Type: image/jpeg\r\n"
1423 "Content-Length: %d\r\n"
1424 "Connection: close\r\n"
1425 "Date: %s\r\n"
1426 "EXT:\r\n"
1427 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1428 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
1429 "Server: " MINIDLNA_SERVER_STRING "\r\n",
1430 ed->size, date);
1432 if( h->reqflags & FLAG_XFERBACKGROUND )
1434 strcat(header, "transferMode.dlna.org: Background\r\n\r\n");
1436 else //if( h->reqflags & FLAG_XFERINTERACTIVE )
1438 strcat(header, "transferMode.dlna.org: Interactive\r\n\r\n");
1441 if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) )
1443 send_data(h, (char *)ed->data, ed->size, 0);
1445 exif_data_unref(ed);
1447 CloseSocket_upnphttp(h);
1448 error:
1449 sqlite3_free_table(result);
1452 void
1453 SendResp_resizedimg(struct upnphttp * h, char * object)
1455 char header[1500];
1456 char str_buf[256];
1457 char **result;
1458 char date[30];
1459 char dlna_pn[4];
1460 time_t curtime = time(NULL);
1461 int width=640, height=480, dstw, dsth, rotation, size;
1462 long srcw, srch;
1463 unsigned char * data = NULL;
1464 char *path, *file_path;
1465 char *resolution, *tn;
1466 char *key, *val;
1467 char *saveptr=NULL, *item=NULL;
1468 char *pixelshape=NULL;
1469 sqlite_int64 id;
1470 int rows=0, chunked=0, ret;
1471 #ifdef __sparc__
1472 ExifData *ed;
1473 ExifLoader *l;
1474 #endif
1475 image *imsrc = NULL, *imdst = NULL;
1476 int scale = 1;
1478 id = strtoll(object, NULL, 10);
1479 sprintf(str_buf, "SELECT PATH, RESOLUTION, THUMBNAIL from DETAILS where ID = '%lld'", id);
1480 ret = sql_get_table(db, str_buf, &result, &rows, NULL);
1481 if( (ret != SQLITE_OK) )
1483 DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id);
1484 Send500(h);
1485 return;
1487 if( !rows || (access(result[3], F_OK) != 0) )
1489 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
1490 sqlite3_free_table(result);
1491 Send404(h);
1492 return;
1494 #if USE_FORK
1495 pid_t newpid = 0;
1496 newpid = fork();
1497 if( newpid )
1498 goto resized_error;
1499 #endif
1500 file_path = result[3];
1501 resolution = result[4];
1502 tn = result[5];
1503 srcw = strtol(resolution, &saveptr, 10);
1504 srch = strtol(saveptr+1, NULL, 10);
1506 path = strdup(object);
1507 if( strtok_r(path, "?", &saveptr) )
1509 item = strtok_r(NULL, "&,", &saveptr);
1511 while( item != NULL )
1513 #ifdef TIVO_SUPPORT
1514 decodeString(item, 1);
1515 #endif
1516 val = item;
1517 key = strsep(&val, "=");
1518 DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val);
1519 if( strcasecmp(key, "width") == 0 )
1521 width = atoi(val);
1523 else if( strcasecmp(key, "height") == 0 )
1525 height = atoi(val);
1527 else if( strcasecmp(key, "rotation") == 0 )
1529 rotation = atoi(val);
1531 else if( strcasecmp(key, "pixelshape") == 0 )
1533 pixelshape = val;
1535 item = strtok_r(NULL, "&,", &saveptr);
1537 free(path);
1539 if( h->reqflags & FLAG_XFERSTREAMING || h->reqflags & FLAG_RANGE )
1541 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with a resized image!\n");
1542 Send406(h);
1543 goto resized_error;
1546 DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %lld [%s]\n", id, file_path);
1548 /* Figure out the best destination resolution we can use */
1549 dstw = width;
1550 dsth = ((((width<<10)/srcw)*srch)>>10);
1551 if( dsth > height )
1553 dsth = height;
1554 dstw = (((height<<10)/srch) * srcw>>10);
1557 if( dstw <= 640 && dsth <= 480 )
1558 strcpy(dlna_pn, "SM");
1559 else if( dstw <= 1024 && dsth <= 768 )
1560 strcpy(dlna_pn, "MED");
1561 else
1562 strcpy(dlna_pn, "LRG");
1564 if( srcw>>3 >= dstw && srch>>3 >= dsth)
1565 scale = 8;
1566 else if( srcw>>2 >= dstw && srch>>2 >= dsth )
1567 scale = 4;
1568 else if( srcw>>1 >= dstw && srch>>1 >= dsth )
1569 scale = 2;
1571 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1572 snprintf(header, sizeof(header)-100, "HTTP/1.1 200 OK\r\n"
1573 "Content-Type: image/jpeg\r\n"
1574 "Connection: close\r\n"
1575 "Date: %s\r\n"
1576 "EXT:\r\n"
1577 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1578 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_%s;DLNA.ORG_CI=1\r\n"
1579 "Server: " MINIDLNA_SERVER_STRING "\r\n",
1580 date, dlna_pn);
1581 if( h->reqflags & FLAG_XFERINTERACTIVE )
1583 strcat(header, "transferMode.dlna.org: Interactive\r\n");
1585 else if( h->reqflags & FLAG_XFERBACKGROUND )
1587 strcat(header, "transferMode.dlna.org: Background\r\n");
1590 /* Resizing from a thumbnail is much faster than from a large image */
1591 #ifdef __sparc__
1592 if( dstw <= 160 && dsth <= 120 && atoi(tn) )
1594 l = exif_loader_new();
1595 exif_loader_write_file(l, file_path);
1596 ed = exif_loader_get_data(l);
1597 exif_loader_unref(l);
1599 if( !ed || !ed->size )
1601 if( ed )
1602 exif_data_unref(ed);
1603 DPRINTF(E_WARN, L_HTTP, "Unable to access image thumbnail!\n");
1604 Send500(h);
1605 goto resized_error;
1607 imsrc = image_new_from_jpeg(NULL, 0, (char *)ed->data, ed->size, 1);
1608 exif_data_unref(ed);
1610 else
1611 #endif
1612 if( strcmp(h->HttpVer, "HTTP/1.0") == 0 )
1614 imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale);
1616 else
1618 chunked = 1;
1619 strcat(header, "Transfer-Encoding: chunked\r\n\r\n");
1622 if( !chunked )
1624 if( !imsrc )
1626 DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path);
1627 Send500(h);
1628 goto resized_error;
1631 imdst = image_resize(imsrc, dstw, dsth);
1632 data = image_save_to_jpeg_buf(imdst, &size);
1634 sprintf(str_buf, "Content-Length: %d\r\n\r\n", size);
1635 strcat(header, str_buf);
1638 if( (send_data(h, header, strlen(header), 0) == 0) && (h->req_command != EHead) )
1640 if( chunked )
1642 imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale);
1643 if( !imsrc )
1645 DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path);
1646 Send500(h);
1647 goto resized_error;
1649 imdst = image_resize(imsrc, dstw, dsth);
1650 data = image_save_to_jpeg_buf(imdst, &size);
1652 ret = sprintf(str_buf, "%x\r\n", size);
1653 send_data(h, str_buf, ret, MSG_MORE);
1654 send_data(h, (char *)data, size, MSG_MORE);
1655 send_data(h, "\r\n0\r\n\r\n", 7, 0);
1657 else
1659 send_data(h, (char *)data, size, 0);
1662 DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path);
1663 if( imsrc )
1664 image_free(imsrc);
1665 if( imdst )
1666 image_free(imdst);
1667 resized_error:
1668 sqlite3_free_table(result);
1669 #if USE_FORK
1670 if( !newpid )
1671 _exit(0);
1672 #endif
1675 void
1676 SendResp_dlnafile(struct upnphttp * h, char * object)
1678 char header[1500];
1679 char hdr_buf[512];
1680 char sql_buf[256];
1681 char **result;
1682 int rows, ret;
1683 char date[30];
1684 time_t curtime = time(NULL);
1685 off_t total, offset, size;
1686 sqlite_int64 id;
1687 int sendfh;
1688 static struct { sqlite_int64 id; char path[PATH_MAX]; char mime[32]; char dlna[64]; } last_file = { 0 };
1689 #if USE_FORK
1690 pid_t newpid = 0;
1691 #endif
1693 id = strtoll(object, NULL, 10);
1694 if( id != last_file.id )
1696 sprintf(sql_buf, "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", id);
1697 ret = sql_get_table(db, sql_buf, &result, &rows, NULL);
1698 if( (ret != SQLITE_OK) )
1700 DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id);
1701 Send500(h);
1702 return;
1704 if( !rows )
1706 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
1707 sqlite3_free_table(result);
1708 Send404(h);
1709 return;
1711 /* Cache the result */
1712 last_file.id = id;
1713 strncpy(last_file.path, result[3], sizeof(last_file.path)-1);
1714 if( result[4] )
1716 strncpy(last_file.mime, result[4], sizeof(last_file.mime)-1);
1717 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
1718 if( h->req_client == ESamsungTV )
1720 if( strcmp(last_file.mime+6, "x-matroska") == 0 )
1721 strcpy(last_file.mime+8, "mkv");
1723 /* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */
1724 else if( h->req_client == ESonyBDP )
1726 if( strcmp(last_file.mime+6, "x-matroska") == 0 ||
1727 strcmp(last_file.mime+6, "mpeg") == 0 )
1728 strcpy(last_file.mime+6, "divx");
1731 else
1733 last_file.mime[0] = '\0';
1735 if( result[5] )
1736 snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s", result[5]);
1737 else if( h->reqflags & FLAG_DLNA )
1738 strcpy(last_file.dlna, dlna_no_conv);
1739 else
1740 last_file.dlna[0] = '\0';
1741 sqlite3_free_table(result);
1743 #if USE_FORK
1744 newpid = fork();
1745 if( newpid )
1746 return;
1747 #endif
1749 DPRINTF(E_INFO, L_HTTP, "Serving DetailID: %lld [%s]\n", id, last_file.path);
1751 if( h->reqflags & FLAG_XFERSTREAMING )
1753 if( strncmp(last_file.mime, "image", 5) == 0 )
1755 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1756 Send406(h);
1757 goto error;
1760 else if( h->reqflags & FLAG_XFERINTERACTIVE )
1762 if( h->reqflags & FLAG_REALTIMEINFO )
1764 DPRINTF(E_WARN, L_HTTP, "Bad realTimeInfo flag with Interactive request!\n");
1765 Send400(h);
1766 goto error;
1768 if( strncmp(last_file.mime, "image", 5) != 0 )
1770 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Interactive without an image!\n");
1771 /* Samsung TVs (well, at least the A950) do this for some reason,
1772 * and I don't see them fixing this bug any time soon. */
1773 if( h->req_client != ESamsungTV || GETFLAG(DLNA_STRICT_MASK) )
1775 Send406(h);
1776 goto error;
1781 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1782 offset = h->req_RangeStart;
1783 sendfh = open(last_file.path, O_RDONLY);
1784 if( sendfh < 0 ) {
1785 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", last_file.path);
1786 goto error;
1788 size = lseek(sendfh, 0, SEEK_END);
1789 lseek(sendfh, 0, SEEK_SET);
1791 sprintf(header, "HTTP/1.1 20%c OK\r\n"
1792 "Content-Type: %s\r\n", (h->reqflags & FLAG_RANGE ? '6' : '0'), last_file.mime);
1793 if( h->reqflags & FLAG_RANGE )
1795 if( !h->req_RangeEnd )
1796 h->req_RangeEnd = size;
1797 if( (h->req_RangeStart > h->req_RangeEnd) || (h->req_RangeStart < 0) )
1799 DPRINTF(E_WARN, L_HTTP, "Specified range was invalid!\n");
1800 Send400(h);
1801 close(sendfh);
1802 goto error;
1804 if( h->req_RangeEnd > size )
1806 DPRINTF(E_WARN, L_HTTP, "Specified range was outside file boundaries!\n");
1807 Send416(h);
1808 close(sendfh);
1809 goto error;
1812 if( h->req_RangeEnd < size )
1814 total = h->req_RangeEnd - h->req_RangeStart + 1;
1815 sprintf(hdr_buf, "Content-Length: %jd\r\n"
1816 "Content-Range: bytes %jd-%jd/%jd\r\n",
1817 total, h->req_RangeStart, h->req_RangeEnd, size);
1819 else
1821 h->req_RangeEnd = size;
1822 total = size - h->req_RangeStart;
1823 sprintf(hdr_buf, "Content-Length: %jd\r\n"
1824 "Content-Range: bytes %jd-%jd/%jd\r\n",
1825 total, h->req_RangeStart, size-1, size);
1828 else
1830 h->req_RangeEnd = size;
1831 total = size;
1832 sprintf(hdr_buf, "Content-Length: %jd\r\n", total);
1834 strcat(header, hdr_buf);
1836 if( h->reqflags & FLAG_XFERSTREAMING )
1838 strcat(header, "transferMode.dlna.org: Streaming\r\n");
1840 else if( h->reqflags & FLAG_XFERBACKGROUND )
1842 if( strncmp(last_file.mime, "image", 5) == 0 )
1843 strcat(header, "transferMode.dlna.org: Background\r\n");
1845 else //if( h->reqflags & FLAG_XFERINTERACTIVE )
1847 if( (strncmp(last_file.mime, "video", 5) == 0) ||
1848 (strncmp(last_file.mime, "audio", 5) == 0) )
1850 strcat(header, "transferMode.dlna.org: Streaming\r\n");
1852 else
1854 strcat(header, "transferMode.dlna.org: Interactive\r\n");
1858 if( h->reqflags & FLAG_CAPTION )
1860 if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%lld'", id) > 0 )
1862 sprintf(hdr_buf, "CaptionInfo.sec: http://%s:%d/Captions/%lld.srt\r\n",
1863 lan_addr[0].str, runtime_vars.port, id);
1864 strcat(header, hdr_buf);
1868 sprintf(hdr_buf, "Accept-Ranges: bytes\r\n"
1869 "Connection: close\r\n"
1870 "Date: %s\r\n"
1871 "EXT:\r\n"
1872 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1873 "contentFeatures.dlna.org: %s\r\n"
1874 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
1875 date, last_file.dlna);
1876 strcat(header, hdr_buf);
1878 if( (send_data(h, header, strlen(header), MSG_MORE) == 0) && (h->req_command != EHead) && (sendfh > 0) )
1880 send_file(h, sendfh, offset, h->req_RangeEnd);
1882 close(sendfh);
1884 error:
1885 #if USE_FORK
1886 if( !newpid )
1887 _exit(0);
1888 #endif
1889 return;