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.
514 lines
14 KiB
514 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. bytestosend:%d l:%d Error:%s\n", bytestosend, l, 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) && (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);
|
|
}
|
|
|
|
//
|
|
// send header and file
|
|
if (filefd <= 0) {
|
|
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;
|
|
}
|
|
else {
|
|
header = CreateHeader (request, error, filetype, filestat.st_size, addheader);
|
|
header = header + GenErrorHtml(error, "some internal server error");
|
|
outbuffer = (char*) malloc (header.length() + 16);
|
|
memcpy (outbuffer, header.c_str(), header.length());
|
|
}
|
|
|
|
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;
|
|
};
|
|
|
|
std::string WebServerClient::GenErrorHtml(int errorcode, std::string errortext) {
|
|
std::string text;
|
|
text = "<html><head><title>ERROR - " + to_string(errorcode) + "</title></head>";
|
|
text += "<body><h1>"+to_string(errorcode)+"</h1><p>"+errortext+"</p></body></html>";
|
|
return text;
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
if (https.Listen(conf_httpsport) != 1) {
|
|
printf ("error on listen\n");
|
|
return 0;
|
|
}
|
|
|
|
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) {
|
|
|
|
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);
|
|
}
|
|
|
|
//
|
|
// 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.
|
|
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.
|
|
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();
|
|
|
|
if (request.compare ("/") == 0) request = "/index.html";
|
|
if (webclient->SendResponseFile(requestbuffer, request, "") != 1) return 0;
|
|
|
|
requestbuffer->Clear();
|
|
return 1;
|
|
};
|
|
|