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.
53 #include <sys/types.h>
54 #include <sys/socket.h>
55 #include <sys/param.h>
59 #include "upnpdescgen.h"
60 #include "minidlnapath.h"
62 #include "upnpevents.h"
64 #include <sys/types.h>
68 #include <sys/sendfile.h>
69 #include <arpa/inet.h>
71 #include "upnpglobalvars.h"
73 #include "getifaddr.h"
74 #include "image_utils.h"
77 #include <libexif/exif-loader.h>
79 #include "tivo_utils.h"
80 #include "tivo_commands.h"
82 #define MAX_BUFFER_SIZE 4194304 // 4MB -- Too much?
83 //#define MAX_BUFFER_SIZE 2147483647 // 2GB -- Too much?
84 #define MIN_BUFFER_SIZE 65536
91 struct upnphttp
* ret
;
94 ret
= (struct upnphttp
*)malloc(sizeof(struct upnphttp
));
97 memset(ret
, 0, sizeof(struct upnphttp
));
103 CloseSocket_upnphttp(struct upnphttp
* h
)
105 if(close(h
->socket
) < 0)
107 DPRINTF(E_ERROR
, L_HTTP
, "CloseSocket_upnphttp: close(%d): %s\n", h
->socket
, strerror(errno
));
114 Delete_upnphttp(struct upnphttp
* h
)
119 CloseSocket_upnphttp(h
);
127 SearchClientCache(struct in_addr addr
, int quiet
)
130 for( i
=0; i
<CLIENT_CACHE_SLOTS
; i
++ )
132 if( clients
[i
].addr
.s_addr
== addr
.s_addr
)
134 /* Invalidate this client cache if it's older than 1 hour */
135 if( (time(NULL
) - clients
[i
].age
) > 3600 )
137 unsigned char mac
[6];
138 if( get_remote_mac(addr
, mac
) == 0 &&
139 memcmp(mac
, clients
[i
].mac
, 6) == 0 )
141 /* Same MAC as last time when we were able to identify the client,
142 * so extend the timeout by another hour. */
143 clients
[i
].age
= time(NULL
);
147 memset(&clients
[i
], 0, sizeof(struct client_cache_s
));
152 DPRINTF(E_DEBUG
, L_HTTP
, "Client found in cache. [type %d/entry %d]\n",
160 /* parse HttpHeaders of the REQUEST */
162 ParseHttpHeaders(struct upnphttp
* h
)
169 /* TODO : check if req_buf, contentoff are ok */
170 while(line
< (h
->req_buf
+ h
->req_contentoff
))
172 colon
= strchr(line
, ':');
175 if(strncasecmp(line
, "Content-Length", 14)==0)
178 while(*p
< '0' || *p
> '9')
180 h
->req_contentlen
= atoi(p
);
182 else if(strncasecmp(line
, "SOAPAction", 10)==0)
186 while(*p
== ':' || *p
== ' ' || *p
== '\t')
192 if((p
[0] == '"' && p
[n
-1] == '"')
193 || (p
[0] == '\'' && p
[n
-1] == '\''))
197 h
->req_soapAction
= p
;
198 h
->req_soapActionLen
= n
;
200 else if(strncasecmp(line
, "Callback", 8)==0)
203 while(*p
!= '<' && *p
!= '\r' )
206 while(p
[n
] != '>' && p
[n
] != '\r' )
208 h
->req_Callback
= p
+ 1;
209 h
->req_CallbackLen
= MAX(0, n
- 1);
211 else if(strncasecmp(line
, "SID", 3)==0)
213 //zqiu: fix bug for test 4.0.5
214 //Skip extra headers like "SIDHEADER: xxxxxx xxx"
215 for(p
=line
+3;p
<colon
;p
++)
219 p
= NULL
; //unexpected header
228 while(!isspace(p
[n
]))
234 /* Timeout: Seconds-nnnn */
236 Recommended. Requested duration until subscription expires,
237 either number of seconds or infinite. Recommendation
238 by a UPnP Forum working committee. Defined by UPnP vendor.
239 Consists of the keyword "Second-" followed (without an
240 intervening space) by either an integer or the keyword "infinite". */
241 else if(strncasecmp(line
, "Timeout", 7)==0)
246 if(strncasecmp(p
, "Second-", 7)==0) {
247 h
->req_Timeout
= atoi(p
+7);
250 // Range: bytes=xxx-yyy
251 else if(strncasecmp(line
, "Range", 5)==0)
256 if(strncasecmp(p
, "bytes=", 6)==0) {
257 h
->reqflags
|= FLAG_RANGE
;
258 h
->req_RangeStart
= strtoll(p
+6, &colon
, 10);
259 h
->req_RangeEnd
= colon
? atoll(colon
+1) : 0;
260 DPRINTF(E_DEBUG
, L_HTTP
, "Range Start-End: %lld - %lld\n",
261 h
->req_RangeStart
, h
->req_RangeEnd
?h
->req_RangeEnd
:-1);
264 else if(strncasecmp(line
, "Host", 4)==0)
267 h
->reqflags
|= FLAG_HOST
;
271 for(n
= 0; n
<n_lan_addr
; n
++)
273 for(i
=0; lan_addr
[n
].str
[i
]; i
++)
275 if(lan_addr
[n
].str
[i
] != p
[i
])
278 if(!lan_addr
[n
].str
[i
])
285 else if(strncasecmp(line
, "User-Agent", 10)==0)
287 /* Skip client detection if we already detected it. */
293 if(strncasecmp(p
, "Xbox/", 5)==0)
295 h
->req_client
= EXbox
;
296 h
->reqflags
|= FLAG_MIME_AVI_AVI
;
297 h
->reqflags
|= FLAG_MS_PFS
;
299 else if(strncmp(p
, "PLAYSTATION", 11)==0)
301 h
->req_client
= EPS3
;
302 h
->reqflags
|= FLAG_DLNA
;
303 h
->reqflags
|= FLAG_MIME_AVI_DIVX
;
305 else if(strncmp(p
, "SEC_HHP_", 8)==0)
307 h
->req_client
= ESamsungTV
;
308 h
->reqflags
|= FLAG_SAMSUNG
;
309 h
->reqflags
|= FLAG_DLNA
;
310 h
->reqflags
|= FLAG_NO_RESIZE
;
312 else if(strncmp(p
, "SamsungWiselinkPro", 18)==0 ||
313 strncmp(p
, "SEC_HHP_TV", 10)==0)
315 h
->req_client
= ESamsungSeriesA
;
316 h
->reqflags
|= FLAG_SAMSUNG
;
317 h
->reqflags
|= FLAG_DLNA
;
318 h
->reqflags
|= FLAG_NO_RESIZE
;
320 else if(strstrc(p
, "bridgeCo-DMP/3", '\r'))
322 h
->req_client
= EDenonReceiver
;
323 h
->reqflags
|= FLAG_DLNA
;
325 else if(strstrc(p
, "fbxupnpav/", '\r'))
327 h
->req_client
= EFreeBox
;
329 else if(strncmp(p
, "SMP8634", 7)==0)
331 h
->req_client
= EPopcornHour
;
332 h
->reqflags
|= FLAG_MIME_FLAC_FLAC
;
334 else if(strstrc(p
, "Microsoft-IPTV-Client", '\r'))
336 h
->req_client
= EMediaRoom
;
337 h
->reqflags
|= FLAG_MS_PFS
;
339 else if(strstrc(p
, "LGE_DLNA_SDK", '\r'))
341 h
->req_client
= ELGDevice
;
342 h
->reqflags
|= FLAG_DLNA
;
344 else if(strncmp(p
, "Verismo,", 8)==0)
346 h
->req_client
= ENetgearEVA2000
;
347 h
->reqflags
|= FLAG_MS_PFS
;
349 else if(strstrc(p
, "UPnP/1.0 DLNADOC/1.50 Intel_SDK_for_UPnP_devices/1.2", '\r'))
351 h
->req_client
= EToshibaTV
;
352 h
->reqflags
|= FLAG_DLNA
;
354 else if(strstrc(p
, "DLNADOC/1.50", '\r'))
356 h
->req_client
= EStandardDLNA150
;
357 h
->reqflags
|= FLAG_DLNA
;
358 h
->reqflags
|= FLAG_MIME_AVI_AVI
;
361 else if(strncasecmp(line
, "X-AV-Client-Info", 16)==0)
363 /* Skip client detection if we already detected it. */
364 if( h
->req_client
&& h
->req_client
< EStandardDLNA150
)
369 if(strstrc(p
, "PLAYSTATION 3", '\r'))
371 h
->req_client
= EPS3
;
372 h
->reqflags
|= FLAG_DLNA
;
373 h
->reqflags
|= FLAG_MIME_AVI_DIVX
;
375 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Blu-ray Disc Player"; mv="2.0" */
376 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BLU-RAY HOME THEATRE SYSTEM"; mv="2.0"; */
377 /* Sony SMP-100 needs the same treatment as their BDP-S370 */
378 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Media Player"; mv="2.0" */
379 else if(strstrc(p
, "Blu-ray Disc Player", '\r') ||
380 strstrc(p
, "BLU-RAY HOME THEATRE SYSTEM", '\r') ||
381 strstrc(p
, "Media Player", '\r'))
383 h
->req_client
= ESonyBDP
;
384 h
->reqflags
|= FLAG_DLNA
;
386 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BRAVIA KDL-40EX503"; mv="1.7"; */
387 /* X-AV-Client-Info: av=5.0; hn=""; cn="Sony Corporation"; mn="INTERNET TV NSX-40GT 1"; mv="0.1"; */
388 else if(strstrc(p
, "BRAVIA", '\r') ||
389 strstrc(p
, "INTERNET TV", '\r'))
391 h
->req_client
= ESonyBravia
;
392 h
->reqflags
|= FLAG_DLNA
;
395 else if(strncasecmp(line
, "Transfer-Encoding", 17)==0)
400 if(strncasecmp(p
, "chunked", 7)==0)
402 h
->reqflags
|= FLAG_CHUNKED
;
405 else if(strncasecmp(line
, "getcontentFeatures.dlna.org", 27)==0)
410 if( (*p
!= '1') || !isspace(p
[1]) )
411 h
->reqflags
|= FLAG_INVALID_REQ
;
413 else if(strncasecmp(line
, "TimeSeekRange.dlna.org", 22)==0)
415 h
->reqflags
|= FLAG_TIMESEEK
;
417 else if(strncasecmp(line
, "PlaySpeed.dlna.org", 18)==0)
419 h
->reqflags
|= FLAG_PLAYSPEED
;
421 else if(strncasecmp(line
, "realTimeInfo.dlna.org", 21)==0)
423 h
->reqflags
|= FLAG_REALTIMEINFO
;
425 else if(strncasecmp(line
, "transferMode.dlna.org", 21)==0)
430 if(strncasecmp(p
, "Streaming", 9)==0)
432 h
->reqflags
|= FLAG_XFERSTREAMING
;
434 if(strncasecmp(p
, "Interactive", 11)==0)
436 h
->reqflags
|= FLAG_XFERINTERACTIVE
;
438 if(strncasecmp(p
, "Background", 10)==0)
440 h
->reqflags
|= FLAG_XFERBACKGROUND
;
443 else if(strncasecmp(line
, "getCaptionInfo.sec", 18)==0)
445 h
->reqflags
|= FLAG_CAPTION
;
449 while(!(line
[0] == '\r' && line
[1] == '\n'))
453 if( h
->reqflags
& FLAG_CHUNKED
)
456 h
->req_chunklen
= -1;
457 if( h
->req_buflen
<= h
->req_contentoff
)
459 while( (line
< (h
->req_buf
+ h
->req_buflen
)) &&
460 (h
->req_chunklen
= strtol(line
, &endptr
, 16)) &&
463 while(!(endptr
[0] == '\r' && endptr
[1] == '\n'))
467 line
= endptr
+h
->req_chunklen
+2;
472 h
->req_chunklen
= -1;
476 /* If the client type wasn't found, search the cache.
477 * This is done because a lot of clients like to send a
478 * different User-Agent with different types of requests. */
479 n
= SearchClientCache(h
->clientaddr
, 0);
482 /* Add this client to the cache if it's not there already. */
485 for( n
=0; n
<CLIENT_CACHE_SLOTS
; n
++ )
487 if( clients
[n
].addr
.s_addr
)
489 get_remote_mac(h
->clientaddr
, clients
[n
].mac
);
490 clients
[n
].addr
= h
->clientaddr
;
491 DPRINTF(E_DEBUG
, L_HTTP
, "Added client [%d/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n",
492 h
->req_client
, inet_ntoa(clients
[n
].addr
),
493 clients
[n
].mac
[0], clients
[n
].mac
[1], clients
[n
].mac
[2],
494 clients
[n
].mac
[3], clients
[n
].mac
[4], clients
[n
].mac
[5], n
);
498 else if( (clients
[n
].type
< EStandardDLNA150
&& h
->req_client
== EStandardDLNA150
) ||
499 (clients
[n
].type
== ESamsungSeriesB
&& h
->req_client
== ESamsungSeriesA
) )
501 /* If we know the client and our new detection is generic, use our cached info */
502 /* If we detected a Samsung Series B earlier, don't overwrite it with Series A info */
503 h
->reqflags
|= clients
[n
].flags
;
504 h
->req_client
= clients
[n
].type
;
507 clients
[n
].type
= h
->req_client
;
508 clients
[n
].flags
= h
->reqflags
& 0xFFF00000;
509 clients
[n
].age
= time(NULL
);
513 h
->reqflags
|= clients
[n
].flags
;
514 h
->req_client
= clients
[n
].type
;
518 /* very minimalistic 400 error message */
520 Send400(struct upnphttp
* h
)
522 static const char body400
[] =
523 "<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>"
524 "<BODY><H1>Bad Request</H1>The request is invalid"
525 " for this HTTP version.</BODY></HTML>\r\n";
526 h
->respflags
= FLAG_HTML
;
527 BuildResp2_upnphttp(h
, 400, "Bad Request",
528 body400
, sizeof(body400
) - 1);
529 SendResp_upnphttp(h
);
530 CloseSocket_upnphttp(h
);
533 /* very minimalistic 404 error message */
535 Send404(struct upnphttp
* h
)
537 static const char body404
[] =
538 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
539 "<BODY><H1>Not Found</H1>The requested URL was not found"
540 " on this server.</BODY></HTML>\r\n";
541 h
->respflags
= FLAG_HTML
;
542 BuildResp2_upnphttp(h
, 404, "Not Found",
543 body404
, sizeof(body404
) - 1);
544 SendResp_upnphttp(h
);
545 CloseSocket_upnphttp(h
);
548 /* very minimalistic 406 error message */
550 Send406(struct upnphttp
* h
)
552 static const char body406
[] =
553 "<HTML><HEAD><TITLE>406 Not Acceptable</TITLE></HEAD>"
554 "<BODY><H1>Not Acceptable</H1>An unsupported operation"
555 " was requested.</BODY></HTML>\r\n";
556 h
->respflags
= FLAG_HTML
;
557 BuildResp2_upnphttp(h
, 406, "Not Acceptable",
558 body406
, sizeof(body406
) - 1);
559 SendResp_upnphttp(h
);
560 CloseSocket_upnphttp(h
);
563 /* very minimalistic 416 error message */
565 Send416(struct upnphttp
* h
)
567 static const char body416
[] =
568 "<HTML><HEAD><TITLE>416 Requested Range Not Satisfiable</TITLE></HEAD>"
569 "<BODY><H1>Requested Range Not Satisfiable</H1>The requested range"
570 " was outside the file's size.</BODY></HTML>\r\n";
571 h
->respflags
= FLAG_HTML
;
572 BuildResp2_upnphttp(h
, 416, "Requested Range Not Satisfiable",
573 body416
, sizeof(body416
) - 1);
574 SendResp_upnphttp(h
);
575 CloseSocket_upnphttp(h
);
578 /* very minimalistic 500 error message */
580 Send500(struct upnphttp
* h
)
582 static const char body500
[] =
583 "<HTML><HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>"
584 "<BODY><H1>Internal Server Error</H1>Server encountered "
585 "and Internal Error.</BODY></HTML>\r\n";
586 h
->respflags
= FLAG_HTML
;
587 BuildResp2_upnphttp(h
, 500, "Internal Server Errror",
588 body500
, sizeof(body500
) - 1);
589 SendResp_upnphttp(h
);
590 CloseSocket_upnphttp(h
);
593 /* very minimalistic 501 error message */
595 Send501(struct upnphttp
* h
)
597 static const char body501
[] =
598 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
599 "<BODY><H1>Not Implemented</H1>The HTTP Method "
600 "is not implemented by this server.</BODY></HTML>\r\n";
601 h
->respflags
= FLAG_HTML
;
602 BuildResp2_upnphttp(h
, 501, "Not Implemented",
603 body501
, sizeof(body501
) - 1);
604 SendResp_upnphttp(h
);
605 CloseSocket_upnphttp(h
);
609 findendheaders(const char * s
, int len
)
613 if(s
[0]=='\r' && s
[1]=='\n' && s
[2]=='\r' && s
[3]=='\n')
620 /* Sends the description generated by the parameter */
622 sendXMLdesc(struct upnphttp
* h
, char * (f
)(int *))
629 DPRINTF(E_ERROR
, L_HTTP
, "Failed to generate XML description\n");
633 BuildResp_upnphttp(h
, desc
, len
);
634 SendResp_upnphttp(h
);
635 CloseSocket_upnphttp(h
);
639 /* ProcessHTTPPOST_upnphttp()
640 * executes the SOAP query if it is possible */
642 ProcessHTTPPOST_upnphttp(struct upnphttp
* h
)
644 if((h
->req_buflen
- h
->req_contentoff
) >= h
->req_contentlen
)
646 if(h
->req_soapAction
)
648 /* we can process the request */
649 DPRINTF(E_DEBUG
, L_HTTP
, "SOAPAction: %.*s\n", h
->req_soapActionLen
, h
->req_soapAction
);
652 h
->req_soapActionLen
);
656 static const char err400str
[] =
657 "<html><body>Bad request</body></html>";
658 DPRINTF(E_WARN
, L_HTTP
, "No SOAPAction in HTTP headers\n");
659 h
->respflags
= FLAG_HTML
;
660 BuildResp2_upnphttp(h
, 400, "Bad Request",
661 err400str
, sizeof(err400str
) - 1);
662 SendResp_upnphttp(h
);
663 CloseSocket_upnphttp(h
);
668 /* waiting for remaining data */
674 ProcessHTTPSubscribe_upnphttp(struct upnphttp
* h
, const char * path
)
677 DPRINTF(E_DEBUG
, L_HTTP
, "ProcessHTTPSubscribe %s\n", path
);
678 DPRINTF(E_DEBUG
, L_HTTP
, "Callback '%.*s' Timeout=%d\n",
679 h
->req_CallbackLen
, h
->req_Callback
, h
->req_Timeout
);
680 DPRINTF(E_DEBUG
, L_HTTP
, "SID '%.*s'\n", h
->req_SIDLen
, h
->req_SID
);
681 if(!h
->req_Callback
&& !h
->req_SID
) {
682 /* Missing or invalid CALLBACK : 412 Precondition Failed.
683 * If CALLBACK header is missing or does not contain a valid HTTP URL,
684 * the publisher must respond with HTTP error 412 Precondition Failed*/
685 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
686 SendResp_upnphttp(h
);
687 CloseSocket_upnphttp(h
);
689 /* - add to the subscriber list
690 * - respond HTTP/x.x 200 OK
691 * - Send the initial event message */
692 /* Server:, SID:; Timeout: Second-(xx|infinite) */
693 if(h
->req_Callback
) {
694 sid
= upnpevents_addSubscriber(path
, h
->req_Callback
,
695 h
->req_CallbackLen
, h
->req_Timeout
);
696 h
->respflags
= FLAG_TIMEOUT
;
698 DPRINTF(E_DEBUG
, L_HTTP
, "generated sid=%s\n", sid
);
699 h
->respflags
|= FLAG_SID
;
701 h
->req_SIDLen
= strlen(sid
);
703 BuildResp_upnphttp(h
, 0, 0);
705 /* subscription renew */
707 412 Precondition Failed. If a SID does not correspond to a known,
708 un-expired subscription, the publisher must respond
709 with HTTP error 412 Precondition Failed. */
710 if(renewSubscription(h
->req_SID
, h
->req_SIDLen
, h
->req_Timeout
) < 0) {
711 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
713 /* A DLNA device must enforce a 5 minute timeout */
714 h
->respflags
= FLAG_TIMEOUT
;
715 h
->req_Timeout
= 300;
716 h
->respflags
|= FLAG_SID
;
717 BuildResp_upnphttp(h
, 0, 0);
720 SendResp_upnphttp(h
);
721 CloseSocket_upnphttp(h
);
726 ProcessHTTPUnSubscribe_upnphttp(struct upnphttp
* h
, const char * path
)
728 DPRINTF(E_DEBUG
, L_HTTP
, "ProcessHTTPUnSubscribe %s\n", path
);
729 DPRINTF(E_DEBUG
, L_HTTP
, "SID '%.*s'\n", h
->req_SIDLen
, h
->req_SID
);
730 /* Remove from the list */
731 if(upnpevents_removeSubscriber(h
->req_SID
, h
->req_SIDLen
) < 0) {
732 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
734 BuildResp_upnphttp(h
, 0, 0);
736 SendResp_upnphttp(h
);
737 CloseSocket_upnphttp(h
);
740 /* Parse and process Http Query
741 * called once all the HTTP headers have been received. */
743 ProcessHttpQuery_upnphttp(struct upnphttp
* h
)
745 char HttpCommand
[16];
753 for(i
= 0; i
<15 && *p
!= ' ' && *p
!= '\r'; i
++)
754 HttpCommand
[i
] = *(p
++);
755 HttpCommand
[i
] = '\0';
758 if(strncmp(p
, "http://", 7) == 0)
764 for(i
= 0; i
<511 && *p
!= ' ' && *p
!= '\r'; i
++)
769 HttpVer
= h
->HttpVer
;
770 for(i
= 0; i
<15 && *p
!= '\r'; i
++)
773 /*DPRINTF(E_INFO, L_HTTP, "HTTP REQUEST : %s %s (%s)\n",
774 HttpCommand, HttpUrl, HttpVer);*/
777 /* see if we need to wait for remaining data */
778 if( (h
->reqflags
& FLAG_CHUNKED
) )
780 if( h
->req_chunklen
)
785 char *chunkstart
, *chunk
, *endptr
, *endbuf
;
786 chunk
= endbuf
= chunkstart
= h
->req_buf
+ h
->req_contentoff
;
788 while( (h
->req_chunklen
= strtol(chunk
, &endptr
, 16)) && (endptr
!= chunk
) )
790 while(!(endptr
[0] == '\r' && endptr
[1] == '\n'))
796 memmove(endbuf
, endptr
, h
->req_chunklen
);
798 endbuf
+= h
->req_chunklen
;
799 chunk
= endptr
+ h
->req_chunklen
;
801 h
->req_contentlen
= endbuf
- chunkstart
;
802 h
->req_buflen
= endbuf
- h
->req_buf
;
806 DPRINTF(E_DEBUG
, L_HTTP
, "HTTP REQUEST: %.*s\n", h
->req_buflen
, h
->req_buf
);
807 if(strcmp("POST", HttpCommand
) == 0)
809 h
->req_command
= EPost
;
810 ProcessHTTPPOST_upnphttp(h
);
812 else if((strcmp("GET", HttpCommand
) == 0) || (strcmp("HEAD", HttpCommand
) == 0))
814 if( ((strcmp(h
->HttpVer
, "HTTP/1.1")==0) && !(h
->reqflags
& FLAG_HOST
)) || (h
->reqflags
& FLAG_INVALID_REQ
) )
816 DPRINTF(E_WARN
, L_HTTP
, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n");
821 else if( ((h
->reqflags
& FLAG_TIMESEEK
) || (h
->reqflags
& FLAG_PLAYSPEED
)) &&
822 !(h
->reqflags
& FLAG_RANGE
) )
824 DPRINTF(E_WARN
, L_HTTP
, "DLNA %s requested, responding ERROR 406\n",
825 h
->reqflags
&FLAG_TIMESEEK
? "TimeSeek" : "PlaySpeed");
830 else if(strcmp("GET", HttpCommand
) == 0)
832 h
->req_command
= EGet
;
836 h
->req_command
= EHead
;
838 if(strcmp(ROOTDESC_PATH
, HttpUrl
) == 0)
840 /* If it's a Xbox360, we might need a special friendly_name to be recognized */
841 if( (h
->req_client
== EXbox
) && !strchr(friendly_name
, ':') )
843 i
= strlen(friendly_name
);
844 snprintf(friendly_name
+i
, FRIENDLYNAME_MAX_LEN
-i
, ": 1");
845 sendXMLdesc(h
, genRootDesc
);
846 friendly_name
[i
] = '\0';
850 sendXMLdesc(h
, genRootDesc
);
853 else if(strcmp(CONTENTDIRECTORY_PATH
, HttpUrl
) == 0)
855 sendXMLdesc(h
, genContentDirectory
);
857 else if(strcmp(CONNECTIONMGR_PATH
, HttpUrl
) == 0)
859 sendXMLdesc(h
, genConnectionManager
);
861 else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH
, HttpUrl
) == 0)
863 sendXMLdesc(h
, genX_MS_MediaReceiverRegistrar
);
865 else if(strncmp(HttpUrl
, "/MediaItems/", 12) == 0)
867 SendResp_dlnafile(h
, HttpUrl
+12);
868 CloseSocket_upnphttp(h
);
870 else if(strncmp(HttpUrl
, "/Thumbnails/", 12) == 0)
872 SendResp_thumbnail(h
, HttpUrl
+12);
874 else if(strncmp(HttpUrl
, "/AlbumArt/", 10) == 0)
876 SendResp_albumArt(h
, HttpUrl
+10);
877 CloseSocket_upnphttp(h
);
880 else if(strncmp(HttpUrl
, "/TiVoConnect", 12) == 0)
882 if( GETFLAG(TIVO_MASK
) )
884 if( *(HttpUrl
+12) == '?' )
886 ProcessTiVoCommand(h
, HttpUrl
+13);
890 DPRINTF(E_WARN
, L_HTTP
, "Invalid TiVo request! %s\n", HttpUrl
+12);
896 DPRINTF(E_WARN
, L_HTTP
, "TiVo request with out TiVo support enabled! %s\n",
902 else if(strncmp(HttpUrl
, "/Resized/", 9) == 0)
904 SendResp_resizedimg(h
, HttpUrl
+9);
905 CloseSocket_upnphttp(h
);
907 else if(strncmp(HttpUrl
, "/icons/", 7) == 0)
909 SendResp_icon(h
, HttpUrl
+7);
910 CloseSocket_upnphttp(h
);
912 else if(strncmp(HttpUrl
, "/Captions/", 10) == 0)
914 SendResp_caption(h
, HttpUrl
+10);
915 CloseSocket_upnphttp(h
);
919 DPRINTF(E_WARN
, L_HTTP
, "%s not found, responding ERROR 404\n", HttpUrl
);
923 else if(strcmp("SUBSCRIBE", HttpCommand
) == 0)
925 h
->req_command
= ESubscribe
;
926 ProcessHTTPSubscribe_upnphttp(h
, HttpUrl
);
928 else if(strcmp("UNSUBSCRIBE", HttpCommand
) == 0)
930 h
->req_command
= EUnSubscribe
;
931 ProcessHTTPUnSubscribe_upnphttp(h
, HttpUrl
);
935 DPRINTF(E_WARN
, L_HTTP
, "Unsupported HTTP Command %s\n", HttpCommand
);
942 Process_upnphttp(struct upnphttp
* h
)
951 n
= recv(h
->socket
, buf
, 2048, 0);
954 DPRINTF(E_ERROR
, L_HTTP
, "recv (state0): %s\n", strerror(errno
));
959 DPRINTF(E_WARN
, L_HTTP
, "HTTP Connection closed unexpectedly\n");
964 const char * endheaders
;
965 /* if 1st arg of realloc() is null,
966 * realloc behaves the same as malloc() */
967 h
->req_buf
= (char *)realloc(h
->req_buf
, n
+ h
->req_buflen
+ 1);
968 memcpy(h
->req_buf
+ h
->req_buflen
, buf
, n
);
970 h
->req_buf
[h
->req_buflen
] = '\0';
971 /* search for the string "\r\n\r\n" */
972 endheaders
= findendheaders(h
->req_buf
, h
->req_buflen
);
975 h
->req_contentoff
= endheaders
- h
->req_buf
+ 4;
976 h
->req_contentlen
= h
->req_buflen
- h
->req_contentoff
;
977 ProcessHttpQuery_upnphttp(h
);
983 n
= recv(h
->socket
, buf
, 2048, 0);
986 DPRINTF(E_ERROR
, L_HTTP
, "recv (state%d): %s\n", h
->state
, strerror(errno
));
991 DPRINTF(E_WARN
, L_HTTP
, "HTTP Connection closed unexpectedly\n");
996 /*fwrite(buf, 1, n, stdout);*/ /* debug */
997 h
->req_buf
= (char *)realloc(h
->req_buf
, n
+ h
->req_buflen
);
998 memcpy(h
->req_buf
+ h
->req_buflen
, buf
, n
);
1000 if((h
->req_buflen
- h
->req_contentoff
) >= h
->req_contentlen
)
1002 /* Need the struct to point to the realloc'd memory locations */
1005 ParseHttpHeaders(h
);
1006 ProcessHTTPPOST_upnphttp(h
);
1008 else if( h
->state
== 2 )
1010 ProcessHttpQuery_upnphttp(h
);
1016 DPRINTF(E_WARN
, L_HTTP
, "Unexpected state: %d\n", h
->state
);
1020 static const char httpresphead
[] =
1022 "Content-Type: %s\r\n"
1023 "Connection: close\r\n"
1024 "Content-Length: %d\r\n"
1025 "Server: " MINIDLNA_SERVER_STRING
"\r\n"
1026 // "Accept-Ranges: bytes\r\n"
1029 "<?xml version=\"1.0\"?>\n"
1030 "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
1031 "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1037 /* with response code and response message
1038 * also allocate enough memory */
1041 BuildHeader_upnphttp(struct upnphttp
* h
, int respcode
,
1042 const char * respmsg
,
1048 templen
= sizeof(httpresphead
) + 192 + bodylen
;
1049 h
->res_buf
= (char *)malloc(templen
);
1050 h
->res_buf_alloclen
= templen
;
1052 h
->res_buflen
= snprintf(h
->res_buf
, h
->res_buf_alloclen
,
1053 //httpresphead, h->HttpVer,
1054 httpresphead
, "HTTP/1.1",
1056 (h
->respflags
&FLAG_HTML
)?"text/html":"text/xml; charset=\"utf-8\"",
1058 /* Additional headers */
1059 if(h
->respflags
& FLAG_TIMEOUT
) {
1060 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1061 h
->res_buf_alloclen
- h
->res_buflen
,
1062 "Timeout: Second-");
1063 if(h
->req_Timeout
) {
1064 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1065 h
->res_buf_alloclen
- h
->res_buflen
,
1066 "%d\r\n", h
->req_Timeout
);
1068 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1069 h
->res_buf_alloclen
- h
->res_buflen
,
1071 //JM DLNA must force to 300 - "infinite\r\n");
1074 if(h
->respflags
& FLAG_SID
) {
1075 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1076 h
->res_buf_alloclen
- h
->res_buflen
,
1077 "SID: %.*s\r\n", h
->req_SIDLen
, h
->req_SID
);
1081 time_t curtime
= time(NULL
);
1082 strftime(szTime
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1083 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1084 h
->res_buf_alloclen
- h
->res_buflen
,
1085 "Date: %s\r\n", szTime
);
1086 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1087 h
->res_buf_alloclen
- h
->res_buflen
,
1088 "contentFeatures.dlna.org: \r\n");
1089 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1090 h
->res_buf_alloclen
- h
->res_buflen
,
1093 h
->res_buf
[h
->res_buflen
++] = '\r';
1094 h
->res_buf
[h
->res_buflen
++] = '\n';
1095 if(h
->res_buf_alloclen
< (h
->res_buflen
+ bodylen
))
1097 h
->res_buf
= (char *)realloc(h
->res_buf
, (h
->res_buflen
+ bodylen
));
1098 h
->res_buf_alloclen
= h
->res_buflen
+ bodylen
;
1103 BuildResp2_upnphttp(struct upnphttp
* h
, int respcode
,
1104 const char * respmsg
,
1105 const char * body
, int bodylen
)
1107 BuildHeader_upnphttp(h
, respcode
, respmsg
, bodylen
);
1108 if( h
->req_command
== EHead
)
1111 memcpy(h
->res_buf
+ h
->res_buflen
, body
, bodylen
);
1112 h
->res_buflen
+= bodylen
;
1115 /* responding 200 OK ! */
1117 BuildResp_upnphttp(struct upnphttp
* h
,
1118 const char * body
, int bodylen
)
1120 BuildResp2_upnphttp(h
, 200, "OK", body
, bodylen
);
1124 SendResp_upnphttp(struct upnphttp
* h
)
1127 DPRINTF(E_DEBUG
, L_HTTP
, "HTTP RESPONSE: %.*s\n", h
->res_buflen
, h
->res_buf
);
1128 n
= send(h
->socket
, h
->res_buf
, h
->res_buflen
, 0);
1131 DPRINTF(E_ERROR
, L_HTTP
, "send(res_buf): %s\n", strerror(errno
));
1133 else if(n
< h
->res_buflen
)
1135 /* TODO : handle correctly this case */
1136 DPRINTF(E_ERROR
, L_HTTP
, "send(res_buf): %d bytes sent (out of %d)\n",
1142 send_data(struct upnphttp
* h
, char * header
, size_t size
, int flags
)
1146 n
= send(h
->socket
, header
, size
, flags
);
1149 DPRINTF(E_ERROR
, L_HTTP
, "send(res_buf): %s\n", strerror(errno
));
1151 else if(n
< h
->res_buflen
)
1153 /* TODO : handle correctly this case */
1154 DPRINTF(E_ERROR
, L_HTTP
, "send(res_buf): %d bytes sent (out of %d)\n",
1165 send_file(struct upnphttp
* h
, int sendfd
, off_t offset
, off_t end_offset
)
1170 int try_sendfile
= 1;
1172 while( offset
< end_offset
)
1176 send_size
= ( ((end_offset
- offset
) < MAX_BUFFER_SIZE
) ? (end_offset
- offset
+ 1) : MAX_BUFFER_SIZE
);
1177 ret
= sendfile(h
->socket
, sendfd
, &offset
, send_size
);
1180 DPRINTF(E_DEBUG
, L_HTTP
, "sendfile error :: error no. %d [%s]\n", errno
, strerror(errno
));
1181 /* If sendfile isn't supported on the filesystem, don't bother trying to use it again. */
1182 if( errno
== EOVERFLOW
|| errno
== EINVAL
)
1184 else if( errno
!= EAGAIN
)
1189 //DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset);
1193 /* Fall back to regular I/O */
1195 buf
= malloc(MIN_BUFFER_SIZE
);
1196 send_size
= ( ((end_offset
- offset
) < MIN_BUFFER_SIZE
) ? (end_offset
- offset
+ 1) : MIN_BUFFER_SIZE
);
1197 lseek(sendfd
, offset
, SEEK_SET
);
1198 ret
= read(sendfd
, buf
, send_size
);
1200 DPRINTF(E_DEBUG
, L_HTTP
, "read error :: error no. %d [%s]\n", errno
, strerror(errno
));
1201 if( errno
!= EAGAIN
)
1204 ret
= write(h
->socket
, buf
, ret
);
1206 DPRINTF(E_DEBUG
, L_HTTP
, "write error :: error no. %d [%s]\n", errno
, strerror(errno
));
1207 if( errno
!= EAGAIN
)
1216 SendResp_icon(struct upnphttp
* h
, char * icon
)
1219 char mime
[12] = "image/";
1223 time_t curtime
= time(NULL
);
1225 if( strcmp(icon
, "sm.png") == 0 )
1227 DPRINTF(E_DEBUG
, L_HTTP
, "Sending small PNG icon\n");
1228 data
= (char *)png_sm
;
1229 size
= sizeof(png_sm
)-1;
1230 strcpy(mime
+6, "png");
1232 else if( strcmp(icon
, "lrg.png") == 0 )
1234 DPRINTF(E_DEBUG
, L_HTTP
, "Sending large PNG icon\n");
1235 data
= (char *)png_lrg
;
1236 size
= sizeof(png_lrg
)-1;
1237 strcpy(mime
+6, "png");
1239 else if( strcmp(icon
, "sm.jpg") == 0 )
1241 DPRINTF(E_DEBUG
, L_HTTP
, "Sending small JPEG icon\n");
1242 data
= (char *)jpeg_sm
;
1243 size
= sizeof(jpeg_sm
)-1;
1244 strcpy(mime
+6, "jpeg");
1246 else if( strcmp(icon
, "lrg.jpg") == 0 )
1248 DPRINTF(E_DEBUG
, L_HTTP
, "Sending large JPEG icon\n");
1249 data
= (char *)jpeg_lrg
;
1250 size
= sizeof(jpeg_lrg
)-1;
1251 strcpy(mime
+6, "jpeg");
1255 DPRINTF(E_WARN
, L_HTTP
, "Invalid icon request: %s\n", icon
);
1260 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1261 ret
= snprintf(header
, sizeof(header
), "HTTP/1.1 200 OK\r\n"
1262 "Content-Type: %s\r\n"
1263 "Content-Length: %d\r\n"
1264 "Connection: close\r\n"
1266 "Server: " MINIDLNA_SERVER_STRING
"\r\n\r\n",
1269 if( send_data(h
, header
, ret
, MSG_MORE
) == 0 )
1271 if( h
->req_command
!= EHead
)
1272 send_data(h
, data
, size
, 0);
1277 SendResp_albumArt(struct upnphttp
* h
, char * object
)
1283 time_t curtime
= time(NULL
);
1288 if( h
->reqflags
& FLAG_XFERSTREAMING
|| h
->reqflags
& FLAG_RANGE
)
1290 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Streaming with an image!\n");
1295 dash
= strchr(object
, '-');
1299 path
= sql_get_text_field(db
, "SELECT PATH from ALBUM_ART where ID = '%s'", object
);
1302 DPRINTF(E_WARN
, L_HTTP
, "ALBUM_ART ID %s not found, responding ERROR 404\n", object
);
1306 DPRINTF(E_INFO
, L_HTTP
, "Serving album art ID: %s [%s]\n", object
, path
);
1308 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1310 fd
= open(path
, O_RDONLY
);
1312 DPRINTF(E_ERROR
, L_HTTP
, "Error opening %s\n", path
);
1318 size
= lseek(fd
, 0, SEEK_END
);
1319 lseek(fd
, 0, SEEK_SET
);
1321 ret
= snprintf(header
, sizeof(header
), "HTTP/1.1 200 OK\r\n"
1322 "Content-Type: image/jpeg\r\n"
1323 "Content-Length: %jd\r\n"
1324 "Connection: close\r\n"
1327 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1328 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
1329 "Server: " MINIDLNA_SERVER_STRING
"\r\n"
1330 "transferMode.dlna.org: %s\r\n\r\n",
1331 (intmax_t)size
, date
,
1332 (h
->reqflags
& FLAG_XFERBACKGROUND
) ? "Background" : "Interactive");
1334 if( send_data(h
, header
, ret
, MSG_MORE
) == 0 )
1336 if( h
->req_command
!= EHead
)
1337 send_file(h
, fd
, 0, size
-1);
1343 SendResp_caption(struct upnphttp
* h
, char * object
)
1348 time_t curtime
= time(NULL
);
1353 path
= sql_get_text_field(db
, "SELECT PATH from CAPTIONS where ID = %s", object
);
1356 DPRINTF(E_WARN
, L_HTTP
, "CAPTION ID %s not found, responding ERROR 404\n", object
);
1360 DPRINTF(E_INFO
, L_HTTP
, "Serving caption ID: %s [%s]\n", object
, path
);
1362 fd
= open(path
, O_RDONLY
);
1364 DPRINTF(E_ERROR
, L_HTTP
, "Error opening %s\n", path
);
1370 size
= lseek(fd
, 0, SEEK_END
);
1371 lseek(fd
, 0, SEEK_SET
);
1372 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1374 ret
= snprintf(header
, sizeof(header
), "HTTP/1.1 200 OK\r\n"
1375 "Content-Type: smi/caption\r\n"
1376 "Content-Length: %jd\r\n"
1377 "Connection: close\r\n"
1380 "Server: " MINIDLNA_SERVER_STRING
"\r\n\r\n",
1381 (intmax_t)size
, date
);
1383 if( send_data(h
, header
, ret
, MSG_MORE
) == 0 )
1385 if( h
->req_command
!= EHead
)
1386 send_file(h
, fd
, 0, size
-1);
1392 SendResp_thumbnail(struct upnphttp
* h
, char * object
)
1397 time_t curtime
= time(NULL
);
1402 if( h
->reqflags
& FLAG_XFERSTREAMING
|| h
->reqflags
& FLAG_RANGE
)
1404 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Streaming with an image!\n");
1410 path
= sql_get_text_field(db
, "SELECT PATH from DETAILS where ID = '%s'", object
);
1413 DPRINTF(E_WARN
, L_HTTP
, "DETAIL ID %s not found, responding ERROR 404\n", object
);
1417 DPRINTF(E_INFO
, L_HTTP
, "Serving thumbnail for ObjectId: %s [%s]\n", object
, path
);
1419 if( access(path
, F_OK
) != 0 )
1421 DPRINTF(E_ERROR
, L_HTTP
, "Error accessing %s\n", path
);
1425 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1427 l
= exif_loader_new();
1428 exif_loader_write_file(l
, path
);
1429 ed
= exif_loader_get_data(l
);
1430 exif_loader_unref(l
);
1433 if( !ed
|| !ed
->size
)
1437 exif_data_unref(ed
);
1440 ret
= snprintf(header
, sizeof(header
), "HTTP/1.1 200 OK\r\n"
1441 "Content-Type: image/jpeg\r\n"
1442 "Content-Length: %d\r\n"
1443 "Connection: close\r\n"
1446 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1447 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
1448 "Server: " MINIDLNA_SERVER_STRING
"\r\n"
1449 "transferMode.dlna.org: %s\r\n\r\n",
1451 (h
->reqflags
& FLAG_XFERBACKGROUND
) ? "Background" : "Interactive");
1453 if( send_data(h
, header
, ret
, MSG_MORE
) == 0 )
1455 if( h
->req_command
!= EHead
)
1456 send_data(h
, (char *)ed
->data
, ed
->size
, 0);
1458 exif_data_unref(ed
);
1459 CloseSocket_upnphttp(h
);
1463 SendResp_resizedimg(struct upnphttp
* h
, char * object
)
1467 struct string_s str
;
1471 time_t curtime
= time(NULL
);
1472 int width
=640, height
=480, dstw
, dsth
, size
;
1474 unsigned char * data
= NULL
;
1475 char *path
, *file_path
;
1478 char *saveptr
=NULL
, *item
=NULL
;
1479 /* Not implemented yet *
1480 char *pixelshape=NULL;
1483 int rows
=0, chunked
, ret
;
1489 image_s
*imsrc
= NULL
, *imdst
= NULL
;
1492 id
= strtoll(object
, NULL
, 10);
1493 sprintf(str_buf
, "SELECT PATH, RESOLUTION, THUMBNAIL from DETAILS where ID = '%lld'", id
);
1494 ret
= sql_get_table(db
, str_buf
, &result
, &rows
, NULL
);
1495 if( (ret
!= SQLITE_OK
) )
1497 DPRINTF(E_ERROR
, L_HTTP
, "Didn't find valid file for %lld!\n", id
);
1501 if( !rows
|| (access(result
[3], F_OK
) != 0) )
1503 DPRINTF(E_WARN
, L_HTTP
, "%s not found, responding ERROR 404\n", object
);
1504 sqlite3_free_table(result
);
1514 file_path
= result
[3];
1515 resolution
= result
[4];
1516 srcw
= strtol(resolution
, &saveptr
, 10);
1517 srch
= strtol(saveptr
+1, NULL
, 10);
1519 path
= strdup(object
);
1520 if( strtok_r(path
, "?", &saveptr
) )
1522 item
= strtok_r(NULL
, "&,", &saveptr
);
1524 while( item
!= NULL
)
1527 decodeString(item
, 1);
1530 key
= strsep(&val
, "=");
1531 DPRINTF(E_DEBUG
, L_GENERAL
, "%s: %s\n", key
, val
);
1532 if( strcasecmp(key
, "width") == 0 )
1536 else if( strcasecmp(key
, "height") == 0 )
1540 /* Not implemented yet *
1541 else if( strcasecmp(key, "rotation") == 0 )
1543 rotation = atoi(val);
1545 else if( strcasecmp(key, "pixelshape") == 0 )
1549 item
= strtok_r(NULL
, "&,", &saveptr
);
1553 if( h
->reqflags
& FLAG_XFERSTREAMING
|| h
->reqflags
& FLAG_RANGE
)
1555 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Streaming with a resized image!\n");
1560 DPRINTF(E_INFO
, L_HTTP
, "Serving resized image for ObjectId: %lld [%s]\n", id
, file_path
);
1562 /* Figure out the best destination resolution we can use */
1564 dsth
= ((((width
<<10)/srcw
)*srch
)>>10);
1568 dstw
= (((height
<<10)/srch
) * srcw
>>10);
1571 if( dstw
<= 640 && dsth
<= 480 )
1572 strcpy(dlna_pn
, "SM");
1573 else if( dstw
<= 1024 && dsth
<= 768 )
1574 strcpy(dlna_pn
, "MED");
1576 strcpy(dlna_pn
, "LRG");
1578 if( srcw
>>3 >= dstw
&& srch
>>3 >= dsth
)
1580 else if( srcw
>>2 >= dstw
&& srch
>>2 >= dsth
)
1582 else if( srcw
>>1 >= dstw
&& srch
>>1 >= dsth
)
1585 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1588 str
.size
= sizeof(header
);
1591 strcatf(&str
, "HTTP/1.1 200 OK\r\n"
1592 "Content-Type: image/jpeg\r\n"
1593 "Connection: close\r\n"
1596 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1597 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_%s;DLNA.ORG_CI=1\r\n"
1598 "Server: " MINIDLNA_SERVER_STRING
"\r\n",
1600 if( h
->reqflags
& FLAG_XFERINTERACTIVE
)
1602 strcatf(&str
, "transferMode.dlna.org: Interactive\r\n");
1604 else if( h
->reqflags
& FLAG_XFERBACKGROUND
)
1606 strcatf(&str
, "transferMode.dlna.org: Background\r\n");
1609 /* Resizing from a thumbnail is much faster than from a large image */
1612 if( dstw
<= 160 && dsth
<= 120 && atoi(tn
) )
1614 l
= exif_loader_new();
1615 exif_loader_write_file(l
, file_path
);
1616 ed
= exif_loader_get_data(l
);
1617 exif_loader_unref(l
);
1619 if( !ed
|| !ed
->size
)
1622 exif_data_unref(ed
);
1623 DPRINTF(E_WARN
, L_HTTP
, "Unable to access image thumbnail!\n");
1627 imsrc
= image_new_from_jpeg(NULL
, 0, (char *)ed
->data
, ed
->size
, 1);
1628 exif_data_unref(ed
);
1632 if( strcmp(h
->HttpVer
, "HTTP/1.0") == 0 )
1635 imsrc
= image_new_from_jpeg(file_path
, 1, NULL
, 0, scale
);
1640 strcatf(&str
, "Transfer-Encoding: chunked\r\n\r\n");
1647 DPRINTF(E_WARN
, L_HTTP
, "Unable to open image %s!\n", file_path
);
1652 imdst
= image_resize(imsrc
, dstw
, dsth
);
1653 data
= image_save_to_jpeg_buf(imdst
, &size
);
1655 strcatf(&str
, "Content-Length: %d\r\n\r\n", size
);
1658 if( (send_data(h
, str
.data
, str
.off
, 0) == 0) && (h
->req_command
!= EHead
) )
1662 imsrc
= image_new_from_jpeg(file_path
, 1, NULL
, 0, scale
);
1665 DPRINTF(E_WARN
, L_HTTP
, "Unable to open image %s!\n", file_path
);
1669 imdst
= image_resize(imsrc
, dstw
, dsth
);
1670 data
= image_save_to_jpeg_buf(imdst
, &size
);
1672 ret
= sprintf(str_buf
, "%x\r\n", size
);
1673 send_data(h
, str_buf
, ret
, MSG_MORE
);
1674 send_data(h
, (char *)data
, size
, MSG_MORE
);
1675 send_data(h
, "\r\n0\r\n\r\n", 7, 0);
1679 send_data(h
, (char *)data
, size
, 0);
1682 DPRINTF(E_INFO
, L_HTTP
, "Done serving %s\n", file_path
);
1688 sqlite3_free_table(result
);
1696 SendResp_dlnafile(struct upnphttp
* h
, char * object
)
1699 struct string_s str
;
1704 time_t curtime
= time(NULL
);
1705 off_t total
, offset
, size
;
1708 static struct { sqlite_int64 id
;
1709 enum client_types client
;
1710 char path
[PATH_MAX
];
1713 } last_file
= { 0, 0 };
1718 id
= strtoll(object
, NULL
, 10);
1719 if( id
!= last_file
.id
|| h
->req_client
!= last_file
.client
)
1721 sprintf(sql_buf
, "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", id
);
1722 ret
= sql_get_table(db
, sql_buf
, &result
, &rows
, NULL
);
1723 if( (ret
!= SQLITE_OK
) )
1725 DPRINTF(E_ERROR
, L_HTTP
, "Didn't find valid file for %lld!\n", id
);
1731 DPRINTF(E_WARN
, L_HTTP
, "%s not found, responding ERROR 404\n", object
);
1732 sqlite3_free_table(result
);
1736 /* Cache the result */
1738 last_file
.client
= h
->req_client
;
1739 strncpy(last_file
.path
, result
[3], sizeof(last_file
.path
)-1);
1742 strncpy(last_file
.mime
, result
[4], sizeof(last_file
.mime
)-1);
1743 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
1744 if( h
->reqflags
& FLAG_SAMSUNG
)
1746 if( strcmp(last_file
.mime
+6, "x-matroska") == 0 )
1747 strcpy(last_file
.mime
+8, "mkv");
1748 /* Samsung TV's such as the A750 can natively support many
1749 Xvid/DivX AVI's however, the DLNA server needs the
1750 mime type to say video/mpeg */
1751 else if( h
->req_client
== ESamsungSeriesA
&&
1752 strcmp(last_file
.mime
+6, "x-msvideo") == 0 )
1753 strcpy(last_file
.mime
+6, "mpeg");
1755 /* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */
1756 else if( h
->req_client
== ESonyBDP
)
1758 if( strcmp(last_file
.mime
+6, "x-matroska") == 0 ||
1759 strcmp(last_file
.mime
+6, "mpeg") == 0 )
1760 strcpy(last_file
.mime
+6, "divx");
1765 last_file
.mime
[0] = '\0';
1768 snprintf(last_file
.dlna
, sizeof(last_file
.dlna
), "DLNA.ORG_PN=%s", result
[5]);
1769 else if( h
->reqflags
& FLAG_DLNA
)
1770 strcpy(last_file
.dlna
, dlna_no_conv
);
1772 last_file
.dlna
[0] = '\0';
1773 sqlite3_free_table(result
);
1781 DPRINTF(E_INFO
, L_HTTP
, "Serving DetailID: %lld [%s]\n", id
, last_file
.path
);
1783 if( h
->reqflags
& FLAG_XFERSTREAMING
)
1785 if( strncmp(last_file
.mime
, "image", 5) == 0 )
1787 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Streaming with an image!\n");
1792 else if( h
->reqflags
& FLAG_XFERINTERACTIVE
)
1794 if( h
->reqflags
& FLAG_REALTIMEINFO
)
1796 DPRINTF(E_WARN
, L_HTTP
, "Bad realTimeInfo flag with Interactive request!\n");
1800 if( strncmp(last_file
.mime
, "image", 5) != 0 )
1802 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Interactive without an image!\n");
1803 /* Samsung TVs (well, at least the A950) do this for some reason,
1804 * and I don't see them fixing this bug any time soon. */
1805 if( !(h
->reqflags
& FLAG_SAMSUNG
) || GETFLAG(DLNA_STRICT_MASK
) )
1813 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1814 offset
= h
->req_RangeStart
;
1815 sendfh
= open(last_file
.path
, O_RDONLY
);
1817 DPRINTF(E_ERROR
, L_HTTP
, "Error opening %s\n", last_file
.path
);
1821 size
= lseek(sendfh
, 0, SEEK_END
);
1822 lseek(sendfh
, 0, SEEK_SET
);
1825 str
.size
= sizeof(header
);
1828 strcatf(&str
, "HTTP/1.1 20%c OK\r\n"
1829 "Content-Type: %s\r\n",
1830 (h
->reqflags
& FLAG_RANGE
? '6' : '0'),
1832 if( h
->reqflags
& FLAG_RANGE
)
1834 if( !h
->req_RangeEnd
|| h
->req_RangeEnd
== size
)
1836 h
->req_RangeEnd
= size
- 1;
1838 if( (h
->req_RangeStart
> h
->req_RangeEnd
) || (h
->req_RangeStart
< 0) )
1840 DPRINTF(E_WARN
, L_HTTP
, "Specified range was invalid!\n");
1845 if( h
->req_RangeEnd
>= size
)
1847 DPRINTF(E_WARN
, L_HTTP
, "Specified range was outside file boundaries!\n");
1853 total
= h
->req_RangeEnd
- h
->req_RangeStart
+ 1;
1854 strcatf(&str
, "Content-Length: %jd\r\n"
1855 "Content-Range: bytes %jd-%jd/%jd\r\n",
1856 (intmax_t)total
, (intmax_t)h
->req_RangeStart
,
1857 (intmax_t)h
->req_RangeEnd
, (intmax_t)size
);
1861 h
->req_RangeEnd
= size
- 1;
1863 strcatf(&str
, "Content-Length: %jd\r\n", (intmax_t)total
);
1866 if( h
->reqflags
& FLAG_XFERSTREAMING
)
1868 strcatf(&str
, "transferMode.dlna.org: Streaming\r\n");
1870 else if( h
->reqflags
& FLAG_XFERBACKGROUND
)
1872 if( strncmp(last_file
.mime
, "image", 5) == 0 )
1873 strcatf(&str
, "transferMode.dlna.org: Background\r\n");
1875 else //if( h->reqflags & FLAG_XFERINTERACTIVE )
1877 if( (strncmp(last_file
.mime
, "video", 5) == 0) ||
1878 (strncmp(last_file
.mime
, "audio", 5) == 0) )
1880 strcatf(&str
, "transferMode.dlna.org: Streaming\r\n");
1884 strcatf(&str
, "transferMode.dlna.org: Interactive\r\n");
1888 if( h
->reqflags
& FLAG_CAPTION
)
1890 if( sql_get_int_field(db
, "SELECT ID from CAPTIONS where ID = '%lld'", id
) > 0 )
1892 strcatf(&str
, "CaptionInfo.sec: http://%s:%d/Captions/%lld.srt\r\n",
1893 lan_addr
[h
->iface
].str
, runtime_vars
.port
, id
);
1897 strcatf(&str
, "Accept-Ranges: bytes\r\n"
1898 "Connection: close\r\n"
1901 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1902 "contentFeatures.dlna.org: %s\r\n"
1903 "Server: " MINIDLNA_SERVER_STRING
"\r\n\r\n",
1904 date
, last_file
.dlna
);
1906 //DEBUG DPRINTF(E_DEBUG, L_HTTP, "RESPONSE: %s\n", str.data);
1907 if( send_data(h
, str
.data
, str
.off
, MSG_MORE
) == 0 )
1909 if( h
->req_command
!= EHead
)
1910 send_file(h
, sendfh
, offset
, h
->req_RangeEnd
);