1142 lines
29 KiB
C++
1142 lines
29 KiB
C++
#pragma once
|
|
//#define GEKKO
|
|
#include "tesseswebserverfeatures.hpp"
|
|
|
|
#include <variant>
|
|
#include <cstdio>
|
|
#include <iostream>
|
|
#include <exception>
|
|
#include <vector>
|
|
#include <atomic>
|
|
#include <string>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <initializer_list>
|
|
#include <map>
|
|
#include <stack>
|
|
|
|
#include <functional>
|
|
#include <cmath>
|
|
#include <bit>
|
|
#include <cctype>
|
|
|
|
#if defined(USE_JANSSON)
|
|
#include <jansson.h>
|
|
#endif
|
|
|
|
#if defined(USE_CURL)
|
|
#include <curl/curl.h>
|
|
#include <curl/easy.h>
|
|
#endif
|
|
|
|
#if defined(USE_SQLITE3)
|
|
#include <sqlite3.h>
|
|
#endif
|
|
|
|
#if defined(USE_MBEDTLS)
|
|
#include <mbedtls/ssl.h>
|
|
#endif
|
|
|
|
#if defined(DEBUG)
|
|
|
|
#define DEBUGOUT(...) printf(__VA_ARGS__)
|
|
#else
|
|
#define DEBUGOUT(...)
|
|
#endif
|
|
|
|
#if defined(GEKKO)
|
|
#include <ogc/lwp.h>
|
|
#include <ogc/mutex.h>
|
|
|
|
#if defined(HW_RVL) && defined(USE_WIISOCKETS)
|
|
#include <sys/socket.h>
|
|
#include <wiisocket.h>
|
|
#else
|
|
#include <network.h>
|
|
#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 <threads.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <unistd.h>
|
|
#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<Thread*> joined_threads;
|
|
mutex_t mtx;
|
|
#else
|
|
mtx_t mtx;
|
|
#endif
|
|
std::atomic<bool> isRunning;
|
|
};
|
|
extern Tesses::WebServer::Statics tesses_webserver_statics;
|
|
class Thread {
|
|
#if defined(GEKKO)
|
|
lwp_t thrd;
|
|
std::atomic<bool> 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();i<tesses_webserver_statics.joined_threads.end();i++)
|
|
{
|
|
auto item = *i;
|
|
if(item->hasExited)
|
|
{
|
|
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<std::pair<std::string,std::string>> QueryParamsDecode(std::string query)
|
|
{
|
|
std::vector<std::pair<std::string,std::string>> strs;
|
|
for(auto item : SplitString(query,"&"))
|
|
{
|
|
std::vector<std::string> ss=SplitString(item,"=",2);
|
|
if(ss.size() >= 1)
|
|
{
|
|
std::string value = {};
|
|
if(ss.size() == 2)
|
|
{
|
|
value = UrlDecode(ss[1]);
|
|
}
|
|
strs.push_back(std::pair<std::string,std::string>(ss[0],value));
|
|
}
|
|
}
|
|
return strs;
|
|
}
|
|
|
|
static std::string QueryParamsEncode(std::vector<std::pair<std::string,std::string>> 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<v.size();i++)
|
|
{
|
|
if(v[i] == '+')
|
|
s.push_back(' ');
|
|
else if(v[i] == '%')
|
|
{
|
|
i++;
|
|
uint8_t n = hex_char_to_nibble(v[i])<<4;
|
|
i++;
|
|
n |= hex_char_to_nibble(v[i]);
|
|
s.push_back((char)n);
|
|
}
|
|
else
|
|
s.push_back(v[i]);
|
|
|
|
}
|
|
return s;
|
|
}
|
|
static std::string UrlPathEncode(std::string v)
|
|
{
|
|
std::string s = {};
|
|
|
|
for(auto item : v)
|
|
{
|
|
if(item >= '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<v.size();i++)
|
|
{
|
|
if(v[i] == '%')
|
|
{
|
|
i++;
|
|
uint8_t n = hex_char_to_nibble(v[i])<<4;
|
|
i++;
|
|
n |= hex_char_to_nibble(v[i]);
|
|
s.push_back((char)n);
|
|
}
|
|
else
|
|
s.push_back(v[i]);
|
|
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static std::string UrlEncode(std::string v)
|
|
{
|
|
std::string s = {};
|
|
|
|
for(auto item : v)
|
|
{
|
|
if(item == ' ')
|
|
s.push_back('+');
|
|
else if(item >= '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<std::string> SplitString(std::string text, std::string delimiter,std::size_t maxCnt = std::string::npos)
|
|
{
|
|
std::vector<std::string> 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<std::pair<std::string,std::string>> items;
|
|
public:
|
|
void Add(std::string key, std::string value)
|
|
{
|
|
Add(std::pair<std::string,std::string>(key,value));
|
|
}
|
|
void Add(std::pair<std::string,std::string> pair)
|
|
{
|
|
items.push_back(pair);
|
|
}
|
|
void Add(std::vector<std::pair<std::string,std::string>>& items)
|
|
{
|
|
for(auto item : items)
|
|
this->items.push_back(item);
|
|
}
|
|
std::vector<std::pair<std::string,std::string>>& GetAll()
|
|
{
|
|
return this->items;
|
|
}
|
|
void Get(std::vector<std::string>& 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<std::string,std::string> item)
|
|
{
|
|
Set(item.first,item.second);
|
|
}
|
|
|
|
void Set(std::string key, std::vector<std::string>& 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();item<items.end();item++)
|
|
{
|
|
if((*item).first == key)
|
|
{
|
|
items.erase(item);
|
|
item--;
|
|
}
|
|
}
|
|
}
|
|
void Clear()
|
|
{
|
|
items.clear();
|
|
}
|
|
};
|
|
|
|
|
|
class ServerContext {
|
|
Stream* strm;
|
|
bool ok;
|
|
public:
|
|
ServerContext(Stream* strm)
|
|
{
|
|
DEBUGOUT("ServerContext Constructed\n");
|
|
this->strm = strm;
|
|
std::string firstLine = strm->ReadLine();
|
|
DEBUGOUT("READ Request line\n");
|
|
std::vector<std::string> 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<std::string> 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<std::string> header=HttpUtils::SplitString(line,": ",2);
|
|
if(header.size() == 2)
|
|
{
|
|
this->requestHeaders.Add(std::pair<std::string,std::string>(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 ? ("<html><head><title>File " + HttpUtils::HtmlEncode(this->originalPath) + " " + HttpUtils::StatusCodeString(this->statusCode) + "</title></head><body><h1>" + std::to_string((int)this->statusCode) + " " + HttpUtils::StatusCodeString(this->statusCode) + "</h1><h4>" + HttpUtils::HtmlEncode(this->originalPath) + "</h4></body></html>") : "";
|
|
|
|
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<IServer*>(args[0]);
|
|
Stream* strm = static_cast<Stream*>(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<void*>(server);
|
|
myArg[1] = static_cast<void*>(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;
|
|
|
|
|
|
|
|
} |