aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/cow_inline.hrl4
-rw-r--r--src/cow_http_hd.erl76
2 files changed, 80 insertions, 0 deletions
diff --git a/include/cow_inline.hrl b/include/cow_inline.hrl
index 4e45960..0fb8b3b 100644
--- a/include/cow_inline.hrl
+++ b/include/cow_inline.hrl
@@ -39,6 +39,10 @@
C =:= $5; C =:= $6; C =:= $7; C =:= $8; C =:= $9
).
+%% IS_ETAGC(Character)
+
+-define(IS_ETAGC(C), C =:= 16#21; C >= 16#23, C =/= 16#7f).
+
%% IS_TOKEN(Character)
-define(IS_TOKEN(C),
diff --git a/src/cow_http_hd.erl b/src/cow_http_hd.erl
index 8427cc7..2c08486 100644
--- a/src/cow_http_hd.erl
+++ b/src/cow_http_hd.erl
@@ -23,6 +23,7 @@
-export([parse_content_type/1]).
-export([parse_date/1]).
-export([parse_expect/1]).
+-export([parse_if_match/1]).
-export([parse_if_modified_since/1]).
-export([parse_if_unmodified_since/1]).
-export([parse_last_modified/1]).
@@ -30,6 +31,9 @@
-export([parse_transfer_encoding/1]).
-export([parse_upgrade/1]).
+-type etag() :: {weak | strong, binary()}.
+-export_type([etag/0]).
+
-type media_type() :: {binary(), binary(), [{binary(), binary()}]}.
-export_type([media_type/0]).
@@ -1013,6 +1017,78 @@ horse_parse_expect() ->
).
-endif.
+%% @doc Parse the If-Match header.
+
+-spec parse_if_match(binary()) -> '*' | [etag()].
+parse_if_match(<<"*">>) ->
+ '*';
+parse_if_match(IfMatch) ->
+ nonempty(etag_list(IfMatch, [])).
+
+etag_list(<<>>, Acc) -> lists:reverse(Acc);
+etag_list(<< $\s, R/bits >>, Acc) -> etag_list(R, Acc);
+etag_list(<< $\t, R/bits >>, Acc) -> etag_list(R, Acc);
+etag_list(<< $,, R/bits >>, Acc) -> etag_list(R, Acc);
+etag_list(<< $W, $/, $", R/bits >>, Acc) -> etagc(R, Acc, weak, <<>>);
+etag_list(<< $", R/bits >>, Acc) -> etagc(R, Acc, strong, <<>>).
+
+etagc(<< $", R/bits >>, Acc, Strength, Tag) -> etag_list_sep(R, [{Strength, Tag}|Acc]);
+etagc(<< C, R/bits >>, Acc, Strength, Tag) when ?IS_ETAGC(C) -> etagc(R, Acc, Strength, << Tag/binary, C >>).
+
+etag_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+etag_list_sep(<< $\s, R/bits >>, Acc) -> etag_list_sep(R, Acc);
+etag_list_sep(<< $\t, R/bits >>, Acc) -> etag_list_sep(R, Acc);
+etag_list_sep(<< $,, R/bits >>, Acc) -> etag_list(R, Acc).
+
+-ifdef(TEST).
+etagc() ->
+ ?SUCHTHAT(C, int(16#21, 16#ff), C =/= 16#22 andalso C =/= 16#7f).
+
+etag() ->
+ ?LET({Strength, Tag},
+ {oneof([weak, strong]), list(etagc())},
+ begin
+ TagBin = list_to_binary(Tag),
+ {{Strength, TagBin},
+ case Strength of
+ weak -> << $W, $/, $", TagBin/binary, $" >>;
+ strong -> << $", TagBin/binary, $" >>
+ end}
+ end).
+
+prop_parse_if_match() ->
+ ?FORALL(L,
+ non_empty(list(etag())),
+ begin
+ << _, IfMatch/binary >> = iolist_to_binary([[$,, T] || {_, T} <- L]),
+ ResL = parse_if_match(IfMatch),
+ CheckedL = [T =:= ResT || {{T, _}, ResT} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_if_match_test_() ->
+ Tests = [
+ {<<"\"xyzzy\"">>, [{strong, <<"xyzzy">>}]},
+ {<<"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"">>,
+ [{strong, <<"xyzzy">>}, {strong, <<"r2d2xxxx">>}, {strong, <<"c3piozzzz">>}]},
+ {<<"*">>, '*'}
+ ],
+ [{V, fun() -> R = parse_if_match(V) end} || {V, R} <- Tests].
+
+parse_if_match_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_if_match(V)) end} || V <- Tests].
+-endif.
+
+-ifdef(PERF).
+horse_parse_if_match() ->
+ horse:repeat(200000,
+ parse_if_match(<<"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"">>)
+ ).
+-endif.
+
%% @doc Parse the If-Modified-Since header.
-spec parse_if_modified_since(binary()) -> calendar:datetime().