tesses-webserver-cpp/wwwgen.cpp

504 lines
18 KiB
C++

#include <fstream>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <filesystem>
#include "src/tesseswebserverhttputils.hpp"
void byte_array_f(std::string& s,FILE* f)
{
s.push_back('{');
uint8_t buffer[1024];
size_t read = 0;
bool first=true;
do {
read=fread(buffer,1,1024,f);
for(size_t i = 0;i<read;i++)
{
if(!first) s.push_back(',');
s.append("0x");
uint8_t high = (uint8_t)((buffer[i] >> 4) & 0x0F);
uint8_t low = (uint8_t)(buffer[i] & 0x0F);
s.push_back(HttpUtils::nibble_to_hex_char(high));
s.push_back(HttpUtils::nibble_to_hex_char(low));
first=false;
}
} while(read > 0);
s.push_back('}');
}
void byte_array_a(std::string& s,uint8_t* buffer,size_t read)
{
s.push_back('{');
bool first=true;
for(size_t i = 0;i<read;i++)
{
if(!first) s.push_back(',');
s.append("0x");
uint8_t high = (uint8_t)((buffer[i] >> 4) & 0x0F);
uint8_t low = (uint8_t)(buffer[i] & 0x0F);
s.push_back(HttpUtils::nibble_to_hex_char(high));
s.push_back(HttpUtils::nibble_to_hex_char(low));
first=false;
}
s.push_back('}');
}
class Resource {
public:
std::string mime;
std::string data;
std::string path;
bool twss;
};
void follow_symlink(std::filesystem::path& p)
{
while(std::filesystem::is_symlink(p))
{
p = std::filesystem::read_symlink(p);
}
}
void get_files(std::vector<Resource>& resources, std::vector<std::string>& indexes, bool enableListing, std::filesystem::path srcDir, std::string httpDir)
{
std::filesystem::path index;
bool useIndexFile=false;
for(auto item : indexes)
{
std::filesystem::path myIndex = srcDir / item;
follow_symlink(myIndex);
if(std::filesystem::is_regular_file(myIndex))
{
index = myIndex;
useIndexFile=true;
break;
}
}
bool mustList=false;
std::vector<std::pair<std::string,bool>> paths;
if(useIndexFile)
{
Resource res;
res.path = httpDir;
res.twss = index.extension() == ".twss";
FILE* f = fopen(index.c_str(),"rb");
byte_array_f(res.data,f);
fclose(f);
res.mime = HttpUtils::MimeType(index);
resources.push_back(res);
if(!httpDir.ends_with('/'))
{
res.path = httpDir + "/";
resources.push_back(res);
}
}
else if(enableListing)
{
mustList=true;
}
for(auto item : std::filesystem::directory_iterator(srcDir))
{
auto ent = item.path();
follow_symlink(ent);
if(std::filesystem::is_directory(ent))
{
get_files(resources, indexes, enableListing,ent,httpDir.ends_with('/') ? (httpDir + item.path().filename().string()) : (httpDir + "/" + item.path().filename().string()));
if(mustList)
paths.push_back(std::pair<std::string,bool>(item.path().filename().string(),true));
}
if(std::filesystem::is_regular_file(ent))
{
Resource res;
res.path = httpDir.ends_with('/') ? (httpDir + item.path().filename().string()) : (httpDir + "/" + item.path().filename().string());
res.twss = item.path().extension() == ".twss";
FILE* f = fopen(ent.c_str(),"rb");
byte_array_f(res.data,f);
fclose(f);
res.mime = HttpUtils::MimeType(ent);
resources.push_back(res);
if(mustList)
paths.push_back(std::pair<std::string,bool>(item.path().filename().string(),false));
}
}
if(mustList)
{
std::string html = "<html><head><title>Index of ";
html.append(HttpUtils::HtmlEncode(httpDir.ends_with('/') ? httpDir : (httpDir + "/")));
html.append("</title></head><body><h1>Index of ");
html.append(HttpUtils::HtmlEncode(httpDir.ends_with('/') ? httpDir : (httpDir + "/")));
html.append("</h1><pre><hr><a href=\"../\">../</a>\r\n");
for(auto item : paths)
{
html.append("<a href=\"");
html.append(HttpUtils::UrlPathEncode(item.first));
if(item.second)
html.append("/\">");
else
html.append("\">");
html.append(HttpUtils::HtmlEncode(item.first));
if(item.second)
html.append("/</a>\r\n");
else
html.append("</a>\r\n");
}
html.append("</pre><hr></body></html>");
Resource res;
res.path = httpDir;
res.twss = false;
byte_array_a(res.data,(uint8_t*)&html[0],html.size());
res.mime = "text/html";
resources.push_back(res);
if(!httpDir.ends_with('/'))
{
res.path = httpDir + "/";
resources.push_back(res);
}
}
}
bool file_exists(const char* dir, const char* path)
{
std::filesystem::path p0(dir);
std::filesystem::path p1(path);
std::filesystem::path p2 = p0 / p1;
follow_symlink(p2);
return std::filesystem::is_regular_file(p2);
}
std::string escape_string(std::string path)
{
std::string s={};
s.push_back('\"');
for(auto item : path)
{
switch(item)
{
case '\\':
case '\"':
case '\'':
s.push_back('\\');
s.push_back(item);
break;
case '\0':
s.push_back('\\');
s.push_back('0');
break;
case '\t':
s.push_back('\\');
s.push_back('t');
break;
case '\n':
s.push_back('\\');
s.push_back('n');
break;
case '\r':
s.push_back('\\');
s.push_back('r');
break;
case '\v':
s.push_back('\\');
s.push_back('v');
break;
case '\f':
s.push_back('\\');
s.push_back('f');
break;
case '\b':
s.push_back('\\');
s.push_back('b');
break;
case '\a':
s.push_back('\\');
s.push_back('a');
break;
case '\?':
s.push_back('\\');
s.push_back('?');
break;
default:
{
if(item >= ' ' && item <= '~')
s.push_back(item);
else
{
s.push_back('\\');
s.push_back('x');
s.push_back(HttpUtils::nibble_to_hex_char((uint8_t)((item >> 4) & 0x0F)));
s.push_back(HttpUtils::nibble_to_hex_char((uint8_t)(item & 0x0F)));
}
}
break;
}
}
s.push_back('\"');
return s;
}
int main(int argc,char** argv)
{
const char* error = NULL;
const char* output = "WWWGen.hpp";
const char* source = "wwwroot";
bool enableScripts=true;
bool enableListing=true;
bool enableDefaultIndexes=true;
bool help=false;
std::vector<std::string> indexes;
for(int i = 1;i<argc;i++)
{
if(strcmp(argv[i],"--help") == 0 || strcmp(argv[i],"-h") == 0)
{
help = true;
break;
}
if(strcmp(argv[i],"--disable-script") == 0)
{
enableScripts = false;
}
if(strcmp(argv[i],"--disable-listing") == 0)
{
enableListing = false;
}
if(strcmp(argv[i],"--disable-default-indexes") == 0)
{
enableDefaultIndexes = false;
}
if(strcmp(argv[i],"-o") == 0 || strcmp(argv[i],"--output") == 0)
{
if(i+1<argc)
{
output = argv[i+1];
i++;
}
else
{
error = "Ran out of arguments";
help = true;
break;
}
}
if(strcmp(argv[i],"-s") == 0 || strcmp(argv[i],"--source") == 0)
{
if(i+1<argc)
{
source = argv[i+1];
i++;
}
else
{
error = "Ran out of arguments";
help = true;
break;
}
}
if(strcmp(argv[i],"-i") == 0 || strcmp(argv[i],"--index") == 0)
{
if(i+1<argc)
{
indexes.push_back(argv[i+1]);
i++;
}
else
{
error = "Ran out of arguments";
help = true;
break;
}
}
}
if(help)
{
if(error != NULL)
{
printf("ERROR: %s\n", error);
}
printf("USAGE: %s [options]\n\n",argv[0]);
printf("FLAGS:\n");
printf("-h, --help\tShow this help screen\n");
printf("--disable-script\tDisable .twss scripts from being ran\n");
printf("--disable-listing\tDisable directory listing on server\n");
printf("--disable-default-indexes\tDisable default index files \"index.html\", \"default.html\", \"index.htm\", \"default.htm\", \"index.twss\", \"default.twss\"\n");
printf("\n");
printf("OPTIONS:\n");
printf("-o [header file], --output [header file]\t(defaults to \"WWWGen.hpp\") Specify the output header filename with extension, be aware this is also the class name (without extension of course so no spaces)\n");
printf("-s [source dir], --source [source dir]\t(defaults to \"wwwroot\") Specify the source directory to convert\n");
printf("-i [index file], --index [index file]\tSpecify custom index file such as index.html where going to / on server will resolve /index.html or /page will become /page/index.html, to use multiple files use this option again followed by filename\n");
printf("\nIf you run this program without any arguments the directory \"wwwroot\" and output file \"WWWGen.hpp\" will be used and scripts WILL be able to run, users WILL be able to list directories without index files and these index filenames will be used \"index.html\", \"default.html\", \"index.htm\", \"default.htm\", \"index.twss\", \"default.twss\"\n");
return 1;
}
else
{
if(enableDefaultIndexes)
{
indexes.push_back("index.html");
indexes.push_back("default.html");
indexes.push_back("index.htm");
indexes.push_back("default.htm");
if(enableScripts)
{
indexes.push_back("index.twss");
indexes.push_back("default.twss");
}
}
std::filesystem::path p(output);
std::string name = p.filename().replace_extension("").string();
std::vector<Resource> resources;
get_files(resources, indexes, enableListing,source,"/");
std::string cls = "#pragma once\n/* Generated via wwwgen */\n#include <string>\n#include <vector>\n#include <initializer_list>\n#include <filesystem>\n#include \"tesseswebserver.hpp\"\nclass " + name + "Resource {public: std::vector<uint8_t> data; std::string mime; std::string path; bool isScript; " + name + "Resource(std::string path, std::string mime,bool isScript, std::initializer_list<uint8_t> data) {this->path = path; this->mime = mime; this->isScript=isScript; this->data = data;} };\nclass " + name + " : public Tesses::WebServer::IServer {\n";
if(enableScripts)
{
cls.append("#if defined(USE_SCRIPT_ENGINE)\nTesses::WebServer::ScriptEngine::RootEnvironment* env;\nTesses::WebServer::ScriptEngine::BytecodeFile* initFile;\n#endif\n");
cls.append("\nstd::vector<uint8_t>* get_script_file(std::filesystem::path p){ int i = get_file(p); if(i > -1) return &(resources[i].data); return nullptr; }\n");
}
cls.append("int get_file(std::filesystem::path p){ int index = 0; for(auto item : resources) { if(p.string() == item.path) return index; index++; } return -1; }");
cls.append("std::filesystem::path sanitise(std::string name){std::filesystem::path p=\"/\"; auto path_parts = Tesses::WebServer::HttpUtils::SplitString(name.substr(1),\"/\");for(auto item : path_parts){auto decoded = Tesses::WebServer::HttpUtils::Sanitise(Tesses::WebServer::HttpUtils::UrlPathDecode(item));if(decoded == \"..\" || decoded == \".\") continue; p /= decoded;} return p;}\n");
cls.append("std::vector<");
cls.append(name);
cls.append("Resource> resources;\n");
cls.append("public:\n");
cls.append(name);
cls.append("(){\n");
for(auto item : resources)
{
cls.append("resources.push_back(");
cls.append(name);
cls.append("Resource(");
cls.append(escape_string(item.path));
cls.push_back(',');
cls.append(escape_string(item.mime));
cls.append(item.twss ? ",true," : ",false,");
cls.append(item.data);
cls.append("));\n");
}
if(enableScripts)
{
cls.append("\n#if defined(USE_SCRIPT_ENGINE)\n");
cls.append("env = new Tesses::WebServer::ScriptEngine::RootEnvironment();");
cls.append("env->print = [](Tesses::WebServer::ScriptEngine::ScriptType t)->void {");
cls.append("std::cout << \"[Script]: \" << Tesses::WebServer::ScriptEngine::ConvertToString(t) << std::endl;");
cls.append("};");
if(file_exists(source,"init.twss"))
{
cls.append("Tesses::WebServer::ScriptEngine::ConstBufferReadFile readFile([this](std::filesystem::path p) -> std::vector<uint8_t>* { return get_script_file(p);});");
cls.append("Tesses::WebServer::ScriptEngine::BytecodeCompiler bcc(Tesses::WebServer::ScriptEngine::ScriptParser::Parse(&readFile,\"/init.twss\"));");
cls.append("bcc.Compile();");
cls.append("this->initFile = bcc.file;auto rf = bcc.file->rootFunction;rf->env = env;rf->isRoot=true;for(auto f : bcc.file->functions){auto rf2 = f.second;rf2->env = env;rf2->isRoot=false;env->SetValue(f.first,Tesses::WebServer::ScriptEngine::ObjectType(f.second));}rf->Execute(env,{});");
}
cls.append("\n#endif\n");
}
cls.append("}");
if(enableScripts)
{
cls.append("\n#if defined(USE_SCRIPT_ENGINE)\nTesses::WebServer::ScriptEngine::RootEnvironment* GetRootEnvironment(){return this->env;}\n#endif\n");
}
cls.append("bool Handle(Tesses::WebServer::ServerContext* ctx){auto path=sanitise(ctx->path); int index = get_file(path); if(index == -1) return false;");
if(enableScripts)
{
cls.append("\n#if defined(USE_SCRIPT_ENGINE)\n");
cls.append("if(resources[index].isScript) {");
cls.append("auto myEnv = new Tesses::WebServer::ScriptEngine::RootEnvironment();");
cls.append("myEnv->global = this->env;");
cls.append("std::string str={};");
cls.append("myEnv->print = [&str](Tesses::WebServer::ScriptEngine::ScriptType typ) -> void {");
cls.append("str.append(Tesses::WebServer::ScriptEngine::ConvertToString(typ));");
cls.append("};");
cls.append("Tesses::WebServer::ScriptEngine::ConstBufferReadFile readFile([this](std::filesystem::path p) -> std::vector<uint8_t>* { return get_script_file(p);});");
cls.append("Tesses::WebServer::ScriptEngine::BytecodeCompiler bcc(Tesses::WebServer::ScriptEngine::ScriptParser::Parse(&readFile,resources[index].path));");
cls.append("bcc.Compile();");
cls.append("auto rf = bcc.file->rootFunction;");
cls.append("rf->env = myEnv;");
cls.append("rf->isRoot=true;");
cls.append("for(auto f : bcc.file->functions)");
cls.append("{");
cls.append("auto rf2 = f.second;");
cls.append("rf2->env = myEnv;");
cls.append("rf2->isRoot=false;");
cls.append("env->SetValue(f.first,Tesses::WebServer::ScriptEngine::ObjectType(f.second));");
cls.append("}");
cls.append("myEnv->RegisterHttpFuncs(ctx);");
cls.append("rf->Execute(myEnv,{});");
cls.append("delete bcc.file;");
cls.append("delete myEnv;");
cls.append("ctx->SetContentType(\"text/html\")->SendText(str);");
cls.append("}else{\n#endif\n");
}
cls.append("ctx->SetContentType(resources[index].mime)->SendBytes(resources[index].data.data(),resources[index].data.size());");
if(enableScripts)
{
cls.append("\n#if defined(USE_SCRIPT_ENGINE)\n}\n#endif\n");
}
cls.append("return true;}");
if(enableScripts){
cls.append("~");
cls.append(name);
cls.append("(){\n#if defined(USE_SCRIPT_ENGINE)\n");
if(file_exists(source,"deinit.twss"))
{
cls.append("Tesses::WebServer::ScriptEngine::ConstBufferReadFile readFile([this](std::filesystem::path p) -> std::vector<uint8_t>* { return get_script_file(p);});");
cls.append("Tesses::WebServer::ScriptEngine::BytecodeCompiler bcc(Tesses::WebServer::ScriptEngine::ScriptParser::Parse(&readFile,\"/deinit.twss\"));");
cls.append("bcc.Compile();");
cls.append("auto rf = bcc.file->rootFunction;rf->env = env;rf->isRoot=true;for(auto f : bcc.file->functions){auto rf2 = f.second;rf2->env = env;rf2->isRoot=false;env->SetValue(f.first,Tesses::WebServer::ScriptEngine::ObjectType(f.second));}rf->Execute(env,{});");
cls.append("delete bcc.file;");
}
cls.append("delete initFile;");
cls.append("delete env;");
cls.append("\n#endif\n}");
}
cls.append("};");
std::ofstream strm(output,std::ofstream::binary);
strm << cls << "\n";
}
return 0;
}