diff options
Diffstat (limited to 'libs/networking/src')
-rw-r--r-- | libs/networking/src/AbstractSocket.cc | 290 | ||||
-rw-r--r-- | libs/networking/src/Authorization.cc | 469 | ||||
-rw-r--r-- | libs/networking/src/ClientSocket.cc | 45 | ||||
-rw-r--r-- | libs/networking/src/ConnectionHandler.cc | 393 | ||||
-rw-r--r-- | libs/networking/src/ConnectionPoint.cc | 89 | ||||
-rw-r--r-- | libs/networking/src/Credentials.cc | 171 | ||||
-rw-r--r-- | libs/networking/src/HTTPAuthorizationRequest.cc | 47 | ||||
-rw-r--r-- | libs/networking/src/HTTPFileResponse.cc | 102 | ||||
-rw-r--r-- | libs/networking/src/HTTPMessage.cc | 165 | ||||
-rw-r--r-- | libs/networking/src/HTTPParser.cc | 42 | ||||
-rw-r--r-- | libs/networking/src/HTTPRequest.cc | 171 | ||||
-rw-r--r-- | libs/networking/src/HTTPRequestHandler.cc | 43 | ||||
-rw-r--r-- | libs/networking/src/HTTPResponse.cc | 183 | ||||
-rw-r--r-- | libs/networking/src/HTTPServer.cc | 156 | ||||
-rw-r--r-- | libs/networking/src/HTTPStatus.cc | 121 | ||||
-rw-r--r-- | libs/networking/src/Principal.cc | 105 | ||||
-rw-r--r-- | libs/networking/src/ServerConfig.cc | 60 | ||||
-rw-r--r-- | libs/networking/src/ServerSocket.cc | 84 | ||||
-rw-r--r-- | libs/networking/src/Url.cc | 229 |
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 |