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>
57 #include <sys/types.h>
61 #include <sys/sendfile.h>
62 #include <arpa/inet.h>
64 #include <sys/resource.h>
68 #include "upnpdescgen.h"
69 #include "minidlnapath.h"
71 #include "upnpevents.h"
72 #include "upnpglobalvars.h"
74 #include "getifaddr.h"
75 #include "image_utils.h"
78 #include <libexif/exif-loader.h>
80 #include "tivo_utils.h"
81 #include "tivo_commands.h"
83 #define MAX_BUFFER_SIZE 4194304 // 4MB -- Too much?
84 //#define MAX_BUFFER_SIZE 2147483647 // 2GB -- Too much?
85 #define MIN_BUFFER_SIZE 65536
92 struct upnphttp
* ret
;
95 ret
= (struct upnphttp
*)malloc(sizeof(struct upnphttp
));
98 memset(ret
, 0, sizeof(struct upnphttp
));
104 CloseSocket_upnphttp(struct upnphttp
* h
)
106 if(close(h
->socket
) < 0)
108 DPRINTF(E_ERROR
, L_HTTP
, "CloseSocket_upnphttp: close(%d): %s\n", h
->socket
, strerror(errno
));
115 Delete_upnphttp(struct upnphttp
* h
)
120 CloseSocket_upnphttp(h
);
128 SearchClientCache(struct in_addr addr
, int quiet
)
131 for( i
=0; i
<CLIENT_CACHE_SLOTS
; i
++ )
133 if( clients
[i
].addr
.s_addr
== addr
.s_addr
)
135 /* Invalidate this client cache if it's older than 1 hour */
136 if( (time(NULL
) - clients
[i
].age
) > 3600 )
138 unsigned char mac
[6];
139 if( get_remote_mac(addr
, mac
) == 0 &&
140 memcmp(mac
, clients
[i
].mac
, 6) == 0 )
142 /* Same MAC as last time when we were able to identify the client,
143 * so extend the timeout by another hour. */
144 clients
[i
].age
= time(NULL
);
148 memset(&clients
[i
], 0, sizeof(struct client_cache_s
));
153 DPRINTF(E_DEBUG
, L_HTTP
, "Client found in cache. [type %d/entry %d]\n",
161 /* parse HttpHeaders of the REQUEST */
163 ParseHttpHeaders(struct upnphttp
* h
)
170 /* TODO : check if req_buf, contentoff are ok */
171 while(line
< (h
->req_buf
+ h
->req_contentoff
))
173 colon
= strchr(line
, ':');
176 if(strncasecmp(line
, "Content-Length", 14)==0)
179 while(*p
< '0' || *p
> '9')
181 h
->req_contentlen
= atoi(p
);
183 else if(strncasecmp(line
, "SOAPAction", 10)==0)
187 while(*p
== ':' || *p
== ' ' || *p
== '\t')
193 if((p
[0] == '"' && p
[n
-1] == '"')
194 || (p
[0] == '\'' && p
[n
-1] == '\''))
198 h
->req_soapAction
= p
;
199 h
->req_soapActionLen
= n
;
201 else if(strncasecmp(line
, "Callback", 8)==0)
204 while(*p
!= '<' && *p
!= '\r' )
207 while(p
[n
] != '>' && p
[n
] != '\r' )
209 h
->req_Callback
= p
+ 1;
210 h
->req_CallbackLen
= MAX(0, n
- 1);
211 /* Verify callback validity */
212 if(strncmp(h
->req_Callback
, "http://", 7) != 0)
213 h
->req_Callback
= NULL
;
215 else if(strncasecmp(line
, "SID", 3)==0)
217 //zqiu: fix bug for test 4.0.5
218 //Skip extra headers like "SIDHEADER: xxxxxx xxx"
219 for(p
=line
+3;p
<colon
;p
++)
223 p
= NULL
; //unexpected header
232 while(!isspace(p
[n
]))
238 else if(strncasecmp(line
, "NT", 2)==0)
244 while(!isspace(p
[n
]))
249 /* Timeout: Seconds-nnnn */
251 Recommended. Requested duration until subscription expires,
252 either number of seconds or infinite. Recommendation
253 by a UPnP Forum working committee. Defined by UPnP vendor.
254 Consists of the keyword "Second-" followed (without an
255 intervening space) by either an integer or the keyword "infinite". */
256 else if(strncasecmp(line
, "Timeout", 7)==0)
261 if(strncasecmp(p
, "Second-", 7)==0) {
262 h
->req_Timeout
= atoi(p
+7);
265 // Range: bytes=xxx-yyy
266 else if(strncasecmp(line
, "Range", 5)==0)
271 if(strncasecmp(p
, "bytes=", 6)==0) {
272 h
->reqflags
|= FLAG_RANGE
;
273 h
->req_RangeStart
= strtoll(p
+6, &colon
, 10);
274 h
->req_RangeEnd
= colon
? atoll(colon
+1) : 0;
275 DPRINTF(E_DEBUG
, L_HTTP
, "Range Start-End: %lld - %lld\n",
276 h
->req_RangeStart
, h
->req_RangeEnd
?h
->req_RangeEnd
:-1);
279 else if(strncasecmp(line
, "Host", 4)==0)
282 h
->reqflags
|= FLAG_HOST
;
286 for(n
= 0; n
<n_lan_addr
; n
++)
288 for(i
=0; lan_addr
[n
].str
[i
]; i
++)
290 if(lan_addr
[n
].str
[i
] != p
[i
])
293 if(!lan_addr
[n
].str
[i
])
300 else if(strncasecmp(line
, "User-Agent", 10)==0)
303 /* Skip client detection if we already detected it. */
309 if(strncasecmp(p
, "Xbox/", 5)==0)
311 h
->req_client
= EXbox
;
312 h
->reqflags
|= FLAG_MIME_AVI_AVI
;
313 h
->reqflags
|= FLAG_MS_PFS
;
315 else if(strncmp(p
, "PLAYSTATION", 11)==0)
317 h
->req_client
= EPS3
;
318 h
->reqflags
|= FLAG_DLNA
;
319 h
->reqflags
|= FLAG_MIME_AVI_DIVX
;
321 else if((s
=strstrc(p
, "SEC_HHP_", '\r')))
323 h
->req_client
= ESamsungSeriesC
;
324 h
->reqflags
|= FLAG_SAMSUNG
;
325 h
->reqflags
|= FLAG_DLNA
;
326 h
->reqflags
|= FLAG_NO_RESIZE
;
327 if(strstrc(s
+8, "TV", '\r'))
328 h
->reqflags
|= FLAG_SAMSUNG_TV
;
330 else if(strncmp(p
, "SamsungWiselinkPro", 18)==0)
332 h
->req_client
= ESamsungSeriesA
;
333 h
->reqflags
|= FLAG_SAMSUNG
;
334 h
->reqflags
|= FLAG_DLNA
;
335 h
->reqflags
|= FLAG_NO_RESIZE
;
337 else if(strstrc(p
, "bridgeCo-DMP/3", '\r'))
339 h
->req_client
= EDenonReceiver
;
340 h
->reqflags
|= FLAG_DLNA
;
342 else if(strstrc(p
, "fbxupnpav/", '\r'))
344 h
->req_client
= EFreeBox
;
346 else if(strncmp(p
, "SMP8634", 7)==0)
348 h
->req_client
= EPopcornHour
;
349 h
->reqflags
|= FLAG_MIME_FLAC_FLAC
;
351 else if(strstrc(p
, "Microsoft-IPTV-Client", '\r'))
353 h
->req_client
= EMediaRoom
;
354 h
->reqflags
|= FLAG_MS_PFS
;
356 else if(strstrc(p
, "LGE_DLNA_SDK", '\r'))
358 h
->req_client
= ELGDevice
;
359 h
->reqflags
|= FLAG_DLNA
;
361 else if(strncmp(p
, "Verismo,", 8)==0)
363 h
->req_client
= ENetgearEVA2000
;
364 h
->reqflags
|= FLAG_MS_PFS
;
366 else if(strstrc(p
, "UPnP/1.0 DLNADOC/1.50 Intel_SDK_for_UPnP_devices/1.2", '\r'))
368 h
->req_client
= EToshibaTV
;
369 h
->reqflags
|= FLAG_DLNA
;
371 else if(strstrc(p
, "DLNADOC/1.50", '\r'))
373 h
->req_client
= EStandardDLNA150
;
374 h
->reqflags
|= FLAG_DLNA
;
375 h
->reqflags
|= FLAG_MIME_AVI_AVI
;
378 else if(strncasecmp(line
, "X-AV-Client-Info", 16)==0)
380 /* Skip client detection if we already detected it. */
381 if( h
->req_client
&& h
->req_client
< EStandardDLNA150
)
386 if(strstrc(p
, "PLAYSTATION 3", '\r'))
388 h
->req_client
= EPS3
;
389 h
->reqflags
|= FLAG_DLNA
;
390 h
->reqflags
|= FLAG_MIME_AVI_DIVX
;
392 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Blu-ray Disc Player"; mv="2.0" */
393 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BLU-RAY HOME THEATRE SYSTEM"; mv="2.0"; */
394 /* Sony SMP-100 needs the same treatment as their BDP-S370 */
395 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="Media Player"; mv="2.0" */
396 else if(strstrc(p
, "Blu-ray Disc Player", '\r') ||
397 strstrc(p
, "BLU-RAY HOME THEATRE SYSTEM", '\r') ||
398 strstrc(p
, "Media Player", '\r'))
400 h
->req_client
= ESonyBDP
;
401 h
->reqflags
|= FLAG_DLNA
;
403 /* X-AV-Client-Info: av=5.0; cn="Sony Corporation"; mn="BRAVIA KDL-40EX503"; mv="1.7"; */
404 /* X-AV-Client-Info: av=5.0; hn=""; cn="Sony Corporation"; mn="INTERNET TV NSX-40GT 1"; mv="0.1"; */
405 else if(strstrc(p
, "BRAVIA", '\r') ||
406 strstrc(p
, "INTERNET TV", '\r'))
408 h
->req_client
= ESonyBravia
;
409 h
->reqflags
|= FLAG_DLNA
;
412 else if(strncasecmp(line
, "Transfer-Encoding", 17)==0)
417 if(strncasecmp(p
, "chunked", 7)==0)
419 h
->reqflags
|= FLAG_CHUNKED
;
422 else if(strncasecmp(line
, "Accept-Language", 15)==0)
424 h
->reqflags
|= FLAG_LANGUAGE
;
426 else if(strncasecmp(line
, "getcontentFeatures.dlna.org", 27)==0)
431 if( (*p
!= '1') || !isspace(p
[1]) )
432 h
->reqflags
|= FLAG_INVALID_REQ
;
434 else if(strncasecmp(line
, "TimeSeekRange.dlna.org", 22)==0)
436 h
->reqflags
|= FLAG_TIMESEEK
;
438 else if(strncasecmp(line
, "PlaySpeed.dlna.org", 18)==0)
440 h
->reqflags
|= FLAG_PLAYSPEED
;
442 else if(strncasecmp(line
, "realTimeInfo.dlna.org", 21)==0)
444 h
->reqflags
|= FLAG_REALTIMEINFO
;
446 else if(strncasecmp(line
, "getAvailableSeekRange.dlna.org", 21)==0)
451 if( (*p
!= '1') || !isspace(p
[1]) )
452 h
->reqflags
|= FLAG_INVALID_REQ
;
454 else if(strncasecmp(line
, "transferMode.dlna.org", 21)==0)
459 if(strncasecmp(p
, "Streaming", 9)==0)
461 h
->reqflags
|= FLAG_XFERSTREAMING
;
463 if(strncasecmp(p
, "Interactive", 11)==0)
465 h
->reqflags
|= FLAG_XFERINTERACTIVE
;
467 if(strncasecmp(p
, "Background", 10)==0)
469 h
->reqflags
|= FLAG_XFERBACKGROUND
;
472 else if(strncasecmp(line
, "getCaptionInfo.sec", 18)==0)
474 h
->reqflags
|= FLAG_CAPTION
;
476 else if(strncasecmp(line
, "FriendlyName", 12)==0)
481 if(strstrc(p
, "LIFETAB", '\r'))
483 h
->req_client
= ELifeTab
;
484 h
->reqflags
|= FLAG_MS_PFS
;
489 while(!(line
[0] == '\r' && line
[1] == '\n'))
493 if( h
->reqflags
& FLAG_CHUNKED
)
496 h
->req_chunklen
= -1;
497 if( h
->req_buflen
<= h
->req_contentoff
)
499 while( (line
< (h
->req_buf
+ h
->req_buflen
)) &&
500 (h
->req_chunklen
= strtol(line
, &endptr
, 16)) &&
503 while(!(endptr
[0] == '\r' && endptr
[1] == '\n'))
507 line
= endptr
+h
->req_chunklen
+2;
512 h
->req_chunklen
= -1;
516 /* If the client type wasn't found, search the cache.
517 * This is done because a lot of clients like to send a
518 * different User-Agent with different types of requests. */
519 n
= SearchClientCache(h
->clientaddr
, 0);
522 /* Add this client to the cache if it's not there already. */
525 for( n
=0; n
<CLIENT_CACHE_SLOTS
; n
++ )
527 if( clients
[n
].addr
.s_addr
)
529 get_remote_mac(h
->clientaddr
, clients
[n
].mac
);
530 clients
[n
].addr
= h
->clientaddr
;
531 DPRINTF(E_DEBUG
, L_HTTP
, "Added client [%d/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n",
532 h
->req_client
, inet_ntoa(clients
[n
].addr
),
533 clients
[n
].mac
[0], clients
[n
].mac
[1], clients
[n
].mac
[2],
534 clients
[n
].mac
[3], clients
[n
].mac
[4], clients
[n
].mac
[5], n
);
538 else if( (clients
[n
].type
< EStandardDLNA150
&& h
->req_client
== EStandardDLNA150
) ||
539 (clients
[n
].type
== ESamsungSeriesB
&& h
->req_client
== ESamsungSeriesA
) )
541 /* If we know the client and our new detection is generic, use our cached info */
542 /* If we detected a Samsung Series B earlier, don't overwrite it with Series A info */
543 h
->reqflags
|= clients
[n
].flags
;
544 h
->req_client
= clients
[n
].type
;
547 clients
[n
].type
= h
->req_client
;
548 clients
[n
].flags
= h
->reqflags
& 0xFFF00000;
549 clients
[n
].age
= time(NULL
);
553 h
->reqflags
|= clients
[n
].flags
;
554 h
->req_client
= clients
[n
].type
;
558 /* very minimalistic 400 error message */
560 Send400(struct upnphttp
* h
)
562 static const char body400
[] =
563 "<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>"
564 "<BODY><H1>Bad Request</H1>The request is invalid"
565 " for this HTTP version.</BODY></HTML>\r\n";
566 h
->respflags
= FLAG_HTML
;
567 BuildResp2_upnphttp(h
, 400, "Bad Request",
568 body400
, sizeof(body400
) - 1);
569 SendResp_upnphttp(h
);
570 CloseSocket_upnphttp(h
);
573 /* very minimalistic 404 error message */
575 Send404(struct upnphttp
* h
)
577 static const char body404
[] =
578 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
579 "<BODY><H1>Not Found</H1>The requested URL was not found"
580 " on this server.</BODY></HTML>\r\n";
581 h
->respflags
= FLAG_HTML
;
582 BuildResp2_upnphttp(h
, 404, "Not Found",
583 body404
, sizeof(body404
) - 1);
584 SendResp_upnphttp(h
);
585 CloseSocket_upnphttp(h
);
588 /* very minimalistic 406 error message */
590 Send406(struct upnphttp
* h
)
592 static const char body406
[] =
593 "<HTML><HEAD><TITLE>406 Not Acceptable</TITLE></HEAD>"
594 "<BODY><H1>Not Acceptable</H1>An unsupported operation"
595 " was requested.</BODY></HTML>\r\n";
596 h
->respflags
= FLAG_HTML
;
597 BuildResp2_upnphttp(h
, 406, "Not Acceptable",
598 body406
, sizeof(body406
) - 1);
599 SendResp_upnphttp(h
);
600 CloseSocket_upnphttp(h
);
603 /* very minimalistic 416 error message */
605 Send416(struct upnphttp
* h
)
607 static const char body416
[] =
608 "<HTML><HEAD><TITLE>416 Requested Range Not Satisfiable</TITLE></HEAD>"
609 "<BODY><H1>Requested Range Not Satisfiable</H1>The requested range"
610 " was outside the file's size.</BODY></HTML>\r\n";
611 h
->respflags
= FLAG_HTML
;
612 BuildResp2_upnphttp(h
, 416, "Requested Range Not Satisfiable",
613 body416
, sizeof(body416
) - 1);
614 SendResp_upnphttp(h
);
615 CloseSocket_upnphttp(h
);
618 /* very minimalistic 500 error message */
620 Send500(struct upnphttp
* h
)
622 static const char body500
[] =
623 "<HTML><HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>"
624 "<BODY><H1>Internal Server Error</H1>Server encountered "
625 "and Internal Error.</BODY></HTML>\r\n";
626 h
->respflags
= FLAG_HTML
;
627 BuildResp2_upnphttp(h
, 500, "Internal Server Errror",
628 body500
, sizeof(body500
) - 1);
629 SendResp_upnphttp(h
);
630 CloseSocket_upnphttp(h
);
633 /* very minimalistic 501 error message */
635 Send501(struct upnphttp
* h
)
637 static const char body501
[] =
638 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
639 "<BODY><H1>Not Implemented</H1>The HTTP Method "
640 "is not implemented by this server.</BODY></HTML>\r\n";
641 h
->respflags
= FLAG_HTML
;
642 BuildResp2_upnphttp(h
, 501, "Not Implemented",
643 body501
, sizeof(body501
) - 1);
644 SendResp_upnphttp(h
);
645 CloseSocket_upnphttp(h
);
649 findendheaders(const char * s
, int len
)
653 if(s
[0]=='\r' && s
[1]=='\n' && s
[2]=='\r' && s
[3]=='\n')
660 /* Sends the description generated by the parameter */
662 sendXMLdesc(struct upnphttp
* h
, char * (f
)(int *))
669 DPRINTF(E_ERROR
, L_HTTP
, "Failed to generate XML description\n");
673 BuildResp_upnphttp(h
, desc
, len
);
674 SendResp_upnphttp(h
);
675 CloseSocket_upnphttp(h
);
680 SendResp_presentation(struct upnphttp
* h
)
684 h
->respflags
= FLAG_HTML
;
687 l
= snprintf(body
, sizeof(body
), "<meta http-equiv=\"refresh\" content=\"0; url=https://%s/admin/\">",
688 lan_addr
[h
->iface
].str
);
691 a
= sql_get_int_field(db
, "SELECT count(*) from DETAILS where MIME glob 'a*'");
692 v
= sql_get_int_field(db
, "SELECT count(*) from DETAILS where MIME glob 'v*'");
693 p
= sql_get_int_field(db
, "SELECT count(*) from DETAILS where MIME glob 'i*'");
694 l
= snprintf(body
, sizeof(body
),
695 "<HTML><HEAD><TITLE>" SERVER_NAME
" " MINIDLNA_VERSION
"</TITLE></HEAD>"
696 "<BODY><div style=\"text-align: center\">"
697 "<h3>" SERVER_NAME
" status</h3>"
698 "Audio files: %d<br>"
699 "Video files: %d<br>"
700 "Image files: %d</div>"
701 "</BODY></HTML>\r\n", a
, v
, p
);
703 BuildResp_upnphttp(h
, body
, l
);
704 SendResp_upnphttp(h
);
705 CloseSocket_upnphttp(h
);
708 /* ProcessHTTPPOST_upnphttp()
709 * executes the SOAP query if it is possible */
711 ProcessHTTPPOST_upnphttp(struct upnphttp
* h
)
713 if((h
->req_buflen
- h
->req_contentoff
) >= h
->req_contentlen
)
715 if(h
->req_soapAction
)
717 /* we can process the request */
718 DPRINTF(E_DEBUG
, L_HTTP
, "SOAPAction: %.*s\n", h
->req_soapActionLen
, h
->req_soapAction
);
721 h
->req_soapActionLen
);
725 static const char err400str
[] =
726 "<html><body>Bad request</body></html>";
727 DPRINTF(E_WARN
, L_HTTP
, "No SOAPAction in HTTP headers\n");
728 h
->respflags
= FLAG_HTML
;
729 BuildResp2_upnphttp(h
, 400, "Bad Request",
730 err400str
, sizeof(err400str
) - 1);
731 SendResp_upnphttp(h
);
732 CloseSocket_upnphttp(h
);
737 /* waiting for remaining data */
743 ProcessHTTPSubscribe_upnphttp(struct upnphttp
* h
, const char * path
)
746 DPRINTF(E_DEBUG
, L_HTTP
, "ProcessHTTPSubscribe %s\n", path
);
747 DPRINTF(E_DEBUG
, L_HTTP
, "Callback '%.*s' Timeout=%d\n",
748 h
->req_CallbackLen
, h
->req_Callback
, h
->req_Timeout
);
749 DPRINTF(E_DEBUG
, L_HTTP
, "SID '%.*s'\n", h
->req_SIDLen
, h
->req_SID
);
750 if((!h
->req_Callback
&& !h
->req_SID
) ||
751 strncmp(h
->req_NT
, "upnp:event", h
->req_NTLen
) != 0) {
752 /* Missing or invalid CALLBACK : 412 Precondition Failed.
753 * If CALLBACK header is missing or does not contain a valid HTTP URL,
754 * the publisher must respond with HTTP error 412 Precondition Failed*/
755 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
756 SendResp_upnphttp(h
);
757 CloseSocket_upnphttp(h
);
759 /* - add to the subscriber list
760 * - respond HTTP/x.x 200 OK
761 * - Send the initial event message */
762 /* Server:, SID:; Timeout: Second-(xx|infinite) */
763 if(h
->req_Callback
) {
764 if(!h
->req_NT
|| strncmp(h
->req_NT
, "upnp:event", h
->req_NTLen
) != 0) {
765 BuildResp2_upnphttp(h
, 400, "Bad Request",
766 "<html><body>Bad request</body></html>", 37);
768 sid
= upnpevents_addSubscriber(path
, h
->req_Callback
,
769 h
->req_CallbackLen
, h
->req_Timeout
);
770 h
->respflags
= FLAG_TIMEOUT
;
772 DPRINTF(E_DEBUG
, L_HTTP
, "generated sid=%s\n", sid
);
773 h
->respflags
|= FLAG_SID
;
775 h
->req_SIDLen
= strlen(sid
);
777 BuildResp_upnphttp(h
, 0, 0);
780 /* subscription renew */
782 412 Precondition Failed. If a SID does not correspond to a known,
783 un-expired subscription, the publisher must respond
784 with HTTP error 412 Precondition Failed. */
785 if(renewSubscription(h
->req_SID
, h
->req_SIDLen
, h
->req_Timeout
) < 0) {
786 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
788 /* A DLNA device must enforce a 5 minute timeout */
789 h
->respflags
= FLAG_TIMEOUT
;
790 h
->req_Timeout
= 300;
791 h
->respflags
|= FLAG_SID
;
792 BuildResp_upnphttp(h
, 0, 0);
795 SendResp_upnphttp(h
);
796 CloseSocket_upnphttp(h
);
801 ProcessHTTPUnSubscribe_upnphttp(struct upnphttp
* h
, const char * path
)
803 DPRINTF(E_DEBUG
, L_HTTP
, "ProcessHTTPUnSubscribe %s\n", path
);
804 DPRINTF(E_DEBUG
, L_HTTP
, "SID '%.*s'\n", h
->req_SIDLen
, h
->req_SID
);
805 /* Remove from the list */
806 if(upnpevents_removeSubscriber(h
->req_SID
, h
->req_SIDLen
) < 0) {
807 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
809 BuildResp_upnphttp(h
, 0, 0);
811 SendResp_upnphttp(h
);
812 CloseSocket_upnphttp(h
);
815 /* Parse and process Http Query
816 * called once all the HTTP headers have been received. */
818 ProcessHttpQuery_upnphttp(struct upnphttp
* h
)
820 char HttpCommand
[16];
828 for(i
= 0; i
<15 && *p
!= ' ' && *p
!= '\r'; i
++)
829 HttpCommand
[i
] = *(p
++);
830 HttpCommand
[i
] = '\0';
833 if(strncmp(p
, "http://", 7) == 0)
839 for(i
= 0; i
<511 && *p
!= ' ' && *p
!= '\r'; i
++)
844 HttpVer
= h
->HttpVer
;
845 for(i
= 0; i
<15 && *p
!= '\r'; i
++)
848 /*DPRINTF(E_INFO, L_HTTP, "HTTP REQUEST : %s %s (%s)\n",
849 HttpCommand, HttpUrl, HttpVer);*/
851 /* set the interface here initially, in case there is no Host header */
852 for(i
= 0; i
<n_lan_addr
; i
++)
854 if( (h
->clientaddr
.s_addr
& lan_addr
[i
].mask
.s_addr
)
855 == (lan_addr
[i
].addr
.s_addr
& lan_addr
[i
].mask
.s_addr
))
864 /* see if we need to wait for remaining data */
865 if( (h
->reqflags
& FLAG_CHUNKED
) )
867 if( h
->req_chunklen
)
872 char *chunkstart
, *chunk
, *endptr
, *endbuf
;
873 chunk
= endbuf
= chunkstart
= h
->req_buf
+ h
->req_contentoff
;
875 while( (h
->req_chunklen
= strtol(chunk
, &endptr
, 16)) && (endptr
!= chunk
) )
877 while(!(endptr
[0] == '\r' && endptr
[1] == '\n'))
883 memmove(endbuf
, endptr
, h
->req_chunklen
);
885 endbuf
+= h
->req_chunklen
;
886 chunk
= endptr
+ h
->req_chunklen
;
888 h
->req_contentlen
= endbuf
- chunkstart
;
889 h
->req_buflen
= endbuf
- h
->req_buf
;
893 DPRINTF(E_DEBUG
, L_HTTP
, "HTTP REQUEST: %.*s\n", h
->req_buflen
, h
->req_buf
);
894 if(strcmp("POST", HttpCommand
) == 0)
896 h
->req_command
= EPost
;
897 ProcessHTTPPOST_upnphttp(h
);
899 else if((strcmp("GET", HttpCommand
) == 0) || (strcmp("HEAD", HttpCommand
) == 0))
901 if( ((strcmp(h
->HttpVer
, "HTTP/1.1")==0) && !(h
->reqflags
& FLAG_HOST
)) || (h
->reqflags
& FLAG_INVALID_REQ
) )
903 DPRINTF(E_WARN
, L_HTTP
, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n");
908 else if( (h
->reqflags
& (FLAG_TIMESEEK
|FLAG_PLAYSPEED
)) &&
909 !(h
->reqflags
& FLAG_RANGE
) )
911 DPRINTF(E_WARN
, L_HTTP
, "DLNA %s requested, responding ERROR 406\n",
912 h
->reqflags
&FLAG_TIMESEEK
? "TimeSeek" : "PlaySpeed");
916 else if(strcmp("GET", HttpCommand
) == 0)
918 h
->req_command
= EGet
;
922 h
->req_command
= EHead
;
924 if(strcmp(ROOTDESC_PATH
, HttpUrl
) == 0)
926 /* If it's a Xbox360, we might need a special friendly_name to be recognized */
927 if( (h
->req_client
== EXbox
) && !strchr(friendly_name
, ':') )
929 i
= strlen(friendly_name
);
930 snprintf(friendly_name
+i
, FRIENDLYNAME_MAX_LEN
-i
, ": 1");
931 sendXMLdesc(h
, genRootDesc
);
932 friendly_name
[i
] = '\0';
934 else if( h
->reqflags
& FLAG_SAMSUNG_TV
)
936 sendXMLdesc(h
, genRootDescSamsung
);
940 sendXMLdesc(h
, genRootDesc
);
943 else if(strcmp(CONTENTDIRECTORY_PATH
, HttpUrl
) == 0)
945 sendXMLdesc(h
, genContentDirectory
);
947 else if(strcmp(CONNECTIONMGR_PATH
, HttpUrl
) == 0)
949 sendXMLdesc(h
, genConnectionManager
);
951 else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH
, HttpUrl
) == 0)
953 sendXMLdesc(h
, genX_MS_MediaReceiverRegistrar
);
955 else if(strncmp(HttpUrl
, "/MediaItems/", 12) == 0)
957 SendResp_dlnafile(h
, HttpUrl
+12);
959 else if(strncmp(HttpUrl
, "/Thumbnails/", 12) == 0)
961 SendResp_thumbnail(h
, HttpUrl
+12);
963 else if(strncmp(HttpUrl
, "/AlbumArt/", 10) == 0)
965 SendResp_albumArt(h
, HttpUrl
+10);
968 else if(strncmp(HttpUrl
, "/TiVoConnect", 12) == 0)
970 if( GETFLAG(TIVO_MASK
) )
972 if( *(HttpUrl
+12) == '?' )
974 ProcessTiVoCommand(h
, HttpUrl
+13);
978 DPRINTF(E_WARN
, L_HTTP
, "Invalid TiVo request! %s\n", HttpUrl
+12);
984 DPRINTF(E_WARN
, L_HTTP
, "TiVo request with out TiVo support enabled! %s\n",
990 else if(strncmp(HttpUrl
, "/Resized/", 9) == 0)
992 SendResp_resizedimg(h
, HttpUrl
+9);
994 else if(strncmp(HttpUrl
, "/icons/", 7) == 0)
996 SendResp_icon(h
, HttpUrl
+7);
998 else if(strncmp(HttpUrl
, "/Captions/", 10) == 0)
1000 SendResp_caption(h
, HttpUrl
+10);
1002 else if(strcmp(HttpUrl
, "/") == 0)
1004 SendResp_presentation(h
);
1008 DPRINTF(E_WARN
, L_HTTP
, "%s not found, responding ERROR 404\n", HttpUrl
);
1012 else if(strcmp("SUBSCRIBE", HttpCommand
) == 0)
1014 h
->req_command
= ESubscribe
;
1015 ProcessHTTPSubscribe_upnphttp(h
, HttpUrl
);
1017 else if(strcmp("UNSUBSCRIBE", HttpCommand
) == 0)
1019 h
->req_command
= EUnSubscribe
;
1020 ProcessHTTPUnSubscribe_upnphttp(h
, HttpUrl
);
1024 DPRINTF(E_WARN
, L_HTTP
, "Unsupported HTTP Command %s\n", HttpCommand
);
1031 Process_upnphttp(struct upnphttp
* h
)
1040 n
= recv(h
->socket
, buf
, 2048, 0);
1043 DPRINTF(E_ERROR
, L_HTTP
, "recv (state0): %s\n", strerror(errno
));
1048 DPRINTF(E_WARN
, L_HTTP
, "HTTP Connection closed unexpectedly\n");
1053 const char * endheaders
;
1054 /* if 1st arg of realloc() is null,
1055 * realloc behaves the same as malloc() */
1056 h
->req_buf
= (char *)realloc(h
->req_buf
, n
+ h
->req_buflen
+ 1);
1057 memcpy(h
->req_buf
+ h
->req_buflen
, buf
, n
);
1059 h
->req_buf
[h
->req_buflen
] = '\0';
1060 /* search for the string "\r\n\r\n" */
1061 endheaders
= findendheaders(h
->req_buf
, h
->req_buflen
);
1064 h
->req_contentoff
= endheaders
- h
->req_buf
+ 4;
1065 h
->req_contentlen
= h
->req_buflen
- h
->req_contentoff
;
1066 ProcessHttpQuery_upnphttp(h
);
1072 n
= recv(h
->socket
, buf
, 2048, 0);
1075 DPRINTF(E_ERROR
, L_HTTP
, "recv (state%d): %s\n", h
->state
, strerror(errno
));
1080 DPRINTF(E_WARN
, L_HTTP
, "HTTP Connection closed unexpectedly\n");
1085 /*fwrite(buf, 1, n, stdout);*/ /* debug */
1086 h
->req_buf
= (char *)realloc(h
->req_buf
, n
+ h
->req_buflen
);
1087 memcpy(h
->req_buf
+ h
->req_buflen
, buf
, n
);
1089 if((h
->req_buflen
- h
->req_contentoff
) >= h
->req_contentlen
)
1091 /* Need the struct to point to the realloc'd memory locations */
1094 ParseHttpHeaders(h
);
1095 ProcessHTTPPOST_upnphttp(h
);
1097 else if( h
->state
== 2 )
1099 ProcessHttpQuery_upnphttp(h
);
1105 DPRINTF(E_WARN
, L_HTTP
, "Unexpected state: %d\n", h
->state
);
1109 /* with response code and response message
1110 * also allocate enough memory */
1113 BuildHeader_upnphttp(struct upnphttp
* h
, int respcode
,
1114 const char * respmsg
,
1117 static const char httpresphead
[] =
1119 "Content-Type: %s\r\n"
1120 "Connection: close\r\n"
1121 "Content-Length: %d\r\n"
1122 "Server: " MINIDLNA_SERVER_STRING
"\r\n";
1123 time_t curtime
= time(NULL
);
1128 templen
= sizeof(httpresphead
) + 256 + bodylen
;
1129 h
->res_buf
= (char *)malloc(templen
);
1130 h
->res_buf_alloclen
= templen
;
1132 h
->res_buflen
= snprintf(h
->res_buf
, h
->res_buf_alloclen
,
1133 httpresphead
, "HTTP/1.1",
1135 (h
->respflags
&FLAG_HTML
)?"text/html":"text/xml; charset=\"utf-8\"",
1137 /* Additional headers */
1138 if(h
->respflags
& FLAG_TIMEOUT
) {
1139 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1140 h
->res_buf_alloclen
- h
->res_buflen
,
1141 "Timeout: Second-");
1142 if(h
->req_Timeout
) {
1143 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1144 h
->res_buf_alloclen
- h
->res_buflen
,
1145 "%d\r\n", h
->req_Timeout
);
1147 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1148 h
->res_buf_alloclen
- h
->res_buflen
,
1152 if(h
->respflags
& FLAG_SID
) {
1153 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1154 h
->res_buf_alloclen
- h
->res_buflen
,
1155 "SID: %.*s\r\n", h
->req_SIDLen
, h
->req_SID
);
1157 if(h
->reqflags
& FLAG_LANGUAGE
) {
1158 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1159 h
->res_buf_alloclen
- h
->res_buflen
,
1160 "Content-Language: en\r\n");
1162 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1163 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1164 h
->res_buf_alloclen
- h
->res_buflen
,
1165 "Date: %s\r\n", date
);
1166 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1167 h
->res_buf_alloclen
- h
->res_buflen
,
1170 h
->res_buflen
+= snprintf(h
->res_buf
+ h
->res_buflen
,
1171 h
->res_buf_alloclen
- h
->res_buflen
,
1172 "contentFeatures.dlna.org: \r\n");
1174 h
->res_buf
[h
->res_buflen
++] = '\r';
1175 h
->res_buf
[h
->res_buflen
++] = '\n';
1176 if(h
->res_buf_alloclen
< (h
->res_buflen
+ bodylen
))
1178 h
->res_buf
= (char *)realloc(h
->res_buf
, (h
->res_buflen
+ bodylen
));
1179 h
->res_buf_alloclen
= h
->res_buflen
+ bodylen
;
1184 BuildResp2_upnphttp(struct upnphttp
* h
, int respcode
,
1185 const char * respmsg
,
1186 const char * body
, int bodylen
)
1188 BuildHeader_upnphttp(h
, respcode
, respmsg
, bodylen
);
1189 if( h
->req_command
== EHead
)
1192 memcpy(h
->res_buf
+ h
->res_buflen
, body
, bodylen
);
1193 h
->res_buflen
+= bodylen
;
1196 /* responding 200 OK ! */
1198 BuildResp_upnphttp(struct upnphttp
* h
,
1199 const char * body
, int bodylen
)
1201 BuildResp2_upnphttp(h
, 200, "OK", body
, bodylen
);
1205 SendResp_upnphttp(struct upnphttp
* h
)
1208 DPRINTF(E_DEBUG
, L_HTTP
, "HTTP RESPONSE: %.*s\n", h
->res_buflen
, h
->res_buf
);
1209 n
= send(h
->socket
, h
->res_buf
, h
->res_buflen
, 0);
1212 DPRINTF(E_ERROR
, L_HTTP
, "send(res_buf): %s\n", strerror(errno
));
1214 else if(n
< h
->res_buflen
)
1216 /* TODO : handle correctly this case */
1217 DPRINTF(E_ERROR
, L_HTTP
, "send(res_buf): %d bytes sent (out of %d)\n",
1223 send_data(struct upnphttp
* h
, char * header
, size_t size
, int flags
)
1227 n
= send(h
->socket
, header
, size
, flags
);
1230 DPRINTF(E_ERROR
, L_HTTP
, "send(res_buf): %s\n", strerror(errno
));
1232 else if(n
< h
->res_buflen
)
1234 /* TODO : handle correctly this case */
1235 DPRINTF(E_ERROR
, L_HTTP
, "send(res_buf): %d bytes sent (out of %d)\n",
1246 send_file(struct upnphttp
* h
, int sendfd
, off_t offset
, off_t end_offset
)
1251 int try_sendfile
= 1;
1253 while( offset
< end_offset
)
1257 send_size
= ( ((end_offset
- offset
) < MAX_BUFFER_SIZE
) ? (end_offset
- offset
+ 1) : MAX_BUFFER_SIZE
);
1258 ret
= sendfile(h
->socket
, sendfd
, &offset
, send_size
);
1261 DPRINTF(E_DEBUG
, L_HTTP
, "sendfile error :: error no. %d [%s]\n", errno
, strerror(errno
));
1262 /* If sendfile isn't supported on the filesystem, don't bother trying to use it again. */
1263 if( errno
== EOVERFLOW
|| errno
== EINVAL
)
1265 else if( errno
!= EAGAIN
)
1270 //DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset);
1274 /* Fall back to regular I/O */
1276 buf
= malloc(MIN_BUFFER_SIZE
);
1277 send_size
= ( ((end_offset
- offset
) < MIN_BUFFER_SIZE
) ? (end_offset
- offset
+ 1) : MIN_BUFFER_SIZE
);
1278 lseek(sendfd
, offset
, SEEK_SET
);
1279 ret
= read(sendfd
, buf
, send_size
);
1281 DPRINTF(E_DEBUG
, L_HTTP
, "read error :: error no. %d [%s]\n", errno
, strerror(errno
));
1282 if( errno
!= EAGAIN
)
1285 ret
= write(h
->socket
, buf
, ret
);
1287 DPRINTF(E_DEBUG
, L_HTTP
, "write error :: error no. %d [%s]\n", errno
, strerror(errno
));
1288 if( errno
!= EAGAIN
)
1297 SendResp_icon(struct upnphttp
* h
, char * icon
)
1300 char mime
[12] = "image/";
1304 time_t curtime
= time(NULL
);
1306 if( strcmp(icon
, "sm.png") == 0 )
1308 DPRINTF(E_DEBUG
, L_HTTP
, "Sending small PNG icon\n");
1309 data
= (char *)png_sm
;
1310 size
= sizeof(png_sm
)-1;
1311 strcpy(mime
+6, "png");
1313 else if( strcmp(icon
, "lrg.png") == 0 )
1315 DPRINTF(E_DEBUG
, L_HTTP
, "Sending large PNG icon\n");
1316 data
= (char *)png_lrg
;
1317 size
= sizeof(png_lrg
)-1;
1318 strcpy(mime
+6, "png");
1320 else if( strcmp(icon
, "sm.jpg") == 0 )
1322 DPRINTF(E_DEBUG
, L_HTTP
, "Sending small JPEG icon\n");
1323 data
= (char *)jpeg_sm
;
1324 size
= sizeof(jpeg_sm
)-1;
1325 strcpy(mime
+6, "jpeg");
1327 else if( strcmp(icon
, "lrg.jpg") == 0 )
1329 DPRINTF(E_DEBUG
, L_HTTP
, "Sending large JPEG icon\n");
1330 data
= (char *)jpeg_lrg
;
1331 size
= sizeof(jpeg_lrg
)-1;
1332 strcpy(mime
+6, "jpeg");
1336 DPRINTF(E_WARN
, L_HTTP
, "Invalid icon request: %s\n", icon
);
1341 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1342 ret
= snprintf(header
, sizeof(header
), "HTTP/1.1 200 OK\r\n"
1343 "Content-Type: %s\r\n"
1344 "Content-Length: %d\r\n"
1345 "Connection: close\r\n"
1347 "Server: " MINIDLNA_SERVER_STRING
"\r\n\r\n",
1350 if( send_data(h
, header
, ret
, MSG_MORE
) == 0 )
1352 if( h
->req_command
!= EHead
)
1353 send_data(h
, data
, size
, 0);
1355 CloseSocket_upnphttp(h
);
1359 SendResp_albumArt(struct upnphttp
* h
, char * object
)
1365 time_t curtime
= time(NULL
);
1370 if( h
->reqflags
& (FLAG_XFERSTREAMING
|FLAG_RANGE
) )
1372 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Streaming with an image!\n");
1377 dash
= strchr(object
, '-');
1381 path
= sql_get_text_field(db
, "SELECT PATH from ALBUM_ART where ID = '%s'", object
);
1384 DPRINTF(E_WARN
, L_HTTP
, "ALBUM_ART ID %s not found, responding ERROR 404\n", object
);
1388 DPRINTF(E_INFO
, L_HTTP
, "Serving album art ID: %s [%s]\n", object
, path
);
1390 fd
= open(path
, O_RDONLY
);
1392 DPRINTF(E_ERROR
, L_HTTP
, "Error opening %s\n", path
);
1398 size
= lseek(fd
, 0, SEEK_END
);
1399 lseek(fd
, 0, SEEK_SET
);
1401 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1402 ret
= snprintf(header
, sizeof(header
), "HTTP/1.1 200 OK\r\n"
1403 "Content-Type: image/jpeg\r\n"
1404 "Content-Length: %jd\r\n"
1405 "Connection: close\r\n"
1408 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1409 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n"
1410 "Server: " MINIDLNA_SERVER_STRING
"\r\n"
1411 "transferMode.dlna.org: Interactive\r\n\r\n",
1412 (intmax_t)size
, date
);
1414 if( send_data(h
, header
, ret
, MSG_MORE
) == 0 )
1416 if( h
->req_command
!= EHead
)
1417 send_file(h
, fd
, 0, size
-1);
1420 CloseSocket_upnphttp(h
);
1424 SendResp_caption(struct upnphttp
* h
, char * object
)
1429 time_t curtime
= time(NULL
);
1434 path
= sql_get_text_field(db
, "SELECT PATH from CAPTIONS where ID = %s", object
);
1437 DPRINTF(E_WARN
, L_HTTP
, "CAPTION ID %s not found, responding ERROR 404\n", object
);
1441 DPRINTF(E_INFO
, L_HTTP
, "Serving caption ID: %s [%s]\n", object
, path
);
1443 fd
= open(path
, O_RDONLY
);
1445 DPRINTF(E_ERROR
, L_HTTP
, "Error opening %s\n", path
);
1451 size
= lseek(fd
, 0, SEEK_END
);
1452 lseek(fd
, 0, SEEK_SET
);
1454 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1455 ret
= snprintf(header
, sizeof(header
), "HTTP/1.1 200 OK\r\n"
1456 "Content-Type: smi/caption\r\n"
1457 "Content-Length: %jd\r\n"
1458 "Connection: close\r\n"
1461 "Server: " MINIDLNA_SERVER_STRING
"\r\n\r\n",
1462 (intmax_t)size
, date
);
1464 if( send_data(h
, header
, ret
, MSG_MORE
) == 0 )
1466 if( h
->req_command
!= EHead
)
1467 send_file(h
, fd
, 0, size
-1);
1470 CloseSocket_upnphttp(h
);
1474 SendResp_thumbnail(struct upnphttp
* h
, char * object
)
1479 time_t curtime
= time(NULL
);
1484 if( h
->reqflags
& (FLAG_XFERSTREAMING
|FLAG_RANGE
) )
1486 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Streaming with an image!\n");
1492 path
= sql_get_text_field(db
, "SELECT PATH from DETAILS where ID = '%s'", object
);
1495 DPRINTF(E_WARN
, L_HTTP
, "DETAIL ID %s not found, responding ERROR 404\n", object
);
1499 DPRINTF(E_INFO
, L_HTTP
, "Serving thumbnail for ObjectId: %s [%s]\n", object
, path
);
1501 if( access(path
, F_OK
) != 0 )
1503 DPRINTF(E_ERROR
, L_HTTP
, "Error accessing %s\n", path
);
1508 l
= exif_loader_new();
1509 exif_loader_write_file(l
, path
);
1510 ed
= exif_loader_get_data(l
);
1511 exif_loader_unref(l
);
1514 if( !ed
|| !ed
->size
)
1518 exif_data_unref(ed
);
1521 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1522 ret
= snprintf(header
, sizeof(header
), "HTTP/1.1 200 OK\r\n"
1523 "Content-Type: image/jpeg\r\n"
1524 "Content-Length: %jd\r\n"
1525 "Connection: close\r\n"
1528 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1529 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN;DLNA.ORG_CI=1\r\n"
1530 "Server: " MINIDLNA_SERVER_STRING
"\r\n"
1531 "transferMode.dlna.org: Interactive\r\n\r\n",
1532 (intmax_t)ed
->size
, date
);
1534 if( send_data(h
, header
, ret
, MSG_MORE
) == 0 )
1536 if( h
->req_command
!= EHead
)
1537 send_data(h
, (char *)ed
->data
, ed
->size
, 0);
1539 exif_data_unref(ed
);
1540 CloseSocket_upnphttp(h
);
1544 SendResp_resizedimg(struct upnphttp
* h
, char * object
)
1548 struct string_s str
;
1552 uint32_t dlna_flags
= DLNA_FLAG_DLNA_V1_5
|DLNA_FLAG_HTTP_STALLING
|DLNA_FLAG_TM_B
|DLNA_FLAG_TM_I
;
1553 time_t curtime
= time(NULL
);
1554 int width
=640, height
=480, dstw
, dsth
, size
;
1556 unsigned char * data
= NULL
;
1557 char *path
, *file_path
;
1560 char *saveptr
, *item
=NULL
;
1562 /* Not implemented yet *
1563 char *pixelshape=NULL; */
1565 int rows
=0, chunked
, ret
;
1566 image_s
*imsrc
= NULL
, *imdst
= NULL
;
1569 id
= strtoll(object
, &saveptr
, 10);
1570 snprintf(buf
, sizeof(buf
), "SELECT PATH, RESOLUTION, ROTATION from DETAILS where ID = '%lld'", id
);
1571 ret
= sql_get_table(db
, buf
, &result
, &rows
, NULL
);
1572 if( (ret
!= SQLITE_OK
) )
1574 DPRINTF(E_ERROR
, L_HTTP
, "Didn't find valid file for %lld!\n", id
);
1578 file_path
= result
[3];
1579 resolution
= result
[4];
1580 rotate
= result
[5] ? atoi(result
[5]) : 0;
1581 if( !rows
|| !file_path
|| !resolution
|| (access(file_path
, F_OK
) != 0) )
1583 DPRINTF(E_WARN
, L_HTTP
, "%s not found, responding ERROR 404\n", object
);
1584 sqlite3_free_table(result
);
1590 saveptr
= strchr(saveptr
, '?');
1591 path
= saveptr
? saveptr
+ 1 : object
;
1592 for( item
= strtok_r(path
, "&,", &saveptr
); item
!= NULL
; item
= strtok_r(NULL
, "&,", &saveptr
) )
1595 decodeString(item
, 1);
1598 key
= strsep(&val
, "=");
1601 DPRINTF(E_DEBUG
, L_GENERAL
, "%s: %s\n", key
, val
);
1602 if( strcasecmp(key
, "width") == 0 )
1606 else if( strcasecmp(key
, "height") == 0 )
1610 else if( strcasecmp(key
, "rotation") == 0 )
1612 rotate
= (rotate
+ atoi(val
)) % 360;
1613 sql_exec(db
, "UPDATE DETAILS set ROTATION = %d where ID = %lld", rotate
, id
);
1615 /* Not implemented yet *
1616 else if( strcasecmp(key, "pixelshape") == 0 )
1627 CloseSocket_upnphttp(h
);
1631 if( h
->reqflags
& (FLAG_XFERSTREAMING
|FLAG_RANGE
) )
1633 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Streaming with an image!\n");
1638 DPRINTF(E_INFO
, L_HTTP
, "Serving resized image for ObjectId: %lld [%s]\n", id
, file_path
);
1642 ret
= sscanf(resolution
, "%dx%d", &srch
, &srcw
);
1646 ret
= sscanf(resolution
, "%dx%d", &srch
, &srcw
);
1647 rotate
= ROTATE_270
;
1650 ret
= sscanf(resolution
, "%dx%d", &srcw
, &srch
);
1651 rotate
= ROTATE_180
;
1654 ret
= sscanf(resolution
, "%dx%d", &srcw
, &srch
);
1655 rotate
= ROTATE_NONE
;
1663 /* Figure out the best destination resolution we can use */
1665 dsth
= ((((width
<<10)/srcw
)*srch
)>>10);
1669 dstw
= (((height
<<10)/srch
) * srcw
>>10);
1672 if( dstw
<= 640 && dsth
<= 480 )
1673 strcpy(dlna_pn
, "DLNA.ORG_PN=JPEG_SM;");
1674 else if( dstw
<= 1024 && dsth
<= 768 )
1675 strcpy(dlna_pn
, "DLNA.ORG_PN=JPEG_MED;");
1677 strcpy(dlna_pn
, "DLNA.ORG_PN=JPEG_LRG;");
1679 if( srcw
>>4 >= dstw
&& srch
>>4 >= dsth
)
1681 else if( srcw
>>3 >= dstw
&& srch
>>3 >= dsth
)
1683 else if( srcw
>>2 >= dstw
&& srch
>>2 >= dsth
)
1687 str
.size
= sizeof(header
);
1690 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1691 strcatf(&str
, "HTTP/1.1 200 OK\r\n"
1692 "Content-Type: image/jpeg\r\n"
1693 "Connection: close\r\n"
1696 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1697 "contentFeatures.dlna.org: %sDLNA.ORG_CI=%X;DLNA.ORG_FLAGS=%08X%024X\r\n"
1698 "Server: " MINIDLNA_SERVER_STRING
"\r\n",
1699 date
, dlna_pn
, 1, dlna_flags
, 0);
1701 if( (h
->reqflags
& FLAG_XFERBACKGROUND
) && (setpriority(PRIO_PROCESS
, 0, 19) == 0) )
1702 strcatf(&str
, "transferMode.dlna.org: Background\r\n");
1705 strcatf(&str
, "transferMode.dlna.org: Interactive\r\n");
1707 if( strcmp(h
->HttpVer
, "HTTP/1.0") == 0 )
1710 imsrc
= image_new_from_jpeg(file_path
, 1, NULL
, 0, scale
, rotate
);
1715 strcatf(&str
, "Transfer-Encoding: chunked\r\n\r\n");
1722 DPRINTF(E_WARN
, L_HTTP
, "Unable to open image %s!\n", file_path
);
1727 imdst
= image_resize(imsrc
, dstw
, dsth
);
1728 data
= image_save_to_jpeg_buf(imdst
, &size
);
1730 strcatf(&str
, "Content-Length: %d\r\n\r\n", size
);
1733 if( (send_data(h
, str
.data
, str
.off
, 0) == 0) && (h
->req_command
!= EHead
) )
1737 imsrc
= image_new_from_jpeg(file_path
, 1, NULL
, 0, scale
, rotate
);
1740 DPRINTF(E_WARN
, L_HTTP
, "Unable to open image %s!\n", file_path
);
1744 imdst
= image_resize(imsrc
, dstw
, dsth
);
1745 data
= image_save_to_jpeg_buf(imdst
, &size
);
1747 ret
= sprintf(buf
, "%x\r\n", size
);
1748 send_data(h
, buf
, ret
, MSG_MORE
);
1749 send_data(h
, (char *)data
, size
, MSG_MORE
);
1750 send_data(h
, "\r\n0\r\n\r\n", 7, 0);
1754 send_data(h
, (char *)data
, size
, 0);
1757 DPRINTF(E_INFO
, L_HTTP
, "Done serving %s\n", file_path
);
1762 CloseSocket_upnphttp(h
);
1764 sqlite3_free_table(result
);
1772 SendResp_dlnafile(struct upnphttp
* h
, char * object
)
1775 struct string_s str
;
1780 time_t curtime
= time(NULL
);
1781 off_t total
, offset
, size
;
1784 uint32_t dlna_flags
= DLNA_FLAG_DLNA_V1_5
|DLNA_FLAG_HTTP_STALLING
|DLNA_FLAG_TM_B
;
1785 static struct { sqlite_int64 id
;
1786 enum client_types client
;
1787 char path
[PATH_MAX
];
1790 } last_file
= { 0, 0 };
1795 id
= strtoll(object
, NULL
, 10);
1796 if( h
->reqflags
& FLAG_MS_PFS
)
1798 if( strstr(object
, "?albumArt=true") )
1801 art
= sql_get_text_field(db
, "SELECT ALBUM_ART from DETAILS where ID = '%lld'", id
);
1802 SendResp_albumArt(h
, art
);
1807 if( id
!= last_file
.id
|| h
->req_client
!= last_file
.client
)
1809 snprintf(buf
, sizeof(buf
), "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", id
);
1810 ret
= sql_get_table(db
, buf
, &result
, &rows
, NULL
);
1811 if( (ret
!= SQLITE_OK
) )
1813 DPRINTF(E_ERROR
, L_HTTP
, "Didn't find valid file for %lld!\n", id
);
1817 if( !rows
|| !result
[3] || !result
[4] )
1819 DPRINTF(E_WARN
, L_HTTP
, "%s not found, responding ERROR 404\n", object
);
1820 sqlite3_free_table(result
);
1824 /* Cache the result */
1826 last_file
.client
= h
->req_client
;
1827 strncpy(last_file
.path
, result
[3], sizeof(last_file
.path
)-1);
1830 strncpy(last_file
.mime
, result
[4], sizeof(last_file
.mime
)-1);
1831 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
1832 if( h
->reqflags
& FLAG_SAMSUNG
)
1834 if( strcmp(last_file
.mime
+6, "x-matroska") == 0 )
1835 strcpy(last_file
.mime
+8, "mkv");
1836 /* Samsung TV's such as the A750 can natively support many
1837 Xvid/DivX AVI's however, the DLNA server needs the
1838 mime type to say video/mpeg */
1839 else if( h
->req_client
== ESamsungSeriesA
&&
1840 strcmp(last_file
.mime
+6, "x-msvideo") == 0 )
1841 strcpy(last_file
.mime
+6, "mpeg");
1843 /* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */
1844 else if( h
->req_client
== ESonyBDP
)
1846 if( strcmp(last_file
.mime
+6, "x-matroska") == 0 ||
1847 strcmp(last_file
.mime
+6, "mpeg") == 0 )
1848 strcpy(last_file
.mime
+6, "divx");
1852 snprintf(last_file
.dlna
, sizeof(last_file
.dlna
), "DLNA.ORG_PN=%s;", result
[5]);
1854 last_file
.dlna
[0] = '\0';
1855 sqlite3_free_table(result
);
1861 CloseSocket_upnphttp(h
);
1866 DPRINTF(E_INFO
, L_HTTP
, "Serving DetailID: %lld [%s]\n", id
, last_file
.path
);
1868 if( h
->reqflags
& FLAG_XFERSTREAMING
)
1870 if( strncmp(last_file
.mime
, "image", 5) == 0 )
1872 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Streaming with an image!\n");
1877 else if( h
->reqflags
& FLAG_XFERINTERACTIVE
)
1879 if( h
->reqflags
& FLAG_REALTIMEINFO
)
1881 DPRINTF(E_WARN
, L_HTTP
, "Bad realTimeInfo flag with Interactive request!\n");
1885 if( strncmp(last_file
.mime
, "image", 5) != 0 )
1887 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Interactive without an image!\n");
1888 /* Samsung TVs (well, at least the A950) do this for some reason,
1889 * and I don't see them fixing this bug any time soon. */
1890 if( !(h
->reqflags
& FLAG_SAMSUNG
) || GETFLAG(DLNA_STRICT_MASK
) )
1898 offset
= h
->req_RangeStart
;
1899 sendfh
= open(last_file
.path
, O_RDONLY
);
1901 DPRINTF(E_ERROR
, L_HTTP
, "Error opening %s\n", last_file
.path
);
1905 size
= lseek(sendfh
, 0, SEEK_END
);
1906 lseek(sendfh
, 0, SEEK_SET
);
1909 str
.size
= sizeof(header
);
1912 strcatf(&str
, "HTTP/1.1 20%c OK\r\n"
1913 "Content-Type: %s\r\n",
1914 (h
->reqflags
& FLAG_RANGE
? '6' : '0'),
1916 if( h
->reqflags
& FLAG_RANGE
)
1918 if( !h
->req_RangeEnd
|| h
->req_RangeEnd
== size
)
1920 h
->req_RangeEnd
= size
- 1;
1922 if( (h
->req_RangeStart
> h
->req_RangeEnd
) || (h
->req_RangeStart
< 0) )
1924 DPRINTF(E_WARN
, L_HTTP
, "Specified range was invalid!\n");
1929 if( h
->req_RangeEnd
>= size
)
1931 DPRINTF(E_WARN
, L_HTTP
, "Specified range was outside file boundaries!\n");
1937 total
= h
->req_RangeEnd
- h
->req_RangeStart
+ 1;
1938 strcatf(&str
, "Content-Length: %jd\r\n"
1939 "Content-Range: bytes %jd-%jd/%jd\r\n",
1940 (intmax_t)total
, (intmax_t)h
->req_RangeStart
,
1941 (intmax_t)h
->req_RangeEnd
, (intmax_t)size
);
1945 h
->req_RangeEnd
= size
- 1;
1947 strcatf(&str
, "Content-Length: %jd\r\n", (intmax_t)total
);
1951 if( (h
->reqflags
& FLAG_XFERBACKGROUND
) && (setpriority(PRIO_PROCESS
, 0, 19) == 0) )
1952 strcatf(&str
, "transferMode.dlna.org: Background\r\n");
1955 if( strncmp(last_file
.mime
, "image", 5) == 0 )
1956 strcatf(&str
, "transferMode.dlna.org: Interactive\r\n");
1958 strcatf(&str
, "transferMode.dlna.org: Streaming\r\n");
1960 switch( *last_file
.mime
)
1963 dlna_flags
|= DLNA_FLAG_TM_I
;
1968 dlna_flags
|= DLNA_FLAG_TM_S
;
1972 if( h
->reqflags
& FLAG_CAPTION
)
1974 if( sql_get_int_field(db
, "SELECT ID from CAPTIONS where ID = '%lld'", id
) > 0 )
1975 strcatf(&str
, "CaptionInfo.sec: http://%s:%d/Captions/%lld.srt\r\n",
1976 lan_addr
[h
->iface
].str
, runtime_vars
.port
, id
);
1979 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1980 strcatf(&str
, "Accept-Ranges: bytes\r\n"
1981 "Connection: close\r\n"
1984 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1985 "contentFeatures.dlna.org: %sDLNA.ORG_OP=%02X;DLNA.ORG_CI=%X;DLNA.ORG_FLAGS=%08X%024X\r\n"
1986 "Server: " MINIDLNA_SERVER_STRING
"\r\n\r\n",
1987 date
, last_file
.dlna
, 1, 0, dlna_flags
, 0);
1989 //DEBUG DPRINTF(E_DEBUG, L_HTTP, "RESPONSE: %s\n", str.data);
1990 if( send_data(h
, str
.data
, str
.off
, MSG_MORE
) == 0 )
1992 if( h
->req_command
!= EHead
)
1993 send_file(h
, sendfh
, offset
, h
->req_RangeEnd
);
1997 CloseSocket_upnphttp(h
);