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 <arpa/inet.h>
63 #include <sys/resource.h>
68 #include "upnpglobalvars.h"
70 #include "upnpdescgen.h"
71 #include "minidlnapath.h"
73 #include "upnpevents.h"
75 #include "getifaddr.h"
76 #include "image_utils.h"
79 #include <libexif/exif-loader.h>
80 #include "tivo_utils.h"
81 #include "tivo_commands.h"
86 #define MAX_BUFFER_SIZE 2147483647
87 #define MIN_BUFFER_SIZE 65536
89 #define INIT_STR(s, d) { s.data = d; s.size = sizeof(d); s.off = 0; }
99 static void SendResp_icon(struct upnphttp
*, char * url
);
100 static void SendResp_albumArt(struct upnphttp
*, char * url
);
101 static void SendResp_caption(struct upnphttp
*, char * url
);
102 static void SendResp_resizedimg(struct upnphttp
*, char * url
);
103 static void SendResp_thumbnail(struct upnphttp
*, char * url
);
104 static void SendResp_dlnafile(struct upnphttp
*, char * url
);
105 static void Process_upnphttp(struct event
*ev
);
110 struct upnphttp
* ret
;
113 ret
= (struct upnphttp
*)malloc(sizeof(struct upnphttp
));
116 memset(ret
, 0, sizeof(struct upnphttp
));
117 ret
->ev
= (struct event
){ .fd
= s
, .rdwr
= EVENT_READ
, .process
= Process_upnphttp
, .data
= ret
};
118 event_module
.add(&ret
->ev
);
123 CloseSocket_upnphttp(struct upnphttp
* h
)
126 event_module
.del(&h
->ev
, EV_FLAG_CLOSING
);
127 if(close(h
->ev
.fd
) < 0)
129 DPRINTF(E_ERROR
, L_HTTP
, "CloseSocket_upnphttp: close(%d): %s\n", h
->ev
.fd
, strerror(errno
));
136 Delete_upnphttp(struct upnphttp
* h
)
141 CloseSocket_upnphttp(h
);
148 /* parse HttpHeaders of the REQUEST */
150 ParseHttpHeaders(struct upnphttp
* h
)
158 /* TODO : check if req_buf, contentoff are ok */
159 while(line
< (h
->req_buf
+ h
->req_contentoff
))
161 colon
= strchr(line
, ':');
164 if(strncasecmp(line
, "Content-Length", 14)==0)
167 while(*p
&& (*p
< '0' || *p
> '9'))
169 h
->req_contentlen
= atoi(p
);
170 if(h
->req_contentlen
< 0) {
171 DPRINTF(E_WARN
, L_HTTP
, "Invalid Content-Length %d", h
->req_contentlen
);
172 h
->req_contentlen
= 0;
175 else if(strncasecmp(line
, "SOAPAction", 10)==0)
179 while(*p
== ':' || *p
== ' ' || *p
== '\t')
184 ((p
[0] == '"' && p
[n
-1] == '"') ||
185 (p
[0] == '\'' && p
[n
-1] == '\'')))
190 h
->req_soapAction
= p
;
191 h
->req_soapActionLen
= n
;
193 else if(strncasecmp(line
, "Callback", 8)==0)
196 while(*p
&& *p
!= '<' && *p
!= '\r' )
199 while(p
[n
] && p
[n
] != '>' && p
[n
] != '\r' )
201 h
->req_Callback
= p
+ 1;
202 h
->req_CallbackLen
= MAX(0, n
- 1);
204 else if(strncasecmp(line
, "SID", 3)==0)
206 //zqiu: fix bug for test 4.0.5
207 //Skip extra headers like "SIDHEADER: xxxxxx xxx"
208 for(p
=line
+3;p
<colon
;p
++)
212 p
= NULL
; //unexpected header
221 while(p
[n
] && !isspace(p
[n
]))
227 else if(strncasecmp(line
, "NT", 2)==0)
233 while(p
[n
] && !isspace(p
[n
]))
238 /* Timeout: Seconds-nnnn */
240 Recommended. Requested duration until subscription expires,
241 either number of seconds or infinite. Recommendation
242 by a UPnP Forum working committee. Defined by UPnP vendor.
243 Consists of the keyword "Second-" followed (without an
244 intervening space) by either an integer or the keyword "infinite". */
245 else if(strncasecmp(line
, "Timeout", 7)==0)
250 if(strncasecmp(p
, "Second-", 7)==0) {
251 h
->req_Timeout
= atoi(p
+7);
254 // Range: bytes=xxx-yyy
255 else if(strncasecmp(line
, "Range", 5)==0)
260 if(strncasecmp(p
, "bytes=", 6)==0) {
261 h
->reqflags
|= FLAG_RANGE
;
262 h
->req_RangeStart
= strtoll(p
+6, &colon
, 10);
263 h
->req_RangeEnd
= colon
? atoll(colon
+1) : 0;
264 DPRINTF(E_DEBUG
, L_HTTP
, "Range Start-End: %lld - %lld\n",
265 (long long)h
->req_RangeStart
,
266 h
->req_RangeEnd
? (long long)h
->req_RangeEnd
: -1);
269 else if(strncasecmp(line
, "Host", 4)==0)
272 h
->reqflags
|= FLAG_HOST
;
281 for(n
= 0; n
< n_lan_addr
; n
++)
283 for(i
= 0; lan_addr
[n
].str
[i
]; i
++)
285 if(lan_addr
[n
].str
[i
] != p
[i
])
288 if(i
&& !lan_addr
[n
].str
[i
])
295 else if(strncasecmp(line
, "User-Agent", 10)==0)
298 /* Skip client detection if we already detected it. */
304 for (i
= 0; client_types
[i
].name
; i
++)
306 if (client_types
[i
].match_type
!= EUserAgent
)
308 if (strstrc(p
, client_types
[i
].match
, '\r') != NULL
)
315 else if(strncasecmp(line
, "X-AV-Client-Info", 16)==0)
318 /* Skip client detection if we already detected it. */
319 if( client
&& client_types
[client
].type
< EStandardDLNA150
)
324 for (i
= 0; client_types
[i
].name
; i
++)
326 if (client_types
[i
].match_type
!= EXAVClientInfo
)
328 if (strstrc(p
, client_types
[i
].match
, '\r') != NULL
)
335 else if(strncasecmp(line
, "Transfer-Encoding", 17)==0)
340 if(strncasecmp(p
, "chunked", 7)==0)
342 h
->reqflags
|= FLAG_CHUNKED
;
345 else if(strncasecmp(line
, "Accept-Language", 15)==0)
347 h
->reqflags
|= FLAG_LANGUAGE
;
349 else if(strncasecmp(line
, "getcontentFeatures.dlna.org", 27)==0)
354 if( (*p
!= '1') || !isspace(p
[1]) )
355 h
->reqflags
|= FLAG_INVALID_REQ
;
357 else if(strncasecmp(line
, "TimeSeekRange.dlna.org", 22)==0)
359 h
->reqflags
|= FLAG_TIMESEEK
;
361 else if(strncasecmp(line
, "PlaySpeed.dlna.org", 18)==0)
363 h
->reqflags
|= FLAG_PLAYSPEED
;
365 else if(strncasecmp(line
, "realTimeInfo.dlna.org", 21)==0)
367 h
->reqflags
|= FLAG_REALTIMEINFO
;
369 else if(strncasecmp(line
, "getAvailableSeekRange.dlna.org", 21)==0)
374 if( (*p
!= '1') || !isspace(p
[1]) )
375 h
->reqflags
|= FLAG_INVALID_REQ
;
377 else if(strncasecmp(line
, "transferMode.dlna.org", 21)==0)
382 if(strncasecmp(p
, "Streaming", 9)==0)
384 h
->reqflags
|= FLAG_XFERSTREAMING
;
386 if(strncasecmp(p
, "Interactive", 11)==0)
388 h
->reqflags
|= FLAG_XFERINTERACTIVE
;
390 if(strncasecmp(p
, "Background", 10)==0)
392 h
->reqflags
|= FLAG_XFERBACKGROUND
;
395 else if(strncasecmp(line
, "getCaptionInfo.sec", 18)==0)
397 h
->reqflags
|= FLAG_CAPTION
;
399 else if(strncasecmp(line
, "FriendlyName", 12)==0)
405 for (i
= 0; client_types
[i
].name
; i
++)
407 if (client_types
[i
].match_type
!= EFriendlyName
)
409 if (strstrc(p
, client_types
[i
].match
, '\r') != NULL
)
416 else if(strncasecmp(line
, "uctt.upnp.org:", 14)==0)
418 /* Conformance testing */
419 SETFLAG(DLNA_STRICT_MASK
);
423 line
= strstr(line
, "\r\n");
428 if (h
->reqflags
& FLAG_CHUNKED
)
431 h
->req_chunklen
= -1;
432 if (h
->req_buflen
<= h
->req_contentoff
)
434 while( (line
< (h
->req_buf
+ h
->req_buflen
)) &&
435 ((h
->req_chunklen
= strtol(line
, &endptr
, 16)) > 0) &&
438 endptr
= strstr(endptr
, "\r\n");
443 line
= endptr
+h
->req_chunklen
+2;
448 h
->req_chunklen
= -1;
452 /* If the client type wasn't found, search the cache.
453 * This is done because a lot of clients like to send a
454 * different User-Agent with different types of requests. */
455 h
->req_client
= SearchClientCache(h
->clientaddr
, 0);
456 /* Add this client to the cache if it's not there already. */
459 h
->req_client
= AddClientCache(h
->clientaddr
, client
);
463 enum client_types type
= client_types
[client
].type
;
464 enum client_types ctype
= h
->req_client
->type
->type
;
465 /* If we know the client and our new detection is generic, use our cached info */
466 /* If we detected a Samsung Series B earlier, don't overwrite it with Series A info */
467 if ((ctype
&& ctype
< EStandardDLNA150
&& type
>= EStandardDLNA150
) ||
468 (ctype
== ESamsungSeriesB
&& type
== ESamsungSeriesA
))
470 h
->req_client
->type
= &client_types
[client
];
471 h
->req_client
->age
= time(NULL
);
475 /* very minimalistic 400 error message */
477 Send400(struct upnphttp
* h
)
479 static const char body400
[] =
480 "<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>"
481 "<BODY><H1>Bad Request</H1>The request is invalid"
482 " for this HTTP version.</BODY></HTML>\r\n";
483 h
->respflags
= FLAG_HTML
;
484 BuildResp2_upnphttp(h
, 400, "Bad Request",
485 body400
, sizeof(body400
) - 1);
486 SendResp_upnphttp(h
);
487 CloseSocket_upnphttp(h
);
490 /* very minimalistic 403 error message */
492 Send403(struct upnphttp
* h
)
494 static const char body403
[] =
495 "<HTML><HEAD><TITLE>403 Forbidden</TITLE></HEAD>"
496 "<BODY><H1>Forbidden</H1>You don't have permission to access this resource."
497 "</BODY></HTML>\r\n";
498 h
->respflags
= FLAG_HTML
;
499 BuildResp2_upnphttp(h
, 403, "Forbidden",
500 body403
, sizeof(body403
) - 1);
501 SendResp_upnphttp(h
);
502 CloseSocket_upnphttp(h
);
505 /* very minimalistic 404 error message */
507 Send404(struct upnphttp
* h
)
509 static const char body404
[] =
510 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
511 "<BODY><H1>Not Found</H1>The requested URL was not found"
512 " on this server.</BODY></HTML>\r\n";
513 h
->respflags
= FLAG_HTML
;
514 BuildResp2_upnphttp(h
, 404, "Not Found",
515 body404
, sizeof(body404
) - 1);
516 SendResp_upnphttp(h
);
517 CloseSocket_upnphttp(h
);
520 /* very minimalistic 406 error message */
522 Send406(struct upnphttp
* h
)
524 static const char body406
[] =
525 "<HTML><HEAD><TITLE>406 Not Acceptable</TITLE></HEAD>"
526 "<BODY><H1>Not Acceptable</H1>An unsupported operation"
527 " was requested.</BODY></HTML>\r\n";
528 h
->respflags
= FLAG_HTML
;
529 BuildResp2_upnphttp(h
, 406, "Not Acceptable",
530 body406
, sizeof(body406
) - 1);
531 SendResp_upnphttp(h
);
532 CloseSocket_upnphttp(h
);
535 /* very minimalistic 416 error message */
537 Send416(struct upnphttp
* h
)
539 static const char body416
[] =
540 "<HTML><HEAD><TITLE>416 Requested Range Not Satisfiable</TITLE></HEAD>"
541 "<BODY><H1>Requested Range Not Satisfiable</H1>The requested range"
542 " was outside the file's size.</BODY></HTML>\r\n";
543 h
->respflags
= FLAG_HTML
;
544 BuildResp2_upnphttp(h
, 416, "Requested Range Not Satisfiable",
545 body416
, sizeof(body416
) - 1);
546 SendResp_upnphttp(h
);
547 CloseSocket_upnphttp(h
);
550 /* very minimalistic 500 error message */
552 Send500(struct upnphttp
* h
)
554 static const char body500
[] =
555 "<HTML><HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>"
556 "<BODY><H1>Internal Server Error</H1>Server encountered "
557 "and Internal Error.</BODY></HTML>\r\n";
558 h
->respflags
= FLAG_HTML
;
559 BuildResp2_upnphttp(h
, 500, "Internal Server Errror",
560 body500
, sizeof(body500
) - 1);
561 SendResp_upnphttp(h
);
562 CloseSocket_upnphttp(h
);
565 /* very minimalistic 501 error message */
567 Send501(struct upnphttp
* h
)
569 static const char body501
[] =
570 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
571 "<BODY><H1>Not Implemented</H1>The HTTP Method "
572 "is not implemented by this server.</BODY></HTML>\r\n";
573 h
->respflags
= FLAG_HTML
;
574 BuildResp2_upnphttp(h
, 501, "Not Implemented",
575 body501
, sizeof(body501
) - 1);
576 SendResp_upnphttp(h
);
577 CloseSocket_upnphttp(h
);
580 /* Sends the description generated by the parameter */
582 sendXMLdesc(struct upnphttp
* h
, char * (f
)(int *))
589 DPRINTF(E_ERROR
, L_HTTP
, "Failed to generate XML description\n");
593 BuildResp_upnphttp(h
, desc
, len
);
594 SendResp_upnphttp(h
);
595 CloseSocket_upnphttp(h
);
601 SendResp_readynas_admin(struct upnphttp
* h
)
606 h
->respflags
= FLAG_HTML
;
607 l
= snprintf(body
, sizeof(body
), "<meta http-equiv=\"refresh\" content=\"0; url=https://%s/admin/\">",
608 lan_addr
[h
->iface
].str
);
610 BuildResp_upnphttp(h
, body
, l
);
611 SendResp_upnphttp(h
);
612 CloseSocket_upnphttp(h
);
617 SendResp_presentation(struct upnphttp
* h
)
625 h
->respflags
= FLAG_HTML
;
627 a
= sql_get_int_field(db
, "SELECT count(*) from DETAILS where MIME glob 'a*'");
628 v
= sql_get_int_field(db
, "SELECT count(*) from DETAILS where MIME glob 'v*'");
629 p
= sql_get_int_field(db
, "SELECT count(*) from DETAILS where MIME glob 'i*'");
631 "<HTML><HEAD><TITLE>" SERVER_NAME
" " MINIDLNA_VERSION
"</TITLE><meta http-equiv=\"refresh\" content=\"20\"></HEAD>"
632 "<BODY><div style=\"text-align: center\">"
633 "<h2>" SERVER_NAME
" status</h2></div>");
636 "<h3>Media library</h3>"
637 "<table border=1 cellpadding=10>"
638 "<tr><td>Audio files</td><td>%d</td></tr>"
639 "<tr><td>Video files</td><td>%d</td></tr>"
640 "<tr><td>Image files</td><td>%d</td></tr>"
641 "</table>", a
, v
, p
);
643 if (GETFLAG(SCANNING_MASK
))
645 "<br><i>* Media scan in progress</i><br>");
648 "<h3>Connected clients</h3>"
649 "<table border=1 cellpadding=10>"
650 "<tr><td>ID</td><td>Type</td><td>IP Address</td><td>HW Address</td><td>Connections</td></tr>");
651 for (i
= 0; i
< CLIENT_CACHE_SLOTS
; i
++)
653 if (!clients
[i
].addr
.s_addr
)
655 strcatf(&str
, "<tr><td>%d</td><td>%s</td><td>%s</td><td>%02X:%02X:%02X:%02X:%02X:%02X</td><td>%d</td></tr>",
656 i
, clients
[i
].type
->name
, inet_ntoa(clients
[i
].addr
),
657 clients
[i
].mac
[0], clients
[i
].mac
[1], clients
[i
].mac
[2],
658 clients
[i
].mac
[3], clients
[i
].mac
[4], clients
[i
].mac
[5], clients
[i
].connections
);
660 strcatf(&str
, "</table>");
662 strcatf(&str
, "<br>%d connection%s currently open<br>", number_of_children
, (number_of_children
== 1 ? "" : "s"));
663 strcatf(&str
, "</BODY></HTML>\r\n");
665 BuildResp_upnphttp(h
, str
.data
, str
.off
);
666 SendResp_upnphttp(h
);
667 CloseSocket_upnphttp(h
);
670 /* ProcessHTTPPOST_upnphttp()
671 * executes the SOAP query if it is possible */
673 ProcessHTTPPOST_upnphttp(struct upnphttp
* h
)
675 if((h
->req_buflen
- h
->req_contentoff
) >= h
->req_contentlen
)
677 if(h
->req_soapAction
)
679 /* we can process the request */
680 DPRINTF(E_DEBUG
, L_HTTP
, "SOAPAction: %.*s\n", h
->req_soapActionLen
, h
->req_soapAction
);
683 h
->req_soapActionLen
);
687 static const char err400str
[] =
688 "<html><body>Bad request</body></html>";
689 DPRINTF(E_WARN
, L_HTTP
, "No SOAPAction in HTTP headers\n");
690 h
->respflags
= FLAG_HTML
;
691 BuildResp2_upnphttp(h
, 400, "Bad Request",
692 err400str
, sizeof(err400str
) - 1);
693 SendResp_upnphttp(h
);
694 CloseSocket_upnphttp(h
);
699 /* waiting for remaining data */
705 check_event(struct upnphttp
*h
)
707 enum event_type type
= E_INVALID
;
711 if (h
->req_SID
|| !h
->req_NT
)
713 BuildResp2_upnphttp(h
, 400, "Bad Request",
714 "<html><body>Bad request</body></html>", 37);
716 else if (strncmp(h
->req_Callback
, "http://", 7) != 0 ||
717 strncmp(h
->req_NT
, "upnp:event", h
->req_NTLen
) != 0)
719 /* Missing or invalid CALLBACK : 412 Precondition Failed.
720 * If CALLBACK header is missing or does not contain a valid HTTP URL,
721 * the publisher must respond with HTTP error 412 Precondition Failed*/
722 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
726 /* Make sure callback URL points to the originating IP */
730 const char *p
= h
->req_Callback
+ 7;
731 while (!strchr("/:>", *p
) && i
< sizeof(addrstr
) - 1 &&
732 p
< (h
->req_Callback
+ h
->req_CallbackLen
))
734 addrstr
[i
++] = *(p
++);
738 if (inet_pton(AF_INET
, addrstr
, &addr
) <= 0 ||
739 memcmp(&addr
, &h
->clientaddr
, sizeof(struct in_addr
)))
741 DPRINTF(E_ERROR
, L_HTTP
, "Bad callback IP (%s)\n", addrstr
);
742 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
750 /* subscription renew */
753 BuildResp2_upnphttp(h
, 400, "Bad Request",
754 "<html><body>Bad request</body></html>", 37);
761 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
768 ProcessHTTPSubscribe_upnphttp(struct upnphttp
* h
, const char * path
)
771 enum event_type type
;
772 DPRINTF(E_DEBUG
, L_HTTP
, "ProcessHTTPSubscribe %s\n", path
);
773 DPRINTF(E_DEBUG
, L_HTTP
, "Callback '%.*s' Timeout=%d\n",
774 h
->req_CallbackLen
, h
->req_Callback
, h
->req_Timeout
);
775 DPRINTF(E_DEBUG
, L_HTTP
, "SID '%.*s'\n", h
->req_SIDLen
, h
->req_SID
);
777 type
= check_event(h
);
778 if (type
== E_SUBSCRIBE
)
780 /* - add to the subscriber list
781 * - respond HTTP/x.x 200 OK
782 * - Send the initial event message */
783 /* Server:, SID:; Timeout: Second-(xx|infinite) */
784 sid
= upnpevents_addSubscriber(path
, h
->req_Callback
,
785 h
->req_CallbackLen
, h
->req_Timeout
);
786 h
->respflags
= FLAG_TIMEOUT
;
789 DPRINTF(E_DEBUG
, L_HTTP
, "generated sid=%s\n", sid
);
790 h
->respflags
|= FLAG_SID
;
792 h
->req_SIDLen
= strlen(sid
);
794 BuildResp_upnphttp(h
, 0, 0);
796 else if (type
== E_RENEW
)
798 /* subscription renew */
799 if (renewSubscription(h
->req_SID
, h
->req_SIDLen
, h
->req_Timeout
) < 0)
802 412 Precondition Failed. If a SID does not correspond to a known,
803 un-expired subscription, the publisher must respond
804 with HTTP error 412 Precondition Failed. */
805 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
809 /* A DLNA device must enforce a 5 minute timeout */
810 h
->respflags
= FLAG_TIMEOUT
;
811 h
->req_Timeout
= 300;
812 h
->respflags
|= FLAG_SID
;
813 BuildResp_upnphttp(h
, 0, 0);
816 SendResp_upnphttp(h
);
817 CloseSocket_upnphttp(h
);
821 ProcessHTTPUnSubscribe_upnphttp(struct upnphttp
* h
, const char * path
)
823 enum event_type type
;
824 DPRINTF(E_DEBUG
, L_HTTP
, "ProcessHTTPUnSubscribe %s\n", path
);
825 DPRINTF(E_DEBUG
, L_HTTP
, "SID '%.*s'\n", h
->req_SIDLen
, h
->req_SID
);
826 /* Remove from the list */
827 type
= check_event(h
);
828 if (type
!= E_INVALID
)
830 if(upnpevents_removeSubscriber(h
->req_SID
, h
->req_SIDLen
) < 0)
831 BuildResp2_upnphttp(h
, 412, "Precondition Failed", 0, 0);
833 BuildResp_upnphttp(h
, 0, 0);
835 SendResp_upnphttp(h
);
836 CloseSocket_upnphttp(h
);
839 /* Parse and process Http Query
840 * called once all the HTTP headers have been received. */
842 ProcessHttpQuery_upnphttp(struct upnphttp
* h
)
844 char HttpCommand
[16];
852 for(i
= 0; i
<15 && *p
&& *p
!= ' ' && *p
!= '\r'; i
++)
853 HttpCommand
[i
] = *(p
++);
854 HttpCommand
[i
] = '\0';
857 for(i
= 0; i
<511 && *p
&& *p
!= ' ' && *p
!= '\r'; i
++)
862 HttpVer
= h
->HttpVer
;
863 for(i
= 0; i
<15 && *p
&& *p
!= '\r'; i
++)
867 /* set the interface here initially, in case there is no Host header */
868 for(i
= 0; i
<n_lan_addr
; i
++)
870 if( (h
->clientaddr
.s_addr
& lan_addr
[i
].mask
.s_addr
)
871 == (lan_addr
[i
].addr
.s_addr
& lan_addr
[i
].mask
.s_addr
))
880 /* see if we need to wait for remaining data */
881 if( (h
->reqflags
& FLAG_CHUNKED
) )
883 if( h
->req_chunklen
== -1)
888 if( h
->req_chunklen
)
893 char *chunkstart
, *chunk
, *endptr
, *endbuf
;
894 chunk
= endbuf
= chunkstart
= h
->req_buf
+ h
->req_contentoff
;
896 while ((h
->req_chunklen
= strtol(chunk
, &endptr
, 16)) > 0 && (endptr
!= chunk
) )
898 endptr
= strstr(endptr
, "\r\n");
906 memmove(endbuf
, endptr
, h
->req_chunklen
);
908 endbuf
+= h
->req_chunklen
;
909 chunk
= endptr
+ h
->req_chunklen
;
911 h
->req_contentlen
= endbuf
- chunkstart
;
912 h
->req_buflen
= endbuf
- h
->req_buf
;
916 DPRINTF(E_DEBUG
, L_HTTP
, "HTTP REQUEST: %.*s\n", h
->req_buflen
, h
->req_buf
);
917 if(h
->req_Host
&& h
->req_HostLen
> 0) {
918 const char *port
= memchr(h
->req_Host
, ':', h
->req_HostLen
);
919 size_t ip_sz
= port
? (port
- h
->req_Host
) : h
->req_HostLen
;
922 DPRINTF(E_MAXDEBUG
, L_HTTP
, "Host: %.*s\n", h
->req_HostLen
, h
->req_Host
);
924 const char *ptr
= port
+ 1;
925 for (i
= ip_sz
+ 2; i
< h
->req_HostLen
; i
++) {
926 if (*ptr
> '9' || *ptr
< '0')
930 if (i
!= h
->req_HostLen
|| atoi(port
+ 1) > 65535) {
931 DPRINTF(E_ERROR
, L_HTTP
, "DNS rebinding attack suspected (Host: %.*s)\n", h
->req_HostLen
, h
->req_Host
);
936 strncpyt(ip_buf
, h
->req_Host
, MIN(ip_sz
+ 1, sizeof(ip_buf
)));
937 if (ip_sz
>= sizeof(ip_buf
) || inet_pton(AF_INET
, ip_buf
, &addr
) <= 0 || !addr
.s_addr
) {
938 DPRINTF(E_ERROR
, L_HTTP
, "DNS rebinding attack suspected (Host: %.*s)\n", h
->req_HostLen
, h
->req_Host
);
943 if(strcmp("POST", HttpCommand
) == 0)
945 h
->req_command
= EPost
;
946 ProcessHTTPPOST_upnphttp(h
);
948 else if((strcmp("GET", HttpCommand
) == 0) || (strcmp("HEAD", HttpCommand
) == 0))
950 if( ((strcmp(h
->HttpVer
, "HTTP/1.1")==0) && !(h
->reqflags
& FLAG_HOST
)) || (h
->reqflags
& FLAG_INVALID_REQ
) )
952 DPRINTF(E_WARN
, L_HTTP
, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n");
957 else if( (h
->reqflags
& (FLAG_TIMESEEK
|FLAG_PLAYSPEED
)) &&
958 !(h
->reqflags
& FLAG_RANGE
) )
960 DPRINTF(E_WARN
, L_HTTP
, "DLNA %s requested, responding ERROR 406\n",
961 h
->reqflags
&FLAG_TIMESEEK
? "TimeSeek" : "PlaySpeed");
965 else if(strcmp("GET", HttpCommand
) == 0)
967 h
->req_command
= EGet
;
971 h
->req_command
= EHead
;
973 if(strcmp(ROOTDESC_PATH
, HttpUrl
) == 0)
975 /* If it's a Xbox360, we might need a special friendly_name to be recognized */
976 if( h
->req_client
&& h
->req_client
->type
->type
== EXbox
)
980 memcpy(model_sav
, modelnumber
, 2);
981 strcpy(modelnumber
, "1");
982 if( !strchr(friendly_name
, ':') )
984 i
= strlen(friendly_name
);
985 snprintf(friendly_name
+i
, FRIENDLYNAME_MAX_LEN
-i
, ": 1");
987 sendXMLdesc(h
, genRootDesc
);
989 friendly_name
[i
] = '\0';
990 memcpy(modelnumber
, model_sav
, 2);
992 else if( h
->req_client
&& h
->req_client
->type
->flags
& FLAG_SAMSUNG_DCM10
)
994 sendXMLdesc(h
, genRootDescSamsung
);
998 sendXMLdesc(h
, genRootDesc
);
1001 else if(strcmp(CONTENTDIRECTORY_PATH
, HttpUrl
) == 0)
1003 sendXMLdesc(h
, genContentDirectory
);
1005 else if(strcmp(CONNECTIONMGR_PATH
, HttpUrl
) == 0)
1007 sendXMLdesc(h
, genConnectionManager
);
1009 else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH
, HttpUrl
) == 0)
1011 sendXMLdesc(h
, genX_MS_MediaReceiverRegistrar
);
1013 else if(strncmp(HttpUrl
, "/MediaItems/", 12) == 0)
1015 SendResp_dlnafile(h
, HttpUrl
+12);
1017 else if(strncmp(HttpUrl
, "/Thumbnails/", 12) == 0)
1019 SendResp_thumbnail(h
, HttpUrl
+12);
1021 else if(strncmp(HttpUrl
, "/AlbumArt/", 10) == 0)
1023 SendResp_albumArt(h
, HttpUrl
+10);
1026 else if(strncmp(HttpUrl
, "/TiVoConnect", 12) == 0)
1028 if( GETFLAG(TIVO_MASK
) )
1030 if( *(HttpUrl
+12) == '?' )
1032 ProcessTiVoCommand(h
, HttpUrl
+13);
1036 DPRINTF(E_WARN
, L_HTTP
, "Invalid TiVo request! %s\n", HttpUrl
+12);
1042 DPRINTF(E_WARN
, L_HTTP
, "TiVo request with out TiVo support enabled! %s\n",
1048 else if(strncmp(HttpUrl
, "/Resized/", 9) == 0)
1050 SendResp_resizedimg(h
, HttpUrl
+9);
1052 else if(strncmp(HttpUrl
, "/icons/", 7) == 0)
1054 SendResp_icon(h
, HttpUrl
+7);
1056 else if(strncmp(HttpUrl
, "/Captions/", 10) == 0)
1058 SendResp_caption(h
, HttpUrl
+10);
1060 else if(strncmp(HttpUrl
, "/status", 7) == 0)
1062 SendResp_presentation(h
);
1064 else if(strcmp(HttpUrl
, "/") == 0)
1067 SendResp_readynas_admin(h
);
1069 SendResp_presentation(h
);
1074 DPRINTF(E_WARN
, L_HTTP
, "%s not found, responding ERROR 404\n", HttpUrl
);
1078 else if(strcmp("SUBSCRIBE", HttpCommand
) == 0)
1080 h
->req_command
= ESubscribe
;
1081 ProcessHTTPSubscribe_upnphttp(h
, HttpUrl
);
1083 else if(strcmp("UNSUBSCRIBE", HttpCommand
) == 0)
1085 h
->req_command
= EUnSubscribe
;
1086 ProcessHTTPUnSubscribe_upnphttp(h
, HttpUrl
);
1090 DPRINTF(E_WARN
, L_HTTP
, "Unsupported HTTP Command %s\n", HttpCommand
);
1096 Process_upnphttp(struct event
*ev
)
1099 struct upnphttp
*h
= ev
->data
;
1105 n
= recv(h
->ev
.fd
, buf
, 2048, 0);
1108 DPRINTF(E_ERROR
, L_HTTP
, "recv (state0): %s\n", strerror(errno
));
1113 DPRINTF(E_DEBUG
, L_HTTP
, "HTTP Connection closed unexpectedly\n");
1119 const char * endheaders
;
1120 /* if 1st arg of realloc() is null,
1121 * realloc behaves the same as malloc() */
1122 new_req_buflen
= n
+ h
->req_buflen
+ 1;
1123 if (new_req_buflen
>= 1024 * 1024)
1125 DPRINTF(E_ERROR
, L_HTTP
, "Receive headers too large (received %d bytes)\n", new_req_buflen
);
1129 h
->req_buf
= (char *)realloc(h
->req_buf
, new_req_buflen
);
1132 DPRINTF(E_ERROR
, L_HTTP
, "Receive headers: %s\n", strerror(errno
));
1136 memcpy(h
->req_buf
+ h
->req_buflen
, buf
, n
);
1138 h
->req_buf
[h
->req_buflen
] = '\0';
1139 /* search for the string "\r\n\r\n" */
1140 endheaders
= strstr(h
->req_buf
, "\r\n\r\n");
1143 h
->req_contentoff
= endheaders
- h
->req_buf
+ 4;
1144 h
->req_contentlen
= h
->req_buflen
- h
->req_contentoff
;
1145 ProcessHttpQuery_upnphttp(h
);
1151 n
= recv(h
->ev
.fd
, buf
, sizeof(buf
), 0);
1154 DPRINTF(E_ERROR
, L_HTTP
, "recv (state%d): %s\n", h
->state
, strerror(errno
));
1159 DPRINTF(E_DEBUG
, L_HTTP
, "HTTP Connection closed unexpectedly\n");
1164 buf
[sizeof(buf
)-1] = '\0';
1165 /*fwrite(buf, 1, n, stdout);*/ /* debug */
1166 h
->req_buf
= (char *)realloc(h
->req_buf
, n
+ h
->req_buflen
);
1169 DPRINTF(E_ERROR
, L_HTTP
, "Receive request body: %s\n", strerror(errno
));
1173 memcpy(h
->req_buf
+ h
->req_buflen
, buf
, n
);
1175 if((h
->req_buflen
- h
->req_contentoff
) >= h
->req_contentlen
)
1177 /* Need the struct to point to the realloc'd memory locations */
1180 ParseHttpHeaders(h
);
1181 ProcessHTTPPOST_upnphttp(h
);
1183 else if( h
->state
== 2 )
1185 ProcessHttpQuery_upnphttp(h
);
1191 DPRINTF(E_WARN
, L_HTTP
, "Unexpected state: %d\n", h
->state
);
1195 /* with response code and response message
1196 * also allocate enough memory */
1199 BuildHeader_upnphttp(struct upnphttp
* h
, int respcode
,
1200 const char * respmsg
,
1203 static const char httpresphead
[] =
1205 "Content-Type: %s\r\n"
1206 "Connection: close\r\n"
1207 "Content-Length: %d\r\n"
1208 "Server: " MINIDLNA_SERVER_STRING
"\r\n";
1209 time_t curtime
= time(NULL
);
1212 struct string_s res
;
1215 templen
= sizeof(httpresphead
) + 256 + bodylen
;
1216 h
->res_buf
= (char *)malloc(templen
);
1217 h
->res_buf_alloclen
= templen
;
1219 res
.data
= h
->res_buf
;
1220 res
.size
= h
->res_buf_alloclen
;
1222 strcatf(&res
, httpresphead
, "HTTP/1.1",
1224 (h
->respflags
&FLAG_HTML
)?"text/html":"text/xml; charset=\"utf-8\"",
1226 /* Additional headers */
1227 if(h
->respflags
& FLAG_TIMEOUT
) {
1228 strcatf(&res
, "Timeout: Second-");
1229 if(h
->req_Timeout
) {
1230 strcatf(&res
, "%d\r\n", h
->req_Timeout
);
1232 strcatf(&res
, "300\r\n");
1235 if(h
->respflags
& FLAG_SID
) {
1236 strcatf(&res
, "SID: %.*s\r\n", h
->req_SIDLen
, h
->req_SID
);
1238 if(h
->reqflags
& FLAG_LANGUAGE
) {
1239 strcatf(&res
, "Content-Language: en\r\n");
1241 strftime(date
, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime
));
1242 strcatf(&res
, "Date: %s\r\n", date
);
1243 strcatf(&res
, "EXT:\r\n");
1244 strcatf(&res
, "\r\n");
1245 h
->res_buflen
= res
.off
;
1246 if(h
->res_buf_alloclen
< (h
->res_buflen
+ bodylen
))
1248 h
->res_buf
= (char *)realloc(h
->res_buf
, (h
->res_buflen
+ bodylen
));
1249 h
->res_buf_alloclen
= h
->res_buflen
+ bodylen
;
1254 BuildResp2_upnphttp(struct upnphttp
* h
, int respcode
,
1255 const char * respmsg
,
1256 const char * body
, int bodylen
)
1258 BuildHeader_upnphttp(h
, respcode
, respmsg
, bodylen
);
1259 if( h
->req_command
== EHead
)
1262 memcpy(h
->res_buf
+ h
->res_buflen
, body
, bodylen
);
1263 h
->res_buflen
+= bodylen
;
1266 /* responding 200 OK ! */
1268 BuildResp_upnphttp(struct upnphttp
*h
, const char *body
, int bodylen
)
1270 BuildResp2_upnphttp(h
, 200, "OK", body
, bodylen
);
1274 SendResp_upnphttp(struct upnphttp
* h
)
1277 DPRINTF(E_DEBUG
, L_HTTP
, "HTTP RESPONSE: %.*s\n", h
->res_buflen
, h
->res_buf
);
1278 n
= send(h
->ev
.fd
, h
->res_buf
, h
->res_buflen
, 0);
1281 DPRINTF(E_ERROR
, L_HTTP
, "send(res_buf): %s\n", strerror(errno
));
1283 else if(n
< h
->res_buflen
)
1285 /* TODO : handle correctly this case */
1286 DPRINTF(E_ERROR
, L_HTTP
, "send(res_buf): %d bytes sent (out of %d)\n",
1292 send_data(struct upnphttp
* h
, char * header
, size_t size
, int flags
)
1296 n
= send(h
->ev
.fd
, header
, size
, flags
);
1299 DPRINTF(E_ERROR
, L_HTTP
, "send(res_buf): %s\n", strerror(errno
));
1301 else if(n
< h
->res_buflen
)
1303 /* TODO : handle correctly this case */
1304 DPRINTF(E_ERROR
, L_HTTP
, "send(res_buf): %d bytes sent (out of %d)\n",
1315 send_file(struct upnphttp
* h
, int sendfd
, off_t offset
, off_t end_offset
)
1321 int try_sendfile
= 1;
1324 while( offset
<= end_offset
)
1329 send_size
= ( ((end_offset
- offset
) < MAX_BUFFER_SIZE
) ? (end_offset
- offset
+ 1) : MAX_BUFFER_SIZE
);
1330 ret
= sys_sendfile(h
->ev
.fd
, sendfd
, &offset
, send_size
);
1333 DPRINTF(E_DEBUG
, L_HTTP
, "sendfile error :: error no. %d [%s]\n", errno
, strerror(errno
));
1334 /* If sendfile isn't supported on the filesystem, don't bother trying to use it again. */
1335 if( errno
== EOVERFLOW
|| errno
== EINVAL
)
1337 else if( errno
!= EAGAIN
)
1342 break; /* Premature end of file */
1346 //DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset);
1351 /* Fall back to regular I/O */
1353 buf
= malloc(MIN_BUFFER_SIZE
);
1354 send_size
= (((end_offset
- offset
) < MIN_BUFFER_SIZE
) ? (end_offset
- offset
+ 1) : MIN_BUFFER_SIZE
);
1355 lseek(sendfd
, offset
, SEEK_SET
);
1356 ret
= read(sendfd
, buf
, send_size
);
1358 DPRINTF(E_DEBUG
, L_HTTP
, "read error :: error no. %d [%s]\n", errno
, strerror(errno
));
1359 if( errno
== EAGAIN
)
1366 break; /* premature end of file */
1368 ret
= write(h
->ev
.fd
, buf
, ret
);
1370 DPRINTF(E_DEBUG
, L_HTTP
, "write error :: error no. %d [%s]\n", errno
, strerror(errno
));
1371 if( errno
== EAGAIN
)
1382 start_dlna_header(struct string_s
*str
, int respcode
, const char *tmode
, const char *mime
)
1388 strftime(date
, sizeof(date
),"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&now
));
1389 strcatf(str
, "HTTP/1.1 %d OK\r\n"
1390 "Connection: close\r\n"
1392 "Server: " MINIDLNA_SERVER_STRING
"\r\n"
1394 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1395 "transferMode.dlna.org: %s\r\n"
1396 "Content-Type: %s\r\n",
1397 respcode
, date
, tmode
, mime
);
1401 _open_file(const char *orig_path
)
1403 struct media_dir_s
*media_path
;
1408 if (!GETFLAG(WIDE_LINKS_MASK
))
1410 path
= realpath(orig_path
, buf
);
1413 DPRINTF(E_ERROR
, L_HTTP
, "Error resolving path %s: %s\n",
1414 orig_path
, strerror(errno
));
1418 for (media_path
= media_dirs
; media_path
; media_path
= media_path
->next
)
1420 if (strncmp(path
, media_path
->path
, strlen(media_path
->path
)) == 0)
1423 if (!media_path
&& strncmp(path
, db_path
, strlen(db_path
)))
1425 DPRINTF(E_ERROR
, L_HTTP
, "Rejecting wide link %s -> %s\n",
1433 fd
= open(path
, O_RDONLY
);
1435 DPRINTF(E_ERROR
, L_HTTP
, "Error opening %s\n", path
);
1441 SendResp_icon(struct upnphttp
* h
, char * icon
)
1444 char mime
[12] = "image/";
1447 struct string_s str
;
1449 if( strcmp(icon
, "sm.png") == 0 )
1451 DPRINTF(E_DEBUG
, L_HTTP
, "Sending small PNG icon\n");
1452 data
= (char *)png_sm
;
1453 size
= sizeof(png_sm
)-1;
1454 strcpy(mime
+6, "png");
1456 else if( strcmp(icon
, "lrg.png") == 0 )
1458 DPRINTF(E_DEBUG
, L_HTTP
, "Sending large PNG icon\n");
1459 data
= (char *)png_lrg
;
1460 size
= sizeof(png_lrg
)-1;
1461 strcpy(mime
+6, "png");
1463 else if( strcmp(icon
, "sm.jpg") == 0 )
1465 DPRINTF(E_DEBUG
, L_HTTP
, "Sending small JPEG icon\n");
1466 data
= (char *)jpeg_sm
;
1467 size
= sizeof(jpeg_sm
)-1;
1468 strcpy(mime
+6, "jpeg");
1470 else if( strcmp(icon
, "lrg.jpg") == 0 )
1472 DPRINTF(E_DEBUG
, L_HTTP
, "Sending large JPEG icon\n");
1473 data
= (char *)jpeg_lrg
;
1474 size
= sizeof(jpeg_lrg
)-1;
1475 strcpy(mime
+6, "jpeg");
1479 DPRINTF(E_WARN
, L_HTTP
, "Invalid icon request: %s\n", icon
);
1484 INIT_STR(str
, header
);
1486 start_dlna_header(&str
, 200, "Interactive", mime
);
1487 strcatf(&str
, "Content-Length: %d\r\n\r\n", size
);
1489 if( send_data(h
, str
.data
, str
.off
, MSG_MORE
) == 0 )
1491 if( h
->req_command
!= EHead
)
1492 send_data(h
, data
, size
, 0);
1494 CloseSocket_upnphttp(h
);
1498 SendResp_albumArt(struct upnphttp
* h
, char * object
)
1505 struct string_s str
;
1507 if( h
->reqflags
& (FLAG_XFERSTREAMING
|FLAG_RANGE
) )
1509 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Streaming with an image!\n");
1514 id
= strtoll(object
, NULL
, 10);
1516 path
= sql_get_text_field(db
, "SELECT PATH from ALBUM_ART where ID = '%lld'", id
);
1519 DPRINTF(E_WARN
, L_HTTP
, "ALBUM_ART ID %s not found, responding ERROR 404\n", object
);
1523 DPRINTF(E_INFO
, L_HTTP
, "Serving album art ID: %lld [%s]\n", id
, path
);
1525 fd
= _open_file(path
);
1535 size
= lseek(fd
, 0, SEEK_END
);
1536 lseek(fd
, 0, SEEK_SET
);
1538 INIT_STR(str
, header
);
1540 start_dlna_header(&str
, 200, "Interactive", "image/jpeg");
1541 strcatf(&str
, "Content-Length: %jd\r\n"
1542 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n\r\n",
1545 if( send_data(h
, str
.data
, str
.off
, MSG_MORE
) == 0 )
1547 if( h
->req_command
!= EHead
)
1548 send_file(h
, fd
, 0, size
-1);
1551 CloseSocket_upnphttp(h
);
1555 SendResp_caption(struct upnphttp
* h
, char * object
)
1562 struct string_s str
;
1564 id
= strtoll(object
, NULL
, 10);
1566 path
= sql_get_text_field(db
, "SELECT PATH from CAPTIONS where ID = %lld", id
);
1569 DPRINTF(E_WARN
, L_HTTP
, "CAPTION ID %s not found, responding ERROR 404\n", object
);
1573 DPRINTF(E_INFO
, L_HTTP
, "Serving caption ID: %lld [%s]\n", id
, path
);
1575 fd
= _open_file(path
);
1585 size
= lseek(fd
, 0, SEEK_END
);
1586 lseek(fd
, 0, SEEK_SET
);
1588 INIT_STR(str
, header
);
1590 start_dlna_header(&str
, 200, "Interactive", "smi/caption");
1591 strcatf(&str
, "Content-Length: %jd\r\n\r\n", (intmax_t)size
);
1593 if( send_data(h
, str
.data
, str
.off
, MSG_MORE
) == 0 )
1595 if( h
->req_command
!= EHead
)
1596 send_file(h
, fd
, 0, size
-1);
1599 CloseSocket_upnphttp(h
);
1603 SendResp_thumbnail(struct upnphttp
* h
, char * object
)
1610 struct string_s str
;
1612 if( h
->reqflags
& (FLAG_XFERSTREAMING
|FLAG_RANGE
) )
1614 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Streaming with an image!\n");
1619 id
= strtoll(object
, NULL
, 10);
1620 path
= sql_get_text_field(db
, "SELECT PATH from DETAILS where ID = '%lld'", id
);
1623 DPRINTF(E_WARN
, L_HTTP
, "DETAIL ID %s not found, responding ERROR 404\n", object
);
1627 DPRINTF(E_INFO
, L_HTTP
, "Serving thumbnail for ObjectId: %lld [%s]\n", id
, path
);
1629 if( access(path
, F_OK
) != 0 )
1631 DPRINTF(E_ERROR
, L_HTTP
, "Error accessing %s\n", path
);
1637 l
= exif_loader_new();
1638 exif_loader_write_file(l
, path
);
1639 ed
= exif_loader_get_data(l
);
1640 exif_loader_unref(l
);
1643 if( !ed
|| !ed
->size
)
1647 exif_data_unref(ed
);
1651 INIT_STR(str
, header
);
1653 start_dlna_header(&str
, 200, "Interactive", "image/jpeg");
1654 strcatf(&str
, "Content-Length: %jd\r\n"
1655 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN;DLNA.ORG_CI=1\r\n\r\n",
1656 (intmax_t)ed
->size
);
1658 if( send_data(h
, str
.data
, str
.off
, MSG_MORE
) == 0 )
1660 if( h
->req_command
!= EHead
)
1661 send_data(h
, (char *)ed
->data
, ed
->size
, 0);
1663 exif_data_unref(ed
);
1664 CloseSocket_upnphttp(h
);
1668 SendResp_resizedimg(struct upnphttp
* h
, char * object
)
1672 struct string_s str
;
1675 uint32_t dlna_flags
= DLNA_FLAG_DLNA_V1_5
|DLNA_FLAG_HTTP_STALLING
|DLNA_FLAG_TM_B
|DLNA_FLAG_TM_I
;
1676 int width
=640, height
=480, dstw
, dsth
, size
;
1678 unsigned char * data
= NULL
;
1679 char *path
, *file_path
= NULL
;
1680 char *resolution
= NULL
;
1682 char *saveptr
, *item
= NULL
;
1684 int pixw
= 0, pixh
= 0;
1686 int rows
=0, chunked
, ret
;
1687 image_s
*imsrc
= NULL
, *imdst
= NULL
;
1691 id
= strtoll(object
, &saveptr
, 10);
1692 snprintf(buf
, sizeof(buf
), "SELECT PATH, RESOLUTION, ROTATION from DETAILS where ID = '%lld'", (long long)id
);
1693 ret
= sql_get_table(db
, buf
, &result
, &rows
, NULL
);
1694 if( ret
!= SQLITE_OK
)
1701 file_path
= result
[3];
1702 resolution
= result
[4];
1704 rotate
= atoi(result
[5]);
1706 if( !file_path
|| !resolution
|| (access(file_path
, F_OK
) != 0) )
1708 DPRINTF(E_WARN
, L_HTTP
, "%s not found, responding ERROR 404\n", object
);
1709 sqlite3_free_table(result
);
1715 saveptr
= strchr(saveptr
, '?');
1716 path
= saveptr
? saveptr
+ 1 : object
;
1717 for( item
= strtok_r(path
, "&,", &saveptr
); item
!= NULL
; item
= strtok_r(NULL
, "&,", &saveptr
) )
1719 decodeString(item
, 1);
1721 key
= strsep(&val
, "=");
1724 DPRINTF(E_DEBUG
, L_GENERAL
, "%s: %s\n", key
, val
);
1725 if( strcasecmp(key
, "width") == 0 )
1729 else if( strcasecmp(key
, "height") == 0 )
1733 else if( strcasecmp(key
, "rotation") == 0 )
1735 rotate
= (rotate
+ atoi(val
)) % 360;
1736 sql_exec(db
, "UPDATE DETAILS set ROTATION = %d where ID = %lld", rotate
, id
);
1738 else if( strcasecmp(key
, "pixelshape") == 0 )
1740 ret
= sscanf(val
, "%d:%d", &pixw
, &pixh
);
1748 newpid
= process_fork(h
->req_client
);
1751 CloseSocket_upnphttp(h
);
1755 if( h
->reqflags
& (FLAG_XFERSTREAMING
|FLAG_RANGE
) )
1757 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Streaming with an image!\n");
1762 DPRINTF(E_INFO
, L_HTTP
, "Serving resized image for ObjectId: %lld [%s]\n", id
, file_path
);
1764 DPRINTF(E_DEBUG
, L_HTTP
, "Rotating image %d degrees\n", rotate
);
1768 ret
= sscanf(resolution
, "%dx%d", &srch
, &srcw
);
1772 ret
= sscanf(resolution
, "%dx%d", &srch
, &srcw
);
1773 rotate
= ROTATE_270
;
1776 ret
= sscanf(resolution
, "%dx%d", &srcw
, &srch
);
1777 rotate
= ROTATE_180
;
1780 ret
= sscanf(resolution
, "%dx%d", &srcw
, &srch
);
1781 rotate
= ROTATE_NONE
;
1789 /* Figure out the best destination resolution we can use */
1791 dsth
= ((((width
<<10)/srcw
)*srch
)>>10);
1795 dstw
= (((height
<<10)/srch
) * srcw
>>10);
1797 /* Account for pixel shape */
1801 dsth
= dsth
* pixw
/ pixh
;
1802 else if( pixw
> pixh
)
1803 dstw
= dstw
* pixh
/ pixw
;
1806 if( dstw
<= 160 && dsth
<= 160 )
1807 strcpy(dlna_pn
, "DLNA.ORG_PN=JPEG_TN;");
1808 else if( dstw
<= 640 && dsth
<= 480 )
1809 strcpy(dlna_pn
, "DLNA.ORG_PN=JPEG_SM;");
1810 else if( dstw
<= 1024 && dsth
<= 768 )
1811 strcpy(dlna_pn
, "DLNA.ORG_PN=JPEG_MED;");
1813 strcpy(dlna_pn
, "DLNA.ORG_PN=JPEG_LRG;");
1815 if( srcw
>>4 >= dstw
&& srch
>>4 >= dsth
)
1817 else if( srcw
>>3 >= dstw
&& srch
>>3 >= dsth
)
1819 else if( srcw
>>2 >= dstw
&& srch
>>2 >= dsth
)
1822 INIT_STR(str
, header
);
1825 if( (h
->reqflags
& FLAG_XFERBACKGROUND
) && (setpriority(PRIO_PROCESS
, 0, 19) == 0) )
1826 tmode
= "Background";
1829 tmode
= "Interactive";
1830 start_dlna_header(&str
, 200, tmode
, "image/jpeg");
1831 strcatf(&str
, "contentFeatures.dlna.org: %sDLNA.ORG_CI=1;DLNA.ORG_FLAGS=%08X%024X\r\n",
1832 dlna_pn
, dlna_flags
, 0);
1834 if( strcmp(h
->HttpVer
, "HTTP/1.0") == 0 )
1837 imsrc
= image_new_from_jpeg(file_path
, 1, NULL
, 0, scale
, rotate
);
1842 strcatf(&str
, "Transfer-Encoding: chunked\r\n\r\n");
1849 DPRINTF(E_WARN
, L_HTTP
, "Unable to open image %s!\n", file_path
);
1854 imdst
= image_resize(imsrc
, dstw
, dsth
);
1855 data
= image_save_to_jpeg_buf(imdst
, &size
);
1857 strcatf(&str
, "Content-Length: %d\r\n\r\n", size
);
1860 if( (send_data(h
, str
.data
, str
.off
, 0) == 0) && (h
->req_command
!= EHead
) )
1864 imsrc
= image_new_from_jpeg(file_path
, 1, NULL
, 0, scale
, rotate
);
1867 DPRINTF(E_WARN
, L_HTTP
, "Unable to open image %s!\n", file_path
);
1871 imdst
= image_resize(imsrc
, dstw
, dsth
);
1872 data
= image_save_to_jpeg_buf(imdst
, &size
);
1874 ret
= sprintf(buf
, "%x\r\n", size
);
1875 send_data(h
, buf
, ret
, MSG_MORE
);
1876 send_data(h
, (char *)data
, size
, MSG_MORE
);
1877 send_data(h
, "\r\n0\r\n\r\n", 7, 0);
1881 send_data(h
, (char *)data
, size
, 0);
1884 DPRINTF(E_INFO
, L_HTTP
, "Done serving %s\n", file_path
);
1889 CloseSocket_upnphttp(h
);
1891 sqlite3_free_table(result
);
1899 SendResp_dlnafile(struct upnphttp
*h
, char *object
)
1902 struct string_s str
;
1906 off_t total
, offset
, size
;
1909 uint32_t dlna_flags
= DLNA_FLAG_DLNA_V1_5
|DLNA_FLAG_HTTP_STALLING
|DLNA_FLAG_TM_B
;
1910 uint32_t cflags
= h
->req_client
? h
->req_client
->type
->flags
: 0;
1912 enum client_types ctype
= h
->req_client
? h
->req_client
->type
->type
: 0;
1913 static struct { int64_t id
;
1914 enum client_types client
;
1915 char path
[PATH_MAX
];
1918 } last_file
= { 0, 0 };
1923 id
= strtoll(object
, NULL
, 10);
1924 if( cflags
& FLAG_MS_PFS
)
1926 if( strstr(object
, "?albumArt=true") )
1929 art
= sql_get_text_field(db
, "SELECT ALBUM_ART from DETAILS where ID = '%lld'", id
);
1932 SendResp_albumArt(h
, art
);
1940 if( id
!= last_file
.id
|| ctype
!= last_file
.client
)
1942 snprintf(buf
, sizeof(buf
), "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", (long long)id
);
1943 ret
= sql_get_table(db
, buf
, &result
, &rows
, NULL
);
1944 if( (ret
!= SQLITE_OK
) )
1946 DPRINTF(E_ERROR
, L_HTTP
, "Didn't find valid file for %lld!\n", (long long)id
);
1950 if( !rows
|| !result
[3] || !result
[4] )
1952 DPRINTF(E_WARN
, L_HTTP
, "%s not found, responding ERROR 404\n", object
);
1953 sqlite3_free_table(result
);
1957 /* Cache the result */
1959 last_file
.client
= ctype
;
1960 strncpy(last_file
.path
, result
[3], sizeof(last_file
.path
)-1);
1963 strncpy(last_file
.mime
, result
[4], sizeof(last_file
.mime
)-1);
1964 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
1965 if( cflags
& FLAG_SAMSUNG
)
1967 if( strcmp(last_file
.mime
+6, "x-matroska") == 0 )
1968 strcpy(last_file
.mime
+8, "mkv");
1969 /* Samsung TV's such as the A750 can natively support many
1970 Xvid/DivX AVI's however, the DLNA server needs the
1971 mime type to say video/mpeg */
1972 else if( ctype
== ESamsungSeriesA
&& strcmp(last_file
.mime
+6, "x-msvideo") == 0 )
1973 strcpy(last_file
.mime
+6, "mpeg");
1975 /* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */
1976 else if( ctype
== ESonyBDP
)
1978 if( strcmp(last_file
.mime
+6, "x-matroska") == 0 ||
1979 strcmp(last_file
.mime
+6, "mpeg") == 0 )
1980 strcpy(last_file
.mime
+6, "divx");
1984 snprintf(last_file
.dlna
, sizeof(last_file
.dlna
), "DLNA.ORG_PN=%s;", result
[5]);
1986 last_file
.dlna
[0] = '\0';
1987 sqlite3_free_table(result
);
1990 newpid
= process_fork(h
->req_client
);
1993 CloseSocket_upnphttp(h
);
1998 DPRINTF(E_INFO
, L_HTTP
, "Serving DetailID: %lld [%s]\n", (long long)id
, last_file
.path
);
2000 if( h
->reqflags
& FLAG_XFERSTREAMING
)
2002 if( strncmp(last_file
.mime
, "image", 5) == 0 )
2004 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Streaming with an image!\n");
2009 else if( h
->reqflags
& FLAG_XFERINTERACTIVE
)
2011 if( h
->reqflags
& FLAG_REALTIMEINFO
)
2013 DPRINTF(E_WARN
, L_HTTP
, "Bad realTimeInfo flag with Interactive request!\n");
2017 if( strncmp(last_file
.mime
, "image", 5) != 0 )
2019 DPRINTF(E_WARN
, L_HTTP
, "Client tried to specify transferMode as Interactive without an image!\n");
2020 /* Samsung TVs (well, at least the A950) do this for some reason,
2021 * and I don't see them fixing this bug any time soon. */
2022 if( !(cflags
& FLAG_SAMSUNG
) || GETFLAG(DLNA_STRICT_MASK
) )
2030 offset
= h
->req_RangeStart
;
2031 sendfh
= _open_file(last_file
.path
);
2039 size
= lseek(sendfh
, 0, SEEK_END
);
2040 lseek(sendfh
, 0, SEEK_SET
);
2042 INIT_STR(str
, header
);
2045 if( (h
->reqflags
& FLAG_XFERBACKGROUND
) && (setpriority(PRIO_PROCESS
, 0, 19) == 0) )
2046 tmode
= "Background";
2049 if( strncmp(last_file
.mime
, "image", 5) == 0 )
2050 tmode
= "Interactive";
2052 tmode
= "Streaming";
2054 start_dlna_header(&str
, (h
->reqflags
& FLAG_RANGE
? 206 : 200), tmode
, last_file
.mime
);
2056 if( h
->reqflags
& FLAG_RANGE
)
2058 if( !h
->req_RangeEnd
|| h
->req_RangeEnd
== size
)
2060 h
->req_RangeEnd
= size
- 1;
2062 if( (h
->req_RangeStart
> h
->req_RangeEnd
) || (h
->req_RangeStart
< 0) )
2064 DPRINTF(E_WARN
, L_HTTP
, "Specified range was invalid!\n");
2069 if( h
->req_RangeEnd
>= size
)
2071 DPRINTF(E_WARN
, L_HTTP
, "Specified range was outside file boundaries!\n");
2077 total
= h
->req_RangeEnd
- h
->req_RangeStart
+ 1;
2078 strcatf(&str
, "Content-Length: %jd\r\n"
2079 "Content-Range: bytes %jd-%jd/%jd\r\n",
2080 (intmax_t)total
, (intmax_t)h
->req_RangeStart
,
2081 (intmax_t)h
->req_RangeEnd
, (intmax_t)size
);
2085 h
->req_RangeEnd
= size
- 1;
2087 strcatf(&str
, "Content-Length: %jd\r\n", (intmax_t)total
);
2090 switch( *last_file
.mime
)
2093 dlna_flags
|= DLNA_FLAG_TM_I
;
2098 dlna_flags
|= DLNA_FLAG_TM_S
;
2102 if( h
->reqflags
& FLAG_CAPTION
)
2104 if( sql_get_int_field(db
, "SELECT ID from CAPTIONS where ID = '%lld'", (long long)id
) > 0 )
2105 strcatf(&str
, "CaptionInfo.sec: http://%s:%d/Captions/%lld.srt\r\n",
2106 lan_addr
[h
->iface
].str
, runtime_vars
.port
, (long long)id
);
2109 strcatf(&str
, "Accept-Ranges: bytes\r\n"
2110 "contentFeatures.dlna.org: %sDLNA.ORG_OP=%02X;DLNA.ORG_CI=%X;DLNA.ORG_FLAGS=%08X%024X\r\n\r\n",
2111 last_file
.dlna
, 1, 0, dlna_flags
, 0);
2113 //DEBUG DPRINTF(E_DEBUG, L_HTTP, "RESPONSE: %s\n", str.data);
2114 if( send_data(h
, str
.data
, str
.off
, MSG_MORE
) == 0 )
2116 if( h
->req_command
!= EHead
)
2117 send_file(h
, sendfh
, offset
, h
->req_RangeEnd
);
2121 CloseSocket_upnphttp(h
);