mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 20:30:23 +00:00
Add 'If-Modified-Since' and 'If-Unmodified-Since' to parse_header/2
Implementing the full HTTP-date type (RFC1123, RFC850, asctime).
This commit is contained in:
parent
aadd974f06
commit
aba1ea4636
2 changed files with 267 additions and 1 deletions
|
@ -18,6 +18,7 @@
|
||||||
%% Parsing.
|
%% Parsing.
|
||||||
-export([list/2, nonempty_list/2,
|
-export([list/2, nonempty_list/2,
|
||||||
media_range/2, conneg/2, digits/1,
|
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]).
|
token/2, token_ci/2, quoted_string/2]).
|
||||||
|
|
||||||
%% Interpretation.
|
%% Interpretation.
|
||||||
|
@ -194,11 +195,239 @@ conneg(Data, Fun) ->
|
||||||
end)
|
end)
|
||||||
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().
|
-spec qparam(binary(), fun()) -> any().
|
||||||
qparam(<< Q, $=, Data/bits >>, Fun) when Q =:= $q; Q =:= $Q ->
|
qparam(<< Q, $=, Data/bits >>, Fun) when Q =:= $q; Q =:= $Q ->
|
||||||
qvalue(Data, Fun).
|
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.
|
%% @doc Skip whitespace.
|
||||||
-spec whitespace(binary(), fun()) -> any().
|
-spec whitespace(binary(), fun()) -> any().
|
||||||
whitespace(<< C, Rest/bits >>, Fun)
|
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) ->
|
qvalue(Data, Fun, Q, _M) ->
|
||||||
Fun(Data, Q).
|
Fun(Data, Q).
|
||||||
|
|
||||||
|
|
||||||
%% Interpretation.
|
%% Interpretation.
|
||||||
|
|
||||||
%% @doc Walk through a tokens list and return whether
|
%% @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].
|
[{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_() ->
|
connection_to_atom_test_() ->
|
||||||
%% {Tokens, Result}
|
%% {Tokens, Result}
|
||||||
Tests = [
|
Tests = [
|
||||||
|
|
|
@ -236,6 +236,12 @@ parse_header(Name, Req, Default) when Name =:= 'Content-Length' ->
|
||||||
fun (Value) ->
|
fun (Value) ->
|
||||||
cowboy_http:digits(Value)
|
cowboy_http:digits(Value)
|
||||||
end);
|
end);
|
||||||
|
parse_header(Name, Req, Default)
|
||||||
|
when Name =:= 'If-Modified-Since'; Name =:= 'If-Unmodified-Since' ->
|
||||||
|
parse_header(Name, Req, Default,
|
||||||
|
fun (Value) ->
|
||||||
|
cowboy_http:http_date(Value)
|
||||||
|
end);
|
||||||
parse_header(Name, Req, Default) ->
|
parse_header(Name, Req, Default) ->
|
||||||
{Value, Req2} = header(Name, Req, Default),
|
{Value, Req2} = header(Name, Req, Default),
|
||||||
{undefined, Value, Req2}.
|
{undefined, Value, Req2}.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue