/* This file is part of ClioPatria. Author: HTTP: http://e-culture.multimedian.nl/ GITWEB: http://gollem.science.uva.nl/git/ClioPatria.git GIT: git://gollem.science.uva.nl/home/git/ClioPatria.git GIT: http://gollem.science.uva.nl/home/git/ClioPatria.git Copyright: 2007, E-Culture/MultimediaN ClioPatria 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. ClioPatria 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 General Public License along with ClioPatria. If not, see . */ :- module(http_cookie, [ http_get/4, % +ClientId, +Request, -Reply, +Options http_remove_client/1, % +ClientId http_remove_all_clients/0, http_current_cookie/4 % ?ClientId, ?Name, ?Value, ?Options ]). :- use_module(library('http/http_client')). :- use_module(library(url)). :- use_module(library(debug)). /** HTTP client cookie handling This module defines http_get/4, a wrapper around http_get/3 where we can define multiple `virtual' clients, each managing a cookie database. It was designed to deal with cookie-based session management in the client, where the same Prolog process is client in multiple concurrent sessions. */ :- dynamic client_cookie/4. % Id, Name, Value, Options %% http_get(+ClientId, +Request, -Reply, +Options) is det. % % Add cookie handling to http_get/3. ClientId is a ground term % representing the client. The library takes care of cookies send % by the server and updates its cookie information. http_get(ClientId, Request0, Reply, Options) :- break_url(Request0, Request), ( memberchk(reply_header(Header), Options) -> GetOptions = Options ; GetOptions = [reply_header(Header)|Options] ), add_cookies(ClientId, Request, GetOptions, AllOptions), http_get(Request, Reply, AllOptions), update_cookies(ClientId, Request, Header). %% break_url(+UrlOrRequest, -Parts) is det. % % Break a URL into parts. Returns input if it already contains % parts. See parse_url/2 for the format. break_url(URL, Request) :- atomic(URL), !, parse_url(URL, Request). break_url(Request, Request). %% add_cookies(+ClientId, +Request, +Options0, -Options) is det. % % Add cookies to an HTTP request. add_cookies(ClientId, Request, Options, [request_header('Cookie'=Cookie)|Options]) :- request_host(Request, Host), request_path(Request, Path), findall(N=V, current_cookie(ClientId, Host, Path, N, V), Cookies), Cookies \== [], !, debug(cookie, 'Cookies for ~w at ~w~w: ~p', [ClientId, Host, Path, Cookies]), cookie_value(Cookies, Cookie). add_cookies(_, _, Options, Options). request_host(Request, Host) :- ( memberchk(host(Host), Request) -> true ; throw(error(existence_error(parameter, host), _)) ). request_path(Request, Path) :- ( memberchk(path(Path), Request) -> true ; Path = (/) ). %% cookie_value(+NameValueList, -CookieString) is det. % % Create a cookie value string with name=value, seperated by ";". cookie_value(List, Cookie) :- with_output_to(string(Cookie), write_cookies(List)). write_cookies([]). write_cookies([Name=Value|T]) :- format('~w=~w', [Name, Value]), ( T == [] -> true ; format('; ', []), write_cookies(T) ). %% update_cookies(+ClientId, +Request, +Header) is det. % % Update the client cookie database. Request is the original % request. Header is the HTTP reply-header. update_cookies(ClientId, Request, Header) :- memberchk(set_cookie(set_cookie(Name, Value, Options)), Header), !, request_host(Request, Host), request_path(Request, Path), with_mutex(http_cookie, update_cookie(ClientId, Host, Path, Name, Value, Options)). update_cookies(_, _, _). update_cookie(ClientId, Host, Path, Name, Value, Options) :- remove_cookies(ClientId, Host, Path, Name, Options), debug(cookie, 'New for ~w: ~w=~p', [ClientId, Name, Value]), assert(client_cookie(ClientId, Name, Value, [host=Host|Options])). %% remove_cookies(+ClientId, +Host, +Path, +Name, +SetOptions) is det. % % Remove all cookies that conflict with the new set-cookie % command. remove_cookies(ClientId, Host, Path, Name, SetOptions) :- ( client_cookie(ClientId, Name, Value, OldOptions), cookie_match_host(Host, SetOptions, OldOptions), cookie_match_path(Path, SetOptions, OldOptions), debug(cookie, 'Del for ~w: ~w=~p', [ClientId, Name, Value]), retract(client_cookie(ClientId, Name, Value, OldOptions)), fail ; true ). cookie_match_host(Host, SetOptions, OldOptions) :- ( memberchk(domain=Domain, SetOptions) -> cookie_match_host(Domain, OldOptions) ; cookie_match_host(Host, OldOptions) ). cookie_match_path(Path, SetOptions, OldOptions) :- ( memberchk(path=PathO, SetOptions) -> cookie_match_path(PathO, OldOptions) ; cookie_match_path(Path, OldOptions) ). %% current_cookie(+ClientId, +Host, +Path, -Name, -Value) is nondet. % % Find cookies that match the given request. current_cookie(ClientId, Host, Path, Name, Value) :- client_cookie(ClientId, Name, Value, Options), cookie_match_host(Host, Options), cookie_match_path(Path, Options), cookie_match_expire(Options). cookie_match_host(Host, Options) :- ( memberchk(domain=Domain, Options) -> downcase_atom(Host, LHost), downcase_atom(Domain, LDomain), sub_atom(LHost, _, _, 0, LDomain) % TBD: check '.'? ; memberchk(host=CHost, Options), downcase_atom(Host, LHost), downcase_atom(CHost, LHost) ). cookie_match_path(Path, Options) :- ( memberchk(path=Root, Options) -> sub_atom(Path, 0, _, _, Root) % TBD: check '/'? ; true ). cookie_match_expire(Options) :- ( memberchk(expire=Expire, Options) -> get_time(Now), Now =< Expire ; true ). %% http_remove_client(+ClientId) is det. % % Fake user quitting a browser. Removes all cookies that do % not have an expire date. http_remove_client(ClientId) :- var(ClientId), !, throw(error(instantiation_error, _)). http_remove_client(ClientId) :- ( client_cookie(ClientId, Name, Value, Options), \+ memberchk(expire=_, Options), retract(client_cookie(ClientId, Name, Value, Options)), fail ; true ). %% http_remove_all_clients is det. % % Simply logout all clients. See http_remove_client/1. http_remove_all_clients :- forall(current_client(ClientId), http_remove_client(ClientId)). %% current_client(?ClientId) is nondet. % % True if ClientId is the identifier of a client. current_client(ClientId) :- client_cookie(ClientId, _Name, _Value, _Options). %% http_current_cookie(?ClientId, ?Name, ?Value, ?Options) % % Query current cookie database http_current_cookie(ClientId, Name, Value, Options) :- client_cookie(ClientId, Name, Value, Options).