#include "__jet.h" #include "Exception.h" #include "Global.h" #include "SessionId.h" #include #include #include #include namespace jet { __jet::__jet(coreutils::ZString &in, coreutils::MString &parentOut, Global &global, Tag *parent, Tag *local) : Tag(in, parentOut, global, parent, this) { char *cookies; if(keywordDefined("cgi") && (resolveKeyword("cgi") == "yes")) { global.cgi = true; if(keywordDefined("sessiondir")) { global.session = true; global.cookies = getenv("HTTP_COOKIE"); coreutils::MString sessionIdValue; if(global.cookies.data["session"] != NULL) { sessionIdValue = global.cookies.data["session"]; // if last activity time is expired then ignore. // follow sessiontimeoutredirecturl. } else { SessionId sessionId; // A 160-bit CSPRNG value colliding with an existing session file // is astronomically unlikely, but cheap to guard against outright // rather than trust the probability - regenerate on an actual // collision instead of ever reusing another session's file. int sessionIdAttempts = 0; while(true) { coreutils::MString candidatePath; candidatePath << resolveKeyword("sessiondir") << "/" << sessionId << ".json"; if(access(candidatePath.c_str(), F_OK) != 0) break; if(++sessionIdAttempts >= 5) throw coreutils::Exception("could not generate a unique session id after multiple attempts."); sessionId = SessionId(); } global.headers["Set-Cookie"] << "session=" << sessionId; global.cookies.data["session"] = sessionId; sessionIdValue = sessionId; if(keywordDefined("sessiontimeout")) { time_t timeout = time(0) + keywords["sessiontimeout"].asInteger(); } // also save last activity time in session file. } // JFile's own RAII behavior (see CoreUtils MFile.cpp) loads // whatever JSON is already at this path, or starts empty if the // file doesn't exist yet (a brand-new session), and writes any // changes back automatically when destroyed - no explicit save // call needed anywhere. coreutils::MString sessionFilePath; sessionFilePath << resolveKeyword("sessiondir"); sessionFilePath << "/"; sessionFilePath << sessionIdValue; sessionFilePath << ".json"; global.sessionFile = new coreutils::JFile(sessionFilePath); } coreutils::ZString requestMethod(getenv("REQUEST_METHOD")); if(requestMethod == "POST") { coreutils::ZString contentLength(getenv("CONTENT_LENGTH")); coreutils::ZString contentType(getenv("CONTENT_TYPE")); coreutils::ZString boundary; int boundaryPos = contentType.find("boundary="); if(boundaryPos != -1) { boundary = contentType.substring(boundaryPos + 9); } // find() leaves contentType's own cursor at the match position - // reset it before handing contentType to CGIFormData, whose // startsWith("multipart/form-data")/startsWith("application/...") // checks are cursor-relative and would otherwise silently miss, // falling through to an unrecognized content type that never // parses anything. contentType.reset(); int bodyLength = contentLength.asInteger(); global.cgiData = new coreutils::CGIFormData(global.cgiReader, contentType, bodyLength, boundary); // Feed the request body off stdin in chunks and drive the parser // resumably (the same feed()/doParse() pattern as a socket-fed // StreamReader) rather than blocking to read it all before parsing // starts - keeps memory bounded for large multipart uploads and // exercises the real Parser::Status::INCOMPLETE resumption path. char cgiReadBuf[4096]; int cgiBytesRead = 0; coreutils::Parser::Status cgiStatus = coreutils::Parser::Status::INCOMPLETE; while(cgiBytesRead < bodyLength && cgiStatus != coreutils::Parser::Status::COMPLETE) { int want = bodyLength - cgiBytesRead; if(want > (int)sizeof(cgiReadBuf)) want = sizeof(cgiReadBuf); int got = ::read(0, cgiReadBuf, want); if(got <= 0) throw coreutils::Exception("CGI request body ended before Content-Length was reached."); cgiBytesRead += got; coreutils::ZString chunk(cgiReadBuf, got); global.cgiReader.feed(chunk); cgiStatus = global.cgiData->doParse(); } if(cgiStatus != coreutils::Parser::Status::COMPLETE) throw coreutils::Exception("CGI request body did not parse to completion."); } } processContainer(container, NULL, true); } }