ejabberd-contrib/ejabberd_auth_http/deps/fusco/src/fusco_protocol.erl

568 lines
28 KiB
Erlang

%%%=============================================================================
%%% @copyright (C) 2013, Erlang Solutions Ltd
%%% @author Diana Corbacho <diana.corbacho@erlang-solutions.com>
%%% @doc
%%%
%%% @end
%%%=============================================================================
-module(fusco_protocol).
-copyright("2013, Erlang Solutions Ltd.").
-include("fusco.hrl").
-define(SIZE(Data, Response), Response#response{size = Response#response.size + byte_size(Data)}).
-define(RECEPTION(Data, Response), Response#response{size = byte_size(Data),
in_timestamp = os:timestamp()}).
-define(TOUT, 1000).
%% Latency is here defined as the time from the start of packet transmission to the start of packet reception
%% API
-export([recv/2, recv/3,
decode_cookie/1]).
%% TEST
-export([decode_header_value/5, decode_header_value/6,
decode_header/3, decode_header/4]).
%% TODO handle partial downloads
recv(Socket, Ssl) ->
recv(Socket, Ssl, infinity).
recv(Socket, Ssl, Timeout) ->
case fusco_sock:recv(Socket, Ssl, Timeout) of
{ok, Data} ->
decode_status_line(<< Data/binary >>,
?RECEPTION(Data, #response{socket = Socket, ssl = Ssl}), Timeout);
{error, Reason} ->
{error, Reason}
end.
decode_status_line(<<"HTTP/1.0\s",C1,C2,C3,$\s,Rest/bits>>, Response, Timeout) ->
decode_reason_phrase(Rest, <<>>, Response#response{version = {1,0},
status_code = <<C1,C2,C3>>}, Timeout);
decode_status_line(<<"HTTP/1.1\s",C1,C2,C3,$\s,Rest/bits>>, Response, Timeout) ->
decode_reason_phrase(Rest, <<>>, Response#response{version = {1,1},
status_code = <<C1,C2,C3>>}, Timeout);
decode_status_line(Bin, Response = #response{size = Size}, Timeout) when Size < 13 ->
case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
{ok, Data} ->
decode_status_line(<<Bin/binary, Data/binary>>, ?SIZE(Data, Response), Timeout);
{error, Reason} ->
{error, Reason}
end;
decode_status_line(_, _, _) ->
{error, status_line}.
decode_reason_phrase(<<>>, Acc, Response, Timeout) ->
case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
{ok, Data} ->
decode_reason_phrase(Data, Acc, ?SIZE(Data, Response), Timeout);
{error, Reason} ->
{error, Reason}
end;
decode_reason_phrase(<<$\r>>, Acc, Response, Timeout) ->
case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
{ok, Data} ->
decode_reason_phrase(<<$\r, Data/binary>>, Acc, ?SIZE(Data, Response), Timeout);
{error, Reason} ->
{error, Reason}
end;
decode_reason_phrase(<<$\n, Rest/bits>>, Acc, Response, Timeout) ->
decode_header(Rest, <<>>, Response#response{reason = Acc}, Timeout);
decode_reason_phrase(<<$\r,$\n, Rest/bits>>, Acc, Response, Timeout) ->
decode_header(Rest, <<>>, Response#response{reason = Acc}, Timeout);
decode_reason_phrase(<<C, Rest/bits>>, Acc, Response, Timeout) ->
decode_reason_phrase(Rest, <<Acc/binary, C>>, Response, Timeout).
decode_header(Data, Acc, Response) ->
decode_header(Data, Acc, Response, infinity).
decode_header(<<>>, Acc, Response, Timeout) ->
case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
{ok, Data} ->
decode_header(Data, Acc, ?SIZE(Data, Response), Timeout);
{error, closed} ->
case Acc of
<<>> ->
decode_body(<<>>, Response, Timeout);
_ ->
{error, closed}
end;
{error, Reason} ->
{error, Reason}
end;
decode_header(<<$\r>>, Acc, Response, Timeout) ->
case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
{ok, Data} ->
decode_header(<<$\r, Data/binary>>, Acc, ?SIZE(Data, Response), Timeout);
{error, Reason} ->
{error, Reason}
end;
decode_header(<<$\s, Rest/bits>>, Acc, Response, Timeout) ->
decode_header(Rest, Acc, Response, Timeout);
decode_header(<<$:, Rest/bits>>, Header, Response, Timeout) ->
decode_header_value_ws(Rest, Header, Response, Timeout);
decode_header(<<$\n, Rest/bits>>, <<>>, Response, Timeout) ->
decode_body(Rest, Response, Timeout);
decode_header(<<$\r, $\n, Rest/bits>>, <<>>, Response, Timeout) ->
decode_body(Rest, Response, Timeout);
decode_header(<<$\r, $\n, _Rest/bits>>, _, _Response, _Timeout) ->
{error, header};
decode_header(<<$A, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $a>>, Response, Timeout);
decode_header(<<$B, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $b>>, Response, Timeout);
decode_header(<<$C, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $c>>, Response, Timeout);
decode_header(<<$D, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $d>>, Response, Timeout);
decode_header(<<$E, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $e>>, Response, Timeout);
decode_header(<<$F, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $f>>, Response, Timeout);
decode_header(<<$G, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $g>>, Response, Timeout);
decode_header(<<$H, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $h>>, Response, Timeout);
decode_header(<<$I, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $i>>, Response, Timeout);
decode_header(<<$J, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $j>>, Response, Timeout);
decode_header(<<$K, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $k>>, Response, Timeout);
decode_header(<<$L, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $l>>, Response, Timeout);
decode_header(<<$M, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $m>>, Response, Timeout);
decode_header(<<$N, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $n>>, Response, Timeout);
decode_header(<<$O, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $o>>, Response, Timeout);
decode_header(<<$P, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $p>>, Response, Timeout);
decode_header(<<$Q, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $q>>, Response, Timeout);
decode_header(<<$R, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $r>>, Response, Timeout);
decode_header(<<$S, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $s>>, Response, Timeout);
decode_header(<<$T, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $t>>, Response, Timeout);
decode_header(<<$U, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $u>>, Response, Timeout);
decode_header(<<$V, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $v>>, Response, Timeout);
decode_header(<<$W, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $w>>, Response, Timeout);
decode_header(<<$X, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $x>>, Response, Timeout);
decode_header(<<$Y, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $y>>, Response, Timeout);
decode_header(<<$Z, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, $z>>, Response, Timeout);
decode_header(<<C, Rest/bits>>, Header, Response, Timeout) ->
decode_header(Rest, <<Header/binary, C>>, Response, Timeout).
decode_header_value_ws(<<$\s, Rest/bits>>, H, S, Timeout) ->
decode_header_value_ws(Rest, H, S, Timeout);
decode_header_value_ws(<<$\t, Rest/bits>>, H, S, Timeout) ->
decode_header_value_ws(Rest, H, S, Timeout);
decode_header_value_ws(Rest, <<"connection">> = H, S, Timeout) ->
decode_header_value_lc(Rest, H, <<>>, <<>>, S, Timeout);
decode_header_value_ws(Rest, <<"transfer-encoding">> = H, S, Timeout) ->
decode_header_value_lc(Rest, H, <<>>, <<>>, S, Timeout);
decode_header_value_ws(Rest, H, S, Timeout) ->
decode_header_value(Rest, H, <<>>, <<>>, S, Timeout).
decode_header_value(Data, H, V, T, Response) ->
decode_header_value(Data, H, V, T, Response, infinity).
decode_header_value(<<>>, H, V, T, Response, Timeout) ->
case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
{ok, Data} ->
decode_header_value(Data, H, V, T, ?SIZE(Data, Response), Timeout);
{error, Reason} ->
{error, Reason}
end;
decode_header_value(<<$\r>>, H, V, T, Response, Timeout) ->
case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
{ok, Data} ->
decode_header_value(<<$\r, Data/binary>>, H, V, T, ?SIZE(Data, Response), Timeout);
{error, Reason} ->
{error, Reason}
end;
decode_header_value(<<$\n, Rest/bits>>, <<"content-length">> = H, V, _T, Response, Timeout) ->
decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers],
content_length = binary_to_integer(V)}, Timeout);
decode_header_value(<<$\n, Rest/bits>>, <<"set-cookie">> = H, V, _T, Response, Timeout) ->
decode_header(Rest, <<>>, Response#response{cookies = [decode_cookie(V)
| Response#response.cookies],
headers = [{H, V} | Response#response.headers]}, Timeout);
decode_header_value(<<$\n, Rest/bits>>, H, V, _T, Response, Timeout) ->
decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers]}, Timeout);
decode_header_value(<<$\r, $\n, Rest/bits>>, <<"set-cookie">> = H, V, _T, Response, Timeout) ->
decode_header(Rest, <<>>, Response#response{cookies = [decode_cookie(V)
| Response#response.cookies],
headers = [{H, V} | Response#response.headers]}, Timeout);
decode_header_value(<<$\r,$\n, Rest/bits>>, <<"content-length">> = H, V, _T, Response, Timeout) ->
decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers],
content_length = binary_to_integer(V)}, Timeout);
decode_header_value(<<$\r, $\n, Rest/bits>>, H, V, _T, Response, Timeout) ->
decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers]}, Timeout);
decode_header_value(<<$\s, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value(Rest, H, V, <<T/binary, $\s>>, Response, Timeout);
decode_header_value(<<$\t, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value(Rest, H, V, <<T/binary, $\t>>, Response, Timeout);
decode_header_value(<<C, Rest/bits>>, H, V, <<>>, Response, Timeout) ->
decode_header_value(Rest, H, <<V/binary, C>>, <<>>, Response, Timeout);
decode_header_value(<<C, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value(Rest, H, <<V/binary, T/binary, C>>, <<>>, Response, Timeout).
decode_header_value_lc(<<>>, H, V, T, Response, Timeout) ->
case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
{ok, Data} ->
decode_header_value_lc(Data, H, V, T, ?SIZE(Data, Response), Timeout);
{error, Reason} ->
{error, Reason}
end;
decode_header_value_lc(<<$\r>>, H, V, T, Response, Timeout) ->
case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
{ok, Data} ->
decode_header_value_lc(<<$\r, Data/binary>>, H, V, T, ?SIZE(Data, Response), Timeout);
{error, Reason} ->
{error, Reason}
end;
decode_header_value_lc(<<$\n, Rest/bits>>, <<"transfer-encoding">> = H, V, _T, Response, Timeout) ->
decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers],
transfer_encoding = V}, Timeout);
decode_header_value_lc(<<$\n, Rest/bits>>, H, V, _T, Response, Timeout) ->
decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers],
connection = V}, Timeout);
decode_header_value_lc(<<$\r, $\n, Rest/bits>>, <<"transfer-encoding">> = H, V, _T, Response, Timeout) ->
decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers],
transfer_encoding = V}, Timeout);
decode_header_value_lc(<<$\r, $\n, Rest/bits>>, H, V, _T, Response, Timeout) ->
decode_header(Rest, <<>>, Response#response{headers = [{H, V} | Response#response.headers],
connection = V}, Timeout);
decode_header_value_lc(<<$\s, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, V, <<T/binary, $\s>>, Response, Timeout);
decode_header_value_lc(<<$\t, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, V, <<T/binary, $\t>>, Response, Timeout);
decode_header_value_lc(<<$A, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $a>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$B, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $b>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$C, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $c>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$D, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $d>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$E, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $e>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$F, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $f>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$G, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $g>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$H, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $h>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$I, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $i>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$J, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $j>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$K, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $k>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$L, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $l>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$M, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $m>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$N, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $n>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$O, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $o>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$P, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $p>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$Q, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $q>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$R, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $r>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$S, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $s>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$T, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $t>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$U, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $u>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$V, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $v>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$W, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $w>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$X, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $x>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$Y, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $y>>, <<>>, Response, Timeout);
decode_header_value_lc(<<$Z, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, $z>>, <<>>, Response, Timeout);
decode_header_value_lc(<<C, Rest/bits>>, H, V, T, Response, Timeout) ->
decode_header_value_lc(Rest, H, <<V/binary, T/binary, C>>, <<>>, Response, Timeout).
%% RFC 6265
%% TODO decode cookie values, this only accepts 'a=b'
decode_cookie(Cookie) ->
decode_cookie_name(Cookie, <<>>).
decode_cookie_name(<<$\s, Rest/bits>>, N) ->
decode_cookie_name(Rest, N);
decode_cookie_name(<<$\t, Rest/bits>>, N) ->
decode_cookie_name(Rest, N);
decode_cookie_name(<<$=, Rest/bits>>, N) ->
decode_cookie_value(Rest, N, <<>>);
decode_cookie_name(<<C, Rest/bits>>, N) ->
decode_cookie_name(Rest, <<N/binary, C>>).
decode_cookie_value(<<$\s, Rest/bits>>, N, V) ->
decode_cookie_value(Rest, N, V);
decode_cookie_value(<<$\t, Rest/bits>>, N, V) ->
decode_cookie_value(Rest, N, V);
decode_cookie_value(<<$;, Rest/bits>>, N, V) ->
decode_cookie_av_ws(Rest, #fusco_cookie{name = N, value = V});
decode_cookie_value(<<C, Rest/bits>>, N, V) ->
decode_cookie_value(Rest, N, <<V/binary, C>>);
decode_cookie_value(<<>>, N, V) ->
#fusco_cookie{name = N, value = V}.
decode_cookie_av_ws(<<$\s, Rest/bits>>, C) ->
decode_cookie_av_ws(Rest, C);
decode_cookie_av_ws(<<$\t, Rest/bits>>, C) ->
decode_cookie_av_ws(Rest, C);
%% We are only interested on Expires, Max-Age, Path, Domain
decode_cookie_av_ws(<<$e, Rest/bits>>, C) ->
decode_cookie_av(Rest, C, <<$e>>);
decode_cookie_av_ws(<<$E, Rest/bits>>, C) ->
decode_cookie_av(Rest, C, <<$e>>);
decode_cookie_av_ws(<<$m, Rest/bits>>, C) ->
decode_cookie_av(Rest, C, <<$m>>);
decode_cookie_av_ws(<<$M, Rest/bits>>, C) ->
decode_cookie_av(Rest, C, <<$m>>);
decode_cookie_av_ws(<<$p, Rest/bits>>, C) ->
decode_cookie_av(Rest, C, <<$p>>);
decode_cookie_av_ws(<<$P, Rest/bits>>, C) ->
decode_cookie_av(Rest, C, <<$p>>);
decode_cookie_av_ws(<<$d, Rest/bits>>, C) ->
decode_cookie_av(Rest, C, <<$d>>);
decode_cookie_av_ws(<<$D, Rest/bits>>, C) ->
decode_cookie_av(Rest, C, <<$d>>);
decode_cookie_av_ws(Rest, C) ->
ignore_cookie_av(Rest, C).
ignore_cookie_av(<<$;, Rest/bits>>, Co) ->
decode_cookie_av_ws(Rest, Co);
ignore_cookie_av(<<_, Rest/bits>>, Co) ->
ignore_cookie_av(Rest, Co);
ignore_cookie_av(<<>>, Co) ->
Co.
%% Match only uppercase chars on Expires, Max-Age, Path, Domain
decode_cookie_av(<<$=, Rest/bits>>, Co, AV) ->
decode_cookie_av_value(Rest, Co, AV, <<>>);
decode_cookie_av(<<$D, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $d>>);
decode_cookie_av(<<$O, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $o>>);
decode_cookie_av(<<$N, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $n>>);
decode_cookie_av(<<$E, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $e>>);
decode_cookie_av(<<$X, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $x>>);
decode_cookie_av(<<$P, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $p>>);
decode_cookie_av(<<$I, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $i>>);
decode_cookie_av(<<$R, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $r>>);
decode_cookie_av(<<$S, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $s>>);
decode_cookie_av(<<$M, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $m>>);
decode_cookie_av(<<$A, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $a>>);
decode_cookie_av(<<$G, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $g>>);
decode_cookie_av(<<$T, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $t>>);
decode_cookie_av(<<$H, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, $h>>);
decode_cookie_av(<<$;, Rest/bits>>, Co, _AV) ->
decode_cookie_av_ws(Rest, Co);
decode_cookie_av(<<C, Rest/bits>>, Co, AV) ->
decode_cookie_av(Rest, Co, <<AV/binary, C>>);
decode_cookie_av(<<>>, Co, _AV) ->
ignore_cookie_av(<<>>, Co).
decode_cookie_av_value(<<>>, Co, <<"path">>, Value) ->
Co#fusco_cookie{path_tokens = binary:split(Value, <<"/">>, [global]),
path = Value};
decode_cookie_av_value(<<>>, Co, <<"max-age">>, Value) ->
Co#fusco_cookie{max_age = max_age(Value)};
decode_cookie_av_value(<<>>, Co, <<"expires">>, Value) ->
Co#fusco_cookie{expires = expires(Value)};
decode_cookie_av_value(<<>>, Co, <<"domain">>, Value) ->
Co#fusco_cookie{domain = Value};
decode_cookie_av_value(<<$;, Rest/bits>>, Co, <<"path">>, Value) ->
Path = binary:split(Value, <<"/">>, [global]),
decode_cookie_av_ws(Rest, Co#fusco_cookie{path_tokens = Path,
path = Value});
decode_cookie_av_value(<<$;, Rest/bits>>, Co, <<"max-age">>, Value) ->
decode_cookie_av_ws(Rest, Co#fusco_cookie{
max_age = max_age(Value)});
decode_cookie_av_value(<<$;, Rest/bits>>, Co, <<"expires">>, Value) ->
%% TODO parse expires
decode_cookie_av_ws(Rest, Co#fusco_cookie{expires = expires(Value)});
decode_cookie_av_value(<<$;, Rest/bits>>, Co, <<"domain">>, Value) ->
decode_cookie_av_ws(Rest, Co#fusco_cookie{domain = Value});
decode_cookie_av_value(<<$;, Rest/bits>>, Co, _, _) ->
decode_cookie_av_ws(Rest, Co);
decode_cookie_av_value(<<C, Rest/bits>>, Co, AV, Value) ->
decode_cookie_av_value(Rest, Co, AV, <<Value/binary, C>>).
decode_body(<<>>, Response = #response{status_code = <<$1, _, _>>,
transfer_encoding = TE}, _Timeout)
when TE =/= <<"chunked">> ->
return(<<>>, Response);
decode_body(<<$\r, $\n, Rest/bits>>, Response, Timeout) ->
decode_body(Rest, Response, Timeout);
decode_body(Rest, Response = #response{status_code = <<$1, _, _>>,
transfer_encoding = TE}, Timeout)
when TE =/= <<"chunked">> ->
decode_status_line(Rest, #response{socket = Response#response.socket,
ssl = Response#response.ssl,
in_timestamp = Response#response.in_timestamp}, Timeout);
decode_body(Rest, Response = #response{transfer_encoding = <<"chunked">>}, Timeout) ->
decode_chunked_body(Rest, <<>>, <<>>, Response, Timeout);
decode_body(Rest, Response, Timeout) ->
case byte_size(Rest) >= Response#response.content_length of
true ->
return(Rest, Response);
false ->
case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
{ok, Data} ->
decode_body(<<Rest/binary, Data/binary>>, ?SIZE(Data, Response), Timeout);
_ ->
%% NOTE: Return what we have so far
return(Rest, Response)
end
end.
download_chunked_body(Rest, Acc, Size, Response, Timeout) ->
case fusco_sock:recv(Response#response.socket, Response#response.ssl, Timeout) of
{ok, Data} ->
decode_chunked_body(<<Rest/bits, Data/bits>>, Acc, Size,
?SIZE(Data, Response), Timeout);
_ ->
return(Acc, Response)
end.
decode_chunked_body(<<$0,$\r,$\n,$\r,$\n>>, Acc, _, Response, _Timeout) ->
return(Acc, Response);
decode_chunked_body(<<$0, Rest/bits>> = R, Acc, Size, Response, Timeout)
when is_binary(Size), byte_size(Rest) < 4 ->
download_chunked_body(R, Acc, Size, Response, Timeout);
decode_chunked_body(<<$\r>> = R, Acc, Size, Response, Timeout) when is_binary(Size) ->
download_chunked_body(R, Acc, Size, Response, Timeout);
decode_chunked_body(<<$\r,$\n, Rest/bits>>, Acc, <<>>, Response, Timeout) ->
decode_chunked_body(Rest, Acc, <<>>, Response, Timeout);
decode_chunked_body(<<$\r,$\n, Rest/bits>>, Acc, Size, Response, Timeout) when is_binary(Size) ->
IntSize = erlang:binary_to_integer(Size, 16),
decode_chunked_body(Rest, Acc, IntSize, Response, Timeout);
decode_chunked_body(<<C, Rest/bits>>, Acc, Size, Response, Timeout) when is_binary(Size) ->
decode_chunked_body(Rest, Acc, <<Size/bits, C>>, Response, Timeout);
decode_chunked_body(<<>> = R, Acc, Size, Response, Timeout) when is_binary(Size) ->
download_chunked_body(R, Acc, Size, Response, Timeout);
decode_chunked_body(Rest, Acc, Size, Response, Timeout) when is_integer(Size) ->
case byte_size(Rest) of
S when S == Size ->
decode_chunked_body(<<>>, <<Acc/bits, Rest/bits>>, <<>>, Response, Timeout);
S when S < Size ->
download_chunked_body(Rest, Acc, Size, Response, Timeout);
S when S > Size ->
Current = binary:part(Rest, 0, Size),
Next = binary:part(Rest, Size, S - Size),
decode_chunked_body(Next, <<Acc/bits, Current/bits>>, <<>>, Response, Timeout)
end.
return(Body, Response) ->
Response#response{body = Body}.
max_age(Value) ->
binary_to_integer(Value) * 1000000.
%% http://tools.ietf.org/html/rfc2616#section-3.3.1
%% Supports some non-standard datetime (Tomcat) Tue, 06-Nov-1994 08:49:37 GMT
expires(<<_,_,_,$,,$\s,D1,D2,$\s,M1,M2,M3,$\s,Y1,Y2,Y3,Y4,$\s,Rest/bits>>) ->
expires(Rest, {list_to_integer([Y1,Y2,Y3,Y4]),month(<<M1,M2,M3>>),list_to_integer([D1,D2])});
expires(<<_,_,_,$\s,Mo1,Mo2,Mo3,$\s,D1,D2,$\s,H1,H2,$:,M1,M2,$:,S1,S2,$\s,Y1,Y2,Y3,Y4,_Rest/bits>>) ->
{{list_to_integer([Y1,Y2,Y3,Y4]),month(<<Mo1,Mo2,Mo3>>),list_to_integer([D1,D2])},
{list_to_integer([H1,H2]), list_to_integer([M1,M2]), list_to_integer([S1,S2])}};
expires(<<_,_,_,$,,$\s,Rest/bits>>) ->
expires(Rest);
expires(<<"Monday",$,,$\s,Rest/bits>>) ->
expires(Rest);
expires(<<"Tuesday",$,,$\s,Rest/bits>>) ->
expires(Rest);
expires(<<"Wednesday",$,,$\s,Rest/bits>>) ->
expires(Rest);
expires(<<"Thursday",$,,$\s,Rest/bits>>) ->
expires(Rest);
expires(<<"Friday",$,,$\s,Rest/bits>>) ->
expires(Rest);
expires(<<"Saturday",$,,$\s,Rest/bits>>) ->
expires(Rest);
expires(<<"Sunday",$,,$\s,Rest/bits>>) ->
expires(Rest);
expires(<<D1,D2,$\-,M1,M2,M3,$\-,Y1,Y2,Y3,Y4,$\s,Rest/bits>>) ->
expires(Rest, {list_to_integer([Y1,Y2,Y3,Y4]),month(<<M1,M2,M3>>),list_to_integer([D1,D2])});
expires(<<D1,D2,$\-,M1,M2,M3,$\-,Y3,Y4,$\s,Rest/bits>>) ->
%% http://tools.ietf.org/html/rfc2616#section-19.3
%% HTTP/1.1 clients and caches SHOULD assume that an RFC-850 date
%% which appears to be more than 50 years in the future is in fact
%% in the past (this helps solve the "year 2000" problem).
expires(Rest, {to_year([Y3, Y4]),month(<<M1,M2,M3>>),list_to_integer([D1,D2])}).
to_year(List) ->
Int = list_to_integer(List),
{Y, _, _} = date(),
case (2000 + Int - Y) > 50 of
true ->
1900 + Int;
false ->
2000 + Int
end.
expires(<<H1,H2,$:,M1,M2,$:,S1,S2,_Rest/bits>>, Date) ->
{Date, {list_to_integer([H1,H2]), list_to_integer([M1,M2]), list_to_integer([S1,S2])}}.
month(<<$J,$a,$n>>) ->
1;
month(<<$F,$e,$b>>) ->
2;
month(<<$M,$a,$r>>) ->
3;
month(<<$A,$p,$r>>) ->
4;
month(<<$M,$a,$y>>) ->
5;
month(<<$J,$u,$n>>) ->
6;
month(<<$J,$u,$l>>) ->
7;
month(<<$A,$u,$g>>) ->
8;
month(<<$S,$e,$p>>) ->
9;
month(<<$O,$c,$t>>) ->
10;
month(<<$N,$o,$v>>) ->
11;
month(<<$D,$e,$c>>) ->
12.