diff --git a/Global.cpp b/Global.cpp index f6bb3c0..5b4e82c 100644 --- a/Global.cpp +++ b/Global.cpp @@ -11,7 +11,12 @@ namespace jet { } Global::~Global() { - + // JFile's destructor (via MFile::~MFile()->update()) is what actually + // writes the session JSON back to disk - it has to run for session + // persistence to happen at all, so sessionFile can't be left as a + // leaked, never-deleted pointer the way cgiData currently is. + if(sessionFile) + delete sessionFile; } void Global::dump() { diff --git a/Global.h b/Global.h index 794ea8d..e61c41a 100644 --- a/Global.h +++ b/Global.h @@ -5,6 +5,7 @@ # include "CGIFormattedData.h" # include "CGIFormData.h" # include "StreamReader.h" +# include "JFile.h" # include namespace jet { @@ -40,6 +41,16 @@ namespace jet { coreutils::StreamReader cgiReader; coreutils::CGIFormData *cgiData = NULL; + + /// + /// sessionFile backs $[@name] (read) and + /// (write) - a JSON file at sessiondir/.json, loaded when + /// opened and saved automatically when destroyed (JFile/MFile's own + /// RAII behavior - see MFile.cpp). NULL unless cgi mode and the + /// sessiondir keyword are both active for this request. + /// + + coreutils::JFile *sessionFile = NULL; std::map variables; std::map sessions; std::map headers; diff --git a/Tag.cpp b/Tag.cpp index 8e0ccdb..356dd5e 100644 --- a/Tag.cpp +++ b/Tag.cpp @@ -428,7 +428,9 @@ namespace jet { throw coreutils::Exception("cgi variable only allowed in cgi mode."); } else if(variable.ifNext("@")) { renderVariableName(variable, name, modifier); - coreutils::MString sessionValue = global.getSession(global.sessionId)->getColumnValue(name); + if(global.sessionFile == NULL) + throw coreutils::Exception("session variable requested but no session is active (missing sessiondir keyword, or cgi mode is off)."); + coreutils::MString sessionValue = (*global.sessionFile)[name]; return processModifier(sessionValue, modifier); } else if(variable.ifNext("%")) { // NOTE: unreachable - '%' is already claimed by the keyword-lookup branch @@ -483,7 +485,15 @@ namespace jet { return; } - void Tag::storeVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope) { + void Tag::assignVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope) { + if(variable.startsWith("@")) { + coreutils::ZString sessionName = variable; + sessionName.ifNext("@"); + if(global.sessionFile == NULL) + throw coreutils::Exception("session variable assignment requested but no session is active (missing sessiondir keyword, or cgi mode is off)."); + (*global.sessionFile)[sessionName] = value; + return; + } if((scope == "global") || (scope == "")) global.variables[variable] = value; else if(scope == "local") @@ -491,39 +501,43 @@ namespace jet { else if(scope == "parent") local->parent->variables[variable] = value; } - + + void Tag::storeVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope) { + assignVariable(variable, value, scope); + } + void Tag::storeVariable(coreutils::ZString variable) { if(keywordDefined("expr")) { if(!keywordDefined("scope") || (resolveKeyword("scope") == "global")) - global.variables[variable] = Operand(keywords["expr"], *this).string; + assignVariable(variable, Operand(keywords["expr"], *this).string, "global"); else if(resolveKeyword("scope") == "local") - local->variables[variable] = Operand(keywords["expr"], *this).string; + assignVariable(variable, Operand(keywords["expr"], *this).string, "local"); else if(resolveKeyword("scope") == "parent") - local->parent->variables[variable] = Operand(keywords["expr"], *this).string; + assignVariable(variable, Operand(keywords["expr"], *this).string, "parent"); } else if(hasContainer) { processContainer(container); if(evaluate) { if(!keywordDefined("scope") || (resolveKeyword("scope") == "global")) - global.variables[variable] = out; + assignVariable(variable, out, "global"); else if(resolveKeyword("scope") == "local") - local->variables[variable] = out; + assignVariable(variable, out, "local"); else if(resolveKeyword("scope") == "parent") - local->parent->variables[variable] = out; + assignVariable(variable, out, "parent"); } else { if(!keywordDefined("scope") || (resolveKeyword("scope") == "global")) - global.variables[variable] = container; + assignVariable(variable, container, "global"); else if(resolveKeyword("scope") == "local") - local->variables[variable] = container; + assignVariable(variable, container, "local"); else if(resolveKeyword("scope") == "parent") - local->parent->variables[variable] = container; - } + assignVariable(variable, container, "parent"); + } } else { if(!keywordDefined("scope") || (resolveKeyword("scope") == "global")) - global.variables[variable] = resolveKeyword("value"); + assignVariable(variable, resolveKeyword("value"), "global"); else if(resolveKeyword("scope") == "local") - local->variables[variable] = resolveKeyword("value"); + assignVariable(variable, resolveKeyword("value"), "local"); else if(resolveKeyword("scope") == "parent") - local->parent->variables[variable] = resolveKeyword("value"); + assignVariable(variable, resolveKeyword("value"), "parent"); } } diff --git a/Tag.h b/Tag.h index 8db4916..cc932ab 100644 --- a/Tag.h +++ b/Tag.h @@ -54,7 +54,12 @@ namespace jet { void renderVariableName(coreutils::ZString &variable, coreutils::MString &name, coreutils::MString &modifier); void storeVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope); void storeVariable(coreutils::ZString variable); - + // Single write path shared by both storeVariable overloads. An + // "@name"-prefixed variable is routed to the active JSON session + // file (global.sessionFile) instead of the normal global/local/parent + // variables maps, mirroring getVariable's "@" read branch. + void assignVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope); + private: bool containerOnly = false; coreutils::ZString splitTagName; diff --git a/__jet.cpp b/__jet.cpp index 6d09696..dac1e6a 100644 --- a/__jet.cpp +++ b/__jet.cpp @@ -17,19 +17,33 @@ namespace jet { if(keywordDefined("sessiondir")) { global.session = true; global.cookies = getenv("HTTP_COOKIE"); + coreutils::MString sessionIdValue; if(global.cookies.data["session"] != NULL) { -// pull sessionfile from sessiondir. + sessionIdValue = global.cookies.data["session"]; // if last activity time is expired then ignore. -// follow sessiontimeoutredirecturl. - } else { +// follow sessiontimeoutredirecturl. + } else { 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. + } +// 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"));