|
@@ -18,6 +18,7 @@
|
|
|
%% Parsing.
|
|
|
-export([list/2, nonempty_list/2,
|
|
|
media_range/2, conneg/2, digits/1,
|
|
|
+ http_date/1, rfc1123_date/1, rfc850_date/1, asctime_date/1,
|
|
|
token/2, token_ci/2, quoted_string/2]).
|
|
|
|
|
|
%% Interpretation.
|
|
@@ -194,11 +195,239 @@ conneg(Data, Fun) ->
|
|
|
end)
|
|
|
end).
|
|
|
|
|
|
-%% Parse a quality parameter string (for example q=0.500).
|
|
|
+%% @doc Parse a quality parameter string (for example q=0.500).
|
|
|
-spec qparam(binary(), fun()) -> any().
|
|
|
qparam(<< Q, $=, Data/bits >>, Fun) when Q =:= $q; Q =:= $Q ->
|
|
|
qvalue(Data, Fun).
|
|
|
|
|
|
+%% @doc Parse an HTTP date (RFC1123, RFC850 or asctime date).
|
|
|
+%% @end
|
|
|
+%%
|
|
|
+%% While this may not be the most efficient date parsing we can do,
|
|
|
+%% it should work fine for our purposes because all HTTP dates should
|
|
|
+%% be sent as RFC1123 dates in HTTP/1.1.
|
|
|
+-spec http_date(binary()) -> any().
|
|
|
+http_date(Data) ->
|
|
|
+ case rfc1123_date(Data) of
|
|
|
+ {error, badarg} ->
|
|
|
+ case rfc850_date(Data) of
|
|
|
+ {error, badarg} ->
|
|
|
+ case asctime_date(Data) of
|
|
|
+ {error, badarg} ->
|
|
|
+ {error, badarg};
|
|
|
+ HTTPDate ->
|
|
|
+ HTTPDate
|
|
|
+ end;
|
|
|
+ HTTPDate ->
|
|
|
+ HTTPDate
|
|
|
+ end;
|
|
|
+ HTTPDate ->
|
|
|
+ HTTPDate
|
|
|
+ end.
|
|
|
+
|
|
|
+%% @doc Parse an RFC1123 date.
|
|
|
+-spec rfc1123_date(binary()) -> any().
|
|
|
+rfc1123_date(Data) ->
|
|
|
+ wkday(Data,
|
|
|
+ fun (<< ", ", Rest/bits >>, _WkDay) ->
|
|
|
+ date1(Rest,
|
|
|
+ fun (<< " ", Rest2/bits >>, Date) ->
|
|
|
+ time(Rest2,
|
|
|
+ fun (<< " GMT", Rest3/bits >>, Time) ->
|
|
|
+ http_date_ret(Rest3, {Date, Time});
|
|
|
+ (_Any, _Time) ->
|
|
|
+ {error, badarg}
|
|
|
+ end);
|
|
|
+ (_Any, _Date) ->
|
|
|
+ {error, badarg}
|
|
|
+ end);
|
|
|
+ (_Any, _WkDay) ->
|
|
|
+ {error, badarg}
|
|
|
+ end).
|
|
|
+
|
|
|
+%% @doc Parse an RFC850 date.
|
|
|
+-spec rfc850_date(binary()) -> any().
|
|
|
+%% From the RFC:
|
|
|
+%% 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).
|
|
|
+rfc850_date(Data) ->
|
|
|
+ weekday(Data,
|
|
|
+ fun (<< ", ", Rest/bits >>, _WeekDay) ->
|
|
|
+ date2(Rest,
|
|
|
+ fun (<< " ", Rest2/bits >>, Date) ->
|
|
|
+ time(Rest2,
|
|
|
+ fun (<< " GMT", Rest3/bits >>, Time) ->
|
|
|
+ http_date_ret(Rest3, {Date, Time});
|
|
|
+ (_Any, _Time) ->
|
|
|
+ {error, badarg}
|
|
|
+ end);
|
|
|
+ (_Any, _Date) ->
|
|
|
+ {error, badarg}
|
|
|
+ end);
|
|
|
+ (_Any, _WeekDay) ->
|
|
|
+ {error, badarg}
|
|
|
+ end).
|
|
|
+
|
|
|
+%% @doc Parse an asctime date.
|
|
|
+-spec asctime_date(binary()) -> any().
|
|
|
+asctime_date(Data) ->
|
|
|
+ wkday(Data,
|
|
|
+ fun (<< " ", Rest/bits >>, _WkDay) ->
|
|
|
+ date3(Rest,
|
|
|
+ fun (<< " ", Rest2/bits >>, PartialDate) ->
|
|
|
+ time(Rest2,
|
|
|
+ fun (<< " ", Rest3/bits >>, Time) ->
|
|
|
+ asctime_year(Rest3,
|
|
|
+ PartialDate, Time);
|
|
|
+ (_Any, _Time) ->
|
|
|
+ {error, badarg}
|
|
|
+ end);
|
|
|
+ (_Any, _PartialDate) ->
|
|
|
+ {error, badarg}
|
|
|
+ end);
|
|
|
+ (_Any, _WkDay) ->
|
|
|
+ {error, badarg1}
|
|
|
+ end).
|
|
|
+
|
|
|
+-spec asctime_year(binary(), tuple(), tuple()) -> any().
|
|
|
+asctime_year(<< Y1, Y2, Y3, Y4, Rest/bits >>, {Month, Day}, Time)
|
|
|
+ when Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9,
|
|
|
+ Y3 >= $0, Y3 =< $9, Y4 >= $0, Y4 =< $9 ->
|
|
|
+ Year = (Y1 - $0) * 1000 + (Y2 - $0) * 100 + (Y3 - $0) * 10 + (Y4 - $0),
|
|
|
+ http_date_ret(Rest, {{Year, Month, Day}, Time}).
|
|
|
+
|
|
|
+-spec http_date_ret(binary(), tuple()) -> any().
|
|
|
+http_date_ret(Data, DateTime = {Date, _Time}) ->
|
|
|
+ whitespace(Data,
|
|
|
+ fun (<<>>) ->
|
|
|
+ case calendar:valid_date(Date) of
|
|
|
+ true -> DateTime;
|
|
|
+ false -> {error, badarg}
|
|
|
+ end;
|
|
|
+ (_Any) ->
|
|
|
+ {error, badarg}
|
|
|
+ end).
|
|
|
+
|
|
|
+%% We never use it, pretty much just checks the wkday is right.
|
|
|
+-spec wkday(binary(), fun()) -> any().
|
|
|
+wkday(<< WkDay:3/binary, Rest/bits >>, Fun)
|
|
|
+ when WkDay =:= <<"Mon">>; WkDay =:= "Tue"; WkDay =:= "Wed";
|
|
|
+ WkDay =:= <<"Thu">>; WkDay =:= "Fri"; WkDay =:= "Sat";
|
|
|
+ WkDay =:= <<"Sun">> ->
|
|
|
+ Fun(Rest, WkDay);
|
|
|
+wkday(_Any, _Fun) ->
|
|
|
+ {error, badarg}.
|
|
|
+
|
|
|
+%% We never use it, pretty much just checks the weekday is right.
|
|
|
+-spec weekday(binary(), fun()) -> any().
|
|
|
+weekday(<< "Monday", Rest/binary >>, Fun) ->
|
|
|
+ Fun(Rest, <<"Monday">>);
|
|
|
+weekday(<< "Tuesday", Rest/binary >>, Fun) ->
|
|
|
+ Fun(Rest, <<"Tuesday">>);
|
|
|
+weekday(<< "Wednesday", Rest/binary >>, Fun) ->
|
|
|
+ Fun(Rest, <<"Wednesday">>);
|
|
|
+weekday(<< "Thursday", Rest/binary >>, Fun) ->
|
|
|
+ Fun(Rest, <<"Thursday">>);
|
|
|
+weekday(<< "Friday", Rest/binary >>, Fun) ->
|
|
|
+ Fun(Rest, <<"Friday">>);
|
|
|
+weekday(<< "Saturday", Rest/binary >>, Fun) ->
|
|
|
+ Fun(Rest, <<"Saturday">>);
|
|
|
+weekday(<< "Sunday", Rest/binary >>, Fun) ->
|
|
|
+ Fun(Rest, <<"Sunday">>);
|
|
|
+weekday(_Any, _Fun) ->
|
|
|
+ {error, badarg}.
|
|
|
+
|
|
|
+-spec date1(binary(), fun()) -> any().
|
|
|
+date1(<< D1, D2, " ", M:3/binary, " ", Y1, Y2, Y3, Y4, Rest/bits >>, Fun)
|
|
|
+ when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9,
|
|
|
+ Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9,
|
|
|
+ Y3 >= $0, Y3 =< $9, Y4 >= $0, Y4 =< $9 ->
|
|
|
+ case month(M) of
|
|
|
+ {error, badarg} ->
|
|
|
+ {error, badarg};
|
|
|
+ Month ->
|
|
|
+ Fun(Rest, {
|
|
|
+ (Y1 - $0) * 1000 + (Y2 - $0) * 100 + (Y3 - $0) * 10 + (Y4 - $0),
|
|
|
+ Month,
|
|
|
+ (D1 - $0) * 10 + (D2 - $0)
|
|
|
+ })
|
|
|
+ end;
|
|
|
+date1(_Data, _Fun) ->
|
|
|
+ {error, badarg}.
|
|
|
+
|
|
|
+-spec date2(binary(), fun()) -> any().
|
|
|
+date2(<< D1, D2, "-", M:3/binary, "-", Y1, Y2, Rest/bits >>, Fun)
|
|
|
+ when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9,
|
|
|
+ Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9 ->
|
|
|
+ case month(M) of
|
|
|
+ {error, badarg} ->
|
|
|
+ {error, badarg};
|
|
|
+ Month ->
|
|
|
+ Year = (Y1 - $0) * 10 + (Y2 - $0),
|
|
|
+ Year2 = case Year > 50 of
|
|
|
+ true -> Year + 1900;
|
|
|
+ false -> Year + 2000
|
|
|
+ end,
|
|
|
+ Fun(Rest, {
|
|
|
+ Year2,
|
|
|
+ Month,
|
|
|
+ (D1 - $0) * 10 + (D2 - $0)
|
|
|
+ })
|
|
|
+ end;
|
|
|
+date2(_Data, _Fun) ->
|
|
|
+ {error, badarg}.
|
|
|
+
|
|
|
+-spec date3(binary(), fun()) -> any().
|
|
|
+date3(<< M:3/binary, " ", D1, D2, Rest/bits >>, Fun)
|
|
|
+ when (D1 >= $0 andalso D1 =< $3) orelse D1 =:= $\s,
|
|
|
+ D2 >= $0, D2 =< $9 ->
|
|
|
+ case month(M) of
|
|
|
+ {error, badarg} ->
|
|
|
+ {error, badarg};
|
|
|
+ Month ->
|
|
|
+ Day = case D1 of
|
|
|
+ $\s -> D2 - $0;
|
|
|
+ D1 -> (D1 - $0) * 10 + (D2 - $0)
|
|
|
+ end,
|
|
|
+ Fun(Rest, {Month, Day})
|
|
|
+ end;
|
|
|
+date3(_Data, _Fun) ->
|
|
|
+ {error, badarg}.
|
|
|
+
|
|
|
+-spec month(<< _:24 >>) -> 1..12 | {error, badarg}.
|
|
|
+month(<<"Jan">>) -> 1;
|
|
|
+month(<<"Feb">>) -> 2;
|
|
|
+month(<<"Mar">>) -> 3;
|
|
|
+month(<<"Apr">>) -> 4;
|
|
|
+month(<<"May">>) -> 5;
|
|
|
+month(<<"Jun">>) -> 6;
|
|
|
+month(<<"Jul">>) -> 7;
|
|
|
+month(<<"Aug">>) -> 8;
|
|
|
+month(<<"Sep">>) -> 9;
|
|
|
+month(<<"Oct">>) -> 10;
|
|
|
+month(<<"Nov">>) -> 11;
|
|
|
+month(<<"Dec">>) -> 12;
|
|
|
+month(_Any) -> {error, badarg}.
|
|
|
+
|
|
|
+-spec time(binary(), fun()) -> any().
|
|
|
+time(<< H1, H2, ":", M1, M2, ":", S1, S2, Rest/bits >>, Fun)
|
|
|
+ when H1 >= $0, H1 =< $2, H2 >= $0, H2 =< $9,
|
|
|
+ M1 >= $0, M1 =< $5, M2 >= $0, M2 =< $9,
|
|
|
+ S1 >= $0, S1 =< $5, S2 >= $0, S2 =< $9 ->
|
|
|
+ Hour = (H1 - $0) * 10 + (H2 - $0),
|
|
|
+ case Hour < 24 of
|
|
|
+ true ->
|
|
|
+ Time = {
|
|
|
+ Hour,
|
|
|
+ (M1 - $0) * 10 + (M2 - $0),
|
|
|
+ (S1 - $0) * 10 + (S2 - $0)
|
|
|
+ },
|
|
|
+ Fun(Rest, Time);
|
|
|
+ false ->
|
|
|
+ {error, badarg}
|
|
|
+ end.
|
|
|
+
|
|
|
%% @doc Skip whitespace.
|
|
|
-spec whitespace(binary(), fun()) -> any().
|
|
|
whitespace(<< C, Rest/bits >>, Fun)
|
|
@@ -300,6 +529,7 @@ qvalue(<< C, Rest/bits >>, Fun, Q, M) when ?IS_DIGIT(C) ->
|
|
|
qvalue(Data, Fun, Q, _M) ->
|
|
|
Fun(Data, Q).
|
|
|
|
|
|
+
|
|
|
%% Interpretation.
|
|
|
|
|
|
%% @doc Walk through a tokens list and return whether
|
|
@@ -385,6 +615,36 @@ media_range_list_test_() ->
|
|
|
],
|
|
|
[{V, fun() -> R = list(V, fun media_range/2) end} || {V, R} <- Tests].
|
|
|
|
|
|
+http_date_test_() ->
|
|
|
+ %% {Tokens, Result}
|
|
|
+ Tests = [
|
|
|
+ {<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}},
|
|
|
+ {<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}},
|
|
|
+ {<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}}
|
|
|
+ ],
|
|
|
+ [{V, fun() -> R = http_date(V) end} || {V, R} <- Tests].
|
|
|
+
|
|
|
+rfc1123_date_test_() ->
|
|
|
+ %% {Tokens, Result}
|
|
|
+ Tests = [
|
|
|
+ {<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}
|
|
|
+ ],
|
|
|
+ [{V, fun() -> R = rfc1123_date(V) end} || {V, R} <- Tests].
|
|
|
+
|
|
|
+rfc850_date_test_() ->
|
|
|
+ %% {Tokens, Result}
|
|
|
+ Tests = [
|
|
|
+ {<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}
|
|
|
+ ],
|
|
|
+ [{V, fun() -> R = rfc850_date(V) end} || {V, R} <- Tests].
|
|
|
+
|
|
|
+asctime_date_test_() ->
|
|
|
+ %% {Tokens, Result}
|
|
|
+ Tests = [
|
|
|
+ {<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}}
|
|
|
+ ],
|
|
|
+ [{V, fun() -> R = asctime_date(V) end} || {V, R} <- Tests].
|
|
|
+
|
|
|
connection_to_atom_test_() ->
|
|
|
%% {Tokens, Result}
|
|
|
Tests = [
|