#include "UDPTCPNetwork.h" #include #include #include #include #include #include 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) 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; } 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::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::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; };