1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357 |
- %% Copyright (c) 2011-2013, Loïc Hoguin <essen@ninenines.eu>
- %% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
- %%
- %% Permission to use, copy, modify, and/or distribute this software for any
- %% purpose with or without fee is hereby granted, provided that the above
- %% copyright notice and this permission notice appear in all copies.
- %%
- %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- %% @doc Core HTTP parsing API.
- -module(cowboy_http).
- %% Parsing.
- -export([list/2]).
- -export([nonempty_list/2]).
- -export([cookie_list/1]).
- -export([content_type/1]).
- -export([media_range/2]).
- -export([conneg/2]).
- -export([language_range/2]).
- -export([entity_tag_match/1]).
- -export([expectation/2]).
- -export([params/2]).
- -export([http_date/1]).
- -export([rfc1123_date/1]).
- -export([rfc850_date/1]).
- -export([asctime_date/1]).
- -export([whitespace/2]).
- -export([digits/1]).
- -export([token/2]).
- -export([token_ci/2]).
- -export([quoted_string/2]).
- -export([authorization/2]).
- %% Decoding.
- -export([te_chunked/2]).
- -export([te_identity/2]).
- -export([ce_identity/1]).
- %% Interpretation.
- -export([cookie_to_iodata/3]).
- -export([version_to_binary/1]).
- -export([urldecode/1]).
- -export([urldecode/2]).
- -export([urlencode/1]).
- -export([urlencode/2]).
- -export([x_www_form_urlencoded/1]).
- -type version() :: {Major::non_neg_integer(), Minor::non_neg_integer()}.
- -type headers() :: [{binary(), iodata()}].
- -type status() :: non_neg_integer() | binary().
- -export_type([version/0]).
- -export_type([headers/0]).
- -export_type([status/0]).
- -ifdef(TEST).
- -include_lib("eunit/include/eunit.hrl").
- -endif.
- %% Parsing.
- %% @doc Parse a non-empty list of the given type.
- -spec nonempty_list(binary(), fun()) -> [any(), ...] | {error, badarg}.
- nonempty_list(Data, Fun) ->
- case list(Data, Fun, []) of
- {error, badarg} -> {error, badarg};
- [] -> {error, badarg};
- L -> lists:reverse(L)
- end.
- %% @doc Parse a list of the given type.
- -spec list(binary(), fun()) -> list() | {error, badarg}.
- list(Data, Fun) ->
- case list(Data, Fun, []) of
- {error, badarg} -> {error, badarg};
- L -> lists:reverse(L)
- end.
- -spec list(binary(), fun(), [binary()]) -> [any()] | {error, badarg}.
- %% From the RFC:
- %% <blockquote>Wherever this construct is used, null elements are allowed,
- %% but do not contribute to the count of elements present.
- %% That is, "(element), , (element) " is permitted, but counts
- %% as only two elements. Therefore, where at least one element is required,
- %% at least one non-null element MUST be present.</blockquote>
- list(Data, Fun, Acc) ->
- whitespace(Data,
- fun (<<>>) -> Acc;
- (<< $,, Rest/binary >>) -> list(Rest, Fun, Acc);
- (Rest) -> Fun(Rest,
- fun (D, I) -> whitespace(D,
- fun (<<>>) -> [I|Acc];
- (<< $,, R/binary >>) -> list(R, Fun, [I|Acc]);
- (_Any) -> {error, badarg}
- end)
- end)
- end).
- %% @doc Parse a list of cookies.
- %%
- %% We need a special function for this because we need to support both
- %% $; and $, as separators as per RFC2109.
- -spec cookie_list(binary()) -> [{binary(), binary()}] | {error, badarg}.
- cookie_list(Data) ->
- case cookie_list(Data, []) of
- {error, badarg} -> {error, badarg};
- [] -> {error, badarg};
- L -> lists:reverse(L)
- end.
- -spec cookie_list(binary(), Acc) -> Acc | {error, badarg}
- when Acc::[{binary(), binary()}].
- cookie_list(Data, Acc) ->
- whitespace(Data,
- fun (<<>>) -> Acc;
- (<< $,, Rest/binary >>) -> cookie_list(Rest, Acc);
- (<< $;, Rest/binary >>) -> cookie_list(Rest, Acc);
- (Rest) -> cookie(Rest,
- fun (Rest2, << $$, _/bits >>, _) ->
- cookie_list(Rest2, Acc);
- (Rest2, Name, Value) ->
- cookie_list(Rest2, [{Name, Value}|Acc])
- end)
- end).
- -spec cookie(binary(), fun()) -> any().
- cookie(Data, Fun) ->
- whitespace(Data,
- fun (Rest) ->
- cookie_name(Rest,
- fun (_Rest2, <<>>) -> {error, badarg};
- (<< $=, Rest2/binary >>, Name) ->
- cookie_value(Rest2,
- fun (Rest3, Value) ->
- Fun(Rest3, Name, Value)
- end);
- (_Rest2, _Attr) -> {error, badarg}
- end)
- end).
- -spec cookie_name(binary(), fun()) -> any().
- cookie_name(Data, Fun) ->
- cookie_name(Data, Fun, <<>>).
- -spec cookie_name(binary(), fun(), binary()) -> any().
- cookie_name(<<>>, Fun, Acc) ->
- Fun(<<>>, Acc);
- cookie_name(Data = << C, _Rest/binary >>, Fun, Acc)
- when C =:= $=; C =:= $,; C =:= $;; C =:= $\s; C =:= $\t;
- C =:= $\r; C =:= $\n; C =:= $\013; C =:= $\014 ->
- Fun(Data, Acc);
- cookie_name(<< C, Rest/binary >>, Fun, Acc) ->
- C2 = cowboy_bstr:char_to_lower(C),
- cookie_name(Rest, Fun, << Acc/binary, C2 >>).
- -spec cookie_value(binary(), fun()) -> any().
- cookie_value(Data, Fun) ->
- cookie_value(Data, Fun, <<>>).
- -spec cookie_value(binary(), fun(), binary()) -> any().
- cookie_value(<<>>, Fun, Acc) ->
- Fun(<<>>, Acc);
- cookie_value(Data = << C, _Rest/binary >>, Fun, Acc)
- when C =:= $,; C =:= $;; C =:= $\s; C =:= $\t;
- C =:= $\r; C =:= $\n; C =:= $\013; C =:= $\014 ->
- Fun(Data, Acc);
- cookie_value(<< C, Rest/binary >>, Fun, Acc) ->
- cookie_value(Rest, Fun, << Acc/binary, C >>).
- %% @doc Parse a content type.
- -spec content_type(binary()) -> any().
- content_type(Data) ->
- media_type(Data,
- fun (Rest, Type, SubType) ->
- params(Rest,
- fun (<<>>, Params) -> {Type, SubType, Params};
- (_Rest2, _) -> {error, badarg}
- end)
- end).
- %% @doc Parse a media range.
- -spec media_range(binary(), fun()) -> any().
- media_range(Data, Fun) ->
- media_type(Data,
- fun (Rest, Type, SubType) ->
- media_range_params(Rest, Fun, Type, SubType, [])
- end).
- -spec media_range_params(binary(), fun(), binary(), binary(),
- [{binary(), binary()}]) -> any().
- media_range_params(Data, Fun, Type, SubType, Acc) ->
- whitespace(Data,
- fun (<< $;, Rest/binary >>) ->
- whitespace(Rest,
- fun (Rest2) ->
- media_range_param_attr(Rest2, Fun, Type, SubType, Acc)
- end);
- (Rest) -> Fun(Rest, {{Type, SubType, lists:reverse(Acc)}, 1000, []})
- end).
- -spec media_range_param_attr(binary(), fun(), binary(), binary(),
- [{binary(), binary()}]) -> any().
- media_range_param_attr(Data, Fun, Type, SubType, Acc) ->
- token_ci(Data,
- fun (_Rest, <<>>) -> {error, badarg};
- (<< $=, Rest/binary >>, Attr) ->
- media_range_param_value(Rest, Fun, Type, SubType, Acc, Attr)
- end).
- -spec media_range_param_value(binary(), fun(), binary(), binary(),
- [{binary(), binary()}], binary()) -> any().
- media_range_param_value(Data, Fun, Type, SubType, Acc, <<"q">>) ->
- qvalue(Data,
- fun (Rest, Quality) ->
- accept_ext(Rest, Fun, Type, SubType, Acc, Quality, [])
- end);
- media_range_param_value(Data, Fun, Type, SubType, Acc, Attr) ->
- word(Data,
- fun (Rest, Value) ->
- media_range_params(Rest, Fun,
- Type, SubType, [{Attr, Value}|Acc])
- end).
- %% @doc Parse a media type.
- -spec media_type(binary(), fun()) -> any().
- media_type(Data, Fun) ->
- token_ci(Data,
- fun (_Rest, <<>>) -> {error, badarg};
- (<< $/, Rest/binary >>, Type) ->
- token_ci(Rest,
- fun (_Rest2, <<>>) -> {error, badarg};
- (Rest2, SubType) -> Fun(Rest2, Type, SubType)
- end);
- %% This is a non-strict parsing clause required by some user agents
- %% that use * instead of */* in the list of media types.
- (Rest, <<"*">> = Type) ->
- token_ci(<<"*", Rest/binary>>,
- fun (_Rest2, <<>>) -> {error, badarg};
- (Rest2, SubType) -> Fun(Rest2, Type, SubType)
- end);
- (_Rest, _Type) -> {error, badarg}
- end).
- -spec accept_ext(binary(), fun(), binary(), binary(),
- [{binary(), binary()}], 0..1000,
- [{binary(), binary()} | binary()]) -> any().
- accept_ext(Data, Fun, Type, SubType, Params, Quality, Acc) ->
- whitespace(Data,
- fun (<< $;, Rest/binary >>) ->
- whitespace(Rest,
- fun (Rest2) ->
- accept_ext_attr(Rest2, Fun,
- Type, SubType, Params, Quality, Acc)
- end);
- (Rest) ->
- Fun(Rest, {{Type, SubType, lists:reverse(Params)},
- Quality, lists:reverse(Acc)})
- end).
- -spec accept_ext_attr(binary(), fun(), binary(), binary(),
- [{binary(), binary()}], 0..1000,
- [{binary(), binary()} | binary()]) -> any().
- accept_ext_attr(Data, Fun, Type, SubType, Params, Quality, Acc) ->
- token_ci(Data,
- fun (_Rest, <<>>) -> {error, badarg};
- (<< $=, Rest/binary >>, Attr) ->
- accept_ext_value(Rest, Fun, Type, SubType, Params,
- Quality, Acc, Attr);
- (Rest, Attr) ->
- accept_ext(Rest, Fun, Type, SubType, Params,
- Quality, [Attr|Acc])
- end).
- -spec accept_ext_value(binary(), fun(), binary(), binary(),
- [{binary(), binary()}], 0..1000,
- [{binary(), binary()} | binary()], binary()) -> any().
- accept_ext_value(Data, Fun, Type, SubType, Params, Quality, Acc, Attr) ->
- word(Data,
- fun (Rest, Value) ->
- accept_ext(Rest, Fun,
- Type, SubType, Params, Quality, [{Attr, Value}|Acc])
- end).
- %% @doc Parse a conneg header (Accept-Charset, Accept-Encoding),
- %% followed by an optional quality value.
- -spec conneg(binary(), fun()) -> any().
- conneg(Data, Fun) ->
- token_ci(Data,
- fun (_Rest, <<>>) -> {error, badarg};
- (Rest, Conneg) ->
- maybe_qparam(Rest,
- fun (Rest2, Quality) ->
- Fun(Rest2, {Conneg, Quality})
- end)
- end).
- %% @doc Parse a language range, followed by an optional quality value.
- -spec language_range(binary(), fun()) -> any().
- language_range(<< $*, Rest/binary >>, Fun) ->
- language_range_ret(Rest, Fun, '*');
- language_range(Data, Fun) ->
- language_tag(Data,
- fun (Rest, LanguageTag) ->
- language_range_ret(Rest, Fun, LanguageTag)
- end).
- -spec language_range_ret(binary(), fun(), '*' | {binary(), [binary()]}) -> any().
- language_range_ret(Data, Fun, LanguageTag) ->
- maybe_qparam(Data,
- fun (Rest, Quality) ->
- Fun(Rest, {LanguageTag, Quality})
- end).
- -spec language_tag(binary(), fun()) -> any().
- language_tag(Data, Fun) ->
- alpha(Data,
- fun (_Rest, Tag) when byte_size(Tag) =:= 0; byte_size(Tag) > 8 ->
- {error, badarg};
- (<< $-, Rest/binary >>, Tag) ->
- language_subtag(Rest, Fun, Tag, []);
- (Rest, Tag) ->
- Fun(Rest, Tag)
- end).
- -spec language_subtag(binary(), fun(), binary(), [binary()]) -> any().
- language_subtag(Data, Fun, Tag, Acc) ->
- alpha(Data,
- fun (_Rest, SubTag) when byte_size(SubTag) =:= 0;
- byte_size(SubTag) > 8 -> {error, badarg};
- (<< $-, Rest/binary >>, SubTag) ->
- language_subtag(Rest, Fun, Tag, [SubTag|Acc]);
- (Rest, SubTag) ->
- %% Rebuild the full tag now that we know it's correct
- Sub = << << $-, S/binary >> || S <- lists:reverse([SubTag|Acc]) >>,
- Fun(Rest, << Tag/binary, Sub/binary >>)
- end).
- -spec maybe_qparam(binary(), fun()) -> any().
- maybe_qparam(Data, Fun) ->
- whitespace(Data,
- fun (<< $;, Rest/binary >>) ->
- whitespace(Rest,
- fun (Rest2) ->
- %% This is a non-strict parsing clause required by some user agents
- %% that use the wrong delimiter putting a charset where a qparam is
- %% expected.
- try qparam(Rest2, Fun) of
- Result -> Result
- catch
- error:function_clause ->
- Fun(<<",", Rest2/binary>>, 1000)
- end
- end);
- (Rest) ->
- Fun(Rest, 1000)
- end).
- %% @doc Parse a quality parameter string (for example q=0.500).
- -spec qparam(binary(), fun()) -> any().
- qparam(<< Q, $=, Data/binary >>, Fun) when Q =:= $q; Q =:= $Q ->
- qvalue(Data, Fun).
- %% @doc Parse either a list of entity tags or a "*".
- -spec entity_tag_match(binary()) -> any().
- entity_tag_match(<< $*, Rest/binary >>) ->
- whitespace(Rest,
- fun (<<>>) -> '*';
- (_Any) -> {error, badarg}
- end);
- entity_tag_match(Data) ->
- nonempty_list(Data, fun entity_tag/2).
- %% @doc Parse an entity-tag.
- -spec entity_tag(binary(), fun()) -> any().
- entity_tag(<< "W/", Rest/binary >>, Fun) ->
- opaque_tag(Rest, Fun, weak);
- entity_tag(Data, Fun) ->
- opaque_tag(Data, Fun, strong).
- -spec opaque_tag(binary(), fun(), weak | strong) -> any().
- opaque_tag(Data, Fun, Strength) ->
- quoted_string(Data,
- fun (_Rest, <<>>) -> {error, badarg};
- (Rest, OpaqueTag) -> Fun(Rest, {Strength, OpaqueTag})
- end).
- %% @doc Parse an expectation.
- -spec expectation(binary(), fun()) -> any().
- expectation(Data, Fun) ->
- token_ci(Data,
- fun (_Rest, <<>>) -> {error, badarg};
- (<< $=, Rest/binary >>, Expectation) ->
- word(Rest,
- fun (Rest2, ExtValue) ->
- params(Rest2, fun (Rest3, ExtParams) ->
- Fun(Rest3, {Expectation, ExtValue, ExtParams})
- end)
- end);
- (Rest, Expectation) ->
- Fun(Rest, Expectation)
- end).
- %% @doc Parse a list of parameters (a=b;c=d).
- -spec params(binary(), fun()) -> any().
- params(Data, Fun) ->
- params(Data, Fun, []).
- -spec params(binary(), fun(), [{binary(), binary()}]) -> any().
- params(Data, Fun, Acc) ->
- whitespace(Data,
- fun (<< $;, Rest/binary >>) ->
- param(Rest,
- fun (Rest2, Attr, Value) ->
- params(Rest2, Fun, [{Attr, Value}|Acc])
- end);
- (Rest) ->
- Fun(Rest, lists:reverse(Acc))
- end).
- -spec param(binary(), fun()) -> any().
- param(Data, Fun) ->
- whitespace(Data,
- fun (Rest) ->
- token_ci(Rest,
- fun (_Rest2, <<>>) -> {error, badarg};
- (<< $=, Rest2/binary >>, Attr) ->
- word(Rest2,
- fun (Rest3, Value) ->
- Fun(Rest3, Attr, Value)
- end);
- (_Rest2, _Attr) -> {error, badarg}
- end)
- end).
- %% @doc Parse an HTTP date (RFC1123, RFC850 or asctime date).
- %% @end
- %%
- %% While this may not be the most efficient date parsing we can do,
- %% it should work fine for our purposes because all HTTP dates should
- %% be sent as RFC1123 dates in HTTP/1.1.
- -spec http_date(binary()) -> any().
- http_date(Data) ->
- case rfc1123_date(Data) of
- {error, badarg} ->
- case rfc850_date(Data) of
- {error, badarg} ->
- case asctime_date(Data) of
- {error, badarg} ->
- {error, badarg};
- HTTPDate ->
- HTTPDate
- end;
- HTTPDate ->
- HTTPDate
- end;
- HTTPDate ->
- HTTPDate
- end.
- %% @doc Parse an RFC1123 date.
- -spec rfc1123_date(binary()) -> any().
- rfc1123_date(Data) ->
- wkday(Data,
- fun (<< ", ", Rest/binary >>, _WkDay) ->
- date1(Rest,
- fun (<< " ", Rest2/binary >>, Date) ->
- time(Rest2,
- fun (<< " GMT", Rest3/binary >>, Time) ->
- http_date_ret(Rest3, {Date, Time});
- (_Any, _Time) ->
- {error, badarg}
- end);
- (_Any, _Date) ->
- {error, badarg}
- end);
- (_Any, _WkDay) ->
- {error, badarg}
- end).
- %% @doc Parse an RFC850 date.
- -spec rfc850_date(binary()) -> any().
- %% From the RFC:
- %% HTTP/1.1 clients and caches SHOULD assume that an RFC-850 date
- %% which appears to be more than 50 years in the future is in fact
- %% in the past (this helps solve the "year 2000" problem).
- rfc850_date(Data) ->
- weekday(Data,
- fun (<< ", ", Rest/binary >>, _WeekDay) ->
- date2(Rest,
- fun (<< " ", Rest2/binary >>, Date) ->
- time(Rest2,
- fun (<< " GMT", Rest3/binary >>, Time) ->
- http_date_ret(Rest3, {Date, Time});
- (_Any, _Time) ->
- {error, badarg}
- end);
- (_Any, _Date) ->
- {error, badarg}
- end);
- (_Any, _WeekDay) ->
- {error, badarg}
- end).
- %% @doc Parse an asctime date.
- -spec asctime_date(binary()) -> any().
- asctime_date(Data) ->
- wkday(Data,
- fun (<< " ", Rest/binary >>, _WkDay) ->
- date3(Rest,
- fun (<< " ", Rest2/binary >>, PartialDate) ->
- time(Rest2,
- fun (<< " ", Rest3/binary >>, Time) ->
- asctime_year(Rest3,
- PartialDate, Time);
- (_Any, _Time) ->
- {error, badarg}
- end);
- (_Any, _PartialDate) ->
- {error, badarg}
- end);
- (_Any, _WkDay) ->
- {error, badarg1}
- end).
- -spec asctime_year(binary(), tuple(), tuple()) -> any().
- asctime_year(<< Y1, Y2, Y3, Y4, Rest/binary >>, {Month, Day}, Time)
- when Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9,
- Y3 >= $0, Y3 =< $9, Y4 >= $0, Y4 =< $9 ->
- Year = (Y1 - $0) * 1000 + (Y2 - $0) * 100 + (Y3 - $0) * 10 + (Y4 - $0),
- http_date_ret(Rest, {{Year, Month, Day}, Time}).
- -spec http_date_ret(binary(), tuple()) -> any().
- http_date_ret(Data, DateTime = {Date, _Time}) ->
- whitespace(Data,
- fun (<<>>) ->
- case calendar:valid_date(Date) of
- true -> DateTime;
- false -> {error, badarg}
- end;
- (_Any) ->
- {error, badarg}
- end).
- %% We never use it, pretty much just checks the wkday is right.
- -spec wkday(binary(), fun()) -> any().
- wkday(<< WkDay:3/binary, Rest/binary >>, Fun)
- when WkDay =:= <<"Mon">>; WkDay =:= <<"Tue">>; WkDay =:= <<"Wed">>;
- WkDay =:= <<"Thu">>; WkDay =:= <<"Fri">>; WkDay =:= <<"Sat">>;
- WkDay =:= <<"Sun">> ->
- Fun(Rest, WkDay);
- wkday(_Any, _Fun) ->
- {error, badarg}.
- %% We never use it, pretty much just checks the weekday is right.
- -spec weekday(binary(), fun()) -> any().
- weekday(<< "Monday", Rest/binary >>, Fun) ->
- Fun(Rest, <<"Monday">>);
- weekday(<< "Tuesday", Rest/binary >>, Fun) ->
- Fun(Rest, <<"Tuesday">>);
- weekday(<< "Wednesday", Rest/binary >>, Fun) ->
- Fun(Rest, <<"Wednesday">>);
- weekday(<< "Thursday", Rest/binary >>, Fun) ->
- Fun(Rest, <<"Thursday">>);
- weekday(<< "Friday", Rest/binary >>, Fun) ->
- Fun(Rest, <<"Friday">>);
- weekday(<< "Saturday", Rest/binary >>, Fun) ->
- Fun(Rest, <<"Saturday">>);
- weekday(<< "Sunday", Rest/binary >>, Fun) ->
- Fun(Rest, <<"Sunday">>);
- weekday(_Any, _Fun) ->
- {error, badarg}.
- -spec date1(binary(), fun()) -> any().
- date1(<< D1, D2, " ", M:3/binary, " ", Y1, Y2, Y3, Y4, Rest/binary >>, Fun)
- when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9,
- Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9,
- Y3 >= $0, Y3 =< $9, Y4 >= $0, Y4 =< $9 ->
- case month(M) of
- {error, badarg} ->
- {error, badarg};
- Month ->
- Fun(Rest, {
- (Y1 - $0) * 1000 + (Y2 - $0) * 100 + (Y3 - $0) * 10 + (Y4 - $0),
- Month,
- (D1 - $0) * 10 + (D2 - $0)
- })
- end;
- date1(_Data, _Fun) ->
- {error, badarg}.
- -spec date2(binary(), fun()) -> any().
- date2(<< D1, D2, "-", M:3/binary, "-", Y1, Y2, Rest/binary >>, Fun)
- when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9,
- Y1 >= $0, Y1 =< $9, Y2 >= $0, Y2 =< $9 ->
- case month(M) of
- {error, badarg} ->
- {error, badarg};
- Month ->
- Year = (Y1 - $0) * 10 + (Y2 - $0),
- Year2 = case Year > 50 of
- true -> Year + 1900;
- false -> Year + 2000
- end,
- Fun(Rest, {
- Year2,
- Month,
- (D1 - $0) * 10 + (D2 - $0)
- })
- end;
- date2(_Data, _Fun) ->
- {error, badarg}.
- -spec date3(binary(), fun()) -> any().
- date3(<< M:3/binary, " ", D1, D2, Rest/binary >>, Fun)
- when (D1 >= $0 andalso D1 =< $3) orelse D1 =:= $\s,
- D2 >= $0, D2 =< $9 ->
- case month(M) of
- {error, badarg} ->
- {error, badarg};
- Month ->
- Day = case D1 of
- $\s -> D2 - $0;
- D1 -> (D1 - $0) * 10 + (D2 - $0)
- end,
- Fun(Rest, {Month, Day})
- end;
- date3(_Data, _Fun) ->
- {error, badarg}.
- -spec month(<< _:24 >>) -> 1..12 | {error, badarg}.
- month(<<"Jan">>) -> 1;
- month(<<"Feb">>) -> 2;
- month(<<"Mar">>) -> 3;
- month(<<"Apr">>) -> 4;
- month(<<"May">>) -> 5;
- month(<<"Jun">>) -> 6;
- month(<<"Jul">>) -> 7;
- month(<<"Aug">>) -> 8;
- month(<<"Sep">>) -> 9;
- month(<<"Oct">>) -> 10;
- month(<<"Nov">>) -> 11;
- month(<<"Dec">>) -> 12;
- month(_Any) -> {error, badarg}.
- -spec time(binary(), fun()) -> any().
- time(<< H1, H2, ":", M1, M2, ":", S1, S2, Rest/binary >>, Fun)
- when H1 >= $0, H1 =< $2, H2 >= $0, H2 =< $9,
- M1 >= $0, M1 =< $5, M2 >= $0, M2 =< $9,
- S1 >= $0, S1 =< $5, S2 >= $0, S2 =< $9 ->
- Hour = (H1 - $0) * 10 + (H2 - $0),
- case Hour < 24 of
- true ->
- Time = {
- Hour,
- (M1 - $0) * 10 + (M2 - $0),
- (S1 - $0) * 10 + (S2 - $0)
- },
- Fun(Rest, Time);
- false ->
- {error, badarg}
- end.
- %% @doc Skip whitespace.
- -spec whitespace(binary(), fun()) -> any().
- whitespace(<< C, Rest/binary >>, Fun)
- when C =:= $\s; C =:= $\t ->
- whitespace(Rest, Fun);
- whitespace(Data, Fun) ->
- Fun(Data).
- %% @doc Parse a list of digits as a non negative integer.
- -spec digits(binary()) -> non_neg_integer() | {error, badarg}.
- digits(Data) ->
- digits(Data,
- fun (Rest, I) ->
- whitespace(Rest,
- fun (<<>>) ->
- I;
- (_Rest2) ->
- {error, badarg}
- end)
- end).
- -spec digits(binary(), fun()) -> any().
- digits(<< C, Rest/binary >>, Fun)
- when C >= $0, C =< $9 ->
- digits(Rest, Fun, C - $0);
- digits(_Data, _Fun) ->
- {error, badarg}.
- -spec digits(binary(), fun(), non_neg_integer()) -> any().
- digits(<< C, Rest/binary >>, Fun, Acc)
- when C >= $0, C =< $9 ->
- digits(Rest, Fun, Acc * 10 + (C - $0));
- digits(Data, Fun, Acc) ->
- Fun(Data, Acc).
- %% @doc Parse a list of case-insensitive alpha characters.
- %%
- %% Changes all characters to lowercase.
- -spec alpha(binary(), fun()) -> any().
- alpha(Data, Fun) ->
- alpha(Data, Fun, <<>>).
- -spec alpha(binary(), fun(), binary()) -> any().
- alpha(<<>>, Fun, Acc) ->
- Fun(<<>>, Acc);
- alpha(<< C, Rest/binary >>, Fun, Acc)
- when C >= $a andalso C =< $z;
- C >= $A andalso C =< $Z ->
- C2 = cowboy_bstr:char_to_lower(C),
- alpha(Rest, Fun, << Acc/binary, C2 >>);
- alpha(Data, Fun, Acc) ->
- Fun(Data, Acc).
- %% @doc Parse either a token or a quoted string.
- -spec word(binary(), fun()) -> any().
- word(Data = << $", _/binary >>, Fun) ->
- quoted_string(Data, Fun);
- word(Data, Fun) ->
- token(Data,
- fun (_Rest, <<>>) -> {error, badarg};
- (Rest, Token) -> Fun(Rest, Token)
- end).
- %% @doc Parse a case-insensitive token.
- %%
- %% Changes all characters to lowercase.
- -spec token_ci(binary(), fun()) -> any().
- token_ci(Data, Fun) ->
- token(Data, Fun, ci, <<>>).
- %% @doc Parse a token.
- -spec token(binary(), fun()) -> any().
- token(Data, Fun) ->
- token(Data, Fun, cs, <<>>).
- -spec token(binary(), fun(), ci | cs, binary()) -> any().
- token(<<>>, Fun, _Case, Acc) ->
- Fun(<<>>, Acc);
- token(Data = << C, _Rest/binary >>, Fun, _Case, Acc)
- when C =:= $(; C =:= $); C =:= $<; C =:= $>; C =:= $@;
- C =:= $,; C =:= $;; C =:= $:; C =:= $\\; C =:= $";
- C =:= $/; C =:= $[; C =:= $]; C =:= $?; C =:= $=;
- C =:= ${; C =:= $}; C =:= $\s; C =:= $\t;
- C < 32; C =:= 127 ->
- Fun(Data, Acc);
- token(<< C, Rest/binary >>, Fun, Case = ci, Acc) ->
- C2 = cowboy_bstr:char_to_lower(C),
- token(Rest, Fun, Case, << Acc/binary, C2 >>);
- token(<< C, Rest/binary >>, Fun, Case, Acc) ->
- token(Rest, Fun, Case, << Acc/binary, C >>).
- %% @doc Parse a quoted string.
- -spec quoted_string(binary(), fun()) -> any().
- quoted_string(<< $", Rest/binary >>, Fun) ->
- quoted_string(Rest, Fun, <<>>).
- -spec quoted_string(binary(), fun(), binary()) -> any().
- quoted_string(<<>>, _Fun, _Acc) ->
- {error, badarg};
- quoted_string(<< $", Rest/binary >>, Fun, Acc) ->
- Fun(Rest, Acc);
- 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 >>).
- %% @doc Parse a quality value.
- -spec qvalue(binary(), fun()) -> any().
- qvalue(<< $0, $., Rest/binary >>, Fun) ->
- qvalue(Rest, Fun, 0, 100);
- %% Some user agents use q=.x instead of q=0.x
- qvalue(<< $., Rest/binary >>, Fun) ->
- qvalue(Rest, Fun, 0, 100);
- qvalue(<< $0, Rest/binary >>, Fun) ->
- Fun(Rest, 0);
- qvalue(<< $1, $., $0, $0, $0, Rest/binary >>, Fun) ->
- Fun(Rest, 1000);
- qvalue(<< $1, $., $0, $0, Rest/binary >>, Fun) ->
- Fun(Rest, 1000);
- qvalue(<< $1, $., $0, Rest/binary >>, Fun) ->
- Fun(Rest, 1000);
- qvalue(<< $1, Rest/binary >>, Fun) ->
- Fun(Rest, 1000);
- qvalue(_Data, _Fun) ->
- {error, badarg}.
- -spec qvalue(binary(), fun(), integer(), 1 | 10 | 100) -> any().
- qvalue(Data, Fun, Q, 0) ->
- Fun(Data, Q);
- qvalue(<< C, Rest/binary >>, Fun, Q, M)
- when C >= $0, C =< $9 ->
- qvalue(Rest, Fun, Q + (C - $0) * M, M div 10);
- qvalue(Data, Fun, Q, _M) ->
- Fun(Data, Q).
- %% @doc Parse authorization value according rfc 2617.
- %% Only Basic authorization is supported so far.
- -spec authorization(binary(), binary()) -> {binary(), any()} | {error, badarg}.
- authorization(UserPass, Type = <<"basic">>) ->
- cowboy_http:whitespace(UserPass,
- fun(D) ->
- authorization_basic_userid(base64:mime_decode(D),
- fun(Rest, Userid) ->
- authorization_basic_password(Rest,
- fun(Password) ->
- {Type, {Userid, Password}}
- end)
- end)
- end);
- authorization(String, Type) ->
- cowboy_http:whitespace(String, fun(Rest) -> {Type, Rest} end).
- %% @doc Parse user credentials.
- -spec authorization_basic_userid(binary(), fun()) -> any().
- authorization_basic_userid(Data, Fun) ->
- authorization_basic_userid(Data, Fun, <<>>).
- authorization_basic_userid(<<>>, _Fun, _Acc) ->
- {error, badarg};
- authorization_basic_userid(<<C, _Rest/binary>>, _Fun, Acc)
- when C < 32; C =:= 127; (C =:=$: andalso Acc =:= <<>>) ->
- {error, badarg};
- authorization_basic_userid(<<$:, Rest/binary>>, Fun, Acc) ->
- Fun(Rest, Acc);
- authorization_basic_userid(<<C, Rest/binary>>, Fun, Acc) ->
- authorization_basic_userid(Rest, Fun, <<Acc/binary, C>>).
- -spec authorization_basic_password(binary(), fun()) -> any().
- authorization_basic_password(Data, Fun) ->
- authorization_basic_password(Data, Fun, <<>>).
- authorization_basic_password(<<>>, _Fun, <<>>) ->
- {error, badarg};
- authorization_basic_password(<<C, _Rest/binary>>, _Fun, _Acc)
- when C < 32; C=:= 127 ->
- {error, badarg};
- authorization_basic_password(<<>>, Fun, Acc) ->
- Fun(Acc);
- authorization_basic_password(<<C, Rest/binary>>, Fun, Acc) ->
- authorization_basic_password(Rest, Fun, <<Acc/binary, C>>).
- %% Decoding.
- %% @doc Decode a stream of chunks.
- -spec te_chunked(Bin, TransferState)
- -> more | {more, non_neg_integer(), Bin, TransferState}
- | {ok, Bin, TransferState} | {ok, Bin, Bin, TransferState}
- | {done, non_neg_integer(), Bin} | {error, badarg}
- when Bin::binary(), TransferState::{non_neg_integer(), non_neg_integer()}.
- te_chunked(<< "0\r\n\r\n", Rest/binary >>, {0, Streamed}) ->
- {done, Streamed, Rest};
- te_chunked(Data, {0, Streamed}) ->
- %% @todo We are expecting an hex size, not a general token.
- token(Data,
- fun (<< "\r\n", Rest/binary >>, BinLen) ->
- Len = list_to_integer(binary_to_list(BinLen), 16),
- te_chunked(Rest, {Len, Streamed});
- %% Chunk size shouldn't take too many bytes,
- %% don't try to stream forever.
- (Rest, _) when byte_size(Rest) < 16 ->
- more;
- (_, _) ->
- {error, badarg}
- end);
- te_chunked(Data, {ChunkRem, Streamed}) when byte_size(Data) >= ChunkRem + 2 ->
- << Chunk:ChunkRem/binary, "\r\n", Rest/binary >> = Data,
- {ok, Chunk, Rest, {0, Streamed + byte_size(Chunk)}};
- te_chunked(Data, {ChunkRem, Streamed}) ->
- {more, ChunkRem + 2, Data, {ChunkRem, Streamed}}.
- %% @doc Decode an identity stream.
- -spec te_identity(Bin, TransferState)
- -> {ok, Bin, TransferState} | {done, Bin, non_neg_integer(), Bin}
- when Bin::binary(), TransferState::{non_neg_integer(), non_neg_integer()}.
- te_identity(Data, {Streamed, Total})
- when Streamed + byte_size(Data) < Total ->
- {ok, Data, {Streamed + byte_size(Data), Total}};
- te_identity(Data, {Streamed, Total}) ->
- Size = Total - Streamed,
- << Data2:Size/binary, Rest/binary >> = Data,
- {done, Data2, Total, Rest}.
- %% @doc Decode an identity content.
- -spec ce_identity(binary()) -> {ok, binary()}.
- ce_identity(Data) ->
- {ok, Data}.
- %% Interpretation.
- %% @doc Convert a cookie name, value and options to its iodata form.
- %% @end
- %%
- %% Initially from Mochiweb:
- %% * Copyright 2007 Mochi Media, Inc.
- %% Initial binary implementation:
- %% * Copyright 2011 Thomas Burdick <thomas.burdick@gmail.com>
- -spec cookie_to_iodata(iodata(), iodata(), cowboy_req:cookie_opts())
- -> iodata().
- cookie_to_iodata(Name, Value, Opts) ->
- case binary:match(iolist_to_binary(Name), [<<$=>>, <<$,>>, <<$;>>,
- <<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]) of
- nomatch -> ok
- end,
- case binary:match(iolist_to_binary(Value), [<<$,>>, <<$;>>,
- <<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]) of
- nomatch -> ok
- end,
- MaxAgeBin = case lists:keyfind(max_age, 1, Opts) of
- false -> <<>>;
- {_, 0} ->
- %% MSIE requires an Expires date in the past to delete a cookie.
- <<"; Expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0">>;
- {_, MaxAge} when is_integer(MaxAge), MaxAge > 0 ->
- UTC = calendar:universal_time(),
- Secs = calendar:datetime_to_gregorian_seconds(UTC),
- Expires = calendar:gregorian_seconds_to_datetime(Secs + MaxAge),
- [<<"; Expires=">>, cowboy_clock:rfc2109(Expires),
- <<"; Max-Age=">>, integer_to_list(MaxAge)]
- end,
- DomainBin = case lists:keyfind(domain, 1, Opts) of
- false -> <<>>;
- {_, Domain} -> [<<"; Domain=">>, Domain]
- end,
- PathBin = case lists:keyfind(path, 1, Opts) of
- false -> <<>>;
- {_, Path} -> [<<"; Path=">>, Path]
- end,
- SecureBin = case lists:keyfind(secure, 1, Opts) of
- false -> <<>>;
- {_, true} -> <<"; Secure">>
- end,
- HttpOnlyBin = case lists:keyfind(http_only, 1, Opts) of
- false -> <<>>;
- {_, true} -> <<"; HttpOnly">>
- end,
- [Name, <<"=">>, Value, <<"; Version=1">>,
- MaxAgeBin, DomainBin, PathBin, SecureBin, HttpOnlyBin].
- %% @doc Convert an HTTP version tuple to its binary form.
- -spec version_to_binary(version()) -> binary().
- version_to_binary({1, 1}) -> <<"HTTP/1.1">>;
- version_to_binary({1, 0}) -> <<"HTTP/1.0">>.
- %% @doc Decode a URL encoded binary.
- %% @equiv urldecode(Bin, crash)
- -spec urldecode(binary()) -> binary().
- urldecode(Bin) when is_binary(Bin) ->
- urldecode(Bin, <<>>, crash).
- %% @doc Decode a URL encoded binary.
- %% The second argument specifies how to handle percent characters that are not
- %% followed by two valid hex characters. Use `skip' to ignore such errors,
- %% if `crash' is used the function will fail with the reason `badarg'.
- -spec urldecode(binary(), crash | skip) -> binary().
- urldecode(Bin, OnError) when is_binary(Bin) ->
- urldecode(Bin, <<>>, OnError).
- -spec urldecode(binary(), binary(), crash | skip) -> binary().
- urldecode(<<$%, H, L, Rest/binary>>, Acc, OnError) ->
- G = unhex(H),
- M = unhex(L),
- if G =:= error; M =:= error ->
- case OnError of skip -> ok; crash -> erlang:error(badarg) end,
- urldecode(<<H, L, Rest/binary>>, <<Acc/binary, $%>>, OnError);
- true ->
- urldecode(Rest, <<Acc/binary, (G bsl 4 bor M)>>, OnError)
- end;
- urldecode(<<$%, Rest/binary>>, Acc, OnError) ->
- case OnError of skip -> ok; crash -> erlang:error(badarg) end,
- urldecode(Rest, <<Acc/binary, $%>>, OnError);
- urldecode(<<$+, Rest/binary>>, Acc, OnError) ->
- urldecode(Rest, <<Acc/binary, $ >>, OnError);
- urldecode(<<C, Rest/binary>>, Acc, OnError) ->
- urldecode(Rest, <<Acc/binary, C>>, OnError);
- urldecode(<<>>, Acc, _OnError) ->
- Acc.
- -spec unhex(byte()) -> byte() | error.
- unhex(C) when C >= $0, C =< $9 -> C - $0;
- unhex(C) when C >= $A, C =< $F -> C - $A + 10;
- unhex(C) when C >= $a, C =< $f -> C - $a + 10;
- unhex(_) -> error.
- %% @doc URL encode a string binary.
- %% @equiv urlencode(Bin, [])
- -spec urlencode(binary()) -> binary().
- urlencode(Bin) ->
- urlencode(Bin, []).
- %% @doc URL encode a string binary.
- %% The `noplus' option disables the default behaviour of quoting space
- %% characters, `\s', as `+'. The `upper' option overrides the default behaviour
- %% of writing hex numbers using lowecase letters to using uppercase letters
- %% instead.
- -spec urlencode(binary(), [noplus|upper]) -> binary().
- urlencode(Bin, Opts) ->
- Plus = not lists:member(noplus, Opts),
- Upper = lists:member(upper, Opts),
- urlencode(Bin, <<>>, Plus, Upper).
- -spec urlencode(binary(), binary(), boolean(), boolean()) -> binary().
- urlencode(<<C, Rest/binary>>, Acc, P=Plus, U=Upper) ->
- if C >= $0, C =< $9 -> urlencode(Rest, <<Acc/binary, C>>, P, U);
- C >= $A, C =< $Z -> urlencode(Rest, <<Acc/binary, C>>, P, U);
- C >= $a, C =< $z -> urlencode(Rest, <<Acc/binary, C>>, P, U);
- C =:= $.; C =:= $-; C =:= $~; C =:= $_ ->
- urlencode(Rest, <<Acc/binary, C>>, P, U);
- C =:= $ , Plus ->
- urlencode(Rest, <<Acc/binary, $+>>, P, U);
- true ->
- H = C band 16#F0 bsr 4, L = C band 16#0F,
- H1 = if Upper -> tohexu(H); true -> tohexl(H) end,
- L1 = if Upper -> tohexu(L); true -> tohexl(L) end,
- urlencode(Rest, <<Acc/binary, $%, H1, L1>>, P, U)
- end;
- urlencode(<<>>, Acc, _Plus, _Upper) ->
- Acc.
- -spec tohexu(byte()) -> byte().
- tohexu(C) when C < 10 -> $0 + C;
- tohexu(C) when C < 17 -> $A + C - 10.
- -spec tohexl(byte()) -> byte().
- tohexl(C) when C < 10 -> $0 + C;
- tohexl(C) when C < 17 -> $a + C - 10.
- -spec x_www_form_urlencoded(binary()) -> list({binary(), binary() | true}).
- x_www_form_urlencoded(<<>>) ->
- [];
- x_www_form_urlencoded(Qs) ->
- Tokens = binary:split(Qs, <<"&">>, [global, trim]),
- [case binary:split(Token, <<"=">>) of
- [Token] -> {urldecode(Token), true};
- [Name, Value] -> {urldecode(Name), urldecode(Value)}
- end || Token <- Tokens].
- %% Tests.
- -ifdef(TEST).
- nonempty_charset_list_test_() ->
- %% {Value, Result}
- Tests = [
- {<<>>, {error, badarg}},
- {<<"iso-8859-5, unicode-1-1;q=0.8">>, [
- {<<"iso-8859-5">>, 1000},
- {<<"unicode-1-1">>, 800}
- ]},
- %% Some user agents send this invalid value for the Accept-Charset header
- {<<"ISO-8859-1;utf-8;q=0.7,*;q=0.7">>, [
- {<<"iso-8859-1">>, 1000},
- {<<"utf-8">>, 700},
- {<<"*">>, 700}
- ]}
- ],
- [{V, fun() -> R = nonempty_list(V, fun conneg/2) end} || {V, R} <- Tests].
- nonempty_language_range_list_test_() ->
- %% {Value, Result}
- Tests = [
- {<<"da, en-gb;q=0.8, en;q=0.7">>, [
- {<<"da">>, 1000},
- {<<"en-gb">>, 800},
- {<<"en">>, 700}
- ]},
- {<<"en, en-US, en-cockney, i-cherokee, x-pig-latin">>, [
- {<<"en">>, 1000},
- {<<"en-us">>, 1000},
- {<<"en-cockney">>, 1000},
- {<<"i-cherokee">>, 1000},
- {<<"x-pig-latin">>, 1000}
- ]}
- ],
- [{V, fun() -> R = nonempty_list(V, fun language_range/2) end}
- || {V, R} <- Tests].
- nonempty_token_list_test_() ->
- %% {Value, Result}
- Tests = [
- {<<>>, {error, badarg}},
- {<<" ">>, {error, badarg}},
- {<<" , ">>, {error, badarg}},
- {<<",,,">>, {error, badarg}},
- {<<"a b">>, {error, badarg}},
- {<<"a , , , ">>, [<<"a">>]},
- {<<" , , , a">>, [<<"a">>]},
- {<<"a, , b">>, [<<"a">>, <<"b">>]},
- {<<"close">>, [<<"close">>]},
- {<<"keep-alive, upgrade">>, [<<"keep-alive">>, <<"upgrade">>]}
- ],
- [{V, fun() -> R = nonempty_list(V, fun token/2) end} || {V, R} <- Tests].
- cookie_list_test_() ->
- %% {Value, Result}.
- Tests = [
- {<<"name=value; name2=value2">>, [
- {<<"name">>, <<"value">>},
- {<<"name2">>, <<"value2">>}
- ]},
- {<<"$Version=1; Customer=WILE_E_COYOTE; $Path=/acme">>, [
- {<<"customer">>, <<"WILE_E_COYOTE">>}
- ]},
- {<<"$Version=1; Customer=WILE_E_COYOTE; $Path=/acme; "
- "Part_Number=Rocket_Launcher_0001; $Path=/acme; "
- "Shipping=FedEx; $Path=/acme">>, [
- {<<"customer">>, <<"WILE_E_COYOTE">>},
- {<<"part_number">>, <<"Rocket_Launcher_0001">>},
- {<<"shipping">>, <<"FedEx">>}
- ]},
- %% Potential edge cases (initially from Mochiweb).
- {<<"foo=\\x">>, [{<<"foo">>, <<"\\x">>}]},
- {<<"=">>, {error, badarg}},
- {<<" foo ; bar ">>, {error, badarg}},
- {<<"foo=;bar=">>, [{<<"foo">>, <<>>}, {<<"bar">>, <<>>}]},
- {<<"foo=\\\";;bar ">>, {error, badarg}},
- {<<"foo=\\\";;bar=good ">>,
- [{<<"foo">>, <<"\\\"">>}, {<<"bar">>, <<"good">>}]},
- {<<"foo=\"\\\";bar">>, {error, badarg}},
- {<<"">>, {error, badarg}},
- {<<"foo=bar , baz=wibble ">>,
- [{<<"foo">>, <<"bar">>}, {<<"baz">>, <<"wibble">>}]}
- ],
- [{V, fun() -> R = cookie_list(V) end} || {V, R} <- Tests].
- media_range_list_test_() ->
- %% {Tokens, Result}
- Tests = [
- {<<"audio/*; q=0.2, audio/basic">>, [
- {{<<"audio">>, <<"*">>, []}, 200, []},
- {{<<"audio">>, <<"basic">>, []}, 1000, []}
- ]},
- {<<"text/plain; q=0.5, text/html, "
- "text/x-dvi; q=0.8, text/x-c">>, [
- {{<<"text">>, <<"plain">>, []}, 500, []},
- {{<<"text">>, <<"html">>, []}, 1000, []},
- {{<<"text">>, <<"x-dvi">>, []}, 800, []},
- {{<<"text">>, <<"x-c">>, []}, 1000, []}
- ]},
- {<<"text/*, text/html, text/html;level=1, */*">>, [
- {{<<"text">>, <<"*">>, []}, 1000, []},
- {{<<"text">>, <<"html">>, []}, 1000, []},
- {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []},
- {{<<"*">>, <<"*">>, []}, 1000, []}
- ]},
- {<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
- "text/html;level=2;q=0.4, */*;q=0.5">>, [
- {{<<"text">>, <<"*">>, []}, 300, []},
- {{<<"text">>, <<"html">>, []}, 700, []},
- {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []},
- {{<<"text">>, <<"html">>, [{<<"level">>, <<"2">>}]}, 400, []},
- {{<<"*">>, <<"*">>, []}, 500, []}
- ]},
- {<<"text/html;level=1;quoted=\"hi hi hi\";"
- "q=0.123;standalone;complex=gits, text/plain">>, [
- {{<<"text">>, <<"html">>,
- [{<<"level">>, <<"1">>}, {<<"quoted">>, <<"hi hi hi">>}]}, 123,
- [<<"standalone">>, {<<"complex">>, <<"gits">>}]},
- {{<<"text">>, <<"plain">>, []}, 1000, []}
- ]},
- {<<"text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2">>, [
- {{<<"text">>, <<"html">>, []}, 1000, []},
- {{<<"image">>, <<"gif">>, []}, 1000, []},
- {{<<"image">>, <<"jpeg">>, []}, 1000, []},
- {{<<"*">>, <<"*">>, []}, 200, []},
- {{<<"*">>, <<"*">>, []}, 200, []}
- ]}
- ],
- [{V, fun() -> R = list(V, fun media_range/2) end} || {V, R} <- Tests].
- entity_tag_match_test_() ->
- %% {Tokens, Result}
- Tests = [
- {<<"\"xyzzy\"">>, [{strong, <<"xyzzy">>}]},
- {<<"\"xyzzy\", W/\"r2d2xxxx\", \"c3piozzzz\"">>,
- [{strong, <<"xyzzy">>},
- {weak, <<"r2d2xxxx">>},
- {strong, <<"c3piozzzz">>}]},
- {<<"*">>, '*'}
- ],
- [{V, fun() -> R = entity_tag_match(V) end} || {V, R} <- Tests].
- http_date_test_() ->
- %% {Tokens, Result}
- Tests = [
- {<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}},
- {<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}},
- {<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}}
- ],
- [{V, fun() -> R = http_date(V) end} || {V, R} <- Tests].
- rfc1123_date_test_() ->
- %% {Tokens, Result}
- Tests = [
- {<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}
- ],
- [{V, fun() -> R = rfc1123_date(V) end} || {V, R} <- Tests].
- rfc850_date_test_() ->
- %% {Tokens, Result}
- Tests = [
- {<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}}
- ],
- [{V, fun() -> R = rfc850_date(V) end} || {V, R} <- Tests].
- asctime_date_test_() ->
- %% {Tokens, Result}
- Tests = [
- {<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}}
- ],
- [{V, fun() -> R = asctime_date(V) end} || {V, R} <- Tests].
- content_type_test_() ->
- %% {ContentType, Result}
- Tests = [
- {<<"text/plain; charset=iso-8859-4">>,
- {<<"text">>, <<"plain">>, [{<<"charset">>, <<"iso-8859-4">>}]}},
- {<<"multipart/form-data \t;Boundary=\"MultipartIsUgly\"">>,
- {<<"multipart">>, <<"form-data">>, [
- {<<"boundary">>, <<"MultipartIsUgly">>}
- ]}},
- {<<"foo/bar; one=FirstParam; two=SecondParam">>,
- {<<"foo">>, <<"bar">>, [
- {<<"one">>, <<"FirstParam">>},
- {<<"two">>, <<"SecondParam">>}
- ]}}
- ],
- [{V, fun () -> R = content_type(V) end} || {V, R} <- Tests].
- digits_test_() ->
- %% {Digits, Result}
- Tests = [
- {<<"42 ">>, 42},
- {<<"69\t">>, 69},
- {<<"1337">>, 1337}
- ],
- [{V, fun() -> R = digits(V) end} || {V, R} <- Tests].
- cookie_to_iodata_test_() ->
- %% {Name, Value, Opts, Result}
- Tests = [
- {<<"Customer">>, <<"WILE_E_COYOTE">>,
- [{http_only, true}, {domain, <<"acme.com">>}],
- <<"Customer=WILE_E_COYOTE; Version=1; "
- "Domain=acme.com; HttpOnly">>},
- {<<"Customer">>, <<"WILE_E_COYOTE">>,
- [{path, <<"/acme">>}],
- <<"Customer=WILE_E_COYOTE; Version=1; Path=/acme">>},
- {<<"Customer">>, <<"WILE_E_COYOTE">>,
- [{path, <<"/acme">>}, {badoption, <<"negatory">>}],
- <<"Customer=WILE_E_COYOTE; Version=1; Path=/acme">>}
- ],
- [{R, fun() -> R = iolist_to_binary(cookie_to_iodata(N, V, O)) end}
- || {N, V, O, R} <- Tests].
- cookie_to_iodata_max_age_test() ->
- F = fun(N, V, O) ->
- binary:split(iolist_to_binary(
- cookie_to_iodata(N, V, O)), <<";">>, [global])
- end,
- [<<"Customer=WILE_E_COYOTE">>,
- <<" Version=1">>,
- <<" Expires=", _/binary>>,
- <<" Max-Age=111">>,
- <<" Secure">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>,
- [{max_age, 111}, {secure, true}]),
- case catch F(<<"Customer">>, <<"WILE_E_COYOTE">>, [{max_age, -111}]) of
- {'EXIT', {{case_clause, {max_age, -111}}, _}} -> ok
- end,
- [<<"Customer=WILE_E_COYOTE">>,
- <<" Version=1">>,
- <<" Expires=", _/binary>>,
- <<" Max-Age=86417">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>,
- [{max_age, 86417}]),
- ok.
- cookie_to_iodata_failures_test_() ->
- F = fun(N, V) ->
- try cookie_to_iodata(N, V, []) of
- _ ->
- false
- catch _:_ ->
- true
- end
- end,
- Tests = [
- {<<"Na=me">>, <<"Value">>},
- {<<"Name;">>, <<"Value">>},
- {<<"\r\name">>, <<"Value">>},
- {<<"Name">>, <<"Value;">>},
- {<<"Name">>, <<"\value">>}
- ],
- [{iolist_to_binary(io_lib:format("{~p, ~p} failure", [N, V])),
- fun() -> true = F(N, V) end}
- || {N, V} <- Tests].
- x_www_form_urlencoded_test_() ->
- %% {Qs, Result}
- Tests = [
- {<<"">>, []},
- {<<"a=b">>, [{<<"a">>, <<"b">>}]},
- {<<"aaa=bbb">>, [{<<"aaa">>, <<"bbb">>}]},
- {<<"a&b">>, [{<<"a">>, true}, {<<"b">>, true}]},
- {<<"a=b&c&d=e">>, [{<<"a">>, <<"b">>},
- {<<"c">>, true}, {<<"d">>, <<"e">>}]},
- {<<"a=b=c=d=e&f=g">>, [{<<"a">>, <<"b=c=d=e">>}, {<<"f">>, <<"g">>}]},
- {<<"a+b=c+d">>, [{<<"a b">>, <<"c d">>}]}
- ],
- [{Qs, fun() -> R = x_www_form_urlencoded(Qs) end} || {Qs, R} <- Tests].
- urldecode_test_() ->
- U = fun urldecode/2,
- [?_assertEqual(<<" ">>, U(<<"%20">>, crash)),
- ?_assertEqual(<<" ">>, U(<<"+">>, crash)),
- ?_assertEqual(<<0>>, U(<<"%00">>, crash)),
- ?_assertEqual(<<255>>, U(<<"%fF">>, crash)),
- ?_assertEqual(<<"123">>, U(<<"123">>, crash)),
- ?_assertEqual(<<"%i5">>, U(<<"%i5">>, skip)),
- ?_assertEqual(<<"%5">>, U(<<"%5">>, skip)),
- ?_assertError(badarg, U(<<"%i5">>, crash)),
- ?_assertError(badarg, U(<<"%5">>, crash))
- ].
- urlencode_test_() ->
- U = fun urlencode/2,
- [?_assertEqual(<<"%ff%00">>, U(<<255,0>>, [])),
- ?_assertEqual(<<"%FF%00">>, U(<<255,0>>, [upper])),
- ?_assertEqual(<<"+">>, U(<<" ">>, [])),
- ?_assertEqual(<<"%20">>, U(<<" ">>, [noplus])),
- ?_assertEqual(<<"aBc">>, U(<<"aBc">>, [])),
- ?_assertEqual(<<".-~_">>, U(<<".-~_">>, [])),
- ?_assertEqual(<<"%ff+">>, urlencode(<<255, " ">>))
- ].
- http_authorization_test_() ->
- [?_assertEqual({<<"basic">>, {<<"Alladin">>, <<"open sesame">>}},
- authorization(<<"QWxsYWRpbjpvcGVuIHNlc2FtZQ==">>, <<"basic">>)),
- ?_assertEqual({error, badarg},
- authorization(<<"dXNlcm5hbWUK">>, <<"basic">>)),
- ?_assertEqual({error, badarg},
- authorization(<<"_[]@#$%^&*()-AA==">>, <<"basic">>)),
- ?_assertEqual({error, badarg},
- authorization(<<"dXNlcjpwYXNzCA==">>, <<"basic">>)), %% user:pass\010
- ?_assertEqual({<<"bearer">>,<<"some_secret_key">>},
- authorization(<<" some_secret_key">>, <<"bearer">>))
- ].
- -endif.
|