You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

513 lines
14 KiB

#include "UDPTCPNetwork.h"
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <list>
WebServerClient::WebServerClient () {
tcp = NULL;
ssl = NULL;
ssl_key = "";
ssl_cert = "";
callback_request = NULL;
};
WebServerClient::~WebServerClient () {
if (ssl) delete ssl;
ssl = NULL;
if (tcp) delete tcp;
tcp = NULL;
};
/// @brief
/// @param socket
/// @param _ssl_key
/// @param _ssl_cert
/// @return
int WebServerClient::Accept (TCP *socket, std::string _ssl_key, std::string _ssl_cert) {
if (ssl) delete ssl;
ssl = NULL;
if (tcp) delete tcp;
tcp = NULL;
tcp = socket;
ssl_key = _ssl_key;
ssl_cert = _ssl_cert;
ssl = new SSLSocket;
ssl->SetCertificat(ssl_cert, ssl_key);
ssl->Accept(tcp->GetSocket(),200);
return 1;
};
/// @brief
/// @param socket
/// @return
int WebServerClient::Accept (TCP *socket) {
if (ssl) delete ssl;
ssl = NULL;
if (tcp) delete tcp;
tcp = NULL;
tcp = socket;
return 1;
};
/// @brief Read data from the network fill in the buffer. The Request must be handled from the server
/// @return 0 no data , 1 request complete
WebRequestBuffer *WebServerClient::Loop () {
int len;
if (ssl) {
len = ssl->Read(buffer, WEB_READ_BUFFER_SIZE);
}
else {
len = tcp->ReadTimeout(buffer, WEB_READ_BUFFER_SIZE, 20);
}
if (len < 0) return NULL;
if (len == 0) return &ReqBuffer;
ReqBuffer.AddBuffer (buffer, len);
return &ReqBuffer;
};
#define TIME_TEXT_SIZE 128
std::string WebServerClient::CreateHeader(WebRequestBuffer *request, int errcode,
std::string doc_type, int doc_size, std::string addheader) {
long int l;
long bytes;
std::string errortext;
std::string response;
char timebuf[TIME_TEXT_SIZE];
struct tm tm;
time_t t = time(NULL);
// debug ("errcode:%d cont_type:%s doc_size:%d\n", errcode, doc_type.c_str(), doc_size);
//
// read the current time int the timebuf
if (localtime_r (&t, &tm) != &tm) {
fprintf (stderr, "%s:%d %s could not get localtime. Error:%s\n",
__FILE__, __LINE__, __FUNCTION__, strerror(errno));
return "";
}
if (asctime_r (&tm, timebuf) != timebuf) {
fprintf (stderr, "%s:%d %s could not get asctime. Error:%s\n",
__FILE__, __LINE__, __FUNCTION__, strerror(errno));
return "";
}
timebuf[strlen(timebuf)-3] = 0;
switch (errcode) {
case (200):
errortext = "200 OK";
break;
default:
errortext = "501 NOT IMPLEMENTED";
break;
}
response = request->GetProtocolVersion() + " " + errortext + "\r\n";
response += "Accept-Ranges: bytes\r\n";
response += "Content-Type: " + doc_type + "\r\n";
response += "Content-Length: " + std::to_string(doc_size) + "\r\n";
response += "Date: " + (std::string) timebuf + "\r\n";
response += "Server: SyslogCollect \r\n";
if (addheader.length() > 0)
response += addheader;
response += "\r\n";
return response;
}
/// @brief send some data as response
/// @param request the orginal request this response is for
/// @param data json type data to send
/// @param addheader add header lines if set
/// @return 0 on error, 1 on success
int WebServerClient::SendResponseData (WebRequestBuffer *request, std::string data, std::string addheader) {
char *outbuffer = NULL;
int outbufferpos = 0;
int error = 200;
long int l;
std::string datatype = "application/x-www-form-urlencoded; charset=UTF-8";
std::string header;
ssize_t databytes;
//
// send header and file
header = CreateHeader (request, error, datatype, data.length(), addheader);
outbufferpos = header.length();
outbuffer = (char*) malloc (outbufferpos + data.length() + 16);
memcpy (outbuffer, header.c_str(), outbufferpos);
memcpy (outbuffer + outbufferpos, data.c_str(), data.length());
databytes = outbufferpos + data.length();
outbuffer[databytes] = 0;
if (ssl) l = ssl->Write(outbuffer, databytes);
else l = tcp->Write(outbuffer, databytes);
if (l != databytes) {
debug ("could not send all data. Error:%s\n", strerror(errno));
free (outbuffer);
return 0;
}
free (outbuffer);
return 1;
}
std::string WebServerClient::GetFileType (std::string fname) {
std::string res = "text/html;charset=utf-8";
std::size_t pos = fname.find_last_of('.', std::string::npos);
if (pos == std::string::npos) return res;
std::string suffix = string_to_lower (fname.substr(pos));
if (suffix.compare (".html") == 0) return res;
if (suffix.compare (".htm") == 0) return res;
if (suffix.compare (".js") == 0) return res;
if (suffix.compare (".json") == 0) return "application/json; charset=utf-8";
if (suffix.compare (".svg") == 0) return "image/svg+xml";
if (suffix.compare (".png") == 0) return "image/png";
if (suffix.compare (".jpg") == 0) return "image/jpeg";
if (suffix.compare (".jpeg") == 0) return "image/jpeg";
return res;
};
int WebServerClient::SendResponseFileFromMemory (WebRequestBuffer *request, std::string fname, std::string addheader,
void *memptr, int memsize) {
char *outbuffer = NULL;
int outbufferpos = 0;
int error = 200;
long int l;
std::string filetype = "text/html;charset=utf-8";
std::string header;
struct stat filestat;
int filefd;
ssize_t bytestosend;
//
// determine file type and size
filetype = GetFileType(fname);
//
// send header and file
header = CreateHeader (request, error, filetype, memsize, addheader);
outbufferpos = header.length();
outbuffer = (char*) malloc (outbufferpos + memsize + 1);
memcpy (outbuffer, header.c_str(), outbufferpos);
//
// append data to send
memcpy (outbuffer+outbufferpos, memptr, memsize);
bytestosend = outbufferpos + memsize;
if (ssl) l = ssl->Write(outbuffer, bytestosend);
else l = tcp->Write(outbuffer, bytestosend);
// debug ("send file %s with %ld bytes\n", fname.c_str(), filereadbytes);
if (l != bytestosend) {
debug ("could not send all data. Error:%s\n", strerror(errno));
free (outbuffer);
return 0;
}
free (outbuffer);
return 1;
};
/// @brief send a file as response
/// @param request requested file
/// @param fname file relative to the DOCUMENT_ROOT
/// @param addheader add header if set -> must contain "\n\r" at each line
/// @return 0 on error, 1 on success
int WebServerClient::SendResponseFile (WebRequestBuffer *request, std::string fname, std::string addheader) {
char *outbuffer = NULL;
int outbufferpos = 0;
int error = 200;
long int l;
std::string fullfname = Get_WebFile_FullPath(fname);
std::string filetype = "text/html;charset=utf-8";
std::string header;
struct stat filestat;
int filefd;
ssize_t filereadbytes;
//
// open file and determine file type and size
filetype = GetFileType(fname);
filefd = open(fullfname.c_str(), O_RDONLY);
if (filefd < 0) {
debug ("could not open file '%s' for reading. Error:%s\n",
fullfname.c_str(), strerror(errno));
//
// set errorcode and reopen error file
error = 404;
fullfname = Get_WebFile_FullPath("error404.html");
filefd = open(fullfname.c_str(), O_RDONLY);
if (filefd < 0) errorexit ("could not open file '%s' for reading. Error:%s\n",
fullfname.c_str(), strerror(errno));
}
if (fstat (filefd, &filestat) != 0) {
debug ("could not get stats on file '%s'. Error:%s\n",
fullfname.c_str(), strerror(errno));
error = 500;
fullfname = Get_WebFile_FullPath("error500.html");
filefd = open(fullfname.c_str(), O_RDONLY);
if (filefd < 0)
errorexit ("could not open file '%s' for reading. Error:%s\n",
fullfname.c_str(), strerror(errno));
if (fstat (filefd, &filestat) != 0)
errorexit ("could not get stats on file '%s'. Error:%s\n",
fullfname.c_str(), strerror(errno));
}
//
// send header and file
header = CreateHeader (request, error, filetype, filestat.st_size, addheader);
outbufferpos = header.length();
outbuffer = (char*) malloc (outbufferpos + filestat.st_size + 16);
memcpy (outbuffer, header.c_str(), outbufferpos);
filereadbytes = read (filefd, outbuffer+outbufferpos, filestat.st_size);
close (filefd);
if (filereadbytes <= 0) {
debug ("could not read all data from file '%s'. Error:%s\n",
fullfname.c_str(), strerror(errno));
free (outbuffer);
return 0;
}
filereadbytes += outbufferpos;
if (ssl) l = ssl->Write(outbuffer, filereadbytes);
else l = tcp->Write(outbuffer, filereadbytes);
// debug ("send file %s with %ld bytes\n", fname.c_str(), filereadbytes);
if (l != filereadbytes) {
debug ("could not send all data. Error:%s\n", strerror(errno));
free (outbuffer);
return 0;
}
free (outbuffer);
return 1;
};
std::string WebServerClient::Get_WebFile_FullPath(std::string file) {
std::string ret = file;
if (DocumentRoot.length() > 0) {
if (DocumentRoot[DocumentRoot.length()-1] == '/') {
if (file[0] == '/') ret = DocumentRoot + file.substr(1, std::string::npos);
else ret = DocumentRoot + file;
}
else {
if (file[0] == '/') ret = DocumentRoot + file;
else ret = DocumentRoot + "/" + file;
}
}
return ret;
};
WebServer::WebServer() {
running = 0;
conf_maxclients = 10;
conf_httpport = 8080;
conf_httpsport = 8081;
conf_docroot = "./www";
conf_sslcert = "./cert.pem";
conf_sslkey = "./privkey.pem";
};
WebServer::~WebServer() {
Stop();
};
/// @brief Setup new ports. Restart needed.
/// @param httpport
/// @param httpsport
/// @return
int WebServer::SetupPorts(int httpport, int httpsport) {
int wasrunning = running;
if (wasrunning) Stop();
conf_httpport = httpport;
conf_httpsport = httpsport;
if (wasrunning) Start();
return 1;
};
/// @brief Setup document root. Restart needed.
/// @param httpport
/// @param httpsport
/// @return
int WebServer::SetupDocumentRoot(std::string docroot) {
int wasrunning = running;
if (wasrunning) Stop();
conf_docroot = docroot;
if (wasrunning) Start();
return 1;
};
/// @brief Setup SSL key and SSL certificate. Reconnect needed
/// @param sslkeyfile
/// @param sslcertfile
/// @return
int WebServer::SetupSSL(std::string sslkeyfile, std::string sslcertfile) {
conf_sslkey = sslkeyfile;
conf_sslcert = sslcertfile;
return 1;
};
int WebServer::Start() {
if (http.Listen(conf_httpport) != 1) {
printf ("error on listen\n");
return 0;
}
printf ("test server is running on port: %d\n", conf_httpport);
if (https.Listen(conf_httpsport) != 1) {
printf ("error on listen\n");
return 0;
}
printf ("test server is running on port: %d\n", conf_httpsport);
running = 1;
return 1;
};
int WebServer::Stop() {
http.Close();
https.Close();
list<WebServerClient*>::iterator wci;
for (wci = webclients.begin(); wci != webclients.end(); wci++) {
delete (WebServerClient*) *wci;
webclients.remove (*wci);
wci = webclients.begin();
if (wci == webclients.end()) continue;
}
return 1;
};
/// @brief Checks for new connections and for data on any connected webclient
/// @return -2 client could not connect, -1 on max connections, 0 on error, 1 no error
int WebServer::Loop() {
list<WebServerClient*>::iterator wci;
WebServerClient *webclient;
TCP *tcpclient = NULL;
int ssl_enabled = 1;
int res;
//
// any new connection?
if ((tcpclient = https.Accept()) == NULL) {
ssl_enabled = 0;
tcpclient = http.Accept();
}
if (tcpclient) {
printf ("new %s connection from %s\n", ssl_enabled ? "HTTPS" : "HTTP", tcpclient->GetRemoteAddr().c_str());
if (webclients.size() > conf_maxclients) {
printf ("max connections reached. closing connection.\n");
tcpclient->Close();
delete tcpclient;
return -1;
}
webclient = new WebServerClient();
if (ssl_enabled) res = webclient->Accept(tcpclient, conf_sslkey, conf_sslcert);
else res = webclient->Accept(tcpclient);
if (res == 0) {
printf ("webclient could not connect. closing connection\n");
delete webclient;
delete tcpclient;
return -2;
}
webclient->SetDecoumentRoot("./www");
webclients.push_back(webclient);
printf ("add new connection to client list\n");
}
//
// go through all the clients and check for new requests
for (wci = webclients.begin(); wci != webclients.end(); wci++) {
WebRequestBuffer *reqbuffer = NULL;
webclient = *wci;
reqbuffer = webclient->Loop();
if (reqbuffer == NULL) {
// error on loop, remove and delete webclient.
printf ("remove connection\n");
webclients.remove(webclient);
delete webclient;
wci = webclients.begin();
if (wci == webclients.end()) continue;
}
else if (reqbuffer->RequestComplete()) {
if (HandleRequest(reqbuffer, *wci) == 1) continue;
// error handling request, remove and delete webclient.
printf ("remove connection\n");
webclients.remove(webclient);
delete webclient;
wci = webclients.begin();
if (wci == webclients.end()) continue;
}
}
return 1;
};
int WebServer::HandleRequest (WebRequestBuffer *requestbuffer, WebServerClient *webclient) {
if (requestbuffer == NULL || webclient == NULL) return 0;
std::string request = requestbuffer->GetRequest();
printf ("WebServerClient::HandleRequest() Request:%s\n", request.c_str());
if (request.compare ("/") == 0) request = "/index.html";
if (webclient->SendResponseFile(requestbuffer, request, "") != 1) return 0;
requestbuffer->Clear();
return 1;
};