"Fossies" - the Fresh Open Source Software Archive

Member "minidlna-1.3.0/upnphttp.c" (24 Nov 2020, 53176 Bytes) of package /linux/privat/minidlna-1.3.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "upnphttp.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.2.1_vs_1.3.0.

    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 * 
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
 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
 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
 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
 1237 BuildResp_upnphttp(struct upnphttp *h, const char *body, int bodylen)
 1238 {
 1239     BuildResp2_upnphttp(h, 200, "OK", body, bodylen);
 1240 }
 1241 
 1242 void
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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
 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 }