restored/web_cgi.inc
← All source files · Download this file
3,750 bytes · SHA256 0f8ae33c9e68cd71079a568da95ff0ef83b8cfaa1f24928adbbd83e8734a60b7
// Buffered CGI-worker request/response API; HTTP framing belongs to the gateway.
static void webValidate(const CodeLine& c) {
count(c,2,4); const auto& a=c.arg; const auto& s=c.source;
if(a[1].text=="request") { count(c,3,3); destination(a[2],s); }
else if(a[1].text=="status") count(c,3,3);
else if(a[1].text=="header") count(c,4,4);
else if(a[1].text=="escape") { count(c,4,4); destination(a[2],s); }
else fail(s,"unknown web operation");
}
static bool webHeaderName(const std::string& name) {
if(name.empty() || name.size()>64) return false;
for(unsigned char ch:name) if(!((ch>='a' && ch<='z') || (ch>='A' && ch<='Z') || (ch>='0' && ch<='9') || ch=='-')) return false;
return true;
}
bool vScript::webCommand(Frame& frame,const CodeLine& c) {
const auto& a=c.arg; if(a[0].text!="web") return false;
const auto& s=c.source; const auto& op=a[1].text;
auto val=[&](std::size_t i){return value(frame,a[i],s);};
if(op=="escape") {
lookup(frame,a[2],s); std::string out;
for(char ch:val(3)) {
if(ch=='&') append(out,"&",s); else if(ch=='<') append(out,"<",s);
else if(ch=='>') append(out,">",s); else if(ch=='\"') append(out,""",s);
else if(ch=='\'') append(out,"'",s); else append(out,std::string(1,ch),s);
}
assign(frame,a[2],out,s); return true;
}
if(!options.webWorker) fail(s,"web request/response operations require the CGI worker");
if(op=="request") {
Json request;
try { request=Json::parse(options.webRequest); } catch(...) { fail(s,"invalid CGI worker request JSON"); }
if(!request.is_object()) fail(s,"CGI worker request must be an object");
putJson(frame,a[2],std::move(request),s);
} else if(op=="status") webStatus=static_cast<int>(integer(val(2),200,599,s));
else {
auto name=val(2), text=val(3), lower=name;
if(!webHeaderName(name) || text.size()>4096) fail(s,"invalid web response header");
for(unsigned char ch:text) if(ch<32 || ch>126) fail(s,"web response header requires printable ASCII (no CR/LF)");
std::transform(lower.begin(),lower.end(),lower.begin(),[](unsigned char ch){return static_cast<char>(std::tolower(ch));});
static const std::set<std::string> reserved={"status","content-length","connection","transfer-encoding","keep-alive","trailer","upgrade","proxy-authenticate","proxy-authorization","x-vanilla-request-id","x-content-type-options"};
if(reserved.count(lower)) fail(s,"reserved web response header");
auto next=webHeaders;
if(lower!="set-cookie") next.erase(std::remove_if(next.begin(),next.end(),[&](const auto& h){return h.first==lower;}),next.end());
next.emplace_back(lower,text);
std::size_t bytes=0; for(const auto& h:next) bytes+=h.first.size()+h.second.size()+4;
if(next.size()>32 || bytes>8192) failLimit(s,"web response header limit (32 headers, 8 KiB)");
webHeaders=std::move(next);
}
return true;
}
void vScript::emit(const std::string& text,const Source& s) {
auto limit=options.webWorker ? valueLimit : 16*valueLimit;
if(text.size()>limit-outputBytes) failLimit(s,options.webWorker ? "CGI response byte limit (1 MiB)" : "output byte limit (16 MiB)");
if(!text.empty()) outputSource=s;
outputBytes+=text.size();
if(options.webWorker) webBody+=text;
else { std::cout << text; if(!std::cout) fail(s,"output write failed"); }
}
std::string vScript::WebResponse() const {
Json headers=Json::array(); for(const auto& h:webHeaders) headers.push_back(Json::array({h.first,h.second}));
return Json{{"status",webStatus},{"headers",std::move(headers)},{"body",webBody}}.dump();
}