37
38:- module(http_session,
39 [ http_set_session_options/1, 40 http_set_session/1, 41 http_set_session/2, 42 http_session_option/1, 43
44 http_session_id/1, 45 http_in_session/1, 46 http_current_session/2, 47 http_close_session/1, 48 http_open_session/2, 49
50 http_session_cookie/1, 51
52 http_session_asserta/1, 53 http_session_assert/1, 54 http_session_retract/1, 55 http_session_retractall/1, 56 http_session_data/1, 57
58 http_session_asserta/2, 59 http_session_assert/2, 60 http_session_retract/2, 61 http_session_retractall/2, 62 http_session_data/2 63 ]). 64:- use_module(http_wrapper). 65:- use_module(http_stream). 66:- use_module(library(error)). 67:- use_module(library(debug)). 68:- use_module(library(socket)). 69:- use_module(library(broadcast)). 70:- use_module(library(lists)). 71:- use_module(library(time)). 72:- use_module(library(option)). 73
74:- predicate_options(http_open_session/2, 2, [renew(boolean)]). 75
111
112:- dynamic
113 session_setting/1, 114 current_session/2, 115 last_used/2, 116 session_data/2. 117
118:- multifile
119 hooked/0,
120 hook/1, 121 session_option/2. 122
123session_setting(timeout(600)). 124session_setting(cookie('swipl_session')).
125session_setting(path(/)).
126session_setting(enabled(true)).
127session_setting(create(auto)).
128session_setting(proxy_enabled(false)).
129session_setting(gc(passive)).
130session_setting(samesite(lax)).
131
132session_option(timeout, integer).
133session_option(cookie, atom).
134session_option(path, atom).
135session_option(create, oneof([auto,noauto])).
136session_option(route, atom).
137session_option(enabled, boolean).
138session_option(proxy_enabled, boolean).
139session_option(gc, oneof([active,passive])).
140session_option(samesite, oneof([none,lax,strict])).
141
210
211http_set_session_options([]) => true.
212http_set_session_options([H|T]) =>
213 http_set_session_option(H),
214 http_set_session_options(T).
215
216http_set_session_option(Option), Option =.. [Name,Value] =>
217 ( session_option(Name, Type)
218 -> must_be(Type, Value)
219 ; domain_error(http_session_option, Option)
220 ),
221 functor(Free, Name, 1),
222 ( clause(session_setting(Free), _, Ref)
223 -> ( Free \== Value
224 -> asserta(session_setting(Option)),
225 erase(Ref),
226 updated_session_setting(Name, Free, Value)
227 ; true
228 )
229 ; asserta(session_setting(Option))
230 ).
231
235
236http_session_option(Option) :-
237 session_setting(Option).
238
243
244:- public session_setting/2. 245
246session_setting(SessionID, Setting) :-
247 nonvar(Setting),
248 get_session_option(SessionID, Setting),
249 !.
250session_setting(_, Setting) :-
251 session_setting(Setting).
252
253get_session_option(SessionID, Setting) :-
254 hooked,
255 !,
256 hook(get_session_option(SessionID, Setting)).
257get_session_option(SessionID, Setting) :-
258 functor(Setting, Name, 1),
259 local_option(Name, Value, Term),
260 session_data(SessionID, '$setting'(Term)),
261 !,
262 arg(1, Setting, Value).
263
264
265updated_session_setting(gc, _, passive) :-
266 stop_session_gc_thread, !.
267updated_session_setting(_, _, _). 268
269
278
279http_set_session(Setting) :-
280 http_session_id(SessionId),
281 http_set_session(SessionId, Setting).
282
283http_set_session(SessionId, Setting) :-
284 functor(Setting, Name, _),
285 ( local_option(Name, _, _)
286 -> true
287 ; permission_error(set, http_session, Setting)
288 ),
289 arg(1, Setting, Value),
290 ( session_option(Name, Type)
291 -> must_be(Type, Value)
292 ; domain_error(http_session_option, Setting)
293 ),
294 set_session_option(SessionId, Setting).
295
296set_session_option(SessionId, Setting) :-
297 hooked,
298 !,
299 hook(set_session_option(SessionId, Setting)).
300set_session_option(SessionId, Setting) :-
301 functor(Setting, Name, Arity),
302 functor(Free, Name, Arity),
303 retractall(session_data(SessionId, '$setting'(Free))),
304 assert(session_data(SessionId, '$setting'(Setting))).
305
306local_option(timeout, X, timeout(X)).
307
316
317http_session_id(SessionID) :-
318 ( http_in_session(ID)
319 -> SessionID = ID
320 ; throw(error(existence_error(http_session, _), _))
321 ).
322
336
337http_in_session(SessionID) :-
338 nb_current(http_session_id, ID),
339 ID \== [],
340 !,
341 debug(http_session, 'Session id from global variable: ~q', [ID]),
342 ID \== no_session,
343 SessionID = ID.
344http_in_session(SessionID) :-
345 http_current_request(Request),
346 http_in_session(Request, SessionID).
347
348http_in_session(Request, SessionID) :-
349 memberchk(session(ID), Request),
350 !,
351 debug(http_session, 'Session id from request: ~q', [ID]),
352 b_setval(http_session_id, ID),
353 SessionID = ID.
354http_in_session(Request, SessionID) :-
355 memberchk(cookie(Cookies), Request),
356 session_setting(cookie(Cookie)),
357 member(Cookie=SessionID0, Cookies),
358 debug(http_session, 'Session id from cookie: ~q', [SessionID0]),
359 peer(Request, Peer),
360 valid_session_id(SessionID0, Peer),
361 !,
362 b_setval(http_session_id, SessionID0),
363 SessionID = SessionID0.
364
365
376
377http_session(Request, Request, SessionID) :-
378 memberchk(session(SessionID0), Request),
379 !,
380 SessionID = SessionID0.
381http_session(Request0, Request, SessionID) :-
382 memberchk(cookie(Cookies), Request0),
383 session_setting(cookie(Cookie)),
384 member(Cookie=SessionID0, Cookies),
385 peer(Request0, Peer),
386 valid_session_id(SessionID0, Peer),
387 !,
388 SessionID = SessionID0,
389 Request = [session(SessionID)|Request0],
390 b_setval(http_session_id, SessionID).
391http_session(Request0, Request, SessionID) :-
392 session_setting(create(auto)),
393 session_setting(path(Path)),
394 memberchk(path(ReqPath), Request0),
395 sub_atom(ReqPath, 0, _, _, Path),
396 !,
397 create_session(Request0, Request, SessionID).
398
399create_session(Request0, Request, SessionID) :-
400 http_gc_sessions,
401 http_session_cookie(SessionID),
402 session_setting(cookie(Cookie)),
403 session_setting(path(Path)),
404 session_setting(samesite(SameSite)),
405 debug(http_session, 'Created session ~q at path=~q', [SessionID, Path]),
406 ( SameSite == none
407 -> format('Set-Cookie: ~w=~w; Path=~w; Version=1\r\n',
408 [Cookie, SessionID, Path])
409 ; format('Set-Cookie: ~w=~w; Path=~w; Version=1; SameSite=~w\r\n',
410 [Cookie, SessionID, Path, SameSite])
411 ),
412 Request = [session(SessionID)|Request0],
413 peer(Request0, Peer),
414 open_session(SessionID, Peer).
415
416
432
433http_open_session(SessionID, Options) :-
434 http_in_session(SessionID0),
435 \+ option(renew(true), Options, false),
436 !,
437 SessionID = SessionID0.
438http_open_session(SessionID, _Options) :-
439 ( in_header_state
440 -> true
441 ; current_output(CGI),
442 permission_error(open, http_session, CGI)
443 ),
444 ( http_in_session(ActiveSession)
445 -> http_close_session(ActiveSession, false)
446 ; true
447 ),
448 http_current_request(Request),
449 create_session(Request, _, SessionID).
450
451
452:- multifile
453 http:request_expansion/2. 454
455http:request_expansion(Request0, Request) :-
456 session_setting(enabled(true)),
457 http_session(Request0, Request, _SessionID).
458
463
464peer(Request, Peer) :-
465 ( session_setting(proxy_enabled(true)),
466 http_peer(Request, Peer)
467 -> true
468 ; memberchk(peer(Peer), Request)
469 -> true
470 ; true
471 ).
472
477
478open_session(SessionID, Peer) :-
479 assert_session(SessionID, Peer),
480 b_setval(http_session_id, SessionID),
481 broadcast(http_session(begin(SessionID, Peer))).
482
483assert_session(SessionID, Peer) :-
484 hooked,
485 !,
486 hook(assert_session(SessionID, Peer)).
487assert_session(SessionID, Peer) :-
488 get_time(Now),
489 assert(current_session(SessionID, Peer)),
490 assert(last_used(SessionID, Now)).
491
496
497valid_session_id(SessionID, Peer) :-
498 active_session(SessionID, SessionPeer, LastUsed),
499 get_time(Now),
500 ( session_setting(SessionID, timeout(Timeout)),
501 Timeout > 0
502 -> Idle is Now - LastUsed,
503 ( Idle =< Timeout
504 -> true
505 ; http_close_session(SessionID),
506 fail
507 )
508 ; Peer \== SessionPeer
509 -> http_close_session(SessionID),
510 fail
511 ; true
512 ),
513 set_last_used(SessionID, Now, Timeout).
514
515active_session(SessionID, Peer, LastUsed) :-
516 hooked,
517 !,
518 hook(active_session(SessionID, Peer, LastUsed)).
519active_session(SessionID, Peer, LastUsed) :-
520 current_session(SessionID, Peer),
521 get_last_used(SessionID, LastUsed).
522
523get_last_used(SessionID, Last) :-
524 atom(SessionID),
525 !,
526 once(last_used(SessionID, Last)).
527get_last_used(SessionID, Last) :-
528 last_used(SessionID, Last).
529
535
536set_last_used(SessionID, Now, TimeOut) :-
537 hooked,
538 !,
539 hook(set_last_used(SessionID, Now, TimeOut)).
540set_last_used(SessionID, Now, TimeOut) :-
541 LastUsed is floor(Now/10)*10,
542 ( clause(last_used(SessionID, CurrentLast), _, Ref)
543 -> ( CurrentLast == LastUsed
544 -> true
545 ; asserta(last_used(SessionID, LastUsed)),
546 erase(Ref),
547 schedule_gc(LastUsed, TimeOut)
548 )
549 ; asserta(last_used(SessionID, LastUsed)),
550 schedule_gc(LastUsed, TimeOut)
551 ).
552
553
554 557
565
566http_session_asserta(Data) :-
567 http_session_id(SessionId),
568 ( hooked
569 -> hook(asserta(session_data(SessionId, Data)))
570 ; asserta(session_data(SessionId, Data))
571 ).
572
573http_session_assert(Data) :-
574 http_session_id(SessionId),
575 ( hooked
576 -> hook(assertz(session_data(SessionId, Data)))
577 ; assertz(session_data(SessionId, Data))
578 ).
579
580http_session_retract(Data) :-
581 http_session_id(SessionId),
582 ( hooked
583 -> hook(retract(session_data(SessionId, Data)))
584 ; retract(session_data(SessionId, Data))
585 ).
586
587http_session_retractall(Data) :-
588 http_session_id(SessionId),
589 ( hooked
590 -> hook(retractall(session_data(SessionId, Data)))
591 ; retractall(session_data(SessionId, Data))
592 ).
593
600
601http_session_data(Data) :-
602 http_session_id(SessionId),
603 ( hooked
604 -> hook(session_data(SessionId, Data))
605 ; session_data(SessionId, Data)
606 ).
607
618
619http_session_asserta(Data, SessionId) :-
620 must_be(atom, SessionId),
621 ( hooked
622 -> hook(asserta(session_data(SessionId, Data)))
623 ; asserta(session_data(SessionId, Data))
624 ).
625
626http_session_assert(Data, SessionId) :-
627 must_be(atom, SessionId),
628 ( hooked
629 -> hook(assertz(session_data(SessionId, Data)))
630 ; assertz(session_data(SessionId, Data))
631 ).
632
633http_session_retract(Data, SessionId) :-
634 must_be(atom, SessionId),
635 ( hooked
636 -> hook(retract(session_data(SessionId, Data)))
637 ; retract(session_data(SessionId, Data))
638 ).
639
640http_session_retractall(Data, SessionId) :-
641 must_be(atom, SessionId),
642 ( hooked
643 -> hook(retractall(session_data(SessionId, Data)))
644 ; retractall(session_data(SessionId, Data))
645 ).
646
647http_session_data(Data, SessionId) :-
648 must_be(atom, SessionId),
649 ( hooked
650 -> hook(session_data(SessionId, Data))
651 ; session_data(SessionId, Data)
652 ).
653
654
655 658
669
670http_current_session(SessionID, Data) :-
671 hooked,
672 !,
673 hook(current_session(SessionID, Data)).
674http_current_session(SessionID, Data) :-
675 get_time(Now),
676 get_last_used(SessionID, Last), 677 Idle is Now - Last,
678 ( session_setting(SessionID, timeout(Timeout)),
679 Timeout > 0
680 -> Idle =< Timeout
681 ; true
682 ),
683 ( Data = idle(Idle)
684 ; Data = peer(Peer),
685 current_session(SessionID, Peer)
686 ; session_data(SessionID, Data)
687 ).
688
689
690 693
726
727http_close_session(SessionId) :-
728 http_close_session(SessionId, true).
729
730http_close_session(SessionId, Expire) :-
731 hooked,
732 !,
733 forall(hook(close_session(SessionId)),
734 expire_session_cookie(Expire)).
735http_close_session(SessionId, Expire) :-
736 must_be(atom, SessionId),
737 ( current_session(SessionId, Peer),
738 ( b_setval(http_session_id, SessionId),
739 broadcast(http_session(end(SessionId, Peer))),
740 fail
741 ; true
742 ),
743 expire_session_cookie(Expire),
744 retractall(current_session(SessionId, _)),
745 retractall(last_used(SessionId, _)),
746 retractall(session_data(SessionId, _)),
747 fail
748 ; true
749 ).
750
751
756
757expire_session_cookie(true) :-
758 !,
759 expire_session_cookie.
760expire_session_cookie(_).
761
762expire_session_cookie :-
763 in_header_state,
764 session_setting(cookie(Cookie)),
765 session_setting(path(Path)),
766 !,
767 format('Set-Cookie: ~w=; \c
768 expires=Tue, 01-Jan-1970 00:00:00 GMT; \c
769 path=~w\r\n',
770 [Cookie, Path]).
771expire_session_cookie.
772
:-
774 current_output(CGI),
775 is_cgi_stream(CGI),
776 cgi_property(CGI, state(header)),
777 !.
778
779
785
786:- dynamic
787 last_gc/1. 788
789http_gc_sessions :-
790 start_session_gc_thread,
791 http_gc_sessions(60).
792http_gc_sessions(TimeOut) :-
793 ( with_mutex(http_session_gc, need_sesion_gc(TimeOut))
794 -> do_http_gc_sessions
795 ; true
796 ).
797
798need_sesion_gc(TimeOut) :-
799 get_time(Now),
800 ( last_gc(LastGC),
801 Now-LastGC < TimeOut
802 -> true
803 ; retractall(last_gc(_)),
804 asserta(last_gc(Now)),
805 do_http_gc_sessions
806 ).
807
808do_http_gc_sessions :-
809 hooked,
810 !,
811 hook(gc_sessions).
812do_http_gc_sessions :-
813 debug(http_session(gc), 'Running HTTP session GC', []),
814 get_time(Now),
815 ( session_setting(SessionID, timeout(Timeout)),
816 last_used(SessionID, Last),
817 Timeout > 0,
818 Idle is Now - Last,
819 Idle > Timeout,
820 http_close_session(SessionID, false),
821 fail
822 ; true
823 ).
824
831
832:- dynamic
833 session_gc_queue/1. 834
835start_session_gc_thread :-
836 session_gc_queue(_),
837 !.
838start_session_gc_thread :-
839 session_setting(gc(active)),
840 !,
841 catch(thread_create(session_gc_loop, _,
842 [ alias('__http_session_gc'),
843 at_exit(retractall(session_gc_queue(_)))
844 ]),
845 error(permission_error(create, thread, _),_),
846 true).
847start_session_gc_thread.
848
849stop_session_gc_thread :-
850 retract(session_gc_queue(Id)),
851 !,
852 thread_send_message(Id, done),
853 thread_join(Id, _).
854stop_session_gc_thread.
855
856session_gc_loop :-
857 thread_self(GcQueue),
858 asserta(session_gc_queue(GcQueue)),
859 repeat,
860 thread_get_message(Message),
861 ( Message == done
862 -> !
863 ; schedule(Message),
864 fail
865 ).
866
867schedule(at(Time)) :-
868 current_alarm(At, _, _, _),
869 Time == At,
870 !.
871schedule(at(Time)) :-
872 debug(http_session(gc), 'Schedule GC at ~p', [Time]),
873 alarm_at(Time, http_gc_sessions(10), _,
874 [ remove(true)
875 ]).
876
877schedule_gc(LastUsed, TimeOut) :-
878 nonvar(TimeOut), 879 session_gc_queue(Queue),
880 !,
881 At is LastUsed+TimeOut+5, 882 thread_send_message(Queue, at(At)).
883schedule_gc(_, _).
884
885
886 889
897
898http_session_cookie(Cookie) :-
899 route(Route),
900 !,
901 random_4(R1,R2,R3,R4),
902 format(atom(Cookie),
903 '~`0t~16r~4|-~`0t~16r~9|-~`0t~16r~14|-~`0t~16r~19|.~w',
904 [R1,R2,R3,R4,Route]).
905http_session_cookie(Cookie) :-
906 random_4(R1,R2,R3,R4),
907 format(atom(Cookie),
908 '~`0t~16r~4|-~`0t~16r~9|-~`0t~16r~14|-~`0t~16r~19|',
909 [R1,R2,R3,R4]).
910
911:- thread_local
912 route_cache/1. 913
921
922route(Route) :-
923 route_cache(Route),
924 !,
925 Route \== ''.
926route(Route) :-
927 route_no_cache(Route),
928 assert(route_cache(Route)),
929 Route \== ''.
930
931route_no_cache(Route) :-
932 session_setting(route(Route)),
933 !.
934route_no_cache(Route) :-
935 gethostname(Host),
936 ( sub_atom(Host, Before, _, _, '.')
937 -> sub_atom(Host, 0, Before, _, Route)
938 ; Route = Host
939 ).
940
941:- if(\+current_prolog_flag(windows, true)). 949
950:- dynamic
951 urandom_handle/1. 952
953urandom(Handle) :-
954 urandom_handle(Handle),
955 !,
956 Handle \== [].
957urandom(Handle) :-
958 catch(open('/dev/urandom', read, In, [type(binary)]), _, fail),
959 !,
960 assert(urandom_handle(In)),
961 Handle = In.
962urandom(_) :-
963 assert(urandom_handle([])),
964 fail.
965
966get_pair(In, Value) :-
967 get_byte(In, B1),
968 get_byte(In, B2),
969 Value is B1<<8+B2.
970:- endif. 971
976
977:- if(current_predicate(urandom/1)). 978random_4(R1,R2,R3,R4) :-
979 urandom(In),
980 !,
981 get_pair(In, R1),
982 get_pair(In, R2),
983 get_pair(In, R3),
984 get_pair(In, R4).
985:- endif. 986random_4(R1,R2,R3,R4) :-
987 R1 is random(65536),
988 R2 is random(65536),
989 R3 is random(65536),
990 R4 is random(65536).
991