aboutsummaryrefslogtreecommitdiffstats
path: root/src/cow_http_hd.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cow_http_hd.erl')
-rw-r--r--src/cow_http_hd.erl108
1 files changed, 64 insertions, 44 deletions
diff --git a/src/cow_http_hd.erl b/src/cow_http_hd.erl
index e2a0a1d..f0e4fba 100644
--- a/src/cow_http_hd.erl
+++ b/src/cow_http_hd.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2014-2018, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2014-2023, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
@@ -1169,7 +1169,8 @@ cache_directive(<< $=, $", R/bits >>, Acc, T)
cache_directive_fields_list(R, Acc, T, []);
cache_directive(<< $=, C, R/bits >>, Acc, T)
when ?IS_DIGIT(C), (T =:= <<"max-age">>) or (T =:= <<"max-stale">>)
- or (T =:= <<"min-fresh">>) or (T =:= <<"s-maxage">>) ->
+ or (T =:= <<"min-fresh">>) or (T =:= <<"s-maxage">>)
+ or (T =:= <<"stale-while-revalidate">>) or (T =:= <<"stale-if-error">>) ->
cache_directive_delta(R, Acc, T, (C - $0));
cache_directive(<< $=, $", R/bits >>, Acc, T) -> cache_directive_quoted_string(R, Acc, T, <<>>);
cache_directive(<< $=, C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> cache_directive_token(R, Acc, T, << C >>);
@@ -1211,14 +1212,18 @@ cache_directive_unreserved_token() ->
?SUCHTHAT(T,
token(),
T =/= <<"max-age">> andalso T =/= <<"max-stale">> andalso T =/= <<"min-fresh">>
- andalso T =/= <<"s-maxage">> andalso T =/= <<"no-cache">> andalso T =/= <<"private">>).
+ andalso T =/= <<"s-maxage">> andalso T =/= <<"no-cache">> andalso T =/= <<"private">>
+ andalso T =/= <<"stale-while-revalidate">> andalso T =/= <<"stale-if-error">>).
cache_directive() ->
oneof([
token(),
{cache_directive_unreserved_token(), token()},
{cache_directive_unreserved_token(), quoted_string()},
- {elements([<<"max-age">>, <<"max-stale">>, <<"min-fresh">>, <<"s-maxage">>]), non_neg_integer()},
+ {elements([
+ <<"max-age">>, <<"max-stale">>, <<"min-fresh">>, <<"s-maxage">>,
+ <<"stale-while-revalidate">>, <<"stale-if-error">>
+ ]), non_neg_integer()},
{fields, elements([<<"no-cache">>, <<"private">>]), small_list(token())}
]).
@@ -1260,7 +1265,13 @@ parse_cache_control_test_() ->
{<<"max-age=30">>, [{<<"max-age">>, 30}]},
{<<"private, community=\"UCI\"">>, [<<"private">>, {<<"community">>, <<"UCI">>}]},
{<<"private=\"Content-Type, Content-Encoding, Content-Language\"">>,
- [{<<"private">>, [<<"content-type">>, <<"content-encoding">>, <<"content-language">>]}]}
+ [{<<"private">>, [<<"content-type">>, <<"content-encoding">>, <<"content-language">>]}]},
+ %% RFC5861 3.1.
+ {<<"max-age=600, stale-while-revalidate=30">>,
+ [{<<"max-age">>, 600}, {<<"stale-while-revalidate">>, 30}]},
+ %% RFC5861 4.1.
+ {<<"max-age=600, stale-if-error=1200">>,
+ [{<<"max-age">>, 600}, {<<"stale-if-error">>, 1200}]}
],
[{V, fun() -> R = parse_cache_control(V) end} || {V, R} <- Tests].
@@ -3227,11 +3238,11 @@ parse_upgrade_error_test_() ->
parse_variant_key(VariantKey, NumMembers) ->
List = cow_http_struct_hd:parse_list(VariantKey),
[case Inner of
- {with_params, InnerList, #{}} ->
+ {list, InnerList, []} ->
NumMembers = length(InnerList),
[case Item of
- {with_params, {token, Value}, #{}} -> Value;
- {with_params, {string, Value}, #{}} -> Value
+ {item, {token, Value}, []} -> Value;
+ {item, {string, Value}, []} -> Value
end || Item <- InnerList]
end || Inner <- List].
@@ -3261,9 +3272,9 @@ parse_variant_key_error_test_() ->
%% We assume that the lists are of correct length.
variant_key(VariantKeys) ->
cow_http_struct_hd:list([
- {with_params, [
- {with_params, {string, Value}, #{}}
- || Value <- InnerList], #{}}
+ {list, [
+ {item, {string, Value}, []}
+ || Value <- InnerList], []}
|| InnerList <- VariantKeys]).
-ifdef(TEST).
@@ -3287,14 +3298,14 @@ variant_key_identity_test_() ->
-spec parse_variants(binary()) -> [{binary(), [binary()]}].
parse_variants(Variants) ->
- {Dict0, Order} = cow_http_struct_hd:parse_dictionary(Variants),
- Dict = maps:map(fun(_, {with_params, List, #{}}) ->
- [case Item of
- {with_params, {token, Value}, #{}} -> Value;
- {with_params, {string, Value}, #{}} -> Value
- end || Item <- List]
- end, Dict0),
- [{Key, maps:get(Key, Dict)} || Key <- Order].
+ Dict = cow_http_struct_hd:parse_dictionary(Variants),
+ [case DictItem of
+ {Key, {list, List, []}} ->
+ {Key, [case Item of
+ {item, {token, Value}, []} -> Value;
+ {item, {string, Value}, []} -> Value
+ end || Item <- List]}
+ end || DictItem <- Dict].
-ifdef(TEST).
parse_variants_test_() ->
@@ -3317,9 +3328,9 @@ parse_variants_test_() ->
-spec variants([{binary(), [binary()]}]) -> iolist().
variants(Variants) ->
cow_http_struct_hd:dictionary([
- {Key, {with_params, [
- {with_params, {string, Value}, #{}}
- || Value <- List], #{}}}
+ {Key, {list, [
+ {item, {string, Value}, []}
+ || Value <- List], []}}
|| {Key, List} <- Variants]).
-ifdef(TEST).
@@ -3383,29 +3394,19 @@ www_auth_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> www_auth_list(R, Acc
www_auth_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) ->
?LOWER(www_auth_scheme, R, Acc, <<>>).
-www_auth_basic_before_realm(<< C, R/bits >>, Acc) when ?IS_WS(C) -> www_auth_basic_before_realm(R, Acc);
-www_auth_basic_before_realm(<< "realm=\"", R/bits >>, Acc) -> www_auth_basic(R, Acc, <<>>).
-
-www_auth_basic(<< $", R/bits >>, Acc, Realm) -> www_auth_list_sep(R, [{basic, Realm}|Acc]);
-www_auth_basic(<< $\\, C, R/bits >>, Acc, Realm) when ?IS_VCHAR_OBS(C) -> www_auth_basic(R, Acc, << Realm/binary, C >>);
-www_auth_basic(<< C, R/bits >>, Acc, Realm) when ?IS_VCHAR_OBS(C) -> www_auth_basic(R, Acc, << Realm/binary, C >>).
-
-www_auth_scheme(<< C, R/bits >>, Acc, Scheme) when ?IS_WS(C) ->
- case Scheme of
- <<"basic">> -> www_auth_basic_before_realm(R, Acc);
- <<"bearer">> -> www_auth_params_list(R, Acc, bearer, []);
- <<"digest">> -> www_auth_params_list(R, Acc, digest, []);
- _ -> www_auth_params_list(R, Acc, Scheme, [])
- end;
+www_auth_scheme(<< C, R/bits >>, Acc, Scheme0) when ?IS_WS(C) ->
+ Scheme = case Scheme0 of
+ <<"basic">> -> basic;
+ <<"bearer">> -> bearer;
+ <<"digest">> -> digest;
+ _ -> Scheme0
+ end,
+ www_auth_params_list(R, Acc, Scheme, []);
www_auth_scheme(<< C, R/bits >>, Acc, Scheme) when ?IS_TOKEN(C) ->
?LOWER(www_auth_scheme, R, Acc, Scheme).
-www_auth_list_sep(<<>>, Acc) -> lists:reverse(Acc);
-www_auth_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> www_auth_list_sep(R, Acc);
-www_auth_list_sep(<< $,, R/bits >>, Acc) -> www_auth_list(R, Acc).
-
www_auth_params_list(<<>>, Acc, Scheme, Params) ->
- lists:reverse([{Scheme, lists:reverse(nonempty(Params))}|Acc]);
+ lists:reverse([www_auth_tuple(Scheme, nonempty(Params))|Acc]);
www_auth_params_list(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_WS_COMMA(C) ->
www_auth_params_list(R, Acc, Scheme, Params);
www_auth_params_list(<< "algorithm=", C, R/bits >>, Acc, Scheme, Params) when ?IS_TOKEN(C) ->
@@ -3442,7 +3443,7 @@ www_auth_param(<< $=, C, R/bits >>, Acc, Scheme, Params, K) when ?IS_TOKEN(C) ->
www_auth_param(<< C, R/bits >>, Acc, Scheme, Params, K) when ?IS_TOKEN(C) ->
?LOWER(www_auth_param, R, Acc, Scheme, Params, K);
www_auth_param(R, Acc, Scheme, Params, NewScheme) ->
- www_auth_scheme(R, [{Scheme, lists:reverse(Params)}|Acc], NewScheme).
+ www_auth_scheme(R, [www_auth_tuple(Scheme, Params)|Acc], NewScheme).
www_auth_token(<< C, R/bits >>, Acc, Scheme, Params, K, V) when ?IS_TOKEN(C) ->
www_auth_token(R, Acc, Scheme, Params, K, << V/binary, C >>);
@@ -3457,19 +3458,26 @@ www_auth_quoted(<< C, R/bits >>, Acc, Scheme, Params, K, V) when ?IS_VCHAR_OBS(C
www_auth_quoted(R, Acc, Scheme, Params, K, << V/binary, C >>).
www_auth_params_list_sep(<<>>, Acc, Scheme, Params) ->
- lists:reverse([{Scheme, lists:reverse(Params)}|Acc]);
+ lists:reverse([www_auth_tuple(Scheme, Params)|Acc]);
www_auth_params_list_sep(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_WS(C) ->
www_auth_params_list_sep(R, Acc, Scheme, Params);
www_auth_params_list_sep(<< $,, R/bits >>, Acc, Scheme, Params) ->
www_auth_params_list_after_sep(R, Acc, Scheme, Params).
www_auth_params_list_after_sep(<<>>, Acc, Scheme, Params) ->
- lists:reverse([{Scheme, lists:reverse(Params)}|Acc]);
+ lists:reverse([www_auth_tuple(Scheme, Params)|Acc]);
www_auth_params_list_after_sep(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_WS_COMMA(C) ->
www_auth_params_list_after_sep(R, Acc, Scheme, Params);
www_auth_params_list_after_sep(R, Acc, Scheme, Params) ->
www_auth_params_list(R, Acc, Scheme, Params).
+www_auth_tuple(basic, Params) ->
+ %% Unknown parameters MUST be ignored. (RFC7617 2)
+ {<<"realm">>, Realm} = lists:keyfind(<<"realm">>, 1, Params),
+ {basic, Realm};
+www_auth_tuple(Scheme, Params) ->
+ {Scheme, lists:reverse(Params)}.
+
-ifdef(TEST).
parse_www_authenticate_test_() ->
Tests = [
@@ -3496,6 +3504,18 @@ parse_www_authenticate_test_() ->
]}]},
{<<"Basic realm=\"WallyWorld\"">>,
[{basic, <<"WallyWorld">>}]},
+ %% RFC7617 2.1.
+ {<<"Basic realm=\"foo\", charset=\"UTF-8\"">>,
+ [{basic, <<"foo">>}]},
+ %% A real-world example.
+ {<<"Basic realm=\"https://123456789012.dkr.ecr.eu-north-1.amazonaws.com/\",service=\"ecr.amazonaws.com\"">>,
+ [{basic, <<"https://123456789012.dkr.ecr.eu-north-1.amazonaws.com/">>}]},
+ {<<"Bearer realm=\"example\", Basic realm=\"foo\", charset=\"UTF-8\"">>,
+ [{bearer, [{<<"realm">>, <<"example">>}]},
+ {basic, <<"foo">>}]},
+ {<<"Basic realm=\"foo\", foo=\"bar\", charset=\"UTF-8\", Bearer realm=\"example\",foo=\"bar\"">>,
+ [{basic, <<"foo">>},
+ {bearer, [{<<"realm">>, <<"example">>}, {<<"foo">>,<<"bar">>}]}]},
{<<"Digest realm=\"[email protected]\", qop=\"auth,auth-int\", "
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
"opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"">>,