35
36:- module(http_session,
37 [ http_set_session_options/1, 38 http_set_session/1, 39 http_set_session/2, 40 http_session_option/1, 41
42 http_session_id/1, 43 http_in_session/1, 44 http_current_session/2, 45 http_close_session/1, 46 http_open_session/2, 47
48 http_session_cookie/1, 49
50 http_session_asserta/1, 51 http_session_assert/1, 52 http_session_retract/1, 53 http_session_retractall/1, 54 http_session_data/1 55 ]). 56:- use_module(http_wrapper). 57:- use_module(http_stream). 58:- use_module(library(error)). 59:- use_module(library(debug)). 60:- use_module(library(socket)). 61:- use_module(library(broadcast)). 62:- use_module(library(lists)). 63:- use_module(library(time)). 64
65:- predicate_options(http_open_session/2, 2, [renew(boolean)]). 66
102
103:- dynamic
104 session_setting/1, 105 current_session/2, 106 last_used/2, 107 session_data/2. 108
109session_setting(timeout(600)). 110session_setting(cookie('swipl_session')).
111session_setting(path(/)).
112session_setting(enabled(true)).
113session_setting(create(auto)).
114session_setting(proxy_enabled(false)).
115session_setting(gc(passive)).
116
117session_option(timeout, integer).
118session_option(cookie, atom).
119session_option(path, atom).
120session_option(create, oneof([auto,noauto])).
121session_option(route, atom).
122session_option(enabled, boolean).
123session_option(proxy_enabled, boolean).
124session_option(gc, oneof([active,passive])).
125
170
171http_set_session_options([]).
172http_set_session_options([H|T]) :-
173 http_set_session_option(H),
174 http_set_session_options(T).
175
176http_set_session_option(Option) :-
177 functor(Option, Name, Arity),
178 arg(1, Option, Value),
179 ( session_option(Name, Type)
180 -> must_be(Type, Value)
181 ; domain_error(http_session_option, Option)
182 ),
183 functor(Free, Name, Arity),
184 ( clause(session_setting(Free), _, Ref)
185 -> ( Free \== Value
186 -> asserta(session_setting(Option)),
187 erase(Ref),
188 updated_session_setting(Name, Free, Value)
189 ; true
190 )
191 ; asserta(session_setting(Option))
192 ).
193
197
198http_session_option(Option) :-
199 session_setting(Option).
200
205
206session_setting(SessionId, Setting) :-
207 nonvar(Setting),
208 functor(Setting, Name, 1),
209 local_option(Name, Value, Term),
210 session_data(SessionId, '$setting'(Term)),
211 !,
212 arg(1, Setting, Value).
213session_setting(_, Setting) :-
214 session_setting(Setting).
215
216updated_session_setting(gc, _, passive) :-
217 stop_session_gc_thread, !.
218updated_session_setting(_, _, _). 219
220
229
230http_set_session(Setting) :-
231 http_session_id(SessionId),
232 http_set_session(SessionId, Setting).
233
234http_set_session(SessionId, Setting) :-
235 functor(Setting, Name, Arity),
236 ( local_option(Name, _, _)
237 -> true
238 ; permission_error(set, http_session, Setting)
239 ),
240 arg(1, Setting, Value),
241 ( session_option(Name, Type)
242 -> must_be(Type, Value)
243 ; domain_error(http_session_option, Setting)
244 ),
245 functor(Free, Name, Arity),
246 retractall(session_data(SessionId, '$setting'(Free))),
247 assert(session_data(SessionId, '$setting'(Setting))).
248
249local_option(timeout, X, timeout(X)).
250
259
260http_session_id(SessionID) :-
261 ( http_in_session(ID)
262 -> SessionID = ID
263 ; throw(error(existence_error(http_session, _), _))
264 ).
265
279
280http_in_session(SessionID) :-
281 nb_current(http_session_id, ID),
282 ID \== [],
283 !,
284 debug(http_session, 'Session id from global variable: ~q', [ID]),
285 ID \== no_session,
286 SessionID = ID.
287http_in_session(SessionID) :-
288 http_current_request(Request),
289 http_in_session(Request, SessionID).
290
291http_in_session(Request, SessionID) :-
292 memberchk(session(ID), Request),
293 !,
294 debug(http_session, 'Session id from request: ~q', [ID]),
295 b_setval(http_session_id, ID),
296 SessionID = ID.
297http_in_session(Request, SessionID) :-
298 memberchk(cookie(Cookies), Request),
299 session_setting(cookie(Cookie)),
300 member(Cookie=SessionID0, Cookies),
301 debug(http_session, 'Session id from cookie: ~q', [SessionID0]),
302 peer(Request, Peer),
303 valid_session_id(SessionID0, Peer),
304 !,
305 b_setval(http_session_id, SessionID0),
306 SessionID = SessionID0.
307
308
319
320http_session(Request, Request, SessionID) :-
321 memberchk(session(SessionID0), Request),
322 !,
323 SessionID = SessionID0.
324http_session(Request0, Request, SessionID) :-
325 memberchk(cookie(Cookies), Request0),
326 session_setting(cookie(Cookie)),
327 member(Cookie=SessionID0, Cookies),
328 peer(Request0, Peer),
329 valid_session_id(SessionID0, Peer),
330 !,
331 SessionID = SessionID0,
332 Request = [session(SessionID)|Request0],
333 b_setval(http_session_id, SessionID).
334http_session(Request0, Request, SessionID) :-
335 session_setting(create(auto)),
336 session_setting(path(Path)),
337 memberchk(path(ReqPath), Request0),
338 sub_atom(ReqPath, 0, _, _, Path),
339 !,
340 create_session(Request0, Request, SessionID).
341
342create_session(Request0, Request, SessionID) :-
343 http_gc_sessions,
344 http_session_cookie(SessionID),
345 session_setting(cookie(Cookie)),
346 session_setting(path(Path)),
347 debug(http_session, 'Created session ~q at path=~q', [SessionID, Path]),
348 format('Set-Cookie: ~w=~w; Path=~w; Version=1\r\n',
349 [Cookie, SessionID, Path]),
350 Request = [session(SessionID)|Request0],
351 peer(Request0, Peer),
352 open_session(SessionID, Peer).
353
354
370
371http_open_session(SessionID, Options) :-
372 http_in_session(SessionID0),
373 \+ option(renew(true), Options, false),
374 !,
375 SessionID = SessionID0.
376http_open_session(SessionID, _Options) :-
377 ( in_header_state
378 -> true
379 ; current_output(CGI),
380 permission_error(open, http_session, CGI)
381 ),
382 ( http_in_session(ActiveSession)
383 -> http_close_session(ActiveSession, false)
384 ; true
385 ),
386 http_current_request(Request),
387 create_session(Request, _, SessionID).
388
389
390:- multifile
391 http:request_expansion/2. 392
393http:request_expansion(Request0, Request) :-
394 session_setting(enabled(true)),
395 http_session(Request0, Request, _SessionID).
396
401
402peer(Request, Peer) :-
403 ( session_setting(proxy_enabled(true)),
404 http_peer(Request, Peer)
405 -> true
406 ; memberchk(peer(Peer), Request)
407 -> true
408 ; true
409 ).
410
415
416open_session(SessionID, Peer) :-
417 get_time(Now),
418 assert(current_session(SessionID, Peer)),
419 assert(last_used(SessionID, Now)),
420 b_setval(http_session_id, SessionID),
421 broadcast(http_session(begin(SessionID, Peer))).
422
423
428
429valid_session_id(SessionID, Peer) :-
430 current_session(SessionID, SessionPeer),
431 get_time(Now),
432 ( session_setting(SessionID, timeout(Timeout)),
433 Timeout > 0
434 -> get_last_used(SessionID, Last),
435 Idle is Now - Last,
436 ( Idle =< Timeout
437 -> true
438 ; http_close_session(SessionID),
439 fail
440 )
441 ; Peer \== SessionPeer
442 -> http_close_session(SessionID),
443 fail
444 ; true
445 ),
446 set_last_used(SessionID, Now, Timeout).
447
448get_last_used(SessionID, Last) :-
449 atom(SessionID),
450 !,
451 once(last_used(SessionID, Last)).
452get_last_used(SessionID, Last) :-
453 last_used(SessionID, Last).
454
460
461set_last_used(SessionID, Now, TimeOut) :-
462 LastUsed is floor(Now/10)*10,
463 ( clause(last_used(SessionID, CurrentLast), _, Ref)
464 -> ( CurrentLast == LastUsed
465 -> true
466 ; asserta(last_used(SessionID, LastUsed)),
467 erase(Ref),
468 schedule_gc(LastUsed, TimeOut)
469 )
470 ; asserta(last_used(SessionID, LastUsed)),
471 schedule_gc(LastUsed, TimeOut)
472 ).
473
474
475 478
486
487http_session_asserta(Data) :-
488 http_session_id(SessionId),
489 asserta(session_data(SessionId, Data)).
490
491http_session_assert(Data) :-
492 http_session_id(SessionId),
493 assert(session_data(SessionId, Data)).
494
495http_session_retract(Data) :-
496 http_session_id(SessionId),
497 retract(session_data(SessionId, Data)).
498
499http_session_retractall(Data) :-
500 http_session_id(SessionId),
501 retractall(session_data(SessionId, Data)).
502
509
510http_session_data(Data) :-
511 http_session_id(SessionId),
512 session_data(SessionId, Data).
513
514
515 518
529
530http_current_session(SessionID, Data) :-
531 get_time(Now),
532 get_last_used(SessionID, Last), 533 Idle is Now - Last,
534 ( session_setting(SessionID, timeout(Timeout)),
535 Timeout > 0
536 -> Idle =< Timeout
537 ; true
538 ),
539 ( Data = idle(Idle)
540 ; Data = peer(Peer),
541 current_session(SessionID, Peer)
542 ; session_data(SessionID, Data)
543 ).
544
545
546 549
582
583http_close_session(SessionId) :-
584 http_close_session(SessionId, true).
585
586http_close_session(SessionId, Expire) :-
587 must_be(atom, SessionId),
588 ( current_session(SessionId, Peer),
589 ( b_setval(http_session_id, SessionId),
590 broadcast(http_session(end(SessionId, Peer))),
591 fail
592 ; true
593 ),
594 ( Expire == true
595 -> expire_session_cookie
596 ; true
597 ),
598 retractall(current_session(SessionId, _)),
599 retractall(last_used(SessionId, _)),
600 retractall(session_data(SessionId, _)),
601 fail
602 ; true
603 ).
604
605
610
611expire_session_cookie :-
612 in_header_state,
613 session_setting(cookie(Cookie)),
614 session_setting(path(Path)),
615 !,
616 format('Set-Cookie: ~w=; \c
617 expires=Tue, 01-Jan-1970 00:00:00 GMT; \c
618 path=~w\r\n',
619 [Cookie, Path]).
620expire_session_cookie.
621
:-
623 current_output(CGI),
624 is_cgi_stream(CGI),
625 cgi_property(CGI, state(header)),
626 !.
627
628
634
635:- dynamic
636 last_gc/1. 637
638http_gc_sessions :-
639 start_session_gc_thread,
640 http_gc_sessions(60).
641http_gc_sessions(TimeOut) :-
642 ( with_mutex(http_session_gc, need_sesion_gc(TimeOut))
643 -> do_http_gc_sessions
644 ; true
645 ).
646
647need_sesion_gc(TimeOut) :-
648 get_time(Now),
649 ( last_gc(LastGC),
650 Now-LastGC < TimeOut
651 -> true
652 ; retractall(last_gc(_)),
653 asserta(last_gc(Now)),
654 do_http_gc_sessions
655 ).
656
657do_http_gc_sessions :-
658 debug(http_session(gc), 'Running HTTP session GC', []),
659 get_time(Now),
660 ( last_used(SessionID, Last),
661 session_setting(SessionID, timeout(Timeout)),
662 Timeout > 0,
663 Idle is Now - Last,
664 Idle > Timeout,
665 http_close_session(SessionID, false),
666 fail
667 ; true
668 ).
669
676
677:- dynamic
678 session_gc_queue/1. 679
680start_session_gc_thread :-
681 session_gc_queue(_),
682 !.
683start_session_gc_thread :-
684 session_setting(gc(active)),
685 !,
686 catch(thread_create(session_gc_loop, _,
687 [ alias('__http_session_gc'),
688 at_exit(retractall(session_gc_queue(_)))
689 ]),
690 error(permission_error(create, thread, _),_),
691 true).
692start_session_gc_thread.
693
694stop_session_gc_thread :-
695 retract(session_gc_queue(Id)),
696 !,
697 thread_send_message(Id, done),
698 thread_join(Id, _).
699stop_session_gc_thread.
700
701session_gc_loop :-
702 thread_self(GcQueue),
703 asserta(session_gc_queue(GcQueue)),
704 repeat,
705 thread_get_message(Message),
706 ( Message == done
707 -> !
708 ; schedule(Message),
709 fail
710 ).
711
712schedule(at(Time)) :-
713 current_alarm(At, _, _, _),
714 Time == At,
715 !.
716schedule(at(Time)) :-
717 debug(http_session(gc), 'Schedule GC at ~p', [Time]),
718 alarm_at(Time, http_gc_sessions(10), _,
719 [ remove(true)
720 ]).
721
722schedule_gc(LastUsed, TimeOut) :-
723 nonvar(TimeOut), 724 session_gc_queue(Queue),
725 !,
726 At is LastUsed+TimeOut+5, 727 thread_send_message(Queue, at(At)).
728schedule_gc(_, _).
729
730
731 734
742
743http_session_cookie(Cookie) :-
744 route(Route),
745 !,
746 random_4(R1,R2,R3,R4),
747 format(atom(Cookie),
748 '~`0t~16r~4|-~`0t~16r~9|-~`0t~16r~14|-~`0t~16r~19|.~w',
749 [R1,R2,R3,R4,Route]).
750http_session_cookie(Cookie) :-
751 random_4(R1,R2,R3,R4),
752 format(atom(Cookie),
753 '~`0t~16r~4|-~`0t~16r~9|-~`0t~16r~14|-~`0t~16r~19|',
754 [R1,R2,R3,R4]).
755
756:- thread_local
757 route_cache/1. 758
766
767route(Route) :-
768 route_cache(Route),
769 !,
770 Route \== ''.
771route(Route) :-
772 route_no_cache(Route),
773 assert(route_cache(Route)),
774 Route \== ''.
775
776route_no_cache(Route) :-
777 session_setting(route(Route)),
778 !.
779route_no_cache(Route) :-
780 gethostname(Host),
781 ( sub_atom(Host, Before, _, _, '.')
782 -> sub_atom(Host, 0, Before, _, Route)
783 ; Route = Host
784 ).
785
786:- if(\+current_prolog_flag(windows, true)). 794
795:- dynamic
796 urandom_handle/1. 797
798urandom(Handle) :-
799 urandom_handle(Handle),
800 !,
801 Handle \== [].
802urandom(Handle) :-
803 catch(open('/dev/urandom', read, In, [type(binary)]), _, fail),
804 !,
805 assert(urandom_handle(In)),
806 Handle = In.
807urandom(_) :-
808 assert(urandom_handle([])),
809 fail.
810
811get_pair(In, Value) :-
812 get_byte(In, B1),
813 get_byte(In, B2),
814 Value is B1<<8+B2.
815:- endif. 816
821
822:- if(current_predicate(urandom/1)). 823random_4(R1,R2,R3,R4) :-
824 urandom(In),
825 !,
826 get_pair(In, R1),
827 get_pair(In, R2),
828 get_pair(In, R3),
829 get_pair(In, R4).
830:- endif. 831random_4(R1,R2,R3,R4) :-
832 R1 is random(65536),
833 R2 is random(65536),
834 R3 is random(65536),
835 R4 is random(65536)