/* MiniDLNA project
*
* http://sourceforge.net/projects/minidlna/
*
* MiniDLNA media server
* Copyright (C) 2008-2009 Justin Maggard
*
* This file is part of MiniDLNA.
*
* MiniDLNA is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* MiniDLNA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MiniDLNA. If not, see .
*
* Portions of the code from the MiniUPnP project:
*
* Copyright (c) 2006-2007, Thomas Bernard
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "config.h"
#include "event.h"
#include "upnpglobalvars.h"
#include "upnphttp.h"
#include "upnpdescgen.h"
#include "minidlnapath.h"
#include "upnpsoap.h"
#include "upnpevents.h"
#include "utils.h"
#include "getifaddr.h"
#include "image_utils.h"
#include "log.h"
#include "sql.h"
#include
#include "tivo_utils.h"
#include "tivo_commands.h"
#include "clients.h"
#include "process.h"
#include "sendfile.h"
#define MAX_BUFFER_SIZE 2147483647
#define MIN_BUFFER_SIZE 65536
#define INIT_STR(s, d) { s.data = d; s.size = sizeof(d); s.off = 0; }
#include "icons.c"
enum event_type {
E_INVALID,
E_SUBSCRIBE,
E_RENEW
};
static void SendResp_icon(struct upnphttp *, char * url);
static void SendResp_albumArt(struct upnphttp *, char * url);
static void SendResp_caption(struct upnphttp *, char * url);
static void SendResp_resizedimg(struct upnphttp *, char * url);
static void SendResp_thumbnail(struct upnphttp *, char * url);
static void SendResp_dlnafile(struct upnphttp *, char * url);
static void Process_upnphttp(struct event *ev);
struct upnphttp *
New_upnphttp(int s)
{
struct upnphttp * ret;
if(s<0)
return NULL;
ret = (struct upnphttp *)malloc(sizeof(struct upnphttp));
if(ret == NULL)
return NULL;
memset(ret, 0, sizeof(struct upnphttp));
ret->ev = (struct event ){ .fd = s, .rdwr = EVENT_READ, .process = Process_upnphttp, .data = ret };
event_module.add(&ret->ev);
return ret;
}
void
CloseSocket_upnphttp(struct upnphttp * h)
{
event_module.del(&h->ev, EV_FLAG_CLOSING);
if(close(h->ev.fd) < 0)
{
DPRINTF(E_ERROR, L_HTTP, "CloseSocket_upnphttp: close(%d): %s\n", h->ev.fd, strerror(errno));
}
h->ev.fd = -1;
h->state = 100;
}
void
Delete_upnphttp(struct upnphttp * h)
{
if(h)
{
if(h->ev.fd >= 0)
CloseSocket_upnphttp(h);
free(h->req_buf);
free(h->res_buf);
free(h);
}
}
/* parse HttpHeaders of the REQUEST */
static void
ParseHttpHeaders(struct upnphttp * h)
{
int client = 0;
char * line;
char * colon;
char * p;
int n;
line = h->req_buf;
/* TODO : check if req_buf, contentoff are ok */
while(line < (h->req_buf + h->req_contentoff))
{
colon = strchr(line, ':');
if(colon)
{
if(strncasecmp(line, "Content-Length", 14)==0)
{
p = colon;
while(*p && (*p < '0' || *p > '9'))
p++;
h->req_contentlen = atoi(p);
if(h->req_contentlen < 0) {
DPRINTF(E_WARN, L_HTTP, "Invalid Content-Length %d", h->req_contentlen);
h->req_contentlen = 0;
}
}
else if(strncasecmp(line, "SOAPAction", 10)==0)
{
p = colon;
n = 0;
while(*p == ':' || *p == ' ' || *p == '\t')
p++;
while(p[n] >= ' ')
n++;
if(n >= 2 &&
((p[0] == '"' && p[n-1] == '"') ||
(p[0] == '\'' && p[n-1] == '\'')))
{
p++;
n -= 2;
}
h->req_soapAction = p;
h->req_soapActionLen = n;
}
else if(strncasecmp(line, "Callback", 8)==0)
{
p = colon;
while(*p && *p != '<' && *p != '\r' )
p++;
n = 0;
while(p[n] && p[n] != '>' && p[n] != '\r' )
n++;
h->req_Callback = p + 1;
h->req_CallbackLen = MAX(0, n - 1);
}
else if(strncasecmp(line, "SID", 3)==0)
{
//zqiu: fix bug for test 4.0.5
//Skip extra headers like "SIDHEADER: xxxxxx xxx"
for(p=line+3;preq_SID = p;
h->req_SIDLen = n;
}
}
else if(strncasecmp(line, "NT", 2)==0)
{
p = colon + 1;
while(isspace(*p))
p++;
n = 0;
while(p[n] && !isspace(p[n]))
n++;
h->req_NT = p;
h->req_NTLen = n;
}
/* Timeout: Seconds-nnnn */
/* TIMEOUT
Recommended. Requested duration until subscription expires,
either number of seconds or infinite. Recommendation
by a UPnP Forum working committee. Defined by UPnP vendor.
Consists of the keyword "Second-" followed (without an
intervening space) by either an integer or the keyword "infinite". */
else if(strncasecmp(line, "Timeout", 7)==0)
{
p = colon + 1;
while(isspace(*p))
p++;
if(strncasecmp(p, "Second-", 7)==0) {
h->req_Timeout = atoi(p+7);
}
}
// Range: bytes=xxx-yyy
else if(strncasecmp(line, "Range", 5)==0)
{
p = colon + 1;
while(isspace(*p))
p++;
if(strncasecmp(p, "bytes=", 6)==0) {
h->reqflags |= FLAG_RANGE;
h->req_RangeStart = strtoll(p+6, &colon, 10);
h->req_RangeEnd = colon ? atoll(colon+1) : 0;
DPRINTF(E_DEBUG, L_HTTP, "Range Start-End: %lld - %lld\n",
(long long)h->req_RangeStart,
h->req_RangeEnd ? (long long)h->req_RangeEnd : -1);
}
}
else if(strncasecmp(line, "Host", 4)==0)
{
int i;
h->reqflags |= FLAG_HOST;
p = colon + 1;
while(isspace(*p))
p++;
n = 0;
while(p[n] >= ' ')
n++;
h->req_Host = p;
h->req_HostLen = n;
for(n = 0; n < n_lan_addr; n++)
{
for(i = 0; lan_addr[n].str[i]; i++)
{
if(lan_addr[n].str[i] != p[i])
break;
}
if(i && !lan_addr[n].str[i])
{
h->iface = n;
break;
}
}
}
else if(strncasecmp(line, "User-Agent", 10)==0)
{
int i;
/* Skip client detection if we already detected it. */
if( client )
goto next_header;
p = colon + 1;
while(isspace(*p))
p++;
for (i = 0; client_types[i].name; i++)
{
if (client_types[i].match_type != EUserAgent)
continue;
if (strstrc(p, client_types[i].match, '\r') != NULL)
{
client = i;
break;
}
}
}
else if(strncasecmp(line, "X-AV-Client-Info", 16)==0)
{
int i;
/* Skip client detection if we already detected it. */
if( client && client_types[client].type < EStandardDLNA150 )
goto next_header;
p = colon + 1;
while(isspace(*p))
p++;
for (i = 0; client_types[i].name; i++)
{
if (client_types[i].match_type != EXAVClientInfo)
continue;
if (strstrc(p, client_types[i].match, '\r') != NULL)
{
client = i;
break;
}
}
}
else if(strncasecmp(line, "Transfer-Encoding", 17)==0)
{
p = colon + 1;
while(isspace(*p))
p++;
if(strncasecmp(p, "chunked", 7)==0)
{
h->reqflags |= FLAG_CHUNKED;
}
}
else if(strncasecmp(line, "Accept-Language", 15)==0)
{
h->reqflags |= FLAG_LANGUAGE;
}
else if(strncasecmp(line, "getcontentFeatures.dlna.org", 27)==0)
{
p = colon + 1;
while(isspace(*p))
p++;
if( (*p != '1') || !isspace(p[1]) )
h->reqflags |= FLAG_INVALID_REQ;
}
else if(strncasecmp(line, "TimeSeekRange.dlna.org", 22)==0)
{
h->reqflags |= FLAG_TIMESEEK;
}
else if(strncasecmp(line, "PlaySpeed.dlna.org", 18)==0)
{
h->reqflags |= FLAG_PLAYSPEED;
}
else if(strncasecmp(line, "realTimeInfo.dlna.org", 21)==0)
{
h->reqflags |= FLAG_REALTIMEINFO;
}
else if(strncasecmp(line, "getAvailableSeekRange.dlna.org", 21)==0)
{
p = colon + 1;
while(isspace(*p))
p++;
if( (*p != '1') || !isspace(p[1]) )
h->reqflags |= FLAG_INVALID_REQ;
}
else if(strncasecmp(line, "transferMode.dlna.org", 21)==0)
{
p = colon + 1;
while(isspace(*p))
p++;
if(strncasecmp(p, "Streaming", 9)==0)
{
h->reqflags |= FLAG_XFERSTREAMING;
}
if(strncasecmp(p, "Interactive", 11)==0)
{
h->reqflags |= FLAG_XFERINTERACTIVE;
}
if(strncasecmp(p, "Background", 10)==0)
{
h->reqflags |= FLAG_XFERBACKGROUND;
}
}
else if(strncasecmp(line, "getCaptionInfo.sec", 18)==0)
{
h->reqflags |= FLAG_CAPTION;
}
else if(strncasecmp(line, "FriendlyName", 12)==0)
{
int i;
p = colon + 1;
while(isspace(*p))
p++;
for (i = 0; client_types[i].name; i++)
{
if (client_types[i].match_type != EFriendlyName)
continue;
if (strstrc(p, client_types[i].match, '\r') != NULL)
{
client = i;
break;
}
}
}
else if(strncasecmp(line, "uctt.upnp.org:", 14)==0)
{
/* Conformance testing */
SETFLAG(DLNA_STRICT_MASK);
}
}
next_header:
line = strstr(line, "\r\n");
if (!line)
return;
line += 2;
}
if (h->reqflags & FLAG_CHUNKED)
{
char *endptr;
h->req_chunklen = -1;
if (h->req_buflen <= h->req_contentoff)
return;
while( (line < (h->req_buf + h->req_buflen)) &&
((h->req_chunklen = strtol(line, &endptr, 16)) > 0) &&
(endptr != line) )
{
endptr = strstr(endptr, "\r\n");
if (!endptr)
{
return;
}
line = endptr+h->req_chunklen+2;
}
if( endptr == line )
{
h->req_chunklen = -1;
return;
}
}
/* If the client type wasn't found, search the cache.
* This is done because a lot of clients like to send a
* different User-Agent with different types of requests. */
h->req_client = SearchClientCache(h->clientaddr, 0);
/* Add this client to the cache if it's not there already. */
if (!h->req_client)
{
h->req_client = AddClientCache(h->clientaddr, client);
}
else if (client)
{
enum client_types type = client_types[client].type;
enum client_types ctype = h->req_client->type->type;
/* If we know the client and our new detection is generic, use our cached info */
/* If we detected a Samsung Series B earlier, don't overwrite it with Series A info */
if ((ctype && ctype < EStandardDLNA150 && type >= EStandardDLNA150) ||
(ctype == ESamsungSeriesB && type == ESamsungSeriesA))
return;
h->req_client->type = &client_types[client];
h->req_client->age = time(NULL);
}
}
/* very minimalistic 400 error message */
static void
Send400(struct upnphttp * h)
{
static const char body400[] =
"400 Bad Request"
"
Bad Request
The request is invalid"
" for this HTTP version.\r\n";
h->respflags = FLAG_HTML;
BuildResp2_upnphttp(h, 400, "Bad Request",
body400, sizeof(body400) - 1);
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
/* very minimalistic 403 error message */
static void
Send403(struct upnphttp * h)
{
static const char body403[] =
"403 Forbidden"
"
Forbidden
You don't have permission to access this resource."
"\r\n";
h->respflags = FLAG_HTML;
BuildResp2_upnphttp(h, 403, "Forbidden",
body403, sizeof(body403) - 1);
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
/* very minimalistic 404 error message */
static void
Send404(struct upnphttp * h)
{
static const char body404[] =
"404 Not Found"
"
Not Found
The requested URL was not found"
" on this server.\r\n";
h->respflags = FLAG_HTML;
BuildResp2_upnphttp(h, 404, "Not Found",
body404, sizeof(body404) - 1);
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
/* very minimalistic 406 error message */
static void
Send406(struct upnphttp * h)
{
static const char body406[] =
"406 Not Acceptable"
"
Not Acceptable
An unsupported operation"
" was requested.\r\n";
h->respflags = FLAG_HTML;
BuildResp2_upnphttp(h, 406, "Not Acceptable",
body406, sizeof(body406) - 1);
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
/* very minimalistic 416 error message */
static void
Send416(struct upnphttp * h)
{
static const char body416[] =
"416 Requested Range Not Satisfiable"
"
Requested Range Not Satisfiable
The requested range"
" was outside the file's size.\r\n";
h->respflags = FLAG_HTML;
BuildResp2_upnphttp(h, 416, "Requested Range Not Satisfiable",
body416, sizeof(body416) - 1);
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
/* very minimalistic 500 error message */
void
Send500(struct upnphttp * h)
{
static const char body500[] =
"500 Internal Server Error"
"