/* sessionfuncs.c: -*- C -*-  Functions for manipulating session data. */

/* Author: Brian J. Fox (bfox@ai.mit.edu) Thu Sep 12 05:42:28 1996.

   This file is part of <Meta-HTML>(tm), a system for the rapid deployment
   of Internet and Intranet applications via the use of the Meta-HTML
   language.

   Copyright (c) 1995, 1996, Brian J. Fox (bfox@ai.mit.edu).
   Copyright (c) 1996, Universal Access Inc. (http://www.ua.com).

   Meta-HTML is free software; you can redistribute it and/or modify
   it under the terms of the UAI Free Software License as published
   by Universal Access Inc.; either version 1, or (at your option) any
   later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   UAI Free Software License for more details.

   You should have received a copy of the UAI Free Software License
   along with this program; if you have not, you may obtain one by
   writing to:

   Universal Access Inc.
   129 El Paseo Court
   Santa Barbara, CA
   93101  */

#include "language.h"
#include "session_data.h"

/************************************************************/
/*							    */
/*		Session Manipulation Functions		    */
/*							    */
/************************************************************/

/* <create-session VAR1 [... VARn] [ALT="value"] [allow-multiple]> */
static void pf_create_session (PFunArgs);

/* <delete-session> */
static void pf_delete_session (PFunArgs);

/* <set-session-var name=value> --> Stores in session database. */
static void pf_set_session_var (PFunArgs);

/* <get-session-var name> --> value from session database. */
static void pf_get_session_var (PFunArgs);

/* <unset-session-var name> Remove NAME from session database */
static void pf_unset_session_var (PFunArgs);

/* <set-session-timeout xxx> change default timeout value to xxx. */
static void pf_set_session_timeout (PFunArgs);

/* <require-session CODE> If invalid session ID, execute CODE. */
static void pf_require_session (PFunArgs);

/* <session-export package-name [session-db-name]>
   --> Package symbols to session-db. */
static void pf_session_export (PFunArgs);

/* <session-import session-db-name [package-name]>
   --> session-db symbols to specified package. */
static void pf_session_import (PFunArgs);

/* <with-locked-session> Per-session semaphore code </with-locked-session> */
static void pf_with_locked_session (PFunArgs);

/* <sessions-of-key key> --> Newline separated list of SIDs. */
static void pf_sessions_of_key (PFunArgs);

/* <get-session-data [sid]> --> Alist of the session data for SID. */
static void pf_get_session_data (PFunArgs);

/* <set-session-data [alist]> --> Set session info from ALIST. */
static void pf_set_session_data (PFunArgs);

#if defined (DEPRECATED_SESSION_COMMANDS)
/* <session-export-posted "prefix"> --> posted items only to session-db. */
static void pf_session_export_posted (PFunArgs);

/* <session-import-posted "prefix"> --> prefix items to PageVars. */
static void pf_session_import_posted (PFunArgs);
#endif /* DEPRECATED_SESSION_COMMANDS */

/* <set-session-db pathname> --> Dynamically change the location of the
   file used to store session information. */
static void pf_set_session_db (PFunArgs);

PFunDesc sessionfunc_table[] =
{
  /* The following functions deal with the session database. */
  { "CREATE-SESSION",	0, 0, pf_create_session },
  { "DELETE-SESSION",	0, 0, pf_delete_session },
  { "SET-SESSION-VAR",	0, 0, pf_set_session_var },
  { "GET-SESSION-VAR",	0, 0, pf_get_session_var },
  { "UNSET-SESSION-VAR",0, 0, pf_unset_session_var },
  { "SET-SESSION-TIMEOUT", 0, 0, pf_set_session_timeout },
  { "REQUIRE-SESSION",	0, 0, pf_require_session },
  { "SESSION-EXPORT",	0, 0, pf_session_export },
  { "SESSION-IMPORT",	0, 0, pf_session_import },
  { "WITH-LOCKED-SESSION", 1, 0, pf_with_locked_session },
  { "SESSIONS-OF-KEY",	0, 0, pf_sessions_of_key },
  { "GET-SESSION-DATA", 0, 0, pf_get_session_data },
  { "SET-SESSION-DATA", 0, 0, pf_set_session_data },
  { "SET-SESSION-DB",	0, 0, pf_set_session_db },

  /* Deprecate this as soon as possible. */
  { "SESSION-DATA-ALIST", 0, 0, pf_get_session_data },

#if defined (DEPRECATED_SESSION_COMMANDS)
  { "SESSION-EXPORT-POSTED", 0, 0, pf_session_export_posted },
  { "SESSION-IMPORT-POSTED", 0, 0, pf_session_import_posted },
#endif /* DEPRECATED_SESSION_COMMANDS */

  { (char *)NULL,	0, 0, (PFunHandler *)NULL }
};

PACKAGE_INITIALIZER (initialize_session_functions)
void
initialize_session_functions (Package *package)
{
  register int i;
  Symbol *sym;

  for (i = 0; sessionfunc_table[i].tag != (char *)NULL; i++)
    {
      sym = symbol_intern_in_package (package, sessionfunc_table[i].tag);
      sym->type = symtype_FUNCTION;
      sym->values = (char **)(&sessionfunc_table[i]);
    }
}

/* Get the values of VAR1 .. VARn and concatenate them to make a key.
   Create the session with that key.
   Special tag "allow-multiple" allows more than one sesson with same key.
   If VAR1 doesn't have a posted value, and there is a value supplied
   for VAR1 in the function call, that value is returned as the final
   page.  Example:
   <create-session
     USERNAME="<set-var ERROR-MESSAGE=\"You must supply a Username\">
		<include registration.html>"
     PASSWORD="<set-var ERROR-MESSAGE=\"You must supply a Password\">
		<include registration.html>"
     ALT="<set-var ERROR-MESSAGE=\"You are already signed on!\">
		<include begin.html>"
   >  */
static void
pf_create_session (PFunArgs)
{
  register int i;
  BPRINTF_BUFFER *key_buffer = (BPRINTF_BUFFER *)NULL;
  char *alternate;
  int allow_multiple_p = var_present_p (vars, "ALLOW-MULTIPLE");
  session_id_t sid;
  char **names = get_vars_names (vars);
  char **vals = get_vars_vals (vars);

  alternate = get_value (vars, "ALT");

  /* If there is already a session ID, then run the ALTernate code. */
  if (get_value (symbol_lookup_package ("DEFAULT"), "SID"))
    {
      if (alternate != (char *)NULL)
	bprintf_insert (page, start, "%s", alternate);
      return;
    }

  for (i = 0; names && names[i]; i++)
    {
      char *value;

      if ((strcasecmp (names[i], "ALT") == 0) ||
	  (strcasecmp (names[i], "ALLOW-MULTIPLE") == 0))
	continue;

      value = forms_get_tag_value (names[i]);

      if (value != (char *)NULL)
	{
	  if (!key_buffer)
	    key_buffer = bprintf_create_buffer ();

	  bprintf (key_buffer, "%s%s", key_buffer->bindex ? "-" : "", value);
	}
      else
	{
	  value = vals[i];

	  if (value != (char *)NULL)
	    {
	      bprintf_insert (page, start, "%s", value);
	      if (key_buffer) bprintf_free_buffer (key_buffer);
	      return;
	    }
	}
    }

  /* session_reap (); */
  if (key_buffer)
    {
      sid = session_begin (key_buffer->buffer, allow_multiple_p);
      bprintf_free_buffer (key_buffer);
    }
  else
    sid = session_begin ("Meta-HTML-Anonymous", allow_multiple_p);

  if (sid != (session_id_t)0)
    {
      pagefunc_set_variable ("DEFAULT::SID", (char *)sid);
    }
  else
    {
      if (alternate != (char *)NULL)
	bprintf_insert (page, start, "%s", alternate);
      else
	page_debug ("Couldn't create a session!");
    }
}

static void
pf_delete_session (PFunArgs)
{
  session_id_t sid =
    (session_id_t)get_value (symbol_lookup_package ("DEFAULT"), "SID");

  if (sid)
    session_end (sid);
}

static int
session_lock (session_id_t sid)
{
  char lockname[100];
  int fd;

  sprintf (lockname, "/tmp/meta-html-%s", (char *)sid);
  fd = os_open (lockname, O_CREAT | O_WRONLY | O_APPEND, 0666);

  if ((fd < 0) || (LOCKFILE (fd) == -1))
    {
      if (fd > -1)
	{
	  char pid_name[100];
	  sprintf (pid_name, "%ld\n", (long)getpid ());
	  write (fd, (void *)pid_name, (size_t) strlen (pid_name));
	  close (fd);
	  fd = -1;
	}
    }

  return (fd);
}

static void
session_unlock (int lock, session_id_t sid)
{
  if (lock > -1)
    {
      char lockname[100];

      sprintf (lockname, "/tmp/meta-html-%s", (char *)sid);
      unlink (lockname);
      UNLOCKFILE (lock);
      close (lock);
    }
}

static void
pf_with_locked_session (PFunArgs)
{
  session_id_t sid =
    (session_id_t)get_value (symbol_lookup_package ("DEFAULT"), "SID");
  int jump_again = 0;

  if (sid != (session_id_t)0)
    {
      int lock = session_lock (sid);
      PAGE *body_code = page_copy_page (body);
      PageEnv *page_environ = pagefunc_save_environment ();

      if ((jump_again = setjmp (page_jmp_buffer)) == 0)
	page_process_page_internal (body_code);

      pagefunc_restore_environment (page_environ);

      if (body_code != (PAGE *)NULL)
	{
	  if (!jump_again && (body_code->buffer != (char *)NULL))
	    {
	      bprintf_insert (page, start, "%s", body_code->buffer);
	      *newstart = start + (body_code->bindex);
	    }

	  page_free_page (body_code);
	}

      session_unlock (lock, sid);
    }
  if (jump_again) longjmp (page_jmp_buffer, 1);
}

static void
pf_sessions_of_key (PFunArgs)
{
  register int i;
  char *arg = mhtml_evaluate_string (get_positional_arg (vars, 0));
  SESSION_INFO **info_list = session_all_sessions ();
  SESSION_INFO *info;

  for (i = 0;
       (info_list != (SESSION_INFO **)NULL) &&
       ((info = info_list[i]) != (SESSION_INFO *)NULL);
       i++)
    {
      if (empty_string_p (arg) ||
	  ((info->key != (char *)NULL) && strcasecmp (info->key, arg) == 0))
	{
	  bprintf_insert (page, start, "%s\n", (char *)info->sid);
	  start += 1 + strlen ((char *)info->sid);
	}

      session_free (info);
    }

  if (arg) free (arg);

  if (info_list) free (info_list);
  *newstart = start;
}
      
static void
pf_set_session_var (PFunArgs)
{
  session_id_t sid =
    (session_id_t)get_value (symbol_lookup_package ("DEFAULT"), "SID");

  if (sid != (session_id_t)0)
    {
      SESSION_INFO *info = session_get_info (sid);

      if ((info != (SESSION_INFO *)NULL) && (vars != (Package *)NULL))
	{
	  register int i;
	  Package *session_data = symbol_get_package ((char *)NULL);
	  char **names = get_vars_names (vars);
	  char **vals = get_vars_vals (vars);

	  sd_info_to_package (info, session_data);

	  for (i = 0; names[i] != (char *)NULL; i++)
	    {
	      char *name = names[i], *value = vals[i];

	      name = mhtml_evaluate_string (name);

	      if (value != (char *)NULL)
		value = mhtml_evaluate_string (value);

	      forms_set_tag_value_in_package (session_data, name, value);
	      if (value) free (value);
	      if (name) free (name);
	    }

	  sd_package_to_info (info, session_data);
	  session_put_info (info);
	  symbol_destroy_package (session_data);
	}

      if (info) session_free (info);
    }
}

static void
pf_get_session_var (PFunArgs)
{
  session_id_t sid =
    (session_id_t)get_value (symbol_lookup_package ("DEFAULT"), "SID");

  if (sid != (session_id_t)0)
    {
      SESSION_INFO *info = session_get_info (sid);

      if ((info != (SESSION_INFO *)NULL) && (vars != (Package *)NULL))
	{
	  register int i;
	  Package *session_data = symbol_get_package ((char *)NULL);
	  char **names = get_vars_names (vars);

	  sd_info_to_package (info, session_data);

	  for (i = 0; names[i] != (char *)NULL; i++)
	    {
	      char *name = names[i], *value = (char *)NULL;

	      name = mhtml_evaluate_string (name);

	      value = forms_get_tag_value_in_package (session_data, name);

	      if (value != (char *)NULL)
		{
		  bprintf_insert (page, start, "%s", value);
		  start += strlen (value);
		}

	      if (name) free (name);
	    }
	  symbol_destroy_package (session_data);
	}

      if (info) session_free (info);
    }
}

static void
pf_get_session_data (PFunArgs)
{
  char *passed = mhtml_evaluate_string (get_positional_arg (vars, 0));
  session_id_t sid;

  if (!empty_string_p (passed))
    sid = (session_id_t)passed;
  else
    sid = (session_id_t)get_value (symbol_lookup_package ("DEFAULT"), "SID");

  if (sid != (session_id_t)0)
    {
      SESSION_INFO *info = session_get_info (sid);

      if ((info != (SESSION_INFO *)NULL) &&
	  (info->data != (unsigned char *)NULL))
	{
	  bprintf_insert (page, start, "%s", (char *)info->data);
	  *newstart += strlen ((char *)info->data);
	}

      if (info) session_free (info);
    }
  if (passed != (char *)NULL) free (passed);
}

static int
valid_alist_p (char *alist)
{
  return (1);
}
  
static void
pf_set_session_data (PFunArgs)
{
  char *alist = mhtml_evaluate_string (get_positional_arg (vars, 0));
  session_id_t sid;

  sid = (session_id_t)get_value (symbol_lookup_package ("DEFAULT"), "SID");

  if ((sid != (session_id_t)0) && (!empty_string_p (alist)))
    {
      SESSION_INFO *info = session_get_info (sid);

      if (info != (SESSION_INFO *)NULL)
	{
	  if (info->data != (unsigned char *)NULL)
	    free (info->data);

	  if (valid_alist_p (alist))
	    {
	      info->data = strdup (alist);
	      info->length = strlen (alist);
	    }
	  else
	    {
	      info->data = (char *)NULL;
	      info->length = 0;
	    }

	  session_put_info (info);
	  session_free (info);
	}
    }

  if (alist != (char *)NULL) free (alist);
}

static void
pf_unset_session_var (PFunArgs)
{
  session_id_t sid =
    (session_id_t)get_value (symbol_lookup_package ("DEFAULT"), "SID");

  if (sid != (session_id_t)0)
    {
      SESSION_INFO *info = session_get_info (sid);

      if ((info != (SESSION_INFO *)NULL) && (vars != (Package *)NULL))
	{
	  register int i;
	  Package *session_data = symbol_get_package ((char *)NULL);
	  char **names = get_vars_names (vars);

	  sd_info_to_package (info, session_data);

	  for (i = 0; names[i] != (char *)NULL; i++)
	    {
	      char *name = names[i];

	      name = mhtml_evaluate_string (name);

	      if (name != (char *)NULL)
		symbol_remove_in_package (session_data, name);

	      if (name && name != names[i]) free (name);
	    }

	  sd_package_to_info (info, session_data);
	  session_put_info (info);
	  symbol_destroy_package (session_data);
	}
      if (info) session_free (info);
    }
}

static void
pf_session_export (PFunArgs)
{
  session_id_t sid =
    (session_id_t)get_value (symbol_lookup_package ("DEFAULT"), "SID");

  if (sid != (session_id_t)0)
    {
      SESSION_INFO *info = session_get_info (sid);

      if (info != (SESSION_INFO *)NULL)
	{
	  char *internal_name, *external_name;
	  Package *package, *session_data;
	  Symbol *sym, **symbols = (Symbol **)NULL;

	  internal_name = mhtml_evaluate_string (get_positional_arg (vars, 0));
	  external_name = mhtml_evaluate_string (get_positional_arg (vars, 1));
	  session_data = symbol_get_package ((char *)NULL);
	  sd_info_to_package (info, session_data);

	  if (empty_string_p (internal_name))
	    package = CurrentPackage;
	  else
	    package = symbol_get_package (internal_name);

	  if (package != (Package *)NULL)
	    symbols = symbols_of_package (package);

	  if (symbols != (Symbol **)NULL)
	    {
	      register int i;
	      char *prefix = (char *)NULL;
	      int prefix_len = 0;

	      if (external_name == (char *)NULL)
		external_name = package->name;

	      if (!empty_string_p (external_name))
		prefix = external_name;
	      else if (!empty_string_p (internal_name))
		prefix = internal_name;

	      if (prefix)
		prefix_len = strlen (prefix);

	      for (i = 0; (sym = symbols[i]) != (Symbol *)NULL; i++)
		{
		  char *save_name = sym->name;
		  Symbol *newsym;

		  if (prefix_len)
		    {
		      save_name = (char *)xmalloc
			(3 + prefix_len + sym->name_len);
		      sprintf (save_name, "%s::%s", prefix, sym->name);
		    }

		  newsym = symbol_copy (sym, session_data);
		  symbol_rename (newsym, save_name);

		  if (save_name != sym->name)
		    free (save_name);
		}
	      free (symbols);
	    }
	  sd_package_to_info (info, session_data);
	  session_put_info (info);
	  symbol_destroy_package (session_data);
	  session_free (info);
	}
    }
}

static void
pf_session_import (PFunArgs)
{
  SESSION_INFO *info = (SESSION_INFO *)NULL;
  session_id_t sid =
    (session_id_t)get_value (symbol_lookup_package ("DEFAULT"), "SID");

  if (sid != (session_id_t)0)
    info = session_get_info (sid);

  /* If there is session info, snarf the vars.. */
  if (info != (SESSION_INFO *)NULL)
    {
      Package *session_data = symbol_get_package ((char *)NULL);
      Symbol **symbols;

      sd_info_to_package (info, session_data);
      symbols = symbols_of_package (session_data);

      if (symbols != (Symbol **)NULL)
	{
	  register int i;
	  char *external = mhtml_evaluate_string (get_positional_arg (vars, 0));
	  char *internal = mhtml_evaluate_string (get_positional_arg (vars, 1));
	  int external_len = (external ? strlen (external) : 0);
	  int internal_len = (internal ? strlen (internal) : 0);
	  Symbol *sym;

	  /* Import variables skipping those that don't start with EXTERNAL.
	     Strip EXTERNAL from imported variable names, perhaps placing
	     them in the package specified by INTERNAL. */
	  for (i = 0; (sym = symbols[i]) != (Symbol *)NULL; i++)
	    {
	      int import_it = 1;

	      if (external_len)
		{
		  if ((strncasecmp (sym->name, external, external_len) == 0) &&
		      (sym->name[external_len] == ':' &&
		       sym->name[external_len + 1] == ':'))
		    import_it = 1;
		  else
		    import_it = 0;
		}

	      if (import_it)
		{
		  char *sym_name = sym->name;
		  char *new_sym_name;
		  Symbol *killer;

		  if (external_len)
		    sym_name += external_len + 2;

		  new_sym_name = sym_name;

		  /* If there is no internal or external package prefix,
		     then import this symbol into the package that is
		     named in the symbol name. */
		  if ((internal_len + external_len) == 0)
		    {
		      char *temp_name;
		      killer = symbol_intern (sym_name);
		      temp_name = strdup (killer->name);
		      sym = symbol_copy (sym, (Package *)killer->package);
		      symbol_rename (sym, temp_name);
		      free (temp_name);
		    }
		  else
		    {
		      char *intern_name = sym_name;
		      Package *import_package;

		      /* Get the package that we are importing to. */
		      if (!internal_len)
			import_package = CurrentPackage;
		      else
			import_package = symbol_get_package (internal);

		      /* If no external name was mentioned, but an internal
			 package exists, then strip off the package name
			 of this symbol before interning it. */
		      if (external == (char *)NULL)
			{
			  intern_name = strstr (sym_name, "::");
			  if (!intern_name)
			    intern_name = sym_name;
			}

		      intern_name = strdup (intern_name);

		      /* Delete this symbol from the import package. */
		      killer =
			symbol_remove_in_package (import_package, intern_name);
		      symbol_free (killer);

		      /* Place this symbol in the import package. */
		      sym = symbol_copy (sym, import_package);
		      symbol_rename (sym, intern_name);
		      free (intern_name);
		    }
		}
	    }
	  free (symbols);
	}
      symbol_destroy_package (session_data);
      session_free (info);
    }
}


static void
pf_set_session_timeout (PFunArgs)
{
  char *timeout_ascii = mhtml_evaluate_string (get_positional_arg (vars, 0));
  session_id_t sid =
    (session_id_t)get_value (symbol_lookup_package ("DEFAULT"), "SID");

  if ((sid != (session_id_t)0) && (timeout_ascii != (char *)NULL))
    {
      int timeout_val = atoi (timeout_ascii);

      if (timeout_val > 0)
	{
	  SESSION_INFO *info = session_get_info (sid);

	  if (info != (SESSION_INFO *)NULL)
	    {
	      info->timeout = timeout_val;
	      session_put_info (info);
	      session_free (info);
	    }
	}
    }
}

/* <require-session [missing/alt]="no-sid code" timeout="code"> */
static void
pf_require_session (PFunArgs)
{
  char *missing_code = get_one_of (vars, "MISSING", "ALT", (char *)0);
  char *timeout_code = get_one_of (vars, "TIMEOUT", "TIMED-OUT", (char *)0);
  char *code = (char *)NULL;
  session_id_t sid =
    (session_id_t)get_value (symbol_lookup_package ("DEFAULT"), "SID");

  if (timeout_code == (char *)NULL)
    timeout_code = missing_code;

  if (empty_string_p ((char *)sid))
    code = missing_code;
  else if (session_access (sid) != 0)
    {
      symbol_remove_in_package (symbol_lookup_package ("DEFAULT"), "SID");
      code = timeout_code;
    }

  if (code)
    bprintf_insert (page, start, "%s", code);
}

static void
pf_set_session_db (PFunArgs)
{
  char *session_db_file = mhtml_evaluate_string (get_positional_arg (vars, 0));

  if (empty_string_p (session_db_file))
    {
      xfree (session_db_file);
      session_db_file = (char *)NULL;
    }

  set_session_database_location (session_db_file);
  pagefunc_set_variable ("mhttpd::session-database-file", session_db_file);

  xfree (session_db_file);
}

#if defined (DEPRECATED_SESSION_COMMANDS)
static void
pf_session_export_posted (PFunArgs)
{
  if (empty_string_p (body->buffer))
    bprintf (body, "posted \"\"");
  else
    bprintf_insert (body, 0, "posted ");
  vars = pagefunc_snarf_vars (body->buffer, 1);
  pf_session_export (page, body, vars, start, end, newstart, debug_level);
  symbol_destroy_package (vars);
}

static void
pf_session_import_posted (PFunArgs)
{
  bprintf (body, " \"\"");
  pf_session_import (page, body, vars, start, end, newstart, debug_level);
}
#endif /* DEPRECATED_SESSION_COMMANDS */
