1 Star 3 Fork 4

jiftle / minidlna

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
upnphttp.c 51.39 KB
一键复制 编辑 原始数据 按行查看 历史
jiftle 提交于 2018-11-05 15:03 . 导入minidlna 1.2.1代码
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071
/* 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 <http://www.gnu.org/licenses/>.
*
* 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 <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <limits.h>
#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 <libexif/exif-loader.h>
#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;p<colon;p++)
{
if(!isspace(*p))
{
p = NULL; //unexpected header
break;
}
}
if(p) {
p = colon + 1;
while(isspace(*p))
p++;
n = 0;
while(p[n] && !isspace(p[n]))
n++;
h->req_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++;
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)) &&
(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[] =
"<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD>"
"<BODY><H1>Bad Request</H1>The request is invalid"
" for this HTTP version.</BODY></HTML>\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[] =
"<HTML><HEAD><TITLE>403 Forbidden</TITLE></HEAD>"
"<BODY><H1>Forbidden</H1>You don't have permission to access this resource."
"</BODY></HTML>\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[] =
"<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD>"
"<BODY><H1>Not Found</H1>The requested URL was not found"
" on this server.</BODY></HTML>\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[] =
"<HTML><HEAD><TITLE>406 Not Acceptable</TITLE></HEAD>"
"<BODY><H1>Not Acceptable</H1>An unsupported operation"
" was requested.</BODY></HTML>\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[] =
"<HTML><HEAD><TITLE>416 Requested Range Not Satisfiable</TITLE></HEAD>"
"<BODY><H1>Requested Range Not Satisfiable</H1>The requested range"
" was outside the file's size.</BODY></HTML>\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[] =
"<HTML><HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>"
"<BODY><H1>Internal Server Error</H1>Server encountered "
"and Internal Error.</BODY></HTML>\r\n";
h->respflags = FLAG_HTML;
BuildResp2_upnphttp(h, 500, "Internal Server Errror",
body500, sizeof(body500) - 1);
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
/* very minimalistic 501 error message */
void
Send501(struct upnphttp * h)
{
static const char body501[] =
"<HTML><HEAD><TITLE>501 Not Implemented</TITLE></HEAD>"
"<BODY><H1>Not Implemented</H1>The HTTP Method "
"is not implemented by this server.</BODY></HTML>\r\n";
h->respflags = FLAG_HTML;
BuildResp2_upnphttp(h, 501, "Not Implemented",
body501, sizeof(body501) - 1);
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
/* Sends the description generated by the parameter */
static void
sendXMLdesc(struct upnphttp * h, char * (f)(int *))
{
char * desc;
int len;
desc = f(&len);
if(!desc)
{
DPRINTF(E_ERROR, L_HTTP, "Failed to generate XML description\n");
Send500(h);
return;
}
BuildResp_upnphttp(h, desc, len);
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
free(desc);
}
#ifdef READYNAS
static void
SendResp_readynas_admin(struct upnphttp * h)
{
char body[128];
int l;
h->respflags = FLAG_HTML;
l = snprintf(body, sizeof(body), "<meta http-equiv=\"refresh\" content=\"0; url=https://%s/admin/\">",
lan_addr[h->iface].str);
BuildResp_upnphttp(h, body, l);
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
#endif
static void
SendResp_presentation(struct upnphttp * h)
{
struct string_s str;
char body[4096];
int a, v, p, i;
INIT_STR(str, body);
h->respflags = FLAG_HTML;
a = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'a*'");
v = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'v*'");
p = sql_get_int_field(db, "SELECT count(*) from DETAILS where MIME glob 'i*'");
strcatf(&str,
"<HTML><HEAD><TITLE>" SERVER_NAME " " MINIDLNA_VERSION "</TITLE></HEAD>"
"<BODY><div style=\"text-align: center\">"
"<h2>" SERVER_NAME " status</h2></div>");
strcatf(&str,
"<h3>Media library</h3>"
"<table border=1 cellpadding=10>"
"<tr><td>Audio files</td><td>%d</td></tr>"
"<tr><td>Video files</td><td>%d</td></tr>"
"<tr><td>Image files</td><td>%d</td></tr>"
"</table>", a, v, p);
if (GETFLAG(SCANNING_MASK))
strcatf(&str,
"<br><i>* Media scan in progress</i><br>");
strcatf(&str,
"<h3>Connected clients</h3>"
"<table border=1 cellpadding=10>"
"<tr><td>ID</td><td>Type</td><td>IP Address</td><td>HW Address</td><td>Connections</td></tr>");
for (i = 0; i < CLIENT_CACHE_SLOTS; i++)
{
if (!clients[i].addr.s_addr)
continue;
strcatf(&str, "<tr><td>%d</td><td>%s</td><td>%s</td><td>%02X:%02X:%02X:%02X:%02X:%02X</td><td>%d</td></tr>",
i, clients[i].type->name, inet_ntoa(clients[i].addr),
clients[i].mac[0], clients[i].mac[1], clients[i].mac[2],
clients[i].mac[3], clients[i].mac[4], clients[i].mac[5], clients[i].connections);
}
strcatf(&str, "</table>");
strcatf(&str, "<br>%d connection%s currently open<br>", number_of_children, (number_of_children == 1 ? "" : "s"));
strcatf(&str, "</BODY></HTML>\r\n");
BuildResp_upnphttp(h, str.data, str.off);
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
/* ProcessHTTPPOST_upnphttp()
* executes the SOAP query if it is possible */
static void
ProcessHTTPPOST_upnphttp(struct upnphttp * h)
{
if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
{
if(h->req_soapAction)
{
/* we can process the request */
DPRINTF(E_DEBUG, L_HTTP, "SOAPAction: %.*s\n", h->req_soapActionLen, h->req_soapAction);
ExecuteSoapAction(h,
h->req_soapAction,
h->req_soapActionLen);
}
else
{
static const char err400str[] =
"<html><body>Bad request</body></html>";
DPRINTF(E_WARN, L_HTTP, "No SOAPAction in HTTP headers\n");
h->respflags = FLAG_HTML;
BuildResp2_upnphttp(h, 400, "Bad Request",
err400str, sizeof(err400str) - 1);
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
}
else
{
/* waiting for remaining data */
h->state = 1;
}
}
static int
check_event(struct upnphttp *h)
{
enum event_type type;
if (h->req_Callback)
{
if (h->req_SID || !h->req_NT)
{
BuildResp2_upnphttp(h, 400, "Bad Request",
"<html><body>Bad request</body></html>", 37);
type = E_INVALID;
}
else if (strncmp(h->req_Callback, "http://", 7) != 0 ||
strncmp(h->req_NT, "upnp:event", h->req_NTLen) != 0)
{
/* Missing or invalid CALLBACK : 412 Precondition Failed.
* If CALLBACK header is missing or does not contain a valid HTTP URL,
* the publisher must respond with HTTP error 412 Precondition Failed*/
BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
type = E_INVALID;
}
else
type = E_SUBSCRIBE;
}
else if (h->req_SID)
{
/* subscription renew */
if (h->req_NT)
{
BuildResp2_upnphttp(h, 400, "Bad Request",
"<html><body>Bad request</body></html>", 37);
type = E_INVALID;
}
else
type = E_RENEW;
}
else
{
BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
type = E_INVALID;
}
return type;
}
static void
ProcessHTTPSubscribe_upnphttp(struct upnphttp * h, const char * path)
{
const char * sid;
enum event_type type;
DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPSubscribe %s\n", path);
DPRINTF(E_DEBUG, L_HTTP, "Callback '%.*s' Timeout=%d\n",
h->req_CallbackLen, h->req_Callback, h->req_Timeout);
DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID);
type = check_event(h);
if (type == E_SUBSCRIBE)
{
/* - add to the subscriber list
* - respond HTTP/x.x 200 OK
* - Send the initial event message */
/* Server:, SID:; Timeout: Second-(xx|infinite) */
sid = upnpevents_addSubscriber(path, h->req_Callback,
h->req_CallbackLen, h->req_Timeout);
h->respflags = FLAG_TIMEOUT;
if (sid)
{
DPRINTF(E_DEBUG, L_HTTP, "generated sid=%s\n", sid);
h->respflags |= FLAG_SID;
h->req_SID = sid;
h->req_SIDLen = strlen(sid);
}
BuildResp_upnphttp(h, 0, 0);
}
else if (type == E_RENEW)
{
/* subscription renew */
if (renewSubscription(h->req_SID, h->req_SIDLen, h->req_Timeout) < 0)
{
/* Invalid SID
412 Precondition Failed. If a SID does not correspond to a known,
un-expired subscription, the publisher must respond
with HTTP error 412 Precondition Failed. */
BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
}
else
{
/* A DLNA device must enforce a 5 minute timeout */
h->respflags = FLAG_TIMEOUT;
h->req_Timeout = 300;
h->respflags |= FLAG_SID;
BuildResp_upnphttp(h, 0, 0);
}
}
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
static void
ProcessHTTPUnSubscribe_upnphttp(struct upnphttp * h, const char * path)
{
enum event_type type;
DPRINTF(E_DEBUG, L_HTTP, "ProcessHTTPUnSubscribe %s\n", path);
DPRINTF(E_DEBUG, L_HTTP, "SID '%.*s'\n", h->req_SIDLen, h->req_SID);
/* Remove from the list */
type = check_event(h);
if (type != E_INVALID)
{
if(upnpevents_removeSubscriber(h->req_SID, h->req_SIDLen) < 0)
BuildResp2_upnphttp(h, 412, "Precondition Failed", 0, 0);
else
BuildResp_upnphttp(h, 0, 0);
}
SendResp_upnphttp(h);
CloseSocket_upnphttp(h);
}
/* Parse and process Http Query
* called once all the HTTP headers have been received. */
static void
ProcessHttpQuery_upnphttp(struct upnphttp * h)
{
char HttpCommand[16];
char HttpUrl[512];
char * HttpVer;
char * p;
int i;
p = h->req_buf;
if(!p)
return;
for(i = 0; i<15 && *p && *p != ' ' && *p != '\r'; i++)
HttpCommand[i] = *(p++);
HttpCommand[i] = '\0';
while(*p==' ')
p++;
for(i = 0; i<511 && *p && *p != ' ' && *p != '\r'; i++)
HttpUrl[i] = *(p++);
HttpUrl[i] = '\0';
while(*p==' ')
p++;
HttpVer = h->HttpVer;
for(i = 0; i<15 && *p && *p != '\r'; i++)
HttpVer[i] = *(p++);
HttpVer[i] = '\0';
/* set the interface here initially, in case there is no Host header */
for(i = 0; i<n_lan_addr; i++)
{
if( (h->clientaddr.s_addr & lan_addr[i].mask.s_addr)
== (lan_addr[i].addr.s_addr & lan_addr[i].mask.s_addr))
{
h->iface = i;
break;
}
}
ParseHttpHeaders(h);
/* see if we need to wait for remaining data */
if( (h->reqflags & FLAG_CHUNKED) )
{
if( h->req_chunklen == -1)
{
Send400(h);
return;
}
if( h->req_chunklen )
{
h->state = 2;
return;
}
char *chunkstart, *chunk, *endptr, *endbuf;
chunk = endbuf = chunkstart = h->req_buf + h->req_contentoff;
while( (h->req_chunklen = strtol(chunk, &endptr, 16)) && (endptr != chunk) )
{
endptr = strstr(endptr, "\r\n");
if (!endptr)
{
Send400(h);
return;
}
endptr += 2;
memmove(endbuf, endptr, h->req_chunklen);
endbuf += h->req_chunklen;
chunk = endptr + h->req_chunklen;
}
h->req_contentlen = endbuf - chunkstart;
h->req_buflen = endbuf - h->req_buf;
h->state = 100;
}
DPRINTF(E_DEBUG, L_HTTP, "HTTP REQUEST: %.*s\n", h->req_buflen, h->req_buf);
if(strcmp("POST", HttpCommand) == 0)
{
h->req_command = EPost;
ProcessHTTPPOST_upnphttp(h);
}
else if((strcmp("GET", HttpCommand) == 0) || (strcmp("HEAD", HttpCommand) == 0))
{
if( ((strcmp(h->HttpVer, "HTTP/1.1")==0) && !(h->reqflags & FLAG_HOST)) || (h->reqflags & FLAG_INVALID_REQ) )
{
DPRINTF(E_WARN, L_HTTP, "Invalid request, responding ERROR 400. (No Host specified in HTTP headers?)\n");
Send400(h);
return;
}
/* 7.3.33.4 */
else if( (h->reqflags & (FLAG_TIMESEEK|FLAG_PLAYSPEED)) &&
!(h->reqflags & FLAG_RANGE) )
{
DPRINTF(E_WARN, L_HTTP, "DLNA %s requested, responding ERROR 406\n",
h->reqflags&FLAG_TIMESEEK ? "TimeSeek" : "PlaySpeed");
Send406(h);
return;
}
else if(strcmp("GET", HttpCommand) == 0)
{
h->req_command = EGet;
}
else
{
h->req_command = EHead;
}
if(strcmp(ROOTDESC_PATH, HttpUrl) == 0)
{
/* If it's a Xbox360, we might need a special friendly_name to be recognized */
if( h->req_client && h->req_client->type->type == EXbox )
{
char model_sav[2];
i = 0;
memcpy(model_sav, modelnumber, 2);
strcpy(modelnumber, "1");
if( !strchr(friendly_name, ':') )
{
i = strlen(friendly_name);
snprintf(friendly_name+i, FRIENDLYNAME_MAX_LEN-i, ": 1");
}
sendXMLdesc(h, genRootDesc);
if( i )
friendly_name[i] = '\0';
memcpy(modelnumber, model_sav, 2);
}
else if( h->req_client && h->req_client->type->flags & FLAG_SAMSUNG_DCM10 )
{
sendXMLdesc(h, genRootDescSamsung);
}
else
{
sendXMLdesc(h, genRootDesc);
}
}
else if(strcmp(CONTENTDIRECTORY_PATH, HttpUrl) == 0)
{
sendXMLdesc(h, genContentDirectory);
}
else if(strcmp(CONNECTIONMGR_PATH, HttpUrl) == 0)
{
sendXMLdesc(h, genConnectionManager);
}
else if(strcmp(X_MS_MEDIARECEIVERREGISTRAR_PATH, HttpUrl) == 0)
{
sendXMLdesc(h, genX_MS_MediaReceiverRegistrar);
}
else if(strncmp(HttpUrl, "/MediaItems/", 12) == 0)
{
SendResp_dlnafile(h, HttpUrl+12);
}
else if(strncmp(HttpUrl, "/Thumbnails/", 12) == 0)
{
SendResp_thumbnail(h, HttpUrl+12);
}
else if(strncmp(HttpUrl, "/AlbumArt/", 10) == 0)
{
SendResp_albumArt(h, HttpUrl+10);
}
#ifdef TIVO_SUPPORT
else if(strncmp(HttpUrl, "/TiVoConnect", 12) == 0)
{
if( GETFLAG(TIVO_MASK) )
{
if( *(HttpUrl+12) == '?' )
{
ProcessTiVoCommand(h, HttpUrl+13);
}
else
{
DPRINTF(E_WARN, L_HTTP, "Invalid TiVo request! %s\n", HttpUrl+12);
Send404(h);
}
}
else
{
DPRINTF(E_WARN, L_HTTP, "TiVo request with out TiVo support enabled! %s\n",
HttpUrl+12);
Send404(h);
}
}
#endif
else if(strncmp(HttpUrl, "/Resized/", 9) == 0)
{
SendResp_resizedimg(h, HttpUrl+9);
}
else if(strncmp(HttpUrl, "/icons/", 7) == 0)
{
SendResp_icon(h, HttpUrl+7);
}
else if(strncmp(HttpUrl, "/Captions/", 10) == 0)
{
SendResp_caption(h, HttpUrl+10);
}
else if(strncmp(HttpUrl, "/status", 7) == 0)
{
SendResp_presentation(h);
}
else if(strcmp(HttpUrl, "/") == 0)
{
#ifdef READYNAS
SendResp_readynas_admin(h);
#else
SendResp_presentation(h);
#endif
}
else
{
DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", HttpUrl);
Send404(h);
}
}
else if(strcmp("SUBSCRIBE", HttpCommand) == 0)
{
h->req_command = ESubscribe;
ProcessHTTPSubscribe_upnphttp(h, HttpUrl);
}
else if(strcmp("UNSUBSCRIBE", HttpCommand) == 0)
{
h->req_command = EUnSubscribe;
ProcessHTTPUnSubscribe_upnphttp(h, HttpUrl);
}
else
{
DPRINTF(E_WARN, L_HTTP, "Unsupported HTTP Command %s\n", HttpCommand);
Send501(h);
}
}
static void
Process_upnphttp(struct event *ev)
{
char buf[2048];
struct upnphttp *h = ev->data;
int n;
switch(h->state)
{
case 0:
n = recv(h->ev.fd, buf, 2048, 0);
if(n<0)
{
DPRINTF(E_ERROR, L_HTTP, "recv (state0): %s\n", strerror(errno));
h->state = 100;
}
else if(n==0)
{
DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n");
h->state = 100;
}
else
{
int new_req_buflen;
const char * endheaders;
/* if 1st arg of realloc() is null,
* realloc behaves the same as malloc() */
new_req_buflen = n + h->req_buflen + 1;
if (new_req_buflen >= 1024 * 1024)
{
DPRINTF(E_ERROR, L_HTTP, "Receive headers too large (received %d bytes)\n", new_req_buflen);
h->state = 100;
break;
}
h->req_buf = (char *)realloc(h->req_buf, new_req_buflen);
if (!h->req_buf)
{
DPRINTF(E_ERROR, L_HTTP, "Receive headers: %s\n", strerror(errno));
h->state = 100;
break;
}
memcpy(h->req_buf + h->req_buflen, buf, n);
h->req_buflen += n;
h->req_buf[h->req_buflen] = '\0';
/* search for the string "\r\n\r\n" */
endheaders = strstr(h->req_buf, "\r\n\r\n");
if(endheaders)
{
h->req_contentoff = endheaders - h->req_buf + 4;
h->req_contentlen = h->req_buflen - h->req_contentoff;
ProcessHttpQuery_upnphttp(h);
}
}
break;
case 1:
case 2:
n = recv(h->ev.fd, buf, sizeof(buf), 0);
if(n < 0)
{
DPRINTF(E_ERROR, L_HTTP, "recv (state%d): %s\n", h->state, strerror(errno));
h->state = 100;
}
else if(n == 0)
{
DPRINTF(E_WARN, L_HTTP, "HTTP Connection closed unexpectedly\n");
h->state = 100;
}
else
{
buf[sizeof(buf)-1] = '\0';
/*fwrite(buf, 1, n, stdout);*/ /* debug */
h->req_buf = (char *)realloc(h->req_buf, n + h->req_buflen);
if (!h->req_buf)
{
DPRINTF(E_ERROR, L_HTTP, "Receive request body: %s\n", strerror(errno));
h->state = 100;
break;
}
memcpy(h->req_buf + h->req_buflen, buf, n);
h->req_buflen += n;
if((h->req_buflen - h->req_contentoff) >= h->req_contentlen)
{
/* Need the struct to point to the realloc'd memory locations */
if( h->state == 1 )
{
ParseHttpHeaders(h);
ProcessHTTPPOST_upnphttp(h);
}
else if( h->state == 2 )
{
ProcessHttpQuery_upnphttp(h);
}
}
}
break;
default:
DPRINTF(E_WARN, L_HTTP, "Unexpected state: %d\n", h->state);
}
}
/* with response code and response message
* also allocate enough memory */
void
BuildHeader_upnphttp(struct upnphttp * h, int respcode,
const char * respmsg,
int bodylen)
{
static const char httpresphead[] =
"%s %d %s\r\n"
"Content-Type: %s\r\n"
"Connection: close\r\n"
"Content-Length: %d\r\n"
"Server: " MINIDLNA_SERVER_STRING "\r\n";
time_t curtime = time(NULL);
char date[30];
int templen;
struct string_s res;
if(!h->res_buf)
{
templen = sizeof(httpresphead) + 256 + bodylen;
h->res_buf = (char *)malloc(templen);
h->res_buf_alloclen = templen;
}
res.data = h->res_buf;
res.size = h->res_buf_alloclen;
res.off = 0;
strcatf(&res, httpresphead, "HTTP/1.1",
respcode, respmsg,
(h->respflags&FLAG_HTML)?"text/html":"text/xml; charset=\"utf-8\"",
bodylen);
/* Additional headers */
if(h->respflags & FLAG_TIMEOUT) {
strcatf(&res, "Timeout: Second-");
if(h->req_Timeout) {
strcatf(&res, "%d\r\n", h->req_Timeout);
} else {
strcatf(&res, "300\r\n");
}
}
if(h->respflags & FLAG_SID) {
strcatf(&res, "SID: %.*s\r\n", h->req_SIDLen, h->req_SID);
}
if(h->reqflags & FLAG_LANGUAGE) {
strcatf(&res, "Content-Language: en\r\n");
}
strftime(date, 30,"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&curtime));
strcatf(&res, "Date: %s\r\n", date);
strcatf(&res, "EXT:\r\n");
strcatf(&res, "\r\n");
h->res_buflen = res.off;
if(h->res_buf_alloclen < (h->res_buflen + bodylen))
{
h->res_buf = (char *)realloc(h->res_buf, (h->res_buflen + bodylen));
h->res_buf_alloclen = h->res_buflen + bodylen;
}
}
void
BuildResp2_upnphttp(struct upnphttp * h, int respcode,
const char * respmsg,
const char * body, int bodylen)
{
BuildHeader_upnphttp(h, respcode, respmsg, bodylen);
if( h->req_command == EHead )
return;
if(body)
memcpy(h->res_buf + h->res_buflen, body, bodylen);
h->res_buflen += bodylen;
}
/* responding 200 OK ! */
void
BuildResp_upnphttp(struct upnphttp *h, const char *body, int bodylen)
{
BuildResp2_upnphttp(h, 200, "OK", body, bodylen);
}
void
SendResp_upnphttp(struct upnphttp * h)
{
int n;
DPRINTF(E_DEBUG, L_HTTP, "HTTP RESPONSE: %.*s\n", h->res_buflen, h->res_buf);
n = send(h->ev.fd, h->res_buf, h->res_buflen, 0);
if(n<0)
{
DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno));
}
else if(n < h->res_buflen)
{
/* TODO : handle correctly this case */
DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
n, h->res_buflen);
}
}
static int
send_data(struct upnphttp * h, char * header, size_t size, int flags)
{
int n;
n = send(h->ev.fd, header, size, flags);
if(n<0)
{
DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %s\n", strerror(errno));
}
else if(n < h->res_buflen)
{
/* TODO : handle correctly this case */
DPRINTF(E_ERROR, L_HTTP, "send(res_buf): %d bytes sent (out of %d)\n",
n, h->res_buflen);
}
else
{
return 0;
}
return 1;
}
static void
send_file(struct upnphttp * h, int sendfd, off_t offset, off_t end_offset)
{
off_t send_size;
off_t ret;
char *buf = NULL;
#if HAVE_SENDFILE
int try_sendfile = 1;
#endif
while( offset <= end_offset )
{
#if HAVE_SENDFILE
if( try_sendfile )
{
send_size = ( ((end_offset - offset) < MAX_BUFFER_SIZE) ? (end_offset - offset + 1) : MAX_BUFFER_SIZE);
ret = sys_sendfile(h->ev.fd, sendfd, &offset, send_size);
if( ret == -1 )
{
DPRINTF(E_DEBUG, L_HTTP, "sendfile error :: error no. %d [%s]\n", errno, strerror(errno));
/* If sendfile isn't supported on the filesystem, don't bother trying to use it again. */
if( errno == EOVERFLOW || errno == EINVAL )
try_sendfile = 0;
else if( errno != EAGAIN )
break;
}
else
{
//DPRINTF(E_DEBUG, L_HTTP, "sent %lld bytes to %d. offset is now %lld.\n", ret, h->socket, offset);
continue;
}
}
#endif
/* Fall back to regular I/O */
if( !buf )
buf = malloc(MIN_BUFFER_SIZE);
send_size = (((end_offset - offset) < MIN_BUFFER_SIZE) ? (end_offset - offset + 1) : MIN_BUFFER_SIZE);
lseek(sendfd, offset, SEEK_SET);
ret = read(sendfd, buf, send_size);
if( ret == -1 ) {
DPRINTF(E_DEBUG, L_HTTP, "read error :: error no. %d [%s]\n", errno, strerror(errno));
if( errno == EAGAIN )
continue;
else
break;
}
ret = write(h->ev.fd, buf, ret);
if( ret == -1 ) {
DPRINTF(E_DEBUG, L_HTTP, "write error :: error no. %d [%s]\n", errno, strerror(errno));
if( errno == EAGAIN )
continue;
else
break;
}
offset += ret;
}
free(buf);
}
static void
start_dlna_header(struct string_s *str, int respcode, const char *tmode, const char *mime)
{
char date[30];
time_t now;
now = time(NULL);
strftime(date, sizeof(date),"%a, %d %b %Y %H:%M:%S GMT" , gmtime(&now));
strcatf(str, "HTTP/1.1 %d OK\r\n"
"Connection: close\r\n"
"Date: %s\r\n"
"Server: " MINIDLNA_SERVER_STRING "\r\n"
"EXT:\r\n"
"realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
"transferMode.dlna.org: %s\r\n"
"Content-Type: %s\r\n",
respcode, date, tmode, mime);
}
static int
_open_file(const char *orig_path)
{
struct media_dir_s *media_path;
char buf[PATH_MAX];
const char *path;
int fd;
if (!GETFLAG(WIDE_LINKS_MASK))
{
path = realpath(orig_path, buf);
if (!path)
{
DPRINTF(E_ERROR, L_HTTP, "Error resolving path %s: %s\n",
orig_path, strerror(errno));
return -1;
}
for (media_path = media_dirs; media_path; media_path = media_path->next)
{
if (strncmp(path, media_path->path, strlen(media_path->path)) == 0)
break;
}
if (!media_path && strncmp(path, db_path, strlen(db_path)))
{
DPRINTF(E_ERROR, L_HTTP, "Rejecting wide link %s -> %s\n",
orig_path, path);
return -403;
}
}
else
path = orig_path;
fd = open(path, O_RDONLY);
if (fd < 0)
DPRINTF(E_ERROR, L_HTTP, "Error opening %s\n", path);
return fd;
}
static void
SendResp_icon(struct upnphttp * h, char * icon)
{
char header[512];
char mime[12] = "image/";
char *data;
int size;
struct string_s str;
if( strcmp(icon, "sm.png") == 0 )
{
DPRINTF(E_DEBUG, L_HTTP, "Sending small PNG icon\n");
data = (char *)png_sm;
size = sizeof(png_sm)-1;
strcpy(mime+6, "png");
}
else if( strcmp(icon, "lrg.png") == 0 )
{
DPRINTF(E_DEBUG, L_HTTP, "Sending large PNG icon\n");
data = (char *)png_lrg;
size = sizeof(png_lrg)-1;
strcpy(mime+6, "png");
}
else if( strcmp(icon, "sm.jpg") == 0 )
{
DPRINTF(E_DEBUG, L_HTTP, "Sending small JPEG icon\n");
data = (char *)jpeg_sm;
size = sizeof(jpeg_sm)-1;
strcpy(mime+6, "jpeg");
}
else if( strcmp(icon, "lrg.jpg") == 0 )
{
DPRINTF(E_DEBUG, L_HTTP, "Sending large JPEG icon\n");
data = (char *)jpeg_lrg;
size = sizeof(jpeg_lrg)-1;
strcpy(mime+6, "jpeg");
}
else
{
DPRINTF(E_WARN, L_HTTP, "Invalid icon request: %s\n", icon);
Send404(h);
return;
}
INIT_STR(str, header);
start_dlna_header(&str, 200, "Interactive", mime);
strcatf(&str, "Content-Length: %d\r\n\r\n", size);
if( send_data(h, str.data, str.off, MSG_MORE) == 0 )
{
if( h->req_command != EHead )
send_data(h, data, size, 0);
}
CloseSocket_upnphttp(h);
}
static void
SendResp_albumArt(struct upnphttp * h, char * object)
{
char header[512];
char *path;
off_t size;
long long id;
int fd;
struct string_s str;
if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) )
{
DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
Send406(h);
return;
}
id = strtoll(object, NULL, 10);
path = sql_get_text_field(db, "SELECT PATH from ALBUM_ART where ID = '%lld'", id);
if( !path )
{
DPRINTF(E_WARN, L_HTTP, "ALBUM_ART ID %s not found, responding ERROR 404\n", object);
Send404(h);
return;
}
DPRINTF(E_INFO, L_HTTP, "Serving album art ID: %lld [%s]\n", id, path);
fd = _open_file(path);
if( fd < 0 ) {
sqlite3_free(path);
if (fd == -403)
Send403(h);
else
Send404(h);
return;
}
sqlite3_free(path);
size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
INIT_STR(str, header);
start_dlna_header(&str, 200, "Interactive", "image/jpeg");
strcatf(&str, "Content-Length: %jd\r\n"
"contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN\r\n\r\n",
(intmax_t)size);
if( send_data(h, str.data, str.off, MSG_MORE) == 0 )
{
if( h->req_command != EHead )
send_file(h, fd, 0, size-1);
}
close(fd);
CloseSocket_upnphttp(h);
}
static void
SendResp_caption(struct upnphttp * h, char * object)
{
char header[512];
char *path;
off_t size;
long long id;
int fd;
struct string_s str;
id = strtoll(object, NULL, 10);
path = sql_get_text_field(db, "SELECT PATH from CAPTIONS where ID = %lld", id);
if( !path )
{
DPRINTF(E_WARN, L_HTTP, "CAPTION ID %s not found, responding ERROR 404\n", object);
Send404(h);
return;
}
DPRINTF(E_INFO, L_HTTP, "Serving caption ID: %lld [%s]\n", id, path);
fd = _open_file(path);
if( fd < 0 ) {
sqlite3_free(path);
if (fd == -403)
Send403(h);
else
Send404(h);
return;
}
sqlite3_free(path);
size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
INIT_STR(str, header);
start_dlna_header(&str, 200, "Interactive", "smi/caption");
strcatf(&str, "Content-Length: %jd\r\n\r\n", (intmax_t)size);
if( send_data(h, str.data, str.off, MSG_MORE) == 0 )
{
if( h->req_command != EHead )
send_file(h, fd, 0, size-1);
}
close(fd);
CloseSocket_upnphttp(h);
}
static void
SendResp_thumbnail(struct upnphttp * h, char * object)
{
char header[512];
char *path;
long long id;
ExifData *ed;
ExifLoader *l;
struct string_s str;
if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) )
{
DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
Send406(h);
return;
}
id = strtoll(object, NULL, 10);
path = sql_get_text_field(db, "SELECT PATH from DETAILS where ID = '%lld'", id);
if( !path )
{
DPRINTF(E_WARN, L_HTTP, "DETAIL ID %s not found, responding ERROR 404\n", object);
Send404(h);
return;
}
DPRINTF(E_INFO, L_HTTP, "Serving thumbnail for ObjectId: %lld [%s]\n", id, path);
if( access(path, F_OK) != 0 )
{
DPRINTF(E_ERROR, L_HTTP, "Error accessing %s\n", path);
Send404(h);
sqlite3_free(path);
return;
}
l = exif_loader_new();
exif_loader_write_file(l, path);
ed = exif_loader_get_data(l);
exif_loader_unref(l);
sqlite3_free(path);
if( !ed || !ed->size )
{
Send404(h);
if( ed )
exif_data_unref(ed);
return;
}
INIT_STR(str, header);
start_dlna_header(&str, 200, "Interactive", "image/jpeg");
strcatf(&str, "Content-Length: %jd\r\n"
"contentFeatures.dlna.org: DLNA.ORG_PN=JPEG_TN;DLNA.ORG_CI=1\r\n\r\n",
(intmax_t)ed->size);
if( send_data(h, str.data, str.off, MSG_MORE) == 0 )
{
if( h->req_command != EHead )
send_data(h, (char *)ed->data, ed->size, 0);
}
exif_data_unref(ed);
CloseSocket_upnphttp(h);
}
static void
SendResp_resizedimg(struct upnphttp * h, char * object)
{
char header[512];
char buf[128];
struct string_s str;
char **result;
char dlna_pn[22];
uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B|DLNA_FLAG_TM_I;
int width=640, height=480, dstw, dsth, size;
int srcw, srch;
unsigned char * data = NULL;
char *path, *file_path = NULL;
char *resolution = NULL;
char *key, *val;
char *saveptr, *item = NULL;
int rotate;
int pixw = 0, pixh = 0;
long long id;
int rows=0, chunked, ret;
image_s *imsrc = NULL, *imdst = NULL;
int scale = 1;
const char *tmode;
id = strtoll(object, &saveptr, 10);
snprintf(buf, sizeof(buf), "SELECT PATH, RESOLUTION, ROTATION from DETAILS where ID = '%lld'", (long long)id);
ret = sql_get_table(db, buf, &result, &rows, NULL);
if( ret != SQLITE_OK )
{
Send500(h);
return;
}
if( rows )
{
file_path = result[3];
resolution = result[4];
rotate = result[5] ? atoi(result[5]) : 0;
}
if( !file_path || !resolution || (access(file_path, F_OK) != 0) )
{
DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
sqlite3_free_table(result);
Send404(h);
return;
}
if( saveptr )
saveptr = strchr(saveptr, '?');
path = saveptr ? saveptr + 1 : object;
for( item = strtok_r(path, "&,", &saveptr); item != NULL; item = strtok_r(NULL, "&,", &saveptr) )
{
decodeString(item, 1);
val = item;
key = strsep(&val, "=");
if( !val )
continue;
DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val);
if( strcasecmp(key, "width") == 0 )
{
width = atoi(val);
}
else if( strcasecmp(key, "height") == 0 )
{
height = atoi(val);
}
else if( strcasecmp(key, "rotation") == 0 )
{
rotate = (rotate + atoi(val)) % 360;
sql_exec(db, "UPDATE DETAILS set ROTATION = %d where ID = %lld", rotate, id);
}
else if( strcasecmp(key, "pixelshape") == 0 )
{
ret = sscanf(val, "%d:%d", &pixw, &pixh);
if( ret != 2 )
pixw = pixh = 0;
}
}
#if USE_FORK
pid_t newpid = 0;
newpid = process_fork(h->req_client);
if( newpid > 0 )
{
CloseSocket_upnphttp(h);
goto resized_error;
}
#endif
if( h->reqflags & (FLAG_XFERSTREAMING|FLAG_RANGE) )
{
DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
Send406(h);
goto resized_error;
}
DPRINTF(E_INFO, L_HTTP, "Serving resized image for ObjectId: %lld [%s]\n", id, file_path);
if( rotate )
DPRINTF(E_DEBUG, L_HTTP, "Rotating image %d degrees\n", rotate);
switch( rotate )
{
case 90:
ret = sscanf(resolution, "%dx%d", &srch, &srcw);
rotate = ROTATE_90;
break;
case 270:
ret = sscanf(resolution, "%dx%d", &srch, &srcw);
rotate = ROTATE_270;
break;
case 180:
ret = sscanf(resolution, "%dx%d", &srcw, &srch);
rotate = ROTATE_180;
break;
default:
ret = sscanf(resolution, "%dx%d", &srcw, &srch);
rotate = ROTATE_NONE;
break;
}
if( ret != 2 )
{
Send500(h);
return;
}
/* Figure out the best destination resolution we can use */
dstw = width;
dsth = ((((width<<10)/srcw)*srch)>>10);
if( dsth > height )
{
dsth = height;
dstw = (((height<<10)/srch) * srcw>>10);
}
/* Account for pixel shape */
if( pixw && pixh )
{
if( pixh > pixw )
dsth = dsth * pixw / pixh;
else if( pixw > pixh )
dstw = dstw * pixh / pixw;
}
if( dstw <= 160 && dsth <= 160 )
strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_TN;");
else if( dstw <= 640 && dsth <= 480 )
strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_SM;");
else if( dstw <= 1024 && dsth <= 768 )
strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_MED;");
else
strcpy(dlna_pn, "DLNA.ORG_PN=JPEG_LRG;");
if( srcw>>4 >= dstw && srch>>4 >= dsth)
scale = 8;
else if( srcw>>3 >= dstw && srch>>3 >= dsth )
scale = 4;
else if( srcw>>2 >= dstw && srch>>2 >= dsth )
scale = 2;
INIT_STR(str, header);
#if USE_FORK
if( (h->reqflags & FLAG_XFERBACKGROUND) && (setpriority(PRIO_PROCESS, 0, 19) == 0) )
tmode = "Background";
else
#endif
tmode = "Interactive";
start_dlna_header(&str, 200, tmode, "image/jpeg");
strcatf(&str, "contentFeatures.dlna.org: %sDLNA.ORG_CI=1;DLNA.ORG_FLAGS=%08X%024X\r\n",
dlna_pn, dlna_flags, 0);
if( strcmp(h->HttpVer, "HTTP/1.0") == 0 )
{
chunked = 0;
imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale, rotate);
}
else
{
chunked = 1;
strcatf(&str, "Transfer-Encoding: chunked\r\n\r\n");
}
if( !chunked )
{
if( !imsrc )
{
DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path);
Send500(h);
goto resized_error;
}
imdst = image_resize(imsrc, dstw, dsth);
data = image_save_to_jpeg_buf(imdst, &size);
strcatf(&str, "Content-Length: %d\r\n\r\n", size);
}
if( (send_data(h, str.data, str.off, 0) == 0) && (h->req_command != EHead) )
{
if( chunked )
{
imsrc = image_new_from_jpeg(file_path, 1, NULL, 0, scale, rotate);
if( !imsrc )
{
DPRINTF(E_WARN, L_HTTP, "Unable to open image %s!\n", file_path);
Send500(h);
goto resized_error;
}
imdst = image_resize(imsrc, dstw, dsth);
data = image_save_to_jpeg_buf(imdst, &size);
ret = sprintf(buf, "%x\r\n", size);
send_data(h, buf, ret, MSG_MORE);
send_data(h, (char *)data, size, MSG_MORE);
send_data(h, "\r\n0\r\n\r\n", 7, 0);
}
else
{
send_data(h, (char *)data, size, 0);
}
}
DPRINTF(E_INFO, L_HTTP, "Done serving %s\n", file_path);
if( imsrc )
image_free(imsrc);
if( imdst )
image_free(imdst);
CloseSocket_upnphttp(h);
resized_error:
sqlite3_free_table(result);
#if USE_FORK
if( newpid == 0 )
_exit(0);
#endif
}
static void
SendResp_dlnafile(struct upnphttp *h, char *object)
{
char header[1024];
struct string_s str;
char buf[128];
char **result;
int rows, ret;
off_t total, offset, size;
int64_t id;
int sendfh;
uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B;
uint32_t cflags = h->req_client ? h->req_client->type->flags : 0;
const char *tmode;
enum client_types ctype = h->req_client ? h->req_client->type->type : 0;
static struct { int64_t id;
enum client_types client;
char path[PATH_MAX];
char mime[32];
char dlna[96];
} last_file = { 0, 0 };
#if USE_FORK
pid_t newpid = 0;
#endif
id = strtoll(object, NULL, 10);
if( cflags & FLAG_MS_PFS )
{
if( strstr(object, "?albumArt=true") )
{
char *art;
art = sql_get_text_field(db, "SELECT ALBUM_ART from DETAILS where ID = '%lld'", id);
if (art)
{
SendResp_albumArt(h, art);
sqlite3_free(art);
}
else
Send404(h);
return;
}
}
if( id != last_file.id || ctype != last_file.client )
{
snprintf(buf, sizeof(buf), "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", (long long)id);
ret = sql_get_table(db, buf, &result, &rows, NULL);
if( (ret != SQLITE_OK) )
{
DPRINTF(E_ERROR, L_HTTP, "Didn't find valid file for %lld!\n", (long long)id);
Send500(h);
return;
}
if( !rows || !result[3] || !result[4] )
{
DPRINTF(E_WARN, L_HTTP, "%s not found, responding ERROR 404\n", object);
sqlite3_free_table(result);
Send404(h);
return;
}
/* Cache the result */
last_file.id = id;
last_file.client = ctype;
strncpy(last_file.path, result[3], sizeof(last_file.path)-1);
if( result[4] )
{
strncpy(last_file.mime, result[4], sizeof(last_file.mime)-1);
/* From what I read, Samsung TV's expect a [wrong] MIME type of x-mkv. */
if( cflags & FLAG_SAMSUNG )
{
if( strcmp(last_file.mime+6, "x-matroska") == 0 )
strcpy(last_file.mime+8, "mkv");
/* Samsung TV's such as the A750 can natively support many
Xvid/DivX AVI's however, the DLNA server needs the
mime type to say video/mpeg */
else if( ctype == ESamsungSeriesA && strcmp(last_file.mime+6, "x-msvideo") == 0 )
strcpy(last_file.mime+6, "mpeg");
}
/* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */
else if( ctype == ESonyBDP )
{
if( strcmp(last_file.mime+6, "x-matroska") == 0 ||
strcmp(last_file.mime+6, "mpeg") == 0 )
strcpy(last_file.mime+6, "divx");
}
}
if( result[5] )
snprintf(last_file.dlna, sizeof(last_file.dlna), "DLNA.ORG_PN=%s;", result[5]);
else
last_file.dlna[0] = '\0';
sqlite3_free_table(result);
}
#if USE_FORK
newpid = process_fork(h->req_client);
if( newpid > 0 )
{
CloseSocket_upnphttp(h);
return;
}
#endif
DPRINTF(E_INFO, L_HTTP, "Serving DetailID: %lld [%s]\n", (long long)id, last_file.path);
if( h->reqflags & FLAG_XFERSTREAMING )
{
if( strncmp(last_file.mime, "image", 5) == 0 )
{
DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Streaming with an image!\n");
Send406(h);
goto error;
}
}
else if( h->reqflags & FLAG_XFERINTERACTIVE )
{
if( h->reqflags & FLAG_REALTIMEINFO )
{
DPRINTF(E_WARN, L_HTTP, "Bad realTimeInfo flag with Interactive request!\n");
Send400(h);
goto error;
}
if( strncmp(last_file.mime, "image", 5) != 0 )
{
DPRINTF(E_WARN, L_HTTP, "Client tried to specify transferMode as Interactive without an image!\n");
/* Samsung TVs (well, at least the A950) do this for some reason,
* and I don't see them fixing this bug any time soon. */
if( !(cflags & FLAG_SAMSUNG) || GETFLAG(DLNA_STRICT_MASK) )
{
Send406(h);
goto error;
}
}
}
offset = h->req_RangeStart;
sendfh = _open_file(last_file.path);
if( sendfh < 0 ) {
if (sendfh == -403)
Send403(h);
else
Send404(h);
goto error;
}
size = lseek(sendfh, 0, SEEK_END);
lseek(sendfh, 0, SEEK_SET);
INIT_STR(str, header);
#if USE_FORK
if( (h->reqflags & FLAG_XFERBACKGROUND) && (setpriority(PRIO_PROCESS, 0, 19) == 0) )
tmode = "Background";
else
#endif
if( strncmp(last_file.mime, "image", 5) == 0 )
tmode = "Interactive";
else
tmode = "Streaming";
start_dlna_header(&str, (h->reqflags & FLAG_RANGE ? 206 : 200), tmode, last_file.mime);
if( h->reqflags & FLAG_RANGE )
{
if( !h->req_RangeEnd || h->req_RangeEnd == size )
{
h->req_RangeEnd = size - 1;
}
if( (h->req_RangeStart > h->req_RangeEnd) || (h->req_RangeStart < 0) )
{
DPRINTF(E_WARN, L_HTTP, "Specified range was invalid!\n");
Send400(h);
close(sendfh);
goto error;
}
if( h->req_RangeEnd >= size )
{
DPRINTF(E_WARN, L_HTTP, "Specified range was outside file boundaries!\n");
Send416(h);
close(sendfh);
goto error;
}
total = h->req_RangeEnd - h->req_RangeStart + 1;
strcatf(&str, "Content-Length: %jd\r\n"
"Content-Range: bytes %jd-%jd/%jd\r\n",
(intmax_t)total, (intmax_t)h->req_RangeStart,
(intmax_t)h->req_RangeEnd, (intmax_t)size);
}
else
{
h->req_RangeEnd = size - 1;
total = size;
strcatf(&str, "Content-Length: %jd\r\n", (intmax_t)total);
}
switch( *last_file.mime )
{
case 'i':
dlna_flags |= DLNA_FLAG_TM_I;
break;
case 'a':
case 'v':
default:
dlna_flags |= DLNA_FLAG_TM_S;
break;
}
if( h->reqflags & FLAG_CAPTION )
{
if( sql_get_int_field(db, "SELECT ID from CAPTIONS where ID = '%lld'", (long long)id) > 0 )
strcatf(&str, "CaptionInfo.sec: http://%s:%d/Captions/%lld.srt\r\n",
lan_addr[h->iface].str, runtime_vars.port, (long long)id);
}
strcatf(&str, "Accept-Ranges: bytes\r\n"
"contentFeatures.dlna.org: %sDLNA.ORG_OP=%02X;DLNA.ORG_CI=%X;DLNA.ORG_FLAGS=%08X%024X\r\n\r\n",
last_file.dlna, 1, 0, dlna_flags, 0);
//DEBUG DPRINTF(E_DEBUG, L_HTTP, "RESPONSE: %s\n", str.data);
if( send_data(h, str.data, str.off, MSG_MORE) == 0 )
{
if( h->req_command != EHead )
send_file(h, sendfh, offset, h->req_RangeEnd);
}
close(sendfh);
CloseSocket_upnphttp(h);
error:
#if USE_FORK
if( newpid == 0 )
_exit(0);
#endif
return;
}
C
1
https://gitee.com/yctxkj/minidlna.git
git@gitee.com:yctxkj/minidlna.git
yctxkj
minidlna
minidlna
master

搜索帮助