diff --git a/CppNet b/CppNet deleted file mode 160000 index e0496e6..0000000 --- a/CppNet +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e0496e67ab90f6001daf22a77e77830b3c6ee357 diff --git a/src/client.cpp b/src/client.cpp index d96173c..f918f29 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -19,14 +19,14 @@ #include #include -#include "../CppNet/src/network/Socket.h" -#include "../CppNet/src/network/http/HttpRequest.h" -#include "../CppNet/src/network/http/HttpConnection.h" +#include "network/Socket.h" +#include "network/http/HttpRequest.h" +#include "network/http/HttpConnection.h" #include "necronda-server.h" -#include "../CppNet/src/network/http/HttpStatusCode.h" +#include "network/http/HttpStatusCode.h" #include "URI.h" #include "procopen.h" -#include "../CppNet/src/network/Address.h" +#include "network/Address.h" typedef struct { diff --git a/src/network/Address.cpp b/src/network/Address.cpp new file mode 100644 index 0000000..d283ea1 --- /dev/null +++ b/src/network/Address.cpp @@ -0,0 +1,68 @@ + + +#include +#include +#include +#include +#include "Address.h" + +using namespace std; + + +Address::Address() { + +} + +Address::Address(string addr) { + // TODO +} + +Address::Address(struct sockaddr_in *addr) { + address = ntohl(addr->sin_addr.s_addr); +} + + +struct sockaddr_in Address::toStruct(unsigned short port)const { + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(address); + addr.sin_port = htons(port); + return addr; +} + + + +string Address::toString() const { + struct sockaddr_in addr = toStruct(0); + struct in_addr ipAddr = addr.sin_addr; + return inet_ntoa(ipAddr); +} + +bool Address::isLocal() { + string a = toString(); + return a.find("127.0.0.") == 0; +} + + +ostream& operator<<(ostream &str, const Address &addr) { + return str << addr.toString(); +} + +string operator+(string &str, const Address &addr) { + return str + addr.toString(); +} + +string operator+(string &str, const Address *addr) { + return str + addr->toString(); +} + +string operator+(const Address &addr, string &str) { + return addr.toString() + str; +} + +string operator+(const Address *addr, string &str) { + return addr->toString() + str; +} + + + diff --git a/src/network/Address.h b/src/network/Address.h new file mode 100644 index 0000000..4583b5c --- /dev/null +++ b/src/network/Address.h @@ -0,0 +1,31 @@ +/** + * Necronda Web Server 3.0 + * HttpHeader.h - HttpHeader Class definition + * Lorenz Stechauner, 2018-05-09 + */ + +#ifndef NECRONDA_ADDRESS +#define NECRONDA_ADDRESS + +using namespace std; + +class Address { +private: + unsigned int address; + +public: + Address(); + + explicit Address(string address); + + explicit Address(struct sockaddr_in *address); + + struct sockaddr_in toStruct(unsigned short port) const; + + string toString() const; + + bool isLocal(); + +}; + +#endif diff --git a/src/network/Socket.cpp b/src/network/Socket.cpp new file mode 100644 index 0000000..19dfc75 --- /dev/null +++ b/src/network/Socket.cpp @@ -0,0 +1,644 @@ +/** + * Necronda Web Server 3.0 + * Socket.cpp - Socket Class methods + * Lorenz Stechauner, 2018-05-09 + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Address.h" +#include "Socket.h" +#include "http/Http.h" + +using namespace std; + + +static void multi_ssl_init() { + SSL_load_error_strings(); + SSL_library_init(); + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); +} + + +char *multi_ssl_get_error(SSL *ssl, int ret) { + if (ret > 0) { + return nullptr; + } + + unsigned long ret2 = ERR_get_error(); + const char *err2 = strerror(errno); + const char *err1 = ERR_reason_error_string(ret2); + + switch (SSL_get_error(ssl, ret)) { + case SSL_ERROR_NONE: + return (char *) "none"; + case SSL_ERROR_ZERO_RETURN: + return (char *) "closed"; + case SSL_ERROR_WANT_READ: + return (char *) "want_read"; + case SSL_ERROR_WANT_WRITE: + return (char *) "want_write"; + case SSL_ERROR_WANT_CONNECT: + return (char *) "want_connect"; + case SSL_ERROR_WANT_ACCEPT: + return (char *) "want_accept"; + case SSL_ERROR_WANT_X509_LOOKUP: + return (char *) "want_x509_lookup"; + case SSL_ERROR_SYSCALL: + return (char *) ((ret2 == 0) ? (ret == 0) ? "protocol violation" : err2 : err1); + case SSL_ERROR_SSL: + return (char *) err1; + default: + return (char *) "unknown error"; + } +} + +char *strerror_socket(int nr) { + if (nr == EAGAIN || nr == EWOULDBLOCK) { + return (char *) "timeout"; + } else if (nr == ECONNRESET) { + return (char *) "closed"; + } else { + return strerror(nr); + } +} + + +Socket::Socket(int fd) { + this->fd = fd; + microsStart = getMicros(); + microsLast = microsStart; + bytesSent = 0; + bytesReceived = 0; + enc = false; + ssl = nullptr; + ctx = nullptr; + clients = false; + servers = false; +} + +Socket::Socket() { + fd = ::socket(AF_INET, SOCK_STREAM, 0); + if (fd == 0) { + throw strerror(errno); + } + enc = false; + microsStart = getMicros(); + microsLast = microsStart; + bytesSent = 0; + bytesReceived = 0; + ssl = nullptr; + ctx = nullptr; + clients = false; + servers = false; +} + +void Socket::setSocketOption(int option, bool value = true) { + int val = value ? 1 : 0; + + if (::setsockopt(fd, SOL_SOCKET, option, &val, sizeof(val)) != 0) { + throw strerror(errno); + } +} + +void Socket::bind(Address *address, unsigned short port) { + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; // address. + addr.sin_port = htons(port); + + if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0) { + throw strerror(errno); + } +} + +void Socket::bind(unsigned short port) { + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + + if (::bind(fd, (struct sockaddr *) &addr, sizeof(addr)) != 0) { + throw strerror(errno); + } +} + +void Socket::listen(int num) { + if (::listen(fd, num) != 0) { + throw strerror(errno); + } +} + +void Socket::connect(Address, unsigned short) { + +} + +Socket* Socket::accept() { + int newfd = ::accept(fd, nullptr, nullptr); + if (newfd < 0) { + throw strerror(errno); + } + Socket *socket = new Socket(newfd); + socket->servers = true; + return socket; +} + +void Socket::close() { + if (isSecured()) { + //SSL_shutdown(ssl); + SSL_free(ssl); + SSL_CTX_free(ctx); + } + + if (::close(fd) != 0) { + throw strerror(errno); + } +} + +Address *Socket::getPeerAddress() const { + struct sockaddr_storage addr; + socklen_t len = sizeof(addr); + getpeername(fd, (struct sockaddr *) &addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *) &addr; + return new Address(s); +} + +unsigned short Socket::getPeerPort() const { + struct sockaddr_storage addr; + socklen_t len = sizeof(addr); + getpeername(fd, (struct sockaddr *) &addr, &len); + return ntohs(((struct sockaddr_in *) &addr)->sin_port); +} + +Address *Socket::getSocketAddress() const { + struct sockaddr_storage addr; + socklen_t len = sizeof(addr); + getsockname(fd, (struct sockaddr *) &addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *) &addr; + return new Address(s); +} + +unsigned short Socket::getSocketPort() const { + struct sockaddr_storage addr; + socklen_t len = sizeof(addr); + getsockname(fd, (struct sockaddr *) &addr, &len); + return ntohs(((struct sockaddr_in *) &addr)->sin_port); +} + + +void Socket::setReuseAddress(bool value) { + setSocketOption(SO_REUSEADDR, value); +} + +void Socket::setReusePort(bool value) { + setSocketOption(SO_REUSEPORT, value); +} + + +string Socket::toString() const { + return "{[Socket]" + getSocketAddress()->toString() + ":" + to_string(getSocketPort()) + "<->" + + getPeerAddress()->toString() + ":" + to_string(getPeerPort()) + "}"; +} + +long Socket::send(string *str) { + return send(str->c_str(), str->length()); +} + +long Socket::send(string str) { + return send(str.c_str(), str.length()); +} + +long Socket::send(const char *str, long length) { + return send((void*) str, length); +} + +long Socket::send(const char *str) { + return send(str, strlen(str)); +} + +Socket::~Socket() { + +} + +long Socket::receive(void *buffer, int size) { + long len; + if (isSecured()) { + len = SSL_read(ssl, buffer, size); + if (len < 0) { + throw multi_ssl_get_error(ssl, (int) len); + } + } else { + len = recv(fd, buffer, (size_t) size, 0); + if (len < 0) { + throw strerror_socket(errno); + } + } + bytesReceived += len; + return len; +} + +long Socket::peek(void *buffer, int size) { + long len; + if (isSecured()) { + len = SSL_peek(ssl, buffer, size); + if (len < 0) { + throw multi_ssl_get_error(ssl, (int) len); + } + } else { + len = recv(fd, buffer, (size_t) size, MSG_PEEK); + if (len < 0) { + throw strerror_socket(errno); + } + } + return len; +} + +long Socket::send(void *buffer, int size) { + long len; + if (isSecured()) { + if (size != 0) { + len = SSL_write(ssl, buffer, size); + if (len <= 0) { + throw multi_ssl_get_error(ssl, (int) len); + } + } else { + len = 0; + } + } else { + len = ::send(fd, buffer, (size_t) size, 0); + if (len < 0) { + throw strerror_socket(errno); + } + } + bytesSent += len; + return len; +} + + +string Socket::receive() { + string *str = new string(); + + char buffer[CPPNET_CHUNK]; + long len = 0; + do { + len = receive((void*) buffer, CPPNET_CHUNK); + str->append(buffer, (unsigned) len); + } while (len > 0 && len == CPPNET_CHUNK); + + return *str; +} + +string Socket::receive(long length) { + string *str = new string(); + + char buffer[CPPNET_CHUNK]; + long len = 0; + long reclen = 0; + do { + len = receive((void*) buffer, CPPNET_CHUNK); + reclen += len; + str->append(buffer, (unsigned) len); + } while (reclen < length); + + return *str; +} + +string Socket::receive(string until) { + string *str = new string(); + + struct pollfd ufds[1]; + ufds[0].fd = fd; + ufds[0].events = POLLIN | POLLOUT; + + char buffer[CPPNET_CHUNK]; + long len = 0; + do { + len = peek((void*) buffer, CPPNET_CHUNK); + if (len != 0) { + string s = string(buffer, (size_t) len); + size_t found = s.find(until); + long l = (found != string::npos) ? found + 1 : len; + long l2 = (found != string::npos) ? found : len; + str->append(buffer, (unsigned) l2); + receive((void *) buffer, (int) l); + if (found != string::npos) { + break; + } + } + if (poll(ufds, 1, 0) < 0) { + throw strerror_socket(errno); + } else if ((ufds[0].revents & POLLIN) == 0) { + if ((ufds[0].revents & POLLOUT) != 0) { + throw (char *) "error"; + } else { + throw (char *) "want_write"; + } + } else if ((ufds[0].revents & POLLERR) != 0) { + throw (char *) "error"; + } else if (ufds[0].revents & (POLLRDHUP | POLLHUP | POLLNVAL) != 0) { + throw (char *) "closed"; + } + } while (true); + + return *str; +} + +string Socket::receive(const char *until) { + return receive(until, (int) (strlen(until))); +} + +string Socket::receive(const char *until, unsigned long strlen) { + return receive(string(until, strlen)); +} + +void Socket::receive(FILE *file) { + char buffer[CPPNET_CHUNK]; + long len = 0; + do { + len = receive((void*) buffer, CPPNET_CHUNK); + fwrite(buffer, 1, CPPNET_CHUNK, file); + } while (len > 0 && len == CPPNET_CHUNK); +} + +void Socket::receive(FILE *file, long size) { + char buffer[CPPNET_CHUNK]; + long len = 0; + long rec = 0; + do { + if (size - rec == 0) { + break; + } + len = receive((void*) buffer, (CPPNET_CHUNK > (size - rec) && size >= 0)?(size - rec):CPPNET_CHUNK); + fwrite(buffer, 1, CPPNET_CHUNK, file); + rec += len; + } while (len > 0); +} + +string Socket::receiveLine() { + string str = receive("\n"); + if (str.length() > 0 && str.at(str.length() - 1) == '\r') { + str = str.substr(0, str.length() - 1); + } + return str; +} + + +long Socket::getDuration() { + return getMicros() - microsStart; +} + + +void Socket::setReceiveTimeout(unsigned long ms) { + struct timeval timeout; + if (ms == 0) { + timeout.tv_sec = 0; + timeout.tv_usec = 1; + } else { + timeout.tv_sec = ms / 1000; + timeout.tv_usec = (ms % 1000) * 1000; + } + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(timeout)) < 0) { + throw strerror(errno); + } +} + +void Socket::setSendTimeout(unsigned long ms) { + struct timeval timeout; + if (ms == 0) { + timeout.tv_sec = 0; + timeout.tv_usec = 1; + } else { + timeout.tv_sec = ms / 1000; + timeout.tv_usec = (ms % 1000) * 1000; + } + if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (char *) &timeout, sizeof(timeout)) < 0) { + throw strerror(errno); + } +} + +bool Socket::isServerSide() { + return servers; +} + +bool Socket::isSecured() { + return enc; +} + +bool Socket::isClientSide() { + return clients; +} + +void Socket::sslHandshake(map sni) { + /*if (isSecured()) { + throw (char *) "Socket already secured"; + } + + const SSL_METHOD *method; + if (isServerSide()) { + method = TLSv1_2_server_method(); + } else if (isClientSide()) { + method = TLSv1_2_client_method(); + } else { + method = TLSv1_2_method(); + } + + SSL_CTX *ctx = SSL_CTX_new(method); + SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4"); + SSL_CTX_set_ecdh_auto(ctx, 1); + + const char *certfile = keypair.fullchain.c_str(); + const char *keyfile = keypair.privkey.c_str(); + + if (isServerSide()) { + if (SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM) != 1) { + throw (char *) ERR_reason_error_string(ERR_get_error()); + } + + if (SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) != 1) { + throw (char *) ERR_reason_error_string(ERR_get_error()); + } + } + + SSL_CTX_set_tlsext_servername_callback + + this->ctx = ctx; + this->ssl = SSL_new(ctx); + SSL_set_fd(ssl, fd); + enc = true; + + while (true) { + int ret = 0; + if (isServerSide()) { + ret = SSL_accept(ssl); + } else if (isClientSide()) { + ret = SSL_connect(ssl); + } else { + ret = SSL_do_handshake(ssl); + } + + if (ret <= 0 && ((isServerSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_READ) || + (isClientSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_WRITE))) { + throw multi_ssl_get_error(ssl, ret); + } else if (ret == 1) { + break; + } + }*/ + +} + +void Socket::sslHandshake() { + sslHandshake(KeyPair{"", ""}); +} + +void Socket::sslHandshake(KeyPair keypair) { + if (isSecured()) { + throw (char *) "Socket already secured"; + } + + const SSL_METHOD *method; + if (isServerSide()) { + method = TLSv1_2_server_method(); + } else if (isClientSide()) { + method = TLSv1_2_client_method(); + } else { + method = TLSv1_2_method(); + } + + SSL_CTX *ctx = SSL_CTX_new(method); + SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE); + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, nullptr); + SSL_CTX_set_min_proto_version(ctx, SSL3_VERSION); // TLS1_VERSION + SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_cipher_list(ctx, "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4"); + SSL_CTX_set_ecdh_auto(ctx, 1); + + const char *certfile = keypair.fullchain.c_str(); + const char *keyfile = keypair.privkey.c_str(); + + if (isServerSide()) { + if (SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM) != 1) { + throw (char *) ERR_reason_error_string(ERR_get_error()); + } + + if (SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) != 1) { + throw (char *) ERR_reason_error_string(ERR_get_error()); + } + } + + this->ctx = ctx; + this->ssl = SSL_new(ctx); + SSL_set_fd(ssl, fd); + enc = true; + + while (true) { + int ret = 0; + if (isServerSide()) { + ret = SSL_accept(ssl); + } else if (isClientSide()) { + ret = SSL_connect(ssl); + } else { + ret = SSL_do_handshake(ssl); + } + + if (ret <= 0 && ((isServerSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_READ) || + (isClientSide() && SSL_get_error(ssl, ret) != SSL_ERROR_WANT_WRITE))) { + throw multi_ssl_get_error(ssl, ret); + } else if (ret == 1) { + break; + } + } + +} + +void Socket::sslHandshake(string privkey, string fullchain) { + sslHandshake(KeyPair{std::move(privkey), std::move(fullchain)}); +} + +long Socket::select(list read, list write, long millis) { + fd_set readfd, writefd; + int maxfd = 0; + FD_ZERO(&readfd); + FD_ZERO(&writefd); + + for (Socket s : read) { + if (s.fd > maxfd) { + maxfd = s.fd; + } + FD_SET(s.fd, &readfd); + } + + for (Socket s : write) { + if (s.fd > maxfd) { + maxfd = s.fd; + } + FD_SET(s.fd, &writefd); + } + + struct timeval *tv = new struct timeval; + if (millis < 0) { + tv = nullptr; + } else if (millis == 0) { + tv->tv_sec = 0; + tv->tv_usec = 1; + } else { + tv->tv_sec = millis / 1000; + tv->tv_usec = (millis % 1000) * 1000; + } + + int ret = ::select(maxfd + 1, &readfd, &writefd, nullptr, tv); + if (ret < 0) { + throw (char *) strerror(errno); + } + return ret; +} + +long Socket::select(list read, list write) { + Socket::select(std::move(read), std::move(write), -1); +} + +unsigned long Socket::getBytesSent() { + return bytesSent; +} + +unsigned long Socket::getBytesReceived() { + return bytesReceived; +} + + +ostream &operator<<(ostream &str, const Socket &socket) { + return str << socket.toString(); +} + +ostream &operator<<(ostream &str, const Socket *socket) { + return str << socket->toString(); +} + +string operator+(string &str, const Socket &socket) { + return str + socket.toString(); +} + +string operator+(const Socket &socket, string &str) { + return socket.toString() + str; +} + + + + diff --git a/src/network/Socket.h b/src/network/Socket.h new file mode 100644 index 0000000..375ff4b --- /dev/null +++ b/src/network/Socket.h @@ -0,0 +1,170 @@ +/** + * Necronda Web Server 3.0 + * Socket.h - Socket Class definition + * Lorenz Stechauner, 2018-05-09 + */ + +#ifndef NECRONDA_SOCKET +#define NECRONDA_SOCKET + +#include + +#define CPPNET_CHUNK 16384 + +typedef struct { + string privkey; + string fullchain; +} KeyPair; + +using namespace std; + + +class Socket { +private: + int fd; + SSL *ssl; + SSL_CTX *ctx; + bool enc; + bool servers; + bool clients; + unsigned long bytesSent; + unsigned long bytesReceived; + long microsStart; + long microsLast; + + void setSocketOption(int, bool); + + long send(void *buffer, int size); + + long receive(void *buffer, int size); + + long peek(void *buffer, int size); + +public: + Socket(); + + explicit Socket(int filedescriptor); + + ~Socket(); + + void bind(Address *address, unsigned short port); + + void bind(unsigned short port); + + void listen(int count = 1); + + void connect(Address address, unsigned short port); + + Socket* accept(); + + void sslHandshake(); + + void sslHandshake(map sni); + + void sslHandshake(KeyPair keypair); + + void sslHandshake(string privkey, string fullchain); + + long send(string *str); + + long send(string str); + + long send(const char *str); + + long send(const char *str, long length); + + string receive(); + + string receive(long length); + + string receive(string until); + + string receive(const char *until, unsigned long strlen); + + string receive(const char *until); + + void receive(FILE *file); + + string receiveLine(); + + void shutdown(); + + void close(); + + long getDuration(); + + Address *getSocketAddress() const; + + unsigned short getSocketPort() const; + + Address *getPeerAddress() const; + + unsigned short getPeerPort() const; + + string toString() const; + + + bool isServerSide(); + + bool isClientSide(); + + bool isSecured(); + + + void setReuseAddress(bool value = true); + + void setReusePort(bool value = true); + + void setSendBufferSize(int value); + + void setReceiveBufferSize(int value); + + void setMinReceiveBytes(int value); + + void setMinSendBytes(int value); + + void setSendTimeout(unsigned long ms); + + void setReceiveTimeout(unsigned long ms); + + + bool getReuseAddress(); + + bool getReusePort(); + + int getSendBufferSize(); + + int getReceiveBufferSize(); + + int getMinReceiveBytes(); + + int getMinSendBytes(); + + long getSendTimeout(); + + long getReceiveTimeout(); + + unsigned long getBytesSent(); + + unsigned long getBytesReceived(); + + static long select(list read, list write, long millis); + + static long select(list read, list write); + + void receive(FILE *file, long size); +}; + +Socket operator<<(Socket sock, const char *str); + +Socket operator<<(Socket sock, string str); + +ostream &operator<<(ostream &str, const Socket &socket); + +ostream &operator<<(ostream &str, const Socket *socket); + +string operator+(string &str, const Socket &socket); + +string operator+(const Socket &socket, string &str); + +#endif diff --git a/src/network/http/Http.cpp b/src/network/http/Http.cpp new file mode 100644 index 0000000..c28211f --- /dev/null +++ b/src/network/http/Http.cpp @@ -0,0 +1,32 @@ +// +// Created by lorenz on 7/10/18. +// + +#include +#include +#include "Http.h" + +unsigned long getMicros() { + struct timeval tv; + gettimeofday(&tv, nullptr); + return (unsigned long) (1000000 * tv.tv_sec + tv.tv_usec); +} + +string getHttpDate() { + time_t rawtime; + time(&rawtime); + return getHttpDate(rawtime); +} + +string getHttpDate(string filename) { + struct stat attrib; + stat(filename.c_str(), &attrib); + return getHttpDate(attrib.st_ctime); +} + +string getHttpDate(time_t time) { + char buffer[64]; + struct tm *timeinfo = gmtime(&time); + strftime(buffer, sizeof(buffer), "%a, %d %b %Y %H:%M:%S GMT", timeinfo); + return string(buffer); +} diff --git a/src/network/http/Http.h b/src/network/http/Http.h new file mode 100644 index 0000000..4f5f238 --- /dev/null +++ b/src/network/http/Http.h @@ -0,0 +1,22 @@ +// +// Created by lorenz on 7/10/18. +// + +#ifndef CPPNET_HTTP_H +#define CPPNET_HTTP_H + +#include +#include + +using namespace std; + +unsigned long getMicros(); + +string getHttpDate(time_t time); + +string getHttpDate(); + +string getHttpDate(string filename); + + +#endif //CPPNET_HTTP_H diff --git a/src/network/http/HttpConnection.cpp b/src/network/http/HttpConnection.cpp new file mode 100644 index 0000000..f739221 --- /dev/null +++ b/src/network/http/HttpConnection.cpp @@ -0,0 +1,194 @@ + + +#include +#include +#include +#include +#include "HttpConnection.h" +#include "../Socket.h" +#include "HttpStatusCode.h" +#include "Http.h" + +HttpConnection::HttpConnection(Socket *socket) { + this->socket = socket; + this->request = new HttpRequest(socket); + this->response = new HttpResponse(); + microsStart = getMicros(); + response->setVersion("1.1"); + response->setField("Server", "Necronda/3.0"); +} + +void HttpConnection::respond(int statuscode) { + if (statuscode >= 400 && statuscode < 600) { + respond(statuscode, + "" + to_string(statuscode) + " " + + ::getStatusCode(statuscode).message + + "

" + to_string(statuscode) + " " + + ::getStatusCode(statuscode).message + + "

" + + ((request->isExistingField("Host")) ? + (request->isExistingField("Referer") && + request->getField("Referer").find(request->getField("Host")) != string::npos) ? + "

Go back to the last page you visited: getField("Referer") + "\">" + + request->getField("Referer") + "

" : + "

Go back to the home page of getField("Host") + "/\">" + + request->getField("Host") + + "

" : "") + "
\r\n" + ); + } else { + respond(statuscode, ""); + } +} + +void HttpConnection::respond(int statuscode, string payload) { + response->setStatusCode(statuscode); + response->setField("Date", getHttpDate()); + response->setField("Content-Length", to_string(payload.length())); + response->sendHeader(socket); + socket->send(std::move(payload)); +} + +void HttpConnection::respond(int statuscode, FILE *file, bool compress, long start, long end) { + response->setStatusCode(statuscode); + response->setField("Transfer-Encoding", "chunked"); + response->setField("Date", getHttpDate()); + + long shouldTransfer; + long transfered = 0; + + fseek(file, 0, SEEK_END); + long len = ftell(file); + + if (start != -1 && end != -1) { + fseek(file, start, SEEK_SET); + response->setField("Content-Length", to_string(end - start + 1)); + shouldTransfer = end - start + 1; + compress = false; + } else { + fseek(file, 0, SEEK_SET); + shouldTransfer = len; + if (len >= 0 && !compress) { + response->setField("Content-Length", to_string(len)); + } + } + + if (compress) { + response->setField("Content-Encoding", "deflate"); + } + + response->sendHeader(socket); + + if (compress) { + int level = 1; + int ret, flush; + unsigned have; + z_stream strm; + unsigned char in[CPPNET_CHUNK]; + unsigned char out[CPPNET_CHUNK]; + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + ret = deflateInit(&strm, level); + if (ret != Z_OK) { + throw (char *) "Unable to open file"; + } + + do { + strm.avail_in = (uInt) fread(in, 1, CPPNET_CHUNK, file); + + if (ferror(file)) { + (void) deflateEnd(&strm); + throw (char *) strerror(errno); + } + flush = feof(file) ? Z_FINISH : Z_NO_FLUSH; + strm.next_in = in; + do { + strm.avail_out = CPPNET_CHUNK; + strm.next_out = out; + ret = deflate(&strm, flush); + assert(ret != Z_STREAM_ERROR); + have = CPPNET_CHUNK - strm.avail_out; + + if (have != 0) { + char buffer[64]; + sprintf(buffer, "%X\r\n", have); + socket->send(buffer); + socket->send((const char *) out, have); + socket->send("\r\n"); + } + } while (strm.avail_out == 0); + assert(strm.avail_in == 0); + } while (flush != Z_FINISH); + assert(ret == Z_STREAM_END); + socket->send("0\r\n\r\n"); + deflateEnd(&strm); + } else { + char buffer[CPPNET_CHUNK]; + char buff[64]; + while (true) { + unsigned long size = fread(buffer, 1, (size_t) ((CPPNET_CHUNK > (shouldTransfer - transfered) && shouldTransfer > 0) ? (shouldTransfer - transfered) : CPPNET_CHUNK), file); + transfered += size; + sprintf(buff, "%lX\r\n", size); + socket->send(buff); + socket->send((const char *) buffer, size); + socket->send("\r\n"); + if (size == 0) { + break; + } + } + } +} + +string HttpConnection::getField(string index) { + return request->getField(std::move(index)); +} + +string HttpConnection::getPath() { + return request->getPath(); +} + +void HttpConnection::setField(string index, string data) { + response->setField(std::move(index), std::move(data)); +} + +bool HttpConnection::isExistingField(string index) { + return request->isExistingField(std::move(index)); +} + +string HttpConnection::getMethod() { + return request->getMethod(); +} + +long HttpConnection::getDuration() { + return getMicros() - microsStart; +} + +HttpStatusCode HttpConnection::getStatusCode() { + return response->getStatusCode(); +} + +void HttpConnection::redirect(int statuscode, string location) { + setField("Location", std::move(location)); + respond(statuscode, ""); +} + +string HttpConnection::getResponseField(string index) { + return response->getField(std::move(index)); +} + +bool HttpConnection::isExistingResponseField(string index) { + return response->isExistingField(std::move(index)); +} + +string HttpConnection::cgiExport() { + return request->cgiExport(); +} + +void HttpConnection::removeField(string index) { + response->removeField(std::move(index)); +} + + + diff --git a/src/network/http/HttpConnection.h b/src/network/http/HttpConnection.h new file mode 100644 index 0000000..2cc15b2 --- /dev/null +++ b/src/network/http/HttpConnection.h @@ -0,0 +1,53 @@ + + +#ifndef NECRONDA_HTTP_CONNECTION +#define NECRONDA_HTTP_CONNECTION + + +#include "../Socket.h" +#include "HttpResponse.h" +#include "HttpRequest.h" + + +class HttpConnection { +private: + Socket *socket; + HttpRequest *request; + HttpResponse *response; + long microsStart; + +public: + explicit HttpConnection(Socket *socket); + + void respond(int statuscode); + + void respond(int statuscode, string payload); + + void respond(int statuscode, FILE *file, bool compress = false, long start = -1, long end = -1); + + void redirect(int statuscode, string location); + + bool isExistingField(string index); + + bool isExistingResponseField(string index); + + string getField(string index); + + string getResponseField(string index); + + string getPath(); + + string getMethod(); + + void setField(string index, string data); + + long getDuration(); + + HttpStatusCode getStatusCode(); + + string cgiExport(); + + void removeField(string index); +}; + +#endif diff --git a/src/network/http/HttpHeader.cpp b/src/network/http/HttpHeader.cpp new file mode 100644 index 0000000..755aa16 --- /dev/null +++ b/src/network/http/HttpHeader.cpp @@ -0,0 +1,122 @@ +/** + * Necronda Web Server 3.0 + * HttpHeader.cpp - HttpHeader Class methods + * Lorenz Stechauner, 2018-05-09 + */ + + +#include +#include +#include "../Socket.h" + +#include "HttpHeader.h" + + +using namespace std; + +string to_cgi(string text) { + for (auto & c: text) c = (char) toupper(c); + long pos = 0; + while ((pos = text.find('-', pos + 1)) != string::npos) { + text.replace(pos, 1, 1, '_'); + } + return text; +} + + +/** + * Default Constructor + */ +HttpHeader::HttpHeader() { + fields = fields; +} + +HttpHeader::HttpHeader(Socket *socket) : HttpHeader::HttpHeader() { + parse(socket); +} + + +void HttpHeader::parse(Socket *socket) { + while (true) { + string line = socket->receiveLine(); + if (line.length() == 0) { + break; + } else { + unsigned long pos = line.find(':'); + if (pos == string::npos) { + throw (char *) "Malformed header"; + } + string index = line.substr(0, pos); + string data = line.substr(pos + 1, line.length() - pos); + while (index[0] == ' ') index.erase(index.begin() + 0); + while (index[index.length() - 1] == ' ') index.erase(index.end() - 1); + while (data[0] == ' ') data.erase(data.begin() + 0); + while (data[data.length() - 1] == ' ') data.erase(data.end() - 1); + setField(index, data); + } + } +} + + +/** + * Default Destructor + */ +HttpHeader::~HttpHeader() { + fields.clear(); +} + + +/** + * Sets a field in the HTTP header + * e.g. Content-Length: 42 + * @param index The field index + * @param data The field data + */ +void HttpHeader::setField(string index, string data) { + removeField(index); + fields.insert(make_pair(index, data)); +} + +void HttpHeader::removeField(string index) { + fields.erase(index); +} + +/** + * Gets a field from the HTTP header + * e.g. Content-Length: 42 + * @param index The field index + * @return The field data + */ +string HttpHeader::getField(string index) { + auto i = fields.find(index); + if (i != fields.end()) { + return fields.at(index); + } else { + return ""; + } +} + + +bool HttpHeader::isExistingField(string index) { + auto i = fields.find(index); + return i != fields.end(); +} + +string HttpHeader::toString() { + string header = ""; + for (auto it = fields.begin(); it != fields.end(); it++ ) { + header += it->first + ": " + it->second + "\r\n"; + } + return header; +} + +string HttpHeader::cgiExport() { + string header = ""; + for (auto it = fields.begin(); it != fields.end(); it++ ) { + header += "HTTP_" + to_cgi(it->first) + "=" + cli_encode(it->second) + " "; + } + return header; +} + + + diff --git a/src/network/http/HttpHeader.h b/src/network/http/HttpHeader.h new file mode 100644 index 0000000..291f434 --- /dev/null +++ b/src/network/http/HttpHeader.h @@ -0,0 +1,53 @@ +/** + * Necronda Web Server 3.0 + * HttpHeader.h - HttpHeader Class definition + * Lorenz Stechauner, 2018-05-09 + */ + +#ifndef NECRONDA_HTTP_HEADER +#define NECRONDA_HTTP_HEADER + +#include + +using namespace std; + +struct comp { + bool operator()(const std::string& lhs, const std::string& rhs) const { + return strcasecmp(lhs.c_str(), rhs.c_str()) < 0; + } +}; + +/** + * Stores Key-Value Pairs for a HTTP header + * e.g. + * Content-Length: 64 + * Host: example.org + */ +class HttpHeader { +private: + map fields; + +public: + HttpHeader(); + + explicit HttpHeader(Socket *socket); + + ~HttpHeader(); + + void setField(string index, string data); + + string getField(string index); + + void removeField(string index); + + bool isExistingField(string index); + + void parse(Socket *socket); + + string toString(); + + string cgiExport(); + +}; + +#endif diff --git a/src/network/http/HttpRequest.cpp b/src/network/http/HttpRequest.cpp new file mode 100644 index 0000000..c323a01 --- /dev/null +++ b/src/network/http/HttpRequest.cpp @@ -0,0 +1,107 @@ + + +#include +#include +#include +#include "../Socket.h" +#include "HttpHeader.h" +#include "HttpRequest.h" + + +HttpRequest::HttpRequest() { + this->header = HttpHeader(); +} + +HttpRequest::HttpRequest(Socket *socket) : HttpRequest::HttpRequest() { + parseHeader(socket); +} + +HttpRequest::HttpRequest(string method, string path, string version) : HttpRequest::HttpRequest() { + this->method = std::move(method); + this->path = std::move(path); + this->version = std::move(version); +} + +void HttpRequest::parseHeader(Socket *socket) { + string line = socket->receiveLine(); + + unsigned long pos1 = line.find(' '); + unsigned long pos2; + + bool invalid = false; + + if (pos1 != string::npos) { + pos2 = line.find(' ', pos1 + 1); + if (pos2 != string::npos) { + method = line.substr(0, pos1); + for (auto &c: method) c = (char) toupper(c); + path = line.substr(pos1 + 1, pos2 - pos1 - 1); + version = line.substr(pos2 + 6, 3); + } else { + invalid = true; + } + } else { + pos2 = string::npos; + invalid = true; + } + + + if (!invalid && (line.substr(pos2 + 1, 5) != "HTTP/" || version[1] != '.' || path[0] != '/' || !(version[0] >= '0' && version[0] <= '9') || !(version[2] >= '0' && version[2] <= '9'))) { + invalid = true; + } + + if (invalid) { + method = ""; + path = ""; + version = ""; + throw (char *) "Malformed header"; + } + + header.parse(socket); +} + +string HttpRequest::getMethod() { + return method; +} + +string HttpRequest::getPath() { + return path; +} + +string HttpRequest::getVersion() { + return version; +} + +void HttpRequest::setMethod(string method) { + this->method = std::move(method); +} + +void HttpRequest::setPath(string path) { + this->path = std::move(path); +} + +void HttpRequest::setVersion(string version) { + this->version = std::move(version); +} + +string HttpRequest::getField(string index) { + return header.getField(std::move(index)); +} + +void HttpRequest::setField(string index, string data) { + header.setField(std::move(index), std::move(data)); +} + +bool HttpRequest::isExistingField(string index) { + return header.isExistingField(std::move(index)); +} + +string HttpRequest::cgiExport() { + return header.cgiExport(); +} + + + + + + diff --git a/src/network/http/HttpRequest.h b/src/network/http/HttpRequest.h new file mode 100644 index 0000000..1432719 --- /dev/null +++ b/src/network/http/HttpRequest.h @@ -0,0 +1,52 @@ +/** + * Necronda Web Server 3.0 + * HttpHeader.h - HttpHeader Class definition + * Lorenz Stechauner, 2018-05-09 + */ + +#ifndef NECRONDA_HTTP_REQUEST +#define NECRONDA_HTTP_REQUEST + +using namespace std; + +class HttpRequest { +private: + HttpHeader header; + string method; + string path; + string version; + +public: + HttpRequest(); + + explicit HttpRequest(Socket *socket); + + HttpRequest(string method, string path, string version = "1.1"); + + void parseHeader(Socket *socket); + + void sendHeader(Socket *socket); + + string getField(string index); + + void setField(string index, string data); + + bool isExistingField(string index); + + string getMethod(); + + string getPath(); + + string getVersion(); + + void setMethod(string method); + + void setPath(string path); + + void setVersion(string version); + + string cgiExport(); + +}; + +#endif diff --git a/src/network/http/HttpResponse.cpp b/src/network/http/HttpResponse.cpp new file mode 100644 index 0000000..83df59e --- /dev/null +++ b/src/network/http/HttpResponse.cpp @@ -0,0 +1,70 @@ +// +// Created by lorenz on 5/17/18. +// + +#include "HttpResponse.h" +#include +#include +#include "HttpStatusCode.h" + + +HttpResponse::HttpResponse() { + this->header = HttpHeader(); +} + +HttpResponse::HttpResponse(Socket *socket) : HttpResponse::HttpResponse() { + this->parseHeader(socket); +} + +HttpResponse::HttpResponse(int statuscode, string version) : HttpResponse::HttpResponse(::getStatusCode(statuscode), std::move(version)) { +} + +HttpResponse::HttpResponse(HttpStatusCode statuscode, string version) : HttpResponse::HttpResponse() { + this->statuscode = statuscode; + this->version = std::move(version); +} + +void HttpResponse::sendHeader(Socket *socket) { + socket->send("HTTP/" + version + " " + to_string(statuscode.code) + " " + statuscode.message + "\r\n" + + header.toString() + "\r\n"); +} + +string HttpResponse::getField(string index) { + return header.getField(std::move(index)); +} + +void HttpResponse::setField(string index, string data) { + header.setField(std::move(index), std::move(data)); +} + +bool HttpResponse::isExistingField(string index) { + return header.isExistingField(std::move(index)); +} + +HttpStatusCode HttpResponse::getStatusCode() { + return statuscode; +} + +string HttpResponse::getVersion() { + return version; +} + +void HttpResponse::setStatusCode(HttpStatusCode statuscode) { + this->statuscode = statuscode; +} + +void HttpResponse::setStatusCode(int statuscode) { + this->statuscode = ::getStatusCode(statuscode); +} + +void HttpResponse::setVersion(string version) { + this->version = std::move(version); +} + +void HttpResponse::parseHeader(Socket *socket) { + +} + +void HttpResponse::removeField(string index) { + header.removeField(std::move(index)); +} diff --git a/src/network/http/HttpResponse.h b/src/network/http/HttpResponse.h new file mode 100644 index 0000000..f664e71 --- /dev/null +++ b/src/network/http/HttpResponse.h @@ -0,0 +1,50 @@ + + +#ifndef NECRONDA_HTTP_RESPONSE +#define NECRONDA_HTTP_RESPONSE + + +#include +#include "HttpHeader.h" +#include "HttpStatusCode.h" +#include "../Socket.h" + +class HttpResponse { +private: + HttpHeader header; + HttpStatusCode statuscode; + string version; + +public: + HttpResponse(); + + explicit HttpResponse(Socket *socket); + + explicit HttpResponse(int statuscode, string version = "1.1"); + + explicit HttpResponse(HttpStatusCode statuscode, string version = "1.1"); + + void parseHeader(Socket *socket); + + void sendHeader(Socket *socket); + + string getField(string index); + + void setField(string index, string data); + + bool isExistingField(string index); + + HttpStatusCode getStatusCode(); + + string getVersion(); + + void setStatusCode(HttpStatusCode statuscode); + + void setStatusCode(int statuscode); + + void setVersion(string version); + + void removeField(string index); +}; + +#endif diff --git a/src/network/http/HttpStatusCode.cpp b/src/network/http/HttpStatusCode.cpp new file mode 100644 index 0000000..bffeafb --- /dev/null +++ b/src/network/http/HttpStatusCode.cpp @@ -0,0 +1,65 @@ +#include "HttpStatusCode.h" + +/** + * Necronda Web Server 3.0 + * HttpStatusCode.cpp - HTTP Status Code definition + * Lorenz Stechauner, 2018-05-16 + * Reference: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + */ + + +HttpStatusCode httpStatusCodes[] = { + HttpStatusCode{100, "Informational", "Continue", ""}, + HttpStatusCode{101, "Informational", "Switching Protocols", ""}, + + HttpStatusCode{200, "Success", "OK", ""}, + HttpStatusCode{201, "Success", "Created", ""}, + HttpStatusCode{202, "Success", "Accepted", ""}, + HttpStatusCode{203, "Success", "Non-Authoritative Information", ""}, + HttpStatusCode{204, "Success", "No Centent", ""}, + HttpStatusCode{205, "Success", "Reset Content", ""}, + HttpStatusCode{206, "Success", "Partial Content", ""}, + + HttpStatusCode{300, "Redirection", "Multiple Choices", ""}, + HttpStatusCode{301, "Redirection", "Moved Permanently", ""}, + HttpStatusCode{302, "Redirection", "Found", ""}, + HttpStatusCode{303, "Redirection", "See Other", ""}, + HttpStatusCode{304, "Redirection", "Not Modified", ""}, + HttpStatusCode{305, "Redirection", "Use Proxy", ""}, + HttpStatusCode{307, "Redirection", "Temporary Redirect", ""}, + + HttpStatusCode{400, "Client Error", "Bad Request", ""}, + HttpStatusCode{401, "Client Error", "Unauthorized", ""}, + HttpStatusCode{402, "Client Error", "Payment Required", ""}, + HttpStatusCode{403, "Client Error", "Forbidden", ""}, + HttpStatusCode{404, "Client Error", "Not Found", ""}, + HttpStatusCode{405, "Client Error", "Method Not Allowed", ""}, + HttpStatusCode{406, "Client Error", "Not Acceptable", ""}, + HttpStatusCode{407, "Client Error", "Proxy Authentication Required", ""}, + HttpStatusCode{408, "Client Error", "Request Timeout", ""}, + HttpStatusCode{409, "Client Error", "Conflict", ""}, + HttpStatusCode{410, "Client Error", "Gone", ""}, + HttpStatusCode{411, "Client Error", "Length Required", ""}, + HttpStatusCode{412, "Client Error", "Precondition Failed", ""}, + HttpStatusCode{413, "Client Error", "Request Entity Too Large", ""}, + HttpStatusCode{414, "Client Error", "Request-URI Too Long", ""}, + HttpStatusCode{415, "Client Error", "Unsupported Media Type", ""}, + HttpStatusCode{416, "Client Error", "Requested Range Not Satisfiable", ""}, + HttpStatusCode{417, "Client Error", "Expectation Failed", ""}, + + HttpStatusCode{500, "Server Error", "Internal Server Error", ""}, + HttpStatusCode{501, "Server Error", "Not Implemented", ""}, + HttpStatusCode{502, "Server Error", "Bad Gateway", ""}, + HttpStatusCode{503, "Server Error", "Service Unavailable", ""}, + HttpStatusCode{504, "Server Error", "Gateway Timeout", ""}, + HttpStatusCode{505, "Server Error", "HTTP Version Not Supported", ""}, +}; + +HttpStatusCode getStatusCode(int statuscode) { + for (HttpStatusCode sc : httpStatusCodes) { + if (sc.code == statuscode) { + return sc; + } + } + throw (char *) "Invalid status code"; +} diff --git a/src/network/http/HttpStatusCode.h b/src/network/http/HttpStatusCode.h new file mode 100644 index 0000000..7773af9 --- /dev/null +++ b/src/network/http/HttpStatusCode.h @@ -0,0 +1,15 @@ + + +#ifndef NECRONDA_HTTP_STATUSCODE +#define NECRONDA_HTTP_STATUSCODE + +typedef struct { + short code; // The status code (e.g. 200) + const char *type; // The status type type (e.g Success) + const char *message; // The status code message (e.g. OK) + const char *description; // The status code description (currently not used) +} HttpStatusCode; + +HttpStatusCode getStatusCode(int statuscode); + +#endif \ No newline at end of file diff --git a/src/network/upnp/Upnp.cpp b/src/network/upnp/Upnp.cpp new file mode 100644 index 0000000..148b73c --- /dev/null +++ b/src/network/upnp/Upnp.cpp @@ -0,0 +1,5 @@ +// +// Created by lorenz on 7/10/18. +// + +#include "Upnp.h" diff --git a/src/network/upnp/Upnp.h b/src/network/upnp/Upnp.h new file mode 100644 index 0000000..53a4e1d --- /dev/null +++ b/src/network/upnp/Upnp.h @@ -0,0 +1,14 @@ +// +// Created by lorenz on 7/10/18. +// + +#ifndef CPPNET_UPNP_H +#define CPPNET_UPNP_H + + +class Upnp { + +}; + + +#endif //CPPNET_UPNP_H diff --git a/src/network/upnp/UpnpDevice.cpp b/src/network/upnp/UpnpDevice.cpp new file mode 100644 index 0000000..4a34cc7 --- /dev/null +++ b/src/network/upnp/UpnpDevice.cpp @@ -0,0 +1,5 @@ +// +// Created by lorenz on 7/10/18. +// + +#include "UpnpDevice.h" diff --git a/src/network/upnp/UpnpDevice.h b/src/network/upnp/UpnpDevice.h new file mode 100644 index 0000000..d07e201 --- /dev/null +++ b/src/network/upnp/UpnpDevice.h @@ -0,0 +1,14 @@ +// +// Created by lorenz on 7/10/18. +// + +#ifndef CPPNET_UPNPDEVICE_H +#define CPPNET_UPNPDEVICE_H + + +class UpnpDevice { + +}; + + +#endif //CPPNET_UPNPDEVICE_H