From 13bef5e9ccd5463cbb7789ec08c5cc62e0944ca3 Mon Sep 17 00:00:00 2001
From: Lorenz Stechauner <lorenz.stechauner@gmail.com>
Date: Thu, 31 May 2018 15:13:12 +0200
Subject: [PATCH] Working

---
 run.sh                  |   2 +-
 src/URI.cpp             |  12 +--
 src/client.cpp          | 181 ++++++++++++++++++++++++++++++++++------
 src/necronda-server.cpp |  69 +++++++++++++++
 src/necronda-server.h   |  12 +++
 src/procopen.cpp        |  41 +++++++++
 src/procopen.h          |  41 +++++++++
 7 files changed, 325 insertions(+), 33 deletions(-)
 create mode 100644 src/procopen.cpp
 create mode 100644 src/procopen.h

diff --git a/run.sh b/run.sh
index db010aa..e36d85c 100644
--- a/run.sh
+++ b/run.sh
@@ -4,4 +4,4 @@ make && \
  echo -e "-- Successfully finished compiling!\n" && \
  sleep 0.0625 && \
  echo -e "-- Starting Server...\n" && \
- ./bin/necronda-server
+ authbind ./bin/necronda-server
diff --git a/src/URI.cpp b/src/URI.cpp
index e3631fc..0ec6573 100644
--- a/src/URI.cpp
+++ b/src/URI.cpp
@@ -50,6 +50,7 @@ URI::URI(string webroot, string reqpath) {
 	if (webroot[webroot.length() - 1] == '/') {
 		webroot.erase(webroot.length() - 1);
 	}
+	reqpath = url_decode(reqpath);
 	if (reqpath.find("/../") != string::npos) {
 		throw (char *) "Invalid path";
 	}
@@ -116,8 +117,9 @@ string URI::getFilePath() {
 }
 
 string URI::getRelativeFilePath() {
-	string rel = getRelativePath();
-	// TODO
+	string str = getFilePath();
+	long len = getWebRoot().length();
+	return str.substr(len, str.length() - len);
 }
 
 string URI::getNewPath() {
@@ -127,7 +129,7 @@ string URI::getNewPath() {
 		}
 	}
 	if (relpath != reqpath) {
-		return relpath + (queryinit? "?" + query : "");
+		return url_encode(relpath) + (queryinit? "?" + query : "");
 	} else {
 		return "";
 	}
@@ -138,7 +140,7 @@ FILE *URI::openFile() {
 }
 
 string URI::getFilePathInfo() {
-	return getAbsolutePath().erase(getFilePath().length(), getAbsolutePath().length());
+	return ""; //getAbsolutePath().erase(getFilePath().length(), getAbsolutePath().length());
 }
 
 string URI::getFileType() {
@@ -146,7 +148,7 @@ string URI::getFileType() {
 }
 
 bool URI::isStatic() {
-	return true;
+	return getExtension(filepath) != "php";
 }
 
 string URI::getQuery() {
diff --git a/src/client.cpp b/src/client.cpp
index 6c5981d..9f28b8b 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -19,18 +19,23 @@
 #include "necronda-server.h"
 #include "network/http/HttpStatusCode.h"
 #include "URI.h"
+#include "procopen.h"
 
 
 /**
  * Writes log messages to the console
  * @param prefix The connection prefix
- * @param string The string to be written
+ * @param str The string to be written
  */
-void log(const char *prefix, const string &string) {
-	printf("%s%s\r\n", prefix, string.c_str());
+void log(const char *prefix, const string &str) {
+	printf("%s%s\r\n", prefix, str.c_str());
 	flush(cout);
 }
 
+void log_error(const char *prefix, const string &str) {
+	log(prefix, "\x1B[1;31m" + str + "\x1B[0m");
+}
+
 string getETag(string filename) {
 	ifstream etags = ifstream("/var/necronda/ETags");
 
@@ -106,6 +111,24 @@ string getETag(string filename) {
 	return md5;
 }
 
+#include <iostream>
+#include <wait.h>
+
+long getPosition(std::string str, char c, int occurence) {
+	int tempOccur = 0;
+	int num = 0;
+	for (auto it : str) {
+		num++;
+		if (it == c) {
+			if (++tempOccur == occurence) {
+				return num;
+			}
+		}
+	}
+
+	return -1;
+}
+
 /**
  * Handles (keep-alive) HTTP connections
  * @param prefix The connection prefix
@@ -114,8 +137,9 @@ string getETag(string filename) {
  * @param num The Connection Number in the client
  * @return Should the server wait for another header?
  */
-bool connection_handler(const char *prefix, Socket *socket, long id, long num) {
+bool connection_handler(const char *preprefix, const char *col1, const char *col2, Socket *socket, long id, long num) {
 	bool error = false;
+	char *prefix = (char *) preprefix;
 	try {
 		HttpConnection req(socket);
 		try {
@@ -131,19 +155,32 @@ bool connection_handler(const char *prefix, Socket *socket, long id, long num) {
 				req.respond(400);
 			} else {
 				string host = req.getField("Host");
+				long pos = host.find(':');
+				if (pos != string::npos) {
+					host.erase(pos, host.length() - pos);
+				}
 
-				string str = string(prefix);
-				unsigned long pos = str.find('[', 8);
-				string n = str.substr(0, 8) + host + str.substr(pos - 1, str.length() - pos + 1);
+				FILE *name = popen(("dig +time=1 -x " + socket->getPeerAddress()->toString() +
+									" | grep -oP \"^[^;].*\\t\\K([^ ]*)\\w\"").c_str(), "r");
+				char hostbuffer[1024];
+				memset(hostbuffer, 0, 1024);
+				size_t size = fread(hostbuffer, 1, 1024, name);
+				hostbuffer[size - 1] = 0; // remove \n
+				if (size <= 1) {
+					sprintf(hostbuffer, "%s", socket->getPeerAddress()->toString().c_str());
+				}
+
+				char buffer[1024];
+				sprintf(buffer, "[\x1B[1m%s\x1B[0m][%i]%s[%s][%i]%s ", host.c_str(), socket->getSocketPort(), col1,
+						hostbuffer, socket->getPeerPort(), col2);
 
-				char buffer[256];
-				sprintf(buffer, "%s", n.c_str());
 				prefix = buffer;
 
 				URI path = URI(getWebRoot(host), req.getPath());
-				log(prefix, req.getMethod() + " " + req.getPath());
+				log(prefix, "\x1B[1m" + req.getMethod() + " " + req.getPath() + "\x1B[0m");
 
 				FILE *file = path.openFile();
+				pid_t childpid = 0;
 
 				if (!path.getNewPath().empty()) {
 					req.redirect(302, path.getNewPath());
@@ -157,6 +194,8 @@ bool connection_handler(const char *prefix, Socket *socket, long id, long num) {
 
 						if (type.find("inode/") == 0) {
 							req.respond(403);
+						} else if (path.getRelativeFilePath().find("/.") != string::npos) {
+							req.respond(403);
 						} else {
 							req.setField("Content-Type", type);
 							req.setField("Last-Modified", getHttpDate(path.getFilePath()));
@@ -183,7 +222,6 @@ bool connection_handler(const char *prefix, Socket *socket, long id, long num) {
 								if (req.getMethod() != "GET" && req.getMethod() != "POST" && req.getMethod() != "PUT") {
 									invalidMethod = true;
 								}
-								system("php");
 							}
 
 							if (invalidMethod) {
@@ -191,15 +229,82 @@ bool connection_handler(const char *prefix, Socket *socket, long id, long num) {
 							} else if (etag) {
 								req.respond(304);
 							} else {
+								int statuscode = 200;
+								if (!path.isStatic()) {
+									string cmd = (string) "env -i" +
+												 " REDIRECT_STATUS=" + cli_encode("CGI") +
+												 " DOCUMENT_ROOT=" + cli_encode(getWebRoot(host)) +
+												 " " + req.cgiExport() +
+												 (req.isExistingField("Content-Length")?" CONTENT_LENGTH="+cli_encode(req.getField("Content-Length")):"") +
+												 (req.isExistingField("Content-Type")?" CONTENT_TYPE="+cli_encode(req.getField("Content-Type")):"") +
+												 ((socket->isSecured())?" HTTPS=on":"") +
+												 " PATH_INFO=" + cli_encode(path.getFilePathInfo()) +
+												 " PATH_TRANSLATED=" + cli_encode(path.getAbsolutePath()) +
+												 " QUERY_STRING=" + cli_encode(path.getQuery()) +
+												 " REMOTE_ADDR=" + cli_encode(socket->getPeerAddress()->toString()) +
+												 " REMOTE_HOST=" + cli_encode(hostbuffer) +
+												 " REMOTE_PORT=" + cli_encode(to_string(socket->getPeerPort())) +
+												 " REQUEST_METHOD=" + cli_encode(req.getMethod()) +
+												 " REQUEST_URI=" + cli_encode(req.getPath()) +
+												 " SCRIPT_FILENAME=" + cli_encode(path.getFilePath()) +
+												 " SCRIPT_NAME=" + cli_encode(path.getRelativePath()) +
+												 " SERVER_ADMIN=" + cli_encode("lorenz.stechauner@gmail.com") +
+												 " SERVER_NAME=" + cli_encode(host) +
+												 " SERVER_PORT=" + cli_encode(to_string(socket->getSocketPort())) +
+												 " SERVER_SOFTWARE=" + cli_encode("Necronda 3.0") +
+												 " SERVER_PROTOCOL=" + cli_encode("HTTP/1.1") +
+												 " GATEWAY_INTERFACE=" + cli_encode("CGI/1.1") +
+												 " /usr/bin/php-cgi";
 
-								bool compress = type.find("text/") == 0 && req.isExistingField("Accept-Encoding") &&
+									stds pipes = procopen(cmd.c_str());
+									childpid = pipes.pid;
+
+									char fdbuffer[4096];
+									if (req.getMethod() == "POST" || req.getMethod() == "PUT") {
+										long len = req.isExistingField("Content-Length") ? strtol(req.getField("Content-Length").c_str(), nullptr, 10) : -1;
+										socket->receive(pipes.stdin);
+									}
+									fclose(pipes.stdin);
+
+									string line;
+									while (!(line = read_line(pipes.stderr)).empty()) {
+										log_error(prefix, line);
+									}
+									fclose(pipes.stderr);
+
+									while (!(line = read_line(pipes.stdout)).empty()) {
+										long pos = line.find(':');
+										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);
+
+										if (index == "Location") {
+											statuscode = 303;
+										}
+
+										req.setField(index, data);
+									}
+
+									fclose(file);
+									file = pipes.stdout;
+
+
+								}
+
+								bool compress = path.isStatic() && type.find("text/") == 0 && req.isExistingField("Accept-Encoding") &&
 												req.getField("Accept-Encoding").find("deflate") != string::npos;
 
 								if (compress) {
 									req.setField("Accept-Ranges", "none");
 								}
 
-								if (req.isExistingField("Range")) {
+								if (compress && req.isExistingField("Range")) {
+									req.respond(416);
+								} else if (req.isExistingField("Range")) {
 									string range = req.getField("Range");
 									if (range.find("bytes=") != 0 || !path.isStatic()) {
 										req.respond(416);
@@ -231,16 +336,39 @@ bool connection_handler(const char *prefix, Socket *socket, long id, long num) {
 										}
 									}
 								} else {
-									req.respond(200, file, compress);
+									req.respond(statuscode, file, compress);
 								}
 							}
 						}
 						fclose(file);
+						if (childpid > 0) {
+							waitpid(childpid, nullptr, 0);
+						}
 					}
 				}
 			}
 			HttpStatusCode status = req.getStatusCode();
-			log(prefix, to_string(status.code) + " " + status.message + " (" + formatTime(req.getDuration()) + ")");
+			int code = status.code;
+			string color = "";
+			string comment = "";
+			if ((code >= 200 && code < 300) || code == 304) {
+				color = "\x1B[1;32m"; // Success (Cached): Green
+			} else if (code >= 100 && code < 200) {
+				color = "\x1B[1;93m"; // Continue: Yellow
+			} else if (code >= 300 && code < 400) {
+				color = "\x1B[1;93m"; // Redirect: Yellow
+				comment = " -> " +
+						  (req.isExistingResponseField("Location") ? req.getResponseField("Location") : "<invalid>");
+			} else if (code >= 400 && code < 500) {
+				color = "\x1B[1;31m"; // Client Error: Red
+				//comment = " -> " + req.getPath();
+			} else if (code >= 500 & code < 600) {
+				color = "\x1B[1;31m"; // Server Error: Red
+				//comment = " -> " + req.getPath();
+			}
+			log(prefix,
+				color + to_string(status.code) + " " + status.message + comment + " (" + formatTime(req.getDuration()) +
+				")\x1B[0m");
 		} catch (char *msg) {
 			HttpStatusCode status = req.getStatusCode();
 			log(prefix, to_string(status.code) + " " + status.message + " (" + formatTime(req.getDuration()) + ")");
@@ -290,26 +418,25 @@ bool connection_handler(const char *prefix, Socket *socket, long id, long num) {
  */
 void client_handler(Socket *socket, long id, bool ssl) {
 	const char *prefix;
+	char const *col1;
+	char const *col2 = "\x1B[0m";
 	{
-		char const *col1;
-		char const *col2 = "\x1B[0m";
 		auto group = (int) (id % 6);
 		if (group == 0) {
-			col1 = "\x1B[1;31m";
+			col1 = "\x1B[0;31m"; // Red
 		} else if (group == 1) {
-			col1 = "\x1B[1;32m";
+			col1 = "\x1B[0;32m"; // Green
 		} else if (group == 2) {
-			col1 = "\x1B[1;34m";
+			col1 = "\x1B[0;34m"; // Blue
 		} else if (group == 3) {
-			col1 = "\x1B[1;33m";
+			col1 = "\x1B[0;33m"; // Yellow
 		} else if (group == 4) {
-			col1 = "\x1B[1;35m";
+			col1 = "\x1B[0;35m"; // Magenta
 		} else {
-			col1 = "\x1B[1;36m";
+			col1 = "\x1B[0;36m"; // Cyan
 		}
-		string *a = new string((string)
-									   col1 + "[" + socket->getSocketAddress()->toString() + "][" +
-							   to_string(socket->getSocketPort()) + "]" +
+		string *a = new string("[" + socket->getSocketAddress()->toString() + "][" +
+							   to_string(socket->getSocketPort()) + "]" + col1 +
 							   "[" + socket->getPeerAddress()->toString() + "][" + to_string(socket->getPeerPort()) +
 							   "]" + col2 + " ");
 		prefix = a->c_str();
@@ -338,7 +465,7 @@ void client_handler(Socket *socket, long id, bool ssl) {
 
 	long reqnum = 0;
 	if (!err) {
-		while (connection_handler(prefix, socket, id, ++reqnum));
+		while (connection_handler(prefix, col1, col2, socket, id, ++reqnum));
 		reqnum--;
 	}
 
diff --git a/src/necronda-server.cpp b/src/necronda-server.cpp
index 9c1df0a..31c621c 100644
--- a/src/necronda-server.cpp
+++ b/src/necronda-server.cpp
@@ -142,6 +142,75 @@ string getWebRoot(string host) {
 }
 
 
+
+string url_decode(string url) {
+	long pos = 0;
+	while ((pos = url.find('+', pos + 1)) != string::npos) {
+		url.replace(pos, 1, 1, ' ');
+	}
+	pos = 0;
+	while ((pos = url.find('%', pos + 1)) != string::npos) {
+		const char *num = url.substr(pos + 1, 2).c_str();
+		auto c = (char) strtol(num, nullptr, 16);
+		url.erase(pos, 3);
+		url.insert(pos, 1, c);
+	}
+
+	return url;
+}
+
+string url_encode(string url) {
+	char buff[4];
+	for (long pos = 0; pos < url.length(); pos++) {
+		auto c = (unsigned char) url[pos];
+		if (c < ' ' || c > '~' || c == ' ' || c == '#' || c == '?' || c == '&' || c == '=' || c == '\\' || c == '%') {
+			sprintf(buff, "%%%02X", c);
+			url.replace(pos, 1, buff);
+		}
+	}
+	return url;
+}
+
+string html_decode(string text) {
+	return text;
+}
+
+string html_encode(string text) {
+	return text;
+}
+
+string cli_encode(string text) {
+	char buff[5];
+	for (long pos = 0; pos < text.length(); pos++) {
+		auto c = (unsigned char) text[pos];
+		if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == ',' || c == '.' || c == '_' || c == '+' || c == ':' || c == '@' || c == '%' || c == '/' || c == '-')) {
+			sprintf(buff, "\\%.1s", &c);
+			text.replace(pos, 1, buff);
+			pos++;
+		}
+	}
+	return text;
+}
+
+string read_line(FILE* file) {
+	char *line = nullptr;
+	size_t len = 0;
+	ssize_t read;
+	if ((read = getline(&line, &len, file)) < 0) {
+		return "";
+	}
+	string l = string(line);
+	if (l[l.length()-1] == '\n') {
+		l.erase(l.length()-1);
+	}
+	if (l[l.length()-1] == '\r') {
+		l.erase(l.length()-1);
+	}
+	return l;
+}
+
+
+#include "procopen.cpp"
 #include "network/Address.cpp"
 #include "network/Socket.cpp"
 #include "URI.cpp"
diff --git a/src/necronda-server.h b/src/necronda-server.h
index 4ff3ae3..452911e 100644
--- a/src/necronda-server.h
+++ b/src/necronda-server.h
@@ -33,5 +33,17 @@ string getTimestamp(time_t time);
 
 long getFileSize(string filename);
 
+string url_decode(string url);
+
+string url_encode(string url);
+
+string html_decode(string text);
+
+string html_encode(string text);
+
+string cli_encode(string text);
+
+string read_line(FILE *file);
+
 
 #endif
diff --git a/src/procopen.cpp b/src/procopen.cpp
new file mode 100644
index 0000000..6a53f60
--- /dev/null
+++ b/src/procopen.cpp
@@ -0,0 +1,41 @@
+//
+// Created by lorenz on 5/30/18.
+//
+
+#include "procopen.h"
+
+stds procopen(const char* command) {
+
+	int pipes[3][2];
+
+	pipe(pipes[PARENT_READ_PIPE]);
+	pipe(pipes[PARENT_WRITE_PIPE]);
+	pipe(pipes[PARENT_ERROR_PIPE]);
+
+	int pid = fork();
+
+	if(pid == 0) {
+		dup2(CHILD_READ_FD, STDIN_FILENO);
+		dup2(CHILD_WRITE_FD, STDOUT_FILENO);
+		dup2(CHILD_ERROR_FD, STDERR_FILENO);
+
+		close(CHILD_READ_FD);
+		close(CHILD_WRITE_FD);
+		close(CHILD_ERROR_FD);
+
+		close(PARENT_READ_FD);
+		close(PARENT_WRITE_FD);
+		close(PARENT_ERROR_FD);
+
+		system(command);
+		exit(0);
+	} else {
+		close(CHILD_READ_FD);
+		close(CHILD_WRITE_FD);
+		close(CHILD_ERROR_FD);
+
+		return stds{fdopen(PARENT_WRITE_FD, "w"), fdopen(PARENT_READ_FD, "r"), fdopen(PARENT_ERROR_FD, "r"), (pid_t) pid};
+	}
+}
+
+
diff --git a/src/procopen.h b/src/procopen.h
new file mode 100644
index 0000000..7edbc84
--- /dev/null
+++ b/src/procopen.h
@@ -0,0 +1,41 @@
+//
+// Created by lorenz on 5/30/18.
+//
+
+
+#include <zconf.h>
+#include <cstdlib>
+#include <cstdio>
+
+#ifndef NECRONDA_PROCOPEN
+#define NECRONDA_PROCOPEN
+
+
+#define PARENT_WRITE_PIPE  0
+#define PARENT_READ_PIPE   1
+#define PARENT_ERROR_PIPE  2
+
+#define READ_FD  0
+#define WRITE_FD 1
+
+#define PARENT_READ_FD  ( pipes[PARENT_READ_PIPE][READ_FD]   )
+#define PARENT_WRITE_FD ( pipes[PARENT_WRITE_PIPE][WRITE_FD] )
+#define PARENT_ERROR_FD ( pipes[PARENT_ERROR_PIPE][READ_FD] )
+
+#define CHILD_READ_FD   ( pipes[PARENT_WRITE_PIPE][READ_FD]  )
+#define CHILD_WRITE_FD  ( pipes[PARENT_READ_PIPE][WRITE_FD]  )
+#define CHILD_ERROR_FD  ( pipes[PARENT_ERROR_PIPE][WRITE_FD]  )
+
+
+typedef struct {
+	FILE* stdin;
+	FILE* stdout;
+	FILE* stderr;
+	pid_t pid;
+} stds;
+
+
+stds procopen(const char* command);
+
+
+#endif