Updates to Tomato RAF including NGINX && PHP
[tomato.git] / release / src / router / minidlna / upnphttp.c
blob045c875dabb3a2a5386d030439c81dccb3266e72
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 <sys/types.h>
58 #include <sys/stat.h>
59 #include <fcntl.h>
60 #include <errno.h>
61 #include <sys/sendfile.h>
62 #include <arpa/inet.h>
63 #include <sys/time.h>
64 #include <sys/resource.h>
66 #include "config.h"
67 #include "upnphttp.h"
68 #include "upnpdescgen.h"
69 #include "minidlnapath.h"
70 #include "upnpsoap.h"
71 #include "upnpevents.h"
72 #include "upnpglobalvars.h"
73 #include "utils.h"
74 #include "getifaddr.h"
75 #include "image_utils.h"
76 #include "log.h"
77 #include "sql.h"
78 #include <libexif/exif-loader.h>
79 #ifdef TIVO_SUPPORT
80 #include "tivo_utils.h"
81 #include "tivo_commands.h"
82 #endif
83 #define MAX_BUFFER_SIZE 4194304 // 4MB -- Too much?
84 //#define MAX_BUFFER_SIZE 2147483647 // 2GB -- Too much?
85 #define MIN_BUFFER_SIZE 65536
87 #include "icons.c"
89 struct upnphttp *
90 New_upnphttp(int s)
92 struct upnphttp * ret;
93 if(s<0)
94 return NULL;
95 ret = (struct upnphttp *)malloc(sizeof(struct upnphttp));
96 if(ret == NULL)
97 return NULL;
98 memset(ret, 0, sizeof(struct upnphttp));
99 ret->socket = s;
100 return ret;
103 void
104 CloseSocket_upnphttp(struct upnphttp * h)
106 if(close(h->socket) < 0)
108 DPRINTF(E_ERROR, L_HTTP, "CloseSocket_upnphttp: close(%d): %s\n", h->socket, strerror(errno));
110 h->socket = -1;
111 h->state = 100;
114 void
115 Delete_upnphttp(struct upnphttp * h)
117 if(h)
119 if(h->socket >= 0)
120 CloseSocket_upnphttp(h);
121 free(h->req_buf);
122 free(h->res_buf);
123 free(h);
128 SearchClientCache(struct in_addr addr, int quiet)
130 int i;
131 for( i=0; i<CLIENT_CACHE_SLOTS; i++ )
133 if( clients[i].addr.s_addr == addr.s_addr )
135 /* Invalidate this client cache if it's older than 1 hour */
136 if( (time(NULL) - clients[i].age) > 3600 )
138 unsigned char mac[6];
139 if( get_remote_mac(addr, mac) == 0 &&
140 memcmp(mac, clients[i].mac, 6) == 0 )
142 /* Same MAC as last time when we were able to identify the client,
143 * so extend the timeout by another hour. */
144 clients[i].age = time(NULL);
146 else
148 memset(&clients[i], 0, sizeof(struct client_cache_s));
149 return -1;
152 if( !quiet )
153 DPRINTF(E_DEBUG, L_HTTP, "Client found in cache. [type %d/entry %d]\n",
154 clients[i].type, i);
155 return i;
158 return -1;
161 /* parse HttpHeaders of the REQUEST */
162 static void
163 ParseHttpHeaders(struct upnphttp * h)
165 char * line;
166 char * colon;
167 char * p;
168 int n;
169 line = h->req_buf;
170 /* TODO : check if req_buf, contentoff are ok */
171 while(line < (h->req_buf + h->req_contentoff))
173 colon = strchr(line, ':');
174 if(colon)
176 if(strncasecmp(line, "Content-Length", 14)==0)
178 p = colon;
179 while(*p < '0' || *p > '9')
180 p++;
181 h->req_contentlen = atoi(p);
183 else if(strncasecmp(line, "SOAPAction", 10)==0)
185 p = colon;
186 n = 0;
187 while(*p == ':' || *p == ' ' || *p == '\t')
188 p++;
189 while(p[n]>=' ')
191 n++;
193 if((p[0] == '"' && p[n-1] == '"')
194 || (p[0] == '\'' && p[n-1] == '\''))
196 p++; n -= 2;
198 h->req_soapAction = p;
199 h->req_soapActionLen = n;
201 else if(strncasecmp(line, "Callback", 8)==0)
203 p = colon;
204 while(*p != '<' && *p != '\r' )
205 p++;
206 n = 0;
207 while(p[n] != '>' && p[n] != '\r' )
208 n++;
209 h->req_Callback = p + 1;
210 h->req_CallbackLen = MAX(0, n - 1);
211 /* Verify callback validity */
212 if(strncmp(h->req_Callback, "http://", 7) != 0)
213 h->req_Callback = NULL;
215 else if(strncasecmp(line, "SID", 3)==0)
217 //zqiu: fix bug for test 4.0.5
218 //Skip extra headers like "SIDHEADER: xxxxxx xxx"
219 for(p=line+3;p<colon;p++)
221 if(!isspace(*p))
223 p = NULL; //unexpected header
224 break;
227 if(p) {
228 p = colon + 1;
229 while(isspace(*p))
230 p++;
231 n = 0;
232 while(!isspace(p[n]))
233 n++;
234 h->req_SID = p;
235 h->req_SIDLen = n;
238 else if(strncasecmp(line, "NT", 2)==0)
240 p = colon + 1;
241 while(isspace(*p))
242 p++;
243 n = 0;
244 while(!isspace(p[n]))
245 n++;
246 h->req_NT = p;
247 h->req_NTLen = n;
249 /* Timeout: Seconds-nnnn */
250 /* TIMEOUT
251 Recommended. Requested duration until subscription expires,
252 either number of seconds or infinite. Recommendation
253 by a UPnP Forum working committee. Defined by UPnP vendor.
254 Consists of the keyword "Second-" followed (without an
255 intervening space) by either an integer or the keyword "infinite". */
256 else if(strncasecmp(line, "Timeout", 7)==0)
258 p = colon + 1;
259 while(isspace(*p))
260 p++;
261 if(strncasecmp(p, "Second-", 7)==0) {
262 h->req_Timeout = atoi(p+7);
265 // Range: bytes=xxx-yyy
266 else if(strncasecmp(line, "Range", 5)==0)
268 p = colon + 1;
269 while(isspace(*p))
270 p++;
271 if(strncasecmp(p, "bytes=", 6)==0) {
272 h->reqflags |= FLAG_RANGE;
273 h->req_RangeStart = strtoll(p+6, &colon, 10);
274 h->req_RangeEnd = colon ? atoll(colon+1) : 0;
275 DPRINTF(E_DEBUG, L_HTTP, "Range Start-End: %lld - %lld\n",
276 h->req_RangeStart, h->req_RangeEnd?h->req_RangeEnd:-1);
279 else if(strncasecmp(line, "Host", 4)==0)
281 int i;
282 h->reqflags |= FLAG_HOST;
283 p = colon + 1;
284 while(isspace(*p))
285 p++;
286 for(n = 0; n<n_lan_addr; n++)
288 for(i=0; lan_addr[n].str[i]; i++)
290 if(lan_addr[n].str[i] != p[i])
291 break;
293 if(!lan_addr[n].str[i])
295 h->iface = n;
296 break;
300 else if(strncasecmp(line, "User-Agent", 10)==0)
302 char *s;
303 /* Skip client detection if we already detected it. */
304 if( h->req_client )
305 goto next_header;
306 p = colon + 1;
307 while(isspace(*p))
308 p++;
309 if(strncasecmp(p, "Xbox/", 5)==0)
311 h->req_client = EXbox;
312 h->reqflags |= FLAG_MIME_AVI_AVI;
313 h->reqflags |= FLAG_MS_PFS;
315 else if(strncmp(p, "PLAYSTATION", 11)==0)
317 h->req_client = EPS3;
318 h->reqflags |= FLAG_DLNA;
319 h->reqflags |= FLAG_MIME_AVI_DIVX;
321 else if((s=strstrc(p, "SEC_HHP_", '\r')))
323 h->req_client = ESamsungSeriesC;
324 h->reqflags |= FLAG_SAMSUNG;
325 h->reqflags |= FLAG_DLNA;
326 h->reqflags |= FLAG_NO_RESIZE;
327 if(strstrc(s+8, "TV", '\r'))
328 h->reqflags |= FLAG_SAMSUNG_TV;
330 else if(strncmp(p, "SamsungWiselinkPro", 18)==0)
332 h->req_client = ESamsungSeriesA;
333 h->reqflags |= FLAG_SAMSUNG;
334 h->reqflags |= FLAG_DLNA;
335 h->reqflags |= FLAG_NO_RESIZE;
337 else if(strstrc(p, "bridgeCo-DMP/3", '\r'))
339 h->req_client = EDenonReceiver;
340 h->reqflags |= FLAG_DLNA;
342 else if(strstrc(p, "fbxupnpav/", '\r'))
344 h->req_client = EFreeBox;
346 else if(strncmp(p, "SMP8634", 7)==0)
348 h->req_client = EPopcornHour;
349 h->reqflags |= FLAG_MIME_FLAC_FLAC;
351 else if(strstrc(p, "Microsoft-IPTV-Client", '\r'))
353 h->req_client = EMediaRoom;
354 h->reqflags |= FLAG_MS_PFS;
356 else if(strstrc(p, "LGE_DLNA_SDK", '\r'))
358 h->req_client = ELGDevice;
359 h->reqflags |= FLAG_DLNA;
361 else if(strncmp(p, "Verismo,", 8)==0)
363 h->req_client = ENetgearEVA2000;
364 h->reqflags |= FLAG_MS_PFS;
366 else if(strstrc(p, "UPnP/1.0 DLNADOC/1.50 Intel_SDK_for_UPnP_devices/1.2", '\r'))
368 h->req_client = EToshibaTV;
369 h->reqflags |= FLAG_DLNA;
371 else if(strstrc(p, "DLNADOC/1.50", '\r'))
373 h->req_client = EStandardDLNA150;
374 h->reqflags |= FLAG_DLNA;
375 h->reqflags |= FLAG_MIME_AVI_AVI;
378 else if(strncasecmp(line, "X-AV-Client-Info", 16)==0)
380 /* Skip client detection if we already detected it. */
381 if( h->req_client && h->req_client < EStandardDLNA150 )
382 goto next_header;
383 p = colon + 1;
384 while(isspace(*p))
385 p++;
386 if(strstrc(p, "PLAYSTATION 3", '\r'))
388 h->req_client = EPS3;
389 h->reqflags |= FLAG_DLNA;
390 h->reqflags |= FLAG_MIME_AVI_DIVX;
392 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Blu-ray Disc Player"; mv="2.0" */
393 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BLU-RAY HOME THEATRE SYSTEM"; mv="2.0"; */
394 /* Sony SMP-100 needs the same treatment as their BDP-S370 */
395 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Media Player"; mv="2.0" */
396 else if(strstrc(p, "Blu-ray Disc Player", '\r') ||
397 strstrc(p, "BLU-RAY HOME THEATRE SYSTEM", '\r') ||
398 strstrc(p, "Media Player", '\r'))
400 h->req_client = ESonyBDP;
401 h->reqflags |= FLAG_DLNA;
403 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BRAVIA KDL-40EX503"; mv="1.7"; */
404 /* X-AV-Client-Info: av=5.0; hn=""; cn="Sony Corporation"; mn="INTERNET TV NSX-40GT 1"; mv="0.1"; */
405 else if(strstrc(p, "BRAVIA", '\r') ||
406 strstrc(p, "INTERNET TV", '\r'))
408 h->req_client = ESonyBravia;
409 h->reqflags |= FLAG_DLNA;
412 else if(strncasecmp(line, "Transfer-Encoding", 17)==0)
414 p = colon + 1;
415 while(isspace(*p))
416 p++;
417 if(strncasecmp(p, "chunked", 7)==0)
419 h->reqflags |= FLAG_CHUNKED;
422 else if(strncasecmp(line, "Accept-Language", 15)==0)
424 h->reqflags |= FLAG_LANGUAGE;
426 else if(strncasecmp(line, "getcontentFeatures.dlna.org", 27)==0)
428 p = colon + 1;
429 while(isspace(*p))
430 p++;
431 if( (*p != '1') || !isspace(p[1]) )
432 h->reqflags |= FLAG_INVALID_REQ;
434 else if(strncasecmp(line, "TimeSeekRange.dlna.org", 22)==0)
436 h->reqflags |= FLAG_TIMESEEK;
438 else if(strncasecmp(line, "PlaySpeed.dlna.org", 18)==0)
440 h->reqflags |= FLAG_PLAYSPEED;
442 else if(strncasecmp(line, "realTimeInfo.dlna.org", 21)==0)
444 h->reqflags |= FLAG_REALTIMEINFO;
446 else if(strncasecmp(line, "getAvailableSeekRange.dlna.org", 21)==0)
448 p = colon + 1;
449 while(isspace(*p))
450 p++;
451 if( (*p != '1') || !isspace(p[1]) )
452 h->reqflags |= FLAG_INVALID_REQ;
454 else if(strncasecmp(line, "transferMode.dlna.org", 21)==0)
456 p = colon + 1;
457 while(isspace(*p))
458 p++;
459 if(strncasecmp(p, "Streaming", 9)==0)
461 h->reqflags |= FLAG_XFERSTREAMING;
463 if(strncasecmp(p, "Interactive", 11)==0)
465 h->reqflags |= FLAG_XFERINTERACTIVE;
467 if(strncasecmp(p, "Background", 10)==0)
469 h->reqflags |= FLAG_XFERBACKGROUND;
472 else if(strncasecmp(line, "getCaptionInfo.sec", 18)==0)
474 h->reqflags |= FLAG_CAPTION;
476 else if(strncasecmp(line, "FriendlyName", 12)==0)
478 p = colon + 1;
479 while(isspace(*p))
480 p++;
481 if(strstrc(p, "LIFETAB", '\r'))
483 h->req_client = ELifeTab;
484 h->reqflags |= FLAG_MS_PFS;
488 next_header:
489 while(!(line[0] == '\r' && line[1] == '\n'))
490 line++;
491 line += 2;
493 if( h->reqflags & FLAG_CHUNKED )
495 char *endptr;
496 h->req_chunklen = -1;
497 if( h->req_buflen <= h->req_contentoff )
498 return;
499 while( (line < (h->req_buf + h->req_buflen)) &&
500 (h->req_chunklen = strtol(line, &endptr, 16)) &&
501 (endptr != line) )
503 while(!(endptr[0] == '\r' && endptr[1] == '\n'))
505 endptr++;
507 line = endptr+h->req_chunklen+2;
510 if( endptr == line )
512 h->req_chunklen = -1;
513 return;
516 /* If the client type wasn't found, search the cache.
517 * This is done because a lot of clients like to send a
518 * different User-Agent with different types of requests. */
519 n = SearchClientCache(h->clientaddr, 0);
520 if( h->req_client )
522 /* Add this client to the cache if it's not there already. */
523 if( n < 0 )
525 for( n=0; n<CLIENT_CACHE_SLOTS; n++ )
527 if( clients[n].addr.s_addr )
528 continue;
529 get_remote_mac(h->clientaddr, clients[n].mac);
530 clients[n].addr = h->clientaddr;
531 DPRINTF(E_DEBUG, L_HTTP, "Added client [%d/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n",
532 h->req_client, inet_ntoa(clients[n].addr),
533 clients[n].mac[0], clients[n].mac[1], clients[n].mac[2],
534 clients[n].mac[3], clients[n].mac[4], clients[n].mac[5], n);
535 break;
538 else if( (clients[n].type < EStandardDLNA150 && h->req_client == EStandardDLNA150) ||
539 (clients[n].type == ESamsungSeriesB && h->req_client == ESamsungSeriesA) )
541 /* If we know the client and our new detection is generic, use our cached info */
542 /* If we detected a Samsung Series B earlier, don't overwrite it with Series A info */
543 h->reqflags |= clients[n].flags;
544 h->req_client = clients[n].type;
545 return;
547 clients[n].type = h->req_client;
548 clients[n].flags = h->reqflags & 0xFFF00000;
549 clients[n].age = time(NULL);
551 else if( n >= 0 )
553 h->reqflags |= clients[n].flags;
554 h->req_client = clients[n].type;
558 /* very minimalistic 400 error message */
559 static void
560 Send400(struct upnphttp * h)
562 static const char body400[] =
563 "<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>"
564 "<BODY><H1>Bad Request</H1>The request is invalid"
565 " for this HTTP version.</BODY></HTML>\r\n";
566 h->respflags = FLAG_HTML;
567 BuildResp2_upnphttp(h, 400, "Bad Request",
568 body400, sizeof(body400) - 1);
569 SendResp_upnphttp(h);
570 CloseSocket_upnphttp(h);
573 /* very minimalistic 404 error message */
574 static void
575 Send404(struct upnphttp * h)
577 static const char body404[] =
578 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
579 "<BODY><H1>Not Found</H1>The requested URL was not found"
580 " on this server.</BODY></HTML>\r\n";
581 h->respflags = FLAG_HTML;
582 BuildResp2_upnphttp(h, 404, "Not Found",
583 body404, sizeof(body404) - 1);
584 SendResp_upnphttp(h);
585 CloseSocket_upnphttp(h);
588 /* very minimalistic 406 error message */
589 static void
590 Send406(struct upnphttp * h)
592 static const char body406[] =
593 "<HTML><HEAD><TITLE>406 Not Acceptable</TITLE></HEAD>"
594 "<BODY><H1>Not Acceptable</H1>An unsupported operation"
595 " was requested.</BODY></HTML>\r\n";
596 h->respflags = FLAG_HTML;
597 BuildResp2_upnphttp(h, 406, "Not Acceptable",
598 body406, sizeof(body406) - 1);
599 SendResp_upnphttp(h);
600 CloseSocket_upnphttp(h);
603 /* very minimalistic 416 error message */
604 static void
605 Send416(struct upnphttp * h)
607 static const char body416[] =
608 "<HTML><HEAD><TITLE>416 Requested Range Not Satisfiable</TITLE></HEAD>"
609 "<BODY><H1>Requested Range Not Satisfiable</H1>The requested range"
610 " was outside the file's size.</BODY></HTML>\r\n";
611 h->respflags = FLAG_HTML;
612 BuildResp2_upnphttp(h, 416, "Requested Range Not Satisfiable",
613 body416, sizeof(body416) - 1);
614 SendResp_upnphttp(h);
615 CloseSocket_upnphttp(h);
618 /* very minimalistic 500 error message */
619 void
620 Send500(struct upnphttp * h)
622 static const char body500[] =
623 "<HTML><HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>"
624 "<BODY><H1>Internal Server Error</H1>Server encountered "
625 "and Internal Error.</BODY></HTML>\r\n";
626 h->respflags = FLAG_HTML;
627 BuildResp2_upnphttp(h, 500, "Internal Server Errror",
628 body500, sizeof(body500) - 1);
629 SendResp_upnphttp(h);
630 CloseSocket_upnphttp(h);
633 /* very minimalistic 501 error message */
634 void
635 Send501(struct upnphttp * h)
637 static const char body501[] =
638 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
639 "<BODY><H1>Not Implemented</H1>The HTTP Method "
640 "is not implemented by this server.</BODY></HTML>\r\n";
641 h->respflags = FLAG_HTML;
642 BuildResp2_upnphttp(h, 501, "Not Implemented",
643 body501, sizeof(body501) - 1);
644 SendResp_upnphttp(h);
645 CloseSocket_upnphttp(h);
648 static const char *
649 findendheaders(const char * s, int len)
651 while(len-- > 0)
653 if(s[0]=='\r' && s[1]=='\n' && s[2]=='\r' && s[3]=='\n')
654 return s;
655 s++;
657 return NULL;
660 /* Sends the description generated by the parameter */
661 static void
662 sendXMLdesc(struct upnphttp * h, char * (f)(int *))
664 char * desc;
665 int len;
666 desc = f(&len);
667 if(!desc)
669 DPRINTF(E_ERROR, L_HTTP, "Failed to generate XML description\n");
670 Send500(h);
671 return;
673 BuildResp_upnphttp(h, desc, len);
674 SendResp_upnphttp(h);
675 CloseSocket_upnphttp(h);
676 free(desc);
679 static void
680 SendResp_presentation(struct upnphttp * h)
682 char body[1024];
683 int l;
684 h->respflags = FLAG_HTML;
686 #ifdef READYNAS
687 l = snprintf(body, sizeof(body), "<meta http-equiv=\"refresh\" content=\"0; url=https://%s/admin/\">",
688 lan_addr[h->iface].str);
689 #else
690 int a, v, p;
691 a = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'a*'");
692 v = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'v*'");
693 p = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'i*'");
694 l = snprintf(body, sizeof(body),
695 "<HTML><HEAD><TITLE>" SERVER_NAME " " MINIDLNA_VERSION "</TITLE></HEAD>"
696 "<BODY><div style=\"text-align: center\">"
697 "<h3>" SERVER_NAME " status</h3>"
698 "Audio files: %d<br>"
699 "Video files: %d<br>"
700 "Image files: %d</div>"
701 "</BODY></HTML>\r\n", a, v, p);
702 #endif
703 BuildResp_upnphttp(h, body, l);
704 SendResp_upnphttp(h);
705 CloseSocket_upnphttp(h);
708 /* ProcessHTTPPOST_upnphttp()
709 * executes the SOAP query if it is possible */
710 static void
711 ProcessHTTPPOST_upnphttp(struct upnphttp * h)
713 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
715 if(h->req_soapAction)
717 /* we can process the request */
718 DPRINTF(E_DEBUG, L_HTTP, "SOAPAction: %.*s\n", h->req_soapActionLen, h->req_soapAction);
719 ExecuteSoapAction(h,
720 h->req_soapAction,
721 h->req_soapActionLen);
723 else
725 static const char err400str[] =
726 "<html><body>Bad request</body></html>";
727 DPRINTF(E_WARN, L_HTTP, "No SOAPAction in HTTP headers\n");
728 h->respflags = FLAG_HTML;
729 BuildResp2_upnphttp(h, 400, "Bad Request",
730 err400str, sizeof(err400str) - 1);
731 SendResp_upnphttp(h);
732 CloseSocket_upnphttp(h);
735 else
737 /* waiting for remaining data */
738 h->state = 1;
742 static void
743 ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path)
745 const char * sid;
746 DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPSubscribe %s\n", path);
747 DPRINTF(E_DEBUG, L_HTTP, "Callback '%.*s' Timeout=%d\n",
748 h->req_CallbackLen, h->req_Callback, h->req_Timeout);
749 DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID);
750 if((!h->req_Callback && !h->req_SID) ||
751 strncmp(h->req_NT, "upnp:event", h->req_NTLen) != 0) {
752 /* Missing or invalid CALLBACK : 412 Precondition Failed.
753 * If CALLBACK header is missing or does not contain a valid HTTP URL,
754 * the publisher must respond with HTTP error 412 Precondition Failed*/
755 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
756 SendResp_upnphttp(h);
757 CloseSocket_upnphttp(h);
758 } else {
759 /* - add to the subscriber list
760 * - respond HTTP/x.x 200 OK
761 * - Send the initial event message */
762 /* Server:, SID:; Timeout: Second-(xx|infinite) */
763 if(h->req_Callback) {
764 if(!h->req_NT || strncmp(h->req_NT, "upnp:event", h->req_NTLen) != 0) {
765 BuildResp2_upnphttp(h, 400, "Bad Request",
766 "<html><body>Bad request</body></html>", 37);
767 } else {
768 sid = upnpevents_addSubscriber(path, h->req_Callback,
769 h->req_CallbackLen, h->req_Timeout);
770 h->respflags = FLAG_TIMEOUT;
771 if(sid) {
772 DPRINTF(E_DEBUG, L_HTTP, "generated sid=%s\n", sid);
773 h->respflags |= FLAG_SID;
774 h->req_SID = sid;
775 h->req_SIDLen = strlen(sid);
777 BuildResp_upnphttp(h, 0, 0);
779 } else {
780 /* subscription renew */
781 /* Invalid SID
782 412 Precondition Failed. If a SID does not correspond to a known,
783 un-expired subscription, the publisher must respond
784 with HTTP error 412 Precondition Failed. */
785 if(renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0) {
786 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
787 } else {
788 /* A DLNA device must enforce a 5 minute timeout */
789 h->respflags = FLAG_TIMEOUT;
790 h->req_Timeout = 300;
791 h->respflags |= FLAG_SID;
792 BuildResp_upnphttp(h, 0, 0);
795 SendResp_upnphttp(h);
796 CloseSocket_upnphttp(h);
800 static void
801 ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path)
803 DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPUnSubscribe %s\n", path);
804 DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID);
805 /* Remove from the list */
806 if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0) {
807 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
808 } else {
809 BuildResp_upnphttp(h, 0, 0);
811 SendResp_upnphttp(h);
812 CloseSocket_upnphttp(h);
815 /* Parse and process Http Query
816 * called once all the HTTP headers have been received. */
817 static void
818 ProcessHttpQuery_upnphttp(struct upnphttp * h)
820 char HttpCommand[16];
821 char HttpUrl[512];
822 char * HttpVer;
823 char * p;
824 int i;
825 p = h->req_buf;
826 if(!p)
827 return;
828 for(i = 0; i<15 && *p != ' ' && *p != '\r'; i++)
829 HttpCommand[i] = *(p++);
830 HttpCommand[i] = '\0';
831 while(*p==' ')
832 p++;
833 if(strncmp(p, "http://", 7) == 0)
835 p = p+7;
836 while(*p!='/')
837 p++;
839 for(i = 0; i<511 && *p != ' ' && *p != '\r'; i++)
840 HttpUrl[i] = *(p++);
841 HttpUrl[i] = '\0';
842 while(*p==' ')
843 p++;
844 HttpVer = h->HttpVer;
845 for(i = 0; i<15 && *p != '\r'; i++)
846 HttpVer[i] = *(p++);
847 HttpVer[i] = '\0';
848 /*DPRINTF(E_INFO, L_HTTP, "HTTP REQUEST : %s %s (%s)\n",
849 HttpCommand, HttpUrl, HttpVer);*/
851 /* set the interface here initially, in case there is no Host header */
852 for(i = 0; i<n_lan_addr; i++)
854 if( (h->clientaddr.s_addr & lan_addr[i].mask.s_addr)
855 == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr))
857 h->iface = i;
858 break;
862 ParseHttpHeaders(h);
864 /* see if we need to wait for remaining data */
865 if( (h->reqflags & FLAG_CHUNKED) )
867 if( h->req_chunklen )
869 h->state = 2;
870 return;
872 char *chunkstart, *chunk, *endptr, *endbuf;
873 chunk = endbuf = chunkstart = h->req_buf + h->req_contentoff;
875 while( (h->req_chunklen = strtol(chunk, &endptr, 16)) && (endptr != chunk) )
877 while(!(endptr[0] == '\r' && endptr[1] == '\n'))
879 endptr++;
881 endptr += 2;
883 memmove(endbuf, endptr, h->req_chunklen);
885 endbuf += h->req_chunklen;
886 chunk = endptr + h->req_chunklen;
888 h->req_contentlen = endbuf - chunkstart;
889 h->req_buflen = endbuf - h->req_buf;
890 h->state = 100;
893 DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf);
894 if(strcmp("POST", HttpCommand) == 0)
896 h->req_command = EPost;
897 ProcessHTTPPOST_upnphttp(h);
899 else if((strcmp("GET", HttpCommand) == 0) || (strcmp("HEAD", HttpCommand) == 0))
901 if( ((strcmp(h->HttpVer, "HTTP/1.1")==0) && !(h->reqflags & FLAG_HOST)) || (h->reqflags & FLAG_INVALID_REQ) )
903 DPRINTF(E_WARN, L_HTTP, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n");
904 Send400(h);
905 return;
907 /* 7.3.33.4 */
908 else if( (h->reqflags & (FLAG_TIMESEEK|FLAG_PLAYSPEED)) &&
909 !(h->reqflags & FLAG_RANGE) )
911 DPRINTF(E_WARN, L_HTTP, "DLNA %s requested, responding ERROR 406\n",
912 h->reqflags&FLAG_TIMESEEK ? "TimeSeek" : "PlaySpeed");
913 Send406(h);
914 return;
916 else if(strcmp("GET", HttpCommand) == 0)
918 h->req_command = EGet;
920 else
922 h->req_command = EHead;
924 if(strcmp(ROOTDESC_PATH, HttpUrl) == 0)
926 /* If it's a Xbox360, we might need a special friendly_name to be recognized */
927 if( (h->req_client == EXbox) && !strchr(friendly_name, ':') )
929 i = strlen(friendly_name);
930 snprintf(friendly_name+i, FRIENDLYNAME_MAX_LEN-i, ": 1");
931 sendXMLdesc(h, genRootDesc);
932 friendly_name[i] = '\0';
934 else if( h->reqflags & FLAG_SAMSUNG_TV )
936 sendXMLdesc(h, genRootDescSamsung);
938 else
940 sendXMLdesc(h, genRootDesc);
943 else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0)
945 sendXMLdesc(h, genContentDirectory);
947 else if(strcmp(CONNECTIONMGR_PATH, HttpUrl) == 0)
949 sendXMLdesc(h, genConnectionManager);
951 else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH, HttpUrl) == 0)
953 sendXMLdesc(h, genX_MS_MediaReceiverRegistrar);
955 else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0)
957 SendResp_dlnafile(h, HttpUrl+12);
959 else if(strncmp(HttpUrl, "/Thumbnails/", 12) == 0)
961 SendResp_thumbnail(h, HttpUrl+12);
963 else if(strncmp(HttpUrl, "/AlbumArt/", 10) == 0)
965 SendResp_albumArt(h, HttpUrl+10);
967 #ifdef TIVO_SUPPORT
968 else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0)
970 if( GETFLAG(TIVO_MASK) )
972 if( *(HttpUrl+12) == '?' )
974 ProcessTiVoCommand(h, HttpUrl+13);
976 else
978 DPRINTF(E_WARN, L_HTTP, "Invalid TiVo request! %s\n", HttpUrl+12);
979 Send404(h);
982 else
984 DPRINTF(E_WARN, L_HTTP, "TiVo request with out TiVo support enabled! %s\n",
985 HttpUrl+12);
986 Send404(h);
989 #endif
990 else if(strncmp(HttpUrl, "/Resized/", 9) == 0)
992 SendResp_resizedimg(h, HttpUrl+9);
994 else if(strncmp(HttpUrl, "/icons/", 7) == 0)
996 SendResp_icon(h, HttpUrl+7);
998 else if(strncmp(HttpUrl, "/Captions/", 10) == 0)
1000 SendResp_caption(h, HttpUrl+10);
1002 else if(strcmp(HttpUrl, "/") == 0)
1004 SendResp_presentation(h);
1006 else
1008 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", HttpUrl);
1009 Send404(h);
1012 else if(strcmp("SUBSCRIBE", HttpCommand) == 0)
1014 h->req_command = ESubscribe;
1015 ProcessHTTPSubscribe_upnphttp(h, HttpUrl);
1017 else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0)
1019 h->req_command = EUnSubscribe;
1020 ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl);
1022 else
1024 DPRINTF(E_WARN, L_HTTP, "Unsupported HTTP Command %s\n", HttpCommand);
1025 Send501(h);
1030 void
1031 Process_upnphttp(struct upnphttp * h)
1033 char buf[2048];
1034 int n;
1035 if(!h)
1036 return;
1037 switch(h->state)
1039 case 0:
1040 n = recv(h->socket, buf, 2048, 0);
1041 if(n<0)
1043 DPRINTF(E_ERROR, L_HTTP, "recv (state0): %s\n", strerror(errno));
1044 h->state = 100;
1046 else if(n==0)
1048 DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n");
1049 h->state = 100;
1051 else
1053 const char * endheaders;
1054 /* if 1st arg of realloc() is null,
1055 * realloc behaves the same as malloc() */
1056 h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen + 1);
1057 memcpy(h->req_buf + h->req_buflen, buf, n);
1058 h->req_buflen += n;
1059 h->req_buf[h->req_buflen] = '\0';
1060 /* search for the string "\r\n\r\n" */
1061 endheaders = findendheaders(h->req_buf, h->req_buflen);
1062 if(endheaders)
1064 h->req_contentoff = endheaders - h->req_buf + 4;
1065 h->req_contentlen = h->req_buflen - h->req_contentoff;
1066 ProcessHttpQuery_upnphttp(h);
1069 break;
1070 case 1:
1071 case 2:
1072 n = recv(h->socket, buf, 2048, 0);
1073 if(n<0)
1075 DPRINTF(E_ERROR, L_HTTP, "recv (state%d): %s\n", h->state, strerror(errno));
1076 h->state = 100;
1078 else if(n==0)
1080 DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n");
1081 h->state = 100;
1083 else
1085 /*fwrite(buf, 1, n, stdout);*/ /* debug */
1086 h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen);
1087 memcpy(h->req_buf + h->req_buflen, buf, n);
1088 h->req_buflen += n;
1089 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
1091 /* Need the struct to point to the realloc'd memory locations */
1092 if( h->state == 1 )
1094 ParseHttpHeaders(h);
1095 ProcessHTTPPOST_upnphttp(h);
1097 else if( h->state == 2 )
1099 ProcessHttpQuery_upnphttp(h);
1103 break;
1104 default:
1105 DPRINTF(E_WARN, L_HTTP, "Unexpected state: %d\n", h->state);
1109 /* with response code and response message
1110 * also allocate enough memory */
1112 void
1113 BuildHeader_upnphttp(struct upnphttp * h, int respcode,
1114 const char * respmsg,
1115 int bodylen)
1117 static const char httpresphead[] =
1118 "%s %d %s\r\n"
1119 "Content-Type: %s\r\n"
1120 "Connection: close\r\n"
1121 "Content-Length: %d\r\n"
1122 "Server: " MINIDLNA_SERVER_STRING "\r\n";
1123 time_t curtime = time(NULL);
1124 char date[30];
1125 int templen;
1126 if(!h->res_buf)
1128 templen = sizeof(httpresphead) + 256 + bodylen;
1129 h->res_buf = (char *)malloc(templen);
1130 h->res_buf_alloclen = templen;
1132 h->res_buflen = snprintf(h->res_buf, h->res_buf_alloclen,
1133 httpresphead, "HTTP/1.1",
1134 respcode, respmsg,
1135 (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"",
1136 bodylen);
1137 /* Additional headers */
1138 if(h->respflags & FLAG_TIMEOUT) {
1139 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1140 h->res_buf_alloclen - h->res_buflen,
1141 "Timeout: Second-");
1142 if(h->req_Timeout) {
1143 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1144 h->res_buf_alloclen - h->res_buflen,
1145 "%d\r\n", h->req_Timeout);
1146 } else {
1147 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1148 h->res_buf_alloclen - h->res_buflen,
1149 "300\r\n");
1152 if(h->respflags & FLAG_SID) {
1153 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1154 h->res_buf_alloclen - h->res_buflen,
1155 "SID: %.*s\r\n", h->req_SIDLen, h->req_SID);
1157 if(h->reqflags & FLAG_LANGUAGE) {
1158 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1159 h->res_buf_alloclen - h->res_buflen,
1160 "Content-Language: en\r\n");
1162 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1163 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1164 h->res_buf_alloclen - h->res_buflen,
1165 "Date: %s\r\n", date);
1166 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1167 h->res_buf_alloclen - h->res_buflen,
1168 "EXT:\r\n");
1169 #if 0 // DLNA
1170 h->res_buflen += snprintf(h->res_buf + h->res_buflen,
1171 h->res_buf_alloclen - h->res_buflen,
1172 "contentFeatures.dlna.org: \r\n");
1173 #endif
1174 h->res_buf[h->res_buflen++] = '\r';
1175 h->res_buf[h->res_buflen++] = '\n';
1176 if(h->res_buf_alloclen < (h->res_buflen + bodylen))
1178 h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen));
1179 h->res_buf_alloclen = h->res_buflen + bodylen;
1183 void
1184 BuildResp2_upnphttp(struct upnphttp * h, int respcode,
1185 const char * respmsg,
1186 const char * body, int bodylen)
1188 BuildHeader_upnphttp(h, respcode, respmsg, bodylen);
1189 if( h->req_command == EHead )
1190 return;
1191 if(body)
1192 memcpy(h->res_buf + h->res_buflen, body, bodylen);
1193 h->res_buflen += bodylen;
1196 /* responding 200 OK ! */
1197 void
1198 BuildResp_upnphttp(struct upnphttp * h,
1199 const char * body, int bodylen)
1201 BuildResp2_upnphttp(h, 200, "OK", body, bodylen);
1204 void
1205 SendResp_upnphttp(struct upnphttp * h)
1207 int n;
1208 DPRINTF(E_DEBUG, L_HTTP, "HTTP RESPONSE: %.*s\n", h->res_buflen, h->res_buf);
1209 n = send(h->socket, h->res_buf, h->res_buflen, 0);
1210 if(n<0)
1212 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno));
1214 else if(n < h->res_buflen)
1216 /* TODO : handle correctly this case */
1217 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
1218 n, h->res_buflen);
1223 send_data(struct upnphttp * h, char * header, size_t size, int flags)
1225 int n;
1227 n = send(h->socket, header, size, flags);
1228 if(n<0)
1230 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno));
1232 else if(n < h->res_buflen)
1234 /* TODO : handle correctly this case */
1235 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
1236 n, h->res_buflen);
1238 else
1240 return 0;
1242 return 1;
1245 void
1246 send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset)
1248 off_t send_size;
1249 off_t ret;
1250 char *buf = NULL;
1251 int try_sendfile = 1;
1253 while( offset < end_offset )
1255 if( try_sendfile )
1257 send_size = ( ((end_offset - offset) < MAX_BUFFER_SIZE) ? (end_offset - offset + 1) : MAX_BUFFER_SIZE);
1258 ret = sendfile(h->socket, sendfd, &offset, send_size);
1259 if( ret == -1 )
1261 DPRINTF(E_DEBUG, L_HTTP, "sendfile error :: error no. %d [%s]\n", errno, strerror(errno));
1262 /* If sendfile isn't supported on the filesystem, don't bother trying to use it again. */
1263 if( errno == EOVERFLOW || errno == EINVAL )
1264 try_sendfile = 0;
1265 else if( errno != EAGAIN )
1266 break;
1268 else
1270 //DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset);
1271 continue;
1274 /* Fall back to regular I/O */
1275 if( !buf )
1276 buf = malloc(MIN_BUFFER_SIZE);
1277 send_size = ( ((end_offset - offset) < MIN_BUFFER_SIZE) ? (end_offset - offset + 1) : MIN_BUFFER_SIZE);
1278 lseek(sendfd, offset, SEEK_SET);
1279 ret = read(sendfd, buf, send_size);
1280 if( ret == -1 ) {
1281 DPRINTF(E_DEBUG, L_HTTP, "read error :: error no. %d [%s]\n", errno, strerror(errno));
1282 if( errno != EAGAIN )
1283 break;
1285 ret = write(h->socket, buf, ret);
1286 if( ret == -1 ) {
1287 DPRINTF(E_DEBUG, L_HTTP, "write error :: error no. %d [%s]\n", errno, strerror(errno));
1288 if( errno != EAGAIN )
1289 break;
1291 offset+=ret;
1293 free(buf);
1296 void
1297 SendResp_icon(struct upnphttp * h, char * icon)
1299 char header[512];
1300 char mime[12] = "image/";
1301 char date[30];
1302 char *data;
1303 int size, ret;
1304 time_t curtime = time(NULL);
1306 if( strcmp(icon, "sm.png") == 0 )
1308 DPRINTF(E_DEBUG, L_HTTP, "Sending small PNG icon\n");
1309 data = (char *)png_sm;
1310 size = sizeof(png_sm)-1;
1311 strcpy(mime+6, "png");
1313 else if( strcmp(icon, "lrg.png") == 0 )
1315 DPRINTF(E_DEBUG, L_HTTP, "Sending large PNG icon\n");
1316 data = (char *)png_lrg;
1317 size = sizeof(png_lrg)-1;
1318 strcpy(mime+6, "png");
1320 else if( strcmp(icon, "sm.jpg") == 0 )
1322 DPRINTF(E_DEBUG, L_HTTP, "Sending small JPEG icon\n");
1323 data = (char *)jpeg_sm;
1324 size = sizeof(jpeg_sm)-1;
1325 strcpy(mime+6, "jpeg");
1327 else if( strcmp(icon, "lrg.jpg") == 0 )
1329 DPRINTF(E_DEBUG, L_HTTP, "Sending large JPEG icon\n");
1330 data = (char *)jpeg_lrg;
1331 size = sizeof(jpeg_lrg)-1;
1332 strcpy(mime+6, "jpeg");
1334 else
1336 DPRINTF(E_WARN, L_HTTP, "Invalid icon request: %s\n", icon);
1337 Send404(h);
1338 return;
1341 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1342 ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n"
1343 "Content-Type: %s\r\n"
1344 "Content-Length: %d\r\n"
1345 "Connection: close\r\n"
1346 "Date: %s\r\n"
1347 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
1348 mime, size, date);
1350 if( send_data(h, header, ret, MSG_MORE) == 0 )
1352 if( h->req_command != EHead )
1353 send_data(h, data, size, 0);
1355 CloseSocket_upnphttp(h);
1358 void
1359 SendResp_albumArt(struct upnphttp * h, char * object)
1361 char header[512];
1362 char *path;
1363 char *dash;
1364 char date[30];
1365 time_t curtime = time(NULL);
1366 off_t size;
1367 int fd;
1368 int ret;
1370 if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) )
1372 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1373 Send406(h);
1374 return;
1377 dash = strchr(object, '-');
1378 if( dash )
1379 *dash = '\0';
1381 path = sql_get_text_field(db, "SELECT PATH from ALBUM_ART where ID = '%s'", object);
1382 if( !path )
1384 DPRINTF(E_WARN, L_HTTP, "ALBUM_ART ID %s not found, responding ERROR 404\n", object);
1385 Send404(h);
1386 return;
1388 DPRINTF(E_INFO, L_HTTP, "Serving album art ID: %s [%s]\n", object, path);
1390 fd = open(path, O_RDONLY);
1391 if( fd < 0 ) {
1392 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path);
1393 sqlite3_free(path);
1394 Send404(h);
1395 return;
1397 sqlite3_free(path);
1398 size = lseek(fd, 0, SEEK_END);
1399 lseek(fd, 0, SEEK_SET);
1401 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1402 ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n"
1403 "Content-Type: image/jpeg\r\n"
1404 "Content-Length: %jd\r\n"
1405 "Connection: close\r\n"
1406 "Date: %s\r\n"
1407 "EXT:\r\n"
1408 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1409 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
1410 "Server: " MINIDLNA_SERVER_STRING "\r\n"
1411 "transferMode.dlna.org: Interactive\r\n\r\n",
1412 (intmax_t)size, date);
1414 if( send_data(h, header, ret, MSG_MORE) == 0 )
1416 if( h->req_command != EHead )
1417 send_file(h, fd, 0, size-1);
1419 close(fd);
1420 CloseSocket_upnphttp(h);
1423 void
1424 SendResp_caption(struct upnphttp * h, char * object)
1426 char header[512];
1427 char *path;
1428 char date[30];
1429 time_t curtime = time(NULL);
1430 off_t size;
1431 int fd, ret;
1433 strip_ext(object);
1434 path = sql_get_text_field(db, "SELECT PATH from CAPTIONS where ID = %s", object);
1435 if( !path )
1437 DPRINTF(E_WARN, L_HTTP, "CAPTION ID %s not found, responding ERROR 404\n", object);
1438 Send404(h);
1439 return;
1441 DPRINTF(E_INFO, L_HTTP, "Serving caption ID: %s [%s]\n", object, path);
1443 fd = open(path, O_RDONLY);
1444 if( fd < 0 ) {
1445 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path);
1446 sqlite3_free(path);
1447 Send404(h);
1448 return;
1450 sqlite3_free(path);
1451 size = lseek(fd, 0, SEEK_END);
1452 lseek(fd, 0, SEEK_SET);
1454 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1455 ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n"
1456 "Content-Type: smi/caption\r\n"
1457 "Content-Length: %jd\r\n"
1458 "Connection: close\r\n"
1459 "Date: %s\r\n"
1460 "EXT:\r\n"
1461 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
1462 (intmax_t)size, date);
1464 if( send_data(h, header, ret, MSG_MORE) == 0 )
1466 if( h->req_command != EHead )
1467 send_file(h, fd, 0, size-1);
1469 close(fd);
1470 CloseSocket_upnphttp(h);
1473 void
1474 SendResp_thumbnail(struct upnphttp * h, char * object)
1476 char header[512];
1477 char *path;
1478 char date[30];
1479 time_t curtime = time(NULL);
1480 int ret;
1481 ExifData *ed;
1482 ExifLoader *l;
1484 if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) )
1486 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1487 Send406(h);
1488 return;
1491 strip_ext(object);
1492 path = sql_get_text_field(db, "SELECT PATH from DETAILS where ID = '%s'", object);
1493 if( !path )
1495 DPRINTF(E_WARN, L_HTTP, "DETAIL ID %s not found, responding ERROR 404\n", object);
1496 Send404(h);
1497 return;
1499 DPRINTF(E_INFO, L_HTTP, "Serving thumbnail for ObjectId: %s [%s]\n", object, path);
1501 if( access(path, F_OK) != 0 )
1503 DPRINTF(E_ERROR, L_HTTP, "Error accessing %s\n", path);
1504 sqlite3_free(path);
1505 return;
1508 l = exif_loader_new();
1509 exif_loader_write_file(l, path);
1510 ed = exif_loader_get_data(l);
1511 exif_loader_unref(l);
1512 sqlite3_free(path);
1514 if( !ed || !ed->size )
1516 Send404(h);
1517 if( ed )
1518 exif_data_unref(ed);
1519 return;
1521 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1522 ret = snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n"
1523 "Content-Type: image/jpeg\r\n"
1524 "Content-Length: %jd\r\n"
1525 "Connection: close\r\n"
1526 "Date: %s\r\n"
1527 "EXT:\r\n"
1528 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1529 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN;DLNA.ORG_CI=1\r\n"
1530 "Server: " MINIDLNA_SERVER_STRING "\r\n"
1531 "transferMode.dlna.org: Interactive\r\n\r\n",
1532 (intmax_t)ed->size, date);
1534 if( send_data(h, header, ret, MSG_MORE) == 0 )
1536 if( h->req_command != EHead )
1537 send_data(h, (char *)ed->data, ed->size, 0);
1539 exif_data_unref(ed);
1540 CloseSocket_upnphttp(h);
1543 void
1544 SendResp_resizedimg(struct upnphttp * h, char * object)
1546 char header[512];
1547 char buf[128];
1548 struct string_s str;
1549 char **result;
1550 char date[30];
1551 char dlna_pn[22];
1552 uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B|DLNA_FLAG_TM_I;
1553 time_t curtime = time(NULL);
1554 int width=640, height=480, dstw, dsth, size;
1555 int srcw, srch;
1556 unsigned char * data = NULL;
1557 char *path, *file_path;
1558 char *resolution;
1559 char *key, *val;
1560 char *saveptr, *item=NULL;
1561 int rotate;
1562 /* Not implemented yet *
1563 char *pixelshape=NULL; */
1564 sqlite_int64 id;
1565 int rows=0, chunked, ret;
1566 image_s *imsrc = NULL, *imdst = NULL;
1567 int scale = 1;
1569 id = strtoll(object, &saveptr, 10);
1570 snprintf(buf, sizeof(buf), "SELECT PATH, RESOLUTION, ROTATION from DETAILS where ID = '%lld'", id);
1571 ret = sql_get_table(db, buf, &result, &rows, NULL);
1572 if( (ret != SQLITE_OK) )
1574 DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id);
1575 Send500(h);
1576 return;
1578 file_path = result[3];
1579 resolution = result[4];
1580 rotate = result[5] ? atoi(result[5]) : 0;
1581 if( !rows || !file_path || !resolution || (access(file_path, F_OK) != 0) )
1583 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
1584 sqlite3_free_table(result);
1585 Send404(h);
1586 return;
1589 if( saveptr )
1590 saveptr = strchr(saveptr, '?');
1591 path = saveptr ? saveptr + 1 : object;
1592 for( item = strtok_r(path, "&,", &saveptr); item != NULL; item = strtok_r(NULL, "&,", &saveptr) )
1594 #ifdef TIVO_SUPPORT
1595 decodeString(item, 1);
1596 #endif
1597 val = item;
1598 key = strsep(&val, "=");
1599 if( !val )
1600 continue;
1601 DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val);
1602 if( strcasecmp(key, "width") == 0 )
1604 width = atoi(val);
1606 else if( strcasecmp(key, "height") == 0 )
1608 height = atoi(val);
1610 else if( strcasecmp(key, "rotation") == 0 )
1612 rotate = (rotate + atoi(val)) % 360;
1613 sql_exec(db, "UPDATE DETAILS set ROTATION = %d where ID = %lld", rotate, id);
1615 /* Not implemented yet *
1616 else if( strcasecmp(key, "pixelshape") == 0 )
1618 pixelshape = val;
1619 } */
1622 #if USE_FORK
1623 pid_t newpid = 0;
1624 newpid = fork();
1625 if( newpid )
1627 CloseSocket_upnphttp(h);
1628 goto resized_error;
1630 #endif
1631 if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) )
1633 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1634 Send406(h);
1635 goto resized_error;
1638 DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %lld [%s]\n", id, file_path);
1639 switch( rotate )
1641 case 90:
1642 ret = sscanf(resolution, "%dx%d", &srch, &srcw);
1643 rotate = ROTATE_90;
1644 break;
1645 case 270:
1646 ret = sscanf(resolution, "%dx%d", &srch, &srcw);
1647 rotate = ROTATE_270;
1648 break;
1649 case 180:
1650 ret = sscanf(resolution, "%dx%d", &srcw, &srch);
1651 rotate = ROTATE_180;
1652 break;
1653 default:
1654 ret = sscanf(resolution, "%dx%d", &srcw, &srch);
1655 rotate = ROTATE_NONE;
1656 break;
1658 if( ret != 2 )
1660 Send500(h);
1661 return;
1663 /* Figure out the best destination resolution we can use */
1664 dstw = width;
1665 dsth = ((((width<<10)/srcw)*srch)>>10);
1666 if( dsth > height )
1668 dsth = height;
1669 dstw = (((height<<10)/srch) * srcw>>10);
1672 if( dstw <= 640 && dsth <= 480 )
1673 strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_SM;");
1674 else if( dstw <= 1024 && dsth <= 768 )
1675 strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_MED;");
1676 else
1677 strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_LRG;");
1679 if( srcw>>4 >= dstw && srch>>4 >= dsth)
1680 scale = 8;
1681 else if( srcw>>3 >= dstw && srch>>3 >= dsth )
1682 scale = 4;
1683 else if( srcw>>2 >= dstw && srch>>2 >= dsth )
1684 scale = 2;
1686 str.data = header;
1687 str.size = sizeof(header);
1688 str.off = 0;
1690 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1691 strcatf(&str, "HTTP/1.1 200 OK\r\n"
1692 "Content-Type: image/jpeg\r\n"
1693 "Connection: close\r\n"
1694 "Date: %s\r\n"
1695 "EXT:\r\n"
1696 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1697 "contentFeatures.dlna.org: %sDLNA.ORG_CI=%X;DLNA.ORG_FLAGS=%08X%024X\r\n"
1698 "Server: " MINIDLNA_SERVER_STRING "\r\n",
1699 date, dlna_pn, 1, dlna_flags, 0);
1700 #if USE_FORK
1701 if( (h->reqflags & FLAG_XFERBACKGROUND) && (setpriority(PRIO_PROCESS, 0, 19) == 0) )
1702 strcatf(&str, "transferMode.dlna.org: Background\r\n");
1703 else
1704 #endif
1705 strcatf(&str, "transferMode.dlna.org: Interactive\r\n");
1707 if( strcmp(h->HttpVer, "HTTP/1.0") == 0 )
1709 chunked = 0;
1710 imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale, rotate);
1712 else
1714 chunked = 1;
1715 strcatf(&str, "Transfer-Encoding: chunked\r\n\r\n");
1718 if( !chunked )
1720 if( !imsrc )
1722 DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path);
1723 Send500(h);
1724 goto resized_error;
1727 imdst = image_resize(imsrc, dstw, dsth);
1728 data = image_save_to_jpeg_buf(imdst, &size);
1730 strcatf(&str, "Content-Length: %d\r\n\r\n", size);
1733 if( (send_data(h, str.data, str.off, 0) == 0) && (h->req_command != EHead) )
1735 if( chunked )
1737 imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale, rotate);
1738 if( !imsrc )
1740 DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path);
1741 Send500(h);
1742 goto resized_error;
1744 imdst = image_resize(imsrc, dstw, dsth);
1745 data = image_save_to_jpeg_buf(imdst, &size);
1747 ret = sprintf(buf, "%x\r\n", size);
1748 send_data(h, buf, ret, MSG_MORE);
1749 send_data(h, (char *)data, size, MSG_MORE);
1750 send_data(h, "\r\n0\r\n\r\n", 7, 0);
1752 else
1754 send_data(h, (char *)data, size, 0);
1757 DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path);
1758 if( imsrc )
1759 image_free(imsrc);
1760 if( imdst )
1761 image_free(imdst);
1762 CloseSocket_upnphttp(h);
1763 resized_error:
1764 sqlite3_free_table(result);
1765 #if USE_FORK
1766 if( !newpid )
1767 _exit(0);
1768 #endif
1771 void
1772 SendResp_dlnafile(struct upnphttp * h, char * object)
1774 char header[1024];
1775 struct string_s str;
1776 char buf[128];
1777 char **result;
1778 int rows, ret;
1779 char date[30];
1780 time_t curtime = time(NULL);
1781 off_t total, offset, size;
1782 sqlite_int64 id;
1783 int sendfh;
1784 uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B;
1785 static struct { sqlite_int64 id;
1786 enum client_types client;
1787 char path[PATH_MAX];
1788 char mime[32];
1789 char dlna[96];
1790 } last_file = { 0, 0 };
1791 #if USE_FORK
1792 pid_t newpid = 0;
1793 #endif
1795 id = strtoll(object, NULL, 10);
1796 if( h->reqflags & FLAG_MS_PFS )
1798 if( strstr(object, "?albumArt=true") )
1800 char *art;
1801 art = sql_get_text_field(db, "SELECT ALBUM_ART from DETAILS where ID = '%lld'", id);
1802 SendResp_albumArt(h, art);
1803 sqlite3_free(art);
1804 return;
1807 if( id != last_file.id || h->req_client != last_file.client )
1809 snprintf(buf, sizeof(buf), "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", id);
1810 ret = sql_get_table(db, buf, &result, &rows, NULL);
1811 if( (ret != SQLITE_OK) )
1813 DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", id);
1814 Send500(h);
1815 return;
1817 if( !rows || !result[3] || !result[4] )
1819 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
1820 sqlite3_free_table(result);
1821 Send404(h);
1822 return;
1824 /* Cache the result */
1825 last_file.id = id;
1826 last_file.client = h->req_client;
1827 strncpy(last_file.path, result[3], sizeof(last_file.path)-1);
1828 if( result[4] )
1830 strncpy(last_file.mime, result[4], sizeof(last_file.mime)-1);
1831 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
1832 if( h->reqflags & FLAG_SAMSUNG )
1834 if( strcmp(last_file.mime+6, "x-matroska") == 0 )
1835 strcpy(last_file.mime+8, "mkv");
1836 /* Samsung TV's such as the A750 can natively support many
1837 Xvid/DivX AVI's however, the DLNA server needs the
1838 mime type to say video/mpeg */
1839 else if( h->req_client == ESamsungSeriesA &&
1840 strcmp(last_file.mime+6, "x-msvideo") == 0 )
1841 strcpy(last_file.mime+6, "mpeg");
1843 /* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */
1844 else if( h->req_client == ESonyBDP )
1846 if( strcmp(last_file.mime+6, "x-matroska") == 0 ||
1847 strcmp(last_file.mime+6, "mpeg") == 0 )
1848 strcpy(last_file.mime+6, "divx");
1851 if( result[5] )
1852 snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s;", result[5]);
1853 else
1854 last_file.dlna[0] = '\0';
1855 sqlite3_free_table(result);
1857 #if USE_FORK
1858 newpid = fork();
1859 if( newpid )
1861 CloseSocket_upnphttp(h);
1862 goto error;
1864 #endif
1866 DPRINTF(E_INFO, L_HTTP, "Serving DetailID: %lld [%s]\n", id, last_file.path);
1868 if( h->reqflags & FLAG_XFERSTREAMING )
1870 if( strncmp(last_file.mime, "image", 5) == 0 )
1872 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1873 Send406(h);
1874 goto error;
1877 else if( h->reqflags & FLAG_XFERINTERACTIVE )
1879 if( h->reqflags & FLAG_REALTIMEINFO )
1881 DPRINTF(E_WARN, L_HTTP, "Bad realTimeInfo flag with Interactive request!\n");
1882 Send400(h);
1883 goto error;
1885 if( strncmp(last_file.mime, "image", 5) != 0 )
1887 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Interactive without an image!\n");
1888 /* Samsung TVs (well, at least the A950) do this for some reason,
1889 * and I don't see them fixing this bug any time soon. */
1890 if( !(h->reqflags & FLAG_SAMSUNG) || GETFLAG(DLNA_STRICT_MASK) )
1892 Send406(h);
1893 goto error;
1898 offset = h->req_RangeStart;
1899 sendfh = open(last_file.path, O_RDONLY);
1900 if( sendfh < 0 ) {
1901 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", last_file.path);
1902 Send404(h);
1903 goto error;
1905 size = lseek(sendfh, 0, SEEK_END);
1906 lseek(sendfh, 0, SEEK_SET);
1908 str.data = header;
1909 str.size = sizeof(header);
1910 str.off = 0;
1912 strcatf(&str, "HTTP/1.1 20%c OK\r\n"
1913 "Content-Type: %s\r\n",
1914 (h->reqflags & FLAG_RANGE ? '6' : '0'),
1915 last_file.mime);
1916 if( h->reqflags & FLAG_RANGE )
1918 if( !h->req_RangeEnd || h->req_RangeEnd == size )
1920 h->req_RangeEnd = size - 1;
1922 if( (h->req_RangeStart > h->req_RangeEnd) || (h->req_RangeStart < 0) )
1924 DPRINTF(E_WARN, L_HTTP, "Specified range was invalid!\n");
1925 Send400(h);
1926 close(sendfh);
1927 goto error;
1929 if( h->req_RangeEnd >= size )
1931 DPRINTF(E_WARN, L_HTTP, "Specified range was outside file boundaries!\n");
1932 Send416(h);
1933 close(sendfh);
1934 goto error;
1937 total = h->req_RangeEnd - h->req_RangeStart + 1;
1938 strcatf(&str, "Content-Length: %jd\r\n"
1939 "Content-Range: bytes %jd-%jd/%jd\r\n",
1940 (intmax_t)total, (intmax_t)h->req_RangeStart,
1941 (intmax_t)h->req_RangeEnd, (intmax_t)size);
1943 else
1945 h->req_RangeEnd = size - 1;
1946 total = size;
1947 strcatf(&str, "Content-Length: %jd\r\n", (intmax_t)total);
1950 #if USE_FORK
1951 if( (h->reqflags & FLAG_XFERBACKGROUND) && (setpriority(PRIO_PROCESS, 0, 19) == 0) )
1952 strcatf(&str, "transferMode.dlna.org: Background\r\n");
1953 else
1954 #endif
1955 if( strncmp(last_file.mime, "image", 5) == 0 )
1956 strcatf(&str, "transferMode.dlna.org: Interactive\r\n");
1957 else
1958 strcatf(&str, "transferMode.dlna.org: Streaming\r\n");
1960 switch( *last_file.mime )
1962 case 'i':
1963 dlna_flags |= DLNA_FLAG_TM_I;
1964 break;
1965 case 'a':
1966 case 'v':
1967 default:
1968 dlna_flags |= DLNA_FLAG_TM_S;
1969 break;
1972 if( h->reqflags & FLAG_CAPTION )
1974 if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%lld'", id) > 0 )
1975 strcatf(&str, "CaptionInfo.sec: http://%s:%d/Captions/%lld.srt\r\n",
1976 lan_addr[h->iface].str, runtime_vars.port, id);
1979 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1980 strcatf(&str, "Accept-Ranges: bytes\r\n"
1981 "Connection: close\r\n"
1982 "Date: %s\r\n"
1983 "EXT:\r\n"
1984 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1985 "contentFeatures.dlna.org: %sDLNA.ORG_OP=%02X;DLNA.ORG_CI=%X;DLNA.ORG_FLAGS=%08X%024X\r\n"
1986 "Server: " MINIDLNA_SERVER_STRING "\r\n\r\n",
1987 date, last_file.dlna, 1, 0, dlna_flags, 0);
1989 //DEBUG DPRINTF(E_DEBUG, L_HTTP, "RESPONSE: %s\n", str.data);
1990 if( send_data(h, str.data, str.off, MSG_MORE) == 0 )
1992 if( h->req_command != EHead )
1993 send_file(h, sendfh, offset, h->req_RangeEnd);
1995 close(sendfh);
1997 CloseSocket_upnphttp(h);
1998 error:
1999 #if USE_FORK
2000 if( !newpid )
2001 _exit(0);
2002 #endif