aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/cowboy_http.erl262
-rw-r--r--src/cowboy_http_req.erl6
2 files changed, 267 insertions, 1 deletions
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl
index a6fec74..aea75d3 100644
--- a/src/cowboy_http.erl
+++ b/src/cowboy_http.erl
@@ -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 = [
diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl
index 911aca1..2b17f9c 100644
--- a/src/cowboy_http_req.erl
+++ b/src/cowboy_http_req.erl
@@ -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}.