mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +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.
|
||||
-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 = [
|
||||
|
|
|
@ -236,6 +236,12 @@ parse_header(Name, Req, Default) when Name =:= 'Content-Length' ->
|
|||
fun (Value) ->
|
||||
cowboy_http:digits(Value)
|
||||
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) ->
|
||||
{Value, Req2} = header(Name, Req, Default),
|
||||
{undefined, Value, Req2}.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue