Permalink
Browse files

improve Cookies support. Host HTTP header is now derived from server …

…or URL

SVN Revision: 227
  • Loading branch information...
nniclausse committed Feb 16, 2004
1 parent d1b9799 commit 56735fe92cc4f24f7ca902820f22a60a6bfe1ff7
View
@@ -51,10 +51,26 @@
status = none, % HTTP resp. status :200, etc. 'none'
% if no current cnx.
close = false, % true if HTTP/1.0 or 'connection: close'
- % has benne received
+ % has been received
cookie=[]
}).
+
+-record(cookie,{
+ key,
+ value,
+ quoted,
+ comment,
+ comment_url,
+ discard,
+ domain,
+ max_age,
+ expires,
+ path,
+ port,
+ secure,
+ version}).
+
%% HTTP Protocol
-define(GET, "GET").
-define(POST, "POST").
View
@@ -33,6 +33,7 @@
-record(state_rcv,
{socket, %
timeout, % ?
+ host, % hostname (or IP) of remote server
protocol, % gen_udp, gen_tcp or ssl
ack, % type of ack: no_ack, local, global or parse
ack_done=false, % 'true' if the ack was sent, else 'false' (unused if ack=no_ack)
View
@@ -122,6 +122,7 @@ init({Session=#session{id = Profile,
case ts_client_rcv:start({ CType, self(),
Socket,
Protocol,
+ ServerName,
?config(tcp_timeout),
PType,
?config(monitoring)}) of
@@ -380,14 +381,6 @@ get_server_cfg(Other,P,Id) ->
handle_next_request(Profile, State) ->
Count = State#state.count-1,
Type = State#state.clienttype,
- case State#state.dyndata of
- [] ->
- Param = Profile#message.param;
- DynData -> % add dynamic info (cookie for ex.) to request parameters
- Param = Type:add_dynparams(Profile#message.param, DynData)
- end,
- Message = Type:get_message(Param),
- Now = now(),
%% does the next message change the server setup ?
case Profile of
@@ -406,13 +399,17 @@ handle_next_request(Profile, State) ->
end
end,
+ Param = Type:add_dynparams(State#state.dyndata,Profile#message.param,Host),
+ Message = Type:get_message(Param),
+ Now = now(),
+
%% reconnect if needed
case reconnect(Socket,Host,Port,Protocol,State#state.ip,State#state.rcvpid) of
{ok, NewSocket} ->
%% warn the receiving side that we are sending a new request
ts_client_rcv:wait_ack({State#state.rcvpid,Profile#message.ack,Now,
Profile#message.endpage, NewSocket,
- Protocol}),
+ Protocol, Host}),
case catch send(Protocol, NewSocket, Message) of
ok ->
ts_mon:sendmes({State#state.monitor, self(), Message}),
@@ -43,8 +43,8 @@ start(Opts) ->
stop(Pid) ->
gen_server:cast(Pid, {stop}).
-wait_ack({Pid, Ack, When, EndPage, Socket, Protocol}) ->
- gen_server:cast(Pid, {wait_ack, Ack, When, EndPage, Socket, Protocol}).
+wait_ack({Pid, Ack, When, EndPage, Socket, Protocol, Host}) ->
+ gen_server:cast(Pid, {wait_ack, Ack, When, EndPage, Socket, Protocol, Host}).
%%%----------------------------------------------------------------------
@@ -58,10 +58,10 @@ wait_ack({Pid, Ack, When, EndPage, Socket, Protocol}) ->
%% ignore |
%% {stop, Reason}
%%----------------------------------------------------------------------
-init([{CType, PPid, Socket, Protocol, Timeout, Ack, Monitor}]) ->
+init([{CType, PPid, Socket, Protocol, ServerName, Timeout, Ack, Monitor}]) ->
{ok, #state_rcv{socket = Socket, timeout= Timeout, ack = Ack,
ppid= PPid, clienttype = CType, protocol= Protocol,
- session = CType:new_session(),
+ session = CType:new_session(), host=ServerName,
monitor = Monitor }}.
%%----------------------------------------------------------------------
@@ -86,7 +86,7 @@ handle_call(Request, From, State) ->
%%----------------------------------------------------------------------
%% ack value -> wait
-handle_cast({wait_ack, Ack, When, EndPage, Socket, Protocol}, State) ->
+handle_cast({wait_ack, Ack, When, EndPage, Socket, Protocol, Host}, State) ->
?DebugF("receive wait_ack: ~p , endpage=~p~n",[Ack, EndPage]),
case State#state_rcv.page_timestamp of
0 -> %first request of a page
@@ -111,6 +111,7 @@ handle_cast({wait_ack, Ack, When, EndPage, Socket, Protocol}, State) ->
endpage=EndPage,
ack_timestamp= When,
protocol= Protocol,
+ host= Host,
page_timestamp = NewPageTimestamp}}
end;
View
@@ -26,7 +26,7 @@
-include("ts_http.hrl").
-export([init_dynparams/0,
- add_dynparams/2,
+ add_dynparams/3,
get_message/1,
parse/2,
parse_config/2,
@@ -70,12 +70,14 @@ parse_config(Element, Conf) ->
ts_http_common:parse_config(Element, Conf).
%%----------------------------------------------------------------------
-%% Function: add_dynparams/2
+%% Function: add_dynparams/3
%% Purpose: add dynamic parameters to build the message
%% this is used for ex. for Cookies in HTTP
%%----------------------------------------------------------------------
-add_dynparams(Param, DynData) ->
- Param#http_request{cookie=DynData}.
+add_dynparams([],Param, Host) ->
+ Param#http_request{server_name=Host};
+add_dynparams(DynData, Param, Host) ->
+ Param#http_request{cookie=DynData,server_name=Host}.
init_dynparams() ->
[].
@@ -54,7 +54,7 @@ http_get(Req=#http_request{url=URL, version=Version, cookie=Cookie,
"Host: ", Host, ?CRLF,
user_agent(),
authenticate(UserId,Passwd),
- get_cookie(Cookie),
+ set_cookie_header(Cookie, Host),
?CRLF]);
http_get(Req=#http_request{url=URL, version=Version, cookie=Cookie,
@@ -65,7 +65,7 @@ http_get(Req=#http_request{url=URL, version=Version, cookie=Cookie,
"Host: ", Host, ?CRLF,
user_agent(),
authenticate(UserId,Passwd),
- get_cookie(Cookie),
+ set_cookie_header(Cookie, Host),
?CRLF]).
%%----------------------------------------------------------------------
@@ -81,7 +81,7 @@ http_post(Req=#http_request{url=URL, version=Version, cookie=Cookie,
"Host: ", Host, ?CRLF,
user_agent(),
authenticate(UserId,Passwd),
- get_cookie(Cookie),
+ set_cookie_header(Cookie, Host),
"Content-Type: ", ContentType, ?CRLF,
"Content-Length: ",ContentLength, ?CRLF,
?CRLF
@@ -100,14 +100,32 @@ authenticate(UserId,Passwd)->
user_agent() ->
["User-Agent: ", ?USER_AGENT, ?CRLF].
-get_cookie(none) -> [];
-get_cookie(Cookies) -> get_cookie(Cookies, []).
-
-get_cookie([], Acc ) -> [lists:reverse(Acc), ?CRLF];
-get_cookie([Cookie|Cookies], []) ->
- get_cookie(Cookies, [["Cookie: ", Cookie]]);
-get_cookie([Cookie|Cookies], Acc) ->
- get_cookie(Cookies, [["; ", Cookie]|Acc]).
+%%----------------------------------------------------------------------
+%% Function: set_cookie_header/2*
+%% Args: Cookies (list), Hostname (string)
+%% Purpose: set Cookie: Header
+%%----------------------------------------------------------------------
+set_cookie_header(none, Host) -> []; % is it useful ?
+set_cookie_header([], Host) -> [];
+set_cookie_header(Cookies, Host) ->
+ MatchDomain = fun (A) -> matchdomain(A,Host) end,
+ CurCookies = lists:filter(MatchDomain, Cookies),
+ set_cookie_header(CurCookies, Host, []). %% TODO: check for domain
+
+set_cookie_header([], Host, Acc) -> [lists:reverse(Acc), ?CRLF];
+set_cookie_header([Cookie|Cookies], Host, []) ->
+ set_cookie_header(Cookies, Host, [["Cookie: ", cookie_rec2str(Cookie)]]);
+set_cookie_header([Cookie|Cookies], Host, Acc) ->
+ set_cookie_header(Cookies, Host, [["; ", cookie_rec2str(Cookie)]|Acc]).
+
+cookie_rec2str(#cookie{key=Key, value=Val}) ->
+ lists:append([Key,"=",Val]).
+
+matchdomain(Cookie, Host) -> % return a cookie only if domain match
+ case string:str(Host, Cookie#cookie.domain) of %% should use regexp:match
+ 0 -> false;
+ _ -> true
+ end.
%%----------------------------------------------------------------------
%% Func: parse_config/2
@@ -182,12 +200,12 @@ parse_config(Element, Conf = #config{}) ->
parse(Data, State) when (State#state_rcv.session)#http.status == none ->
List = binary_to_list(Data),
TotalSize = size(Data),
- {ok, Http, Tail} = parse_headers(#http{},List),
+ {ok, Http, Tail} = parse_headers(#http{},List, State#state_rcv.host),
ts_mon:add({ count, Http#http.status }),
BodySize= length(Tail),
CLength = Http#http.content_length,
Close = Http#http.close,
- Cookie = lists:append([Http#http.cookie, State#state_rcv.dyndata]),
+ Cookie = concat_cookies(Http#http.cookie, State#state_rcv.dyndata),
if
CLength == 0, Http#http.chunk_toread == 0 ->
case parse_chunked(Tail, State#state_rcv{session=Http}) of
@@ -276,8 +294,8 @@ read_chunk(<<Char:1/binary, Data/binary>>, State, Int, Acc) ->
<<?CR>> when Int>0 ->
read_chunk_data(Data, State, Int+3, Acc+1);
<<?CR>> when Int==0 -> %% should be the end of tranfer
- Cookie=lists:append([(State#state_rcv.session)#http.cookie,
- State#state_rcv.dyndata]),
+ Cookie = concat_cookies((State#state_rcv.session)#http.cookie,
+ State#state_rcv.dyndata),
?DebugF("Finish tranfer chunk ~p~n", [binary_to_list(Data)]),
{State#state_rcv{session= #http{}, ack_done = true,
datasize = Acc, %% FIXME: is it the correct size?
@@ -312,19 +330,62 @@ read_chunk_data(Data, State, Int, Acc) -> % not enough data in buffer
dyndata = Cookie},[]}.
%%----------------------------------------------------------------------
-%% Func: get_cookie_val/1
+%% Func: add_new_cookie/3
%% Purpose: Separate cookie values from attributes
%%----------------------------------------------------------------------
-get_cookie_val(Cookie) ->
- case string:tokens( Cookie, "; ") of
- [CookieVal |CookieOtherAttrib] ->
- %% FIXME: handle path attribute
- %% several cookies can be set with a different path attribute
- CookieVal ;
- _Other -> % something wrong
- []
+add_new_cookie(Cookie, Host, OldCookies) ->
+ {ok, Fields} = regexp:split( Cookie, "; "),
+ New = parse_set_cookie(Fields, #cookie{domain=Host}),
+ concat_cookies([New],OldCookies).
+
+%%----------------------------------------------------------------------
+%% Func: concat_cookie/2
+%% Purpose: add new cookies to a list of old ones. If the keys already
+%% exists, replace with the new ones
+%%----------------------------------------------------------------------
+concat_cookies([], CookiesList) -> CookiesList;
+concat_cookies(New, []) -> New;
+concat_cookies([New=#cookie{}| Rest], OldCookies) ->
+ case lists:keysearch(New#cookie.key, #cookie.key, OldCookies) of
+ {value, OldVal} ->
+ ?DebugF("Reset key ~p with new value ~p~n",[New#cookie.key,
+ New#cookie.value]),
+ NewList = lists:keyreplace(New#cookie.key, #cookie.key, OldCookies, New),
+ concat_cookies(Rest, NewList);
+ false ->
+ concat_cookies(Rest, [New | OldCookies])
end.
-
+
+
+parse_set_cookie([], Cookie) -> Cookie;
+parse_set_cookie([Field| Rest], Cookie=#cookie{}) ->
+ {Key,Val} = get_cookie_key(Field,[]),
+ ?DebugF("Parse cookie key ~p with value ~p~n",[Key, Val]),
+ parse_set_cookie(Rest, set_cookie_key(Key, Val, Cookie)).
+
+set_cookie_key([L|"ersion"],Val,Cookie) when L == $V; L==$v ->
+ Cookie#cookie{version=Val};
+set_cookie_key([L|"omain"],Val,Cookie) when L == $D; L==$d ->
+ Cookie#cookie{domain=Val};
+set_cookie_key([L|"ath"],Val,Cookie) when L == $P; L==$p ->
+ Cookie#cookie{path=Val};
+set_cookie_key([L|"ax-Age"],Val,Cookie) when L == $M; L==$m ->
+ Cookie#cookie{max_age=Val};
+set_cookie_key([L|"xpires"],Val,Cookie) when L == $E; L==$e ->
+ Cookie#cookie{expires=Val};
+set_cookie_key([L|"ort"],Val,Cookie) when L == $P; L==$p ->
+ Cookie#cookie{port=Val};
+set_cookie_key([L|"iscard"],Val,Cookie) when L == $D; L==$d ->
+ Cookie#cookie{discard=true};
+set_cookie_key([L|"ecure"],Val,Cookie) when L == $S; L==$s ->
+ Cookie#cookie{secure=true};
+set_cookie_key(Key,Val,Cookie) ->
+ Cookie#cookie{key=Key,value=Val}.
+
+get_cookie_key([],Acc) -> bad_cookie_format;
+get_cookie_key([$=|Rest],Acc) -> {lists:reverse(Acc), Rest};
+get_cookie_key([Char|Rest],Acc)-> get_cookie_key(Rest, [Char|Acc]).
+
%%----------------------------------------------------------------------
%% Func: parse_URL/1
%% Returns: #url
@@ -417,12 +478,12 @@ set_port(#url{port=Port}) -> integer_to_list(Port).
%% Purpose: Parse HTTP headers line by line
%% Returns: {ok, #http, Body}
%%--------------------------------------------------------------------
-parse_headers(H, Tail) ->
+parse_headers(H, Tail, Host) ->
case get_line(Tail) of
{line, Line, Tail2} ->
- parse_headers(parse_line(Line, H), Tail2);
+ parse_headers(parse_line(Line, H, Host), Tail2, Host);
{lastline, Line, Tail2} ->
- {ok, parse_line(Line, H), Tail2}
+ {ok, parse_line(Line, H, Host), Tail2}
end.
%%--------------------------------------------------------------------
@@ -440,32 +501,32 @@ parse_status([A,B,C|Tail], Http) ->
%% Purpose: Parse a HTTP header
%% Returns: #http
%%--------------------------------------------------------------------
-parse_line("HTTP/1.1 " ++ TailLine, Http )->
+parse_line("HTTP/1.1 " ++ TailLine, Http, Host )->
parse_status(TailLine, Http);
-parse_line("HTTP/1.0 " ++ TailLine, Http )->
+parse_line("HTTP/1.0 " ++ TailLine, Http, Host)->
parse_status(TailLine, Http#http{close=true});
-parse_line("Content-length: "++Tail, Http)->
+parse_line("Content-length: "++Tail, Http, Host)->
CL=list_to_integer(Tail),
?DebugF("HTTP Content-Length ~p~n",[CL]),
Http#http{content_length=CL};
-parse_line("Content-Length: "++Tail, Http)->
+parse_line("Content-Length: "++Tail, Http, Host)->
CL=list_to_integer(Tail),
?DebugF("HTTP Content-Length ~p~n",[CL]),
Http#http{content_length=CL};
-parse_line("Connection: close"++Tail, Http)->
+parse_line("Connection: close"++Tail, Http, Host)->
Http#http{close=true};
-parse_line("Transfer-Encoding: chunked"++Tail, Http)->
+parse_line("Transfer-Encoding: chunked"++Tail, Http, Host)->
?LOG("Chunked transfer encoding~n",?DEB),
Http#http{chunk_toread=0};
-parse_line("Transfer-Encoding:"++Tail, Http)->
+parse_line("Transfer-Encoding:"++Tail, Http, Host)->
?LOGF("Unknown tranfer encoding ~p~n",[Tail],?NOTICE),
Http;
-parse_line("Set-Cookie: "++Tail, Http=#http{cookie=PrevCookies})->
- Cookie = get_cookie_val(Tail), %% FIXME: is it ok ?
+parse_line("Set-Cookie: "++Tail, Http=#http{cookie=PrevCookies}, Host)->
+ Cookie = add_new_cookie(Tail, Host, PrevCookies),
?DebugF("HTTP New cookie val ~p~n",[Cookie]),
- Http#http{cookie=lists:reverse([Cookie|PrevCookies])};
-parse_line(Line,Http) ->
+ Http#http{cookie=Cookie};
+parse_line(Line,Http, Host) ->
?DebugF("Skip header ~p (Http record is ~p)~n",[Line,Http]),
Http.
@@ -29,7 +29,7 @@
-include("ts_jabber.hrl").
-export([init_dynparams/0,
- add_dynparams/2,
+ add_dynparams/3,
get_message/1,
parse/2,
parse_config/2,
@@ -72,7 +72,9 @@ parse_config(Element, Conf) ->
%% Function: add_dynparams/2
%% Purpose: add dynamic parameters to build the message
%%----------------------------------------------------------------------
-add_dynparams(Param, DynData) ->
+add_dynparams([], Param, Host) ->
+ Param;
+add_dynparams(DynData, Param, Host) ->
Param#jabber{id=DynData}.
init_dynparams() ->

0 comments on commit 56735fe

Please sign in to comment.