/* * 123 Application Module * (c) Kai Voigt, k@123.org * session scope added by Azundris * * BETA version for Roxen 2.* * * To use an SQL database for storing the session and user variables, specify * the database in the config interface and create a table "variables" * with the following command within your database (this example is * from MySQL, you might need to modify it for other systems) * * create table variables (id varchar(255) not null, * region varchar(255), * lastusage int, * svalues mediumtext, * key(id)); * * In your documents, you can use id->misc->session_variables as a * mapping for session variables that will be accessible during the entire * session. * * TODO: This module needs comments, documentation, testing and some * mutex stuff. DBM storage can be added later. Error handling is badly * needed to catch sql errors and the like. * */ string cvs_version = "$Id: 123app.pike,v 1.0 2001/07/18 12:34:56 k Exp $"; inherit "module"; inherit "roxenlib"; #include mapping (string:mapping (string:mixed)) _variables = ([]); object myconf; int foundcookieandprestate = 0; int storage_is_not_sql() { return (QUERY(storage) != "sql"); } int storage_is_not_file() { return (QUERY(storage) != "file"); } int hide_gc () { return (!QUERY(dogc)); } void start(int num, object conf) { // query_tag_set()->prepare_context=set_entities; if (conf) { myconf = conf; } } void stop () { } void create () { defvar("secret", "zekritt vord borris", "Secret Word", TYPE_STRING, "a secret word that is needed to create secure IDs." ); defvar("dogc", 1, "Garbage Collection", TYPE_FLAG, "Garbage collection will be done by this module." ); defvar("garbage", 100, "Garbage Collection Frequency", TYPE_INT, "after how many connects expiration of old session should happen", 0, hide_gc); defvar("expire", 3600, "Expiration Time", TYPE_INT, "after how many seconds an unactive session is removed", 0, hide_gc); defvar("storage", "memory", "Storage Method", TYPE_MULTIPLE_STRING, "The method to be used for storing the session and user variables." " Available are Memory, Database and File storage. Each" " of them have their pros and cons regarding speed and" " persistance.", ({"memory", "sql", "file"})); defvar("sql_url", "", "Database URL", TYPE_STRING, "Which database to use for the session and user variables, use" " a common database URL", 0, storage_is_not_sql); defvar("filepath", "", "File Method Path", TYPE_DIR, "The storage directory for File Method", 0, storage_is_not_file); defvar("secure", 0, "Secure Cookies", TYPE_FLAG, "If used, cookies will be flagged as 'Secure' (RFC 2109)." ); } mixed register_module() { return ({ MODULE_FIRST | MODULE_FILTER | MODULE_PARSER | MODULE_PROVIDER, "123 Application Module++", "This Module will provide each session with a distinct set " "of session variables." "

" "Warning: This module has not been tested a lot." "
" "Read the module code for instructions.", ({}), 1, }); } int session_size_memory() { if (_variables->session) { return (sizeof(_variables->session)); } else { return(0); } } int session_size_sql() { object(Sql.sql) con; function sql_connect = myconf->sql_connect; con = sql_connect(QUERY(sql_url)); string query = "select count(*) as size from variables where region='session'"; array(mapping(string:mixed)) result = con->query(query); return ((int)result[0]->size); } int session_size_file() { int result=0; foreach(get_dir(QUERY(filepath)), string filename) { if (filename[sizeof(filename)-8..] == ".session") { result++; } } return (result); } string status() { string result = ""; int size; switch(QUERY(storage)) { case "memory": size = session_size_memory(); break; case "sql": size = session_size_sql(); break; case "file": size = session_size_file(); break; } result += sprintf("%d session%s active.
\n", size, (size != 1) ? "s" : ""); return (result); } void session_gc_memory() { if (!_variables->session) { return; } foreach (indices(_variables->session), string session_id) { if (time() > (_variables->session[session_id]->lastusage+QUERY(expire))) { m_delete(_variables->session, session_id); } } } void session_gc_sql() { object(Sql.sql) con; function sql_connect = myconf->sql_connect; con = sql_connect(QUERY(sql_url)); int exptime = time()-QUERY(expire); con->query("delete from variables where lastusage < '"+exptime+"' and region='session'"); } void session_gc_file() { string filepath=QUERY(filepath); string sfile; int exptime = time()-QUERY(expire); foreach (get_dir(filepath), string filename) { if (filename[sizeof(filename)-8..] == ".session") { sfile=combine_path(filepath,filename); if (file_stat(sfile)[2] < exptime) { rm(sfile); } } } } void session_gc() { switch(QUERY(storage)) { case "memory": session_gc_memory(); break; case "sql": session_gc_sql(); break; case "file": session_gc_file(); break; } } mapping (string:mixed) variables_retrieve_memory(string region, string key) { if (!_variables[region]) { _variables[region] = ([]); } if (!_variables[region][key]) { _variables[region][key] = ([]); } if (!_variables[region][key]->values) { _variables[region][key]->values = ([]); } return (_variables[region][key]->values); } mapping (string:mixed) variables_retrieve_sql(string region, string key) { object(Sql.sql) con; function sql_connect = myconf->sql_connect; con = sql_connect(QUERY(sql_url)); string query = "select svalues from variables where region='"+region+"' and id='"+key+"'"; array(mapping(string:mixed)) result = con->query(query); if (sizeof(result) != 0) { return (string2values(result[0]->svalues)); } else { return ([]); } } mapping (string:mixed) variables_retrieve_file(string region, string key) { string sfile=combine_path(QUERY(filepath),key+"."+region); if (file_stat(sfile)) { return (string2values(Stdio.read_bytes(sfile))); } return ([]); } mapping (string:mixed) variables_retrieve(string region, string key) { switch(QUERY(storage)) { case "memory": return (variables_retrieve_memory(region, key)); break; case "sql": return (variables_retrieve_sql(region, key)); break; case "file": return (variables_retrieve_file(region, key)); break; } } void variables_store_memory(string region, string key, mapping values) { if (!_variables[region]) { _variables[region] = ([]); } if (!_variables[region][key]) { _variables[region][key] = ([]); } _variables[region][key]->lastusage = time(); _variables[region][key]->values = values; } string values2string(mixed values) { return (MIME.encode_base64(encode_value(values))); } mixed string2values(string encoded_string) { return (decode_value(MIME.decode_base64(encoded_string))); } void variables_store_sql(string region, string key, mapping values) { object(Sql.sql) con; function sql_connect = myconf->sql_connect; con = sql_connect(QUERY(sql_url)); con->query("delete from variables where region='"+region+"' and id='"+key+"'"); con->query("insert into variables(id, region, lastusage, svalues) values ('"+key+"', '"+region+"', '"+time()+"', '"+values2string(values)+"')"); } void variables_store_file(string region, string key, mapping values) { string sfile=combine_path(QUERY(filepath),key+"."+region); if (file_stat(sfile)) { rm(sfile); } object sf=Stdio.File(); sf->open(sfile,"wct"); sf->write(values2string(values)); sf->close(); destruct(sf); } void variables_store(string region, string key, mapping values) { switch(QUERY(storage)) { case "memory": variables_store_memory(region, key, values); break; case "sql": variables_store_sql(region, key, values); break; case "file": variables_store_file(region, key, values); break; } } string sessionid_create() { object md5 = Crypto.md5(); md5->update(QUERY(secret)); md5->update(sprintf("%d", roxen->increase_id())); md5->update(sprintf("%d", time(1))); return(Crypto.string_to_hex(md5->digest())); } mixed sessionid_set_prestate(object id, string SessionID) { string url=strip_prestate(strip_config(id->raw_url)); string new_prestate = "SessionID="+SessionID; id->prestate += (); return(http_redirect(url, id)); } mixed sessionid_remove_prestate(object id) { string url=strip_prestate(strip_config(id->raw_url)); id->prestate = (<>); return(http_redirect(id->not_query)); } void sessionid_set_cookie(object id, string SessionID) { //string Cookie = "SessionID="+SessionID+"; path=/; Secure"; string Cookie = "SessionID="+SessionID+"; path=/"; id->cookies->SessionID = SessionID; id->misc->moreheads = ([ "Set-Cookie": Cookie, "Expires": "0", "Pragma": "no-cache", "is_dynamic": 1, "Last-Modified": http_date(time(1)), "Cache-Control": "no-cache, must-revalidate" ]); } string sessionid_get(object id) { string SessionID; int foundcookie=0; int foundprestate=0; if (id->cookies->SessionID && sizeof (id->cookies->SessionID)) { SessionID = id->cookies->SessionID; foundcookie=1; } foreach (indices(id->prestate), string prestate) { if (prestate[..8] == "SessionID" ) { SessionID = prestate[10..]; foundprestate=1; } } if ((foundcookie == 1) && (foundprestate == 1)) { foundcookieandprestate = 1; } return(SessionID); } mixed first_try(object id) { if (query ("dogc") && (random (query ("garbage")) == 0)) { session_gc(); } string SessionID = sessionid_get(id); if (!SessionID) { SessionID = sessionid_create(); sessionid_set_cookie(id, SessionID); return (sessionid_set_prestate(id, SessionID)); } if (foundcookieandprestate == 1) { foundcookieandprestate = 0; return (sessionid_remove_prestate(id)); } id->misc->session_variables = variables_retrieve("session", SessionID); id->misc->session_variables->counter++; id->misc->session_id = SessionID; } void store_everything(object id) { if (id->misc->session_id) { string SessionID = id->misc->session_id; variables_store("session", SessionID, id->misc->session_variables); } } void filter(mapping m, object id) { store_everything(id); } // --- RXML Tags ----------------------------------------------- class TagSession { inherit RXML.Tag; constant name = "session"; mapping(string:RXML.Type) req_arg_types = ([ "id" : RXML.t_text(RXML.PEnt) ]); class Frame { inherit RXML.Frame; mapping vars; string scope_name; array do_enter(RequestID id) { NOCACHE(); // vars = cache.get_session_data(args->id) || ([]); vars = id->misc->session_variables; scope_name = args->scope || "session"; } array do_return(RequestID id) { result = content; if(!sizeof(vars)) return 0; // cache.set_session_data(vars, args->id, // args->life?(int)args->life+time(1):0, // !!args["force-db"] ); id->misc->session_variables = vars; } } }