summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorschmirl <schmirl>2010-07-20 12:26:29 +0000
committerschmirl <schmirl>2010-07-20 12:26:29 +0000
commitc0a7f1610a3cbc6655d26d4752d9d4f553d98af3 (patch)
treec4926c59f5f7818d794657dd03065d5f42860af9
parentedc3ad1c8e079a96a985d60a800a56918186b9e3 (diff)
downloadvdr-plugin-streamdev-c0a7f1610a3cbc6655d26d4752d9d4f553d98af3.tar.gz
vdr-plugin-streamdev-c0a7f1610a3cbc6655d26d4752d9d4f553d98af3.tar.bz2
fixed wrong URL path in m3u playlists
-rw-r--r--CONTRIBUTORS3
-rw-r--r--HISTORY7
-rw-r--r--server/connectionHTTP.c360
3 files changed, 215 insertions, 155 deletions
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 5f8ef8d..2e8d5e3 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -154,3 +154,6 @@ carel
wolfi.m
for reporting a typo in externermux quality parameter value
+
+Norman Thiel
+ for reporting a wrong URL path in m3u playlists
diff --git a/HISTORY b/HISTORY
index 95fb1c7..3ca77bc 100644
--- a/HISTORY
+++ b/HISTORY
@@ -1,10 +1,15 @@
VDR Plugin 'streamdev' Revision History
---------------------------------------
+2010-07-20: Version 0.5.0b
+
+- fixed wrong URL path in m3u playlists (reported by Norman Thiel)
+
2010-07-20: Version 0.5.0a
- set externremux.sh executable in distribution archive
-- externremux quality value should be wlan54, not wlan45
+- externremux quality value should be wlan54, not wlan45 (reported by
+ wolfi.m@vdrportal)
2010-07-19: Version 0.5.0
diff --git a/server/connectionHTTP.c b/server/connectionHTTP.c
index 83e568d..028b9ea 100644
--- a/server/connectionHTTP.c
+++ b/server/connectionHTTP.c
@@ -1,5 +1,5 @@
/*
- * $Id: connectionHTTP.c,v 1.17 2009/06/19 06:32:45 schmirl Exp $
+ * $Id: connectionHTTP.c,v 1.19 2010/07/20 12:26:29 schmirl Exp $
*/
#include <ctype.h>
@@ -13,13 +13,13 @@ cConnectionHTTP::cConnectionHTTP(void):
cServerConnection("HTTP"),
m_Status(hsRequest),
m_LiveStreamer(NULL),
- m_StreamerParameter(""),
m_Channel(NULL),
- m_Apid(0),
m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType),
m_ChannelList(NULL)
{
Dprintf("constructor hsRequest\n");
+ m_Apid[0] = m_Apid[1] = 0;
+ m_Dpid[0] = m_Dpid[1] = 0;
}
cConnectionHTTP::~cConnectionHTTP()
@@ -37,30 +37,64 @@ bool cConnectionHTTP::Command(char *Cmd)
Dprintf("command %s\n", Cmd);
switch (m_Status) {
case hsRequest:
- Dprintf("Request\n");
- m_Request = Cmd;
- m_Status = hsHeaders;
- return true;
+ // parse METHOD PATH[?QUERY] VERSION
+ {
+ char *p, *q, *v;
+ p = strchr(Cmd, ' ');
+ if (p) {
+ *p = 0;
+ v = strchr(++p, ' ');
+ if (v) {
+ *v = 0;
+ SetHeader("REQUEST_METHOD", Cmd);
+ q = strchr(p, '?');
+ if (q)
+ *q = 0;
+ SetHeader("QUERY_STRING", q ? ++q : "");
+ SetHeader("PATH_INFO", p);
+ m_Status = hsHeaders;
+ return true;
+ }
+ }
+ }
+ return false;
case hsHeaders:
if (*Cmd == '\0') {
m_Status = hsBody;
return ProcessRequest();
}
- if (strncasecmp(Cmd, "Host:", 5) == 0) {
- Dprintf("Host-Header\n");
- m_Host = (std::string) skipspace(Cmd + 5);
- return true;
+ else if (isspace(*Cmd)) {
+ ; //TODO: multi-line header
}
- else if (strncasecmp(Cmd, "Authorization:", 14) == 0) {
- Cmd = skipspace(Cmd + 14);
- if (strncasecmp(Cmd, "Basic", 5) == 0) {
- Dprintf("'Authorization Basic'-Header\n");
- m_Authorization = (std::string) skipspace(Cmd + 5);
- return true;
+ else {
+ // convert header name to CGI conventions:
+ // uppercase, '-' replaced with '_', prefix "HTTP_"
+ char *p;
+ for (p = Cmd; *p != 0 && *p != ':'; p++) {
+ if (*p == '-')
+ *p = '_';
+ else
+ *p = toupper(*p);
+ }
+ if (*p == ':') {
+ *p = 0;
+ p = skipspace(++p);
+ // don't disclose Authorization header
+ if (strcmp(Cmd, "AUTHORIZATION") == 0) {
+ char *q;
+ for (q = p; *q != 0 && *q != ' '; q++)
+ *q = toupper(*q);
+ if (p != q) {
+ *q = 0;
+ SetHeader("AUTH_TYPE", p);
+ m_Authorization = (std::string) skipspace(++q);
+ }
+ }
+ else
+ SetHeader(Cmd, p, "HTTP_");
}
}
- Dprintf("header\n");
return true;
default:
// skip additional blank lines
@@ -73,10 +107,31 @@ bool cConnectionHTTP::Command(char *Cmd)
bool cConnectionHTTP::ProcessRequest(void)
{
+ // keys for Headers() hash
+ const static std::string AUTH_TYPE("AUTH_TYPE");
+ const static std::string REQUEST_METHOD("REQUEST_METHOD");
+ const static std::string PATH_INFO("PATH_INFO");
+
Dprintf("process\n");
- if (!StreamdevHosts.Acceptable(RemoteIpAddr()))
- {
- if (!opt_auth || m_Authorization.empty() || m_Authorization.compare(opt_auth) != 0) {
+ if (!StreamdevHosts.Acceptable(RemoteIpAddr())) {
+ bool authOk = opt_auth && !m_Authorization.empty();
+ if (authOk) {
+ tStrStrMap::const_iterator it = Headers().find(AUTH_TYPE);
+
+ if (it == Headers().end()) {
+ // no authorization header present
+ authOk = false;
+ }
+ else if (it->second.compare("BASIC") == 0) {
+ // basic auth
+ authOk &= m_Authorization.compare(opt_auth) == 0;
+ }
+ else {
+ // unsupported auth type
+ authOk = false;
+ }
+ }
+ if (!authOk) {
isyslog("streamdev-server: HTTP authorization required");
DeferClose();
return Respond("HTTP/1.0 401 Authorization Required")
@@ -84,28 +139,22 @@ bool cConnectionHTTP::ProcessRequest(void)
&& Respond("");
}
}
- if (m_Request.substr(0, 4) == "GET " && CmdGET(m_Request.substr(4))) {
- switch (m_Job) {
- case hjListing:
- if (m_ChannelList)
- return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
- break;
- case hjTransfer:
- if (m_Channel == NULL) {
- DeferClose();
- return Respond("HTTP/1.0 404 not found");
- }
-
- m_LiveStreamer = new cStreamdevLiveStreamer(0, m_StreamerParameter);
+ if (Headers().at(REQUEST_METHOD).compare("GET") == 0 && ProcessURI(Headers().at(PATH_INFO))) {
+ if (m_ChannelList)
+ return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
+ else if (m_Channel != NULL) {
cDevice *device = GetDevice(m_Channel, 0);
if (device != NULL) {
device->SwitchChannel(m_Channel, false);
- if (m_LiveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid)) {
+ m_LiveStreamer = new cStreamdevLiveStreamer(0, this);
+ if (m_LiveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL)) {
m_LiveStreamer->SetDevice(device);
if (!SetDSCP())
LOG_ERROR_STR("unable to set DSCP sockopt");
- if (m_StreamType == stES && (m_Apid != 0 || ISRADIO(m_Channel))) {
+ if (m_StreamType == stEXT) {
+ return Respond("HTTP/1.0 200 OK");
+ } else if (ISRADIO(m_Channel) || (m_StreamType == stES && (m_Apid[0] || m_Dpid[0]))) {
return Respond("HTTP/1.0 200 OK")
&& Respond("Content-Type: audio/mpeg")
&& Respond("icy-name: %s", true, m_Channel->Name())
@@ -116,12 +165,46 @@ bool cConnectionHTTP::ProcessRequest(void)
&& Respond("");
}
}
+ DELETENULL(m_LiveStreamer);
}
- DELETENULL(m_LiveStreamer);
DeferClose();
return Respond("HTTP/1.0 409 Channel not available")
&& Respond("");
}
+ else {
+ DeferClose();
+ return Respond("HTTP/1.0 404 not found")
+ && Respond("");
+ }
+ } else if (Headers().at(REQUEST_METHOD).compare("HEAD") == 0 && ProcessURI(Headers().at(PATH_INFO))) {
+ DeferClose();
+ if (m_ChannelList)
+ return Respond("%s", true, m_ChannelList->HttpHeader().c_str());
+ else if (m_Channel != NULL) {
+ cDevice *device = GetDevice(m_Channel, 0);
+ if (device != NULL) {
+ if (m_StreamType == stEXT) {
+ // TODO
+ return Respond("HTTP/1.0 200 OK")
+ && Respond("");
+ } else if (ISRADIO(m_Channel) || (m_StreamType == stES && (m_Apid[0] || m_Dpid[0]))) {
+ return Respond("HTTP/1.0 200 OK")
+ && Respond("Content-Type: audio/mpeg")
+ && Respond("icy-name: %s", true, m_Channel->Name())
+ && Respond("");
+ } else {
+ return Respond("HTTP/1.0 200 OK")
+ && Respond("Content-Type: video/mpeg")
+ && Respond("");
+ }
+ }
+ return Respond("HTTP/1.0 409 Channel not available")
+ && Respond("");
+ }
+ else {
+ return Respond("HTTP/1.0 404 not found")
+ && Respond("");
+ }
}
DeferClose();
@@ -136,161 +219,130 @@ void cConnectionHTTP::Flushed(void)
if (m_Status != hsBody)
return;
- switch (m_Job) {
- case hjListing:
- if (m_ChannelList) {
- if (m_ChannelList->HasNext()) {
- if (!Respond("%s", true, m_ChannelList->Next().c_str()))
- DeferClose();
- }
- else {
- DELETENULL(m_ChannelList);
- m_Status = hsFinished;
+ if (m_ChannelList) {
+ if (m_ChannelList->HasNext()) {
+ if (!Respond("%s", true, m_ChannelList->Next().c_str()))
DeferClose();
- }
- return;
}
- // should never be reached
- esyslog("streamdev-server cConnectionHTTP::Flushed(): no channel list");
- m_Status = hsFinished;
- break;
-
- case hjTransfer:
+ else {
+ DELETENULL(m_ChannelList);
+ m_Status = hsFinished;
+ DeferClose();
+ }
+ return;
+ }
+ else if (m_Channel != NULL) {
Dprintf("streamer start\n");
m_LiveStreamer->Start(this);
m_Status = hsFinished;
- break;
+ }
+ else {
+ // should never be reached
+ esyslog("streamdev-server cConnectionHTTP::Flushed(): no job to do");
+ m_Status = hsFinished;
}
}
-bool cConnectionHTTP::CmdGET(const std::string &Opts)
+cChannelList* cConnectionHTTP::ChannelListFromString(const std::string& Path, const std::string& Filebase, const std::string& Fileext) const
{
- const char *ptr, *sp, *pp, *fp, *xp, *qp, *ep;
- const cChannel *chan;
- int apid = 0;
-
- ptr = Opts.c_str();
-
- // find begin of URL
- sp = skipspace(ptr);
- // find end of URL (\0 or first space character)
- for (ep = sp; *ep && !isspace(*ep); ep++)
- ;
- // find begin of query string (first ?)
- for (qp = sp; qp < ep && *qp != '?'; qp++)
- ;
- // find begin of filename (last /)
- for (fp = qp; fp > sp && *fp != '/'; --fp)
- ;
- // find begin of section params (first ;)
- for (pp = sp; pp < fp && *pp != ';'; pp++)
- ;
- // find filename extension (first .)
- for (xp = fp; xp < qp && *xp != '.'; xp++)
- ;
- if (qp - xp > 5) // too long for a filename extension
- xp = qp;
-
- std::string type, filespec, fileext, query;
- // Streamtype with leading / stripped off
- if (pp > sp)
- type = Opts.substr(sp - ptr + 1, pp - sp - 1);
- // Section parameters with leading ; stripped off
- if (fp > pp)
- m_StreamerParameter = Opts.substr(pp - ptr + 1, fp - pp - 1);
- // file basename with leading / stripped off
- if (xp > fp)
- filespec = Opts.substr(fp - ptr + 1, xp - fp - 1);
- // file extension including leading .
- fileext = Opts.substr(xp - ptr, qp - xp);
- // query string including leading ?
- query = Opts.substr(qp - ptr, ep - qp);
+ // keys for Headers() hash
+ const static std::string QUERY_STRING("QUERY_STRING");
+ const static std::string HOST("HTTP_HOST");
- Dprintf("before channelfromstring: type(%s) param(%s) filespec(%s) fileext(%s) query(%s)\n", type.c_str(), m_StreamerParameter.c_str(), filespec.c_str(), fileext.c_str(), query.c_str());
-
- const char* pType = type.c_str();
- if (strcasecmp(pType, "PS") == 0) {
- m_StreamType = stPS;
- } else if (strcasecmp(pType, "PES") == 0) {
- m_StreamType = stPES;
- } else if (strcasecmp(pType, "TS") == 0) {
- m_StreamType = stTS;
- } else if (strcasecmp(pType, "ES") == 0) {
- m_StreamType = stES;
- } else if (strcasecmp(pType, "Extern") == 0) {
- m_StreamType = stExtern;
- }
+ const std::string query = Headers().at(QUERY_STRING);
std::string groupTarget;
cChannelIterator *iterator = NULL;
- if (filespec.compare("tree") == 0) {
+ if (Filebase.compare("tree") == 0) {
const cChannel* c = NULL;
size_t groupIndex = query.find("group=");
if (groupIndex != std::string::npos)
c = cChannelList::GetGroup(atoi(query.c_str() + groupIndex + 6));
iterator = new cListTree(c);
- groupTarget = filespec + fileext;
- } else if (filespec.compare("groups") == 0) {
+ groupTarget = Filebase + Fileext;
+ } else if (Filebase.compare("groups") == 0) {
iterator = new cListGroups();
- groupTarget = (std::string) "group" + fileext;
- } else if (filespec.compare("group") == 0) {
+ groupTarget = (std::string) "group" + Fileext;
+ } else if (Filebase.compare("group") == 0) {
const cChannel* c = NULL;
size_t groupIndex = query.find("group=");
if (groupIndex != std::string::npos)
c = cChannelList::GetGroup(atoi(query.c_str() + groupIndex + 6));
iterator = new cListGroup(c);
- } else if (filespec.compare("channels") == 0) {
+ } else if (Filebase.compare("channels") == 0) {
iterator = new cListChannels();
- } else if (filespec.compare("all") == 0 ||
- (filespec.empty() && fileext.empty())) {
+ } else if (Filebase.compare("all") == 0 ||
+ (Filebase.empty() && Fileext.empty())) {
iterator = new cListAll();
}
if (iterator) {
- if (filespec.empty() || fileext.compare(".htm") == 0 || fileext.compare(".html") == 0) {
- m_ChannelList = new cHtmlChannelList(iterator, m_StreamType, (filespec + fileext + query).c_str(), groupTarget.c_str());
- m_Job = hjListing;
- } else if (fileext.compare(".m3u") == 0) {
+ if (Filebase.empty() || Fileext.compare(".htm") == 0 || Fileext.compare(".html") == 0) {
+ std::string self = Filebase + Fileext;
+ if (!query.empty())
+ self += '?' + query;
+ return new cHtmlChannelList(iterator, m_StreamType, self.c_str(), groupTarget.c_str());
+ } else if (Fileext.compare(".m3u") == 0) {
std::string base;
- if (*(m_Host.c_str()))
- base = "http://" + m_Host + "/";
+ tStrStrMap::const_iterator it = Headers().find(HOST);
+ if (it != Headers().end())
+ base = "http://" + it->second + "/";
else
base = (std::string) "http://" + LocalIp() + ":" +
(const char*) itoa(StreamdevServerSetup.HTTPServerPort) + "/";
- if (type.empty())
- {
- switch (m_StreamType)
- {
- case stTS: base += "TS/"; break;
- case stPS: base += "PS/"; break;
- case stPES: base += "PES/"; break;
- case stES: base += "ES/"; break;
- case stExtern: base += "Extern/"; break;
- default: break;
-
- }
- } else {
- base += type;
- if (!m_StreamerParameter.empty())
- base += ";" + m_StreamerParameter;
- base += "/";
- }
- m_ChannelList = new cM3uChannelList(iterator, base.c_str());
- m_Job = hjListing;
+ base += Path;
+ return new cM3uChannelList(iterator, base.c_str());
} else {
delete iterator;
- return false;
}
- } else if ((chan = ChannelFromString(filespec.c_str(), &apid)) != NULL) {
- m_Channel = chan;
- m_Apid = apid;
- Dprintf("Apid is %d\n", apid);
- m_Job = hjTransfer;
+ }
+ return NULL;
+}
+
+bool cConnectionHTTP::ProcessURI(const std::string& PathInfo)
+{
+ std::string filespec, fileext;
+ size_t file_pos = PathInfo.rfind('/');
+
+ if (file_pos != std::string::npos) {
+ size_t ext_pos = PathInfo.rfind('.');
+ // file basename with leading / stripped off
+ filespec = PathInfo.substr(file_pos + 1, ext_pos - file_pos - 1);
+ if (ext_pos != std::string::npos)
+ // file extension including leading .
+ fileext = PathInfo.substr(ext_pos);
+ }
+ if (fileext.length() > 5) {
+ //probably not an extension
+ filespec += fileext;
+ fileext.clear();
+ }
+
+ // Streamtype with leading / stripped off
+ std::string type = PathInfo.substr(1, PathInfo.find_first_of("/;", 1) - 1);
+ const char* pType = type.c_str();
+ if (strcasecmp(pType, "PS") == 0) {
+ m_StreamType = stPS;
+ } else if (strcasecmp(pType, "PES") == 0) {
+ m_StreamType = stPES;
+ } else if (strcasecmp(pType, "TS") == 0) {
+ m_StreamType = stTS;
+ } else if (strcasecmp(pType, "ES") == 0) {
+ m_StreamType = stES;
+ } else if (strcasecmp(pType, "EXT") == 0) {
+ m_StreamType = stEXT;
+ }
+
+ Dprintf("before channelfromstring: type(%s) filespec(%s) fileext(%s)\n", type.c_str(), filespec.c_str(), fileext.c_str());
+
+ if ((m_ChannelList = ChannelListFromString(PathInfo.substr(1, file_pos), filespec.c_str(), fileext.c_str())) != NULL) {
+ Dprintf("Channel list requested\n");
+ return true;
+ } else if ((m_Channel = ChannelFromString(filespec.c_str(), &m_Apid[0], &m_Dpid[0])) != NULL) {
+ Dprintf("Channel found. Apid/Dpid is %d/%d\n", m_Apid[0], m_Dpid[0]);
+ return true;
} else
return false;
-
- Dprintf("after channelfromstring\n");
- return true;
}