From bbf122a7eb18f35c41b839dfcb4048f753d548a9 Mon Sep 17 00:00:00 2001 From: Mike Nolan Date: Fri, 5 Dec 2025 18:08:09 -0600 Subject: [PATCH] Add CGI support --- src/compiler/parser.cpp | 43 +++++--- src/runtime_methods/net.cpp | 185 +++++++++++++++++++------------- src/runtime_methods/process.cpp | 8 ++ src/vm/vm.cpp | 113 +++++++++++++++++++ 4 files changed, 259 insertions(+), 90 deletions(-) diff --git a/src/compiler/parser.cpp b/src/compiler/parser.cpp index 18c6ad0..3feafa4 100644 --- a/src/compiler/parser.cpp +++ b/src/compiler/parser.cpp @@ -227,6 +227,14 @@ namespace Tesses::CrossLang } return false; } + static std::string EnsureSafeVariable(LexToken token) + { + if(token.text.find("__compGen") == 0) + { + throw SyntaxException(token.lineInfo,"__compGen* is reserved for compilers (this error is enforced by the compiler)"); + } + return token.text; + } void Parser::EnsureSymbol(std::string txt) { if(i < tokens.size()) @@ -841,6 +849,7 @@ namespace Tesses::CrossLang { if(i >= tokens.size()) throw std::out_of_range("End of file"); auto variable = tokens[i]; + i++; if(variable.type == LexTokenType::Symbol && variable.text == ".") @@ -861,7 +870,7 @@ namespace Tesses::CrossLang else if(variable.type != LexTokenType::Identifier) throw SyntaxException(variable.lineInfo, "Expected an identifier got a " + LexTokenType_ToString(variable.type) + " \"" + variable.text + "\""); else { - node = AdvancedSyntaxNode::Create(DeclareExpression,true,{variable.text}); + node = AdvancedSyntaxNode::Create(DeclareExpression,true,{EnsureSafeVariable(variable)}); } } else if(IsIdentifier("const")) @@ -888,7 +897,7 @@ namespace Tesses::CrossLang else if(variable.type != LexTokenType::Identifier) throw SyntaxException(variable.lineInfo, "Expected an identifier got a " + LexTokenType_ToString(variable.type) + " \"" + variable.text + "\""); else { - node = AdvancedSyntaxNode::Create(ConstExpression,true,{variable.text}); + node = AdvancedSyntaxNode::Create(ConstExpression,true,{EnsureSafeVariable(variable)}); } } else if(IsIdentifier("comptime")) @@ -956,37 +965,37 @@ namespace Tesses::CrossLang } else if(tokens[i].type == LexTokenType::Identifier) { - std::string token = tokens[i].text; + auto token=tokens[i]; i++; bool hasNumber=true; int64_t lngNum = 0; - if(token.size() == 1 && token[0] == '0') + if(token.text.size() == 1 && token.text[0] == '0') { lngNum = 0; } else - if(token.size() > 0 && token[0] == '0') + if(token.text.size() > 0 && token.text[0] == '0') { - if(token.size() > 1 && token[1] == 'x') + if(token.text.size() > 1 && token.text[1] == 'x') { - lngNum = std::stoll(token.substr(2),nullptr,16); + lngNum = std::stoll(token.text.substr(2),nullptr,16); } - else if(token.size() > 1 && token[1] == 'b') + else if(token.text.size() > 1 && token.text[1] == 'b') { - lngNum = std::stoll(token.substr(2),nullptr,2); + lngNum = std::stoll(token.text.substr(2),nullptr,2); } else { - lngNum = std::stoll(token.substr(1),nullptr,8); + lngNum = std::stoll(token.text.substr(1),nullptr,8); } } - else if(token.size() > 0 && token[0] >= '0' && token[0] <= '9') + else if(token.text.size() > 0 && token.text[0] >= '0' && token.text[0] <= '9') { - lngNum=std::stoll(token,nullptr,10); + lngNum=std::stoll(token.text,nullptr,10); } else { @@ -1018,16 +1027,16 @@ namespace Tesses::CrossLang if(!hasNumber) { - if(token == "true") + if(token.text == "true") node = true; - else if(token == "false") + else if(token.text == "false") node = false; - else if(token == "null") + else if(token.text == "null") node = nullptr; - else if(token == "undefined") + else if(token.text == "undefined") node = Undefined(); else { - node = AdvancedSyntaxNode::Create(GetVariableExpression,true,{token}); + node = AdvancedSyntaxNode::Create(GetVariableExpression,true,{EnsureSafeVariable(token)}); } } } diff --git a/src/runtime_methods/net.cpp b/src/runtime_methods/net.cpp index e98c21b..22d7c3c 100644 --- a/src/runtime_methods/net.cpp +++ b/src/runtime_methods/net.cpp @@ -728,7 +728,7 @@ namespace Tesses::CrossLang } return Undefined(); } - static TObject Net_NetworkStream(GCList& ls, std::vector args) + static TObject New_NetworkStream(GCList& ls, std::vector args) { bool ipv6; bool datagram; @@ -770,7 +770,7 @@ namespace Tesses::CrossLang } }; - static TObject Net_Http_HttpServer(GCList& ls, std::vector args,TRootEnvironment* env) + static TObject New_HttpServer(GCList& ls, std::vector args,TRootEnvironment* env) { int64_t port; std::string pathStr; @@ -1440,14 +1440,103 @@ namespace Tesses::CrossLang } return ""; } + static TObject New_FileServer(GCList& ls, std::vector args) + { + std::shared_ptr vfs; + bool allowlisting; + bool spa; + if(GetArgument(args,0,vfs) && GetArgument(args,1,allowlisting) && GetArgument(args,2,spa)) + { + return std::make_shared(vfs,allowlisting,spa); + + } + return nullptr; + } + static TObject New_BasicAuthServer(GCList& ls, std::vector args) + { + auto bAuth = std::make_shared(); + + TCallable* cb; + if(!args.empty()) + bAuth->server = ToHttpServer(ls.GetGC(),args[0]); + + if(GetArgumentHeap(args,1,cb)) + { + auto marked= CreateMarkedTObject(ls, cb); + bAuth->authorization = [marked](std::string user,std::string password)->bool { + GCList ls(marked->GetGC()); + TCallable* callable; + if(GetObjectHeap(marked->GetObject(), callable)) + { + return ToBool(callable->Call(ls,{user,password})); + } + return false; + }; + } + GetArgument(args,2,bAuth->realm); + return bAuth; + } + static TObject New_MountableServer(GCList& ls, std::vector args) + { + if(args.empty()) return Undefined(); + return std::make_shared(ToHttpServer(ls.GetGC(),args[0])); + } + static TObject New_StreamHttpRequestBody(GCList& ls, std::vector args) + { + std::shared_ptr strm; + std::string mimeType; + + if(GetArgument(args,0,strm) && GetArgument(args, 1, mimeType)) + { + return TNativeObject::Create(ls, new StreamHttpRequestBody(strm, mimeType)); + } + return nullptr; + } + static TObject New_TextHttpRequestBody(GCList& ls, std::vector args) + { + std::string text; + std::string mimeType; + if(GetArgument(args, 0, text) && GetArgument(args, 1, mimeType)) + { + return TNativeObject::Create(ls,new TextHttpRequestBody(text, mimeType)); + + } + return nullptr; + } + static TObject New_JsonHttpRequestBody(GCList& ls, std::vector args) + { + + if(!args.empty()) + { + return TNativeObject::Create(ls,new TextHttpRequestBody(Json_Encode(args[0]), "application/json")); + + } + return nullptr; + + } void TStd::RegisterNet(GC* gc, TRootEnvironment* env) { env->permissions.canRegisterNet=true; GCList ls(gc); - TDictionary* dict = TDictionary::Create(ls); + + gc->BarrierBegin(); + TDictionary* dict = env->EnsureDictionary(gc,"Net"); + TDictionary* _new = env->EnsureDictionary(gc,"New"); + _new->DeclareFunction(gc, "StreamHttpRequestBody","Create a stream request body",{"stream","mimeType"},New_StreamHttpRequestBody); + _new->DeclareFunction(gc, "TextHttpRequestBody","Create a text request body",{"text","mimeType"},New_TextHttpRequestBody); + _new->DeclareFunction(gc, "JsonHttpRequestBody","Create a text request body",{"json"},New_JsonHttpRequestBody); + _new->DeclareFunction(gc, "HttpServer", "Create a http server (allows multiple)",{"server","portOrUnixPath","$printIPs"},[env](GCList& ls, std::vector args)->TObject{ + return New_HttpServer(ls,args,env); + }); + _new->DeclareFunction(gc, "FileServer","Create a file server",{"vfs","allowlisting","spa"}, New_FileServer); + _new->DeclareFunction(gc, "BasicAuthServer", "Create a basic auth server", {"$server","$auth","$realm"},New_BasicAuthServer); + _new->DeclareFunction(gc, "MountableServer","Create a server you can mount to, must mount parents before child",{"root"}, New_MountableServer); + _new->DeclareFunction(gc, "NetworkStream","Create a network stream",{"ipv6","datagram"},New_NetworkStream); + + TDictionary* http = TDictionary::Create(ls); TDictionary* smtp = TDictionary::Create(ls); http->DeclareFunction(gc, "StatusCodeString", "Get the status code string",{"statusCode"},Net_Http_StatusCodeString); @@ -1461,88 +1550,40 @@ namespace Tesses::CrossLang http->DeclareFunction(gc, "MimeType","Get mimetype from extension",{"ext"},Net_Http_MimeType); - //http->DeclareFunction(gc, "ProcessServer","Process HTTP server connection",{"networkstream","server","ip","port","encrypted"},, Net_ProcessServer); - http->DeclareFunction(gc, "StreamHttpRequestBody","Create a stream request body",{"stream","mimeType"},[](GCList& ls, std::vector args)->TObject { - std::shared_ptr strm; - std::string mimeType; - - if(GetArgument(args,0,strm) && GetArgument(args, 1, mimeType)) - { - return TNativeObject::Create(ls, new StreamHttpRequestBody(strm, mimeType)); - } - return nullptr; - }); - http->DeclareFunction(gc, "TextHttpRequestBody","Create a text request body",{"text","mimeType"},[](GCList& ls, std::vector args)->TObject { - std::string text; - std::string mimeType; - if(GetArgument(args, 0, text) && GetArgument(args, 1, mimeType)) - { - return TNativeObject::Create(ls,new TextHttpRequestBody(text, mimeType)); - - } - return nullptr; - }); - http->DeclareFunction(gc, "JsonHttpRequestBody","Create a text request body",{"json"},[](GCList& ls, std::vector args)->TObject { - - if(!args.empty()) - { - return TNativeObject::Create(ls,new TextHttpRequestBody(Json_Encode(args[0]), "application/json")); - - } - return nullptr; - }); + http->DeclareFunction(gc, "StreamHttpRequestBody","Create a stream request body",{"stream","mimeType"},New_StreamHttpRequestBody); + http->DeclareFunction(gc, "TextHttpRequestBody","Create a text request body",{"text","mimeType"},New_TextHttpRequestBody); + http->DeclareFunction(gc, "JsonHttpRequestBody","Create a text request body",{"json"},New_JsonHttpRequestBody); http->DeclareFunction(gc, "MakeRequest", "Create an http request", {"url","$extra"}, [env](GCList& ls, std::vector args)->TObject {return Net_Http_MakeRequest(ls,args,env);}); http->DeclareFunction(gc, "WebSocketClient", "Create a websocket connection",{"url","headers","conn","$successCB"},Net_Http_WebSocketClient); http->DeclareFunction(gc, "DownloadToString","Return the http file's contents as a string",{"url"},Net_Http_DownloadToString); http->DeclareFunction(gc, "DownloadToStream","Download file to stream",{"url","stream"},Net_Http_DownloadToStream); http->DeclareFunction(gc, "DownloadToFile","Download file to file in vfs",{"url","vfs","path"},Net_Http_DownloadToFile); http->DeclareFunction(gc, "HttpServer", "Create a http server (allows multiple)",{"server","portOrUnixPath","$printIPs"},[env](GCList& ls, std::vector args)->TObject{ - return Net_Http_HttpServer(ls,args,env); + return New_HttpServer(ls,args,env); }); http->DeclareFunction(gc, "ListenSimpleWithLoop", "Listen (creates application loop)", {"server","port"},Net_Http_ListenSimpleWithLoop); http->DeclareFunction(gc, "ListenOnUnusedPort","Listen on unused localhost port and print Port: theport",{"server"},Net_Http_ListenOnUnusedPort); //FileServer svr() - http->DeclareFunction(gc, "FileServer","Create a file server",{"vfs","allowlisting","spa"}, [](GCList& ls, std::vector args)->TObject{ - - std::shared_ptr vfs; - bool allowlisting; - bool spa; - if(GetArgument(args,0,vfs) && GetArgument(args,1,allowlisting) && GetArgument(args,2,spa)) + http->DeclareFunction(gc, "FileServer","Create a file server",{"vfs","allowlisting","spa"}, New_FileServer); + http->DeclareFunction(gc, "BasicAuthServer", "Create a basic auth server", {"$server","$auth","$realm"},New_BasicAuthServer); + http->DeclareFunction(gc, "BasicAuthGetCreds","Get creds from str",{"ctx"},[](GCList& ls, std::vector args)->TObject { + TServerContext* sc; + if(GetArgumentHeap(args,0,sc)) { - return std::make_shared(vfs,allowlisting,spa); - + std::string user; + std::string pass; + if(sc->IsAvailable() && BasicAuthServer::GetCreds(*sc->GetContext(),user,pass)) + { + return TDictionary::Create(ls,{ + TDItem("Username",user), + TDItem("Password",pass) + }); + } } return nullptr; }); - http->DeclareFunction(gc, "MountableServer","Create a server you can mount to, must mount parents before child",{"root"}, [](GCList& ls, std::vector args)->TObject{ - TCallable* call; - TDictionary* dict; - TClassObject* cls; - std::shared_ptr mySvr; - if(GetArgumentHeap(args,0,call)) - { - auto svr = std::make_shared(ls.GetGC(), call); - return std::make_shared(svr); - - } - else if(GetArgumentHeap(args,0,dict)) - { - auto svr = std::make_shared(ls.GetGC(), dict); - return std::make_shared(svr); - - } - else if(GetArgumentHeap(args,0,cls)) - { - auto svr = std::make_shared(ls.GetGC(), cls); - return std::make_shared(svr); - } - else if(GetArgument(args,0,mySvr)) - { - return std::make_shared(mySvr); - } - return nullptr; - }); - dict->DeclareFunction(gc, "NetworkStream","Create a network stream",{"ipv6","datagram"},Net_NetworkStream); + http->DeclareFunction(gc, "MountableServer","Create a server you can mount to, must mount parents before child",{"root"}, New_MountableServer); + dict->DeclareFunction(gc, "NetworkStream","Create a network stream",{"ipv6","datagram"},New_NetworkStream); smtp->DeclareFunction(gc, "Send","Send email via smtp server",{"messageStruct"},Net_Smtp_Send); dict->DeclareFunction(gc, "getIPAddresses","Get the ip addresses of this machine",{"$ipv6"},[](GCList& ls, std::vector args)->TObject{ TList* a = TList::Create(ls); @@ -1559,10 +1600,8 @@ namespace Tesses::CrossLang ls.GetGC()->BarrierEnd();; return a; }); - gc->BarrierBegin(); dict->SetValue("Http", http); dict->SetValue("Smtp", smtp); - env->DeclareVariable("Net", dict); gc->BarrierEnd(); } } diff --git a/src/runtime_methods/process.cpp b/src/runtime_methods/process.cpp index 9f863e0..012ba1c 100644 --- a/src/runtime_methods/process.cpp +++ b/src/runtime_methods/process.cpp @@ -291,6 +291,14 @@ namespace Tesses::CrossLang env->SetVariable("Process",dict); auto process = env->EnsureDictionary(gc,"New"); process->DeclareFunction(gc, "Process", "Create process",{},New_Process); + process->DeclareFunction(gc, "CGIServer", "Create a CGI Server",{"path"},[](GCList& ls, std::vector args)->TObject{ + Tesses::Framework::Filesystem::VFSPath path; + if(GetArgumentAsPath(args,0,path)) + { + return std::make_shared(path); + } + return Undefined(); + }); gc->BarrierEnd(); } } diff --git a/src/vm/vm.cpp b/src/vm/vm.cpp index 88d6da7..12c20d6 100644 --- a/src/vm/vm.cpp +++ b/src/vm/vm.cpp @@ -3763,6 +3763,7 @@ namespace Tesses::CrossLang { if(svr != nullptr) { auto mountable = std::dynamic_pointer_cast(svr); + if(mountable != nullptr) { if(key == "Mount") @@ -6256,6 +6257,118 @@ namespace Tesses::CrossLang { cse.back()->Push(gc,Undefined()); return false; } + if(std::holds_alternative>(instance)) + { + auto svr = std::get>(instance); + auto bas = std::dynamic_pointer_cast(svr); + auto cgi = std::dynamic_pointer_cast(svr); + auto changable = std::dynamic_pointer_cast(svr); + if(changable != nullptr) + { + if(key == "Server") + { + bas->server = ToHttpServer(gc,value); + stk->Push(gc,value); + return false; + } + } + + if(bas != nullptr) + { + if(key == "Realm") + { + bool val; + if(GetObject(value,val)) + { + bas->realm = val; + stk->Push(gc,val ); + + return false; + } + } + if(key == "Server") + { + bas->server = ToHttpServer(gc,value); + stk->Push(gc,value); + return false; + } + if(key == "Authorization") + { + TCallable* val; + if(GetObjectHeap(value,val)) + { + auto marked= CreateMarkedTObject(ls, val); + bas->authorization = [marked](std::string user,std::string password)->bool { + GCList ls(marked->GetGC()); + TCallable* callable; + if(GetObjectHeap(marked->GetObject(), callable)) + { + return ToBool(callable->Call(ls,{user,password})); + } + return false; + }; + stk->Push(gc,val); + + return false; + } + } + } + + if(cgi != nullptr) + { + if(key == "WorkingDirectory") + { + Tesses::Framework::Filesystem::VFSPath path; + if(GetObjectAsPath(value,path)) + { + cgi->workingDirectory = path; + stk->Push(gc,path); + return false; + } + else { + cgi->workingDirectory = std::nullopt; + stk->Push(gc,nullptr); + return false; + } + + } + if(key == "DocumentRoot") + { + Tesses::Framework::Filesystem::VFSPath path; + if(GetObjectAsPath(value,path)) + { + cgi->document_root= path; + stk->Push(gc,path); + return false; + } + else { + cgi->document_root= std::nullopt; + stk->Push(gc,nullptr); + return false; + } + + } + if(key == "AdminEmail") + { + std::string str; + if(GetObject(value,str)) + { + cgi->adminEmail = str; + stk->Push(gc,str); + return false; + } + else { + cgi->adminEmail= std::nullopt; + stk->Push(gc,nullptr); + return false; + } + + } + } + + stk->Push(gc, Undefined()); + return false; + } if(std::holds_alternative>(instance)) { auto strm = std::get>(instance);