513 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
		
		
			
		
	
	
			513 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
| 
								 | 
							
								/*  $Id$
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Part of SWI-Prolog
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    Author:        Jan Wielemaker
							 | 
						||
| 
								 | 
							
								    E-mail:        wielemak@science.uva.nl
							 | 
						||
| 
								 | 
							
								    WWW:           http://www.swi-prolog.org
							 | 
						||
| 
								 | 
							
								    Copyright (C): 2006, University of Amsterdam
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    This program is free software; you can redistribute it and/or
							 | 
						||
| 
								 | 
							
								    modify it under the terms of the GNU General Public License
							 | 
						||
| 
								 | 
							
								    as published by the Free Software Foundation; either version 2
							 | 
						||
| 
								 | 
							
								    of the License, 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
							 | 
						||
| 
								 | 
							
								    GNU General Public License for more details.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    You should have received a copy of the GNU Lesser General Public
							 | 
						||
| 
								 | 
							
								    License along with this library; if not, write to the Free Software
							 | 
						||
| 
								 | 
							
								    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    As a special exception, if you link this library with other files,
							 | 
						||
| 
								 | 
							
								    compiled with a Free Software compiler, to produce an executable, this
							 | 
						||
| 
								 | 
							
								    library does not by itself cause the resulting executable to be covered
							 | 
						||
| 
								 | 
							
								    by the GNU General Public License. This exception does not however
							 | 
						||
| 
								 | 
							
								    invalidate any other reasons why the executable file might be covered by
							 | 
						||
| 
								 | 
							
								    the GNU General Public License.
							 | 
						||
| 
								 | 
							
								*/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- module(http_session,
							 | 
						||
| 
								 | 
							
									  [ http_set_session_options/1,	% +Options
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									    http_session_id/1,		% -SessionId
							 | 
						||
| 
								 | 
							
									    http_in_session/1,		% -SessionId
							 | 
						||
| 
								 | 
							
									    http_current_session/2,	% ?SessionId, ?Data
							 | 
						||
| 
								 | 
							
									    http_close_session/1,	% +SessionId
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									    http_session_asserta/1,	% +Data
							 | 
						||
| 
								 | 
							
									    http_session_assert/1,	% +Data
							 | 
						||
| 
								 | 
							
									    http_session_retract/1,	% ?Data
							 | 
						||
| 
								 | 
							
									    http_session_retractall/1,	% +Data
							 | 
						||
| 
								 | 
							
									    http_session_data/1		% ?Data
							 | 
						||
| 
								 | 
							
									  ]).
							 | 
						||
| 
								 | 
							
								:- use_module(http_wrapper).
							 | 
						||
| 
								 | 
							
								:- use_module(library(error)).
							 | 
						||
| 
								 | 
							
								:- use_module(library(debug)).
							 | 
						||
| 
								 | 
							
								:- use_module(library(socket)).
							 | 
						||
| 
								 | 
							
								:- use_module(library(broadcast)).
							 | 
						||
| 
								 | 
							
								:- use_module(library(lists)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/** <module> HTTP Session management
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This library defines session management based   on HTTP cookies. Session
							 | 
						||
| 
								 | 
							
								management is enabled simply by  loading   this  module.  Details can be
							 | 
						||
| 
								 | 
							
								modified using http_set_session_options/1.  If   sessions  are  enabled,
							 | 
						||
| 
								 | 
							
								http_session_id/1 produces the current session and http_session_assert/1
							 | 
						||
| 
								 | 
							
								and  friends  maintain  data  about  the  session.  If  the  session  is
							 | 
						||
| 
								 | 
							
								reclaimed, all associated data is reclaimed too.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Begin and end of sessions can be monitored using library(broadcast). The
							 | 
						||
| 
								 | 
							
								broadcasted messages are:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    * http_session(begin(SessionID, Peer))
							 | 
						||
| 
								 | 
							
								    Broadcasted if a session is started
							 | 
						||
| 
								 | 
							
								    * http_session(end(SessionId, Peer))
							 | 
						||
| 
								 | 
							
								    Broadcasted if a session is ended. See http_close_session/1.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								For example, the  following  calls   end_session(SessionId)  whenever  a
							 | 
						||
| 
								 | 
							
								session terminates. Please note that sessions  ends are not scheduled to
							 | 
						||
| 
								 | 
							
								happen at the actual timeout moment of  the session. Instead, creating a
							 | 
						||
| 
								 | 
							
								new session scans the  active  list   for  timed-out  sessions. This may
							 | 
						||
| 
								 | 
							
								change in future versions of this library.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    ==
							 | 
						||
| 
								 | 
							
								    :- listen(http_session(end(SessionId, Peer)),
							 | 
						||
| 
								 | 
							
									      end_session(SessionId)).
							 | 
						||
| 
								 | 
							
								    ==
							 | 
						||
| 
								 | 
							
								*/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- dynamic
							 | 
						||
| 
								 | 
							
									session_setting/1,		% Name(Value)
							 | 
						||
| 
								 | 
							
									current_session/2,		% SessionId, Peer
							 | 
						||
| 
								 | 
							
									last_used/2,			% SessionId, Time
							 | 
						||
| 
								 | 
							
									session_data/2.			% SessionId, Data
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								session_setting(timeout(600)).		% timeout in seconds
							 | 
						||
| 
								 | 
							
								session_setting(cookie('swipl_session')).
							 | 
						||
| 
								 | 
							
								session_setting(path(/)).
							 | 
						||
| 
								 | 
							
								session_setting(enabled(true)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								session_option(timeout, integer).
							 | 
						||
| 
								 | 
							
								session_option(cookie, atom).
							 | 
						||
| 
								 | 
							
								session_option(path, atom).
							 | 
						||
| 
								 | 
							
								session_option(route, atom).
							 | 
						||
| 
								 | 
							
								session_option(enabled, boolean).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	http_set_session_options(+Options) is det.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Set options for the session library.  Provided options are:
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%		* timeout(+Seconds)
							 | 
						||
| 
								 | 
							
								%		Session timeout in seconds.  Default is 600 (10 min).
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%		* cookie(+Cookiekname)
							 | 
						||
| 
								 | 
							
								%		Name to use for the cookie to identify the session.
							 | 
						||
| 
								 | 
							
								%		Default =swipl_session=.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%		* path(+Path)
							 | 
						||
| 
								 | 
							
								%		Path to which the cookie is associated.  Default is
							 | 
						||
| 
								 | 
							
								%		=|/|=.	Cookies are only sent if the HTTP request path
							 | 
						||
| 
								 | 
							
								%		is a refinement of Path.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%		* route(+Route)
							 | 
						||
| 
								 | 
							
								%		Set the route name. Default is the unqualified
							 | 
						||
| 
								 | 
							
								%		hostname. To cancel adding a route, use the empty
							 | 
						||
| 
								 | 
							
								%		atom.  See route/1.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%		* enabled(+Boolean)
							 | 
						||
| 
								 | 
							
								%		Enable/disable session management.  Sesion management
							 | 
						||
| 
								 | 
							
								%		is enabled by default after loading this file.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_set_session_options([]).
							 | 
						||
| 
								 | 
							
								http_set_session_options([H|T]) :-
							 | 
						||
| 
								 | 
							
									http_session_option(H),
							 | 
						||
| 
								 | 
							
									http_set_session_options(T).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_session_option(Option) :-
							 | 
						||
| 
								 | 
							
									functor(Option, Name, Arity),
							 | 
						||
| 
								 | 
							
									arg(1, Option, Value),
							 | 
						||
| 
								 | 
							
									(   session_option(Name, Type)
							 | 
						||
| 
								 | 
							
									->  must_be(Type, Value)
							 | 
						||
| 
								 | 
							
									;   domain_error(http_session_option, Option)
							 | 
						||
| 
								 | 
							
									),
							 | 
						||
| 
								 | 
							
									functor(Free, Name, Arity),
							 | 
						||
| 
								 | 
							
									retractall(session_setting(Free)),
							 | 
						||
| 
								 | 
							
									assert(session_setting(Option)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	http_session_id(-SessionId) is det.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	True if SessionId is an identifier for the current session.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	@param SessionId is an atom.
							 | 
						||
| 
								 | 
							
								%	@error existence_error(http_session, _)
							 | 
						||
| 
								 | 
							
								%	@see   http_in_session/1 for a version that fails if there is
							 | 
						||
| 
								 | 
							
								%	       no session.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_session_id(SessionID) :-
							 | 
						||
| 
								 | 
							
									(   http_in_session(ID)
							 | 
						||
| 
								 | 
							
									->  SessionID = ID
							 | 
						||
| 
								 | 
							
									;   throw(error(existence_error(http_session, _), _))
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	http_in_session(-SessionId) is semidet.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	True if SessionId is an identifier  for the current session. The
							 | 
						||
| 
								 | 
							
								%	current session is extracted from   session(ID) from the current
							 | 
						||
| 
								 | 
							
								%	HTTP request (see http_current_request/1). The   value is cached
							 | 
						||
| 
								 | 
							
								%	in a backtrackable global variable   =http_session_id=.  Using a
							 | 
						||
| 
								 | 
							
								%	backtrackable global variable is safe  because continuous worker
							 | 
						||
| 
								 | 
							
								%	threads use a failure driven  look   and  spawned  threads start
							 | 
						||
| 
								 | 
							
								%	without any global variables. This variable  can be set from the
							 | 
						||
| 
								 | 
							
								%	commandline to fake running a goal   from the commandline in the
							 | 
						||
| 
								 | 
							
								%	context of a session.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	@see http_session_id/1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_in_session(SessionID) :-
							 | 
						||
| 
								 | 
							
									(   nb_current(http_session_id, ID),
							 | 
						||
| 
								 | 
							
									    ID \== []
							 | 
						||
| 
								 | 
							
									->  true
							 | 
						||
| 
								 | 
							
									;   http_current_request(Request),
							 | 
						||
| 
								 | 
							
									    memberchk(session(ID), Request),
							 | 
						||
| 
								 | 
							
									    b_setval(http_session_id, ID)
							 | 
						||
| 
								 | 
							
									;   b_setval(http_session_id, no_session),
							 | 
						||
| 
								 | 
							
									    fail
							 | 
						||
| 
								 | 
							
									),
							 | 
						||
| 
								 | 
							
									ID \== no_session,
							 | 
						||
| 
								 | 
							
									SessionID = ID.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	http_session(+RequestIn, -RequestOut, -SessionID) is semidet.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Maintain the notion of a  session   using  a client-side cookie.
							 | 
						||
| 
								 | 
							
								%	This must be called first when handling a request that wishes to
							 | 
						||
| 
								 | 
							
								%	do session management, after which the possibly modified request
							 | 
						||
| 
								 | 
							
								%	must be used for further processing.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_session(Request, Request, SessionID) :-
							 | 
						||
| 
								 | 
							
									memberchk(session(SessionID0), Request), !,
							 | 
						||
| 
								 | 
							
									SessionID = SessionID0.
							 | 
						||
| 
								 | 
							
								http_session(Request0, Request, SessionID) :-
							 | 
						||
| 
								 | 
							
									memberchk(cookie(Cookies), Request0),
							 | 
						||
| 
								 | 
							
									session_setting(cookie(Cookie)),
							 | 
						||
| 
								 | 
							
									memberchk(Cookie=SessionID0, Cookies),
							 | 
						||
| 
								 | 
							
									peer(Request0, Peer),
							 | 
						||
| 
								 | 
							
									valid_session_id(SessionID0, Peer), !,
							 | 
						||
| 
								 | 
							
									SessionID = SessionID0,
							 | 
						||
| 
								 | 
							
									Request = [session(SessionID)|Request0],
							 | 
						||
| 
								 | 
							
									b_setval(http_session_id, SessionID).
							 | 
						||
| 
								 | 
							
								http_session(Request0, Request, SessionID) :-
							 | 
						||
| 
								 | 
							
									session_setting(path(Path)),
							 | 
						||
| 
								 | 
							
									memberchk(path(ReqPath), Request0),
							 | 
						||
| 
								 | 
							
									sub_atom(ReqPath, 0, _, _, Path), !,
							 | 
						||
| 
								 | 
							
									http_gc_sessions,		% GC dead sessions
							 | 
						||
| 
								 | 
							
									gen_cookie(SessionID),
							 | 
						||
| 
								 | 
							
									session_setting(cookie(Cookie)),
							 | 
						||
| 
								 | 
							
									format('Set-Cookie: ~w=~w; path=~w~n', [Cookie, SessionID, Path]),
							 | 
						||
| 
								 | 
							
									Request = [session(SessionID)|Request0],
							 | 
						||
| 
								 | 
							
									peer(Request0, Peer),
							 | 
						||
| 
								 | 
							
									open_session(SessionID, Peer),
							 | 
						||
| 
								 | 
							
									b_setval(http_session_id, SessionID).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- multifile
							 | 
						||
| 
								 | 
							
									http:request_expansion/2.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http:request_expansion(Request0, Request) :-
							 | 
						||
| 
								 | 
							
									session_setting(enabled(true)),
							 | 
						||
| 
								 | 
							
									http_session(Request0, Request, _SessionID).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	peer(+Request, -Peer)
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Find peer for current request. If   unknown we leave it unbound.
							 | 
						||
| 
								 | 
							
								%	Alternatively we should treat this as an error.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								peer(Request, Peer) :-
							 | 
						||
| 
								 | 
							
									(   memberchk(peer(Peer), Request)
							 | 
						||
| 
								 | 
							
									->  true
							 | 
						||
| 
								 | 
							
									;   true
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	open_session(+SessionID, +Peer)
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Open a new session.  Uses broadcast/1 with the term
							 | 
						||
| 
								 | 
							
								%	http_session(begin(SessionID, Peer)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								open_session(SessionID, Peer) :-
							 | 
						||
| 
								 | 
							
									get_time(Now),
							 | 
						||
| 
								 | 
							
									assert(current_session(SessionID, Peer)),
							 | 
						||
| 
								 | 
							
									assert(last_used(SessionID, Now)),
							 | 
						||
| 
								 | 
							
									broadcast(http_session(begin(SessionID, Peer))).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	valid_session_id(+SessionID, +Peer)
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Check if this sessionID is known. If so, check the idle time and
							 | 
						||
| 
								 | 
							
								%	update the last_used for this session.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								valid_session_id(SessionID, Peer) :-
							 | 
						||
| 
								 | 
							
									current_session(SessionID, SessionPeer),
							 | 
						||
| 
								 | 
							
									get_time(Now),
							 | 
						||
| 
								 | 
							
									(   session_setting(timeout(Timeout)),
							 | 
						||
| 
								 | 
							
									    Timeout > 0
							 | 
						||
| 
								 | 
							
									->  get_last_used(SessionID, Last),
							 | 
						||
| 
								 | 
							
									    Idle is Now - Last,
							 | 
						||
| 
								 | 
							
									    (	Idle =< Timeout
							 | 
						||
| 
								 | 
							
									    ->  true
							 | 
						||
| 
								 | 
							
									    ;   http_close_session(SessionID),
							 | 
						||
| 
								 | 
							
										fail
							 | 
						||
| 
								 | 
							
									    )
							 | 
						||
| 
								 | 
							
									;   Peer \== SessionPeer
							 | 
						||
| 
								 | 
							
									->  http_close_session(SessionID),
							 | 
						||
| 
								 | 
							
									    fail
							 | 
						||
| 
								 | 
							
									;   true
							 | 
						||
| 
								 | 
							
									),
							 | 
						||
| 
								 | 
							
									set_last_used(SessionID, Now).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								get_last_used(SessionID, Last) :-
							 | 
						||
| 
								 | 
							
									atom(SessionID), !,
							 | 
						||
| 
								 | 
							
									with_mutex(http_session, last_used(SessionID, Last)).
							 | 
						||
| 
								 | 
							
								get_last_used(SessionID, Last) :-
							 | 
						||
| 
								 | 
							
									with_mutex(http_session,
							 | 
						||
| 
								 | 
							
										   findall(SessionID-Last,
							 | 
						||
| 
								 | 
							
											   last_used(SessionID, Last),
							 | 
						||
| 
								 | 
							
											   Pairs)),
							 | 
						||
| 
								 | 
							
									member(SessionID-Last, Pairs).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								set_last_used(SessionID, Now) :-
							 | 
						||
| 
								 | 
							
									with_mutex(http_session,
							 | 
						||
| 
								 | 
							
										  (   retractall(last_used(SessionID, _)),
							 | 
						||
| 
								 | 
							
										      assert(last_used(SessionID, Now)))).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										 /*******************************
							 | 
						||
| 
								 | 
							
										 *	   SESSION DATA		*
							 | 
						||
| 
								 | 
							
										 *******************************/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	http_session_asserta(+Data) is det.
							 | 
						||
| 
								 | 
							
								%%	http_session_assert(+Data) is det.
							 | 
						||
| 
								 | 
							
								%%	http_session_retract(?Data) is nondet.
							 | 
						||
| 
								 | 
							
								%%	http_session_retractall(?Data) is det.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Versions of assert/1, retract/1 and retractall/1 that associate
							 | 
						||
| 
								 | 
							
								%	data with the current HTTP session.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_session_asserta(Data) :-
							 | 
						||
| 
								 | 
							
									http_session_id(SessionId),
							 | 
						||
| 
								 | 
							
									asserta(session_data(SessionId, Data)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_session_assert(Data) :-
							 | 
						||
| 
								 | 
							
									http_session_id(SessionId),
							 | 
						||
| 
								 | 
							
									assert(session_data(SessionId, Data)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_session_retract(Data) :-
							 | 
						||
| 
								 | 
							
									http_session_id(SessionId),
							 | 
						||
| 
								 | 
							
									retract(session_data(SessionId, Data)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_session_retractall(Data) :-
							 | 
						||
| 
								 | 
							
									http_session_id(SessionId),
							 | 
						||
| 
								 | 
							
									retractall(session_data(SessionId, Data)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%	http_session_data(?Data) is nondet.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	True if Data is associated using http_session_assert/1 to the
							 | 
						||
| 
								 | 
							
								%	current HTTP session.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_session_data(Data) :-
							 | 
						||
| 
								 | 
							
									http_session_id(SessionId),
							 | 
						||
| 
								 | 
							
									session_data(SessionId, Data).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										 /*******************************
							 | 
						||
| 
								 | 
							
										 *	     ENUMERATE		*
							 | 
						||
| 
								 | 
							
										 *******************************/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	http_current_session(?SessionID, ?Data) is nondet.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Enumerate the current sessions and   associated data.  There are
							 | 
						||
| 
								 | 
							
								%	two _Pseudo_ data elements:
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%		* idle(Seconds)
							 | 
						||
| 
								 | 
							
								%		Session has been idle for Seconds.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%		* peer(Peer)
							 | 
						||
| 
								 | 
							
								%		Peer of the connection.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_current_session(SessionID, Data) :-
							 | 
						||
| 
								 | 
							
									get_time(Now),
							 | 
						||
| 
								 | 
							
									get_last_used(SessionID, Last),
							 | 
						||
| 
								 | 
							
									Idle is Now - Last,
							 | 
						||
| 
								 | 
							
									(   session_setting(timeout(Timeout)),
							 | 
						||
| 
								 | 
							
									    Timeout > 0
							 | 
						||
| 
								 | 
							
									->  Idle =< Timeout
							 | 
						||
| 
								 | 
							
									;   true
							 | 
						||
| 
								 | 
							
									),
							 | 
						||
| 
								 | 
							
									(   Data = idle(Idle)
							 | 
						||
| 
								 | 
							
									;   Data = peer(Peer),
							 | 
						||
| 
								 | 
							
									    current_session(SessionID, Peer)
							 | 
						||
| 
								 | 
							
									;   session_data(SessionID, Data)
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										 /*******************************
							 | 
						||
| 
								 | 
							
										 *	    GC SESSIONS		*
							 | 
						||
| 
								 | 
							
										 *******************************/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	http_close_session(+SessionID) is det.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Closes an HTTP session. This predicate   can  be called from any
							 | 
						||
| 
								 | 
							
								%	thread to terminate a session.  It uses the broadcast/1 service
							 | 
						||
| 
								 | 
							
								%	with the message below.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%		http_session(end(SessionId, Peer))
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	The broadcast is done *before* the session data is destroyed and
							 | 
						||
| 
								 | 
							
								%	the listen-handlers are executed in context  of the session that
							 | 
						||
| 
								 | 
							
								%	is being closed. Here  is  an   example  that  destroys a Prolog
							 | 
						||
| 
								 | 
							
								%	thread that is associated to a thread:
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	==
							 | 
						||
| 
								 | 
							
								%	:- listen(http_session(end(SessionId, _Peer)),
							 | 
						||
| 
								 | 
							
								%		  kill_session_thread(SessionID)).
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	kill_session_thread(SessionID) :-
							 | 
						||
| 
								 | 
							
								%		http_session_data(thread(ThreadID)),
							 | 
						||
| 
								 | 
							
								%		thread_signal(ThreadID, throw(session_closed)).
							 | 
						||
| 
								 | 
							
								%	==
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Succeed without any effect if  SessionID   does  not refer to an
							 | 
						||
| 
								 | 
							
								%	active session.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	@error	type_error(atom, SessionID)
							 | 
						||
| 
								 | 
							
								%	@see	listen/2 for acting upon closed sessions
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_close_session(SessionId) :-
							 | 
						||
| 
								 | 
							
									must_be(atom, SessionId),
							 | 
						||
| 
								 | 
							
									(   current_session(SessionId, Peer),
							 | 
						||
| 
								 | 
							
									    (	b_setval(http_session_id, SessionId),
							 | 
						||
| 
								 | 
							
										broadcast(http_session(end(SessionId, Peer))),
							 | 
						||
| 
								 | 
							
										fail
							 | 
						||
| 
								 | 
							
									    ;	true
							 | 
						||
| 
								 | 
							
									    ),
							 | 
						||
| 
								 | 
							
									    retractall(current_session(SessionId, _)),
							 | 
						||
| 
								 | 
							
									    retractall(last_used(SessionId, _)),
							 | 
						||
| 
								 | 
							
									    retractall(session_data(SessionId, _)),
							 | 
						||
| 
								 | 
							
									    fail
							 | 
						||
| 
								 | 
							
									;   true
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%	http_gc_sessions/0
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Delete dead sessions. When  should  we   be  calling  this? This
							 | 
						||
| 
								 | 
							
								%	assumes that updated sessions are at the end of the clause list,
							 | 
						||
| 
								 | 
							
								%	so we can break  as  soon   as  we  encounter  a no-yet-timedout
							 | 
						||
| 
								 | 
							
								%	session.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								http_gc_sessions :-
							 | 
						||
| 
								 | 
							
									session_setting(timeout(Timeout)),
							 | 
						||
| 
								 | 
							
									Timeout > 0, !,
							 | 
						||
| 
								 | 
							
									get_time(Now),
							 | 
						||
| 
								 | 
							
									(   last_used(SessionID, Last),
							 | 
						||
| 
								 | 
							
									    Idle is Now - Last,
							 | 
						||
| 
								 | 
							
									    (	Idle > Timeout
							 | 
						||
| 
								 | 
							
									    ->	http_close_session(SessionID),
							 | 
						||
| 
								 | 
							
										fail
							 | 
						||
| 
								 | 
							
									    ;	!
							 | 
						||
| 
								 | 
							
									    )
							 | 
						||
| 
								 | 
							
									;   true
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								http_gc_sessions.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										 /*******************************
							 | 
						||
| 
								 | 
							
										 *	       UTIL		*
							 | 
						||
| 
								 | 
							
										 *******************************/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	gen_cookie(-Cookie) is det.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Generate a random cookie that  can  be   used  by  a  browser to
							 | 
						||
| 
								 | 
							
								%	identify  the  current  session.  The   cookie  has  the  format
							 | 
						||
| 
								 | 
							
								%	XXXX-XXXX-XXXX-XXXX[.<route>], where XXXX are random hexadecimal
							 | 
						||
| 
								 | 
							
								%	numbers  and  [.<route>]  is  the    optionally   added  routing
							 | 
						||
| 
								 | 
							
								%	information.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								gen_cookie(Cookie) :-
							 | 
						||
| 
								 | 
							
									route(Route), !,
							 | 
						||
| 
								 | 
							
									random_4(R1,R2,R3,R4),
							 | 
						||
| 
								 | 
							
									format(atom(Cookie),
							 | 
						||
| 
								 | 
							
										'~`0t~16r~4|-~`0t~16r~9|-~`0t~16r~14|-~`0t~16r~19|.~w',
							 | 
						||
| 
								 | 
							
										[R1,R2,R3,R4,Route]).
							 | 
						||
| 
								 | 
							
								gen_cookie(Cookie) :-
							 | 
						||
| 
								 | 
							
									random_4(R1,R2,R3,R4),
							 | 
						||
| 
								 | 
							
									format(atom(Cookie),
							 | 
						||
| 
								 | 
							
										'~`0t~16r~4|-~`0t~16r~9|-~`0t~16r~14|-~`0t~16r~19|',
							 | 
						||
| 
								 | 
							
										[R1,R2,R3,R4]).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- thread_local
							 | 
						||
| 
								 | 
							
									route_cache/1.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	route(-RouteID) is semidet.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Fetch the route identifier. This value   is added as .<route> to
							 | 
						||
| 
								 | 
							
								%	the session cookie and used  by   -for  example- the apache load
							 | 
						||
| 
								 | 
							
								%	balanching module. The default route is   the  local name of the
							 | 
						||
| 
								 | 
							
								%	host.     Alternatives     may      be       provided      using
							 | 
						||
| 
								 | 
							
								%	http_set_session_options/1.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								route(Route) :-
							 | 
						||
| 
								 | 
							
									route_cache(Route), !,
							 | 
						||
| 
								 | 
							
									Route \== ''.
							 | 
						||
| 
								 | 
							
								route(Route) :-
							 | 
						||
| 
								 | 
							
									route_no_cache(Route),
							 | 
						||
| 
								 | 
							
									assert(route_cache(Route)),
							 | 
						||
| 
								 | 
							
									Route \== ''.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								route_no_cache(Route) :-
							 | 
						||
| 
								 | 
							
									session_setting(route(Route)), !.
							 | 
						||
| 
								 | 
							
								route_no_cache(Route) :-
							 | 
						||
| 
								 | 
							
									gethostname(Host),
							 | 
						||
| 
								 | 
							
									(   sub_atom(Host, Before, _, _, '.')
							 | 
						||
| 
								 | 
							
									->  sub_atom(Host, 0, Before, _, Route)
							 | 
						||
| 
								 | 
							
									;   Route = Host
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	random_4(-R1,-R2,-R3,-R4) is det.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Generate 4 2-byte random  numbers.   Uses  =|/dev/urandom|= when
							 | 
						||
| 
								 | 
							
								%	available to make prediction of the session IDs hard.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								random_4(R1,R2,R3,R4) :-
							 | 
						||
| 
								 | 
							
									urandom(In), !,
							 | 
						||
| 
								 | 
							
									get_pair(In, R1),
							 | 
						||
| 
								 | 
							
									get_pair(In, R2),
							 | 
						||
| 
								 | 
							
									get_pair(In, R3),
							 | 
						||
| 
								 | 
							
									get_pair(In, R4).
							 | 
						||
| 
								 | 
							
								random_4(R1,R2,R3,R4) :-
							 | 
						||
| 
								 | 
							
									R1 is random(65536),
							 | 
						||
| 
								 | 
							
									R2 is random(65536),
							 | 
						||
| 
								 | 
							
									R3 is random(65536),
							 | 
						||
| 
								 | 
							
									R4 is random(65536).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- dynamic
							 | 
						||
| 
								 | 
							
									urandom_handle/1.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								urandom(Handle) :-
							 | 
						||
| 
								 | 
							
									urandom_handle(Handle), !,
							 | 
						||
| 
								 | 
							
									Handle \== [].
							 | 
						||
| 
								 | 
							
								urandom(Handle) :-
							 | 
						||
| 
								 | 
							
									catch(open('/dev/urandom', read, In, [type(binary)]), _, fail), !,
							 | 
						||
| 
								 | 
							
									assert(urandom_handle(In)),
							 | 
						||
| 
								 | 
							
									Handle = In.
							 | 
						||
| 
								 | 
							
								urandom(_) :-
							 | 
						||
| 
								 | 
							
									assert(urandom_handle([])),
							 | 
						||
| 
								 | 
							
									fail.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								get_pair(In, Value) :-
							 | 
						||
| 
								 | 
							
									get_byte(In, B1),
							 | 
						||
| 
								 | 
							
									get_byte(In, B2),
							 | 
						||
| 
								 | 
							
									Value is B1<<8+B2.
							 |