#pragma once //#define GEKKO #include "tesseswebserverfeatures.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(USE_JANSSON) #include #endif #if defined(USE_CURL) #include #include #endif #if defined(USE_SQLITE3) #include #endif #if defined(USE_MBEDTLS) #include #endif #if defined(DEBUG) #define DEBUGOUT(...) printf(__VA_ARGS__) #else #define DEBUGOUT(...) #endif #if defined(GEKKO) #include #include #if defined(HW_RVL) && defined(USE_WIISOCKETS) #include #include #else #include #define socket net_socket #define bind net_bind #define listen net_listen #define recv net_recv #define send net_send #define close net_close #endif #else #include #include #include #include #endif namespace Tesses::WebServer { #if defined(GEKKO) typedef void* THREAD_RETURN_TYPE; #define THREAD_DEFAULT_RETURN_TYPE (NULL) typedef THREAD_RETURN_TYPE (*callback)(void* arg); #else typedef int THREAD_RETURN_TYPE; #define THREAD_DEFAULT_RETURN_TYPE 0 typedef THREAD_RETURN_TYPE (*callback)(void* arg); #endif class Statics { public: #if defined(GEKKO) std::vector joined_threads; mutex_t mtx; #else mtx_t mtx; #endif std::atomic isRunning; }; extern Tesses::WebServer::Statics tesses_webserver_statics; class Thread { #if defined(GEKKO) lwp_t thrd; std::atomic hasExited; callback cb; void* arg; #else thrd_t thrd; #endif public: static void Init() { #if defined(GEKKO) LWP_MutexInit(&tesses_webserver_statics.mtx,true); #else mtx_init(&tesses_webserver_statics.mtx,mtx_recursive); #endif } Thread(callback cb,void* arg,uint32_t stackSize) { #if defined(GEKKO) this->hasExited=false; this->cb = cb; this->arg = arg; LWP_CreateThread(&this->thrd,HANDLER,arg,NULL,stackSize,80); #else thrd_create(&thrd,cb,arg); #endif } #if defined(GEKKO) static THREAD_RETURN_TYPE HANDLER(void* arg) { Thread* thrd = (Thread*)arg; THREAD_RETURN_TYPE t = thrd->cb(arg); thrd->hasExited=true; return t; } #endif int Join(THREAD_RETURN_TYPE* retType) { #if defined(GEKKO) return LWP_JoinThread(this->thrd,retType); #else return thrd_join(this->thrd,retType); #endif } void Detach() { #if defined(GEKKO) Lock(); tesses_webserver_statics.joined_thrads.push_back(this); Unlock(); #else thrd_detach(this->thrd); delete this; #endif } static void Lock() { #if defined(GEKKO) LWP_MutexLock(tesses_webserver_statics.mtx); #else mtx_lock(&tesses_webserver_statics.mtx); #endif } static void Unlock() { #if defined(GEKKO) LWP_MutexUnlock(tesses_webserver_statics.mtx); #else mtx_unlock(&tesses_webserver_statics.mtx); #endif } static void CleanThreads() { #if defined(GEKKO) Lock(); for(auto i = tesses_webserver_statics.joined_threads.begin();ihasExited) { THREAD_RETURN_TYPE arg; item->Join(&arg); delete item; tesses_webserver_statics.joined_threads.erase(i); i--; } } Unlock(); #endif } }; class Stream { public: virtual ssize_t Read(uint8_t* buffer,size_t len)=0; virtual ssize_t Write(uint8_t* buffer,size_t len)=0; virtual void Seek(uint64_t offset,int whence) { } virtual int64_t Position() { return 0; } virtual bool CanSeek() { return false; } void WriteBlock(uint8_t* buffer,size_t len) { size_t read; do{ read = 1024; if(len < 1024) read = len; if(read > 0) read=this->Write(buffer,read); buffer += read; len -= read; } while(read > 0); } virtual int64_t Length() { return -1; } virtual ~Stream() { } int ReadByte() { uint8_t byte; if(Read(&byte,1) == 0) return -1; return byte; } std::string ReadLine() { std::string str = {}; int c = ReadByte(); while(c != '\n' && c != -1) { if(c != '\r') { str.push_back((char)c); } c = ReadByte(); } DEBUGOUT("ReadLine: %s\n",str.c_str()); return str; } }; class FileStream : public Stream { private: FILE* f; bool canSeek; bool owns; public: FileStream(std::string filename, std::string mode) { this->f = fopen(filename.c_str(),mode.c_str()); this->canSeek=true; this->owns=true; } FileStream(FILE* f,bool canSeek,bool owns) { this->f = f; this->canSeek = canSeek; this->owns = owns; } ssize_t Read(uint8_t* buffer,size_t len) { return (ssize_t)fread(buffer,1,len,this->f); } ssize_t Write(uint8_t* buffer, size_t len) { return (ssize_t)fwrite(buffer,1,len,this->f); } int64_t Position() { #if(_FILE_OFFSET_BITS == 64) #if defined(WIN32) return (int64_t)_ftelli64(this->f); #else return (int64_t)ftello(this->f); #endif #else return (int64_t)ftell(this->f); #endif } void Seek(uint64_t offset,int whence) { #if(_FILE_OFFSET_BITS == 64) #if defined(WIN32) _ftelli64(this->f,(__int64)offset,whence); #else fseeko(this->f,(off_t)offset,whence); #endif #else fseek(this->f,(long)offset,whence); #endif } }; class SocketStream : public Stream { int sock; public: SocketStream(int sock) { this->sock = sock; } ssize_t Read(uint8_t* buffer,size_t len) { ssize_t r= recv(this->sock,buffer,len,0); return r; } ssize_t Write(uint8_t* buffer,size_t len) { return send(this->sock,buffer,len,0); } ~SocketStream() { close(sock); } }; typedef enum StatusCode { Continue=100, SwitchingProtocols=101, Processing=102, EarlyHints=103, OK=200, Created=201, Accepted=202, NonAuthoritativeInformation=203, NoContent=204, ResetContent=205, PartialContent=206, MultiStatus=207, AlreadyReported=208, IMUsed=226, MultipleChoices=300, MovedPermanently=301, Found=302, SeeOther=303, NotModified=304, UseProxy=305, TemporaryRedirect=307, PermanentRedirect=308, BadRequest=400, Unauthorized=401, PaymentRequired=402, Forbidden=403, NotFound=404, MethodNotAllowed=405, NotAcceptable=406, ProxyAuthenticationRequired=407, RequestTimeout=408, Conflict=409, Gone=410, LengthRequired=411, PreconditionFailed=412, PayloadTooLarge=413, URITooLong=414, UnsupportedMediaType=415, RangeNotSatisfiable=416, ExpectationFailed=417, ImATeapot=418, MisdirectedRequest=421, UnprocessableContent=422, Locked=423, FailedDependency=424, TooEarly=425, UpgradeRequired=426, PreconditionRequired=428, TooManyRequests=429, RequestHeaderFieldsTooLarge=431, UnavailableForLegalReasons=451, InternalServerError=500, NotImplemented=501, BadGateway=502, ServiceUnavailable=503, GatewayTimeout=504, HTTPVersionNotSupported=505, VariantAlsoNegotiates=506, InsufficientStorage=507, LoopDetected=508, NotExtended=510, NetworkAuthenticationRequired=511 } StatusCode; class HttpUtils { public: static char nibble_to_hex_char(uint8_t number) { if(number >= 0 && number <= 9) { return '0'+number; } if(number >= 0xA && number <= 0xF) { return (char)(55+number); } return '0'; } static uint8_t hex_char_to_nibble(char c) { if(c >= '0' && c <= '9') return c - '0'; if(c >= 'A' && c <= 'F') return (c-'A')+0xA; if(c >= 'a' && c <= 'f') return (c-'a')+0xA; return 0; } static std::vector> QueryParamsDecode(std::string query) { std::vector> strs; for(auto item : SplitString(query,"&")) { std::vector ss=SplitString(item,"=",2); if(ss.size() >= 1) { std::string value = {}; if(ss.size() == 2) { value = UrlDecode(ss[1]); } strs.push_back(std::pair(ss[0],value)); } } return strs; } static std::string QueryParamsEncode(std::vector> query) { std::string s={}; bool first = true; for(auto item : query) { if(!first) { s.push_back('&'); } s.insert(s.size(),item.first); s.push_back('='); s.insert(s.size(),UrlEncode(item.second)); first=false; } return s; } static std::string UrlDecode(std::string v) { std::string s = {}; for(size_t i = 0;i= 'A' && item <= 'Z') s.push_back(item); else if(item >= 'a' && item <= 'z') s.push_back(item); else if(item >= '0' && item <= '9') s.push_back(item); else if(item == '-' || item == '_' || item == '.' || item == '~') s.push_back(item); else { s.push_back('%'); s.push_back(nibble_to_hex_char((item >> 4) & 0xF)); s.push_back(nibble_to_hex_char((item) & 0xF)); } } return s; } static std::string UrlPathDecode(std::string v) { std::string s = {}; for(size_t i = 0;i= 'A' && item <= 'Z') s.push_back(item); else if(item >= 'a' && item <= 'z') s.push_back(item); else if(item >= '0' && item <= '9') s.push_back(item); else if(item == '-' || item == '_' || item == '.' || item == '~') s.push_back(item); else { s.push_back('%'); s.push_back(nibble_to_hex_char((item >> 4) & 0xF)); s.push_back(nibble_to_hex_char((item) & 0xF)); } } return s; } static std::vector SplitString(std::string text, std::string delimiter,std::size_t maxCnt = std::string::npos) { std::vector strs; std::size_t i = 1; while(text.length() > 0) { if(i == maxCnt) { strs.push_back(text); break; } std::size_t index= text.find_first_of(delimiter); if(index == std::string::npos) { strs.push_back(text); break; } else { std::string left = text.substr(0,index); text = text.substr(index+delimiter.size()); strs.push_back(left); } i++; } return strs; } static std::string HtmlEncode(std::string html) { std::string myHtml = {}; for(auto item : html) { if(item == '\"') { myHtml.append("""); } else if(item == '\'') { myHtml.append("'"); } else if(item == '&') { myHtml.append("&"); } else if(item == '<') { myHtml.append("<"); } else if(item == '>') { myHtml.append(">"); } else { myHtml.push_back(item); } } return myHtml; } static std::string StatusCodeString(StatusCode code) { switch(code) { case StatusCode::Continue: return "Continue"; case StatusCode::SwitchingProtocols: return "Switching Protocols"; case StatusCode::Processing: return "Processing"; case StatusCode::EarlyHints: return "Early Hints"; case StatusCode::OK: return "OK"; case StatusCode::Created: return "Created"; case StatusCode::Accepted: return "Accepted"; case StatusCode::NonAuthoritativeInformation: return "Non-Authoritative Information"; case StatusCode::NoContent: return "No Content"; case StatusCode::ResetContent: return "Reset Content"; case StatusCode::PartialContent: return "PartialContent"; case StatusCode::MultiStatus: return "Multi-Status"; case StatusCode::AlreadyReported: return "Already Reported"; case StatusCode::IMUsed: return "IM Used"; case StatusCode::MultipleChoices: return "Multiple Choices"; case StatusCode::MovedPermanently: return "Moved Permanently"; case StatusCode::Found: return "Found"; case StatusCode::SeeOther: return "See Other"; case StatusCode::NotModified: return "Not Modified"; case StatusCode::UseProxy: return "Use Proxy"; case StatusCode::TemporaryRedirect: return "Temporary Redirect"; case StatusCode::PermanentRedirect: return "Permanent Redirect"; case StatusCode::BadRequest: return "Bad Request"; case StatusCode::Unauthorized: return "Unauthorized"; case StatusCode::PaymentRequired: return "Payment Required"; case StatusCode::Forbidden: return "Forbidden"; case StatusCode::NotFound: return "Not Found"; case StatusCode::MethodNotAllowed: return "Method Not Allowed"; case StatusCode::NotAcceptable: return "Not Acceptable"; case StatusCode::ProxyAuthenticationRequired: return "Proxy Authentication Required"; case StatusCode::RequestTimeout: return "Request Timeout"; case StatusCode::Conflict: return "Conflict"; case StatusCode::Gone: return "Gone"; case StatusCode::LengthRequired: return "Length Required"; case StatusCode::PreconditionFailed: return "Precondition Failed"; case StatusCode::PayloadTooLarge: return "Payload Too Large"; case StatusCode::URITooLong: return "URI Too Long"; case StatusCode::UnsupportedMediaType: return "Unsupported Media Type"; case StatusCode::RangeNotSatisfiable: return "Range Not Satisfiable"; case StatusCode::ExpectationFailed: return "Expectation Failed"; case StatusCode::ImATeapot: return "I'm a teapot"; case StatusCode::MisdirectedRequest: return "Misdirected Request"; case StatusCode::UnprocessableContent: return "Unprocessable Content"; case StatusCode::Locked: return "Locked"; case StatusCode::FailedDependency: return "Failed Dependency"; case StatusCode::TooEarly: return "Too Early"; case StatusCode::UpgradeRequired: return "Upgrade Required"; case StatusCode::PreconditionRequired: return "Precondition Required"; case StatusCode::TooManyRequests: return "Too Many Requests"; case StatusCode::RequestHeaderFieldsTooLarge: return "Request Header Fields Too Large"; case StatusCode::UnavailableForLegalReasons: return "Unavailable For Legal Reasons"; case StatusCode::InternalServerError: return "Internal Server Error"; case StatusCode::NotImplemented: return "Not Implemented"; case StatusCode::ServiceUnavailable: return "Service Unavailable"; case StatusCode::GatewayTimeout: return "Gateway Timeout"; case StatusCode::HTTPVersionNotSupported: return "HTTP Version Not Supported"; case StatusCode::VariantAlsoNegotiates: return "Variant Also Negotiates"; case StatusCode::InsufficientStorage: return "Insufficient Storage"; case StatusCode::LoopDetected: return "Loop Detected"; case StatusCode::NotExtended: return "Not Extended"; case StatusCode::NetworkAuthenticationRequired: return "Network Authentication Required"; default: return ""; } } }; class QueryParams { private: std::vector> items; public: void Add(std::string key, std::string value) { Add(std::pair(key,value)); } void Add(std::pair pair) { items.push_back(pair); } void Add(std::vector>& items) { for(auto item : items) this->items.push_back(item); } std::vector>& GetAll() { return this->items; } void Get(std::vector& v, std::string key) { for(auto item : this->items) if(item.first == key) v.push_back(item.second); } void Set(std::string key,std::string value) { Clear(key); Add(key,value); } void Set(std::pair item) { Set(item.first,item.second); } void Set(std::string key, std::vector& value) { Clear(key); for(auto item : value) Add(key,item); } bool TryGetFirst(std::string key,std::string& val) { for(auto item : this->items) if(item.first == key) { val = item.second; return true; } return false; } void Clear(std::string key) { for(auto item = items.begin();itemstrm = strm; std::string firstLine = strm->ReadLine(); DEBUGOUT("READ Request line\n"); std::vector firstLineA=HttpUtils::SplitString(firstLine," ",4); DEBUGOUT("Split Request line\n"); if(firstLineA.size() == 4 || firstLineA.size() < 3) { DEBUGOUT("Request line does not have 3 entries\n"); this->ok=false; } else { this->ok=false; method = firstLineA[0]; std::vector p = HttpUtils::SplitString(firstLineA[1],"?",2); if(p.size() >= 1) { this->ok = true; this->originalPath = p[0]; DEBUGOUT("Path %s\n",this->originalPath.c_str()); this->path = originalPath; if(p.size() == 2) { auto qp=HttpUtils::QueryParamsDecode(p[1]); this->queryParams.Add(qp); } } version = firstLineA[2]; responseVersion=version; std::string line; while((line=strm->ReadLine()).size() > 0) { std::vector header=HttpUtils::SplitString(line,": ",2); if(header.size() == 2) { this->requestHeaders.Add(std::pair(header[0],header[1])); } } } } bool CanHandleRequest() { return this->ok; } std::string method; std::string originalPath; std::string path; std::string version; std::string responseVersion; bool ConnectionOpen() { std::string keep_alive; if(requestHeaders.TryGetFirst("Connection",keep_alive)) { return keep_alive.compare("keep-alive") == 0; } return false; } QueryParams queryParams; QueryParams requestHeaders; QueryParams responseHeaders; StatusCode statusCode=StatusCode::OK; void SendHeaders() { std::string headers = responseVersion + " " + std::to_string((int)statusCode) + " " + HttpUtils::StatusCodeString(statusCode) + "\r\n"; for(auto header : responseHeaders.GetAll()) { headers += header.first + ": " + header.second + "\r\n"; } headers += "\r\n"; this->strm->WriteBlock((uint8_t*)headers.c_str(),headers.size()); } void SendErrorPage(bool showPath) { std::string errorHtml = showPath ? ("File " + HttpUtils::HtmlEncode(this->originalPath) + " " + HttpUtils::StatusCodeString(this->statusCode) + "

" + std::to_string((int)this->statusCode) + " " + HttpUtils::StatusCodeString(this->statusCode) + "

" + HttpUtils::HtmlEncode(this->originalPath) + "

") : ""; SetContentType("text/html")->SendText(errorHtml); } ServerContext* SetContentType(std::string contentType) { return this->SetHeader("Content-Type",contentType); } ServerContext* SetHeader(std::string key,std::string value) { this->responseHeaders.Set(key,value); return this; } void SendBytes(uint8_t* buffer,size_t len) { this->SetHeader("Content-Length",std::to_string(len))->SendHeaders(); this->strm->WriteBlock(buffer,len); } void SendText(std::string text) { SendBytes((uint8_t*)text.c_str(),text.size()); } void SendNotFound() { statusCode = StatusCode::NotFound; SendErrorPage(true); } void SendBadRequest() { statusCode = StatusCode::BadRequest; SendErrorPage(false); } ~ServerContext() { if(!ConnectionOpen()) delete strm; } }; class IServer { public: virtual bool Handle(ServerContext* ctx)=0; }; #if defined(USE_SCRIPT_ENGINE) #define INCLUDED_FROM_TESSESWEBSERVER_H #include "tessesscriptengine.hpp" #undef INCLUDED_FROM_TESSESWEBSERVER_H #endif class HttpServerListener { static THREAD_RETURN_TYPE HandleRequestOnThread(void* arg) { DEBUGOUT("Started thread\n"); void** args = (void**)arg; IServer* svr= static_cast(args[0]); Stream* strm = static_cast(args[1]); delete args; HandleRequest(svr,strm); return THREAD_DEFAULT_RETURN_TYPE; } public: static void Init() { tesses_webserver_statics.isRunning=true; Thread::Init(); } static void HandleRequest(IServer* server,Stream* strm) { start: ServerContext* ctx = new ServerContext(strm); if(!ctx->CanHandleRequest()) { ctx->SendBadRequest(); delete ctx; return; } if(!server->Handle(ctx)) ctx->SendNotFound(); if(ctx->ConnectionOpen()) { delete ctx; goto start; } delete ctx; } static void ListenWithBoundSocket(IServer* server,int boundAndListeningSocket) { printf("\033[31mAlmost ready to listen\033[0m\n"); while(tesses_webserver_statics.isRunning) { struct sockaddr addr; socklen_t addrlen; int a = accept(boundAndListeningSocket, &addr, &addrlen); void** myArg = new void*[2]; myArg[0] = static_cast(server); myArg[1] = static_cast(new SocketStream(a)); Thread* thread = new Thread(HandleRequestOnThread,myArg,512000); thread->Detach(); /*ServerContext* ctx = ReadHeaders(a); if(!server->Handle(ctx)) ctx->SendNotFound(); delete ctx; close(a); */ } } static void Listen(IServer* server, unsigned int domain,const struct sockaddr* addr, socklen_t addrlen) { int s=socket(domain,SOCK_STREAM,0); if(s == -1) { printf("Socket error\n"); throw std::exception(); } if(bind(s,addr,addrlen) == -1) { printf("Bind error\n"); throw std::exception(); } if(listen(s,10) == -1) { printf("Listen error\n"); throw std::exception(); } ListenWithBoundSocket(server,s); } static void Listen(IServer* server, int port) { struct sockaddr_in addr; memset(&addr,0,sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons((uint16_t)port); addr.sin_addr.s_addr = 0; Listen(server,AF_INET,(struct sockaddr*)&addr,(socklen_t)sizeof(addr)); } static void DoJobs() { #if defined(GEKKO) Thread::CleanThreads(); #endif } static void Stop() { } }; #define TESSESWEBSERVER_STATIC_DECLARATION Tesses::WebServer::Statics Tesses::WebServer::tesses_webserver_statics; }