12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625 |
- %% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.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.
- %% Originally based on the Webmachine Diagram from Alan Dean and
- %% Justin Sheehy.
- -module(cowboy_rest).
- -behaviour(cowboy_sub_protocol).
- -ifdef(OTP_RELEASE).
- -compile({nowarn_deprecated_function, [{erlang, get_stacktrace, 0}]}).
- -endif.
- -export([upgrade/4]).
- -export([upgrade/5]).
- -type switch_handler() :: {switch_handler, module()}
- | {switch_handler, module(), any()}.
- %% Common handler callbacks.
- -callback init(Req, any())
- -> {ok | module(), Req, any()}
- | {module(), Req, any(), any()}
- when Req::cowboy_req:req().
- -callback terminate(any(), cowboy_req:req(), any()) -> ok.
- -optional_callbacks([terminate/3]).
- %% REST handler callbacks.
- -callback allowed_methods(Req, State)
- -> {[binary()], Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([allowed_methods/2]).
- -callback allow_missing_post(Req, State)
- -> {boolean(), Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([allow_missing_post/2]).
- -callback charsets_provided(Req, State)
- -> {[binary()], Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([charsets_provided/2]).
- -callback content_types_accepted(Req, State)
- -> {[{binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([content_types_accepted/2]).
- -callback content_types_provided(Req, State)
- -> {[{binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([content_types_provided/2]).
- -callback delete_completed(Req, State)
- -> {boolean(), Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([delete_completed/2]).
- -callback delete_resource(Req, State)
- -> {boolean(), Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([delete_resource/2]).
- -callback expires(Req, State)
- -> {calendar:datetime() | binary() | undefined, Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([expires/2]).
- -callback forbidden(Req, State)
- -> {boolean(), Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([forbidden/2]).
- -callback generate_etag(Req, State)
- -> {binary() | {weak | strong, binary()}, Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([generate_etag/2]).
- -callback is_authorized(Req, State)
- -> {true | {false, iodata()}, Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([is_authorized/2]).
- -callback is_conflict(Req, State)
- -> {boolean(), Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([is_conflict/2]).
- -callback known_methods(Req, State)
- -> {[binary()], Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([known_methods/2]).
- -callback languages_provided(Req, State)
- -> {[binary()], Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([languages_provided/2]).
- -callback last_modified(Req, State)
- -> {calendar:datetime(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([last_modified/2]).
- -callback malformed_request(Req, State)
- -> {boolean(), Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([malformed_request/2]).
- -callback moved_permanently(Req, State)
- -> {{true, iodata()} | false, Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([moved_permanently/2]).
- -callback moved_temporarily(Req, State)
- -> {{true, iodata()} | false, Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([moved_temporarily/2]).
- -callback multiple_choices(Req, State)
- -> {boolean(), Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([multiple_choices/2]).
- -callback options(Req, State)
- -> {ok, Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([options/2]).
- -callback previously_existed(Req, State)
- -> {boolean(), Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([previously_existed/2]).
- -callback range_satisfiable(Req, State)
- -> {boolean() | {false, non_neg_integer() | iodata()}, Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([range_satisfiable/2]).
- -callback ranges_provided(Req, State)
- -> {[{binary(), atom()}], Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([ranges_provided/2]).
- -callback rate_limited(Req, State)
- -> {{true, non_neg_integer() | calendar:datetime()} | false, Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([rate_limited/2]).
- -callback resource_exists(Req, State)
- -> {boolean(), Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([resource_exists/2]).
- -callback service_available(Req, State)
- -> {boolean(), Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([service_available/2]).
- -callback uri_too_long(Req, State)
- -> {boolean(), Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([uri_too_long/2]).
- -callback valid_content_headers(Req, State)
- -> {boolean(), Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([valid_content_headers/2]).
- -callback valid_entity_length(Req, State)
- -> {boolean(), Req, State}
- | {stop, Req, State}
- | {switch_handler(), Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([valid_entity_length/2]).
- -callback variances(Req, State)
- -> {[binary()], Req, State}
- when Req::cowboy_req:req(), State::any().
- -optional_callbacks([variances/2]).
- %% End of REST callbacks. Whew!
- -record(state, {
- method = undefined :: binary(),
- %% Handler.
- handler :: atom(),
- handler_state :: any(),
- %% Allowed methods. Only used for OPTIONS requests.
- allowed_methods :: [binary()] | undefined,
- %% Media type.
- content_types_p = [] ::
- [{binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
- atom()}],
- content_type_a :: undefined
- | {binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
- atom()},
- %% Language.
- languages_p = [] :: [binary()],
- language_a :: undefined | binary(),
- %% Charset.
- charsets_p = undefined :: undefined | [binary()],
- charset_a :: undefined | binary(),
- %% Range units.
- ranges_a = [] :: [{binary(), atom()}],
- %% Whether the resource exists.
- exists = false :: boolean(),
- %% Cached resource calls.
- etag :: undefined | no_call | {strong | weak, binary()},
- last_modified :: undefined | no_call | calendar:datetime(),
- expires :: undefined | no_call | calendar:datetime() | binary()
- }).
- -spec upgrade(Req, Env, module(), any())
- -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
- upgrade(Req0, Env, Handler, HandlerState0) ->
- Method = cowboy_req:method(Req0),
- case service_available(Req0, #state{method=Method,
- handler=Handler, handler_state=HandlerState0}) of
- {ok, Req, Result} ->
- {ok, Req, Env#{result => Result}};
- {Mod, Req, HandlerState} ->
- Mod:upgrade(Req, Env, Handler, HandlerState);
- {Mod, Req, HandlerState, Opts} ->
- Mod:upgrade(Req, Env, Handler, HandlerState, Opts)
- end.
- -spec upgrade(Req, Env, module(), any(), any())
- -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
- %% cowboy_rest takes no options.
- upgrade(Req, Env, Handler, HandlerState, _Opts) ->
- upgrade(Req, Env, Handler, HandlerState).
- service_available(Req, State) ->
- expect(Req, State, service_available, true, fun known_methods/2, 503).
- %% known_methods/2 should return a list of binary methods.
- known_methods(Req, State=#state{method=Method}) ->
- case call(Req, State, known_methods) of
- no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>;
- Method =:= <<"POST">>; Method =:= <<"PUT">>;
- Method =:= <<"PATCH">>; Method =:= <<"DELETE">>;
- Method =:= <<"OPTIONS">> ->
- next(Req, State, fun uri_too_long/2);
- no_call ->
- next(Req, State, 501);
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {List, Req2, State2} ->
- case lists:member(Method, List) of
- true -> next(Req2, State2, fun uri_too_long/2);
- false -> next(Req2, State2, 501)
- end
- end.
- uri_too_long(Req, State) ->
- expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414).
- %% allowed_methods/2 should return a list of binary methods.
- allowed_methods(Req, State=#state{method=Method}) ->
- case call(Req, State, allowed_methods) of
- no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
- next(Req, State, fun malformed_request/2);
- no_call when Method =:= <<"OPTIONS">> ->
- next(Req, State#state{allowed_methods=
- [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]},
- fun malformed_request/2);
- no_call ->
- method_not_allowed(Req, State,
- [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]);
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {List, Req2, State2} ->
- case lists:member(Method, List) of
- true when Method =:= <<"OPTIONS">> ->
- next(Req2, State2#state{allowed_methods=List},
- fun malformed_request/2);
- true ->
- next(Req2, State2, fun malformed_request/2);
- false ->
- method_not_allowed(Req2, State2, List)
- end
- end.
- method_not_allowed(Req, State, []) ->
- Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req),
- respond(Req2, State, 405);
- method_not_allowed(Req, State, Methods) ->
- << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- Methods >>,
- Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req),
- respond(Req2, State, 405).
- malformed_request(Req, State) ->
- expect(Req, State, malformed_request, false, fun is_authorized/2, 400).
- %% is_authorized/2 should return true or {false, WwwAuthenticateHeader}.
- is_authorized(Req, State) ->
- case call(Req, State, is_authorized) of
- no_call ->
- forbidden(Req, State);
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {true, Req2, State2} ->
- forbidden(Req2, State2);
- {{false, AuthHead}, Req2, State2} ->
- Req3 = cowboy_req:set_resp_header(
- <<"www-authenticate">>, AuthHead, Req2),
- respond(Req3, State2, 401)
- end.
- forbidden(Req, State) ->
- expect(Req, State, forbidden, false, fun rate_limited/2, 403).
- rate_limited(Req, State) ->
- case call(Req, State, rate_limited) of
- no_call ->
- valid_content_headers(Req, State);
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {false, Req2, State2} ->
- valid_content_headers(Req2, State2);
- {{true, RetryAfter0}, Req2, State2} ->
- RetryAfter = if
- is_integer(RetryAfter0), RetryAfter0 >= 0 ->
- integer_to_binary(RetryAfter0);
- is_tuple(RetryAfter0) ->
- cowboy_clock:rfc1123(RetryAfter0)
- end,
- Req3 = cowboy_req:set_resp_header(<<"retry-after">>, RetryAfter, Req2),
- respond(Req3, State2, 429)
- end.
- valid_content_headers(Req, State) ->
- expect(Req, State, valid_content_headers, true,
- fun valid_entity_length/2, 501).
- valid_entity_length(Req, State) ->
- expect(Req, State, valid_entity_length, true, fun options/2, 413).
- %% If you need to add additional headers to the response at this point,
- %% you should do it directly in the options/2 call using set_resp_headers.
- options(Req, State=#state{allowed_methods=Methods, method= <<"OPTIONS">>}) ->
- case call(Req, State, options) of
- no_call when Methods =:= [] ->
- Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req),
- respond(Req2, State, 200);
- no_call ->
- << ", ", Allow/binary >>
- = << << ", ", M/binary >> || M <- Methods >>,
- Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req),
- respond(Req2, State, 200);
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {ok, Req2, State2} ->
- respond(Req2, State2, 200)
- end;
- options(Req, State) ->
- content_types_provided(Req, State).
- %% content_types_provided/2 should return a list of content types and their
- %% associated callback function as a tuple: {{Type, SubType, Params}, Fun}.
- %% Type and SubType are the media type as binary. Params is a list of
- %% Key/Value tuple, with Key and Value a binary. Fun is the name of the
- %% callback that will be used to return the content of the response. It is
- %% given as an atom.
- %%
- %% An example of such return value would be:
- %% {{<<"text">>, <<"html">>, []}, to_html}
- %%
- %% Note that it is also possible to return a binary content type that will
- %% then be parsed by Cowboy. However note that while this may make your
- %% resources a little more readable, this is a lot less efficient.
- %%
- %% An example of such return value would be:
- %% {<<"text/html">>, to_html}
- content_types_provided(Req, State) ->
- case call(Req, State, content_types_provided) of
- no_call ->
- State2 = State#state{
- content_types_p=[{{<<"text">>, <<"html">>, '*'}, to_html}]},
- try cowboy_req:parse_header(<<"accept">>, Req) of
- undefined ->
- languages_provided(
- Req#{media_type => {<<"text">>, <<"html">>, []}},
- State2#state{content_type_a={{<<"text">>, <<"html">>, []}, to_html}});
- Accept ->
- choose_media_type(Req, State2, prioritize_accept(Accept))
- catch _:_ ->
- respond(Req, State2, 400)
- end;
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {[], Req2, State2} ->
- not_acceptable(Req2, State2);
- {CTP, Req2, State2} ->
- CTP2 = [normalize_content_types(P) || P <- CTP],
- State3 = State2#state{content_types_p=CTP2},
- try cowboy_req:parse_header(<<"accept">>, Req2) of
- undefined ->
- {PMT, _Fun} = HeadCTP = hd(CTP2),
- languages_provided(
- Req2#{media_type => PMT},
- State3#state{content_type_a=HeadCTP});
- Accept ->
- choose_media_type(Req2, State3, prioritize_accept(Accept))
- catch _:_ ->
- respond(Req2, State3, 400)
- end
- end.
- normalize_content_types({ContentType, Callback})
- when is_binary(ContentType) ->
- {cow_http_hd:parse_content_type(ContentType), Callback};
- normalize_content_types(Normalized) ->
- Normalized.
- prioritize_accept(Accept) ->
- lists:sort(
- fun ({MediaTypeA, Quality, _AcceptParamsA},
- {MediaTypeB, Quality, _AcceptParamsB}) ->
- %% Same quality, check precedence in more details.
- prioritize_mediatype(MediaTypeA, MediaTypeB);
- ({_MediaTypeA, QualityA, _AcceptParamsA},
- {_MediaTypeB, QualityB, _AcceptParamsB}) ->
- %% Just compare the quality.
- QualityA > QualityB
- end, Accept).
- %% Media ranges can be overridden by more specific media ranges or
- %% specific media types. If more than one media range applies to a given
- %% type, the most specific reference has precedence.
- %%
- %% We always choose B over A when we can't decide between the two.
- prioritize_mediatype({TypeA, SubTypeA, ParamsA}, {TypeB, SubTypeB, ParamsB}) ->
- case TypeB of
- TypeA ->
- case SubTypeB of
- SubTypeA -> length(ParamsA) > length(ParamsB);
- <<"*">> -> true;
- _Any -> false
- end;
- <<"*">> -> true;
- _Any -> false
- end.
- %% Ignoring the rare AcceptParams. Not sure what should be done about them.
- choose_media_type(Req, State, []) ->
- not_acceptable(Req, State);
- choose_media_type(Req, State=#state{content_types_p=CTP},
- [MediaType|Tail]) ->
- match_media_type(Req, State, Tail, CTP, MediaType).
- match_media_type(Req, State, Accept, [], _MediaType) ->
- choose_media_type(Req, State, Accept);
- match_media_type(Req, State, Accept, CTP,
- MediaType = {{<<"*">>, <<"*">>, _Params_A}, _QA, _APA}) ->
- match_media_type_params(Req, State, Accept, CTP, MediaType);
- match_media_type(Req, State, Accept,
- CTP = [{{Type, SubType_P, _PP}, _Fun}|_Tail],
- MediaType = {{Type, SubType_A, _PA}, _QA, _APA})
- when SubType_P =:= SubType_A; SubType_A =:= <<"*">> ->
- match_media_type_params(Req, State, Accept, CTP, MediaType);
- match_media_type(Req, State, Accept, [_Any|Tail], MediaType) ->
- match_media_type(Req, State, Accept, Tail, MediaType).
- match_media_type_params(Req, State, Accept,
- [Provided = {{TP, STP, '*'}, _Fun}|Tail],
- MediaType = {{TA, _STA, Params_A0}, _QA, _APA}) ->
- case lists:keytake(<<"charset">>, 1, Params_A0) of
- {value, {_, Charset}, Params_A} when TA =:= <<"text">> ->
- %% When we match against a wildcard, the media type is text
- %% and has a charset parameter, we call charsets_provided
- %% and check that the charset is provided. If the callback
- %% is not exported, we accept inconditionally but ignore
- %% the given charset so as to not send a wrong value back.
- case call(Req, State, charsets_provided) of
- no_call ->
- languages_provided(Req#{media_type => {TP, STP, Params_A0}},
- State#state{content_type_a=Provided});
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {CP, Req2, State2} ->
- State3 = State2#state{charsets_p=CP},
- case lists:member(Charset, CP) of
- false ->
- match_media_type(Req2, State3, Accept, Tail, MediaType);
- true ->
- languages_provided(Req2#{media_type => {TP, STP, Params_A}},
- State3#state{content_type_a=Provided,
- charset_a=Charset})
- end
- end;
- _ ->
- languages_provided(Req#{media_type => {TP, STP, Params_A0}},
- State#state{content_type_a=Provided})
- end;
- match_media_type_params(Req, State, Accept,
- [Provided = {PMT = {TP, STP, Params_P0}, Fun}|Tail],
- MediaType = {{_TA, _STA, Params_A}, _QA, _APA}) ->
- case lists:sort(Params_P0) =:= lists:sort(Params_A) of
- true when TP =:= <<"text">> ->
- %% When a charset was provided explicitly in both the charset header
- %% and the media types provided and the negotiation is successful,
- %% we keep the charset and don't call charsets_provided. This only
- %% applies to text media types, however.
- {Charset, Params_P} = case lists:keytake(<<"charset">>, 1, Params_P0) of
- false -> {undefined, Params_P0};
- {value, {_, Charset0}, Params_P1} -> {Charset0, Params_P1}
- end,
- languages_provided(Req#{media_type => {TP, STP, Params_P}},
- State#state{content_type_a={{TP, STP, Params_P}, Fun},
- charset_a=Charset});
- true ->
- languages_provided(Req#{media_type => PMT},
- State#state{content_type_a=Provided});
- false ->
- match_media_type(Req, State, Accept, Tail, MediaType)
- end.
- %% languages_provided should return a list of binary values indicating
- %% which languages are accepted by the resource.
- %%
- %% @todo I suppose we should also ask the resource if it wants to
- %% set a language itself or if it wants it to be automatically chosen.
- languages_provided(Req, State) ->
- case call(Req, State, languages_provided) of
- no_call ->
- charsets_provided(Req, State);
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {[], Req2, State2} ->
- not_acceptable(Req2, State2);
- {LP, Req2, State2} ->
- State3 = State2#state{languages_p=LP},
- case cowboy_req:parse_header(<<"accept-language">>, Req2) of
- undefined ->
- set_language(Req2, State3#state{language_a=hd(LP)});
- AcceptLanguage ->
- AcceptLanguage2 = prioritize_languages(AcceptLanguage),
- choose_language(Req2, State3, AcceptLanguage2)
- end
- end.
- %% A language-range matches a language-tag if it exactly equals the tag,
- %% or if it exactly equals a prefix of the tag such that the first tag
- %% character following the prefix is "-". The special range "*", if
- %% present in the Accept-Language field, matches every tag not matched
- %% by any other range present in the Accept-Language field.
- %%
- %% @todo The last sentence probably means we should always put '*'
- %% at the end of the list.
- prioritize_languages(AcceptLanguages) ->
- lists:sort(
- fun ({_TagA, QualityA}, {_TagB, QualityB}) ->
- QualityA > QualityB
- end, AcceptLanguages).
- choose_language(Req, State, []) ->
- not_acceptable(Req, State);
- choose_language(Req, State=#state{languages_p=LP}, [Language|Tail]) ->
- match_language(Req, State, Tail, LP, Language).
- match_language(Req, State, Accept, [], _Language) ->
- choose_language(Req, State, Accept);
- match_language(Req, State, _Accept, [Provided|_Tail], {'*', _Quality}) ->
- set_language(Req, State#state{language_a=Provided});
- match_language(Req, State, _Accept, [Provided|_Tail], {Provided, _Quality}) ->
- set_language(Req, State#state{language_a=Provided});
- match_language(Req, State, Accept, [Provided|Tail],
- Language = {Tag, _Quality}) ->
- Length = byte_size(Tag),
- case Provided of
- << Tag:Length/binary, $-, _Any/bits >> ->
- set_language(Req, State#state{language_a=Provided});
- _Any ->
- match_language(Req, State, Accept, Tail, Language)
- end.
- set_language(Req, State=#state{language_a=Language}) ->
- Req2 = cowboy_req:set_resp_header(<<"content-language">>, Language, Req),
- charsets_provided(Req2#{language => Language}, State).
- %% charsets_provided should return a list of binary values indicating
- %% which charsets are accepted by the resource.
- %%
- %% A charset may have been selected while negotiating the accept header.
- %% There's no need to select one again.
- charsets_provided(Req, State=#state{charset_a=Charset})
- when Charset =/= undefined ->
- set_content_type(Req, State);
- %% If charsets_p is defined, use it instead of calling charsets_provided
- %% again. We also call this clause during normal execution to avoid
- %% duplicating code.
- charsets_provided(Req, State=#state{charsets_p=[]}) ->
- not_acceptable(Req, State);
- charsets_provided(Req, State=#state{charsets_p=CP})
- when CP =/= undefined ->
- case cowboy_req:parse_header(<<"accept-charset">>, Req) of
- undefined ->
- set_content_type(Req, State#state{charset_a=hd(CP)});
- AcceptCharset0 ->
- AcceptCharset = prioritize_charsets(AcceptCharset0),
- choose_charset(Req, State, AcceptCharset)
- end;
- charsets_provided(Req, State) ->
- case call(Req, State, charsets_provided) of
- no_call ->
- set_content_type(Req, State);
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {CP, Req2, State2} ->
- charsets_provided(Req2, State2#state{charsets_p=CP})
- end.
- prioritize_charsets(AcceptCharsets) ->
- lists:sort(
- fun ({_CharsetA, QualityA}, {_CharsetB, QualityB}) ->
- QualityA > QualityB
- end, AcceptCharsets).
- choose_charset(Req, State, []) ->
- not_acceptable(Req, State);
- %% A q-value of 0 means not acceptable.
- choose_charset(Req, State, [{_, 0}|Tail]) ->
- choose_charset(Req, State, Tail);
- choose_charset(Req, State=#state{charsets_p=CP}, [Charset|Tail]) ->
- match_charset(Req, State, Tail, CP, Charset).
- match_charset(Req, State, Accept, [], _Charset) ->
- choose_charset(Req, State, Accept);
- match_charset(Req, State, _Accept, [Provided|_], {<<"*">>, _}) ->
- set_content_type(Req, State#state{charset_a=Provided});
- match_charset(Req, State, _Accept, [Provided|_], {Provided, _}) ->
- set_content_type(Req, State#state{charset_a=Provided});
- match_charset(Req, State, Accept, [_|Tail], Charset) ->
- match_charset(Req, State, Accept, Tail, Charset).
- set_content_type(Req, State=#state{
- content_type_a={{Type, SubType, Params}, _Fun},
- charset_a=Charset}) ->
- ParamsBin = set_content_type_build_params(Params, []),
- ContentType = [Type, <<"/">>, SubType, ParamsBin],
- ContentType2 = case {Type, Charset} of
- {<<"text">>, Charset} when Charset =/= undefined ->
- [ContentType, <<"; charset=">>, Charset];
- _ ->
- ContentType
- end,
- Req2 = cowboy_req:set_resp_header(<<"content-type">>, ContentType2, Req),
- encodings_provided(Req2#{charset => Charset}, State).
- set_content_type_build_params('*', []) ->
- <<>>;
- set_content_type_build_params([], []) ->
- <<>>;
- set_content_type_build_params([], Acc) ->
- lists:reverse(Acc);
- set_content_type_build_params([{Attr, Value}|Tail], Acc) ->
- set_content_type_build_params(Tail, [[Attr, <<"=">>, Value], <<";">>|Acc]).
- %% @todo Match for identity as we provide nothing else for now.
- %% @todo Don't forget to set the Content-Encoding header when we reply a body
- %% and the found encoding is something other than identity.
- encodings_provided(Req, State) ->
- ranges_provided(Req, State).
- not_acceptable(Req, State) ->
- respond(Req, State, 406).
- ranges_provided(Req, State) ->
- case call(Req, State, ranges_provided) of
- no_call ->
- variances(Req, State);
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {[], Req2, State2} ->
- Req3 = cowboy_req:set_resp_header(<<"accept-ranges">>, <<"none">>, Req2),
- variances(Req3, State2#state{ranges_a=[]});
- {RP, Req2, State2} ->
- <<", ", AcceptRanges/binary>> = <<<<", ", R/binary>> || {R, _} <- RP>>,
- Req3 = cowboy_req:set_resp_header(<<"accept-ranges">>, AcceptRanges, Req2),
- variances(Req3, State2#state{ranges_a=RP})
- end.
- %% variances/2 should return a list of headers that will be added
- %% to the Vary response header. The Accept, Accept-Language,
- %% Accept-Charset and Accept-Encoding headers do not need to be
- %% specified.
- %%
- %% @todo Do Accept-Encoding too when we handle it.
- %% @todo Does the order matter?
- variances(Req, State=#state{content_types_p=CTP,
- languages_p=LP, charsets_p=CP}) ->
- Variances = case CTP of
- [] -> [];
- [_] -> [];
- [_|_] -> [<<"accept">>]
- end,
- Variances2 = case LP of
- [] -> Variances;
- [_] -> Variances;
- [_|_] -> [<<"accept-language">>|Variances]
- end,
- Variances3 = case CP of
- undefined -> Variances2;
- [] -> Variances2;
- [_] -> Variances2;
- [_|_] -> [<<"accept-charset">>|Variances2]
- end,
- try variances(Req, State, Variances3) of
- {Variances4, Req2, State2} ->
- case [[<<", ">>, V] || V <- Variances4] of
- [] ->
- resource_exists(Req2, State2);
- [[<<", ">>, H]|Variances5] ->
- Req3 = cowboy_req:set_resp_header(
- <<"vary">>, [H|Variances5], Req2),
- resource_exists(Req3, State2)
- end
- catch Class:Reason ->
- error_terminate(Req, State, Class, Reason)
- end.
- variances(Req, State, Variances) ->
- case unsafe_call(Req, State, variances) of
- no_call ->
- {Variances, Req, State};
- {HandlerVariances, Req2, State2} ->
- {Variances ++ HandlerVariances, Req2, State2}
- end.
- resource_exists(Req, State) ->
- expect(Req, State, resource_exists, true,
- fun if_match_exists/2, fun if_match_must_not_exist/2).
- if_match_exists(Req, State) ->
- State2 = State#state{exists=true},
- case cowboy_req:parse_header(<<"if-match">>, Req) of
- undefined ->
- if_unmodified_since_exists(Req, State2);
- '*' ->
- if_unmodified_since_exists(Req, State2);
- ETagsList ->
- if_match(Req, State2, ETagsList)
- end.
- if_match(Req, State, EtagsList) ->
- try generate_etag(Req, State) of
- %% Strong Etag comparison: weak Etag never matches.
- {{weak, _}, Req2, State2} ->
- precondition_failed(Req2, State2);
- {Etag, Req2, State2} ->
- case lists:member(Etag, EtagsList) of
- true -> if_none_match_exists(Req2, State2);
- %% Etag may be `undefined' which cannot be a member.
- false -> precondition_failed(Req2, State2)
- end
- catch Class:Reason ->
- error_terminate(Req, State, Class, Reason)
- end.
- if_match_must_not_exist(Req, State) ->
- case cowboy_req:header(<<"if-match">>, Req) of
- undefined -> is_put_to_missing_resource(Req, State);
- _ -> precondition_failed(Req, State)
- end.
- if_unmodified_since_exists(Req, State) ->
- try cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of
- undefined ->
- if_none_match_exists(Req, State);
- IfUnmodifiedSince ->
- if_unmodified_since(Req, State, IfUnmodifiedSince)
- catch _:_ ->
- if_none_match_exists(Req, State)
- end.
- %% If LastModified is the atom 'no_call', we continue.
- if_unmodified_since(Req, State, IfUnmodifiedSince) ->
- try last_modified(Req, State) of
- {LastModified, Req2, State2} ->
- case LastModified > IfUnmodifiedSince of
- true -> precondition_failed(Req2, State2);
- false -> if_none_match_exists(Req2, State2)
- end
- catch Class:Reason ->
- error_terminate(Req, State, Class, Reason)
- end.
- if_none_match_exists(Req, State) ->
- case cowboy_req:parse_header(<<"if-none-match">>, Req) of
- undefined ->
- if_modified_since_exists(Req, State);
- '*' ->
- precondition_is_head_get(Req, State);
- EtagsList ->
- if_none_match(Req, State, EtagsList)
- end.
- if_none_match(Req, State, EtagsList) ->
- try generate_etag(Req, State) of
- {Etag, Req2, State2} ->
- case Etag of
- undefined ->
- precondition_failed(Req2, State2);
- Etag ->
- case is_weak_match(Etag, EtagsList) of
- true -> precondition_is_head_get(Req2, State2);
- false -> method(Req2, State2)
- end
- end
- catch Class:Reason ->
- error_terminate(Req, State, Class, Reason)
- end.
- %% Weak Etag comparison: only check the opaque tag.
- is_weak_match(_, []) ->
- false;
- is_weak_match({_, Tag}, [{_, Tag}|_]) ->
- true;
- is_weak_match(Etag, [_|Tail]) ->
- is_weak_match(Etag, Tail).
- precondition_is_head_get(Req, State=#state{method=Method})
- when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
- not_modified(Req, State);
- precondition_is_head_get(Req, State) ->
- precondition_failed(Req, State).
- if_modified_since_exists(Req, State) ->
- try cowboy_req:parse_header(<<"if-modified-since">>, Req) of
- undefined ->
- method(Req, State);
- IfModifiedSince ->
- if_modified_since_now(Req, State, IfModifiedSince)
- catch _:_ ->
- method(Req, State)
- end.
- if_modified_since_now(Req, State, IfModifiedSince) ->
- case IfModifiedSince > erlang:universaltime() of
- true -> method(Req, State);
- false -> if_modified_since(Req, State, IfModifiedSince)
- end.
- if_modified_since(Req, State, IfModifiedSince) ->
- try last_modified(Req, State) of
- {undefined, Req2, State2} ->
- method(Req2, State2);
- {LastModified, Req2, State2} ->
- case LastModified > IfModifiedSince of
- true -> method(Req2, State2);
- false -> not_modified(Req2, State2)
- end
- catch Class:Reason ->
- error_terminate(Req, State, Class, Reason)
- end.
- not_modified(Req, State) ->
- Req2 = cowboy_req:delete_resp_header(<<"content-type">>, Req),
- try set_resp_etag(Req2, State) of
- {Req3, State2} ->
- try set_resp_expires(Req3, State2) of
- {Req4, State3} ->
- respond(Req4, State3, 304)
- catch Class:Reason ->
- error_terminate(Req, State2, Class, Reason)
- end
- catch Class:Reason ->
- error_terminate(Req, State, Class, Reason)
- end.
- precondition_failed(Req, State) ->
- respond(Req, State, 412).
- is_put_to_missing_resource(Req, State=#state{method= <<"PUT">>}) ->
- moved_permanently(Req, State, fun is_conflict/2);
- is_put_to_missing_resource(Req, State) ->
- previously_existed(Req, State).
- %% moved_permanently/2 should return either false or {true, Location}
- %% with Location the full new URI of the resource.
- moved_permanently(Req, State, OnFalse) ->
- case call(Req, State, moved_permanently) of
- {{true, Location}, Req2, State2} ->
- Req3 = cowboy_req:set_resp_header(
- <<"location">>, Location, Req2),
- respond(Req3, State2, 301);
- {false, Req2, State2} ->
- OnFalse(Req2, State2);
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- no_call ->
- OnFalse(Req, State)
- end.
- previously_existed(Req, State) ->
- expect(Req, State, previously_existed, false,
- fun (R, S) -> is_post_to_missing_resource(R, S, 404) end,
- fun (R, S) -> moved_permanently(R, S, fun moved_temporarily/2) end).
- %% moved_temporarily/2 should return either false or {true, Location}
- %% with Location the full new URI of the resource.
- moved_temporarily(Req, State) ->
- case call(Req, State, moved_temporarily) of
- {{true, Location}, Req2, State2} ->
- Req3 = cowboy_req:set_resp_header(
- <<"location">>, Location, Req2),
- respond(Req3, State2, 307);
- {false, Req2, State2} ->
- is_post_to_missing_resource(Req2, State2, 410);
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- no_call ->
- is_post_to_missing_resource(Req, State, 410)
- end.
- is_post_to_missing_resource(Req, State=#state{method= <<"POST">>}, OnFalse) ->
- allow_missing_post(Req, State, OnFalse);
- is_post_to_missing_resource(Req, State, OnFalse) ->
- respond(Req, State, OnFalse).
- allow_missing_post(Req, State, OnFalse) ->
- expect(Req, State, allow_missing_post, true, fun accept_resource/2, OnFalse).
- method(Req, State=#state{method= <<"DELETE">>}) ->
- delete_resource(Req, State);
- method(Req, State=#state{method= <<"PUT">>}) ->
- is_conflict(Req, State);
- method(Req, State=#state{method=Method})
- when Method =:= <<"POST">>; Method =:= <<"PATCH">> ->
- accept_resource(Req, State);
- method(Req, State=#state{method=Method})
- when Method =:= <<"GET">>; Method =:= <<"HEAD">> ->
- set_resp_body_etag(Req, State);
- method(Req, State) ->
- multiple_choices(Req, State).
- %% delete_resource/2 should start deleting the resource and return.
- delete_resource(Req, State) ->
- expect(Req, State, delete_resource, false, 500, fun delete_completed/2).
- %% delete_completed/2 indicates whether the resource has been deleted yet.
- delete_completed(Req, State) ->
- expect(Req, State, delete_completed, true, fun has_resp_body/2, 202).
- is_conflict(Req, State) ->
- expect(Req, State, is_conflict, false, fun accept_resource/2, 409).
- %% content_types_accepted should return a list of media types and their
- %% associated callback functions in the same format as content_types_provided.
- %%
- %% The callback will then be called and is expected to process the content
- %% pushed to the resource in the request body.
- %%
- %% content_types_accepted SHOULD return a different list
- %% for each HTTP method.
- accept_resource(Req, State) ->
- case call(Req, State, content_types_accepted) of
- no_call ->
- respond(Req, State, 415);
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {CTA, Req2, State2} ->
- CTA2 = [normalize_content_types(P) || P <- CTA],
- try cowboy_req:parse_header(<<"content-type">>, Req2) of
- %% We do not match against the boundary parameter for multipart.
- {Type = <<"multipart">>, SubType, Params} ->
- ContentType = {Type, SubType, lists:keydelete(<<"boundary">>, 1, Params)},
- choose_content_type(Req2, State2, ContentType, CTA2);
- ContentType ->
- choose_content_type(Req2, State2, ContentType, CTA2)
- catch _:_ ->
- respond(Req2, State2, 415)
- end
- end.
- %% The special content type '*' will always match. It can be used as a
- %% catch-all content type for accepting any kind of request content.
- %% Note that because it will always match, it should be the last of the
- %% list of content types, otherwise it'll shadow the ones following.
- choose_content_type(Req, State, _ContentType, []) ->
- respond(Req, State, 415);
- choose_content_type(Req, State, ContentType, [{Accepted, Fun}|_Tail])
- when Accepted =:= '*'; Accepted =:= ContentType ->
- process_content_type(Req, State, Fun);
- %% The special parameter '*' will always match any kind of content type
- %% parameters.
- %% Note that because it will always match, it should be the last of the
- %% list for specific content type, otherwise it'll shadow the ones following.
- choose_content_type(Req, State, {Type, SubType, Param},
- [{{Type, SubType, AcceptedParam}, Fun}|_Tail])
- when AcceptedParam =:= '*'; AcceptedParam =:= Param ->
- process_content_type(Req, State, Fun);
- choose_content_type(Req, State, ContentType, [_Any|Tail]) ->
- choose_content_type(Req, State, ContentType, Tail).
- process_content_type(Req, State=#state{method=Method, exists=Exists}, Fun) ->
- try case call(Req, State, Fun) of
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {true, Req2, State2} when Exists ->
- next(Req2, State2, fun has_resp_body/2);
- {true, Req2, State2} ->
- next(Req2, State2, fun maybe_created/2);
- {false, Req2, State2} ->
- respond(Req2, State2, 400);
- {{true, ResURL}, Req2, State2} when Method =:= <<"POST">> ->
- Req3 = cowboy_req:set_resp_header(
- <<"location">>, ResURL, Req2),
- if
- Exists -> respond(Req3, State2, 303);
- true -> respond(Req3, State2, 201)
- end
- end catch Class:Reason = {case_clause, no_call} ->
- error_terminate(Req, State, Class, Reason)
- end.
- %% If PUT was used then the resource has been created at the current URL.
- %% Otherwise, if a location header has been set then the resource has been
- %% created at a new URL. If not, send a 200 or 204 as expected from a
- %% POST or PATCH request.
- maybe_created(Req, State=#state{method= <<"PUT">>}) ->
- respond(Req, State, 201);
- maybe_created(Req, State) ->
- case cowboy_req:has_resp_header(<<"location">>, Req) of
- true -> respond(Req, State, 201);
- false -> has_resp_body(Req, State)
- end.
- has_resp_body(Req, State) ->
- case cowboy_req:has_resp_body(Req) of
- true -> multiple_choices(Req, State);
- false -> respond(Req, State, 204)
- end.
- %% Set the Etag header if any for the response provided.
- set_resp_body_etag(Req, State) ->
- try set_resp_etag(Req, State) of
- {Req2, State2} ->
- set_resp_body_last_modified(Req2, State2)
- catch Class:Reason ->
- error_terminate(Req, State, Class, Reason)
- end.
- %% Set the Last-Modified header if any for the response provided.
- set_resp_body_last_modified(Req, State) ->
- try last_modified(Req, State) of
- {LastModified, Req2, State2} ->
- case LastModified of
- LastModified when is_atom(LastModified) ->
- set_resp_body_expires(Req2, State2);
- LastModified ->
- LastModifiedBin = cowboy_clock:rfc1123(LastModified),
- Req3 = cowboy_req:set_resp_header(
- <<"last-modified">>, LastModifiedBin, Req2),
- set_resp_body_expires(Req3, State2)
- end
- catch Class:Reason ->
- error_terminate(Req, State, Class, Reason)
- end.
- %% Set the Expires header if any for the response provided.
- set_resp_body_expires(Req, State) ->
- try set_resp_expires(Req, State) of
- {Req2, State2} ->
- if_range(Req2, State2)
- catch Class:Reason ->
- error_terminate(Req, State, Class, Reason)
- end.
- %% When both the if-range and range headers are set, we perform
- %% a strong comparison. If it fails, we send a full response.
- if_range(Req=#{headers := #{<<"if-range">> := _, <<"range">> := _}},
- State=#state{etag=Etag}) ->
- try cowboy_req:parse_header(<<"if-range">>, Req) of
- %% Strong etag comparison is an exact match with the generate_etag result.
- Etag={strong, _} ->
- range(Req, State);
- %% We cannot do a strong date comparison because we have
- %% no way of knowing whether the representation changed
- %% twice during the second covered by the presented
- %% validator. (RFC7232 2.2.2)
- _ ->
- set_resp_body(Req, State)
- catch _:_ ->
- set_resp_body(Req, State)
- end;
- if_range(Req, State) ->
- range(Req, State).
- range(Req, State=#state{ranges_a=[]}) ->
- set_resp_body(Req, State);
- range(Req, State) ->
- try cowboy_req:parse_header(<<"range">>, Req) of
- undefined ->
- set_resp_body(Req, State);
- %% @todo Maybe change parse_header to return <<"bytes">> in 3.0.
- {bytes, BytesRange} ->
- choose_range(Req, State, {<<"bytes">>, BytesRange});
- Range ->
- choose_range(Req, State, Range)
- catch _:_ ->
- %% We send a 416 response back when we can't parse the
- %% range header at all. I'm not sure this is the right
- %% way to go but at least this can help clients identify
- %% what went wrong when their range requests never work.
- range_not_satisfiable(Req, State, undefined)
- end.
- choose_range(Req, State=#state{ranges_a=RangesAccepted}, Range={RangeUnit, _}) ->
- case lists:keyfind(RangeUnit, 1, RangesAccepted) of
- {_, Callback} ->
- %% We pass the selected range onward in the Req.
- range_satisfiable(Req#{range => Range}, State, Callback);
- false ->
- set_resp_body(Req, State)
- end.
- range_satisfiable(Req, State, Callback) ->
- case call(Req, State, range_satisfiable) of
- no_call ->
- set_ranged_body(Req, State, Callback);
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {true, Req2, State2} ->
- set_ranged_body(Req2, State2, Callback);
- {false, Req2, State2} ->
- range_not_satisfiable(Req2, State2, undefined);
- {{false, Int}, Req2, State2} when is_integer(Int) ->
- range_not_satisfiable(Req2, State2, [<<"*/">>, integer_to_binary(Int)]);
- {{false, Iodata}, Req2, State2} when is_binary(Iodata); is_list(Iodata) ->
- range_not_satisfiable(Req2, State2, Iodata)
- end.
- %% When the callback selected is 'auto' and the range unit
- %% is bytes, we call the normal provide callback and split
- %% the content automatically.
- set_ranged_body(Req=#{range := {<<"bytes">>, _}}, State, auto) ->
- set_ranged_body_auto(Req, State);
- set_ranged_body(Req, State, Callback) ->
- set_ranged_body_callback(Req, State, Callback).
- set_ranged_body_auto(Req, State=#state{handler=Handler, content_type_a={_, Callback}}) ->
- try case call(Req, State, Callback) of
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {Body, Req2, State2} ->
- maybe_set_ranged_body_auto(Req2, State2, Body)
- end catch Class:{case_clause, no_call} ->
- error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},
- 'A callback specified in content_types_provided/2 is not exported.'})
- end.
- maybe_set_ranged_body_auto(Req=#{range := {_, Ranges}}, State, Body) ->
- Size = case Body of
- {sendfile, _, Bytes, _} -> Bytes;
- _ -> iolist_size(Body)
- end,
- Checks = [case Range of
- {From, infinity} -> From < Size;
- {From, To} -> (From < Size) andalso (From =< To) andalso (To =< Size);
- Neg -> (Neg =/= 0) andalso (-Neg < Size)
- end || Range <- Ranges],
- case lists:usort(Checks) of
- [true] -> set_ranged_body_auto(Req, State, Body);
- _ -> range_not_satisfiable(Req, State, [<<"*/">>, integer_to_binary(Size)])
- end.
- %% We might also want to have some checks about range order,
- %% number of ranges, and perhaps also join ranges that are
- %% too close into one contiguous range. Some of these can
- %% be done before calling the ProvideCallback.
- set_ranged_body_auto(Req=#{range := {_, Ranges}}, State, Body) ->
- Parts = [ranged_partition(Range, Body) || Range <- Ranges],
- case Parts of
- [OnePart] -> set_one_ranged_body(Req, State, OnePart);
- _ when is_tuple(Body) -> send_multipart_ranged_body(Req, State, Parts);
- _ -> set_multipart_ranged_body(Req, State, Parts)
- end.
- ranged_partition(Range, {sendfile, Offset0, Bytes0, Path}) ->
- {From, To, Offset, Bytes} = case Range of
- {From0, infinity} -> {From0, Bytes0 - 1, Offset0 + From0, Bytes0 - From0};
- {From0, To0} -> {From0, To0, Offset0 + From0, 1 + To0 - From0};
- Neg -> {Bytes0 + Neg, Bytes0 - 1, Offset0 + Bytes0 + Neg, -Neg}
- end,
- {{From, To, Bytes0}, {sendfile, Offset, Bytes, Path}};
- ranged_partition(Range, Data0) ->
- Total = iolist_size(Data0),
- {From, To, Data} = case Range of
- {From0, infinity} ->
- {_, Data1} = cow_iolists:split(From0, Data0),
- {From0, Total - 1, Data1};
- {From0, To0} ->
- {_, Data1} = cow_iolists:split(From0, Data0),
- {Data2, _} = cow_iolists:split(To0 - From0 + 1, Data1),
- {From0, To0, Data2};
- Neg ->
- {_, Data1} = cow_iolists:split(Total + Neg, Data0),
- {Total + Neg, Total - 1, Data1}
- end,
- {{From, To, Total}, Data}.
- -ifdef(TEST).
- ranged_partition_test_() ->
- Tests = [
- %% Sendfile with open-ended range.
- {{0, infinity}, {sendfile, 0, 12, "t"}, {{0, 11, 12}, {sendfile, 0, 12, "t"}}},
- {{6, infinity}, {sendfile, 0, 12, "t"}, {{6, 11, 12}, {sendfile, 6, 6, "t"}}},
- {{11, infinity}, {sendfile, 0, 12, "t"}, {{11, 11, 12}, {sendfile, 11, 1, "t"}}},
- %% Sendfile with open-ended range. Sendfile tuple has an offset originally.
- {{0, infinity}, {sendfile, 3, 12, "t"}, {{0, 11, 12}, {sendfile, 3, 12, "t"}}},
- {{6, infinity}, {sendfile, 3, 12, "t"}, {{6, 11, 12}, {sendfile, 9, 6, "t"}}},
- {{11, infinity}, {sendfile, 3, 12, "t"}, {{11, 11, 12}, {sendfile, 14, 1, "t"}}},
- %% Sendfile with a specific range.
- {{0, 11}, {sendfile, 0, 12, "t"}, {{0, 11, 12}, {sendfile, 0, 12, "t"}}},
- {{6, 11}, {sendfile, 0, 12, "t"}, {{6, 11, 12}, {sendfile, 6, 6, "t"}}},
- {{11, 11}, {sendfile, 0, 12, "t"}, {{11, 11, 12}, {sendfile, 11, 1, "t"}}},
- {{1, 10}, {sendfile, 0, 12, "t"}, {{1, 10, 12}, {sendfile, 1, 10, "t"}}},
- %% Sendfile with a specific range. Sendfile tuple has an offset originally.
- {{0, 11}, {sendfile, 3, 12, "t"}, {{0, 11, 12}, {sendfile, 3, 12, "t"}}},
- {{6, 11}, {sendfile, 3, 12, "t"}, {{6, 11, 12}, {sendfile, 9, 6, "t"}}},
- {{11, 11}, {sendfile, 3, 12, "t"}, {{11, 11, 12}, {sendfile, 14, 1, "t"}}},
- {{1, 10}, {sendfile, 3, 12, "t"}, {{1, 10, 12}, {sendfile, 4, 10, "t"}}},
- %% Sendfile with negative range.
- {-12, {sendfile, 0, 12, "t"}, {{0, 11, 12}, {sendfile, 0, 12, "t"}}},
- {-6, {sendfile, 0, 12, "t"}, {{6, 11, 12}, {sendfile, 6, 6, "t"}}},
- {-1, {sendfile, 0, 12, "t"}, {{11, 11, 12}, {sendfile, 11, 1, "t"}}},
- %% Sendfile with negative range. Sendfile tuple has an offset originally.
- {-12, {sendfile, 3, 12, "t"}, {{0, 11, 12}, {sendfile, 3, 12, "t"}}},
- {-6, {sendfile, 3, 12, "t"}, {{6, 11, 12}, {sendfile, 9, 6, "t"}}},
- {-1, {sendfile, 3, 12, "t"}, {{11, 11, 12}, {sendfile, 14, 1, "t"}}},
- %% Iodata with open-ended range.
- {{0, infinity}, <<"Hello world!">>, {{0, 11, 12}, <<"Hello world!">>}},
- {{6, infinity}, <<"Hello world!">>, {{6, 11, 12}, <<"world!">>}},
- {{11, infinity}, <<"Hello world!">>, {{11, 11, 12}, <<"!">>}},
- %% Iodata with a specific range. The resulting data is
- %% wrapped in a list because of how cow_iolists:split/2 works.
- {{0, 11}, <<"Hello world!">>, {{0, 11, 12}, [<<"Hello world!">>]}},
- {{6, 11}, <<"Hello world!">>, {{6, 11, 12}, [<<"world!">>]}},
- {{11, 11}, <<"Hello world!">>, {{11, 11, 12}, [<<"!">>]}},
- {{1, 10}, <<"Hello world!">>, {{1, 10, 12}, [<<"ello world">>]}},
- %% Iodata with negative range.
- {-12, <<"Hello world!">>, {{0, 11, 12}, <<"Hello world!">>}},
- {-6, <<"Hello world!">>, {{6, 11, 12}, <<"world!">>}},
- {-1, <<"Hello world!">>, {{11, 11, 12}, <<"!">>}}
- ],
- [{iolist_to_binary(io_lib:format("range ~p data ~p", [VR, VD])),
- fun() -> R = ranged_partition(VR, VD) end} || {VR, VD, R} <- Tests].
- -endif.
- set_ranged_body_callback(Req, State=#state{handler=Handler}, Callback) ->
- try case call(Req, State, Callback) of
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- %% When we receive a single range, we send it directly.
- {[OneRange], Req2, State2} ->
- set_one_ranged_body(Req2, State2, OneRange);
- %% When we receive multiple ranges we have to send them as multipart/byteranges.
- %% This also applies to non-bytes units. (RFC7233 A) If users don't want to use
- %% this for non-bytes units they can always return a single range with a binary
- %% content-range information.
- {Ranges, Req2, State2} when length(Ranges) > 1 ->
- %% We have to check whether there are sendfile tuples in the
- %% ranges to be sent. If there are we must use stream_reply.
- HasSendfile = [] =/= [true || {_, {sendfile, _, _, _}} <- Ranges],
- case HasSendfile of
- true -> send_multipart_ranged_body(Req2, State2, Ranges);
- false -> set_multipart_ranged_body(Req2, State2, Ranges)
- end
- end catch Class:{case_clause, no_call} ->
- error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},
- 'A callback specified in ranges_provided/2 is not exported.'})
- end.
- set_one_ranged_body(Req0, State, OneRange) ->
- {ContentRange, Body} = prepare_range(Req0, OneRange),
- Req1 = cowboy_req:set_resp_header(<<"content-range">>, ContentRange, Req0),
- Req = cowboy_req:set_resp_body(Body, Req1),
- respond(Req, State, 206).
- set_multipart_ranged_body(Req, State, [FirstRange|MoreRanges]) ->
- Boundary = cow_multipart:boundary(),
- ContentType = cowboy_req:resp_header(<<"content-type">>, Req),
- {FirstContentRange, FirstPartBody} = prepare_range(Req, FirstRange),
- FirstPartHead = cow_multipart:first_part(Boundary, [
- {<<"content-type">>, ContentType},
- {<<"content-range">>, FirstContentRange}
- ]),
- MoreParts = [begin
- {NextContentRange, NextPartBody} = prepare_range(Req, NextRange),
- NextPartHead = cow_multipart:part(Boundary, [
- {<<"content-type">>, ContentType},
- {<<"content-range">>, NextContentRange}
- ]),
- [NextPartHead, NextPartBody]
- end || NextRange <- MoreRanges],
- Body = [FirstPartHead, FirstPartBody, MoreParts, cow_multipart:close(Boundary)],
- Req2 = cowboy_req:set_resp_header(<<"content-type">>,
- [<<"multipart/byteranges; boundary=">>, Boundary], Req),
- Req3 = cowboy_req:set_resp_body(Body, Req2),
- respond(Req3, State, 206).
- %% Similar to set_multipart_ranged_body except we have to stream
- %% the data because the parts contain sendfile tuples.
- send_multipart_ranged_body(Req, State, [FirstRange|MoreRanges]) ->
- Boundary = cow_multipart:boundary(),
- ContentType = cowboy_req:resp_header(<<"content-type">>, Req),
- Req2 = cowboy_req:set_resp_header(<<"content-type">>,
- [<<"multipart/byteranges; boundary=">>, Boundary], Req),
- Req3 = cowboy_req:stream_reply(206, Req2),
- {FirstContentRange, FirstPartBody} = prepare_range(Req, FirstRange),
- FirstPartHead = cow_multipart:first_part(Boundary, [
- {<<"content-type">>, ContentType},
- {<<"content-range">>, FirstContentRange}
- ]),
- cowboy_req:stream_body(FirstPartHead, nofin, Req3),
- cowboy_req:stream_body(FirstPartBody, nofin, Req3),
- _ = [begin
- {NextContentRange, NextPartBody} = prepare_range(Req, NextRange),
- NextPartHead = cow_multipart:part(Boundary, [
- {<<"content-type">>, ContentType},
- {<<"content-range">>, NextContentRange}
- ]),
- cowboy_req:stream_body(NextPartHead, nofin, Req3),
- cowboy_req:stream_body(NextPartBody, nofin, Req3),
- [NextPartHead, NextPartBody]
- end || NextRange <- MoreRanges],
- cowboy_req:stream_body(cow_multipart:close(Boundary), fin, Req3),
- terminate(Req3, State).
- prepare_range(#{range := {RangeUnit, _}}, {{From, To, Total0}, Body}) ->
- Total = case Total0 of
- '*' -> <<"*">>;
- _ -> integer_to_binary(Total0)
- end,
- ContentRange = [RangeUnit, $\s, integer_to_binary(From),
- $-, integer_to_binary(To), $/, Total],
- {ContentRange, Body};
- prepare_range(#{range := {RangeUnit, _}}, {RangeData, Body}) ->
- {[RangeUnit, $\s, RangeData], Body}.
- %% We send the content-range header when we can on error.
- range_not_satisfiable(Req, State, undefined) ->
- respond(Req, State, 416);
- range_not_satisfiable(Req0=#{range := {RangeUnit, _}}, State, RangeData) ->
- Req = cowboy_req:set_resp_header(<<"content-range">>,
- [RangeUnit, $\s, RangeData], Req0),
- respond(Req, State, 416).
- %% Set the response headers and call the callback found using
- %% content_types_provided/2 to obtain the request body and add
- %% it to the response.
- set_resp_body(Req, State=#state{handler=Handler, content_type_a={_, Callback}}) ->
- try case call(Req, State, Callback) of
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {Body, Req2, State2} ->
- Req3 = cowboy_req:set_resp_body(Body, Req2),
- multiple_choices(Req3, State2)
- end catch Class:{case_clause, no_call} ->
- error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},
- 'A callback specified in content_types_provided/2 is not exported.'})
- end.
- multiple_choices(Req, State) ->
- expect(Req, State, multiple_choices, false, 200, 300).
- %% Response utility functions.
- set_resp_etag(Req, State) ->
- {Etag, Req2, State2} = generate_etag(Req, State),
- case Etag of
- undefined ->
- {Req2, State2};
- Etag ->
- Req3 = cowboy_req:set_resp_header(
- <<"etag">>, encode_etag(Etag), Req2),
- {Req3, State2}
- end.
- -spec encode_etag({strong | weak, binary()}) -> iolist().
- encode_etag({strong, Etag}) -> [$",Etag,$"];
- encode_etag({weak, Etag}) -> ["W/\"",Etag,$"].
- set_resp_expires(Req, State) ->
- {Expires, Req2, State2} = expires(Req, State),
- case Expires of
- Expires when is_atom(Expires) ->
- {Req2, State2};
- Expires when is_binary(Expires) ->
- Req3 = cowboy_req:set_resp_header(
- <<"expires">>, Expires, Req2),
- {Req3, State2};
- Expires ->
- ExpiresBin = cowboy_clock:rfc1123(Expires),
- Req3 = cowboy_req:set_resp_header(
- <<"expires">>, ExpiresBin, Req2),
- {Req3, State2}
- end.
- %% Info retrieval. No logic.
- generate_etag(Req, State=#state{etag=no_call}) ->
- {undefined, Req, State};
- generate_etag(Req, State=#state{etag=undefined}) ->
- case unsafe_call(Req, State, generate_etag) of
- no_call ->
- {undefined, Req, State#state{etag=no_call}};
- {Etag, Req2, State2} when is_binary(Etag) ->
- Etag2 = cow_http_hd:parse_etag(Etag),
- {Etag2, Req2, State2#state{etag=Etag2}};
- {Etag, Req2, State2} ->
- {Etag, Req2, State2#state{etag=Etag}}
- end;
- generate_etag(Req, State=#state{etag=Etag}) ->
- {Etag, Req, State}.
- last_modified(Req, State=#state{last_modified=no_call}) ->
- {undefined, Req, State};
- last_modified(Req, State=#state{last_modified=undefined}) ->
- case unsafe_call(Req, State, last_modified) of
- no_call ->
- {undefined, Req, State#state{last_modified=no_call}};
- {LastModified, Req2, State2} ->
- {LastModified, Req2, State2#state{last_modified=LastModified}}
- end;
- last_modified(Req, State=#state{last_modified=LastModified}) ->
- {LastModified, Req, State}.
- expires(Req, State=#state{expires=no_call}) ->
- {undefined, Req, State};
- expires(Req, State=#state{expires=undefined}) ->
- case unsafe_call(Req, State, expires) of
- no_call ->
- {undefined, Req, State#state{expires=no_call}};
- {Expires, Req2, State2} ->
- {Expires, Req2, State2#state{expires=Expires}}
- end;
- expires(Req, State=#state{expires=Expires}) ->
- {Expires, Req, State}.
- %% REST primitives.
- expect(Req, State, Callback, Expected, OnTrue, OnFalse) ->
- case call(Req, State, Callback) of
- no_call ->
- next(Req, State, OnTrue);
- {stop, Req2, State2} ->
- terminate(Req2, State2);
- {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
- switch_handler(Switch, Req2, State2);
- {Expected, Req2, State2} ->
- next(Req2, State2, OnTrue);
- {_Unexpected, Req2, State2} ->
- next(Req2, State2, OnFalse)
- end.
- call(Req0, State=#state{handler=Handler,
- handler_state=HandlerState0}, Callback) ->
- case erlang:function_exported(Handler, Callback, 2) of
- true ->
- try Handler:Callback(Req0, HandlerState0) of
- no_call ->
- no_call;
- {Result, Req, HandlerState} ->
- {Result, Req, State#state{handler_state=HandlerState}}
- catch Class:Reason ->
- error_terminate(Req0, State, Class, Reason)
- end;
- false ->
- no_call
- end.
- unsafe_call(Req0, State=#state{handler=Handler,
- handler_state=HandlerState0}, Callback) ->
- case erlang:function_exported(Handler, Callback, 2) of
- false ->
- no_call;
- true ->
- case Handler:Callback(Req0, HandlerState0) of
- no_call ->
- no_call;
- {Result, Req, HandlerState} ->
- {Result, Req, State#state{handler_state=HandlerState}}
- end
- end.
- next(Req, State, Next) when is_function(Next) ->
- Next(Req, State);
- next(Req, State, StatusCode) when is_integer(StatusCode) ->
- respond(Req, State, StatusCode).
- respond(Req0, State, StatusCode) ->
- %% We remove the content-type header when there is no body,
- %% except when the status code is 200 because it might have
- %% been intended (for example sending an empty file).
- Req = case cowboy_req:has_resp_body(Req0) of
- true when StatusCode =:= 200 -> Req0;
- true -> Req0;
- false -> cowboy_req:delete_resp_header(<<"content-type">>, Req0)
- end,
- terminate(cowboy_req:reply(StatusCode, Req), State).
- switch_handler({switch_handler, Mod}, Req, #state{handler_state=HandlerState}) ->
- {Mod, Req, HandlerState};
- switch_handler({switch_handler, Mod, Opts}, Req, #state{handler_state=HandlerState}) ->
- {Mod, Req, HandlerState, Opts}.
- -spec error_terminate(cowboy_req:req(), #state{}, atom(), any()) -> no_return().
- error_terminate(Req, #state{handler=Handler, handler_state=HandlerState}, Class, Reason) ->
- cowboy_handler:terminate({crash, Class, Reason}, Req, HandlerState, Handler),
- erlang:raise(Class, Reason, erlang:get_stacktrace()).
- terminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->
- Result = cowboy_handler:terminate(normal, Req, HandlerState, Handler),
- {ok, Req, Result}.
|