|
@@ -14,7 +14,9 @@
|
|
|
|
|
|
-module(cow_http_hd).
|
|
|
|
|
|
-%% Parsing.
|
|
|
+%% Functions are ordered by header name, with the parse
|
|
|
+%% function before the build function.
|
|
|
+
|
|
|
-export([parse_accept/1]).
|
|
|
-export([parse_accept_charset/1]).
|
|
|
% @todo -export([parse_accept_datetime/1]). RFC7089
|
|
@@ -23,11 +25,17 @@
|
|
|
-export([parse_accept_language/1]).
|
|
|
-export([parse_accept_ranges/1]).
|
|
|
% @todo -export([parse_access_control_allow_credentials/1]). CORS
|
|
|
+-export([access_control_allow_credentials/0]).
|
|
|
% @todo -export([parse_access_control_allow_headers/1]). CORS
|
|
|
+-export([access_control_allow_headers/1]).
|
|
|
% @todo -export([parse_access_control_allow_methods/1]). CORS
|
|
|
+-export([access_control_allow_methods/1]).
|
|
|
% @todo -export([parse_access_control_allow_origin/1]). CORS
|
|
|
+-export([access_control_allow_origin/1]).
|
|
|
% @todo -export([parse_access_control_expose_headers/1]). CORS
|
|
|
+-export([access_control_expose_headers/1]).
|
|
|
% @todo -export([parse_access_control_max_age/1]). CORS
|
|
|
+-export([access_control_max_age/1]).
|
|
|
-export([parse_access_control_request_headers/1]).
|
|
|
-export([parse_access_control_request_method/1]).
|
|
|
-export([parse_age/1]).
|
|
@@ -102,7 +110,9 @@
|
|
|
% @todo -export([parse_user_agent/1]). RFC7231
|
|
|
% @todo -export([parse_variant_vary/1]). RFC2295
|
|
|
-export([parse_variant_key/2]).
|
|
|
+-export([variant_key/1]).
|
|
|
-export([parse_variants/1]).
|
|
|
+-export([variants/1]).
|
|
|
-export([parse_vary/1]).
|
|
|
% @todo -export([parse_via/1]). RFC7230
|
|
|
% @todo -export([parse_want_digest/1]). RFC3230
|
|
@@ -113,16 +123,6 @@
|
|
|
-export([parse_x_forwarded_for/1]).
|
|
|
% @todo -export([parse_x_frame_options/1]). RFC7034
|
|
|
|
|
|
-%% Building.
|
|
|
--export([access_control_allow_credentials/0]).
|
|
|
--export([access_control_allow_headers/1]).
|
|
|
--export([access_control_allow_methods/1]).
|
|
|
--export([access_control_allow_origin/1]).
|
|
|
--export([access_control_expose_headers/1]).
|
|
|
--export([access_control_max_age/1]).
|
|
|
--export([variant_key/1]).
|
|
|
--export([variants/1]).
|
|
|
-
|
|
|
-type etag() :: {weak | strong, binary()}.
|
|
|
-export_type([etag/0]).
|
|
|
|
|
@@ -216,9 +216,7 @@ qvalue_to_iodata(Q) when Q < 1000 -> [<<"0.">>, integer_to_binary(Q)];
|
|
|
qvalue_to_iodata(1000) -> <<"1">>.
|
|
|
-endif.
|
|
|
|
|
|
-%% Parsing.
|
|
|
-
|
|
|
-%% @doc Parse the Accept header.
|
|
|
+%% Accept header.
|
|
|
|
|
|
-spec parse_accept(binary()) -> [{media_type(), qvalue(), [binary() | {binary(), binary()}]}].
|
|
|
parse_accept(<<"*/*">>) ->
|
|
@@ -438,7 +436,7 @@ horse_parse_accept() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Accept-Charset header.
|
|
|
+%% Accept-Charset header.
|
|
|
|
|
|
-spec parse_accept_charset(binary()) -> [{binary(), qvalue()}].
|
|
|
parse_accept_charset(Charset) ->
|
|
@@ -529,7 +527,7 @@ horse_parse_accept_charset() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Accept-Encoding header.
|
|
|
+%% Accept-Encoding header.
|
|
|
|
|
|
-spec parse_accept_encoding(binary()) -> [{binary(), qvalue()}].
|
|
|
parse_accept_encoding(Encoding) ->
|
|
@@ -585,7 +583,7 @@ horse_parse_accept_encoding() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Accept-Language header.
|
|
|
+%% Accept-Language header.
|
|
|
|
|
|
-spec parse_accept_language(binary()) -> [{binary(), qvalue()}].
|
|
|
parse_accept_language(LanguageRange) ->
|
|
@@ -702,7 +700,7 @@ horse_parse_accept_language() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Accept-Ranges header.
|
|
|
+%% Accept-Ranges header.
|
|
|
|
|
|
-spec parse_accept_ranges(binary()) -> [binary()].
|
|
|
parse_accept_ranges(<<"none">>) -> [];
|
|
@@ -741,7 +739,148 @@ horse_parse_accept_ranges_other() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Access-Control-Request-Headers header.
|
|
|
+%% Access-Control-Allow-Credentials header.
|
|
|
+
|
|
|
+-spec access_control_allow_credentials() -> iodata().
|
|
|
+access_control_allow_credentials() -> <<"true">>.
|
|
|
+
|
|
|
+%% Access-Control-Allow-Headers header.
|
|
|
+
|
|
|
+-spec access_control_allow_headers([binary()]) -> iodata().
|
|
|
+access_control_allow_headers(Headers) ->
|
|
|
+ join_token_list(nonempty(Headers)).
|
|
|
+
|
|
|
+-ifdef(TEST).
|
|
|
+access_control_allow_headers_test_() ->
|
|
|
+ Tests = [
|
|
|
+ {[<<"accept">>], <<"accept">>},
|
|
|
+ {[<<"accept">>, <<"authorization">>, <<"content-type">>], <<"accept, authorization, content-type">>}
|
|
|
+ ],
|
|
|
+ [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
+ fun() -> R = iolist_to_binary(access_control_allow_headers(V)) end} || {V, R} <- Tests].
|
|
|
+
|
|
|
+access_control_allow_headers_error_test_() ->
|
|
|
+ Tests = [
|
|
|
+ []
|
|
|
+ ],
|
|
|
+ [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
+ fun() -> {'EXIT', _} = (catch access_control_allow_headers(V)) end} || V <- Tests].
|
|
|
+
|
|
|
+horse_access_control_allow_headers() ->
|
|
|
+ horse:repeat(200000,
|
|
|
+ access_control_allow_headers([<<"accept">>, <<"authorization">>, <<"content-type">>])
|
|
|
+ ).
|
|
|
+-endif.
|
|
|
+
|
|
|
+%% Access-Control-Allow-Methods header.
|
|
|
+
|
|
|
+-spec access_control_allow_methods([binary()]) -> iodata().
|
|
|
+access_control_allow_methods(Methods) ->
|
|
|
+ join_token_list(nonempty(Methods)).
|
|
|
+
|
|
|
+-ifdef(TEST).
|
|
|
+access_control_allow_methods_test_() ->
|
|
|
+ Tests = [
|
|
|
+ {[<<"GET">>], <<"GET">>},
|
|
|
+ {[<<"GET">>, <<"POST">>, <<"DELETE">>], <<"GET, POST, DELETE">>}
|
|
|
+ ],
|
|
|
+ [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
+ fun() -> R = iolist_to_binary(access_control_allow_methods(V)) end} || {V, R} <- Tests].
|
|
|
+
|
|
|
+access_control_allow_methods_error_test_() ->
|
|
|
+ Tests = [
|
|
|
+ []
|
|
|
+ ],
|
|
|
+ [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
+ fun() -> {'EXIT', _} = (catch access_control_allow_methods(V)) end} || V <- Tests].
|
|
|
+
|
|
|
+horse_access_control_allow_methods() ->
|
|
|
+ horse:repeat(200000,
|
|
|
+ access_control_allow_methods([<<"GET">>, <<"POST">>, <<"DELETE">>])
|
|
|
+ ).
|
|
|
+-endif.
|
|
|
+
|
|
|
+%% Access-Control-Allow-Origin header.
|
|
|
+
|
|
|
+-spec access_control_allow_origin({binary(), binary(), 0..65535} | reference() | '*') -> iodata().
|
|
|
+access_control_allow_origin({Scheme, Host, Port}) ->
|
|
|
+ case default_port(Scheme) of
|
|
|
+ Port -> [Scheme, <<"://">>, Host];
|
|
|
+ _ -> [Scheme, <<"://">>, Host, <<":">>, integer_to_binary(Port)]
|
|
|
+ end;
|
|
|
+access_control_allow_origin('*') -> <<$*>>;
|
|
|
+access_control_allow_origin(Ref) when is_reference(Ref) -> <<"null">>.
|
|
|
+
|
|
|
+-ifdef(TEST).
|
|
|
+access_control_allow_origin_test_() ->
|
|
|
+ Tests = [
|
|
|
+ {{<<"http">>, <<"www.example.org">>, 8080}, <<"http://www.example.org:8080">>},
|
|
|
+ {{<<"http">>, <<"www.example.org">>, 80}, <<"http://www.example.org">>},
|
|
|
+ {{<<"http">>, <<"192.0.2.1">>, 8080}, <<"http://192.0.2.1:8080">>},
|
|
|
+ {{<<"http">>, <<"192.0.2.1">>, 80}, <<"http://192.0.2.1">>},
|
|
|
+ {{<<"http">>, <<"[2001:db8::1]">>, 8080}, <<"http://[2001:db8::1]:8080">>},
|
|
|
+ {{<<"http">>, <<"[2001:db8::1]">>, 80}, <<"http://[2001:db8::1]">>},
|
|
|
+ {{<<"http">>, <<"[::ffff:192.0.2.1]">>, 8080}, <<"http://[::ffff:192.0.2.1]:8080">>},
|
|
|
+ {{<<"http">>, <<"[::ffff:192.0.2.1]">>, 80}, <<"http://[::ffff:192.0.2.1]">>},
|
|
|
+ {make_ref(), <<"null">>},
|
|
|
+ {'*', <<$*>>}
|
|
|
+ ],
|
|
|
+ [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
+ fun() -> R = iolist_to_binary(access_control_allow_origin(V)) end} || {V, R} <- Tests].
|
|
|
+
|
|
|
+horse_access_control_allow_origin() ->
|
|
|
+ horse:repeat(200000,
|
|
|
+ access_control_allow_origin({<<"http">>, <<"example.org">>, 8080})
|
|
|
+ ).
|
|
|
+-endif.
|
|
|
+
|
|
|
+%% Access-Control-Expose-Headers header.
|
|
|
+
|
|
|
+-spec access_control_expose_headers([binary()]) -> iodata().
|
|
|
+access_control_expose_headers(Headers) ->
|
|
|
+ join_token_list(nonempty(Headers)).
|
|
|
+
|
|
|
+-ifdef(TEST).
|
|
|
+access_control_expose_headers_test_() ->
|
|
|
+ Tests = [
|
|
|
+ {[<<"accept">>], <<"accept">>},
|
|
|
+ {[<<"accept">>, <<"authorization">>, <<"content-type">>], <<"accept, authorization, content-type">>}
|
|
|
+ ],
|
|
|
+ [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
+ fun() -> R = iolist_to_binary(access_control_expose_headers(V)) end} || {V, R} <- Tests].
|
|
|
+
|
|
|
+access_control_expose_headers_error_test_() ->
|
|
|
+ Tests = [
|
|
|
+ []
|
|
|
+ ],
|
|
|
+ [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
+ fun() -> {'EXIT', _} = (catch access_control_expose_headers(V)) end} || V <- Tests].
|
|
|
+
|
|
|
+horse_access_control_expose_headers() ->
|
|
|
+ horse:repeat(200000,
|
|
|
+ access_control_expose_headers([<<"accept">>, <<"authorization">>, <<"content-type">>])
|
|
|
+ ).
|
|
|
+-endif.
|
|
|
+
|
|
|
+%% Access-Control-Max-Age header.
|
|
|
+
|
|
|
+-spec access_control_max_age(non_neg_integer()) -> iodata().
|
|
|
+access_control_max_age(MaxAge) -> integer_to_binary(MaxAge).
|
|
|
+
|
|
|
+-ifdef(TEST).
|
|
|
+access_control_max_age_test_() ->
|
|
|
+ Tests = [
|
|
|
+ {0, <<"0">>},
|
|
|
+ {42, <<"42">>},
|
|
|
+ {69, <<"69">>},
|
|
|
+ {1337, <<"1337">>},
|
|
|
+ {3495, <<"3495">>},
|
|
|
+ {1234567890, <<"1234567890">>}
|
|
|
+ ],
|
|
|
+ [{V, fun() -> R = access_control_max_age(V) end} || {V, R} <- Tests].
|
|
|
+-endif.
|
|
|
+
|
|
|
+%% Access-Control-Request-Headers header.
|
|
|
|
|
|
-spec parse_access_control_request_headers(binary()) -> [binary()].
|
|
|
parse_access_control_request_headers(Headers) ->
|
|
@@ -778,7 +917,7 @@ horse_parse_access_control_request_headers() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Access-Control-Request-Method header.
|
|
|
+%% Access-Control-Request-Method header.
|
|
|
|
|
|
-spec parse_access_control_request_method(binary()) -> binary().
|
|
|
parse_access_control_request_method(Method) ->
|
|
@@ -815,7 +954,7 @@ horse_parse_access_control_request_method() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Age header.
|
|
|
+%% Age header.
|
|
|
|
|
|
-spec parse_age(binary()) -> non_neg_integer().
|
|
|
parse_age(Age) ->
|
|
@@ -844,7 +983,7 @@ parse_age_error_test_() ->
|
|
|
[{V, fun() -> {'EXIT', _} = (catch parse_age(V)) end} || V <- Tests].
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Allow header.
|
|
|
+%% Allow header.
|
|
|
|
|
|
-spec parse_allow(binary()) -> [binary()].
|
|
|
parse_allow(Allow) ->
|
|
@@ -879,7 +1018,7 @@ horse_parse_allow() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Authorization header.
|
|
|
+%% Authorization header.
|
|
|
%%
|
|
|
%% We support Basic, Digest and Bearer schemes only.
|
|
|
%%
|
|
@@ -1007,7 +1146,7 @@ horse_parse_authorization_digest() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Cache-Control header.
|
|
|
+%% Cache-Control header.
|
|
|
%%
|
|
|
%% In the fields list case, we do not support escaping, which shouldn't be needed anyway.
|
|
|
|
|
@@ -1157,7 +1296,7 @@ horse_parse_cache_control_fields() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Connection header.
|
|
|
+%% Connection header.
|
|
|
|
|
|
-spec parse_connection(binary()) -> [binary()].
|
|
|
parse_connection(<<"close">>) ->
|
|
@@ -1209,7 +1348,7 @@ horse_parse_connection_keepalive_upgrade() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Content-Encoding header.
|
|
|
+%% Content-Encoding header.
|
|
|
|
|
|
-spec parse_content_encoding(binary()) -> [binary()].
|
|
|
parse_content_encoding(ContentEncoding) ->
|
|
@@ -1234,7 +1373,7 @@ horse_parse_content_encoding() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Content-Language header.
|
|
|
+%% Content-Language header.
|
|
|
%%
|
|
|
%% We do not support irregular deprecated tags that do not match the ABNF.
|
|
|
|
|
@@ -1520,7 +1659,7 @@ horse_parse_content_language() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Content-Length header.
|
|
|
+%% Content-Length header.
|
|
|
|
|
|
-spec parse_content_length(binary()) -> non_neg_integer().
|
|
|
parse_content_length(ContentLength) ->
|
|
@@ -1567,7 +1706,7 @@ horse_parse_content_length_giga() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Content-Range header.
|
|
|
+%% Content-Range header.
|
|
|
|
|
|
-spec parse_content_range(binary())
|
|
|
-> {bytes, non_neg_integer(), non_neg_integer(), non_neg_integer() | '*'}
|
|
@@ -1667,7 +1806,7 @@ horse_parse_content_range_other() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Content-Type header.
|
|
|
+%% Content-Type header.
|
|
|
|
|
|
-spec parse_content_type(binary()) -> media_type().
|
|
|
parse_content_type(<< C, R/bits >>) when ?IS_TOKEN(C) ->
|
|
@@ -1775,7 +1914,7 @@ horse_parse_content_type() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Date header.
|
|
|
+%% Date header.
|
|
|
|
|
|
-spec parse_date(binary()) -> calendar:datetime().
|
|
|
parse_date(Date) ->
|
|
@@ -1789,7 +1928,7 @@ parse_date_test_() ->
|
|
|
[{V, fun() -> R = parse_date(V) end} || {V, R} <- Tests].
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the ETag header.
|
|
|
+%% ETag header.
|
|
|
|
|
|
-spec parse_etag(binary()) -> etag().
|
|
|
parse_etag(<< $W, $/, $", R/bits >>) ->
|
|
@@ -1846,7 +1985,7 @@ horse_parse_etag() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Expect header.
|
|
|
+%% Expect header.
|
|
|
|
|
|
-spec parse_expect(binary()) -> continue.
|
|
|
parse_expect(<<"100-continue">>) ->
|
|
@@ -1894,7 +2033,7 @@ horse_parse_expect() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Expires header.
|
|
|
+%% Expires header.
|
|
|
%%
|
|
|
%% Recipients must interpret invalid date formats as a date
|
|
|
%% in the past. The value "0" is commonly used.
|
|
@@ -1929,7 +2068,7 @@ horse_parse_expires_invalid() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Host header.
|
|
|
+%% Host header.
|
|
|
%%
|
|
|
%% We only seek to have legal characters and separate the
|
|
|
%% host and port values. The number of segments in the host
|
|
@@ -2013,13 +2152,13 @@ horse_parse_host_ipv6_v4() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the HTTP2-Settings header.
|
|
|
+%% HTTP2-Settings header.
|
|
|
|
|
|
-spec parse_http2_settings(binary()) -> map().
|
|
|
parse_http2_settings(HTTP2Settings) ->
|
|
|
cow_http2:parse_settings_payload(base64:decode(HTTP2Settings)).
|
|
|
|
|
|
-%% @doc Parse the If-Match header.
|
|
|
+%% If-Match header.
|
|
|
|
|
|
-spec parse_if_match(binary()) -> '*' | [etag()].
|
|
|
parse_if_match(<<"*">>) ->
|
|
@@ -2071,7 +2210,7 @@ horse_parse_if_match() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the If-Modified-Since header.
|
|
|
+%% If-Modified-Since header.
|
|
|
|
|
|
-spec parse_if_modified_since(binary()) -> calendar:datetime().
|
|
|
parse_if_modified_since(IfModifiedSince) ->
|
|
@@ -2085,7 +2224,7 @@ parse_if_modified_since_test_() ->
|
|
|
[{V, fun() -> R = parse_if_modified_since(V) end} || {V, R} <- Tests].
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the If-None-Match header.
|
|
|
+%% If-None-Match header.
|
|
|
|
|
|
-spec parse_if_none_match(binary()) -> '*' | [etag()].
|
|
|
parse_if_none_match(<<"*">>) ->
|
|
@@ -2118,7 +2257,7 @@ horse_parse_if_none_match() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the If-Range header.
|
|
|
+%% If-Range header.
|
|
|
|
|
|
-spec parse_if_range(binary()) -> etag() | calendar:datetime().
|
|
|
parse_if_range(<< $W, $/, $", R/bits >>) ->
|
|
@@ -2154,7 +2293,7 @@ horse_parse_if_range_date() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the If-Unmodified-Since header.
|
|
|
+%% If-Unmodified-Since header.
|
|
|
|
|
|
-spec parse_if_unmodified_since(binary()) -> calendar:datetime().
|
|
|
parse_if_unmodified_since(IfModifiedSince) ->
|
|
@@ -2168,7 +2307,7 @@ parse_if_unmodified_since_test_() ->
|
|
|
[{V, fun() -> R = parse_if_unmodified_since(V) end} || {V, R} <- Tests].
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Last-Modified header.
|
|
|
+%% Last-Modified header.
|
|
|
|
|
|
-spec parse_last_modified(binary()) -> calendar:datetime().
|
|
|
parse_last_modified(LastModified) ->
|
|
@@ -2182,13 +2321,13 @@ parse_last_modified_test_() ->
|
|
|
[{V, fun() -> R = parse_last_modified(V) end} || {V, R} <- Tests].
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Link header.
|
|
|
+%% Link header.
|
|
|
|
|
|
-spec parse_link(binary()) -> [cow_link:link()].
|
|
|
parse_link(Link) ->
|
|
|
cow_link:parse_link(Link).
|
|
|
|
|
|
-%% @doc Parse the Max-Forwards header.
|
|
|
+%% Max-Forwards header.
|
|
|
|
|
|
-spec parse_max_forwards(binary()) -> non_neg_integer().
|
|
|
parse_max_forwards(MaxForwards) ->
|
|
@@ -2223,7 +2362,7 @@ parse_max_forwards_error_test_() ->
|
|
|
[{V, fun() -> {'EXIT', _} = (catch parse_max_forwards(V)) end} || V <- Tests].
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Origin header.
|
|
|
+%% Origin header.
|
|
|
|
|
|
%% According to the RFC6454 we should generate
|
|
|
%% a fresh globally unique identifier and return that value if:
|
|
@@ -2365,7 +2504,7 @@ horse_parse_origin_null() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Pragma header.
|
|
|
+%% Pragma header.
|
|
|
%%
|
|
|
%% Legacy header kept for backward compatibility with HTTP/1.0 caches.
|
|
|
%% Only the "no-cache" directive was ever specified, and only for
|
|
@@ -2378,7 +2517,7 @@ horse_parse_origin_null() ->
|
|
|
parse_pragma(<<"no-cache">>) -> no_cache;
|
|
|
parse_pragma(_) -> cache.
|
|
|
|
|
|
-%% @doc Parse the Proxy-Authenticate header.
|
|
|
+%% Proxy-Authenticate header.
|
|
|
%%
|
|
|
%% Alias of parse_www_authenticate/1 due to identical syntax.
|
|
|
|
|
@@ -2387,7 +2526,7 @@ parse_pragma(_) -> cache.
|
|
|
parse_proxy_authenticate(ProxyAuthenticate) ->
|
|
|
parse_www_authenticate(ProxyAuthenticate).
|
|
|
|
|
|
-%% @doc Parse the Proxy-Authorization header.
|
|
|
+%% Proxy-Authorization header.
|
|
|
%%
|
|
|
%% Alias of parse_authorization/1 due to identical syntax.
|
|
|
|
|
@@ -2398,7 +2537,7 @@ parse_proxy_authenticate(ProxyAuthenticate) ->
|
|
|
parse_proxy_authorization(ProxyAuthorization) ->
|
|
|
parse_authorization(ProxyAuthorization).
|
|
|
|
|
|
-%% @doc Parse the Range header.
|
|
|
+%% Range header.
|
|
|
|
|
|
-spec parse_range(binary())
|
|
|
-> {bytes, [{non_neg_integer(), non_neg_integer() | infinity} | neg_integer()]}
|
|
@@ -2522,7 +2661,7 @@ horse_parse_range_other() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Retry-After header.
|
|
|
+%% Retry-After header.
|
|
|
|
|
|
-spec parse_retry_after(binary()) -> non_neg_integer() | calendar:datetime().
|
|
|
parse_retry_after(RetryAfter = << D, _/bits >>) when ?IS_DIGIT(D) ->
|
|
@@ -2557,7 +2696,7 @@ horse_parse_retry_after_delay_seconds() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Dummy parsing function for the Sec-WebSocket-Accept header.
|
|
|
+%% Sec-WebSocket-Accept header.
|
|
|
%%
|
|
|
%% The argument is returned without any processing. This value is
|
|
|
%% expected to be matched directly by the client so no parsing is
|
|
@@ -2567,7 +2706,7 @@ horse_parse_retry_after_delay_seconds() ->
|
|
|
parse_sec_websocket_accept(SecWebSocketAccept) ->
|
|
|
SecWebSocketAccept.
|
|
|
|
|
|
-%% @doc Parse the Sec-WebSocket-Extensions request header.
|
|
|
+%% Sec-WebSocket-Extensions header.
|
|
|
|
|
|
-spec parse_sec_websocket_extensions(binary()) -> [{binary(), [binary() | {binary(), binary()}]}].
|
|
|
parse_sec_websocket_extensions(SecWebSocketExtensions) ->
|
|
@@ -2660,7 +2799,7 @@ horse_parse_sec_websocket_extensions() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Dummy parsing function for the Sec-WebSocket-Key header.
|
|
|
+%% Sec-WebSocket-Key header.
|
|
|
%%
|
|
|
%% The argument is returned without any processing. This value is
|
|
|
%% expected to be prepended to a static value, the result of which
|
|
@@ -2671,7 +2810,7 @@ horse_parse_sec_websocket_extensions() ->
|
|
|
parse_sec_websocket_key(SecWebSocketKey) ->
|
|
|
SecWebSocketKey.
|
|
|
|
|
|
-%% @doc Parse the Sec-WebSocket-Protocol request header.
|
|
|
+%% Sec-WebSocket-Protocol request header.
|
|
|
|
|
|
-spec parse_sec_websocket_protocol_req(binary()) -> [binary()].
|
|
|
parse_sec_websocket_protocol_req(SecWebSocketProtocol) ->
|
|
@@ -2698,7 +2837,7 @@ horse_parse_sec_websocket_protocol_req() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Sec-Websocket-Protocol response header.
|
|
|
+%% Sec-Websocket-Protocol response header.
|
|
|
|
|
|
-spec parse_sec_websocket_protocol_resp(binary()) -> binary().
|
|
|
parse_sec_websocket_protocol_resp(Protocol) ->
|
|
@@ -2732,7 +2871,7 @@ horse_parse_sec_websocket_protocol_resp() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Sec-WebSocket-Version request header.
|
|
|
+%% Sec-WebSocket-Version request header.
|
|
|
|
|
|
-spec parse_sec_websocket_version_req(binary()) -> websocket_version().
|
|
|
parse_sec_websocket_version_req(SecWebSocketVersion) when byte_size(SecWebSocketVersion) < 4 ->
|
|
@@ -2774,7 +2913,7 @@ horse_parse_sec_websocket_version_req_255() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Sec-WebSocket-Version response header.
|
|
|
+%% Sec-WebSocket-Version response header.
|
|
|
|
|
|
-spec parse_sec_websocket_version_resp(binary()) -> [websocket_version()].
|
|
|
parse_sec_websocket_version_resp(SecWebSocketVersion) ->
|
|
@@ -2825,7 +2964,7 @@ horse_parse_sec_websocket_version_resp() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the TE header.
|
|
|
+%% TE header.
|
|
|
%%
|
|
|
%% This function does not support parsing of transfer-parameter.
|
|
|
|
|
@@ -2922,7 +3061,7 @@ horse_parse_te() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Trailer header.
|
|
|
+%% Trailer header.
|
|
|
|
|
|
-spec parse_trailer(binary()) -> [binary()].
|
|
|
parse_trailer(Trailer) ->
|
|
@@ -2947,7 +3086,7 @@ horse_parse_trailer() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Transfer-Encoding header.
|
|
|
+%% Transfer-Encoding header.
|
|
|
%%
|
|
|
%% This function does not support parsing of transfer-parameter.
|
|
|
|
|
@@ -3001,7 +3140,7 @@ horse_parse_transfer_encoding_custom() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Upgrade header.
|
|
|
+%% Upgrade header.
|
|
|
%%
|
|
|
%% It is unclear from the RFC whether the values here are
|
|
|
%% case sensitive.
|
|
@@ -3064,7 +3203,7 @@ parse_upgrade_error_test_() ->
|
|
|
|| V <- Tests].
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Variant-Key header.
|
|
|
+%% Variant-Key-06 (draft) header.
|
|
|
%%
|
|
|
%% The Variants header must be parsed first in order to know
|
|
|
%% the NumMembers argument as it is the number of members in
|
|
@@ -3104,7 +3243,33 @@ parse_variant_key_error_test_() ->
|
|
|
[{V, fun() -> {'EXIT', _} = (catch parse_variant_key(V, N)) end} || {V, N} <- Tests].
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Variants header.
|
|
|
+-spec variant_key([[binary()]]) -> iolist().
|
|
|
+%% 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], #{}}
|
|
|
+ || InnerList <- VariantKeys]).
|
|
|
+
|
|
|
+-ifdef(TEST).
|
|
|
+variant_key_identity_test_() ->
|
|
|
+ Tests = [
|
|
|
+ {1, [[<<"en">>]]},
|
|
|
+ {2, [[<<"gzip">>, <<"fr">>]]},
|
|
|
+ {2, [[<<"gzip">>, <<"fr">>], [<<"identity">>, <<"fr">>]]},
|
|
|
+ {2, [[<<"gzip ">>, <<"fr">>]]},
|
|
|
+ {2, [[<<"en">>, <<"br">>]]},
|
|
|
+ {1, [[<<"0">>]]},
|
|
|
+ {1, [[<<"silver">>], [<<"bronze">>]]},
|
|
|
+ {1, [[<<"some_person">>]]},
|
|
|
+ {2, [[<<"gold">>, <<"europe">>]]}
|
|
|
+ ],
|
|
|
+ [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
+ fun() -> V = parse_variant_key(iolist_to_binary(variant_key(V)), N) end} || {N, V} <- Tests].
|
|
|
+-endif.
|
|
|
+
|
|
|
+%% Variants-06 (draft) header.
|
|
|
|
|
|
-spec parse_variants(binary()) -> [{binary(), [binary()]}].
|
|
|
parse_variants(Variants) ->
|
|
@@ -3135,7 +3300,34 @@ parse_variants_test_() ->
|
|
|
[{V, fun() -> R = parse_variants(V) end} || {V, R} <- Tests].
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the Vary header.
|
|
|
+-spec variants([{binary(), [binary()]}]) -> iolist().
|
|
|
+variants(Variants) ->
|
|
|
+ cow_http_struct_hd:dictionary([
|
|
|
+ {Key, {with_params, [
|
|
|
+ {with_params, {string, Value}, #{}}
|
|
|
+ || Value <- List], #{}}}
|
|
|
+ || {Key, List} <- Variants]).
|
|
|
+
|
|
|
+-ifdef(TEST).
|
|
|
+variants_identity_test_() ->
|
|
|
+ Tests = [
|
|
|
+ [{<<"accept-language">>, [<<"de">>, <<"en">>, <<"jp">>]}],
|
|
|
+ [{<<"accept-encoding">>, [<<"gzip">>]}],
|
|
|
+ [{<<"accept-encoding">>, []}],
|
|
|
+ [
|
|
|
+ {<<"accept-encoding">>, [<<"gzip">>, <<"br">>]},
|
|
|
+ {<<"accept-language">>, [<<"en">>, <<"fr">>]}
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {<<"accept-language">>, [<<"en">>, <<"fr">>, <<"de">>]},
|
|
|
+ {<<"accept-encoding">>, [<<"gzip">>, <<"br">>]}
|
|
|
+ ]
|
|
|
+ ],
|
|
|
+ [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
+ fun() -> V = parse_variants(iolist_to_binary(variants(V))) end} || V <- Tests].
|
|
|
+-endif.
|
|
|
+
|
|
|
+%% Vary header.
|
|
|
|
|
|
-spec parse_vary(binary()) -> '*' | [binary()].
|
|
|
parse_vary(<<"*">>) ->
|
|
@@ -3159,7 +3351,7 @@ parse_vary_error_test_() ->
|
|
|
[{V, fun() -> {'EXIT', _} = (catch parse_vary(V)) end} || V <- Tests].
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the WWW-Authenticate header.
|
|
|
+%% WWW-Authenticate header.
|
|
|
%%
|
|
|
%% Unknown schemes are represented as the lowercase binary
|
|
|
%% instead of an atom. Unlike with parse_authorization/1,
|
|
@@ -3314,7 +3506,7 @@ horse_parse_www_authenticate() ->
|
|
|
).
|
|
|
-endif.
|
|
|
|
|
|
-%% @doc Parse the X-Forwarded-For header.
|
|
|
+%% X-Forwarded-For header.
|
|
|
%%
|
|
|
%% This header has no specification but *looks like* it is
|
|
|
%% a list of tokens.
|
|
@@ -3380,206 +3572,6 @@ parse_x_forwarded_for_error_test_() ->
|
|
|
[{V, fun() -> {'EXIT', _} = (catch parse_x_forwarded_for(V)) end} || V <- Tests].
|
|
|
-endif.
|
|
|
|
|
|
-%% Building.
|
|
|
-
|
|
|
-%% @doc Build the Access-Control-Allow-Credentials header.
|
|
|
-
|
|
|
--spec access_control_allow_credentials() -> iodata().
|
|
|
-access_control_allow_credentials() -> <<"true">>.
|
|
|
-
|
|
|
-%% @doc Build the Access-Control-Allow-Headers header.
|
|
|
-
|
|
|
--spec access_control_allow_headers([binary()]) -> iodata().
|
|
|
-access_control_allow_headers(Headers) ->
|
|
|
- join_token_list(nonempty(Headers)).
|
|
|
-
|
|
|
--ifdef(TEST).
|
|
|
-access_control_allow_headers_test_() ->
|
|
|
- Tests = [
|
|
|
- {[<<"accept">>], <<"accept">>},
|
|
|
- {[<<"accept">>, <<"authorization">>, <<"content-type">>], <<"accept, authorization, content-type">>}
|
|
|
- ],
|
|
|
- [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
- fun() -> R = iolist_to_binary(access_control_allow_headers(V)) end} || {V, R} <- Tests].
|
|
|
-
|
|
|
-access_control_allow_headers_error_test_() ->
|
|
|
- Tests = [
|
|
|
- []
|
|
|
- ],
|
|
|
- [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
- fun() -> {'EXIT', _} = (catch access_control_allow_headers(V)) end} || V <- Tests].
|
|
|
-
|
|
|
-horse_access_control_allow_headers() ->
|
|
|
- horse:repeat(200000,
|
|
|
- access_control_allow_headers([<<"accept">>, <<"authorization">>, <<"content-type">>])
|
|
|
- ).
|
|
|
--endif.
|
|
|
-
|
|
|
-%% @doc Build the Access-Control-Allow-Methods header.
|
|
|
-
|
|
|
--spec access_control_allow_methods([binary()]) -> iodata().
|
|
|
-access_control_allow_methods(Methods) ->
|
|
|
- join_token_list(nonempty(Methods)).
|
|
|
-
|
|
|
--ifdef(TEST).
|
|
|
-access_control_allow_methods_test_() ->
|
|
|
- Tests = [
|
|
|
- {[<<"GET">>], <<"GET">>},
|
|
|
- {[<<"GET">>, <<"POST">>, <<"DELETE">>], <<"GET, POST, DELETE">>}
|
|
|
- ],
|
|
|
- [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
- fun() -> R = iolist_to_binary(access_control_allow_methods(V)) end} || {V, R} <- Tests].
|
|
|
-
|
|
|
-access_control_allow_methods_error_test_() ->
|
|
|
- Tests = [
|
|
|
- []
|
|
|
- ],
|
|
|
- [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
- fun() -> {'EXIT', _} = (catch access_control_allow_methods(V)) end} || V <- Tests].
|
|
|
-
|
|
|
-horse_access_control_allow_methods() ->
|
|
|
- horse:repeat(200000,
|
|
|
- access_control_allow_methods([<<"GET">>, <<"POST">>, <<"DELETE">>])
|
|
|
- ).
|
|
|
--endif.
|
|
|
-
|
|
|
-%% @doc Build the Access-Control-Allow-Origin header.
|
|
|
-
|
|
|
--spec access_control_allow_origin({binary(), binary(), 0..65535} | reference() | '*') -> iodata().
|
|
|
-access_control_allow_origin({Scheme, Host, Port}) ->
|
|
|
- case default_port(Scheme) of
|
|
|
- Port -> [Scheme, <<"://">>, Host];
|
|
|
- _ -> [Scheme, <<"://">>, Host, <<":">>, integer_to_binary(Port)]
|
|
|
- end;
|
|
|
-access_control_allow_origin('*') -> <<$*>>;
|
|
|
-access_control_allow_origin(Ref) when is_reference(Ref) -> <<"null">>.
|
|
|
-
|
|
|
--ifdef(TEST).
|
|
|
-access_control_allow_origin_test_() ->
|
|
|
- Tests = [
|
|
|
- {{<<"http">>, <<"www.example.org">>, 8080}, <<"http://www.example.org:8080">>},
|
|
|
- {{<<"http">>, <<"www.example.org">>, 80}, <<"http://www.example.org">>},
|
|
|
- {{<<"http">>, <<"192.0.2.1">>, 8080}, <<"http://192.0.2.1:8080">>},
|
|
|
- {{<<"http">>, <<"192.0.2.1">>, 80}, <<"http://192.0.2.1">>},
|
|
|
- {{<<"http">>, <<"[2001:db8::1]">>, 8080}, <<"http://[2001:db8::1]:8080">>},
|
|
|
- {{<<"http">>, <<"[2001:db8::1]">>, 80}, <<"http://[2001:db8::1]">>},
|
|
|
- {{<<"http">>, <<"[::ffff:192.0.2.1]">>, 8080}, <<"http://[::ffff:192.0.2.1]:8080">>},
|
|
|
- {{<<"http">>, <<"[::ffff:192.0.2.1]">>, 80}, <<"http://[::ffff:192.0.2.1]">>},
|
|
|
- {make_ref(), <<"null">>},
|
|
|
- {'*', <<$*>>}
|
|
|
- ],
|
|
|
- [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
- fun() -> R = iolist_to_binary(access_control_allow_origin(V)) end} || {V, R} <- Tests].
|
|
|
-
|
|
|
-horse_access_control_allow_origin() ->
|
|
|
- horse:repeat(200000,
|
|
|
- access_control_allow_origin({<<"http">>, <<"example.org">>, 8080})
|
|
|
- ).
|
|
|
--endif.
|
|
|
-
|
|
|
-%% @doc Build the Access-Control-Expose-Headers header.
|
|
|
-
|
|
|
--spec access_control_expose_headers([binary()]) -> iodata().
|
|
|
-access_control_expose_headers(Headers) ->
|
|
|
- join_token_list(nonempty(Headers)).
|
|
|
-
|
|
|
--ifdef(TEST).
|
|
|
-access_control_expose_headers_test_() ->
|
|
|
- Tests = [
|
|
|
- {[<<"accept">>], <<"accept">>},
|
|
|
- {[<<"accept">>, <<"authorization">>, <<"content-type">>], <<"accept, authorization, content-type">>}
|
|
|
- ],
|
|
|
- [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
- fun() -> R = iolist_to_binary(access_control_expose_headers(V)) end} || {V, R} <- Tests].
|
|
|
-
|
|
|
-access_control_expose_headers_error_test_() ->
|
|
|
- Tests = [
|
|
|
- []
|
|
|
- ],
|
|
|
- [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
- fun() -> {'EXIT', _} = (catch access_control_expose_headers(V)) end} || V <- Tests].
|
|
|
-
|
|
|
-horse_access_control_expose_headers() ->
|
|
|
- horse:repeat(200000,
|
|
|
- access_control_expose_headers([<<"accept">>, <<"authorization">>, <<"content-type">>])
|
|
|
- ).
|
|
|
--endif.
|
|
|
-
|
|
|
-%% @doc Build the Access-Control-Max-Age header.
|
|
|
-
|
|
|
--spec access_control_max_age(non_neg_integer()) -> iodata().
|
|
|
-access_control_max_age(MaxAge) -> integer_to_binary(MaxAge).
|
|
|
-
|
|
|
--ifdef(TEST).
|
|
|
-access_control_max_age_test_() ->
|
|
|
- Tests = [
|
|
|
- {0, <<"0">>},
|
|
|
- {42, <<"42">>},
|
|
|
- {69, <<"69">>},
|
|
|
- {1337, <<"1337">>},
|
|
|
- {3495, <<"3495">>},
|
|
|
- {1234567890, <<"1234567890">>}
|
|
|
- ],
|
|
|
- [{V, fun() -> R = access_control_max_age(V) end} || {V, R} <- Tests].
|
|
|
--endif.
|
|
|
-
|
|
|
-%% @doc Build the Variant-Key-06 (draft) header.
|
|
|
-
|
|
|
--spec variant_key([[binary()]]) -> iolist().
|
|
|
-%% 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], #{}}
|
|
|
- || InnerList <- VariantKeys]).
|
|
|
-
|
|
|
--ifdef(TEST).
|
|
|
-variant_key_identity_test_() ->
|
|
|
- Tests = [
|
|
|
- {1, [[<<"en">>]]},
|
|
|
- {2, [[<<"gzip">>, <<"fr">>]]},
|
|
|
- {2, [[<<"gzip">>, <<"fr">>], [<<"identity">>, <<"fr">>]]},
|
|
|
- {2, [[<<"gzip ">>, <<"fr">>]]},
|
|
|
- {2, [[<<"en">>, <<"br">>]]},
|
|
|
- {1, [[<<"0">>]]},
|
|
|
- {1, [[<<"silver">>], [<<"bronze">>]]},
|
|
|
- {1, [[<<"some_person">>]]},
|
|
|
- {2, [[<<"gold">>, <<"europe">>]]}
|
|
|
- ],
|
|
|
- [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
- fun() -> V = parse_variant_key(iolist_to_binary(variant_key(V)), N) end} || {N, V} <- Tests].
|
|
|
--endif.
|
|
|
-
|
|
|
-%% @doc Build the Variants-06 (draft) header.
|
|
|
-
|
|
|
--spec variants([{binary(), [binary()]}]) -> iolist().
|
|
|
-variants(Variants) ->
|
|
|
- cow_http_struct_hd:dictionary([
|
|
|
- {Key, {with_params, [
|
|
|
- {with_params, {string, Value}, #{}}
|
|
|
- || Value <- List], #{}}}
|
|
|
- || {Key, List} <- Variants]).
|
|
|
-
|
|
|
--ifdef(TEST).
|
|
|
-variants_identity_test_() ->
|
|
|
- Tests = [
|
|
|
- [{<<"accept-language">>, [<<"de">>, <<"en">>, <<"jp">>]}],
|
|
|
- [{<<"accept-encoding">>, [<<"gzip">>]}],
|
|
|
- [{<<"accept-encoding">>, []}],
|
|
|
- [
|
|
|
- {<<"accept-encoding">>, [<<"gzip">>, <<"br">>]},
|
|
|
- {<<"accept-language">>, [<<"en">>, <<"fr">>]}
|
|
|
- ],
|
|
|
- [
|
|
|
- {<<"accept-language">>, [<<"en">>, <<"fr">>, <<"de">>]},
|
|
|
- {<<"accept-encoding">>, [<<"gzip">>, <<"br">>]}
|
|
|
- ]
|
|
|
- ],
|
|
|
- [{lists:flatten(io_lib:format("~p", [V])),
|
|
|
- fun() -> V = parse_variants(iolist_to_binary(variants(V))) end} || V <- Tests].
|
|
|
--endif.
|
|
|
-
|
|
|
%% Internal.
|
|
|
|
|
|
%% Only return if the list is not empty.
|