v. vanilla 2.0

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,"&amp;",s); else if(ch=='<') append(out,"&lt;",s);
            else if(ch=='>') append(out,"&gt;",s); else if(ch=='\"') append(out,"&quot;",s);
            else if(ch=='\'') append(out,"&#39;",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();
}