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