diff --git a/src/hackney_bstr.erl b/src/hackney_bstr.erl index db5b1406..7bb516d0 100644 --- a/src/hackney_bstr.erl +++ b/src/hackney_bstr.erl @@ -21,6 +21,15 @@ word/2, trim/1]). +%% BEGIN: Remove when OTP 17 not officially supported +-export([split/3]). + +-export_type([cp/0]). + +-opaque cp() :: {'am' | 'bm', binary()}. +-type part() :: {Start :: non_neg_integer(), Length :: integer()}. +%% END: Remove when OTP 17 not officially supported + -export([quoted_string/2]). to_binary(V) when is_list(V) -> @@ -361,3 +370,80 @@ quoted_string(<< $\\, C, Rest/binary >>, Fun, Acc) -> quoted_string(Rest, Fun, << Acc/binary, C >>); quoted_string(<< C, Rest/binary >>, Fun, Acc) -> quoted_string(Rest, Fun, << Acc/binary, C >>). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% binary:split/3 from OTP 18 +%% remove when support for < 18 +%% formally dropped +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec split(Subject, Pattern, Options) -> Parts when + Subject :: binary(), + Pattern :: binary() | [binary()] | cp(), + Options :: [Option], + Option :: {scope, part()} | trim | global | trim_all, + Parts :: [binary()]. + +split(Haystack,Needles,Options) -> + try + {Part,Global,Trim,TrimAll} = + get_opts_split(Options,{no,false,false,false}), + Moptlist = case Part of + no -> + []; + {A,B} -> + [{scope,{A,B}}] + end, + MList = if + Global -> + binary:matches(Haystack,Needles,Moptlist); + true -> + case binary:match(Haystack,Needles,Moptlist) of + nomatch -> []; + Match -> [Match] + end + end, + do_split(Haystack,MList,0,Trim,TrimAll) + catch + _:_ -> + erlang:error(badarg) + end. + +do_split(H,[],N,true,_) when N >= byte_size(H) -> + []; +do_split(H,[],N,_,true) when N >= byte_size(H) -> + []; +do_split(H,[],N,_,_) -> + [binary:part(H,{N,byte_size(H)-N})]; +do_split(H,[{A,B}|T],N,Trim,TrimAll) -> + case binary:part(H,{N,A-N}) of + <<>> when TrimAll == true -> + do_split(H,T,A+B,Trim,TrimAll); + <<>> -> + Rest = do_split(H,T,A+B,Trim,TrimAll), + case {Trim, Rest} of + {true,[]} -> + []; + _ -> + [<<>> | Rest] + end; + Oth -> + [Oth | do_split(H,T,A+B,Trim,TrimAll)] + end. + +get_opts_split([],{Part,Global,Trim,TrimAll}) -> + {Part,Global,Trim,TrimAll}; +get_opts_split([{scope,{A,B}} | T],{_Part,Global,Trim,TrimAll}) -> + get_opts_split(T,{{A,B},Global,Trim,TrimAll}); +get_opts_split([global | T],{Part,_Global,Trim,TrimAll}) -> + get_opts_split(T,{Part,true,Trim,TrimAll}); +get_opts_split([trim | T],{Part,Global,_Trim,TrimAll}) -> + get_opts_split(T,{Part,Global,true,TrimAll}); +get_opts_split([trim_all | T],{Part,Global,Trim,_TrimAll}) -> + get_opts_split(T,{Part,Global,Trim,true}); +get_opts_split(_,_) -> + throw(badopt). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% END binary:split from OTP 18 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/src/hackney_url.erl b/src/hackney_url.erl index b39c1188..8972b34c 100644 --- a/src/hackney_url.erl +++ b/src/hackney_url.erl @@ -329,8 +329,8 @@ tohexl(C) when C < 16 -> $a + C - 10. parse_qs(<<>>) -> []; parse_qs(Bin) -> - Tokens = binary:split(Bin, <<"&">>, [trim, global]), - [case binary:split(Token, <<"=">>, [trim]) of + Tokens = hackney_bstr:split(Bin, <<"&">>, [trim_all, global]), + [case hackney_bstr:split(Token, <<"=">>, [trim_all]) of [T] -> {urldecode(T), true}; [Name, Value] -> diff --git a/test/hackney_url_tests.erl b/test/hackney_url_tests.erl index 548a94c4..380aaee2 100644 --- a/test/hackney_url_tests.erl +++ b/test/hackney_url_tests.erl @@ -249,7 +249,9 @@ parse_qs_test_() -> Tests = [ {<<"a=b">>, [{<<"a">>,<<"b">>}]}, {<<"a=b&c=d">>, [{<<"a">>,<<"b">>}, {<<"c">>, <<"d">>}]}, - {<<"a=b&c">>, [{<<"a">>,<<"b">>}, {<<"c">>, true}]} + {<<"&a=b&&c=d&">>, [{<<"a">>,<<"b">>}, {<<"c">>, <<"d">>}]}, + {<<"a=b&c">>, [{<<"a">>,<<"b">>}, {<<"c">>, true}]}, + {<<"&a=b&c&&">>, [{<<"a">>,<<"b">>}, {<<"c">>, true}]} ], [{V, fun() -> R = hackney_url:parse_qs(V) end} || {V, R} <- Tests].