Files
JetCore/__jet.cpp
T
Brad Arant b276bdb6cc Guard against a session id collision: check the session file does not already exist, regenerate if it does
A RAND_bytes()-generated 160-bit session id colliding with an existing session file is astronomically unlikely, but the check is cheap and removes any dependence on that probability - regenerates (up to 5 attempts, then throws) rather than ever reusing an existing sessions file.
2026-08-26 11:12:41 -07:00

110 lines
4.6 KiB
C++

#include "__jet.h"
#include "Exception.h"
#include "Global.h"
#include "SessionId.h"
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <unistd.h>
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);
}
}