From 8aa74cb77a672b5b0ba528df77e8b9d0e53ee8a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 25 Nov 2019 17:29:24 +0100 Subject: Reorganize cow_http_hd --- src/cow_http_hd.erl | 524 ++++++++++++++++++++++++++-------------------------- 1 file changed, 258 insertions(+), 266 deletions(-) diff --git a/src/cow_http_hd.erl b/src/cow_http_hd.erl index 3189b01..2b28c41 100644 --- a/src/cow_http_hd.erl +++ b/src/cow_http_hd.erl @@ -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. -- cgit v1.2.3