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 http_session_asserta/2, 57 http_session_assert/2, 58 http_session_retract/2, 59 http_session_retractall/2, 60 http_session_data/2 61 ]). 62:- use_module(http_wrapper). 63:- use_module(http_stream). 64:- use_module(library(error)). 65:- use_module(library(debug)). 66:- use_module(library(socket)). 67:- use_module(library(broadcast)). 68:- use_module(library(lists)). 69:- use_module(library(time)). 70
71:- predicate_options(http_open_session/2, 2, [renew(boolean)]). 72
108
109:- dynamic
110 session_setting/1, 111 current_session/2, 112 last_used/2, 113 session_data/2. 114
115session_setting(timeout(600)). 116session_setting(cookie('swipl_session')).
117session_setting(path(/)).
118session_setting(enabled(true)).
119session_setting(create(auto)).
120session_setting(proxy_enabled(false)).
121session_setting(gc(passive)).
122
123session_option(timeout, integer).
124session_option(cookie, atom).
125session_option(path, atom).
126session_option(create, oneof([auto,noauto])).
127session_option(route, atom).
128session_option(enabled, boolean).
129session_option(proxy_enabled, boolean).
130session_option(gc, oneof([active,passive])).
131
176
177http_set_session_options([]).
178http_set_session_options([H|T]) :-
179 http_set_session_option(H),
180 http_set_session_options(T).
181
182http_set_session_option(Option) :-
183 functor(Option, Name, Arity),
184 arg(1, Option, Value),
185 ( session_option(Name, Type)
186 -> must_be(Type, Value)
187 ; domain_error(http_session_option, Option)
188 ),
189 functor(Free, Name, Arity),
190 ( clause(session_setting(Free), _, Ref)
191 -> ( Free \== Value
192 -> asserta(session_setting(Option)),
193 erase(Ref),
194 updated_session_setting(Name, Free, Value)
195 ; true
196 )
197 ; asserta(session_setting(Option))
198 ).
199
203
204http_session_option(Option) :-
205 session_setting(Option).
206
211
212session_setting(SessionId, Setting) :-
213 nonvar(Setting),
214 functor(Setting, Name, 1),
215 local_option(Name, Value, Term),
216 session_data(SessionId, '$setting'(Term)),
217 !,
218 arg(1, Setting, Value).
219session_setting(_, Setting) :-
220 session_setting(Setting).
221
222updated_session_setting(gc, _, passive) :-
223 stop_session_gc_thread, !.
224updated_session_setting(_, _, _). 225
226
235
236http_set_session(Setting) :-
237 http_session_id(SessionId),
238 http_set_session(SessionId, Setting).
239
240http_set_session(SessionId, Setting) :-
241 functor(Setting, Name, Arity),
242 ( local_option(Name, _, _)
243 -> true
244 ; permission_error(set, http_session, Setting)
245 ),
246 arg(1, Setting, Value),
247 ( session_option(Name, Type)
248 -> must_be(Type, Value)
249 ; domain_error(http_session_option, Setting)
250 ),
251 functor(Free, Name, Arity),
252 retractall(session_data(SessionId, '$setting'(Free))),
253 assert(session_data(SessionId, '$setting'(Setting))).
254
255local_option(timeout, X, timeout(X)).
256
265
266http_session_id(SessionID) :-
267 ( http_in_session(ID)
268 -> SessionID = ID
269 ; throw(error(existence_error(http_session, _), _))
270 ).
271
285
286http_in_session(SessionID) :-
287 nb_current(http_session_id, ID),
288 ID \== [],
289 !,
290 debug(http_session, 'Session id from global variable: ~q', [ID]),
291 ID \== no_session,
292 SessionID = ID.
293http_in_session(SessionID) :-
294 http_current_request(Request),
295 http_in_session(Request, SessionID).
296
297http_in_session(Request, SessionID) :-
298 memberchk(session(ID), Request),
299 !,
300 debug(http_session, 'Session id from request: ~q', [ID]),
301 b_setval(http_session_id, ID),
302 SessionID = ID.
303http_in_session(Request, SessionID) :-
304 memberchk(cookie(Cookies), Request),
305 session_setting(cookie(Cookie)),
306 member(Cookie=SessionID0, Cookies),
307 debug(http_session, 'Session id from cookie: ~q', [SessionID0]),
308 peer(Request, Peer),
309 valid_session_id(SessionID0, Peer),
310 !,
311 b_setval(http_session_id, SessionID0),
312 SessionID = SessionID0.
313
314
325
326http_session(Request, Request, SessionID) :-
327 memberchk(session(SessionID0), Request),
328 !,
329 SessionID = SessionID0.
330http_session(Request0, Request, SessionID) :-
331 memberchk(cookie(Cookies), Request0),
332 session_setting(cookie(Cookie)),
333 member(Cookie=SessionID0, Cookies),
334 peer(Request0, Peer),
335 valid_session_id(SessionID0, Peer),
336 !,
337 SessionID = SessionID0,
338 Request = [session(SessionID)|Request0],
339 b_setval(http_session_id, SessionID).
340http_session(Request0, Request, SessionID) :-
341 session_setting(create(auto)),
342 session_setting(path(Path)),
343 memberchk(path(ReqPath), Request0),
344 sub_atom(ReqPath, 0, _, _, Path),
345 !,
346 create_session(Request0, Request, SessionID).
347
348create_session(Request0, Request, SessionID) :-
349 http_gc_sessions,
350 http_session_cookie(SessionID),
351 session_setting(cookie(Cookie)),
352 session_setting(path(Path)),
353 debug(http_session, 'Created session ~q at path=~q', [SessionID, Path]),
354 format('Set-Cookie: ~w=~w; Path=~w; Version=1\r\n',
355 [Cookie, SessionID, Path]),
356 Request = [session(SessionID)|Request0],
357 peer(Request0, Peer),
358 open_session(SessionID, Peer).
359
360
376
377http_open_session(SessionID, Options) :-
378 http_in_session(SessionID0),
379 \+ option(renew(true), Options, false),
380 !,
381 SessionID = SessionID0.
382http_open_session(SessionID, _Options) :-
383 ( in_header_state
384 -> true
385 ; current_output(CGI),
386 permission_error(open, http_session, CGI)
387 ),
388 ( http_in_session(ActiveSession)
389 -> http_close_session(ActiveSession, false)
390 ; true
391 ),
392 http_current_request(Request),
393 create_session(Request, _, SessionID).
394
395
396:- multifile
397 http:request_expansion/2. 398
399http:request_expansion(Request0, Request) :-
400 session_setting(enabled(true)),
401 http_session(Request0, Request, _SessionID).
402
407
408peer(Request, Peer) :-
409 ( session_setting(proxy_enabled(true)),
410 http_peer(Request, Peer)
411 -> true
412 ; memberchk(peer(Peer), Request)
413 -> true
414 ; true
415 ).
416
421
422open_session(SessionID, Peer) :-
423 get_time(Now),
424 assert(current_session(SessionID, Peer)),
425 assert(last_used(SessionID, Now)),
426 b_setval(http_session_id, SessionID),
427 broadcast(http_session(begin(SessionID, Peer))).
428
429
434
435valid_session_id(SessionID, Peer) :-
436 current_session(SessionID, SessionPeer),
437 get_time(Now),
438 ( session_setting(SessionID, timeout(Timeout)),
439 Timeout > 0
440 -> get_last_used(SessionID, Last),
441 Idle is Now - Last,
442 ( Idle =< Timeout
443 -> true
444 ; http_close_session(SessionID),
445 fail
446 )
447 ; Peer \== SessionPeer
448 -> http_close_session(SessionID),
449 fail
450 ; true
451 ),
452 set_last_used(SessionID, Now, Timeout).
453
454get_last_used(SessionID, Last) :-
455 atom(SessionID),
456 !,
457 once(last_used(SessionID, Last)).
458get_last_used(SessionID, Last) :-
459 last_used(SessionID, Last).
460
466
467set_last_used(SessionID, Now, TimeOut) :-
468 LastUsed is floor(Now/10)*10,
469 ( clause(last_used(SessionID, CurrentLast), _, Ref)
470 -> ( CurrentLast == LastUsed
471 -> true
472 ; asserta(last_used(SessionID, LastUsed)),
473 erase(Ref),
474 schedule_gc(LastUsed, TimeOut)
475 )
476 ; asserta(last_used(SessionID, LastUsed)),
477 schedule_gc(LastUsed, TimeOut)
478 ).
479
480
481 484
492
493http_session_asserta(Data) :-
494 http_session_id(SessionId),
495 asserta(session_data(SessionId, Data)).
496
497http_session_assert(Data) :-
498 http_session_id(SessionId),
499 assert(session_data(SessionId, Data)).
500
501http_session_retract(Data) :-
502 http_session_id(SessionId),
503 retract(session_data(SessionId, Data)).
504
505http_session_retractall(Data) :-
506 http_session_id(SessionId),
507 retractall(session_data(SessionId, Data)).
508
515
516http_session_data(Data) :-
517 http_session_id(SessionId),
518 session_data(SessionId, Data).
519
530
531http_session_asserta(Data, SessionId) :-
532 must_be(atom, SessionId),
533 asserta(session_data(SessionId, Data)).
534
535http_session_assert(Data, SessionId) :-
536 must_be(atom, SessionId),
537 assert(session_data(SessionId, Data)).
538
539http_session_retract(Data, SessionId) :-
540 must_be(atom, SessionId),
541 retract(session_data(SessionId, Data)).
542
543http_session_retractall(Data, SessionId) :-
544 must_be(atom, SessionId),
545 retractall(session_data(SessionId, Data)).
546
547http_session_data(Data, SessionId) :-
548 must_be(atom, SessionId),
549 session_data(SessionId, Data).
550
551
552
553 556
567
568http_current_session(SessionID, Data) :-
569 get_time(Now),
570 get_last_used(SessionID, Last), 571 Idle is Now - Last,
572 ( session_setting(SessionID, timeout(Timeout)),
573 Timeout > 0
574 -> Idle =< Timeout
575 ; true
576 ),
577 ( Data = idle(Idle)
578 ; Data = peer(Peer),
579 current_session(SessionID, Peer)
580 ; session_data(SessionID, Data)
581 ).
582
583
584 587
620
621http_close_session(SessionId) :-
622 http_close_session(SessionId, true).
623
624http_close_session(SessionId, Expire) :-
625 must_be(atom, SessionId),
626 ( current_session(SessionId, Peer),
627 ( b_setval(http_session_id, SessionId),
628 broadcast(http_session(end(SessionId, Peer))),
629 fail
630 ; true
631 ),
632 ( Expire == true
633 -> expire_session_cookie
634 ; true
635 ),
636 retractall(current_session(SessionId, _)),
637 retractall(last_used(SessionId, _)),
638 retractall(session_data(SessionId, _)),
639 fail
640 ; true
641 ).
642
643
648
649expire_session_cookie :-
650 in_header_state,
651 session_setting(cookie(Cookie)),
652 session_setting(path(Path)),
653 !,
654 format('Set-Cookie: ~w=; \c
655 expires=Tue, 01-Jan-1970 00:00:00 GMT; \c
656 path=~w\r\n',
657 [Cookie, Path]).
658expire_session_cookie.
659
:-
661 current_output(CGI),
662 is_cgi_stream(CGI),
663 cgi_property(CGI, state(header)),
664 !.
665
666
672
673:- dynamic
674 last_gc/1. 675
676http_gc_sessions :-
677 start_session_gc_thread,
678 http_gc_sessions(60).
679http_gc_sessions(TimeOut) :-
680 ( with_mutex(http_session_gc, need_sesion_gc(TimeOut))
681 -> do_http_gc_sessions
682 ; true
683 ).
684
685need_sesion_gc(TimeOut) :-
686 get_time(Now),
687 ( last_gc(LastGC),
688 Now-LastGC < TimeOut
689 -> true
690 ; retractall(last_gc(_)),
691 asserta(last_gc(Now)),
692 do_http_gc_sessions
693 ).
694
695do_http_gc_sessions :-
696 debug(http_session(gc), 'Running HTTP session GC', []),
697 get_time(Now),
698 ( last_used(SessionID, Last),
699 session_setting(SessionID, timeout(Timeout)),
700 Timeout > 0,
701 Idle is Now - Last,
702 Idle > Timeout,
703 http_close_session(SessionID, false),
704 fail
705 ; true
706 ).
707
714
715:- dynamic
716 session_gc_queue/1. 717
718start_session_gc_thread :-
719 session_gc_queue(_),
720 !.
721start_session_gc_thread :-
722 session_setting(gc(active)),
723 !,
724 catch(thread_create(session_gc_loop, _,
725 [ alias('__http_session_gc'),
726 at_exit(retractall(session_gc_queue(_)))
727 ]),
728 error(permission_error(create, thread, _),_),
729 true).
730start_session_gc_thread.
731
732stop_session_gc_thread :-
733 retract(session_gc_queue(Id)),
734 !,
735 thread_send_message(Id, done),
736 thread_join(Id, _).
737stop_session_gc_thread.
738
739session_gc_loop :-
740 thread_self(GcQueue),
741 asserta(session_gc_queue(GcQueue)),
742 repeat,
743 thread_get_message(Message),
744 ( Message == done
745 -> !
746 ; schedule(Message),
747 fail
748 ).
749
750schedule(at(Time)) :-
751 current_alarm(At, _, _, _),
752 Time == At,
753 !.
754schedule(at(Time)) :-
755 debug(http_session(gc), 'Schedule GC at ~p', [Time]),
756 alarm_at(Time, http_gc_sessions(10), _,
757 [ remove(true)
758 ]).
759
760schedule_gc(LastUsed, TimeOut) :-
761 nonvar(TimeOut), 762 session_gc_queue(Queue),
763 !,
764 At is LastUsed+TimeOut+5, 765 thread_send_message(Queue, at(At)).
766schedule_gc(_, _).
767
768
769 772
780
781http_session_cookie(Cookie) :-
782 route(Route),
783 !,
784 random_4(R1,R2,R3,R4),
785 format(atom(Cookie),
786 '~`0t~16r~4|-~`0t~16r~9|-~`0t~16r~14|-~`0t~16r~19|.~w',
787 [R1,R2,R3,R4,Route]).
788http_session_cookie(Cookie) :-
789 random_4(R1,R2,R3,R4),
790 format(atom(Cookie),
791 '~`0t~16r~4|-~`0t~16r~9|-~`0t~16r~14|-~`0t~16r~19|',
792 [R1,R2,R3,R4]).
793
794:- thread_local
795 route_cache/1. 796
804
805route(Route) :-
806 route_cache(Route),
807 !,
808 Route \== ''.
809route(Route) :-
810 route_no_cache(Route),
811 assert(route_cache(Route)),
812 Route \== ''.
813
814route_no_cache(Route) :-
815 session_setting(route(Route)),
816 !.
817route_no_cache(Route) :-
818 gethostname(Host),
819 ( sub_atom(Host, Before, _, _, '.')
820 -> sub_atom(Host, 0, Before, _, Route)
821 ; Route = Host
822 ).
823
824:- if(\+current_prolog_flag(windows, true)). 832
833:- dynamic
834 urandom_handle/1. 835
836urandom(Handle) :-
837 urandom_handle(Handle),
838 !,
839 Handle \== [].
840urandom(Handle) :-
841 catch(open('/dev/urandom', read, In, [type(binary)]), _, fail),
842 !,
843 assert(urandom_handle(In)),
844 Handle = In.
845urandom(_) :-
846 assert(urandom_handle([])),
847 fail.
848
849get_pair(In, Value) :-
850 get_byte(In, B1),
851 get_byte(In, B2),
852 Value is B1<<8+B2.
853:- endif. 854
859
860:- if(current_predicate(urandom/1)). 861random_4(R1,R2,R3,R4) :-
862 urandom(In),
863 !,
864 get_pair(In, R1),
865 get_pair(In, R2),
866 get_pair(In, R3),
867 get_pair(In, R4).
868:- endif. 869random_4(R1,R2,R3,R4) :-
870 R1 is random(65536),
871 R2 is random(65536),
872 R3 is random(65536),
873 R4 is random(65536)