summaryrefslogtreecommitdiff
path: root/libs/networking/src
diff options
context:
space:
mode:
authorgeronimo <geronimo013@gmx.de>2012-07-13 04:26:40 +0200
committergeronimo <geronimo013@gmx.de>2012-07-13 04:26:40 +0200
commit2d48ae784ea6828e8626c32c848f64232d8f35c0 (patch)
treefab114b03e91125783a778b835dd1913b039cebe /libs/networking/src
downloadcmp-2d48ae784ea6828e8626c32c848f64232d8f35c0.tar.gz
cmp-2d48ae784ea6828e8626c32c848f64232d8f35c0.tar.bz2
initial import
Diffstat (limited to 'libs/networking/src')
-rw-r--r--libs/networking/src/AbstractSocket.cc290
-rw-r--r--libs/networking/src/Authorization.cc469
-rw-r--r--libs/networking/src/ClientSocket.cc45
-rw-r--r--libs/networking/src/ConnectionHandler.cc393
-rw-r--r--libs/networking/src/ConnectionPoint.cc89
-rw-r--r--libs/networking/src/Credentials.cc171
-rw-r--r--libs/networking/src/HTTPAuthorizationRequest.cc47
-rw-r--r--libs/networking/src/HTTPFileResponse.cc102
-rw-r--r--libs/networking/src/HTTPMessage.cc165
-rw-r--r--libs/networking/src/HTTPParser.cc42
-rw-r--r--libs/networking/src/HTTPRequest.cc171
-rw-r--r--libs/networking/src/HTTPRequestHandler.cc43
-rw-r--r--libs/networking/src/HTTPResponse.cc183
-rw-r--r--libs/networking/src/HTTPServer.cc156
-rw-r--r--libs/networking/src/HTTPStatus.cc121
-rw-r--r--libs/networking/src/Principal.cc105
-rw-r--r--libs/networking/src/ServerConfig.cc60
-rw-r--r--libs/networking/src/ServerSocket.cc84
-rw-r--r--libs/networking/src/Url.cc229
19 files changed, 2965 insertions, 0 deletions
diff --git a/libs/networking/src/AbstractSocket.cc b/libs/networking/src/AbstractSocket.cc
new file mode 100644
index 0000000..827d466
--- /dev/null
+++ b/libs/networking/src/AbstractSocket.cc
@@ -0,0 +1,290 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: AbstractSocket.cc
+ * Created: 4. Juli 2012, 07:13
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <AbstractSocket.h>
+#include <Logging.h>
+#include <stddef.h>
+#include <arpa/inet.h>
+#include <resolv.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <poll.h>
+
+cAbstractSocket::cAbstractSocket(int Port, int Queue)
+ : sock(-1)
+ , queue(Queue)
+ , blocking(true)
+ , thisSide(NULL)
+{
+}
+
+cAbstractSocket::cAbstractSocket(const char *ServerName, int Port)
+ : sock(-1)
+ , queue(0)
+ , blocking(true)
+ , thisSide(NULL)
+{
+ others.push_back(new cConnectionPoint(ServerName, Port));
+}
+
+cAbstractSocket::~cAbstractSocket()
+{
+ Close();
+ if (thisSide) delete thisSide;
+ cConnectionPoint *p;
+
+ for (size_t i=0; i < others.size(); ++i) {
+ p = others[i];
+ delete p;
+ }
+}
+
+void cAbstractSocket::Close(void)
+{
+ if (sock >= 0) {
+ close(sock);
+ sock = -1;
+ }
+}
+
+// the client side
+bool cAbstractSocket::Connect(void)
+{
+ if (sock < 0) {
+ struct addrinfo hints, *result, *rp;
+ char buf[20];
+ int s;
+
+ /* Obtain address(es) matching host/port */
+ memset(buf, 0, sizeof(buf));
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6
+ hints.ai_socktype = SOCK_STREAM;
+ snprintf(buf, sizeof(buf), "%d", OtherSide()->Port());
+
+ if ((s = getaddrinfo(OtherSide()->HostName(), buf, &hints, &result))) {
+ esyslog("getaddrinfo: %s", gai_strerror(s));
+
+ return -1;
+ }
+
+ // getaddrinfo potentially returns a list of addresses, that may fit,
+ // so try each address until we successfully connect
+ for (rp = result; rp; rp = rp->ai_next) {
+ sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (sock == -1) continue;
+
+ if (connect(sock, rp->ai_addr, rp->ai_addrlen) != -1)
+ break; // we got a connection
+ close(sock);
+ }
+ if (rp == NULL) { // no address could be connected to
+ esyslog("Could not connect!");
+ Close();
+
+ return false;
+ }
+ // here we have a valid connection, so lets gather some information
+ thisSide = GetNameAndAddress(sock, rp->ai_addr, rp->ai_addrlen);
+ OtherSide()->SetSocket(sock);
+ char nameBuf[512];
+
+ memset(nameBuf, 0, sizeof(nameBuf));
+ if ((s = getnameinfo(rp->ai_addr, rp->ai_addrlen, nameBuf, sizeof(nameBuf), NULL, 0, NI_NAMEREQD))) {
+ esyslog("failed to determine servers hostname %s", gai_strerror(s));
+ }
+ else {
+ OtherSide()->SetRealName(nameBuf);
+ }
+ freeaddrinfo(result);
+ }
+ return sock > 0;
+}
+
+// as each address may be IP4 or IP6, cut this off
+static void * getAddr(struct sockaddr *sa)
+{
+ if (sa->sa_family == AF_INET)
+ return &(((struct sockaddr_in*)sa)->sin_addr);
+ return &(((struct sockaddr_in6*)sa)->sin6_addr);
+}
+
+// same is true for port access
+static int getPort(struct sockaddr *sa)
+{
+ if (sa->sa_family == AF_INET)
+ return ((struct sockaddr_in*)sa)->sin_port;
+ return ((struct sockaddr_in6*)sa)->sin6_port;
+}
+
+cConnectionPoint *cAbstractSocket::GetNameAndAddress(int Socket, struct sockaddr *sa, socklen_t sa_size)
+{
+ cConnectionPoint *rv = NULL;
+ char nameBuf[512], addrBuf[INET6_ADDRSTRLEN];
+
+ if (getnameinfo(sa, sa_size, nameBuf, sizeof(nameBuf), NULL, 0, NI_NAMEREQD)) {
+ // ok, just ip address has to suffice to start work
+ rv = new cConnectionPoint(inet_ntop(sa->sa_family, getAddr(sa), addrBuf, sizeof(addrBuf))
+ , ntohs(getPort(sa))
+ , inet_ntop(sa->sa_family, getAddr(sa), addrBuf, sizeof(addrBuf)));
+ }
+ else {
+ rv = new cConnectionPoint(inet_ntop(sa->sa_family, getAddr(sa), addrBuf, sizeof(addrBuf))
+ , ntohs(getPort(sa))
+ , nameBuf);
+ }
+ if (rv) rv->SetSocket(Socket);
+
+ return rv;
+}
+
+void cAbstractSocket::SetBlockingIO(bool ForceBlockingIO)
+{
+ blocking = ForceBlockingIO;
+}
+
+// server side first part
+bool cAbstractSocket::Open(int Port)
+{
+ if (sock < 0) {
+ isyslog("socket is < 0 - so start a new connection ...");
+ struct addrinfo hints, *result, *rp;
+ char buf[20];
+ int s;
+
+ memset(&hints, 0, sizeof(hints));
+ memset(buf, 0, sizeof(buf));
+ hints.ai_family = AF_UNSPEC; // allow IPv4 or IPv6
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+ snprintf(buf, sizeof(buf), "%d", Port);
+
+ if ((s = getaddrinfo(NULL, buf, &hints, &result))) {
+ esyslog("getaddrinfo: %s\n", gai_strerror(s));
+
+ return false;
+ }
+ int yes=1;
+
+ // getaddrinfo potentially returns a list of addresses, that may fit,
+ // so try each address until we successfully bound
+ for (rp = result; rp; rp = rp->ai_next) {
+ sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (sock == -1) continue;
+
+ setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
+
+ if (bind(sock, rp->ai_addr, rp->ai_addrlen) != -1)
+ break; // ok, we succeeded to bind
+ close(sock);
+ }
+ if (!rp) {
+ esyslog("could not bind");
+ Close();
+
+ return false;
+ }
+ ConfigureSocket(sock);
+ freeaddrinfo(result);
+
+ // listen to the socket:
+ if (listen(sock, queue) < 0) {
+ esyslog("failed to listen to bound socket!");
+ Close();
+ return (false);
+ }
+ }
+ return (true);
+}
+
+// server side second part
+cConnectionPoint *cAbstractSocket::Accept(int Port, int TimeoutMs)
+{
+ cConnectionPoint *client = NULL;
+
+ if (Open(Port)) {
+ struct sockaddr_storage clientname;
+ uint cl_size = sizeof(clientname);
+ struct pollfd pf[1];
+
+ pf[0].fd = sock;
+ pf[0].events = POLLIN;
+
+ if (poll(pf, 1, TimeoutMs) < 1) return NULL;
+ int newsock = accept(sock, (struct sockaddr *) &clientname, &cl_size);
+
+ if (newsock > 0) {
+ isyslog("ok, got client connection request ...");
+
+ // if server address has not been restricted at bind-time, its possible that we don't
+ // have a local address yet. If server is bound to 0 (any address), we can determine
+ // the real server address in use now. But only do it once.
+ if (!thisSide) {
+ struct sockaddr_storage server;
+ uint sa_size = sizeof(server);
+
+ if (getpeername(newsock, (struct sockaddr *) &server, &sa_size)) {
+ esyslog("getpeername failed: #%d", errno);
+ }
+ else thisSide = GetNameAndAddress(newsock, (struct sockaddr *) &server, sa_size);
+ }
+
+ // At server side, multiple clients can have open connections at the same time,
+ // so get rid of that here. We always fetch connections informations
+ // before any access decisions, so those could be extended to use names
+ // some day ...
+ client = GetNameAndAddress(newsock, (struct sockaddr *) &clientname, cl_size);
+ if (client) {
+ bool accepted = true;
+ //FIXME: change determination of accepting client access!
+ // accepted = SVDRPhosts.Acceptable(clientname.sin_addr.s_addr);
+ // change to:
+ // accepted = SVDRPHosts.Acceptable(cpi);
+ // so in SVDRPHosts you have IP-Address and real name (if accessible)
+
+ if (!accepted) {
+ const char *s = "Access denied!\n";
+
+ if (write(newsock, s, strlen(s)) < 0) {
+ esyslog(s);
+ close(newsock);
+ }
+ newsock = -1;
+ client = NULL;
+ }
+ else {
+ others.push_back(client);
+ }
+ isyslog("connect from %s, port %hu - %s", client->HostName(), client->Port(), accepted ? "accepted" : "DENIED");
+ }
+ }
+ else if (errno != EINTR && errno != EAGAIN) {
+ esyslog("failed to accept client");
+ }
+ }
+ return (client);
+}
diff --git a/libs/networking/src/Authorization.cc b/libs/networking/src/Authorization.cc
new file mode 100644
index 0000000..b22145a
--- /dev/null
+++ b/libs/networking/src/Authorization.cc
@@ -0,0 +1,469 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: Authorization.cc
+ * Created: 3. Juli 2012, 17:27
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <Authorization.h>
+#include <Credentials.h>
+#include <MD5Calculator.h>
+#include <Principal.h>
+#include <ConnectionPoint.h>
+#include <Logging.h>
+#include <stdio.h>
+#include <util.h>
+
+static bool useSessionHash = true;
+
+cAuthorization::cAuthorization(const cHTTPRequest &OriginalRequest, char *NOnceFromHeap)
+///< use this constructor to create an authorization request for responses to
+///< clients without valid authorization headers.
+ : tmp(NULL)
+ , principal(NULL)
+ , method(OriginalRequest.Method())
+ , serverID(NULL)
+ , sessAlgo(useSessionHash)
+ , nonce(NOnceFromHeap)
+ , uri(OriginalRequest.Url().ToString())
+ , response(NULL)
+ , opaque(NULL)
+ , cnonce(NULL)
+ , qop(NULL)
+ , counter(0)
+ , authTime(time(NULL))
+{
+ tmp = new cPrincipal("unset", Credentials.ApplicationRealm());
+ opaque = cAuthorizations::CreateSessionID(OriginalRequest, authTime);
+}
+
+cAuthorization::cAuthorization(cHTTPRequest::HTTPRequestMethod Method, const char *Raw)
+///< use this constructor to create an authorization from clients request message
+ : tmp(NULL)
+ , principal(NULL)
+ , method(Method)
+ , serverID(NULL)
+ , sessAlgo(false)
+ , nonce(NULL)
+ , uri(NULL)
+ , response(NULL)
+ , opaque(NULL)
+ , cnonce(NULL)
+ , qop(NULL)
+ , counter(0)
+{
+ ParseRawBuffer(Raw);
+}
+
+cAuthorization::cAuthorization(const cPrincipal *Principal, cHTTPRequest::HTTPRequestMethod Method, const cAuthorization &other)
+ : tmp(other.tmp ? new cPrincipal(*other.tmp) : NULL)
+ , principal(Principal)
+ , method(Method)
+ , serverID(other.serverID ? strdup(other.serverID) : NULL)
+ , sessAlgo(other.sessAlgo)
+ , nonce(other.nonce ? strdup(other.nonce) : NULL)
+ , uri(other.uri ? strdup(other.uri) : NULL)
+ , response(other.response ? strdup(other.response) : NULL)
+ , opaque(other.opaque ? strdup(other.opaque) : NULL)
+ , cnonce(other.cnonce ? strdup(other.cnonce) : NULL)
+ , qop(other.qop ? strdup(other.qop) : NULL)
+ , counter(other.counter)
+{
+}
+
+cAuthorization::cAuthorization(const cAuthorization& other)
+ : tmp(other.tmp ? new cPrincipal(*other.tmp) : NULL)
+ , principal(other.principal)
+ , method(other.method)
+ , serverID(other.serverID ? strdup(other.serverID) : NULL)
+ , sessAlgo(other.sessAlgo)
+ , nonce(other.nonce ? strdup(other.nonce) : NULL)
+ , uri(other.uri ? strdup(other.uri) : NULL)
+ , response(other.response ? strdup(other.response) : NULL)
+ , opaque(other.opaque ? strdup(other.opaque) : NULL)
+ , cnonce(other.cnonce ? strdup(other.cnonce) : NULL)
+ , qop(other.qop ? strdup(other.qop) : NULL)
+ , counter(other.counter + 1)
+{
+}
+
+cAuthorization::~cAuthorization()
+{
+ if (tmp) delete tmp;
+ FREE(nonce);
+ FREE(uri);
+ FREE(serverID);
+ FREE(response);
+ FREE(opaque);
+ FREE(cnonce);
+ FREE(qop);
+}
+
+cAuthorization& cAuthorization::operator=(const cAuthorization& other)
+{
+ if (&other == this) return *this;
+ if (tmp) delete tmp;
+ FREE(nonce);
+ FREE(uri);
+ FREE(serverID);
+ FREE(response);
+ FREE(opaque);
+ FREE(cnonce);
+
+ principal = other.principal;
+ sessAlgo = other.sessAlgo;
+ method = other.method;
+ serverID = other.serverID ? strdup(other.serverID) : NULL;
+ nonce = other.nonce ? strdup(other.nonce) : NULL;
+ uri = other.uri ? strdup(other.uri) : NULL;
+ response = other.response ? strdup(other.response) : NULL;
+ opaque = other.opaque ? strdup(other.opaque) : NULL;
+ cnonce = other.cnonce ? strdup(other.cnonce) : NULL;
+ counter = other.counter;
+ qop = other.qop ? strdup(other.qop) : NULL;
+ tmp = other.tmp ? new cPrincipal(*other.tmp) : NULL;
+
+ return *this;
+}
+
+void cAuthorization::CreateClientHash()
+{
+ cMD5Calculator hc;
+ char buf[40];
+
+ snprintf(buf, sizeof(buf), "%lu", random());
+
+ hc.AddContent(serverID);
+ hc.AddContent(":");
+ hc.AddContent(buf);
+
+ cnonce = hc.Hash();
+}
+
+static const char *getAttrName(char *buf, int bufSize, const char *src)
+{
+ const char *s = src;
+ char *d = buf;
+
+ while (*s && isspace(*s)) ++s;
+ while (*s && ((d - buf) < bufSize) && *s != '=' && !isspace(*s)) *d++ = *s++;
+ *d = 0;
+
+ return *s ? s : NULL;
+}
+
+static const char *getValue(char *buf, int bufSize, const char *src)
+{
+ const char *s = src;
+ char *d = buf;
+
+ if (*s == '"') {
+ ++s;
+ while (*s && ((d - buf) < bufSize) && *s != '"') *d++ = *s++;
+ if (*s != '"') return NULL;
+ ++s;
+ }
+ else {
+ while (*s && ((d - buf) < bufSize) && *s != ',') *d++ = *s++;
+ }
+ *d = 0;
+
+ return *s ? s : NULL;
+}
+
+size_t cAuthorization::Write(char* Buffer, size_t BufSize)
+{
+ static const char *UnAuthorizedMask = "WWW-Authenticate: Digest realm=\"%s\", "
+ "nonce=\"%s\", opaque=\"%s\", algorithm=\"MD5%s\", qop=\"auth\"\n";
+ static const char *AuthorizedMask = "Authorization: Digest username=\"%s\", "
+ "realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", opaque=\"%s\", qop=\"auth\", nc=%08X, algorithm=\"MD5%s\", cnonce=\"%s\"";
+ static const char *AuthenticationMaskDef = "Authentication-Info: qop=auth, nc=%08X, rspauth=\"%s\", cnonce=\"%s\"";
+ static const char *AuthenticationMaskNew = "Authentication-Info: nextnounce=\"%s\", qop=auth, nc=%08X, rspauth=\"%s\", cnonce=\"%s\"";
+ size_t n=0;
+
+ if (principal && strcmp(principal->Name(), "unset")) {
+ static bool timeout = 0;
+ //TODO: authorization info, should be sent after certain timeout
+ if (timeout) n = snprintf(Buffer, BufSize, AuthenticationMaskNew, nonce, counter, response, cnonce);
+ else n = snprintf(Buffer, BufSize, AuthenticationMaskDef, counter, response, cnonce);
+ }
+ else if (!principal && tmp && tmp->Name() && tmp->Hash()) {
+ n = snprintf(Buffer, BufSize, AuthorizedMask, tmp->Name(), tmp->Realm(), nonce, uri, response, opaque,
+ counter, sessAlgo ? "-sess" : "", cnonce);
+ }
+ else {
+ n = snprintf(Buffer, BufSize, UnAuthorizedMask, Credentials.ApplicationRealm(), nonce, opaque, sessAlgo ? "-sess" : "");
+ }
+ return n;
+}
+
+
+// authorization is one line, so conforms to header value. Digest has been broken for readability
+// Digest username="ich oder",
+// realm="Schnarcher@my.box",
+// nonce="59F980BB771C1CC31EE7360C7B06F15D",
+// uri="/favicon.ico",
+// response="7d060b3de37d132e2f4e4b014b127818",
+// opaque="33B9703065AD7E1A1DED1DFE07AA357C",
+// qop=auth,
+// nc=00000001,
+// cnonce="4dd872a6c6debac8"
+void cAuthorization::ParseRawBuffer(const char* Raw)
+{
+ static const char *eyecatch = "Digest";
+
+ if (strncmp(Raw, eyecatch, strlen(eyecatch))) {
+// esyslog("Authentication is not of type Digest! - not supported!");
+
+ return;
+ }
+ char nameBuf[30];
+ char scratch[128];
+ const char *p = Raw + strlen(eyecatch);
+ bool done=false;
+
+ if (tmp) {
+ delete tmp;
+ tmp = NULL;
+ }
+ while (!done) {
+ p = getAttrName(nameBuf, sizeof(nameBuf), p);
+ if (*p != '=') {
+ esyslog("invalid digest format! Expected '=' and found %C (%02X)", *p, *p);
+ return;
+ }
+ p = getValue(scratch, sizeof(scratch), ++p);
+ SetAttribute(nameBuf, scratch);
+
+ if (!p) break;
+ if (*p != ',') break;
+ else ++p;
+ }
+}
+
+void cAuthorization::SetUser(const char *UserID, const char *Password)
+{
+ if (principal) {
+ //TODO: do we really want to support change of user/password on existing principal?
+ }
+ else if (tmp) {
+ tmp->SetName(UserID);
+ tmp->CreateHash(Password);
+ CreateClientHash();
+ }
+}
+
+void cAuthorization::SetAttribute(char* Name, const char* Value)
+{
+ char **p = NULL;
+
+// isyslog("cAuthorization::SetAttribute(\"%s\") => [%s]", Name, Value);
+ if (!strcasecmp(Name, "username")) {
+ if (!tmp) tmp = new cPrincipal(Value, "unset");
+ else tmp->SetName(Value);
+ }
+ else if (!strcasecmp(Name, "realm")) {
+ if (!tmp) tmp = new cPrincipal("unset", Value);
+ else tmp->SetRealm(Value);
+ }
+ else if (!strcasecmp(Name, "algorithm")) {
+ if (!strcmp("MD5-sess", Value)) sessAlgo = true;
+ }
+ else if (!strcasecmp(Name, "nonce")) p = &nonce;
+ else if (!strcasecmp(Name, "uri")) p = &uri;
+ else if (!strcasecmp(Name, "response")) p = &response;
+ else if (!strcasecmp(Name, "opaque")) p = &opaque;
+ else if (!strcasecmp(Name, "cnonce")) p = &cnonce;
+ else if (!strcasecmp(Name, "qop")) {
+ if (!strncmp(Value, "auth", 4)) p = &qop;
+ else esyslog("invalid/unsupported auth method! - only \"auth\" supported!");
+ }
+ else if (!strcasecmp(Name, "nc")) {
+ counter = strtol(Value, NULL, 16);
+ }
+ if (p) {
+ free(*p);
+ *p = strdup(Value);
+ }
+ if (tmp) {
+ principal = Credentials.FindPrincipal(tmp->Name(), tmp->Realm());
+ if (principal) {
+ delete tmp;
+ tmp = NULL;
+ }
+ }
+}
+
+char * cAuthorization::CalculateA1(const char *Username, const char *Password)
+{
+ if (Username && Password) SetUser(Username, Password);
+ char *principalHash = (char *) (principal ? principal->Hash() : tmp->Hash());
+
+ if (sessAlgo) {
+ cMD5Calculator hc;
+
+ hc.AddContent(principalHash);
+ hc.AddContent(":");
+ hc.AddContent(nonce);
+ hc.AddContent(":");
+ hc.AddContent(cnonce);
+
+ return hc.Hash();
+ }
+ return strdup(principalHash);
+}
+
+char * cAuthorization::CalculateA2(const char *Uri)
+{
+ cMD5Calculator hc;
+
+ // don't send method on authorized response
+ if (!principal || strcmp(principal->Name(), "unset")) hc.AddContent(requestMethod2String(method));
+ hc.AddContent(":");
+ hc.AddContent(Uri);
+
+ return hc.Hash();
+}
+
+const char * cAuthorization::CalculateResponse(const char *Uri, const char *Username, const char *Password)
+{
+ free(response);
+ char *a1 = CalculateA1(Username, Password);
+ char *a2 = CalculateA2(Uri ? Uri : uri);
+ cMD5Calculator hc;
+ char buf[12];
+
+ snprintf(buf, sizeof(buf), "%08x", counter);
+ hc.AddContent(a1);
+ hc.AddContent(":");
+ hc.AddContent(nonce);
+ if (qop) {
+ hc.AddContent(":");
+ hc.AddContent(buf);
+ hc.AddContent(":");
+ hc.AddContent(cnonce);
+ hc.AddContent(":");
+ hc.AddContent(qop);
+ }
+ hc.AddContent(":");
+ hc.AddContent(a2);
+
+ response = hc.Hash();
+
+// printf("CalculateResponse: Realm-Digest (A1) =>%s<\n", a1);
+// printf("CalculateResponse: URL-Digest (A2) =>%s<\n", a2);
+// printf("CalculateResponse: Request-Digest =>%s<\n", response);
+
+ free(a1);
+ free(a2);
+
+ return response;
+}
+
+void cAuthorization::Dump(void) const
+{
+ printf(">>>---------- Authorization ------------------\n");
+ if (principal) principal->Dump();
+ else if (tmp) tmp->Dump();
+ else printf("principal is NULL\n");
+ printf("nonce ...: >%s<\n", nonce);
+ printf("method ..: >%s<\n", requestMethod2String(method));
+ printf("uri .....: >%s<\n", uri);
+ printf("response : >%s<\n", response);
+ printf("opaque ..: >%s<\n", opaque);
+ printf("cnonce ..: >%s<\n", cnonce);
+ printf("counter .: >%d<\n", counter);
+ printf("============= Authorization ===============<<<\n");
+}
+
+// --- Authorizations ---------------------------------------------------------
+
+static void freeAuthorizationCallback(void *elem)
+{
+ delete (cAuthorization *)elem;
+}
+
+cAuthorizations::cAuthorizations()
+ : cManagedVector(freeAuthorizationCallback)
+{
+}
+
+cAuthorizations::~cAuthorizations()
+{
+}
+
+cAuthorization *cAuthorizations::FindAuthorization(const char *SessionID)
+{
+ cAuthorization *a;
+
+ for (size_t i=0; i < size(); ++i) {
+ a = (cAuthorization *) (*this)[i];
+ if (!strcmp(a->Opaque(), SessionID)) return a;
+ }
+ return NULL;
+}
+
+const cAuthorization &cAuthorizations::CreateAuthorization(const cHTTPRequest &originalRequest, const cConnectionPoint &client, unsigned long connectionNumber)
+{
+ cAuthorization *rv = new cAuthorization(originalRequest, CreateNOnce(client, connectionNumber));
+
+ push_back(rv);
+
+ return *rv;
+}
+
+char *cAuthorizations::CreateNOnce(const cConnectionPoint &client, unsigned long connectionNumber)
+{
+ char buf[40];
+ cMD5Calculator hc;
+
+ snprintf(buf, sizeof(buf), "%lu:%lu", connectionNumber, random());
+
+ hc.AddContent(buf);
+ hc.AddContent(":");
+ hc.AddContent(client.HostName());
+
+ return hc.Hash();
+}
+
+char *cAuthorizations::CreateSessionID(const cHTTPRequest &OriginalRequest, time_t AuthTime)
+{
+ char buf[40];
+ cMD5Calculator hc;
+ const char *p0, *p1;
+
+ snprintf(buf, sizeof(buf), "%lu", AuthTime);
+
+ p0 = OriginalRequest.ClientHost();
+ p1 = OriginalRequest.UserAgent();
+
+ hc.AddContent(p0 ? p0 : "localhost");
+ hc.AddContent(":");
+ hc.AddContent(buf);
+ hc.AddContent(":");
+ hc.AddContent(p1 ? p1 : "unknown");
+
+ return hc.Hash();
+}
+
+void cAuthorizations::Del(cAuthorization *Auth2Invalidate)
+{
+//TODO:
+} \ No newline at end of file
diff --git a/libs/networking/src/ClientSocket.cc b/libs/networking/src/ClientSocket.cc
new file mode 100644
index 0000000..db42cf9
--- /dev/null
+++ b/libs/networking/src/ClientSocket.cc
@@ -0,0 +1,45 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: ClientSocket.cc
+ * Created: 4. Juli 2012, 07:25
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <ClientSocket.h>
+
+cClientSocket::cClientSocket(const char *ServerName, int Port)
+ : cAbstractSocket(ServerName, Port)
+{
+}
+
+cClientSocket::~cClientSocket()
+{
+}
+
+bool cClientSocket::Connect(void)
+{
+ return cAbstractSocket::Connect();
+}
+
+void cClientSocket::Close(void)
+{
+ cAbstractSocket::Close();
+}
+
diff --git a/libs/networking/src/ConnectionHandler.cc b/libs/networking/src/ConnectionHandler.cc
new file mode 100644
index 0000000..c5d5ebf
--- /dev/null
+++ b/libs/networking/src/ConnectionHandler.cc
@@ -0,0 +1,393 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: ConnectionHandler.cc
+ * Created: 4. Juli 2012, 07:32
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <ConnectionHandler.h>
+#include <ServerSocket.h>
+#include <HTTPRequest.h>
+#include <HTTPFileResponse.h>
+#include <HTTPAuthorizationRequest.h>
+#include <HTTPRequestHandler.h>
+#include <Authorization.h>
+#include <Credentials.h>
+#include <MD5Calculator.h>
+#include <StringBuilder.h>
+#include <TimeMs.h>
+#include <Logging.h>
+#include <util.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <iostream>
+#include <tr1/unordered_map>
+
+static unsigned long connectionCounter = 0;
+class cHTTPRequestHandlers {
+public:
+ typedef std::tr1::unordered_map<std::string, cHTTPRequestHandler *>::iterator iterator;
+ cHTTPRequestHandlers();
+ ~cHTTPRequestHandlers();
+
+ cHTTPRequestHandler *Handler(const char *UrlPrefix);
+ void SetHandler(const char *UrlPrefix, cHTTPRequestHandler *Handler);
+ cHTTPRequestHandler *DefaultHandler(void) { return defaultHandler; }
+ void SetDefaultHandler(cHTTPRequestHandler *Handler) { defaultHandler = Handler; }
+
+ cHTTPRequestHandlers::iterator begin() { return internalMap.begin(); }
+ cHTTPRequestHandlers::iterator end() { return internalMap.end(); }
+
+private:
+ cHTTPRequestHandler *defaultHandler;
+ std::tr1::unordered_map<std::string, cHTTPRequestHandler *> internalMap;
+ };
+
+cHTTPRequestHandlers::cHTTPRequestHandlers()
+ : defaultHandler(NULL)
+{
+}
+
+cHTTPRequestHandlers::~cHTTPRequestHandlers()
+{
+ cHTTPRequestHandlers::iterator it = internalMap.begin();
+
+ while (it != internalMap.end()) {
+ delete it->second;
+ ++it;
+ }
+ if (defaultHandler) delete defaultHandler;
+}
+
+cHTTPRequestHandler *cHTTPRequestHandlers::Handler(const char* UrlPrefix)
+{
+ cHTTPRequestHandlers::iterator it = internalMap.find(UrlPrefix);
+
+ if (it == internalMap.end()) return NULL;
+ return it->second;
+}
+
+void cHTTPRequestHandlers::SetHandler(const char* UrlPrefix, cHTTPRequestHandler* Handler)
+{
+ Handler->SetID(UrlPrefix);
+ internalMap[UrlPrefix] = Handler;
+}
+static cHTTPRequestHandlers registeredHandlers;
+
+cConnectionHandler::cConnectionHandler(cConnectionPoint &Client, cServerConfig &Config, bool StayConnected)
+ : config(Config)
+ , client(Client)
+ , connectionNumber(++connectionCounter)
+ , bufSize(2048)
+ , scratch(NULL)
+ , nonce(NULL)
+ , stayConnected(StayConnected)
+{
+}
+
+cConnectionHandler::~cConnectionHandler()
+{
+ Cancel();
+}
+
+void cConnectionHandler::Action()
+{
+ if (!scratch) scratch = (char *)malloc(bufSize);
+ if (!scratch) {
+ esyslog("failed to allocate scratch buffer of size %ld", bufSize);
+ return;
+ }
+ cHTTPRequest *request;
+ cHTTPResponse *response = NULL;
+ uint64_t start, end;
+ size_t nTrans;
+
+ isyslog("ConnectionHandler::Action() - start the loop");
+ while (Running()) {
+ memset(scratch, 0, bufSize);
+
+ // process at least one request
+ isyslog("read next request from Client");
+ nTrans = read(client.Socket(), scratch, bufSize);
+ if (nTrans < 1) {
+ esyslog("failed to read client-Request! End of this connection handler ... #%d", errno);
+ return;
+ }
+ start = cTimeMs::Now();
+ if (nTrans == bufSize) {
+ char *p = scratch + nTrans;
+
+ bufSize += 2048;
+ scratch = (char *) realloc(scratch, bufSize);
+ nTrans += read(client.Socket(), p, 2048);
+
+ //TODO: should we support multiple buffer resize?
+ if (nTrans == bufSize) {
+ esyslog("OUPS - buffer overflow? - Lets stop this connection handler ...");
+ return;
+ }
+ }
+ isyslog("#%lu - got client request |>%s<|", connectionNumber, scratch);
+
+ request = new cHTTPRequest(scratch);
+ if (!request) {
+ esyslog("ERROR: failed to parse request from client!");
+ response = new cHTTPResponse(HTTP_NotAcceptable);
+ }
+ else {
+ isyslog("got request from client (%ld bytes) %s", nTrans, request->Url().Path());
+
+ if (AuthorizationRequired()) {
+ if (request->Authorization()) {
+ char *url = request->Url().ToString();
+
+ for (EVER) {
+ //TODO: 1, check uri from request against uri from auth
+ if (strcmp(request->Authorization()->Uri(), url)) {
+ esyslog("ATTENTION - security attack! URI mismatch between request and authorization header!");
+ response = new cHTTPResponse(HTTP_BadRequest);
+ break;
+ }
+
+ //TODO: 2. search user/principal
+ cAuthorization *auth = Authorizations().FindAuthorization(request->Authorization()->Opaque());
+
+ if (!auth) {
+ response = new cHTTPAuthorizationRequest(Authorizations().CreateAuthorization(*request, client, connectionNumber));
+ esyslog("Huh? - didn't find a matching authorization, but client sent authorization header. Something went wrong!");
+ break;
+ }
+
+ //TODO: 3. check auth->principal->hash against hash from found principal
+ if (IsAuthorizationValid(auth, *request))
+ response = ProcessRequest(*request);
+ if (!response) {
+ //TODO: 406 or should we create a new authorization request?
+ response = new cHTTPResponse(HTTP_NotAcceptable);
+ }
+ break;
+ }
+ free(url);
+ }
+ else {
+ //TODO: create authorization request
+ response = new cHTTPAuthorizationRequest(Authorizations().CreateAuthorization(*request, client, connectionNumber));
+ }
+ }
+ else response = ProcessRequest(*request);
+ }
+ TransferResponse(response);
+ delete response;
+ delete request;
+ response = NULL;
+ request = NULL;
+ end = cTimeMs::Now();
+ isyslog("processing of request took %ld ms.", (end - start));
+
+ isyslog("check IO status ...");
+ if (!client.IOWait(500)) {
+ if (!StayConnected()) {
+ isyslog(" >>> connection timed out without any data <<<");
+ break; // leave temporary connections after timeout
+ }
+ }
+ }
+ Cancel();
+}
+
+void cConnectionHandler::Cancel(int WaitSeconds)
+{
+ dsyslog("Ok, lets close the client socket ...");
+ client.Close();
+ FREE(scratch);
+ FREE(nonce);
+}
+
+bool cConnectionHandler::IsAuthorizationValid(cAuthorization *ServerAuth, const cHTTPRequest &request)
+{
+ // Auth is the authorization based on opaque value from request->auth (session-ID)
+ // check other values from auth/request too
+ const cAuthorization *ClientAuth = request.Authorization();
+ const cPrincipal *principal = ServerAuth->Principal();
+
+ if (!principal || !strcmp(ServerAuth->UserID(), "unset"))
+ principal = Credentials.FindPrincipal(ClientAuth->UserID(), ClientAuth->Realm());
+
+ for (EVER) {
+ if (!principal) {
+ esyslog("username or realm is unknown");
+ break;
+ }
+ if (strcmp(ClientAuth->UserID(), principal->Name()) || strcmp(ClientAuth->Realm(), principal->Realm())) {
+ esyslog("username or realm did not match authenticated session");
+ break;
+ }
+ if (strcmp(principal->Hash(), ClientAuth->UserCredential())) {
+ esyslog("password given was invalid");
+ break;
+ }
+ cAuthorization *authCheck = new cAuthorization(principal, request.Method(), *ClientAuth);
+ const char *authHash = authCheck->CalculateResponse();
+
+ if (strcmp(authHash, ClientAuth->Response())) {
+ delete authCheck;
+ break;
+ }
+
+ if (strcmp(ServerAuth->UserID(), principal->Name())) {
+ // validation passed, so remember authorized user
+ ServerAuth->SetPrincipal(principal);
+ }
+ delete authCheck;
+
+ return true;
+ }
+ // validation of authorization failed, so remove any existing authorization from this session
+ Authorizations().Del(ServerAuth);
+
+ return false;
+}
+
+cHTTPResponse *cConnectionHandler::ProcessRequest(cHTTPRequest &Request)
+{
+ cHTTPResponse *res = NULL;
+
+ isyslog("ConnectionHandler::ProcessRequest: %s", Request.Url().Path());
+ if (!strcmp(Request.Url().Path(), "/stop")) {
+ ServerSocket().SetActive(false);
+ res = new cHTTPResponse(HTTP_Gone);
+ }
+ else if (!strcmp(Request.Url().Path(), "/favicon.ico")) {
+ res = new cHTTPFileResponse(config.AppIconPath());
+ }
+ else if (!strcmp(Request.Url().Path(), "/help")) {
+ cHTTPResponse *ir = new cHTTPResponse();
+
+ isyslog("start assembling usage message ...");
+ Usage(ir->StringBuilder());
+
+ ir->StringBuilder().Append("<hr>").Append(ir->ServerID()).Append(" ").Append(config.DocumentRoot());
+ isyslog("assembling of usage message done - let's send it to client ...");
+ ir->SetContentType("text/html");
+ ir->SetContentSize(ir->StringBuilder().Size());
+ res = ir;
+ }
+ else {
+ cHTTPRequestHandler *rh = registeredHandlers.Handler(Request.Url().Path());
+
+ if (rh) res = rh->ProcessRequest(Request);
+ if (!rh || !res) {
+ rh = registeredHandlers.DefaultHandler();
+
+ if (rh) res = rh->ProcessRequest(Request);
+ }
+ }
+ if (!res) res = new cHTTPResponse(HTTP_NotFound);
+
+ return res;
+}
+
+void cConnectionHandler::Usage(cStringBuilder& sb)
+{
+ cHTTPRequestHandlers::iterator it = registeredHandlers.begin();
+
+ isyslog("start of cConnectionHandler::Usage() ...");
+ sb.Append("<h2>Media server</h2><p>serves media files to remote/client media-players. Those ");
+ sb.Append("media-player should support the http-protocol. Opposed to well known http-servers, this ");
+ sb.Append("server handles multifile media transparently for the client.</p>");
+ sb.Append("<h3>supported requests:</h3>");
+ sb.Append("<dl>");
+
+ while (it != registeredHandlers.end()) {
+ sb.Append("<dt><br/><em>");
+ sb.Append(it->first.c_str());
+ sb.Append("</em></dt><dd>");
+ it->second->Usage(sb);
+ sb.Append("</dd>");
+ ++it;
+ }
+ if (registeredHandlers.DefaultHandler()) {
+ sb.Append("<dt><br/><em>");
+ sb.Append("default");
+ sb.Append("</em></dt><dd>");
+ registeredHandlers.DefaultHandler()->Usage(sb);
+ sb.Append("</dd>");
+ sb.Append("</dl>");
+ }
+ isyslog("end of cConnectionHandler::Usage() ...");
+}
+
+void cConnectionHandler::TransferResponse(cHTTPResponse *Response)
+{
+ if (!Response) {
+ esyslog("OUPS - should not happen!!! - Response was empty!");
+ close(client.Socket());
+
+ return;
+ }
+//#ifdef DEBUG
+// static int responseCounter = 0;
+// char filename[64] = {0};
+// sprintf(filename, "/tmp/rednose%03d", ++responseCounter);
+//#endif
+
+ memset(scratch, 0, bufSize);
+
+//#ifdef DEBUG
+// int fdClient = open(filename, O_WRONLY | O_CREAT);
+//#else
+ int fdClient = client.Socket();
+//#endif
+ isyslog("gonna sent message to client (Socket #%d)", fdClient);
+
+ int nRaw = Response->WritePrefix(scratch, bufSize);
+ int nTrans = send(fdClient, scratch, nRaw, MSG_NOSIGNAL);
+ size_t total = 0;
+
+ if (nTrans != nRaw) esyslog("ERROR: failed to transmit response header! (%d <> %d)", nRaw, nTrans);
+ while ((nRaw = Response->ReadContentChunk(scratch, bufSize)) > 0) {
+ int nTrans = send(fdClient, scratch, nRaw, MSG_NOSIGNAL);
+
+ if (nTrans < 1) {
+ esyslog("failed to transmit chunk. Error #%d", errno);
+ break;
+ }
+ total += nTrans;
+ if (nTrans == nRaw) isyslog("successfully written message chunk of %d bytes", nTrans);
+ else esyslog("failed to transmit response chunk (%d <> %d)", nRaw, nTrans);
+ }
+ if (total != Response->ContentSize())
+ esyslog("failed to transfer response - should be %ld, was %ld", Response->ContentSize(), total);
+//#ifdef DEBUG
+// close(fdClient);
+//#endif
+}
+
+void cConnectionHandler::RegisterDefaultHandler(cHTTPRequestHandler *DefaultHandler)
+{
+ registeredHandlers.SetDefaultHandler(DefaultHandler);
+}
+
+void cConnectionHandler::RegisterRequestHandler(const char *UrlPrefix, cHTTPRequestHandler *RequestHandler)
+{
+ registeredHandlers.SetHandler(UrlPrefix, RequestHandler);
+}
diff --git a/libs/networking/src/ConnectionPoint.cc b/libs/networking/src/ConnectionPoint.cc
new file mode 100644
index 0000000..4700652
--- /dev/null
+++ b/libs/networking/src/ConnectionPoint.cc
@@ -0,0 +1,89 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: ConnectionPoint.cc
+ * Created: 4. Juli 2012, 06:29
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <ConnectionPoint.h>
+#include <util.h>
+#include <poll.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+
+cConnectionPoint::cConnectionPoint(const char *NameOrIP, int Port, const char *RealName)
+ : nameOrIP(strdup(NameOrIP))
+ , realName(RealName ? strdup(RealName): NULL)
+ , combined(NULL)
+ , port(Port)
+ , sock(-1)
+{
+}
+
+cConnectionPoint::~cConnectionPoint()
+{
+ Close();
+}
+
+void cConnectionPoint::Close()
+{
+ if (sock > 0) {
+ close(sock);
+ sock = -1;
+ }
+ FREE(nameOrIP);
+ FREE(realName);
+ FREE(combined);
+}
+
+void cConnectionPoint::SetRealName(const char *Name)
+{
+ FREE(realName);
+ realName = Name ? strdup(Name) : NULL;
+}
+
+int cConnectionPoint::IOWait(long MilliSeconds)
+{
+ struct pollfd fds;
+
+ fds.fd = sock;
+ fds.events = POLLIN | POLLPRI;
+
+ int rv = poll(&fds, 1, MilliSeconds);
+
+ if (rv > 0) return fds.revents; // so app can ask for priority events
+ return rv;
+}
+
+const char *cConnectionPoint::AssembleCombined() const
+{
+ FREE(combined);
+ size_t len = strlen(nameOrIP) + 16;
+
+ if (realName) len += strlen(realName);
+ char *p = (char *)malloc(len);
+
+ if (realName) snprintf(p, len, "%s:%d (%s)", nameOrIP, port, realName);
+ else snprintf(p, len, "%s:%d", nameOrIP, port);
+ if (p) ((cConnectionPoint *)this)->combined = p;
+
+ return combined;
+}
diff --git a/libs/networking/src/Credentials.cc b/libs/networking/src/Credentials.cc
new file mode 100644
index 0000000..db1e219
--- /dev/null
+++ b/libs/networking/src/Credentials.cc
@@ -0,0 +1,171 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: Credentials.cc
+ * Created: 3. Juli 2012, 14:37
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <Credentials.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+cCredentials Credentials;
+static const char *appRealm = NULL;
+
+cCredentials::cCredentials()
+{
+}
+
+cCredentials::~cCredentials()
+{
+ Clear();
+}
+
+void cCredentials::Clear()
+{
+ iterator it = internalMap.begin();
+
+ while (it != internalMap.end()) {
+ delete it->second;
+ ++it;
+ }
+}
+
+const char *cCredentials::ApplicationRealm() const
+{
+ return appRealm;
+}
+
+const cPrincipal *cCredentials::FindPrincipal(const char* Name, const char* Realm)
+{
+ std::string tmp = Name;
+ cPrincipal *rv = internalMap[tmp];
+
+ if (!strcmp(rv->Name(), Name) && !strcmp(rv->Realm(), Realm)) return rv;
+ return NULL;
+}
+
+void cCredentials::SetApplicationRealm(const char* ApplicationRealm)
+{
+ appRealm = ApplicationRealm;
+}
+
+void cCredentials::Put(const char* Key, cPrincipal* p)
+{
+ internalMap[Key] = p;
+}
+
+cPrincipal *cCredentials::Get(const char* Key)
+{
+ iterator it = internalMap.find(Key);
+
+ if (it != internalMap.end()) return it->second;
+ return NULL;
+}
+
+int cCredentials::Load(const char *FileName)
+{
+ char buf[256];
+ FILE *fp = fopen(FileName, "r");
+ cPrincipal *principal = NULL;
+ char *chunk = buf;
+ int principalsProcessed = 0;
+ int bytesRead = 0;
+
+ if (!fp) {
+ //TODO: verbose message?
+ return 0;
+ }
+ while ((bytesRead = fread(chunk, sizeof(char), sizeof(buf) - (chunk - buf), fp)) > 0) {
+ char *endOfLine = index(buf, '\n');
+
+ while (chunk && endOfLine) {
+ principal = parsePrincipal(chunk, endOfLine - chunk);
+ if (!principal) break;
+ std::string userid = principal->Name();
+
+ internalMap[userid] = principal;
+ ++principalsProcessed;
+ chunk = endOfLine < (buf + sizeof(buf)) ? endOfLine + 1 : NULL;
+ endOfLine = index(chunk, '\n');
+ }
+ // shift rest of buffer down and read a smaller chunk
+ if (chunk && !endOfLine) {
+ int rest = sizeof(buf) - (chunk - buf);
+
+ memmove(buf, chunk, rest);
+ chunk = buf + rest;
+ }
+ }
+ fclose(fp);
+
+ return principalsProcessed;
+}
+
+cPrincipal *cCredentials::parsePrincipal(char* buf, size_t bufSize)
+///< format is: name:hash:age:extendedInfo
+{
+ if (!buf || !*buf || bufSize < 30) return NULL;
+ cPrincipal *rv = NULL;
+ char *hash = index(buf, ':') + 1;
+ char *age = index(hash, ':') + 1;
+ char *xi = index(age, ':') + 1;
+
+ if (hash == (char *)1 || !*hash) return NULL;
+ if (age == (char *)1 || !*age) return NULL;
+ if (xi == (char *)1 || !*xi) return NULL;
+
+ *(hash - 1) = 0;
+ *(age - 1) = 0;
+ *(xi - 1) = 0;
+ *(buf + bufSize - 1) = 0;
+
+ rv = new cPrincipal(buf, ApplicationRealm());
+ if (rv) {
+ rv->SetHash(hash);
+ rv->SetAge(atoi(age));
+ rv->SetExtendedInfo(xi);
+ }
+ return rv;
+}
+
+int cCredentials::Store(const char* FileName)
+{
+ FILE *fp = fopen(FileName, "w");
+ cPrincipal *p;
+ int principalsProcessed = 0;
+
+ if (!fp) {
+ //TODO: verbose message?
+ return 0;
+ }
+ cCredentials::const_iterator principals = begin();
+
+ while (principals != end()) {
+ p = principals->second;
+ fprintf(fp, "%s:%s:%d:%s\n", p->Name(), p->Hash(), p->Age(), p->ExtendedInfo() ? p->ExtendedInfo() : " ");
+ ++principalsProcessed;
+ ++principals;
+ }
+ fclose(fp);
+
+ return principalsProcessed;
+} \ No newline at end of file
diff --git a/libs/networking/src/HTTPAuthorizationRequest.cc b/libs/networking/src/HTTPAuthorizationRequest.cc
new file mode 100644
index 0000000..5c558f8
--- /dev/null
+++ b/libs/networking/src/HTTPAuthorizationRequest.cc
@@ -0,0 +1,47 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: HTTPAuthorizationRequest.cc
+ * Created: 4. Juli 2012, 07:41
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <HTTPAuthorizationRequest.h>
+#include <Authorization.h>
+#include <HTTPRequest.h>
+#include <stddef.h>
+
+cHTTPAuthorizationRequest::cHTTPAuthorizationRequest(const cHTTPRequest &OriginalRequest, char *NOnceFromHeap)
+ : cHTTPResponse(HTTP_UnAuthorized)
+{
+ SetHeader("Connection", "Close");
+ SetAuthorization(new cAuthorization(OriginalRequest, NOnceFromHeap ? strdup(NOnceFromHeap) : NULL));
+}
+
+cHTTPAuthorizationRequest::cHTTPAuthorizationRequest(const cAuthorization &Authorization)
+ : cHTTPResponse(HTTP_UnAuthorized)
+{
+ SetHeader("Connection", "Close");
+ SetAuthorization(new cAuthorization(Authorization));
+}
+
+cHTTPAuthorizationRequest::~cHTTPAuthorizationRequest()
+{
+}
+
diff --git a/libs/networking/src/HTTPFileResponse.cc b/libs/networking/src/HTTPFileResponse.cc
new file mode 100644
index 0000000..d79b085
--- /dev/null
+++ b/libs/networking/src/HTTPFileResponse.cc
@@ -0,0 +1,102 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: HTTPFileResponse.cc
+ * Created: 4. Juli 2012, 07:50
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <HTTPFileResponse.h>
+#include <Logging.h>
+#include <util.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+cHTTPFileResponse::cHTTPFileResponse(const char *RealPath)
+ : cHTTPResponse(HTTP_OK)
+ , realPath(RealPath)
+ , fd(0)
+{
+ DetermineTypeAndSize(realPath);
+}
+
+cHTTPFileResponse::cHTTPFileResponse()
+ : cHTTPResponse(HTTP_OK)
+ , realPath(NULL)
+ , fd(0)
+{
+}
+
+cHTTPFileResponse::~cHTTPFileResponse()
+{
+}
+
+void cHTTPFileResponse::DetermineTypeAndSize(const char* Path)
+{
+ struct stat st;
+ const char *xt;
+
+ if (!Path) return;
+ if (stat(Path, &st) < 0) {
+ esyslog("invalid path given - %d", errno);
+ return;
+ }
+ SetContentSize(st.st_size);
+ xt = strrchr(Path, '.');
+ if (!xt) SetContentType(MT_Unknown);
+ ++xt;
+
+ // File response serves only simple files, like tiny webservers
+ if (!strcasecmp("html", xt)) SetContentType(MT_Html);
+ else if (!strcasecmp("css", xt)) SetContentType(MT_CSS);
+ else if (!strcasecmp("js", xt)) SetContentType(MT_JavaScript);
+ else if (!strcasecmp("ico", xt)) SetContentType(MT_Ico);
+ else if (!strcasecmp("gif", xt)) SetContentType(MT_Gif);
+ else if (!strcasecmp("png", xt)) SetContentType(MT_Png);
+ else if (!strcasecmp("jpg", xt)) SetContentType(MT_Jpg);
+ else if (!strcasecmp("jpeg", xt)) SetContentType(MT_Jpg);
+ else if (!strcasecmp("dtd", xt)) SetContentType(MT_XmlDtd);
+ else if (!strcasecmp("xml", xt)) SetContentType(MT_Xml);
+}
+
+size_t cHTTPFileResponse::ReadContentChunk(char* Buf, size_t bufSize)
+{
+ long rv = 0;
+
+ if (fd < 1) {
+ fd = open(RealPath(), O_RDONLY | O_LARGEFILE);
+ if (fd < 1) {
+ esyslog("could not open requested path %s - Error #%d", RealPath(), errno);
+ return 0;
+ }
+ }
+ isyslog("have filehandle #%d (%s)", fd, RealPath());
+ if ((rv = read(fd, Buf, bufSize)) < 0)
+ esyslog("ERROR: failed to read from file %s #%d", RealPath(), errno);
+ else
+ isyslog("read %u bytes from file", rv);
+ if (rv < (long) bufSize) { // most probabely end of file
+ close(fd);
+ }
+ return rv;
+}
diff --git a/libs/networking/src/HTTPMessage.cc b/libs/networking/src/HTTPMessage.cc
new file mode 100644
index 0000000..9e1b5ba
--- /dev/null
+++ b/libs/networking/src/HTTPMessage.cc
@@ -0,0 +1,165 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: HTTPMessage.cc
+ * Created: 3. Juli 2012, 17:40
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <HTTPMessage.h>
+#include <Authorization.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <util.h>
+
+static void freeHeaderCallback(void *elem)
+{
+ free(elem);
+}
+
+cHTTPMessage::cHTTPMessage(HTTPProtocol Proto)
+ : protocol(Proto)
+ , timeStamp(0)
+ , header(freeHeaderCallback)
+ , auth(NULL)
+ , contentSize(0)
+ , contentType(NULL)
+{
+}
+
+cHTTPMessage::~cHTTPMessage()
+{
+ if (auth) delete auth;
+ FREE(contentType);
+}
+
+char *cHTTPMessage::FormatTime(time_t timeStamp)
+{
+ struct tm *t = gmtime(&timeStamp);
+ char buf[128];
+
+ strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", t);
+
+ return strdup(buf);
+}
+
+void cHTTPMessage::SetHeader(const char *Name, const char *Value)
+{
+ header.put(Name, Value ? strdup(Value) : NULL);
+}
+
+const char *cHTTPMessage::GetHeader(const char* Name) const
+{
+ return (const char *) header.get(Name);
+}
+
+size_t cHTTPMessage::WritePrefix(char *Buffer, size_t BufSize)
+///< writes message prefix to existing buffer. Prefix is the message without
+///< the content body, so internal and external content will be processed
+///< separately.
+///< IMPORTANT: content size and content type has to be set before this call!
+///< or this information needs to be added by custom call.
+{
+ cManagedMap::const_iterator it = Headers().begin();
+ size_t n = WriteFirstLine(Buffer, BufSize);
+
+ strcpy(Buffer + n, "Date: ");
+ n += 6;
+ n += WriteTime(Buffer + n, BufSize - n);
+ while (it != Headers().end()) {
+ n += snprintf(Buffer + n, BufSize - n, "%s: %s\r\n", it->first.c_str(), (const char *) it->second);
+ ++it;
+ }
+ if (auth) n += auth->Write(Buffer + n, BufSize - n);
+ if (ContentSize() > 0) {
+ n += snprintf(Buffer + n, BufSize - n, "Content-Type: %s\r\n", ContentType());
+ n += snprintf(Buffer + n, BufSize - n, "Content-Length: %ld\r\n", ContentSize());
+ if (n < BufSize - 3) {
+ Buffer[n++] = '\n'; // this is the separator between header and content!
+ Buffer[n] = 0;
+ }
+ }
+ return n;
+}
+
+int cHTTPMessage::WriteTime(char *Buffer, size_t BufSize)
+{
+ if (!timeStamp) timeStamp = time(NULL);
+ struct tm *t = gmtime(&timeStamp);
+
+ return strftime(Buffer, BufSize, "%a, %d %b %Y %H:%M:%S GMT\r\n", t);
+}
+
+void cHTTPMessage::Reset()
+{
+ header.clear();
+ contentSize = 0;
+}
+
+size_t cHTTPMessage::ContentSize() const
+{
+ return contentSize;
+}
+
+void cHTTPMessage::SetContentType(const char* ContentType)
+{
+ FREE(contentType);
+ contentType = ContentType ? strdup(ContentType) : NULL;
+}
+
+void cHTTPMessage::SetAuthorization(cAuthorization* Authorization)
+{
+ if (auth) {
+ delete auth;
+ auth = NULL;
+ }
+ auth = Authorization;
+}
+
+const char *cHTTPMessage::ProtocolString(void) {
+ switch (protocol) {
+ case HTTP_1_0: return "HTTP/1.0";
+ case HTTP_1_1: return "HTTP/1.1";
+ default: return "Unknown/Unsupported";
+ }
+}
+
+void cHTTPMessage::Dump(void )
+{
+ cManagedMap::const_iterator it = Headers().begin();
+ char buf[1024];
+
+ WriteFirstLine(buf, sizeof(buf));
+ printf("=========== Dump HTTP message ==============\n");
+ printf("Protocol: %s\n", ProtocolString());
+ printf("first line: %s\n", buf);
+ printf("header entries:\n");
+ while (it != Headers().end()) {
+ printf("\t[%s] ==> >|%s|<\n", it->first.c_str(), (const char *) it->second);
+ ++it;
+ }
+ if (auth) auth->Dump();
+ if (ContentSize()) {
+ printf("---------- content (%ld Bytes of type %s) -------------\n"
+ , ContentSize(), ContentType());
+ }
+ else printf(">>> NO Content!\n");
+ printf("========== End Dump HTTP message ===========\n");
+}
+
diff --git a/libs/networking/src/HTTPParser.cc b/libs/networking/src/HTTPParser.cc
new file mode 100644
index 0000000..2fc1dad
--- /dev/null
+++ b/libs/networking/src/HTTPParser.cc
@@ -0,0 +1,42 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: HTTPParser.cc
+ * Created: 10. Juli 2012, 08:37
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <HTTPParser.h>
+#include <HTTPRequest.h>
+#include <HTTPResponse.h>
+#include <string.h>
+
+cHTTPParser::cHTTPParser()
+{
+}
+
+cHTTPParser::~cHTTPParser()
+{
+}
+
+cHTTPMessage *cHTTPParser::ParseMessage(const char* MessageBuf)
+{
+ if (!strncmp("HTTP", MessageBuf, 4)) return new cHTTPResponse(MessageBuf);
+ return new cHTTPRequest(MessageBuf);
+}
diff --git a/libs/networking/src/HTTPRequest.cc b/libs/networking/src/HTTPRequest.cc
new file mode 100644
index 0000000..c17b29c
--- /dev/null
+++ b/libs/networking/src/HTTPRequest.cc
@@ -0,0 +1,171 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: HTTPRequest.cc
+ * Created: 3. Juli 2012, 17:54
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <HTTPRequest.h>
+#include <HTTPResponse.h>
+#include <Authorization.h>
+#include <Principal.h>
+#include <Logging.h>
+#include <util.h>
+#include <stdio.h>
+
+
+cHTTPRequest::cHTTPRequest(const char *MessageBuf)
+ : method(INVALID)
+ , url(NULL)
+{
+ ParseMessage(MessageBuf);
+}
+
+cHTTPRequest::cHTTPRequest(cHTTPRequest::HTTPRequestMethod Method, const char* Url)
+ : method(Method)
+ , url(Url)
+{
+}
+
+cHTTPRequest::cHTTPRequest(const cHTTPResponse &res, const char *Url)
+ : method(cHTTPRequest::GET)
+ , url(Url)
+{
+ SetAuthorization(new cAuthorization(*res.Authorization()));
+ Authorization()->SetUri(Url);
+}
+
+
+cHTTPRequest::~cHTTPRequest()
+{
+}
+
+const char *requestMethod2String(cHTTPRequest::HTTPRequestMethod Method)
+{
+ switch (Method) {
+ case cHTTPRequest::GET: return "GET";
+ case cHTTPRequest::POST: return "POST";
+ default: return "unknown/unsupported";
+ }
+}
+
+void cHTTPRequest::SetURL(const char *Url)
+{
+ url.ParseURL(Url);
+}
+
+const char *cHTTPRequest::ClientHost(void ) const
+{
+ return (const char *) Headers().get("Host");
+}
+
+const char *cHTTPRequest::UserAgent() const
+{
+ return (const char *) Headers().get("User-Agent");
+}
+
+void cHTTPRequest::SetUser(const char *UserID, const char *Password)
+{
+ char *uri = Url().ToString();
+ this->Authorization()->CalculateResponse(uri, UserID, Password);
+ free(uri);
+}
+
+static const char *fetchLine(char *Buf, int BufSize, const char *Source)
+{
+ const char *end = strchr(Source, '\r');
+ int len = 0;
+
+ if (!end) end = strchr(Source, '\n');
+ if (!end) end = Source + strlen(Source);
+ len = end - Source;
+ if (len > BufSize) len = BufSize - 1;
+
+ strncpy(Buf, Source, len);
+ Buf[len] = 0;
+ end = Source + len;
+
+ return end;
+}
+
+void cHTTPRequest::ParseMessage(const char *RequestBuf)
+{
+ char scratch[512];
+ const char *start = RequestBuf, *end = strchr(start, ' ');
+ const char *name;
+ char *value;
+
+ if (strncasecmp("GET", start, end - start)) SetMethod(GET);
+ else if (strncasecmp("POST", start, end - start)) SetMethod(POST);
+ start = end;
+ while (*start == ' ') ++start;
+ end = strchr(start, ' ');
+// printf("request-URI %*.*s\n", (int)(end - start), (int)(end - start), start);
+ if ((end - start) >= (int)sizeof(scratch)) {
+ esyslog("URI exhausted buffer - abort request!");
+ return;
+ }
+ else {
+ strncpy(scratch, start, end - start);
+ scratch[end - start] = 0;
+ SetURL(scratch);
+ }
+ start = end;
+ while (*start == ' ') ++start;
+ end = strchr(start, '\r');
+ if (!end) end = strchr(start, '\n');
+// printf("http-protocol %*.*s\n", (int)(end - start), (int)(end - start), start);
+ if (!strncmp("HTTP/1.1", start, end - start)) SetProtocol(HTTP_1_1);
+ else SetProtocol(HTTP_1_0);
+
+ for (start = end; start && *start; start = end) {
+ if (*start == '\r') ++start;
+ if (*start == '\n') ++start;
+ if (!*start) break;
+ end = fetchLine(scratch, sizeof(scratch), start);
+// printf("a line from request (%03d) [%s]\n", strlen(scratch), scratch);
+
+ name = scratch;
+ value = strchr((char *)name, ':');
+ if (value) {
+ if (value) *value++ = 0;
+ if (*value == ' ') *value++ = 0;
+
+ SetHeader(name, value);
+ }
+ else {
+// printf("possibly end of header ...\n");
+ continue;
+ }
+// printf("\nresult - name [%s] => |>%s<|\n", name, value);
+ }
+}
+
+size_t cHTTPRequest::WriteFirstLine(char* Buffer, size_t BufSize)
+{
+ size_t n = snprintf(Buffer, BufSize, "%s ", requestMethod2String(Method()));
+ int tmp = url.WriteBuf(Buffer + n, BufSize - n);
+
+ if (tmp > 0) n += tmp;
+ n += snprintf(Buffer + n, BufSize - n, " %s\n", ProtocolString());
+
+ return n;
+}
+
diff --git a/libs/networking/src/HTTPRequestHandler.cc b/libs/networking/src/HTTPRequestHandler.cc
new file mode 100644
index 0000000..60ce8d9
--- /dev/null
+++ b/libs/networking/src/HTTPRequestHandler.cc
@@ -0,0 +1,43 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: HTTPRequestHandler.cc
+ * Created: 4. Juli 2012, 15:12
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <HTTPRequestHandler.h>
+#include <string.h>
+#include <util.h>
+
+cHTTPRequestHandler::cHTTPRequestHandler()
+ : prefix(NULL)
+{
+}
+
+cHTTPRequestHandler::~cHTTPRequestHandler()
+{
+ FREE(prefix);
+}
+
+void cHTTPRequestHandler::SetID(const char* UrlPrefix)
+{
+ FREE(prefix);
+ prefix = UrlPrefix ? strdup(UrlPrefix) : NULL;
+} \ No newline at end of file
diff --git a/libs/networking/src/HTTPResponse.cc b/libs/networking/src/HTTPResponse.cc
new file mode 100644
index 0000000..2f0eeef
--- /dev/null
+++ b/libs/networking/src/HTTPResponse.cc
@@ -0,0 +1,183 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: HTTPResponse.cc
+ * Created: 4. Juli 2012, 06:03
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <HTTPResponse.h>
+#include <Authorization.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <util.h>
+
+static const char *serverID = NULL;
+cHTTPResponse::cHTTPResponse(HTTPStatusCode Status)
+ : status(Status)
+ , content(512)
+{
+ SetHeader("Server", serverID);
+ if (!(Status <= HTTP_OK /* || Status == HTTP_NoContent */ || Status == 304))
+ SetDefaultStatusBody(Status);
+}
+
+cHTTPResponse::cHTTPResponse(const char *Buffer)
+{
+ ParseMessage(Buffer);
+ if (Authorization()) Authorization()->SetServerID(ServerID());
+}
+
+cHTTPResponse::~cHTTPResponse()
+{
+}
+
+size_t cHTTPResponse::ContentSize() const
+{
+ if (content.Size()) return content.Size();
+ return cHTTPMessage::ContentSize();
+}
+
+const char * cHTTPResponse::ServerID(void)
+{
+ return serverID;
+}
+
+void cHTTPResponse::SetServerID(const char* ServerID)
+{
+ serverID = ServerID;
+}
+
+void cHTTPResponse::SetDefaultStatusBody(HTTPStatusCode Status)
+{
+ SetContentType(MT_Html);
+ content.Append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" "
+"\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\n"
+"<HTML>\n"
+" <HEAD>\n"
+" <TITLE>Error ");
+ content.Append(Status).Append("</TITLE>\n"
+" <META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">\n"
+" </HEAD>\n"
+" <BODY><H1>");
+ content.Append(Status).Append(" ").Append(httpStatus2Text(Status)).Append(".</H1></BODY>\n"
+"</HTML>\n");
+}
+
+size_t cHTTPResponse::ReadContentChunk(char* Buf, size_t bufSize)
+{
+ return content.Copy(Buf, bufSize);
+}
+
+size_t cHTTPResponse::WriteFirstLine(char* Buffer, size_t BufSize)
+{
+ return snprintf(Buffer, BufSize, "%s %d %s\n", ProtocolString(), status, httpStatus2Text(status));
+}
+
+//static const char *SampleRawResponse =
+//HTTP/1.1 401 Unauthorized
+//Date: Tue, 29 May 2012 03:54:02 GMT
+//Server: (null)
+//Connection: Close
+//WWW-Authenticate: Digest realm="validUsers@MyTestApp", nonce="dbe67572c707d25689a394ef7a9a9e8c", opaque="94c70a11bef0589719f190baf192bbcd", qop="auth"
+//Content-Type: text/html
+//Content-Length: 301
+
+//<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
+//<HTML>
+// <HEAD>
+// <TITLE>Error</TITLE>
+// <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-8859-1">
+// </HEAD>
+// <BODY><H1>401 Unauthorized.</H1></BODY>
+//</HTML>
+//
+void cHTTPResponse::ParseMessage(const char *RawMessage)
+{
+ char scratch[512];
+ const char *p = RawMessage;
+ char *tmp;
+ ParseState state = ExpectProtocol;
+ ParseResult status = Continue;
+
+ Reset();
+ while (status == Continue) {
+ memset(scratch, 0, sizeof(scratch));
+ switch (state) {
+ case ExpectStatus:
+ p = getWord(scratch, sizeof(scratch), p);
+ this->status = strtoHTTPStatus(scratch);
+ p = restOfLine(scratch, sizeof(scratch), p); // just read the rest of the line
+ state = ExpectHeader;
+ break;
+
+ case ExpectProtocol:
+ p = getWord(scratch, sizeof(scratch), p);
+ if (!strcmp(scratch, "HTTP/1.0")) {
+ SetProtocol(HTTP_1_0);
+ state = ExpectStatus;
+ }
+ else if (!strcmp(scratch, "HTTP/1.1")) {
+ SetProtocol(HTTP_1_1);
+ state = ExpectStatus;
+ }
+ else status = UnsupportedProtocol;
+ break;
+
+ case ExpectContent:
+ printf("should read %ld bytes from %10.10s\n", ContentSize(), p);
+ status = Done;
+ break;
+
+ case ExpectHeader:
+ p = getWord(scratch, sizeof(scratch), p);
+ if (!strncmp(p, "\n\n", 2)) {
+ p += 2;
+ state = ExpectContent;
+ break;
+ }
+ if (!strncmp(p, "\r\n\r\n", 4)) {
+ p += 4;
+ state = ExpectContent;
+ break;
+ }
+ tmp = scratch + strlen(scratch);
+ if (*(tmp -1) == ':') {
+ *(tmp -1) = 0;
+ tmp = strdup(scratch);
+ p = restOfLine(scratch, sizeof(scratch), p);
+ if (!strcasecmp(tmp, "WWW-Authenticate") || !strcasecmp(tmp, "Authorization-Info"))
+ SetAuthorization(new cAuthorization(cHTTPRequest::GET, scratch));
+ else if (!strcasecmp(tmp, "content-type"))
+ SetContentType(scratch);
+ else if (!strcasecmp(tmp, "content-length"))
+ SetContentSize(atol(scratch));
+ else SetHeader(tmp, scratch);
+ FREE(tmp);
+ }
+ else {
+ status = InvalidName;
+ }
+ break;
+
+ default: break;
+ } // end switch
+ } // end while (status == OK)
+}
+
diff --git a/libs/networking/src/HTTPServer.cc b/libs/networking/src/HTTPServer.cc
new file mode 100644
index 0000000..f435e2f
--- /dev/null
+++ b/libs/networking/src/HTTPServer.cc
@@ -0,0 +1,156 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: HTTPServer.cc
+ * Created: 4. Juli 2012, 12:16
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <HTTPServer.h>
+#include <HTTPResponse.h>
+#include <ConnectionHandler.h>
+#include <CondWait.h>
+#include <Mutex.h>
+#include <Logging.h>
+#include <util.h>
+#include <stdio.h>
+
+static volatile bool running = true;
+cHTTPServer::cHTTPServer(cServerConfig &Config)
+ : config(Config)
+#ifdef LIVE_CLEANUP
+ , cleaner(NULL)
+#endif
+{
+ threads = new std::vector<cThread *>();
+#ifdef LIVE_CLEANUP
+ cleaner = new cThread(cleanerThread, NULL, "cleanup thread stubs");
+#endif
+}
+
+cHTTPServer::~cHTTPServer()
+{
+#ifdef LIVE_CLEANUP
+ cleaner->Cancel(0);
+ delete cleaner;
+#else
+ Cleanup();
+#endif
+ delete threads;
+}
+
+void cHTTPServer::Action()
+{
+#ifdef LIVE_CLEANUP
+ if (cleaner) {
+ fprintf(stderr, "check size of thread pool ...\n");
+ if (!threads->empty()) fprintf(stderr, "thread pool has size of %lu\n", threads->size());
+ fprintf(stderr, "start cleaner thread\n");
+ cleaner->Start();
+ }
+#endif
+ fprintf(stderr, "startup server now\n");
+ while (Running()) {
+ cConnectionPoint *cp = ServerSocket().Accept();
+
+ if (cp) {
+ // this has to come after accept, as the hostnames can not be determined before an established connection.
+ if (!cHTTPResponse::ServerID()) {
+ char *id;
+
+ asprintf(&id, "HTTPServer %s:%d", ServerSocket().ThisSide()->RealName(), ServerSocket().Port());
+ cHTTPResponse::SetServerID(id);
+ }
+ cConnectionHandler *ch = new cConnectionHandler(*cp, config);
+
+ ch->Start();
+
+#ifdef LIVE_CLEANUP
+ cMutexLock poolSync(&poolMutex);
+#endif
+ threads->push_back(ch);
+ }
+ }
+}
+
+bool cHTTPServer::Running()
+{
+ return ServerSocket().Active();
+}
+
+#ifdef LIVE_CLEANUP
+int cHTTPServer::cleanerThread(void *opaque, cThread *ThreadContext)
+{
+ cHTTPServer *server = (cHTTPServer *)opaque;
+
+ server->Cleanup(ThreadContext);
+ return 0;
+}
+
+void cHTTPServer::Cleanup(cThread *ThreadContext)
+{
+ if (!ThreadContext) return;
+ cThread *p;
+
+ fprintf(stderr, "cleanup-Thread - gonna start loop ...\n");
+ while (ThreadContext->Running()) {
+ fprintf(stderr, "check list of possible threads ...\n");
+ if (!threads->empty()) {
+ fprintf(stderr, "thread pool is not empty ...\n");
+ for (size_t i=0; i < threads->size(); ++i) {
+ fprintf(stderr, "ok, got first entry to check ...\n");
+ p = (*threads)[i];
+ if (p->Active()) continue;
+
+ fprintf(stderr, "thread %lu looks dead ...\n", i);
+ isyslog("thread %lu is not active, so just clean it up", i);
+ p->Cancel();
+ delete p;
+ cMutexLock poolSync(&poolMutex);
+ threads->erase(threads->begin() + i);
+ break;
+ }
+ }
+ cCondWait::SleepMs(200);
+ }
+ ThreadContext->Cancel();
+}
+#else
+void cHTTPServer::Cleanup()
+{
+ cThread *t;
+
+ for (size_t i=0; i < threads->size(); ++i) {
+ t = (*threads)[i];
+ t->Cancel();
+ delete t;
+ }
+}
+#endif
+
+bool cHTTPServer::Start()
+{
+ Action();
+ return true;
+}
+
+void cHTTPServer::Stop()
+{
+ ServerSocket().SetActive(false);
+} \ No newline at end of file
diff --git a/libs/networking/src/HTTPStatus.cc b/libs/networking/src/HTTPStatus.cc
new file mode 100644
index 0000000..74623ed
--- /dev/null
+++ b/libs/networking/src/HTTPStatus.cc
@@ -0,0 +1,121 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: HTTPStatus.cc
+ * Created: 3. Juli 2012, 17:34
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <HTTPStatus.h>
+#include <stdlib.h>
+
+const char *httpStatus2Text(int Status)
+{
+ switch (Status) {
+ case HTTP_Continue: return "Continue";
+ case HTTP_SwitchProtocol: return "Gonna switch Protocol";
+ case HTTP_OK: return "Ok";
+ case HTTP_Created: return "Created";
+ case HTTP_Accepted: return "Accepted";
+ case HTTP_NoContent: return "No Content";
+ case HTTP_ResetContent: return "reset Content";
+ case HTTP_PartialContent: return "partial Content";
+ case HTTP_MultipleChoices: return "Multiple Choices";
+ case HTTP_MovedPermanently: return "Permanently moved";
+ case HTTP_Found: return "Found";
+ case HTTP_SeeOther: return "See other";
+ case HTTP_NotModified: return "Not modified";
+ case HTTP_UseProxy: return "Use Proxy";
+ case HTTP_MovedTemporarily: return "Temporarily moved";
+ case HTTP_BadRequest: return "Bad Request";
+ case HTTP_UnAuthorized: return "Unauthorized";
+ case HTTP_PaymentRequired: return "Payment required";
+ case HTTP_Forbidden: return "Forbidden";
+ case HTTP_NotFound: return "Not Found";
+ case HTTP_MethodNotAllowed: return "Not Allowed";
+ case HTTP_NotAcceptable: return "Not acceptable";
+ case HTTP_ProxyAuthenticationRequired: return "Authentication required";
+ case HTTP_RequestTimeout: return "Request timed out";
+ case HTTP_Conflict: return "Conflict";
+ case HTTP_Gone: return "Gone";
+ case HTTP_LengthRequired: return "Length required";
+ case HTTP_PreconditionFailed: return "Precondition failed";
+ case HTTP_RequestTooLarge: return "Request too large";
+ case HTTP_RequestURIToLong: return "Requested URI to long";
+ case HTTP_UnsupportedMediaType: return "Unsupported Media Type";
+ case HTTP_RequestRangeNotSatisfiable: return "Request Range Not satisfiable";
+ case HTTP_ExpectationFailed: return "Expectation failed";
+ case HTTP_InternalServerError: return "Internal Server Error";
+ case HTTP_NotImplemented: return "Not implemented";
+ case HTTP_BadGateway: return "Bad Gateway";
+ case HTTP_ServiceUnavailable: return "Service unavailable";
+ case HTTP_GatewayTimeout: return "Gateway timed out";
+ case HTTP_VersionNotSupported: return "Version not supported";
+ default: return "Unsupported/Unknown status";
+ }
+}
+
+HTTPStatusCode strtoHTTPStatus(const char *p)
+{
+ int tmp = atoi(p);
+
+ switch (tmp) {
+ case HTTP_Continue: return HTTP_Continue;
+ case HTTP_SwitchProtocol: return HTTP_SwitchProtocol;
+ case HTTP_OK: return HTTP_OK;
+ case HTTP_Created: return HTTP_Created;
+ case HTTP_Accepted: return HTTP_Accepted;
+ case HTTP_NoContent: return HTTP_NoContent;
+ case HTTP_ResetContent: return HTTP_ResetContent;
+ case HTTP_PartialContent: return HTTP_PartialContent;
+ case HTTP_MultipleChoices: return HTTP_MultipleChoices;
+ case HTTP_MovedPermanently: return HTTP_MovedPermanently;
+ case HTTP_Found: return HTTP_Found;
+ case HTTP_SeeOther: return HTTP_SeeOther;
+ case HTTP_NotModified: return HTTP_NotModified;
+ case HTTP_UseProxy: return HTTP_UseProxy;
+ case HTTP_MovedTemporarily: return HTTP_MovedTemporarily;
+ case HTTP_BadRequest: return HTTP_BadRequest;
+ case HTTP_UnAuthorized: return HTTP_UnAuthorized;
+ case HTTP_PaymentRequired: return HTTP_PaymentRequired;
+ case HTTP_Forbidden: return HTTP_Forbidden;
+ case HTTP_NotFound: return HTTP_NotFound;
+ case HTTP_MethodNotAllowed: return HTTP_MethodNotAllowed;
+ case HTTP_NotAcceptable: return HTTP_NotAcceptable;
+ case HTTP_ProxyAuthenticationRequired: return HTTP_ProxyAuthenticationRequired;
+ case HTTP_RequestTimeout: return HTTP_RequestTimeout;
+ case HTTP_Conflict: return HTTP_Conflict;
+ case HTTP_Gone: return HTTP_Gone;
+ case HTTP_LengthRequired: return HTTP_LengthRequired;
+ case HTTP_PreconditionFailed: return HTTP_PreconditionFailed;
+ case HTTP_RequestTooLarge: return HTTP_RequestTooLarge;
+ case HTTP_RequestURIToLong: return HTTP_RequestURIToLong;
+ case HTTP_UnsupportedMediaType: return HTTP_UnsupportedMediaType;
+ case HTTP_RequestRangeNotSatisfiable: return HTTP_RequestRangeNotSatisfiable;
+ case HTTP_ExpectationFailed: return HTTP_ExpectationFailed;
+ case HTTP_InternalServerError: return HTTP_InternalServerError;
+ case HTTP_NotImplemented: return HTTP_NotImplemented;
+ case HTTP_BadGateway: return HTTP_BadGateway;
+ case HTTP_ServiceUnavailable: return HTTP_ServiceUnavailable;
+ case HTTP_GatewayTimeout: return HTTP_GatewayTimeout;
+ case HTTP_VersionNotSupported: return HTTP_VersionNotSupported;
+ default: return HTTP_InternalServerError;
+ }
+}
+
diff --git a/libs/networking/src/Principal.cc b/libs/networking/src/Principal.cc
new file mode 100644
index 0000000..65103d9
--- /dev/null
+++ b/libs/networking/src/Principal.cc
@@ -0,0 +1,105 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: Principal.cc
+ * Created: 3. Juli 2012, 12:50
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <Principal.h>
+#include <MD5Calculator.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <util.h>
+
+cPrincipal::cPrincipal(const char *Name, const char *Realm)
+ : name(strdup(Name))
+ , realm(strdup(Realm))
+ , hash(NULL)
+ , xinfo(NULL)
+ , age(0)
+{
+}
+
+cPrincipal::cPrincipal(const cPrincipal& other)
+ : name(other.name ? strdup(other.name) : NULL)
+ , realm(other.realm ? strdup(other.realm) : NULL)
+ , hash(other.hash ? strdup(other.hash) : NULL)
+ , xinfo(other.xinfo ? strdup(other.xinfo) : NULL)
+ , age(other.age)
+{
+}
+
+cPrincipal::~cPrincipal()
+{
+ FREE(name);
+ FREE(realm);
+ FREE(hash);
+ FREE(xinfo);
+}
+
+void cPrincipal::CreateHash(const char* Password)
+{
+ cMD5Calculator hc;
+
+ FREE(hash);
+ hc.AddContent(name, strlen(name));
+ hc.AddContent(":", 1);
+ hc.AddContent(realm, strlen(realm));
+ hc.AddContent(":", 1);
+ hc.AddContent(Password, strlen(Password));
+
+ hash = hc.Hash();
+}
+
+void cPrincipal::SetName(const char* Name)
+{
+ FREE(name);
+ name = Name ? strdup(Name) : NULL;
+}
+
+void cPrincipal::SetRealm(const char* Realm)
+{
+ FREE(realm);
+ realm = Realm ? strdup(Realm) : NULL;
+}
+
+void cPrincipal::SetHash(const char* Hash)
+{
+ FREE(hash);
+ hash = Hash ? strdup(Hash) : NULL;
+}
+
+void cPrincipal::SetExtendedInfo(const char* Info)
+{
+ FREE(xinfo);
+ xinfo = Info ? strdup(Info) : NULL;
+}
+
+void cPrincipal::Dump(void) const
+{
+ printf(">>> ========= Principal ======================\n");
+ printf("name ....: >%s<\n", name);
+ printf("realm ...: >%s<\n", realm);
+ printf("hash ....: >%s<\n", hash);
+ printf("age .....: >%d<\n", age);
+ printf("ext. info: >%s<\n", xinfo);
+ printf("------------- Principal ------------------ <<<\n");
+}
diff --git a/libs/networking/src/ServerConfig.cc b/libs/networking/src/ServerConfig.cc
new file mode 100644
index 0000000..e6ad4b8
--- /dev/null
+++ b/libs/networking/src/ServerConfig.cc
@@ -0,0 +1,60 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: ServerConfig.cc
+ * Created: 8. Juli 2012, 06:12
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <ServerConfig.h>
+#include <Logging.h>
+#include <util.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+cServerConfig::cServerConfig(int Port)
+ : server(Port, 5)
+ , documentRoot(NULL)
+ , appIconPath(NULL)
+{
+}
+
+cServerConfig::~cServerConfig()
+{
+ FREE(appIconPath);
+ FREE(documentRoot);
+}
+
+void cServerConfig::SetAppIcon(const char* AppIcon)
+{
+ struct stat st;
+
+ if (!AppIcon) return;
+ if (!stat(AppIcon, &st)) {
+ FREE(appIconPath);
+ appIconPath = strdup(AppIcon);
+ }
+ else esyslog("ERROR: failed to stat application icon! %s", AppIcon);
+}
+
+void cServerConfig::SetDocumentRoot(const char* DocumentRoot)
+{
+ free(documentRoot);
+ documentRoot = DocumentRoot ? strdup(DocumentRoot) : NULL;
+} \ No newline at end of file
diff --git a/libs/networking/src/ServerSocket.cc b/libs/networking/src/ServerSocket.cc
new file mode 100644
index 0000000..2ff92fb
--- /dev/null
+++ b/libs/networking/src/ServerSocket.cc
@@ -0,0 +1,84 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: ServerSocket.cc
+ * Created: 4. Juli 2012, 07:28
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <ServerSocket.h>
+#include <Logging.h>
+#include <fcntl.h>
+
+cServerSocket::cServerSocket(int Port, int Queue)
+ : cAbstractSocket(Port, Queue)
+ , port(Port)
+ , active(true)
+{
+}
+
+cServerSocket::~cServerSocket()
+{
+}
+
+bool cServerSocket::Open(void)
+{
+ return cAbstractSocket::Open(port);
+}
+
+cConnectionPoint *cServerSocket::Accept(void)
+{
+ cConnectionPoint *rv = NULL;
+
+ while (active && !rv) {
+ rv = cAbstractSocket::Accept(port, 500);
+ }
+ return rv;
+}
+
+void cServerSocket::ConfigureSocket(int Socket)
+{
+ if (!ForceBlockingIO()) {
+ // make it non-blocking:
+ int oldflags = fcntl(Socket, F_GETFL, 0);
+
+ if (oldflags < 0) {
+ esyslog("could not retrieve old socket flags");
+
+ return;
+ }
+
+ oldflags |= O_NONBLOCK;
+ if (fcntl(Socket, F_SETFL, oldflags) < 0) {
+ esyslog("failed to set nonblocking state of socket");
+
+ return;
+ }
+ }
+}
+
+void cServerSocket::SetBlockingIO(bool ForceBlockingIO)
+{
+ cAbstractSocket::SetBlockingIO(ForceBlockingIO);
+}
+
+bool cServerSocket::ForceBlockingIO() const
+{
+ return cAbstractSocket::ForceBlockingIO();
+}
diff --git a/libs/networking/src/Url.cc b/libs/networking/src/Url.cc
new file mode 100644
index 0000000..011d493
--- /dev/null
+++ b/libs/networking/src/Url.cc
@@ -0,0 +1,229 @@
+/**
+ * ======================== legal notice ======================
+ *
+ * File: Url.cc
+ * Created: 4. Juli 2012, 05:42
+ * Author: <a href="mailto:geronimo013@gmx.de">Geronimo</a>
+ * Project: libnetworking: classes for tcp/ip sockets and http-protocol handling
+ *
+ * CMP - compound media player
+ *
+ * is a client/server mediaplayer intended to play any media from any workstation
+ * without the need to export or mount shares. cmps is an easy to use backend
+ * with a (ready to use) HTML-interface. Additionally the backend supports
+ * authentication via HTTP-digest authorization.
+ * cmpc is a client with vdr-like osd-menues.
+ *
+ * Copyright (c) 2012 Reinhard Mantey, some rights reserved!
+ * published under Creative Commons by-sa
+ * For details see http://creativecommons.org/licenses/by-sa/3.0/
+ *
+ * The cmp project's homepage is at http://projects.vdr-developer.org/projects/cmp
+ *
+ * --------------------------------------------------------------
+ */
+#include <Url.h>
+#include <Codec.h>
+#include <util.h>
+#ifdef DEBUG
+#include <iostream>
+#endif
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+
+static cURLEncoder * encoder = NULL;
+static cURLDecoder * decoder = NULL;
+
+cUrl::cUrl(const char* RawURL)
+ : path(NULL)
+{
+ ParseURL(RawURL);
+}
+
+cUrl::~cUrl()
+{
+ FREE(path);
+}
+
+cURLDecoder* cUrl::Decoder(void) const
+{
+ if (!decoder) decoder = new cURLDecoder();
+ return decoder;
+}
+
+cURLEncoder* cUrl::Encoder(void) const
+{
+ if (!encoder) encoder = new cURLEncoder();
+ return encoder;
+}
+
+const char *cUrl::Parameter(const char* Name)
+{
+ std::tr1::unordered_map<std::string, std::string>::iterator found = parameters.find(Name);
+ if (found != parameters.end()) return found->second.c_str();
+ return NULL;
+}
+
+void cUrl::SetParameter(const char* Name, const char* Value)
+{
+ std::string name = Name;
+ std::string value = Value ? Value : " ";
+ parameters[name] = value;
+}
+
+size_t cUrl::EstimatedSize(void ) const
+{
+ size_t rv = parameters.size() * 3;
+
+ if (path) rv += strlen(path) + 4;
+ ParameterMap::const_iterator pm = parameters.begin();
+
+ while (pm != parameters.end()) {
+ rv += pm->first.length() * 3;
+ rv += pm->second.length() * 3;
+ ++pm;
+ }
+ return rv;
+}
+
+void cUrl::ParseURL(const char *URL)
+{
+ FREE(path);
+ parameters.clear();
+ if (!URL) return;
+ const char *q = strchr(URL, '?'); // divider between url and querystring
+// char *realURL;
+// size_t l;
+
+ if (!q) q = URL + strlen(URL);
+// l = q - URL;
+// realURL = (char *)malloc(l + 2);
+// if (!realURL) return;
+// strncpy(realURL, URL, l);
+// realURL[l] = 0;
+ path = Decoder()->Decode(URL, q - URL);
+ if (*q) ParseQueryString(++q);
+}
+
+void cUrl::ParseQueryString(const char* QueryString)
+{
+ if (!(QueryString && *QueryString)) return;
+ const char *start, *last;
+ char *scratch = strdup(QueryString);
+ char *p, *end;
+ size_t srcLen = strlen(QueryString);
+
+ for (start = (const char *)scratch, end = (char *) start, last = scratch + srcLen; end && start < last; start = (const char *)++end) {
+ end = (char *) strchr(start, '&');
+ if (!end) end = (char *)start + strlen(start);
+ *end = 0;
+ p = (char *) strchr(start, '=');
+ if (p) {
+ *p++ = 0;
+ char *pn = p ? Decoder()->Decode(start) : NULL;
+ char *pv = p ? Decoder()->Decode(p) : NULL;
+
+ std::string name = pn;
+ std::string value = pv ? pv : " ";
+
+ parameters[name] = value;
+
+ free(pn);
+ free(pv);
+ }
+ else {
+ char *pn = Decoder()->Decode(start);
+
+ std::string name = pn;
+ parameters[name] = " ";
+ free(pn);
+ }
+ }
+ free(scratch);
+}
+
+char* cUrl::ToString(void) const
+///< returns the address of the newly allocated buffer
+{
+ size_t bufSize = EstimatedSize();
+ char *rv = (char *)malloc(bufSize);
+
+ if (!rv) return NULL;
+ int n = WriteBuf(rv, bufSize);
+
+ if (n < 0) {
+ bufSize += 128;
+ rv = (char *) realloc(rv, bufSize);
+ WriteBuf(rv, bufSize);
+ }
+ return rv;
+}
+
+int cUrl::WriteBuf(char* buf, size_t bufSize) const
+///< returns the characters written. -1 as return value indicates a buffer overrun.
+{
+ char *p, *tmp;
+ bool first = true;
+ int n = 0;
+
+ if (path) n += snprintf(buf + n, bufSize - n, "%s", path);
+ p = buf + n;
+ ParameterMap::const_iterator pm = parameters.begin();
+
+ while (pm != parameters.end()) {
+ tmp = Encoder()->Encode(pm->first.c_str());
+ if (p - buf + strlen(tmp) + 2 > bufSize)
+ return -1;
+ if (first) {
+ first = false;
+ *p++ = '?';
+ }
+ else *p++ = '&';
+ strcpy(p, tmp);
+ p += strlen(p);
+ FREE(tmp);
+
+ if (strcmp(pm->second.c_str(), " ")) {
+ tmp = Encoder()->Encode(pm->second.c_str());
+ if (p - buf + strlen(tmp) + 2 > bufSize)
+ return -1;
+ *p++ = '=';
+ strcpy(p, tmp);
+ p += strlen(p);
+ FREE(tmp);
+ }
+ ++pm;
+ }
+ p += strlen(p);
+
+ return p - buf;
+}
+
+#ifdef DEBUG
+void cUrl::Dump(void )
+{
+ ParameterMap::const_iterator pm = parameters.begin();
+
+ while (pm != parameters.end()) {
+ std::cout << "parameter [" << pm->first << "]";
+ if (strcmp(pm->second.c_str(), " "))
+ std::cout << " has value <|" << pm->second << "|>" << std::endl;
+ else
+ std::cout << " has NO value!" << std::endl;
+ ++pm;
+ }
+}
+#endif
+
+void cUrl::Cleanup(void )
+{
+ if (encoder) {
+ delete encoder;
+ encoder = NULL;
+ }
+ if (decoder) {
+ delete decoder;
+ decoder = NULL;
+ }
+} \ No newline at end of file