1 /* MiniDLNA project
2 *
3 * http://sourceforge.net/projects/minidlna/
4 *
5 * MiniDLNA media server
6 * Copyright (C) 2008-2009 Justin Maggard
7 *
8 * This file is part of MiniDLNA.
9 *
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.
13 *
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.
18 *
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/>.
21 *
22 * Portions of the code from the MiniUPnP project:
23 *
24 * Copyright (c) 2006-2007, Thomas Bernard
25 * All rights reserved.
26 *
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.
36 *
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.
48 */
49 #include <stdlib.h>
50 #include <unistd.h>
51 #include <stdio.h>
52 #include <string.h>
53 #include <sys/types.h>
54 #include <sys/socket.h>
55 #include <sys/param.h>
56 #include <ctype.h>
57 #include <sys/types.h>
58 #include <sys/stat.h>
59 #include <fcntl.h>
60 #include <errno.h>
61 #include <arpa/inet.h>
62 #include <sys/time.h>
63 #include <sys/resource.h>
64 #include <limits.h>
65
66 #include "config.h"
67 #include "event.h"
68 #include "upnpglobalvars.h"
69 #include "upnphttp.h"
70 #include "upnpdescgen.h"
71 #include "minidlnapath.h"
72 #include "upnpsoap.h"
73 #include "upnpevents.h"
74 #include "utils.h"
75 #include "getifaddr.h"
76 #include "image_utils.h"
77 #include "log.h"
78 #include "sql.h"
79 #include <libexif/exif-loader.h>
80 #include "tivo_utils.h"
81 #include "tivo_commands.h"
82 #include "clients.h"
83 #include "process.h"
84 #include "sendfile.h"
85
86 #define MAX_BUFFER_SIZE 2147483647
87 #define MIN_BUFFER_SIZE 65536
88
89 #define INIT_STR(s, d) { s.data = d; s.size = sizeof(d); s.off = 0; }
90
91 #include "icons.c"
92
93 enum event_type {
94 E_INVALID,
95 E_SUBSCRIBE,
96 E_RENEW
97 };
98
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);
106
107 struct upnphttp *
New_upnphttp(int s)108 New_upnphttp(int s)
109 {
110 struct upnphttp * ret;
111 if(s<0)
112 return NULL;
113 ret = (struct upnphttp *)malloc(sizeof(struct upnphttp));
114 if(ret == NULL)
115 return NULL;
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);
119 return ret;
120 }
121
122 void
CloseSocket_upnphttp(struct upnphttp * h)123 CloseSocket_upnphttp(struct upnphttp * h)
124 {
125
126 event_module.del(&h->ev, EV_FLAG_CLOSING);
127 if(close(h->ev.fd) < 0)
128 {
129 DPRINTF(E_ERROR, L_HTTP, "CloseSocket_upnphttp: close(%d): %s\n", h->ev.fd, strerror(errno));
130 }
131 h->ev.fd = -1;
132 h->state = 100;
133 }
134
135 void
Delete_upnphttp(struct upnphttp * h)136 Delete_upnphttp(struct upnphttp * h)
137 {
138 if(h)
139 {
140 if(h->ev.fd >= 0)
141 CloseSocket_upnphttp(h);
142 free(h->req_buf);
143 free(h->res_buf);
144 free(h);
145 }
146 }
147
148 /* parse HttpHeaders of the REQUEST */
149 static void
ParseHttpHeaders(struct upnphttp * h)150 ParseHttpHeaders(struct upnphttp * h)
151 {
152 int client = 0;
153 char * line;
154 char * colon;
155 char * p;
156 int n;
157 line = h->req_buf;
158 /* TODO : check if req_buf, contentoff are ok */
159 while(line < (h->req_buf + h->req_contentoff))
160 {
161 colon = strchr(line, ':');
162 if(colon)
163 {
164 if(strncasecmp(line, "Content-Length", 14)==0)
165 {
166 p = colon;
167 while(*p && (*p < '0' || *p > '9'))
168 p++;
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;
173 }
174 }
175 else if(strncasecmp(line, "SOAPAction", 10)==0)
176 {
177 p = colon;
178 n = 0;
179 while(*p == ':' || *p == ' ' || *p == '\t')
180 p++;
181 while(p[n] >= ' ')
182 n++;
183 if(n >= 2 &&
184 ((p[0] == '"' && p[n-1] == '"') ||
185 (p[0] == '\'' && p[n-1] == '\'')))
186 {
187 p++;
188 n -= 2;
189 }
190 h->req_soapAction = p;
191 h->req_soapActionLen = n;
192 }
193 else if(strncasecmp(line, "Callback", 8)==0)
194 {
195 p = colon;
196 while(*p && *p != '<' && *p != '\r' )
197 p++;
198 n = 0;
199 while(p[n] && p[n] != '>' && p[n] != '\r' )
200 n++;
201 h->req_Callback = p + 1;
202 h->req_CallbackLen = MAX(0, n - 1);
203 }
204 else if(strncasecmp(line, "SID", 3)==0)
205 {
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++)
209 {
210 if(!isspace(*p))
211 {
212 p = NULL; //unexpected header
213 break;
214 }
215 }
216 if(p) {
217 p = colon + 1;
218 while(isspace(*p))
219 p++;
220 n = 0;
221 while(p[n] && !isspace(p[n]))
222 n++;
223 h->req_SID = p;
224 h->req_SIDLen = n;
225 }
226 }
227 else if(strncasecmp(line, "NT", 2)==0)
228 {
229 p = colon + 1;
230 while(isspace(*p))
231 p++;
232 n = 0;
233 while(p[n] && !isspace(p[n]))
234 n++;
235 h->req_NT = p;
236 h->req_NTLen = n;
237 }
238 /* Timeout: Seconds-nnnn */
239 /* TIMEOUT
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)
246 {
247 p = colon + 1;
248 while(isspace(*p))
249 p++;
250 if(strncasecmp(p, "Second-", 7)==0) {
251 h->req_Timeout = atoi(p+7);
252 }
253 }
254 // Range: bytes=xxx-yyy
255 else if(strncasecmp(line, "Range", 5)==0)
256 {
257 p = colon + 1;
258 while(isspace(*p))
259 p++;
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);
267 }
268 }
269 else if(strncasecmp(line, "Host", 4)==0)
270 {
271 int i;
272 h->reqflags |= FLAG_HOST;
273 p = colon + 1;
274 while(isspace(*p))
275 p++;
276 for(n = 0; n < n_lan_addr; n++)
277 {
278 for(i = 0; lan_addr[n].str[i]; i++)
279 {
280 if(lan_addr[n].str[i] != p[i])
281 break;
282 }
283 if(i && !lan_addr[n].str[i])
284 {
285 h->iface = n;
286 break;
287 }
288 }
289 }
290 else if(strncasecmp(line, "User-Agent", 10)==0)
291 {
292 int i;
293 /* Skip client detection if we already detected it. */
294 if( client )
295 goto next_header;
296 p = colon + 1;
297 while(isspace(*p))
298 p++;
299 for (i = 0; client_types[i].name; i++)
300 {
301 if (client_types[i].match_type != EUserAgent)
302 continue;
303 if (strstrc(p, client_types[i].match, '\r') != NULL)
304 {
305 client = i;
306 break;
307 }
308 }
309 }
310 else if(strncasecmp(line, "X-AV-Client-Info", 16)==0)
311 {
312 int i;
313 /* Skip client detection if we already detected it. */
314 if( client && client_types[client].type < EStandardDLNA150 )
315 goto next_header;
316 p = colon + 1;
317 while(isspace(*p))
318 p++;
319 for (i = 0; client_types[i].name; i++)
320 {
321 if (client_types[i].match_type != EXAVClientInfo)
322 continue;
323 if (strstrc(p, client_types[i].match, '\r') != NULL)
324 {
325 client = i;
326 break;
327 }
328 }
329 }
330 else if(strncasecmp(line, "Transfer-Encoding", 17)==0)
331 {
332 p = colon + 1;
333 while(isspace(*p))
334 p++;
335 if(strncasecmp(p, "chunked", 7)==0)
336 {
337 h->reqflags |= FLAG_CHUNKED;
338 }
339 }
340 else if(strncasecmp(line, "Accept-Language", 15)==0)
341 {
342 h->reqflags |= FLAG_LANGUAGE;
343 }
344 else if(strncasecmp(line, "getcontentFeatures.dlna.org", 27)==0)
345 {
346 p = colon + 1;
347 while(isspace(*p))
348 p++;
349 if( (*p != '1') || !isspace(p[1]) )
350 h->reqflags |= FLAG_INVALID_REQ;
351 }
352 else if(strncasecmp(line, "TimeSeekRange.dlna.org", 22)==0)
353 {
354 h->reqflags |= FLAG_TIMESEEK;
355 }
356 else if(strncasecmp(line, "PlaySpeed.dlna.org", 18)==0)
357 {
358 h->reqflags |= FLAG_PLAYSPEED;
359 }
360 else if(strncasecmp(line, "realTimeInfo.dlna.org", 21)==0)
361 {
362 h->reqflags |= FLAG_REALTIMEINFO;
363 }
364 else if(strncasecmp(line, "getAvailableSeekRange.dlna.org", 21)==0)
365 {
366 p = colon + 1;
367 while(isspace(*p))
368 p++;
369 if( (*p != '1') || !isspace(p[1]) )
370 h->reqflags |= FLAG_INVALID_REQ;
371 }
372 else if(strncasecmp(line, "transferMode.dlna.org", 21)==0)
373 {
374 p = colon + 1;
375 while(isspace(*p))
376 p++;
377 if(strncasecmp(p, "Streaming", 9)==0)
378 {
379 h->reqflags |= FLAG_XFERSTREAMING;
380 }
381 if(strncasecmp(p, "Interactive", 11)==0)
382 {
383 h->reqflags |= FLAG_XFERINTERACTIVE;
384 }
385 if(strncasecmp(p, "Background", 10)==0)
386 {
387 h->reqflags |= FLAG_XFERBACKGROUND;
388 }
389 }
390 else if(strncasecmp(line, "getCaptionInfo.sec", 18)==0)
391 {
392 h->reqflags |= FLAG_CAPTION;
393 }
394 else if(strncasecmp(line, "FriendlyName", 12)==0)
395 {
396 int i;
397 p = colon + 1;
398 while(isspace(*p))
399 p++;
400 for (i = 0; client_types[i].name; i++)
401 {
402 if (client_types[i].match_type != EFriendlyName)
403 continue;
404 if (strstrc(p, client_types[i].match, '\r') != NULL)
405 {
406 client = i;
407 break;
408 }
409 }
410 }
411 else if(strncasecmp(line, "uctt.upnp.org:", 14)==0)
412 {
413 /* Conformance testing */
414 SETFLAG(DLNA_STRICT_MASK);
415 }
416 }
417 next_header:
418 line = strstr(line, "\r\n");
419 if (!line)
420 return;
421 line += 2;
422 }
423 if (h->reqflags & FLAG_CHUNKED)
424 {
425 char *endptr;
426 h->req_chunklen = -1;
427 if (h->req_buflen <= h->req_contentoff)
428 return;
429 while( (line < (h->req_buf + h->req_buflen)) &&
430 (h->req_chunklen = strtol(line, &endptr, 16) > 0) &&
431 (endptr != line) )
432 {
433 endptr = strstr(endptr, "\r\n");
434 if (!endptr)
435 {
436 return;
437 }
438 line = endptr+h->req_chunklen+2;
439 }
440
441 if( endptr == line )
442 {
443 h->req_chunklen = -1;
444 return;
445 }
446 }
447 /* If the client type wasn't found, search the cache.
448 * This is done because a lot of clients like to send a
449 * different User-Agent with different types of requests. */
450 h->req_client = SearchClientCache(h->clientaddr, 0);
451 /* Add this client to the cache if it's not there already. */
452 if (!h->req_client)
453 {
454 h->req_client = AddClientCache(h->clientaddr, client);
455 }
456 else if (client)
457 {
458 enum client_types type = client_types[client].type;
459 enum client_types ctype = h->req_client->type->type;
460 /* If we know the client and our new detection is generic, use our cached info */
461 /* If we detected a Samsung Series B earlier, don't overwrite it with Series A info */
462 if ((ctype && ctype < EStandardDLNA150 && type >= EStandardDLNA150) ||
463 (ctype == ESamsungSeriesB && type == ESamsungSeriesA))
464 return;
465 h->req_client->type = &client_types[client];
466 h->req_client->age = time(NULL);
467 }
468 }
469
470 /* very minimalistic 400 error message */
471 static void
Send400(struct upnphttp * h)472 Send400(struct upnphttp * h)
473 {
474 static const char body400[] =
475 "<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>"
476 "<BODY><H1>Bad Request</H1>The request is invalid"
477 " for this HTTP version.</BODY></HTML>\r\n";
478 h->respflags = FLAG_HTML;
479 BuildResp2_upnphttp(h, 400, "Bad Request",
480 body400, sizeof(body400) - 1);
481 SendResp_upnphttp(h);
482 CloseSocket_upnphttp(h);
483 }
484
485 /* very minimalistic 403 error message */
486 static void
Send403(struct upnphttp * h)487 Send403(struct upnphttp * h)
488 {
489 static const char body403[] =
490 "<HTML><HEAD><TITLE>403 Forbidden</TITLE></HEAD>"
491 "<BODY><H1>Forbidden</H1>You don't have permission to access this resource."
492 "</BODY></HTML>\r\n";
493 h->respflags = FLAG_HTML;
494 BuildResp2_upnphttp(h, 403, "Forbidden",
495 body403, sizeof(body403) - 1);
496 SendResp_upnphttp(h);
497 CloseSocket_upnphttp(h);
498 }
499
500 /* very minimalistic 404 error message */
501 static void
Send404(struct upnphttp * h)502 Send404(struct upnphttp * h)
503 {
504 static const char body404[] =
505 "<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
506 "<BODY><H1>Not Found</H1>The requested URL was not found"
507 " on this server.</BODY></HTML>\r\n";
508 h->respflags = FLAG_HTML;
509 BuildResp2_upnphttp(h, 404, "Not Found",
510 body404, sizeof(body404) - 1);
511 SendResp_upnphttp(h);
512 CloseSocket_upnphttp(h);
513 }
514
515 /* very minimalistic 406 error message */
516 static void
Send406(struct upnphttp * h)517 Send406(struct upnphttp * h)
518 {
519 static const char body406[] =
520 "<HTML><HEAD><TITLE>406 Not Acceptable</TITLE></HEAD>"
521 "<BODY><H1>Not Acceptable</H1>An unsupported operation"
522 " was requested.</BODY></HTML>\r\n";
523 h->respflags = FLAG_HTML;
524 BuildResp2_upnphttp(h, 406, "Not Acceptable",
525 body406, sizeof(body406) - 1);
526 SendResp_upnphttp(h);
527 CloseSocket_upnphttp(h);
528 }
529
530 /* very minimalistic 416 error message */
531 static void
Send416(struct upnphttp * h)532 Send416(struct upnphttp * h)
533 {
534 static const char body416[] =
535 "<HTML><HEAD><TITLE>416 Requested Range Not Satisfiable</TITLE></HEAD>"
536 "<BODY><H1>Requested Range Not Satisfiable</H1>The requested range"
537 " was outside the file's size.</BODY></HTML>\r\n";
538 h->respflags = FLAG_HTML;
539 BuildResp2_upnphttp(h, 416, "Requested Range Not Satisfiable",
540 body416, sizeof(body416) - 1);
541 SendResp_upnphttp(h);
542 CloseSocket_upnphttp(h);
543 }
544
545 /* very minimalistic 500 error message */
546 void
Send500(struct upnphttp * h)547 Send500(struct upnphttp * h)
548 {
549 static const char body500[] =
550 "<HTML><HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>"
551 "<BODY><H1>Internal Server Error</H1>Server encountered "
552 "and Internal Error.</BODY></HTML>\r\n";
553 h->respflags = FLAG_HTML;
554 BuildResp2_upnphttp(h, 500, "Internal Server Errror",
555 body500, sizeof(body500) - 1);
556 SendResp_upnphttp(h);
557 CloseSocket_upnphttp(h);
558 }
559
560 /* very minimalistic 501 error message */
561 void
Send501(struct upnphttp * h)562 Send501(struct upnphttp * h)
563 {
564 static const char body501[] =
565 "<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
566 "<BODY><H1>Not Implemented</H1>The HTTP Method "
567 "is not implemented by this server.</BODY></HTML>\r\n";
568 h->respflags = FLAG_HTML;
569 BuildResp2_upnphttp(h, 501, "Not Implemented",
570 body501, sizeof(body501) - 1);
571 SendResp_upnphttp(h);
572 CloseSocket_upnphttp(h);
573 }
574
575 /* Sends the description generated by the parameter */
576 static void
sendXMLdesc(struct upnphttp * h,char * (f)(int *))577 sendXMLdesc(struct upnphttp * h, char * (f)(int *))
578 {
579 char * desc;
580 int len;
581 desc = f(&len);
582 if(!desc)
583 {
584 DPRINTF(E_ERROR, L_HTTP, "Failed to generate XML description\n");
585 Send500(h);
586 return;
587 }
588 BuildResp_upnphttp(h, desc, len);
589 SendResp_upnphttp(h);
590 CloseSocket_upnphttp(h);
591 free(desc);
592 }
593
594 #ifdef READYNAS
595 static void
SendResp_readynas_admin(struct upnphttp * h)596 SendResp_readynas_admin(struct upnphttp * h)
597 {
598 char body[128];
599 int l;
600
601 h->respflags = FLAG_HTML;
602 l = snprintf(body, sizeof(body), "<meta http-equiv=\"refresh\" content=\"0; url=https://%s/admin/\">",
603 lan_addr[h->iface].str);
604
605 BuildResp_upnphttp(h, body, l);
606 SendResp_upnphttp(h);
607 CloseSocket_upnphttp(h);
608 }
609 #endif
610
611 static void
SendResp_presentation(struct upnphttp * h)612 SendResp_presentation(struct upnphttp * h)
613 {
614 struct string_s str;
615 char body[4096];
616 int a, v, p, i;
617
618 INIT_STR(str, body);
619
620 h->respflags = FLAG_HTML;
621
622 a = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'a*'");
623 v = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'v*'");
624 p = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'i*'");
625 strcatf(&str,
626 "<HTML><HEAD><TITLE>" SERVER_NAME " " MINIDLNA_VERSION "</TITLE></HEAD>"
627 "<BODY><div style=\"text-align: center\">"
628 "<h2>" SERVER_NAME " status</h2></div>");
629
630 strcatf(&str,
631 "<h3>Media library</h3>"
632 "<table border=1 cellpadding=10>"
633 "<tr><td>Audio files</td><td>%d</td></tr>"
634 "<tr><td>Video files</td><td>%d</td></tr>"
635 "<tr><td>Image files</td><td>%d</td></tr>"
636 "</table>", a, v, p);
637
638 if (GETFLAG(SCANNING_MASK))
639 strcatf(&str,
640 "<br><i>* Media scan in progress</i><br>");
641
642 strcatf(&str,
643 "<h3>Connected clients</h3>"
644 "<table border=1 cellpadding=10>"
645 "<tr><td>ID</td><td>Type</td><td>IP Address</td><td>HW Address</td><td>Connections</td></tr>");
646 for (i = 0; i < CLIENT_CACHE_SLOTS; i++)
647 {
648 if (!clients[i].addr.s_addr)
649 continue;
650 strcatf(&str, "<tr><td>%d</td><td>%s</td><td>%s</td><td>%02X:%02X:%02X:%02X:%02X:%02X</td><td>%d</td></tr>",
651 i, clients[i].type->name, inet_ntoa(clients[i].addr),
652 clients[i].mac[0], clients[i].mac[1], clients[i].mac[2],
653 clients[i].mac[3], clients[i].mac[4], clients[i].mac[5], clients[i].connections);
654 }
655 strcatf(&str, "</table>");
656
657 strcatf(&str, "<br>%d connection%s currently open<br>", number_of_children, (number_of_children == 1 ? "" : "s"));
658 strcatf(&str, "</BODY></HTML>\r\n");
659
660 BuildResp_upnphttp(h, str.data, str.off);
661 SendResp_upnphttp(h);
662 CloseSocket_upnphttp(h);
663 }
664
665 /* ProcessHTTPPOST_upnphttp()
666 * executes the SOAP query if it is possible */
667 static void
ProcessHTTPPOST_upnphttp(struct upnphttp * h)668 ProcessHTTPPOST_upnphttp(struct upnphttp * h)
669 {
670 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
671 {
672 if(h->req_soapAction)
673 {
674 /* we can process the request */
675 DPRINTF(E_DEBUG, L_HTTP, "SOAPAction: %.*s\n", h->req_soapActionLen, h->req_soapAction);
676 ExecuteSoapAction(h,
677 h->req_soapAction,
678 h->req_soapActionLen);
679 }
680 else
681 {
682 static const char err400str[] =
683 "<html><body>Bad request</body></html>";
684 DPRINTF(E_WARN, L_HTTP, "No SOAPAction in HTTP headers\n");
685 h->respflags = FLAG_HTML;
686 BuildResp2_upnphttp(h, 400, "Bad Request",
687 err400str, sizeof(err400str) - 1);
688 SendResp_upnphttp(h);
689 CloseSocket_upnphttp(h);
690 }
691 }
692 else
693 {
694 /* waiting for remaining data */
695 h->state = 1;
696 }
697 }
698
699 static int
check_event(struct upnphttp * h)700 check_event(struct upnphttp *h)
701 {
702 enum event_type type = E_INVALID;
703
704 if (h->req_Callback)
705 {
706 if (h->req_SID || !h->req_NT)
707 {
708 BuildResp2_upnphttp(h, 400, "Bad Request",
709 "<html><body>Bad request</body></html>", 37);
710 }
711 else if (strncmp(h->req_Callback, "http://", 7) != 0 ||
712 strncmp(h->req_NT, "upnp:event", h->req_NTLen) != 0)
713 {
714 /* Missing or invalid CALLBACK : 412 Precondition Failed.
715 * If CALLBACK header is missing or does not contain a valid HTTP URL,
716 * the publisher must respond with HTTP error 412 Precondition Failed*/
717 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
718 }
719 else
720 {
721 /* Make sure callback URL points to the originating IP */
722 struct in_addr addr;
723 char addrstr[16];
724 int i = 0;
725 const char *p = h->req_Callback + 7;
726 while (!strchr("/:>", *p) && i < sizeof(addrstr) - 1 &&
727 p < (h->req_Callback + h->req_CallbackLen))
728 {
729 addrstr[i++] = *(p++);
730 }
731 addrstr[i] = '\0';
732
733 if (inet_pton(AF_INET, addrstr, &addr) <= 0 ||
734 memcmp(&addr, &h->clientaddr, sizeof(struct in_addr)))
735 {
736 DPRINTF(E_ERROR, L_HTTP, "Bad callback IP (%s)\n", addrstr);
737 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
738 }
739 else
740 type = E_SUBSCRIBE;
741 }
742 }
743 else if (h->req_SID)
744 {
745 /* subscription renew */
746 if (h->req_NT)
747 {
748 BuildResp2_upnphttp(h, 400, "Bad Request",
749 "<html><body>Bad request</body></html>", 37);
750 }
751 else
752 type = E_RENEW;
753 }
754 else
755 {
756 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
757 }
758
759 return type;
760 }
761
762 static void
ProcessHTTPSubscribe_upnphttp(struct upnphttp * h,const char * path)763 ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path)
764 {
765 const char * sid;
766 enum event_type type;
767 DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPSubscribe %s\n", path);
768 DPRINTF(E_DEBUG, L_HTTP, "Callback '%.*s' Timeout=%d\n",
769 h->req_CallbackLen, h->req_Callback, h->req_Timeout);
770 DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID);
771
772 type = check_event(h);
773 if (type == E_SUBSCRIBE)
774 {
775 /* - add to the subscriber list
776 * - respond HTTP/x.x 200 OK
777 * - Send the initial event message */
778 /* Server:, SID:; Timeout: Second-(xx|infinite) */
779 sid = upnpevents_addSubscriber(path, h->req_Callback,
780 h->req_CallbackLen, h->req_Timeout);
781 h->respflags = FLAG_TIMEOUT;
782 if (sid)
783 {
784 DPRINTF(E_DEBUG, L_HTTP, "generated sid=%s\n", sid);
785 h->respflags |= FLAG_SID;
786 h->req_SID = sid;
787 h->req_SIDLen = strlen(sid);
788 }
789 BuildResp_upnphttp(h, 0, 0);
790 }
791 else if (type == E_RENEW)
792 {
793 /* subscription renew */
794 if (renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0)
795 {
796 /* Invalid SID
797 412 Precondition Failed. If a SID does not correspond to a known,
798 un-expired subscription, the publisher must respond
799 with HTTP error 412 Precondition Failed. */
800 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
801 }
802 else
803 {
804 /* A DLNA device must enforce a 5 minute timeout */
805 h->respflags = FLAG_TIMEOUT;
806 h->req_Timeout = 300;
807 h->respflags |= FLAG_SID;
808 BuildResp_upnphttp(h, 0, 0);
809 }
810 }
811 SendResp_upnphttp(h);
812 CloseSocket_upnphttp(h);
813 }
814
815 static void
ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h,const char * path)816 ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path)
817 {
818 enum event_type type;
819 DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPUnSubscribe %s\n", path);
820 DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID);
821 /* Remove from the list */
822 type = check_event(h);
823 if (type != E_INVALID)
824 {
825 if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0)
826 BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
827 else
828 BuildResp_upnphttp(h, 0, 0);
829 }
830 SendResp_upnphttp(h);
831 CloseSocket_upnphttp(h);
832 }
833
834 /* Parse and process Http Query
835 * called once all the HTTP headers have been received. */
836 static void
ProcessHttpQuery_upnphttp(struct upnphttp * h)837 ProcessHttpQuery_upnphttp(struct upnphttp * h)
838 {
839 char HttpCommand[16];
840 char HttpUrl[512];
841 char * HttpVer;
842 char * p;
843 int i;
844 p = h->req_buf;
845 if(!p)
846 return;
847 for(i = 0; i<15 && *p && *p != ' ' && *p != '\r'; i++)
848 HttpCommand[i] = *(p++);
849 HttpCommand[i] = '\0';
850 while(*p==' ')
851 p++;
852 for(i = 0; i<511 && *p && *p != ' ' && *p != '\r'; i++)
853 HttpUrl[i] = *(p++);
854 HttpUrl[i] = '\0';
855 while(*p==' ')
856 p++;
857 HttpVer = h->HttpVer;
858 for(i = 0; i<15 && *p && *p != '\r'; i++)
859 HttpVer[i] = *(p++);
860 HttpVer[i] = '\0';
861
862 /* set the interface here initially, in case there is no Host header */
863 for(i = 0; i<n_lan_addr; i++)
864 {
865 if( (h->clientaddr.s_addr & lan_addr[i].mask.s_addr)
866 == (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr))
867 {
868 h->iface = i;
869 break;
870 }
871 }
872
873 ParseHttpHeaders(h);
874
875 /* see if we need to wait for remaining data */
876 if( (h->reqflags & FLAG_CHUNKED) )
877 {
878 if( h->req_chunklen == -1)
879 {
880 Send400(h);
881 return;
882 }
883 if( h->req_chunklen )
884 {
885 h->state = 2;
886 return;
887 }
888 char *chunkstart, *chunk, *endptr, *endbuf;
889 chunk = endbuf = chunkstart = h->req_buf + h->req_contentoff;
890
891 while ((h->req_chunklen = strtol(chunk, &endptr, 16)) > 0 && (endptr != chunk) )
892 {
893 endptr = strstr(endptr, "\r\n");
894 if (!endptr)
895 {
896 Send400(h);
897 return;
898 }
899 endptr += 2;
900
901 memmove(endbuf, endptr, h->req_chunklen);
902
903 endbuf += h->req_chunklen;
904 chunk = endptr + h->req_chunklen;
905 }
906 h->req_contentlen = endbuf - chunkstart;
907 h->req_buflen = endbuf - h->req_buf;
908 h->state = 100;
909 }
910
911 DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf);
912 if(strcmp("POST", HttpCommand) == 0)
913 {
914 h->req_command = EPost;
915 ProcessHTTPPOST_upnphttp(h);
916 }
917 else if((strcmp("GET", HttpCommand) == 0) || (strcmp("HEAD", HttpCommand) == 0))
918 {
919 if( ((strcmp(h->HttpVer, "HTTP/1.1")==0) && !(h->reqflags & FLAG_HOST)) || (h->reqflags & FLAG_INVALID_REQ) )
920 {
921 DPRINTF(E_WARN, L_HTTP, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n");
922 Send400(h);
923 return;
924 }
925 /* 7.3.33.4 */
926 else if( (h->reqflags & (FLAG_TIMESEEK|FLAG_PLAYSPEED)) &&
927 !(h->reqflags & FLAG_RANGE) )
928 {
929 DPRINTF(E_WARN, L_HTTP, "DLNA %s requested, responding ERROR 406\n",
930 h->reqflags&FLAG_TIMESEEK ? "TimeSeek" : "PlaySpeed");
931 Send406(h);
932 return;
933 }
934 else if(strcmp("GET", HttpCommand) == 0)
935 {
936 h->req_command = EGet;
937 }
938 else
939 {
940 h->req_command = EHead;
941 }
942 if(strcmp(ROOTDESC_PATH, HttpUrl) == 0)
943 {
944 /* If it's a Xbox360, we might need a special friendly_name to be recognized */
945 if( h->req_client && h->req_client->type->type == EXbox )
946 {
947 char model_sav[2];
948 i = 0;
949 memcpy(model_sav, modelnumber, 2);
950 strcpy(modelnumber, "1");
951 if( !strchr(friendly_name, ':') )
952 {
953 i = strlen(friendly_name);
954 snprintf(friendly_name+i, FRIENDLYNAME_MAX_LEN-i, ": 1");
955 }
956 sendXMLdesc(h, genRootDesc);
957 if( i )
958 friendly_name[i] = '\0';
959 memcpy(modelnumber, model_sav, 2);
960 }
961 else if( h->req_client && h->req_client->type->flags & FLAG_SAMSUNG_DCM10 )
962 {
963 sendXMLdesc(h, genRootDescSamsung);
964 }
965 else
966 {
967 sendXMLdesc(h, genRootDesc);
968 }
969 }
970 else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0)
971 {
972 sendXMLdesc(h, genContentDirectory);
973 }
974 else if(strcmp(CONNECTIONMGR_PATH, HttpUrl) == 0)
975 {
976 sendXMLdesc(h, genConnectionManager);
977 }
978 else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH, HttpUrl) == 0)
979 {
980 sendXMLdesc(h, genX_MS_MediaReceiverRegistrar);
981 }
982 else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0)
983 {
984 SendResp_dlnafile(h, HttpUrl+12);
985 }
986 else if(strncmp(HttpUrl, "/Thumbnails/", 12) == 0)
987 {
988 SendResp_thumbnail(h, HttpUrl+12);
989 }
990 else if(strncmp(HttpUrl, "/AlbumArt/", 10) == 0)
991 {
992 SendResp_albumArt(h, HttpUrl+10);
993 }
994 #ifdef TIVO_SUPPORT
995 else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0)
996 {
997 if( GETFLAG(TIVO_MASK) )
998 {
999 if( *(HttpUrl+12) == '?' )
1000 {
1001 ProcessTiVoCommand(h, HttpUrl+13);
1002 }
1003 else
1004 {
1005 DPRINTF(E_WARN, L_HTTP, "Invalid TiVo request! %s\n", HttpUrl+12);
1006 Send404(h);
1007 }
1008 }
1009 else
1010 {
1011 DPRINTF(E_WARN, L_HTTP, "TiVo request with out TiVo support enabled! %s\n",
1012 HttpUrl+12);
1013 Send404(h);
1014 }
1015 }
1016 #endif
1017 else if(strncmp(HttpUrl, "/Resized/", 9) == 0)
1018 {
1019 SendResp_resizedimg(h, HttpUrl+9);
1020 }
1021 else if(strncmp(HttpUrl, "/icons/", 7) == 0)
1022 {
1023 SendResp_icon(h, HttpUrl+7);
1024 }
1025 else if(strncmp(HttpUrl, "/Captions/", 10) == 0)
1026 {
1027 SendResp_caption(h, HttpUrl+10);
1028 }
1029 else if(strncmp(HttpUrl, "/status", 7) == 0)
1030 {
1031 SendResp_presentation(h);
1032 }
1033 else if(strcmp(HttpUrl, "/") == 0)
1034 {
1035 #ifdef READYNAS
1036 SendResp_readynas_admin(h);
1037 #else
1038 SendResp_presentation(h);
1039 #endif
1040 }
1041 else
1042 {
1043 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", HttpUrl);
1044 Send404(h);
1045 }
1046 }
1047 else if(strcmp("SUBSCRIBE", HttpCommand) == 0)
1048 {
1049 h->req_command = ESubscribe;
1050 ProcessHTTPSubscribe_upnphttp(h, HttpUrl);
1051 }
1052 else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0)
1053 {
1054 h->req_command = EUnSubscribe;
1055 ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl);
1056 }
1057 else
1058 {
1059 DPRINTF(E_WARN, L_HTTP, "Unsupported HTTP Command %s\n", HttpCommand);
1060 Send501(h);
1061 }
1062 }
1063
1064 static void
Process_upnphttp(struct event * ev)1065 Process_upnphttp(struct event *ev)
1066 {
1067 char buf[2048];
1068 struct upnphttp *h = ev->data;
1069 int n;
1070
1071 switch(h->state)
1072 {
1073 case 0:
1074 n = recv(h->ev.fd, buf, 2048, 0);
1075 if(n<0)
1076 {
1077 DPRINTF(E_ERROR, L_HTTP, "recv (state0): %s\n", strerror(errno));
1078 h->state = 100;
1079 }
1080 else if(n==0)
1081 {
1082 DPRINTF(E_DEBUG, L_HTTP, "HTTP Connection closed unexpectedly\n");
1083 h->state = 100;
1084 }
1085 else
1086 {
1087 int new_req_buflen;
1088 const char * endheaders;
1089 /* if 1st arg of realloc() is null,
1090 * realloc behaves the same as malloc() */
1091 new_req_buflen = n + h->req_buflen + 1;
1092 if (new_req_buflen >= 1024 * 1024)
1093 {
1094 DPRINTF(E_ERROR, L_HTTP, "Receive headers too large (received %d bytes)\n", new_req_buflen);
1095 h->state = 100;
1096 break;
1097 }
1098 h->req_buf = (char *)realloc(h->req_buf, new_req_buflen);
1099 if (!h->req_buf)
1100 {
1101 DPRINTF(E_ERROR, L_HTTP, "Receive headers: %s\n", strerror(errno));
1102 h->state = 100;
1103 break;
1104 }
1105 memcpy(h->req_buf + h->req_buflen, buf, n);
1106 h->req_buflen += n;
1107 h->req_buf[h->req_buflen] = '\0';
1108 /* search for the string "\r\n\r\n" */
1109 endheaders = strstr(h->req_buf, "\r\n\r\n");
1110 if(endheaders)
1111 {
1112 h->req_contentoff = endheaders - h->req_buf + 4;
1113 h->req_contentlen = h->req_buflen - h->req_contentoff;
1114 ProcessHttpQuery_upnphttp(h);
1115 }
1116 }
1117 break;
1118 case 1:
1119 case 2:
1120 n = recv(h->ev.fd, buf, sizeof(buf), 0);
1121 if(n < 0)
1122 {
1123 DPRINTF(E_ERROR, L_HTTP, "recv (state%d): %s\n", h->state, strerror(errno));
1124 h->state = 100;
1125 }
1126 else if(n == 0)
1127 {
1128 DPRINTF(E_DEBUG, L_HTTP, "HTTP Connection closed unexpectedly\n");
1129 h->state = 100;
1130 }
1131 else
1132 {
1133 buf[sizeof(buf)-1] = '\0';
1134 /*fwrite(buf, 1, n, stdout);*/ /* debug */
1135 h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen);
1136 if (!h->req_buf)
1137 {
1138 DPRINTF(E_ERROR, L_HTTP, "Receive request body: %s\n", strerror(errno));
1139 h->state = 100;
1140 break;
1141 }
1142 memcpy(h->req_buf + h->req_buflen, buf, n);
1143 h->req_buflen += n;
1144 if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
1145 {
1146 /* Need the struct to point to the realloc'd memory locations */
1147 if( h->state == 1 )
1148 {
1149 ParseHttpHeaders(h);
1150 ProcessHTTPPOST_upnphttp(h);
1151 }
1152 else if( h->state == 2 )
1153 {
1154 ProcessHttpQuery_upnphttp(h);
1155 }
1156 }
1157 }
1158 break;
1159 default:
1160 DPRINTF(E_WARN, L_HTTP, "Unexpected state: %d\n", h->state);
1161 }
1162 }
1163
1164 /* with response code and response message
1165 * also allocate enough memory */
1166
1167 void
BuildHeader_upnphttp(struct upnphttp * h,int respcode,const char * respmsg,int bodylen)1168 BuildHeader_upnphttp(struct upnphttp * h, int respcode,
1169 const char * respmsg,
1170 int bodylen)
1171 {
1172 static const char httpresphead[] =
1173 "%s %d %s\r\n"
1174 "Content-Type: %s\r\n"
1175 "Connection: close\r\n"
1176 "Content-Length: %d\r\n"
1177 "Server: " MINIDLNA_SERVER_STRING "\r\n";
1178 time_t curtime = time(NULL);
1179 char date[30];
1180 int templen;
1181 struct string_s res;
1182 if(!h->res_buf)
1183 {
1184 templen = sizeof(httpresphead) + 256 + bodylen;
1185 h->res_buf = (char *)malloc(templen);
1186 h->res_buf_alloclen = templen;
1187 }
1188 res.data = h->res_buf;
1189 res.size = h->res_buf_alloclen;
1190 res.off = 0;
1191 strcatf(&res, httpresphead, "HTTP/1.1",
1192 respcode, respmsg,
1193 (h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"",
1194 bodylen);
1195 /* Additional headers */
1196 if(h->respflags & FLAG_TIMEOUT) {
1197 strcatf(&res, "Timeout: Second-");
1198 if(h->req_Timeout) {
1199 strcatf(&res, "%d\r\n", h->req_Timeout);
1200 } else {
1201 strcatf(&res, "300\r\n");
1202 }
1203 }
1204 if(h->respflags & FLAG_SID) {
1205 strcatf(&res, "SID: %.*s\r\n", h->req_SIDLen, h->req_SID);
1206 }
1207 if(h->reqflags & FLAG_LANGUAGE) {
1208 strcatf(&res, "Content-Language: en\r\n");
1209 }
1210 strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
1211 strcatf(&res, "Date: %s\r\n", date);
1212 strcatf(&res, "EXT:\r\n");
1213 strcatf(&res, "\r\n");
1214 h->res_buflen = res.off;
1215 if(h->res_buf_alloclen < (h->res_buflen + bodylen))
1216 {
1217 h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen));
1218 h->res_buf_alloclen = h->res_buflen + bodylen;
1219 }
1220 }
1221
1222 void
BuildResp2_upnphttp(struct upnphttp * h,int respcode,const char * respmsg,const char * body,int bodylen)1223 BuildResp2_upnphttp(struct upnphttp * h, int respcode,
1224 const char * respmsg,
1225 const char * body, int bodylen)
1226 {
1227 BuildHeader_upnphttp(h, respcode, respmsg, bodylen);
1228 if( h->req_command == EHead )
1229 return;
1230 if(body)
1231 memcpy(h->res_buf + h->res_buflen, body, bodylen);
1232 h->res_buflen += bodylen;
1233 }
1234
1235 /* responding 200 OK ! */
1236 void
BuildResp_upnphttp(struct upnphttp * h,const char * body,int bodylen)1237 BuildResp_upnphttp(struct upnphttp *h, const char *body, int bodylen)
1238 {
1239 BuildResp2_upnphttp(h, 200, "OK", body, bodylen);
1240 }
1241
1242 void
SendResp_upnphttp(struct upnphttp * h)1243 SendResp_upnphttp(struct upnphttp * h)
1244 {
1245 int n;
1246 DPRINTF(E_DEBUG, L_HTTP, "HTTP RESPONSE: %.*s\n", h->res_buflen, h->res_buf);
1247 n = send(h->ev.fd, h->res_buf, h->res_buflen, 0);
1248 if(n<0)
1249 {
1250 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno));
1251 }
1252 else if(n < h->res_buflen)
1253 {
1254 /* TODO : handle correctly this case */
1255 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
1256 n, h->res_buflen);
1257 }
1258 }
1259
1260 static int
send_data(struct upnphttp * h,char * header,size_t size,int flags)1261 send_data(struct upnphttp * h, char * header, size_t size, int flags)
1262 {
1263 int n;
1264
1265 n = send(h->ev.fd, header, size, flags);
1266 if(n<0)
1267 {
1268 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno));
1269 }
1270 else if(n < h->res_buflen)
1271 {
1272 /* TODO : handle correctly this case */
1273 DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
1274 n, h->res_buflen);
1275 }
1276 else
1277 {
1278 return 0;
1279 }
1280 return 1;
1281 }
1282
1283 static void
send_file(struct upnphttp * h,int sendfd,off_t offset,off_t end_offset)1284 send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset)
1285 {
1286 off_t send_size;
1287 off_t ret;
1288 char *buf = NULL;
1289 #if HAVE_SENDFILE
1290 int try_sendfile = 1;
1291 #endif
1292
1293 while( offset <= end_offset )
1294 {
1295 #if HAVE_SENDFILE
1296 if( try_sendfile )
1297 {
1298 send_size = ( ((end_offset - offset) < MAX_BUFFER_SIZE) ? (end_offset - offset + 1) : MAX_BUFFER_SIZE);
1299 ret = sys_sendfile(h->ev.fd, sendfd, &offset, send_size);
1300 if( ret == -1 )
1301 {
1302 DPRINTF(E_DEBUG, L_HTTP, "sendfile error :: error no. %d [%s]\n", errno, strerror(errno));
1303 /* If sendfile isn't supported on the filesystem, don't bother trying to use it again. */
1304 if( errno == EOVERFLOW || errno == EINVAL )
1305 try_sendfile = 0;
1306 else if( errno != EAGAIN )
1307 break;
1308 }
1309 else
1310 {
1311 //DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset);
1312 continue;
1313 }
1314 }
1315 #endif
1316 /* Fall back to regular I/O */
1317 if( !buf )
1318 buf = malloc(MIN_BUFFER_SIZE);
1319 send_size = (((end_offset - offset) < MIN_BUFFER_SIZE) ? (end_offset - offset + 1) : MIN_BUFFER_SIZE);
1320 lseek(sendfd, offset, SEEK_SET);
1321 ret = read(sendfd, buf, send_size);
1322 if( ret == -1 ) {
1323 DPRINTF(E_DEBUG, L_HTTP, "read error :: error no. %d [%s]\n", errno, strerror(errno));
1324 if( errno == EAGAIN )
1325 continue;
1326 else
1327 break;
1328 }
1329 ret = write(h->ev.fd, buf, ret);
1330 if( ret == -1 ) {
1331 DPRINTF(E_DEBUG, L_HTTP, "write error :: error no. %d [%s]\n", errno, strerror(errno));
1332 if( errno == EAGAIN )
1333 continue;
1334 else
1335 break;
1336 }
1337 offset += ret;
1338 }
1339 free(buf);
1340 }
1341
1342 static void
start_dlna_header(struct string_s * str,int respcode,const char * tmode,const char * mime)1343 start_dlna_header(struct string_s *str, int respcode, const char *tmode, const char *mime)
1344 {
1345 char date[30];
1346 time_t now;
1347
1348 now = time(NULL);
1349 strftime(date, sizeof(date),"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&now));
1350 strcatf(str, "HTTP/1.1 %d OK\r\n"
1351 "Connection: close\r\n"
1352 "Date: %s\r\n"
1353 "Server: " MINIDLNA_SERVER_STRING "\r\n"
1354 "EXT:\r\n"
1355 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
1356 "transferMode.dlna.org: %s\r\n"
1357 "Content-Type: %s\r\n",
1358 respcode, date, tmode, mime);
1359 }
1360
1361 static int
_open_file(const char * orig_path)1362 _open_file(const char *orig_path)
1363 {
1364 struct media_dir_s *media_path;
1365 char buf[PATH_MAX];
1366 const char *path;
1367 int fd;
1368
1369 if (!GETFLAG(WIDE_LINKS_MASK))
1370 {
1371 path = realpath(orig_path, buf);
1372 if (!path)
1373 {
1374 DPRINTF(E_ERROR, L_HTTP, "Error resolving path %s: %s\n",
1375 orig_path, strerror(errno));
1376 return -1;
1377 }
1378
1379 for (media_path = media_dirs; media_path; media_path = media_path->next)
1380 {
1381 if (strncmp(path, media_path->path, strlen(media_path->path)) == 0)
1382 break;
1383 }
1384 if (!media_path && strncmp(path, db_path, strlen(db_path)))
1385 {
1386 DPRINTF(E_ERROR, L_HTTP, "Rejecting wide link %s -> %s\n",
1387 orig_path, path);
1388 return -403;
1389 }
1390 }
1391 else
1392 path = orig_path;
1393
1394 fd = open(path, O_RDONLY);
1395 if (fd < 0)
1396 DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path);
1397
1398 return fd;
1399 }
1400
1401 static void
SendResp_icon(struct upnphttp * h,char * icon)1402 SendResp_icon(struct upnphttp * h, char * icon)
1403 {
1404 char header[512];
1405 char mime[12] = "image/";
1406 char *data;
1407 int size;
1408 struct string_s str;
1409
1410 if( strcmp(icon, "sm.png") == 0 )
1411 {
1412 DPRINTF(E_DEBUG, L_HTTP, "Sending small PNG icon\n");
1413 data = (char *)png_sm;
1414 size = sizeof(png_sm)-1;
1415 strcpy(mime+6, "png");
1416 }
1417 else if( strcmp(icon, "lrg.png") == 0 )
1418 {
1419 DPRINTF(E_DEBUG, L_HTTP, "Sending large PNG icon\n");
1420 data = (char *)png_lrg;
1421 size = sizeof(png_lrg)-1;
1422 strcpy(mime+6, "png");
1423 }
1424 else if( strcmp(icon, "sm.jpg") == 0 )
1425 {
1426 DPRINTF(E_DEBUG, L_HTTP, "Sending small JPEG icon\n");
1427 data = (char *)jpeg_sm;
1428 size = sizeof(jpeg_sm)-1;
1429 strcpy(mime+6, "jpeg");
1430 }
1431 else if( strcmp(icon, "lrg.jpg") == 0 )
1432 {
1433 DPRINTF(E_DEBUG, L_HTTP, "Sending large JPEG icon\n");
1434 data = (char *)jpeg_lrg;
1435 size = sizeof(jpeg_lrg)-1;
1436 strcpy(mime+6, "jpeg");
1437 }
1438 else
1439 {
1440 DPRINTF(E_WARN, L_HTTP, "Invalid icon request: %s\n", icon);
1441 Send404(h);
1442 return;
1443 }
1444
1445 INIT_STR(str, header);
1446
1447 start_dlna_header(&str, 200, "Interactive", mime);
1448 strcatf(&str, "Content-Length: %d\r\n\r\n", size);
1449
1450 if( send_data(h, str.data, str.off, MSG_MORE) == 0 )
1451 {
1452 if( h->req_command != EHead )
1453 send_data(h, data, size, 0);
1454 }
1455 CloseSocket_upnphttp(h);
1456 }
1457
1458 static void
SendResp_albumArt(struct upnphttp * h,char * object)1459 SendResp_albumArt(struct upnphttp * h, char * object)
1460 {
1461 char header[512];
1462 char *path;
1463 off_t size;
1464 long long id;
1465 int fd;
1466 struct string_s str;
1467
1468 if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) )
1469 {
1470 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1471 Send406(h);
1472 return;
1473 }
1474
1475 id = strtoll(object, NULL, 10);
1476
1477 path = sql_get_text_field(db, "SELECT PATH from ALBUM_ART where ID = '%lld'", id);
1478 if( !path )
1479 {
1480 DPRINTF(E_WARN, L_HTTP, "ALBUM_ART ID %s not found, responding ERROR 404\n", object);
1481 Send404(h);
1482 return;
1483 }
1484 DPRINTF(E_INFO, L_HTTP, "Serving album art ID: %lld [%s]\n", id, path);
1485
1486 fd = _open_file(path);
1487 if( fd < 0 ) {
1488 sqlite3_free(path);
1489 if (fd == -403)
1490 Send403(h);
1491 else
1492 Send404(h);
1493 return;
1494 }
1495 sqlite3_free(path);
1496 size = lseek(fd, 0, SEEK_END);
1497 lseek(fd, 0, SEEK_SET);
1498
1499 INIT_STR(str, header);
1500
1501 start_dlna_header(&str, 200, "Interactive", "image/jpeg");
1502 strcatf(&str, "Content-Length: %jd\r\n"
1503 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n\r\n",
1504 (intmax_t)size);
1505
1506 if( send_data(h, str.data, str.off, MSG_MORE) == 0 )
1507 {
1508 if( h->req_command != EHead )
1509 send_file(h, fd, 0, size-1);
1510 }
1511 close(fd);
1512 CloseSocket_upnphttp(h);
1513 }
1514
1515 static void
SendResp_caption(struct upnphttp * h,char * object)1516 SendResp_caption(struct upnphttp * h, char * object)
1517 {
1518 char header[512];
1519 char *path;
1520 off_t size;
1521 long long id;
1522 int fd;
1523 struct string_s str;
1524
1525 id = strtoll(object, NULL, 10);
1526
1527 path = sql_get_text_field(db, "SELECT PATH from CAPTIONS where ID = %lld", id);
1528 if( !path )
1529 {
1530 DPRINTF(E_WARN, L_HTTP, "CAPTION ID %s not found, responding ERROR 404\n", object);
1531 Send404(h);
1532 return;
1533 }
1534 DPRINTF(E_INFO, L_HTTP, "Serving caption ID: %lld [%s]\n", id, path);
1535
1536 fd = _open_file(path);
1537 if( fd < 0 ) {
1538 sqlite3_free(path);
1539 if (fd == -403)
1540 Send403(h);
1541 else
1542 Send404(h);
1543 return;
1544 }
1545 sqlite3_free(path);
1546 size = lseek(fd, 0, SEEK_END);
1547 lseek(fd, 0, SEEK_SET);
1548
1549 INIT_STR(str, header);
1550
1551 start_dlna_header(&str, 200, "Interactive", "smi/caption");
1552 strcatf(&str, "Content-Length: %jd\r\n\r\n", (intmax_t)size);
1553
1554 if( send_data(h, str.data, str.off, MSG_MORE) == 0 )
1555 {
1556 if( h->req_command != EHead )
1557 send_file(h, fd, 0, size-1);
1558 }
1559 close(fd);
1560 CloseSocket_upnphttp(h);
1561 }
1562
1563 static void
SendResp_thumbnail(struct upnphttp * h,char * object)1564 SendResp_thumbnail(struct upnphttp * h, char * object)
1565 {
1566 char header[512];
1567 char *path;
1568 long long id;
1569 ExifData *ed;
1570 ExifLoader *l;
1571 struct string_s str;
1572
1573 if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) )
1574 {
1575 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1576 Send406(h);
1577 return;
1578 }
1579
1580 id = strtoll(object, NULL, 10);
1581 path = sql_get_text_field(db, "SELECT PATH from DETAILS where ID = '%lld'", id);
1582 if( !path )
1583 {
1584 DPRINTF(E_WARN, L_HTTP, "DETAIL ID %s not found, responding ERROR 404\n", object);
1585 Send404(h);
1586 return;
1587 }
1588 DPRINTF(E_INFO, L_HTTP, "Serving thumbnail for ObjectId: %lld [%s]\n", id, path);
1589
1590 if( access(path, F_OK) != 0 )
1591 {
1592 DPRINTF(E_ERROR, L_HTTP, "Error accessing %s\n", path);
1593 Send404(h);
1594 sqlite3_free(path);
1595 return;
1596 }
1597
1598 l = exif_loader_new();
1599 exif_loader_write_file(l, path);
1600 ed = exif_loader_get_data(l);
1601 exif_loader_unref(l);
1602 sqlite3_free(path);
1603
1604 if( !ed || !ed->size )
1605 {
1606 Send404(h);
1607 if( ed )
1608 exif_data_unref(ed);
1609 return;
1610 }
1611
1612 INIT_STR(str, header);
1613
1614 start_dlna_header(&str, 200, "Interactive", "image/jpeg");
1615 strcatf(&str, "Content-Length: %jd\r\n"
1616 "contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN;DLNA.ORG_CI=1\r\n\r\n",
1617 (intmax_t)ed->size);
1618
1619 if( send_data(h, str.data, str.off, MSG_MORE) == 0 )
1620 {
1621 if( h->req_command != EHead )
1622 send_data(h, (char *)ed->data, ed->size, 0);
1623 }
1624 exif_data_unref(ed);
1625 CloseSocket_upnphttp(h);
1626 }
1627
1628 static void
SendResp_resizedimg(struct upnphttp * h,char * object)1629 SendResp_resizedimg(struct upnphttp * h, char * object)
1630 {
1631 char header[512];
1632 char buf[128];
1633 struct string_s str;
1634 char **result;
1635 char dlna_pn[22];
1636 uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B|DLNA_FLAG_TM_I;
1637 int width=640, height=480, dstw, dsth, size;
1638 int srcw, srch;
1639 unsigned char * data = NULL;
1640 char *path, *file_path = NULL;
1641 char *resolution = NULL;
1642 char *key, *val;
1643 char *saveptr, *item = NULL;
1644 int rotate = 0;
1645 int pixw = 0, pixh = 0;
1646 long long id;
1647 int rows=0, chunked, ret;
1648 image_s *imsrc = NULL, *imdst = NULL;
1649 int scale = 1;
1650 const char *tmode;
1651
1652 id = strtoll(object, &saveptr, 10);
1653 snprintf(buf, sizeof(buf), "SELECT PATH, RESOLUTION, ROTATION from DETAILS where ID = '%lld'", (long long)id);
1654 ret = sql_get_table(db, buf, &result, &rows, NULL);
1655 if( ret != SQLITE_OK )
1656 {
1657 Send500(h);
1658 return;
1659 }
1660 if( rows )
1661 {
1662 file_path = result[3];
1663 resolution = result[4];
1664 if (result[5])
1665 rotate = atoi(result[5]);
1666 }
1667 if( !file_path || !resolution || (access(file_path, F_OK) != 0) )
1668 {
1669 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
1670 sqlite3_free_table(result);
1671 Send404(h);
1672 return;
1673 }
1674
1675 if( saveptr )
1676 saveptr = strchr(saveptr, '?');
1677 path = saveptr ? saveptr + 1 : object;
1678 for( item = strtok_r(path, "&,", &saveptr); item != NULL; item = strtok_r(NULL, "&,", &saveptr) )
1679 {
1680 decodeString(item, 1);
1681 val = item;
1682 key = strsep(&val, "=");
1683 if( !val )
1684 continue;
1685 DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val);
1686 if( strcasecmp(key, "width") == 0 )
1687 {
1688 width = atoi(val);
1689 }
1690 else if( strcasecmp(key, "height") == 0 )
1691 {
1692 height = atoi(val);
1693 }
1694 else if( strcasecmp(key, "rotation") == 0 )
1695 {
1696 rotate = (rotate + atoi(val)) % 360;
1697 sql_exec(db, "UPDATE DETAILS set ROTATION = %d where ID = %lld", rotate, id);
1698 }
1699 else if( strcasecmp(key, "pixelshape") == 0 )
1700 {
1701 ret = sscanf(val, "%d:%d", &pixw, &pixh);
1702 if( ret != 2 )
1703 pixw = pixh = 0;
1704 }
1705 }
1706
1707 #if USE_FORK
1708 pid_t newpid = 0;
1709 newpid = process_fork(h->req_client);
1710 if( newpid > 0 )
1711 {
1712 CloseSocket_upnphttp(h);
1713 goto resized_error;
1714 }
1715 #endif
1716 if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) )
1717 {
1718 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1719 Send406(h);
1720 goto resized_error;
1721 }
1722
1723 DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %lld [%s]\n", id, file_path);
1724 if( rotate )
1725 DPRINTF(E_DEBUG, L_HTTP, "Rotating image %d degrees\n", rotate);
1726 switch( rotate )
1727 {
1728 case 90:
1729 ret = sscanf(resolution, "%dx%d", &srch, &srcw);
1730 rotate = ROTATE_90;
1731 break;
1732 case 270:
1733 ret = sscanf(resolution, "%dx%d", &srch, &srcw);
1734 rotate = ROTATE_270;
1735 break;
1736 case 180:
1737 ret = sscanf(resolution, "%dx%d", &srcw, &srch);
1738 rotate = ROTATE_180;
1739 break;
1740 default:
1741 ret = sscanf(resolution, "%dx%d", &srcw, &srch);
1742 rotate = ROTATE_NONE;
1743 break;
1744 }
1745 if( ret != 2 )
1746 {
1747 Send500(h);
1748 return;
1749 }
1750 /* Figure out the best destination resolution we can use */
1751 dstw = width;
1752 dsth = ((((width<<10)/srcw)*srch)>>10);
1753 if( dsth > height )
1754 {
1755 dsth = height;
1756 dstw = (((height<<10)/srch) * srcw>>10);
1757 }
1758 /* Account for pixel shape */
1759 if( pixw && pixh )
1760 {
1761 if( pixh > pixw )
1762 dsth = dsth * pixw / pixh;
1763 else if( pixw > pixh )
1764 dstw = dstw * pixh / pixw;
1765 }
1766
1767 if( dstw <= 160 && dsth <= 160 )
1768 strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_TN;");
1769 else if( dstw <= 640 && dsth <= 480 )
1770 strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_SM;");
1771 else if( dstw <= 1024 && dsth <= 768 )
1772 strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_MED;");
1773 else
1774 strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_LRG;");
1775
1776 if( srcw>>4 >= dstw && srch>>4 >= dsth)
1777 scale = 8;
1778 else if( srcw>>3 >= dstw && srch>>3 >= dsth )
1779 scale = 4;
1780 else if( srcw>>2 >= dstw && srch>>2 >= dsth )
1781 scale = 2;
1782
1783 INIT_STR(str, header);
1784
1785 #if USE_FORK
1786 if( (h->reqflags & FLAG_XFERBACKGROUND) && (setpriority(PRIO_PROCESS, 0, 19) == 0) )
1787 tmode = "Background";
1788 else
1789 #endif
1790 tmode = "Interactive";
1791 start_dlna_header(&str, 200, tmode, "image/jpeg");
1792 strcatf(&str, "contentFeatures.dlna.org: %sDLNA.ORG_CI=1;DLNA.ORG_FLAGS=%08X%024X\r\n",
1793 dlna_pn, dlna_flags, 0);
1794
1795 if( strcmp(h->HttpVer, "HTTP/1.0") == 0 )
1796 {
1797 chunked = 0;
1798 imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale, rotate);
1799 }
1800 else
1801 {
1802 chunked = 1;
1803 strcatf(&str, "Transfer-Encoding: chunked\r\n\r\n");
1804 }
1805
1806 if( !chunked )
1807 {
1808 if( !imsrc )
1809 {
1810 DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path);
1811 Send500(h);
1812 goto resized_error;
1813 }
1814
1815 imdst = image_resize(imsrc, dstw, dsth);
1816 data = image_save_to_jpeg_buf(imdst, &size);
1817
1818 strcatf(&str, "Content-Length: %d\r\n\r\n", size);
1819 }
1820
1821 if( (send_data(h, str.data, str.off, 0) == 0) && (h->req_command != EHead) )
1822 {
1823 if( chunked )
1824 {
1825 imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale, rotate);
1826 if( !imsrc )
1827 {
1828 DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path);
1829 Send500(h);
1830 goto resized_error;
1831 }
1832 imdst = image_resize(imsrc, dstw, dsth);
1833 data = image_save_to_jpeg_buf(imdst, &size);
1834
1835 ret = sprintf(buf, "%x\r\n", size);
1836 send_data(h, buf, ret, MSG_MORE);
1837 send_data(h, (char *)data, size, MSG_MORE);
1838 send_data(h, "\r\n0\r\n\r\n", 7, 0);
1839 }
1840 else
1841 {
1842 send_data(h, (char *)data, size, 0);
1843 }
1844 }
1845 DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path);
1846 if( imsrc )
1847 image_free(imsrc);
1848 if( imdst )
1849 image_free(imdst);
1850 CloseSocket_upnphttp(h);
1851 resized_error:
1852 sqlite3_free_table(result);
1853 #if USE_FORK
1854 if( newpid == 0 )
1855 _exit(0);
1856 #endif
1857 }
1858
1859 static void
SendResp_dlnafile(struct upnphttp * h,char * object)1860 SendResp_dlnafile(struct upnphttp *h, char *object)
1861 {
1862 char header[1024];
1863 struct string_s str;
1864 char buf[128];
1865 char **result;
1866 int rows, ret;
1867 off_t total, offset, size;
1868 int64_t id;
1869 int sendfh;
1870 uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B;
1871 uint32_t cflags = h->req_client ? h->req_client->type->flags : 0;
1872 const char *tmode;
1873 enum client_types ctype = h->req_client ? h->req_client->type->type : 0;
1874 static struct { int64_t id;
1875 enum client_types client;
1876 char path[PATH_MAX];
1877 char mime[32];
1878 char dlna[96];
1879 } last_file = { 0, 0 };
1880 #if USE_FORK
1881 pid_t newpid = 0;
1882 #endif
1883
1884 id = strtoll(object, NULL, 10);
1885 if( cflags & FLAG_MS_PFS )
1886 {
1887 if( strstr(object, "?albumArt=true") )
1888 {
1889 char *art;
1890 art = sql_get_text_field(db, "SELECT ALBUM_ART from DETAILS where ID = '%lld'", id);
1891 if (art)
1892 {
1893 SendResp_albumArt(h, art);
1894 sqlite3_free(art);
1895 }
1896 else
1897 Send404(h);
1898 return;
1899 }
1900 }
1901 if( id != last_file.id || ctype != last_file.client )
1902 {
1903 snprintf(buf, sizeof(buf), "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", (long long)id);
1904 ret = sql_get_table(db, buf, &result, &rows, NULL);
1905 if( (ret != SQLITE_OK) )
1906 {
1907 DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", (long long)id);
1908 Send500(h);
1909 return;
1910 }
1911 if( !rows || !result[3] || !result[4] )
1912 {
1913 DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
1914 sqlite3_free_table(result);
1915 Send404(h);
1916 return;
1917 }
1918 /* Cache the result */
1919 last_file.id = id;
1920 last_file.client = ctype;
1921 strncpy(last_file.path, result[3], sizeof(last_file.path)-1);
1922 if( result[4] )
1923 {
1924 strncpy(last_file.mime, result[4], sizeof(last_file.mime)-1);
1925 /* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
1926 if( cflags & FLAG_SAMSUNG )
1927 {
1928 if( strcmp(last_file.mime+6, "x-matroska") == 0 )
1929 strcpy(last_file.mime+8, "mkv");
1930 /* Samsung TV's such as the A750 can natively support many
1931 Xvid/DivX AVI's however, the DLNA server needs the
1932 mime type to say video/mpeg */
1933 else if( ctype == ESamsungSeriesA && strcmp(last_file.mime+6, "x-msvideo") == 0 )
1934 strcpy(last_file.mime+6, "mpeg");
1935 }
1936 /* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */
1937 else if( ctype == ESonyBDP )
1938 {
1939 if( strcmp(last_file.mime+6, "x-matroska") == 0 ||
1940 strcmp(last_file.mime+6, "mpeg") == 0 )
1941 strcpy(last_file.mime+6, "divx");
1942 }
1943 }
1944 if( result[5] )
1945 snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s;", result[5]);
1946 else
1947 last_file.dlna[0] = '\0';
1948 sqlite3_free_table(result);
1949 }
1950 #if USE_FORK
1951 newpid = process_fork(h->req_client);
1952 if( newpid > 0 )
1953 {
1954 CloseSocket_upnphttp(h);
1955 return;
1956 }
1957 #endif
1958
1959 DPRINTF(E_INFO, L_HTTP, "Serving DetailID: %lld [%s]\n", (long long)id, last_file.path);
1960
1961 if( h->reqflags & FLAG_XFERSTREAMING )
1962 {
1963 if( strncmp(last_file.mime, "image", 5) == 0 )
1964 {
1965 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
1966 Send406(h);
1967 goto error;
1968 }
1969 }
1970 else if( h->reqflags & FLAG_XFERINTERACTIVE )
1971 {
1972 if( h->reqflags & FLAG_REALTIMEINFO )
1973 {
1974 DPRINTF(E_WARN, L_HTTP, "Bad realTimeInfo flag with Interactive request!\n");
1975 Send400(h);
1976 goto error;
1977 }
1978 if( strncmp(last_file.mime, "image", 5) != 0 )
1979 {
1980 DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Interactive without an image!\n");
1981 /* Samsung TVs (well, at least the A950) do this for some reason,
1982 * and I don't see them fixing this bug any time soon. */
1983 if( !(cflags & FLAG_SAMSUNG) || GETFLAG(DLNA_STRICT_MASK) )
1984 {
1985 Send406(h);
1986 goto error;
1987 }
1988 }
1989 }
1990
1991 offset = h->req_RangeStart;
1992 sendfh = _open_file(last_file.path);
1993 if( sendfh < 0 ) {
1994 if (sendfh == -403)
1995 Send403(h);
1996 else
1997 Send404(h);
1998 goto error;
1999 }
2000 size = lseek(sendfh, 0, SEEK_END);
2001 lseek(sendfh, 0, SEEK_SET);
2002
2003 INIT_STR(str, header);
2004
2005 #if USE_FORK
2006 if( (h->reqflags & FLAG_XFERBACKGROUND) && (setpriority(PRIO_PROCESS, 0, 19) == 0) )
2007 tmode = "Background";
2008 else
2009 #endif
2010 if( strncmp(last_file.mime, "image", 5) == 0 )
2011 tmode = "Interactive";
2012 else
2013 tmode = "Streaming";
2014
2015 start_dlna_header(&str, (h->reqflags & FLAG_RANGE ? 206 : 200), tmode, last_file.mime);
2016
2017 if( h->reqflags & FLAG_RANGE )
2018 {
2019 if( !h->req_RangeEnd || h->req_RangeEnd == size )
2020 {
2021 h->req_RangeEnd = size - 1;
2022 }
2023 if( (h->req_RangeStart > h->req_RangeEnd) || (h->req_RangeStart < 0) )
2024 {
2025 DPRINTF(E_WARN, L_HTTP, "Specified range was invalid!\n");
2026 Send400(h);
2027 close(sendfh);
2028 goto error;
2029 }
2030 if( h->req_RangeEnd >= size )
2031 {
2032 DPRINTF(E_WARN, L_HTTP, "Specified range was outside file boundaries!\n");
2033 Send416(h);
2034 close(sendfh);
2035 goto error;
2036 }
2037
2038 total = h->req_RangeEnd - h->req_RangeStart + 1;
2039 strcatf(&str, "Content-Length: %jd\r\n"
2040 "Content-Range: bytes %jd-%jd/%jd\r\n",
2041 (intmax_t)total, (intmax_t)h->req_RangeStart,
2042 (intmax_t)h->req_RangeEnd, (intmax_t)size);
2043 }
2044 else
2045 {
2046 h->req_RangeEnd = size - 1;
2047 total = size;
2048 strcatf(&str, "Content-Length: %jd\r\n", (intmax_t)total);
2049 }
2050
2051 switch( *last_file.mime )
2052 {
2053 case 'i':
2054 dlna_flags |= DLNA_FLAG_TM_I;
2055 break;
2056 case 'a':
2057 case 'v':
2058 default:
2059 dlna_flags |= DLNA_FLAG_TM_S;
2060 break;
2061 }
2062
2063 if( h->reqflags & FLAG_CAPTION )
2064 {
2065 if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%lld'", (long long)id) > 0 )
2066 strcatf(&str, "CaptionInfo.sec: http://%s:%d/Captions/%lld.srt\r\n",
2067 lan_addr[h->iface].str, runtime_vars.port, (long long)id);
2068 }
2069
2070 strcatf(&str, "Accept-Ranges: bytes\r\n"
2071 "contentFeatures.dlna.org: %sDLNA.ORG_OP=%02X;DLNA.ORG_CI=%X;DLNA.ORG_FLAGS=%08X%024X\r\n\r\n",
2072 last_file.dlna, 1, 0, dlna_flags, 0);
2073
2074 //DEBUG DPRINTF(E_DEBUG, L_HTTP, "RESPONSE: %s\n", str.data);
2075 if( send_data(h, str.data, str.off, MSG_MORE) == 0 )
2076 {
2077 if( h->req_command != EHead )
2078 send_file(h, sendfh, offset, h->req_RangeEnd);
2079 }
2080 close(sendfh);
2081
2082 CloseSocket_upnphttp(h);
2083 error:
2084 #if USE_FORK
2085 if( newpid == 0 )
2086 _exit(0);
2087 #endif
2088 return;
2089 }
2090