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