cowboy_req.erl (36161B)
1 %% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu> 2 %% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu> 3 %% 4 %% Permission to use, copy, modify, and/or distribute this software for any 5 %% purpose with or without fee is hereby granted, provided that the above 6 %% copyright notice and this permission notice appear in all copies. 7 %% 8 %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 16 -module(cowboy_req). 17 18 %% Request. 19 -export([method/1]). 20 -export([version/1]). 21 -export([peer/1]). 22 -export([sock/1]). 23 -export([cert/1]). 24 -export([scheme/1]). 25 -export([host/1]). 26 -export([host_info/1]). 27 -export([port/1]). 28 -export([path/1]). 29 -export([path_info/1]). 30 -export([qs/1]). 31 -export([parse_qs/1]). 32 -export([match_qs/2]). 33 -export([uri/1]). 34 -export([uri/2]). 35 -export([binding/2]). 36 -export([binding/3]). 37 -export([bindings/1]). 38 -export([header/2]). 39 -export([header/3]). 40 -export([headers/1]). 41 -export([parse_header/2]). 42 -export([parse_header/3]). 43 -export([filter_cookies/2]). 44 -export([parse_cookies/1]). 45 -export([match_cookies/2]). 46 47 %% Request body. 48 -export([has_body/1]). 49 -export([body_length/1]). 50 -export([read_body/1]). 51 -export([read_body/2]). 52 -export([read_urlencoded_body/1]). 53 -export([read_urlencoded_body/2]). 54 -export([read_and_match_urlencoded_body/2]). 55 -export([read_and_match_urlencoded_body/3]). 56 57 %% Multipart. 58 -export([read_part/1]). 59 -export([read_part/2]). 60 -export([read_part_body/1]). 61 -export([read_part_body/2]). 62 63 %% Response. 64 -export([set_resp_cookie/3]). 65 -export([set_resp_cookie/4]). 66 -export([resp_header/2]). 67 -export([resp_header/3]). 68 -export([resp_headers/1]). 69 -export([set_resp_header/3]). 70 -export([set_resp_headers/2]). 71 -export([has_resp_header/2]). 72 -export([delete_resp_header/2]). 73 -export([set_resp_body/2]). 74 %% @todo set_resp_body/3 with a ContentType or even Headers argument, to set content headers. 75 -export([has_resp_body/1]). 76 -export([inform/2]). 77 -export([inform/3]). 78 -export([reply/2]). 79 -export([reply/3]). 80 -export([reply/4]). 81 -export([stream_reply/2]). 82 -export([stream_reply/3]). 83 %% @todo stream_body/2 (nofin) 84 -export([stream_body/3]). 85 %% @todo stream_events/2 (nofin) 86 -export([stream_events/3]). 87 -export([stream_trailers/2]). 88 -export([push/3]). 89 -export([push/4]). 90 91 %% Stream handlers. 92 -export([cast/2]). 93 94 %% Internal. 95 -export([response_headers/2]). 96 97 -type read_body_opts() :: #{ 98 length => non_neg_integer() | infinity, 99 period => non_neg_integer(), 100 timeout => timeout() 101 }. 102 -export_type([read_body_opts/0]). 103 104 %% While sendfile allows a Len of 0 that means "everything past Offset", 105 %% Cowboy expects the real length as it is used as metadata. 106 -type resp_body() :: iodata() 107 | {sendfile, non_neg_integer(), non_neg_integer(), file:name_all()}. 108 -export_type([resp_body/0]). 109 110 -type push_opts() :: #{ 111 method => binary(), 112 scheme => binary(), 113 host => binary(), 114 port => inet:port_number(), 115 qs => binary() 116 }. 117 -export_type([push_opts/0]). 118 119 -type req() :: #{ 120 %% Public interface. 121 method := binary(), 122 version := cowboy:http_version() | atom(), 123 scheme := binary(), 124 host := binary(), 125 port := inet:port_number(), 126 path := binary(), 127 qs := binary(), 128 headers := cowboy:http_headers(), 129 peer := {inet:ip_address(), inet:port_number()}, 130 sock := {inet:ip_address(), inet:port_number()}, 131 cert := binary() | undefined, 132 133 %% Private interface. 134 ref := ranch:ref(), 135 pid := pid(), 136 streamid := cowboy_stream:streamid(), 137 138 host_info => cowboy_router:tokens(), 139 path_info => cowboy_router:tokens(), 140 bindings => cowboy_router:bindings(), 141 142 has_body := boolean(), 143 body_length := non_neg_integer() | undefined, 144 has_read_body => true, 145 multipart => {binary(), binary()} | done, 146 147 has_sent_resp => headers | true, 148 resp_cookies => #{iodata() => iodata()}, 149 resp_headers => #{binary() => iodata()}, 150 resp_body => resp_body(), 151 152 proxy_header => ranch_proxy_header:proxy_info(), 153 media_type => {binary(), binary(), [{binary(), binary()}]}, 154 language => binary() | undefined, 155 charset => binary() | undefined, 156 range => {binary(), binary() 157 | [{non_neg_integer(), non_neg_integer() | infinity} | neg_integer()]}, 158 websocket_version => 7 | 8 | 13, 159 160 %% The user is encouraged to use the Req to store information 161 %% when no better solution is available. 162 _ => _ 163 }. 164 -export_type([req/0]). 165 166 %% Request. 167 168 -spec method(req()) -> binary(). 169 method(#{method := Method}) -> 170 Method. 171 172 -spec version(req()) -> cowboy:http_version(). 173 version(#{version := Version}) -> 174 Version. 175 176 -spec peer(req()) -> {inet:ip_address(), inet:port_number()}. 177 peer(#{peer := Peer}) -> 178 Peer. 179 180 -spec sock(req()) -> {inet:ip_address(), inet:port_number()}. 181 sock(#{sock := Sock}) -> 182 Sock. 183 184 -spec cert(req()) -> binary() | undefined. 185 cert(#{cert := Cert}) -> 186 Cert. 187 188 -spec scheme(req()) -> binary(). 189 scheme(#{scheme := Scheme}) -> 190 Scheme. 191 192 -spec host(req()) -> binary(). 193 host(#{host := Host}) -> 194 Host. 195 196 %% @todo The host_info is undefined if cowboy_router isn't used. Do we want to crash? 197 -spec host_info(req()) -> cowboy_router:tokens() | undefined. 198 host_info(#{host_info := HostInfo}) -> 199 HostInfo. 200 201 -spec port(req()) -> inet:port_number(). 202 port(#{port := Port}) -> 203 Port. 204 205 -spec path(req()) -> binary(). 206 path(#{path := Path}) -> 207 Path. 208 209 %% @todo The path_info is undefined if cowboy_router isn't used. Do we want to crash? 210 -spec path_info(req()) -> cowboy_router:tokens() | undefined. 211 path_info(#{path_info := PathInfo}) -> 212 PathInfo. 213 214 -spec qs(req()) -> binary(). 215 qs(#{qs := Qs}) -> 216 Qs. 217 218 %% @todo Might be useful to limit the number of keys. 219 -spec parse_qs(req()) -> [{binary(), binary() | true}]. 220 parse_qs(#{qs := Qs}) -> 221 try 222 cow_qs:parse_qs(Qs) 223 catch _:_:Stacktrace -> 224 erlang:raise(exit, {request_error, qs, 225 'Malformed query string; application/x-www-form-urlencoded expected.' 226 }, Stacktrace) 227 end. 228 229 -spec match_qs(cowboy:fields(), req()) -> map(). 230 match_qs(Fields, Req) -> 231 case filter(Fields, kvlist_to_map(Fields, parse_qs(Req))) of 232 {ok, Map} -> 233 Map; 234 {error, Errors} -> 235 exit({request_error, {match_qs, Errors}, 236 'Query string validation constraints failed for the reasons provided.'}) 237 end. 238 239 -spec uri(req()) -> iodata(). 240 uri(Req) -> 241 uri(Req, #{}). 242 243 -spec uri(req(), map()) -> iodata(). 244 uri(#{scheme := Scheme0, host := Host0, port := Port0, 245 path := Path0, qs := Qs0}, Opts) -> 246 Scheme = case maps:get(scheme, Opts, Scheme0) of 247 S = undefined -> S; 248 S -> iolist_to_binary(S) 249 end, 250 Host = maps:get(host, Opts, Host0), 251 Port = maps:get(port, Opts, Port0), 252 {Path, Qs} = case maps:get(path, Opts, Path0) of 253 <<"*">> -> {<<>>, <<>>}; 254 P -> {P, maps:get(qs, Opts, Qs0)} 255 end, 256 Fragment = maps:get(fragment, Opts, undefined), 257 [uri_host(Scheme, Scheme0, Port, Host), uri_path(Path), uri_qs(Qs), uri_fragment(Fragment)]. 258 259 uri_host(_, _, _, undefined) -> <<>>; 260 uri_host(Scheme, Scheme0, Port, Host) -> 261 case iolist_size(Host) of 262 0 -> <<>>; 263 _ -> [uri_scheme(Scheme), <<"//">>, Host, uri_port(Scheme, Scheme0, Port)] 264 end. 265 266 uri_scheme(undefined) -> <<>>; 267 uri_scheme(Scheme) -> 268 case iolist_size(Scheme) of 269 0 -> Scheme; 270 _ -> [Scheme, $:] 271 end. 272 273 uri_port(_, _, undefined) -> <<>>; 274 uri_port(undefined, <<"http">>, 80) -> <<>>; 275 uri_port(undefined, <<"https">>, 443) -> <<>>; 276 uri_port(<<"http">>, _, 80) -> <<>>; 277 uri_port(<<"https">>, _, 443) -> <<>>; 278 uri_port(_, _, Port) -> 279 [$:, integer_to_binary(Port)]. 280 281 uri_path(undefined) -> <<>>; 282 uri_path(Path) -> Path. 283 284 uri_qs(undefined) -> <<>>; 285 uri_qs(Qs) -> 286 case iolist_size(Qs) of 287 0 -> Qs; 288 _ -> [$?, Qs] 289 end. 290 291 uri_fragment(undefined) -> <<>>; 292 uri_fragment(Fragment) -> 293 case iolist_size(Fragment) of 294 0 -> Fragment; 295 _ -> [$#, Fragment] 296 end. 297 298 -ifdef(TEST). 299 uri1_test() -> 300 <<"http://localhost/path">> = iolist_to_binary(uri(#{ 301 scheme => <<"http">>, host => <<"localhost">>, port => 80, 302 path => <<"/path">>, qs => <<>>})), 303 <<"http://localhost:443/path">> = iolist_to_binary(uri(#{ 304 scheme => <<"http">>, host => <<"localhost">>, port => 443, 305 path => <<"/path">>, qs => <<>>})), 306 <<"http://localhost:8080/path">> = iolist_to_binary(uri(#{ 307 scheme => <<"http">>, host => <<"localhost">>, port => 8080, 308 path => <<"/path">>, qs => <<>>})), 309 <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(#{ 310 scheme => <<"http">>, host => <<"localhost">>, port => 8080, 311 path => <<"/path">>, qs => <<"dummy=2785">>})), 312 <<"https://localhost/path">> = iolist_to_binary(uri(#{ 313 scheme => <<"https">>, host => <<"localhost">>, port => 443, 314 path => <<"/path">>, qs => <<>>})), 315 <<"https://localhost:8443/path">> = iolist_to_binary(uri(#{ 316 scheme => <<"https">>, host => <<"localhost">>, port => 8443, 317 path => <<"/path">>, qs => <<>>})), 318 <<"https://localhost:8443/path?dummy=2785">> = iolist_to_binary(uri(#{ 319 scheme => <<"https">>, host => <<"localhost">>, port => 8443, 320 path => <<"/path">>, qs => <<"dummy=2785">>})), 321 ok. 322 323 uri2_test() -> 324 Req = #{ 325 scheme => <<"http">>, host => <<"localhost">>, port => 8080, 326 path => <<"/path">>, qs => <<"dummy=2785">> 327 }, 328 <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{})), 329 %% Disable individual components. 330 <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => undefined})), 331 <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => undefined})), 332 <<"http://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => undefined})), 333 <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => undefined})), 334 <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => undefined})), 335 <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => undefined})), 336 <<"http://localhost:8080">> = iolist_to_binary(uri(Req, #{path => undefined, qs => undefined})), 337 <<>> = iolist_to_binary(uri(Req, #{host => undefined, path => undefined, qs => undefined})), 338 %% Empty values. 339 <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => <<>>})), 340 <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => ""})), 341 <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => [<<>>]})), 342 <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => <<>>})), 343 <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => ""})), 344 <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => [<<>>]})), 345 <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => <<>>})), 346 <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => ""})), 347 <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => [<<>>]})), 348 <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => <<>>})), 349 <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => ""})), 350 <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => [<<>>]})), 351 <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => <<>>})), 352 <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => ""})), 353 <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => [<<>>]})), 354 %% Port is integer() | undefined. 355 {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => <<>>}))), 356 {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => ""}))), 357 {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => [<<>>]}))), 358 %% Update components. 359 <<"https://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => "https"})), 360 <<"http://example.org:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => "example.org"})), 361 <<"http://localhost:123/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => 123})), 362 <<"http://localhost:8080/custom?dummy=2785">> = iolist_to_binary(uri(Req, #{path => "/custom"})), 363 <<"http://localhost:8080/path?smart=42">> = iolist_to_binary(uri(Req, #{qs => "smart=42"})), 364 <<"http://localhost:8080/path?dummy=2785#intro">> = iolist_to_binary(uri(Req, #{fragment => "intro"})), 365 %% Interesting combinations. 366 <<"http://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => 80})), 367 <<"https://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => "https", port => 443})), 368 ok. 369 -endif. 370 371 -spec binding(atom(), req()) -> any() | undefined. 372 binding(Name, Req) -> 373 binding(Name, Req, undefined). 374 375 -spec binding(atom(), req(), Default) -> any() | Default when Default::any(). 376 binding(Name, #{bindings := Bindings}, Default) when is_atom(Name) -> 377 case Bindings of 378 #{Name := Value} -> Value; 379 _ -> Default 380 end; 381 binding(Name, _, Default) when is_atom(Name) -> 382 Default. 383 384 -spec bindings(req()) -> cowboy_router:bindings(). 385 bindings(#{bindings := Bindings}) -> 386 Bindings; 387 bindings(_) -> 388 #{}. 389 390 -spec header(binary(), req()) -> binary() | undefined. 391 header(Name, Req) -> 392 header(Name, Req, undefined). 393 394 -spec header(binary(), req(), Default) -> binary() | Default when Default::any(). 395 header(Name, #{headers := Headers}, Default) -> 396 maps:get(Name, Headers, Default). 397 398 -spec headers(req()) -> cowboy:http_headers(). 399 headers(#{headers := Headers}) -> 400 Headers. 401 402 -spec parse_header(binary(), Req) -> any() when Req::req(). 403 parse_header(Name = <<"content-length">>, Req) -> 404 parse_header(Name, Req, 0); 405 parse_header(Name = <<"cookie">>, Req) -> 406 parse_header(Name, Req, []); 407 parse_header(Name, Req) -> 408 parse_header(Name, Req, undefined). 409 410 -spec parse_header(binary(), Req, any()) -> any() when Req::req(). 411 parse_header(Name, Req, Default) -> 412 try 413 parse_header(Name, Req, Default, parse_header_fun(Name)) 414 catch _:_:Stacktrace -> 415 erlang:raise(exit, {request_error, {header, Name}, 416 'Malformed header. Please consult the relevant specification.' 417 }, Stacktrace) 418 end. 419 420 parse_header_fun(<<"accept">>) -> fun cow_http_hd:parse_accept/1; 421 parse_header_fun(<<"accept-charset">>) -> fun cow_http_hd:parse_accept_charset/1; 422 parse_header_fun(<<"accept-encoding">>) -> fun cow_http_hd:parse_accept_encoding/1; 423 parse_header_fun(<<"accept-language">>) -> fun cow_http_hd:parse_accept_language/1; 424 parse_header_fun(<<"access-control-request-headers">>) -> fun cow_http_hd:parse_access_control_request_headers/1; 425 parse_header_fun(<<"access-control-request-method">>) -> fun cow_http_hd:parse_access_control_request_method/1; 426 parse_header_fun(<<"authorization">>) -> fun cow_http_hd:parse_authorization/1; 427 parse_header_fun(<<"connection">>) -> fun cow_http_hd:parse_connection/1; 428 parse_header_fun(<<"content-encoding">>) -> fun cow_http_hd:parse_content_encoding/1; 429 parse_header_fun(<<"content-language">>) -> fun cow_http_hd:parse_content_language/1; 430 parse_header_fun(<<"content-length">>) -> fun cow_http_hd:parse_content_length/1; 431 parse_header_fun(<<"content-type">>) -> fun cow_http_hd:parse_content_type/1; 432 parse_header_fun(<<"cookie">>) -> fun cow_cookie:parse_cookie/1; 433 parse_header_fun(<<"expect">>) -> fun cow_http_hd:parse_expect/1; 434 parse_header_fun(<<"if-match">>) -> fun cow_http_hd:parse_if_match/1; 435 parse_header_fun(<<"if-modified-since">>) -> fun cow_http_hd:parse_if_modified_since/1; 436 parse_header_fun(<<"if-none-match">>) -> fun cow_http_hd:parse_if_none_match/1; 437 parse_header_fun(<<"if-range">>) -> fun cow_http_hd:parse_if_range/1; 438 parse_header_fun(<<"if-unmodified-since">>) -> fun cow_http_hd:parse_if_unmodified_since/1; 439 parse_header_fun(<<"max-forwards">>) -> fun cow_http_hd:parse_max_forwards/1; 440 parse_header_fun(<<"origin">>) -> fun cow_http_hd:parse_origin/1; 441 parse_header_fun(<<"proxy-authorization">>) -> fun cow_http_hd:parse_proxy_authorization/1; 442 parse_header_fun(<<"range">>) -> fun cow_http_hd:parse_range/1; 443 parse_header_fun(<<"sec-websocket-extensions">>) -> fun cow_http_hd:parse_sec_websocket_extensions/1; 444 parse_header_fun(<<"sec-websocket-protocol">>) -> fun cow_http_hd:parse_sec_websocket_protocol_req/1; 445 parse_header_fun(<<"sec-websocket-version">>) -> fun cow_http_hd:parse_sec_websocket_version_req/1; 446 parse_header_fun(<<"trailer">>) -> fun cow_http_hd:parse_trailer/1; 447 parse_header_fun(<<"upgrade">>) -> fun cow_http_hd:parse_upgrade/1; 448 parse_header_fun(<<"x-forwarded-for">>) -> fun cow_http_hd:parse_x_forwarded_for/1. 449 450 parse_header(Name, Req, Default, ParseFun) -> 451 case header(Name, Req) of 452 undefined -> Default; 453 Value -> ParseFun(Value) 454 end. 455 456 -spec filter_cookies([atom() | binary()], Req) -> Req when Req::req(). 457 filter_cookies(Names0, Req=#{headers := Headers}) -> 458 Names = [if 459 is_atom(N) -> atom_to_binary(N, utf8); 460 true -> N 461 end || N <- Names0], 462 case header(<<"cookie">>, Req) of 463 undefined -> Req; 464 Value0 -> 465 Cookies0 = binary:split(Value0, <<$;>>), 466 Cookies = lists:filter(fun(Cookie) -> 467 lists:member(cookie_name(Cookie), Names) 468 end, Cookies0), 469 Value = iolist_to_binary(lists:join($;, Cookies)), 470 Req#{headers => Headers#{<<"cookie">> => Value}} 471 end. 472 473 %% This is a specialized function to extract a cookie name 474 %% regardless of whether the name is valid or not. We skip 475 %% whitespace at the beginning and take whatever's left to 476 %% be the cookie name, up to the = sign. 477 cookie_name(<<$\s, Rest/binary>>) -> cookie_name(Rest); 478 cookie_name(<<$\t, Rest/binary>>) -> cookie_name(Rest); 479 cookie_name(Name) -> cookie_name(Name, <<>>). 480 481 cookie_name(<<>>, Name) -> Name; 482 cookie_name(<<$=, _/bits>>, Name) -> Name; 483 cookie_name(<<C, Rest/bits>>, Acc) -> cookie_name(Rest, <<Acc/binary, C>>). 484 485 -spec parse_cookies(req()) -> [{binary(), binary()}]. 486 parse_cookies(Req) -> 487 parse_header(<<"cookie">>, Req). 488 489 -spec match_cookies(cowboy:fields(), req()) -> map(). 490 match_cookies(Fields, Req) -> 491 case filter(Fields, kvlist_to_map(Fields, parse_cookies(Req))) of 492 {ok, Map} -> 493 Map; 494 {error, Errors} -> 495 exit({request_error, {match_cookies, Errors}, 496 'Cookie validation constraints failed for the reasons provided.'}) 497 end. 498 499 %% Request body. 500 501 -spec has_body(req()) -> boolean(). 502 has_body(#{has_body := HasBody}) -> 503 HasBody. 504 505 %% The length may not be known if HTTP/1.1 with a transfer-encoding; 506 %% or HTTP/2 with no content-length header. The length is always 507 %% known once the body has been completely read. 508 -spec body_length(req()) -> undefined | non_neg_integer(). 509 body_length(#{body_length := Length}) -> 510 Length. 511 512 -spec read_body(Req) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req(). 513 read_body(Req) -> 514 read_body(Req, #{}). 515 516 -spec read_body(Req, read_body_opts()) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req(). 517 read_body(Req=#{has_body := false}, _) -> 518 {ok, <<>>, Req}; 519 read_body(Req=#{has_read_body := true}, _) -> 520 {ok, <<>>, Req}; 521 read_body(Req, Opts) -> 522 Length = maps:get(length, Opts, 8000000), 523 Period = maps:get(period, Opts, 15000), 524 Timeout = maps:get(timeout, Opts, Period + 1000), 525 Ref = make_ref(), 526 cast({read_body, self(), Ref, Length, Period}, Req), 527 receive 528 {request_body, Ref, nofin, Body} -> 529 {more, Body, Req}; 530 {request_body, Ref, fin, BodyLength, Body} -> 531 {ok, Body, set_body_length(Req, BodyLength)} 532 after Timeout -> 533 exit(timeout) 534 end. 535 536 set_body_length(Req=#{headers := Headers}, BodyLength) -> 537 Req#{ 538 headers => Headers#{<<"content-length">> => integer_to_binary(BodyLength)}, 539 body_length => BodyLength, 540 has_read_body => true 541 }. 542 543 -spec read_urlencoded_body(Req) -> {ok, [{binary(), binary() | true}], Req} when Req::req(). 544 read_urlencoded_body(Req) -> 545 read_urlencoded_body(Req, #{length => 64000, period => 5000}). 546 547 -spec read_urlencoded_body(Req, read_body_opts()) -> {ok, [{binary(), binary() | true}], Req} when Req::req(). 548 read_urlencoded_body(Req0, Opts) -> 549 case read_body(Req0, Opts) of 550 {ok, Body, Req} -> 551 try 552 {ok, cow_qs:parse_qs(Body), Req} 553 catch _:_:Stacktrace -> 554 erlang:raise(exit, {request_error, urlencoded_body, 555 'Malformed body; application/x-www-form-urlencoded expected.' 556 }, Stacktrace) 557 end; 558 {more, Body, _} -> 559 Length = maps:get(length, Opts, 64000), 560 if 561 byte_size(Body) < Length -> 562 exit({request_error, timeout, 563 'The request body was not received within the configured time.'}); 564 true -> 565 exit({request_error, payload_too_large, 566 'The request body is larger than allowed by configuration.'}) 567 end 568 end. 569 570 -spec read_and_match_urlencoded_body(cowboy:fields(), Req) 571 -> {ok, map(), Req} when Req::req(). 572 read_and_match_urlencoded_body(Fields, Req) -> 573 read_and_match_urlencoded_body(Fields, Req, #{length => 64000, period => 5000}). 574 575 -spec read_and_match_urlencoded_body(cowboy:fields(), Req, read_body_opts()) 576 -> {ok, map(), Req} when Req::req(). 577 read_and_match_urlencoded_body(Fields, Req0, Opts) -> 578 {ok, Qs, Req} = read_urlencoded_body(Req0, Opts), 579 case filter(Fields, kvlist_to_map(Fields, Qs)) of 580 {ok, Map} -> 581 {ok, Map, Req}; 582 {error, Errors} -> 583 exit({request_error, {read_and_match_urlencoded_body, Errors}, 584 'Urlencoded request body validation constraints failed for the reasons provided.'}) 585 end. 586 587 %% Multipart. 588 589 -spec read_part(Req) 590 -> {ok, cowboy:http_headers(), Req} | {done, Req} 591 when Req::req(). 592 read_part(Req) -> 593 read_part(Req, #{length => 64000, period => 5000}). 594 595 -spec read_part(Req, read_body_opts()) 596 -> {ok, cowboy:http_headers(), Req} | {done, Req} 597 when Req::req(). 598 read_part(Req, Opts) -> 599 case maps:is_key(multipart, Req) of 600 true -> 601 {Data, Req2} = stream_multipart(Req, Opts, headers), 602 read_part(Data, Opts, Req2); 603 false -> 604 read_part(init_multipart(Req), Opts) 605 end. 606 607 read_part(Buffer, Opts, Req=#{multipart := {Boundary, _}}) -> 608 try cow_multipart:parse_headers(Buffer, Boundary) of 609 more -> 610 {Data, Req2} = stream_multipart(Req, Opts, headers), 611 read_part(<< Buffer/binary, Data/binary >>, Opts, Req2); 612 {more, Buffer2} -> 613 {Data, Req2} = stream_multipart(Req, Opts, headers), 614 read_part(<< Buffer2/binary, Data/binary >>, Opts, Req2); 615 {ok, Headers0, Rest} -> 616 Headers = maps:from_list(Headers0), 617 %% Reject multipart content containing duplicate headers. 618 true = map_size(Headers) =:= length(Headers0), 619 {ok, Headers, Req#{multipart => {Boundary, Rest}}}; 620 %% Ignore epilogue. 621 {done, _} -> 622 {done, Req#{multipart => done}} 623 catch _:_:Stacktrace -> 624 erlang:raise(exit, {request_error, {multipart, headers}, 625 'Malformed body; multipart expected.' 626 }, Stacktrace) 627 end. 628 629 -spec read_part_body(Req) 630 -> {ok, binary(), Req} | {more, binary(), Req} 631 when Req::req(). 632 read_part_body(Req) -> 633 read_part_body(Req, #{}). 634 635 -spec read_part_body(Req, read_body_opts()) 636 -> {ok, binary(), Req} | {more, binary(), Req} 637 when Req::req(). 638 read_part_body(Req, Opts) -> 639 case maps:is_key(multipart, Req) of 640 true -> 641 read_part_body(<<>>, Opts, Req, <<>>); 642 false -> 643 read_part_body(init_multipart(Req), Opts) 644 end. 645 646 read_part_body(Buffer, Opts, Req=#{multipart := {Boundary, _}}, Acc) -> 647 Length = maps:get(length, Opts, 8000000), 648 case byte_size(Acc) > Length of 649 true -> 650 {more, Acc, Req#{multipart => {Boundary, Buffer}}}; 651 false -> 652 {Data, Req2} = stream_multipart(Req, Opts, body), 653 case cow_multipart:parse_body(<< Buffer/binary, Data/binary >>, Boundary) of 654 {ok, Body} -> 655 read_part_body(<<>>, Opts, Req2, << Acc/binary, Body/binary >>); 656 {ok, Body, Rest} -> 657 read_part_body(Rest, Opts, Req2, << Acc/binary, Body/binary >>); 658 done -> 659 {ok, Acc, Req2}; 660 {done, Body} -> 661 {ok, << Acc/binary, Body/binary >>, Req2}; 662 {done, Body, Rest} -> 663 {ok, << Acc/binary, Body/binary >>, 664 Req2#{multipart => {Boundary, Rest}}} 665 end 666 end. 667 668 init_multipart(Req) -> 669 {<<"multipart">>, _, Params} = parse_header(<<"content-type">>, Req), 670 case lists:keyfind(<<"boundary">>, 1, Params) of 671 {_, Boundary} -> 672 Req#{multipart => {Boundary, <<>>}}; 673 false -> 674 exit({request_error, {multipart, boundary}, 675 'Missing boundary parameter for multipart media type.'}) 676 end. 677 678 stream_multipart(Req=#{multipart := done}, _, _) -> 679 {<<>>, Req}; 680 stream_multipart(Req=#{multipart := {_, <<>>}}, Opts, Type) -> 681 case read_body(Req, Opts) of 682 {more, Data, Req2} -> 683 {Data, Req2}; 684 %% We crash when the data ends unexpectedly. 685 {ok, <<>>, _} -> 686 exit({request_error, {multipart, Type}, 687 'Malformed body; multipart expected.'}); 688 {ok, Data, Req2} -> 689 {Data, Req2} 690 end; 691 stream_multipart(Req=#{multipart := {Boundary, Buffer}}, _, _) -> 692 {Buffer, Req#{multipart => {Boundary, <<>>}}}. 693 694 %% Response. 695 696 -spec set_resp_cookie(iodata(), iodata(), Req) 697 -> Req when Req::req(). 698 set_resp_cookie(Name, Value, Req) -> 699 set_resp_cookie(Name, Value, Req, #{}). 700 701 %% The cookie name cannot contain any of the following characters: 702 %% =,;\s\t\r\n\013\014 703 %% 704 %% The cookie value cannot contain any of the following characters: 705 %% ,; \t\r\n\013\014 706 -spec set_resp_cookie(binary(), iodata(), Req, cow_cookie:cookie_opts()) 707 -> Req when Req::req(). 708 set_resp_cookie(Name, Value, Req, Opts) -> 709 Cookie = cow_cookie:setcookie(Name, Value, Opts), 710 RespCookies = maps:get(resp_cookies, Req, #{}), 711 Req#{resp_cookies => RespCookies#{Name => Cookie}}. 712 713 %% @todo We could add has_resp_cookie and delete_resp_cookie now. 714 715 -spec set_resp_header(binary(), iodata(), Req) 716 -> Req when Req::req(). 717 set_resp_header(Name, Value, Req=#{resp_headers := RespHeaders}) -> 718 Req#{resp_headers => RespHeaders#{Name => Value}}; 719 set_resp_header(Name,Value, Req) -> 720 Req#{resp_headers => #{Name => Value}}. 721 722 -spec set_resp_headers(cowboy:http_headers(), Req) 723 -> Req when Req::req(). 724 set_resp_headers(Headers, Req=#{resp_headers := RespHeaders}) -> 725 Req#{resp_headers => maps:merge(RespHeaders, Headers)}; 726 set_resp_headers(Headers, Req) -> 727 Req#{resp_headers => Headers}. 728 729 -spec resp_header(binary(), req()) -> binary() | undefined. 730 resp_header(Name, Req) -> 731 resp_header(Name, Req, undefined). 732 733 -spec resp_header(binary(), req(), Default) 734 -> binary() | Default when Default::any(). 735 resp_header(Name, #{resp_headers := Headers}, Default) -> 736 maps:get(Name, Headers, Default); 737 resp_header(_, #{}, Default) -> 738 Default. 739 740 -spec resp_headers(req()) -> cowboy:http_headers(). 741 resp_headers(#{resp_headers := RespHeaders}) -> 742 RespHeaders; 743 resp_headers(#{}) -> 744 #{}. 745 746 -spec set_resp_body(resp_body(), Req) -> Req when Req::req(). 747 set_resp_body(Body, Req) -> 748 Req#{resp_body => Body}. 749 750 -spec has_resp_header(binary(), req()) -> boolean(). 751 has_resp_header(Name, #{resp_headers := RespHeaders}) -> 752 maps:is_key(Name, RespHeaders); 753 has_resp_header(_, _) -> 754 false. 755 756 -spec has_resp_body(req()) -> boolean(). 757 has_resp_body(#{resp_body := {sendfile, _, _, _}}) -> 758 true; 759 has_resp_body(#{resp_body := RespBody}) -> 760 iolist_size(RespBody) > 0; 761 has_resp_body(_) -> 762 false. 763 764 -spec delete_resp_header(binary(), Req) 765 -> Req when Req::req(). 766 delete_resp_header(Name, Req=#{resp_headers := RespHeaders}) -> 767 Req#{resp_headers => maps:remove(Name, RespHeaders)}; 768 %% There are no resp headers so we have nothing to delete. 769 delete_resp_header(_, Req) -> 770 Req. 771 772 -spec inform(cowboy:http_status(), req()) -> ok. 773 inform(Status, Req) -> 774 inform(Status, #{}, Req). 775 776 -spec inform(cowboy:http_status(), cowboy:http_headers(), req()) -> ok. 777 inform(_, _, #{has_sent_resp := _}) -> 778 error(function_clause); %% @todo Better error message. 779 inform(Status, Headers, Req) when is_integer(Status); is_binary(Status) -> 780 cast({inform, Status, Headers}, Req). 781 782 -spec reply(cowboy:http_status(), Req) -> Req when Req::req(). 783 reply(Status, Req) -> 784 reply(Status, #{}, Req). 785 786 -spec reply(cowboy:http_status(), cowboy:http_headers(), Req) 787 -> Req when Req::req(). 788 reply(Status, Headers, Req=#{resp_body := Body}) -> 789 reply(Status, Headers, Body, Req); 790 reply(Status, Headers, Req) -> 791 reply(Status, Headers, <<>>, Req). 792 793 -spec reply(cowboy:http_status(), cowboy:http_headers(), resp_body(), Req) 794 -> Req when Req::req(). 795 reply(_, _, _, #{has_sent_resp := _}) -> 796 error(function_clause); %% @todo Better error message. 797 reply(Status, Headers, {sendfile, _, 0, _}, Req) 798 when is_integer(Status); is_binary(Status) -> 799 do_reply(Status, Headers#{ 800 <<"content-length">> => <<"0">> 801 }, <<>>, Req); 802 reply(Status, Headers, SendFile = {sendfile, _, Len, _}, Req) 803 when is_integer(Status); is_binary(Status) -> 804 do_reply(Status, Headers#{ 805 <<"content-length">> => integer_to_binary(Len) 806 }, SendFile, Req); 807 %% 204 responses must not include content-length. 304 responses may 808 %% but only when set explicitly. (RFC7230 3.3.1, RFC7230 3.3.2) 809 %% Neither status code must include a response body. (RFC7230 3.3) 810 reply(Status, Headers, Body, Req) 811 when Status =:= 204; Status =:= 304 -> 812 0 = iolist_size(Body), 813 do_reply(Status, Headers, Body, Req); 814 reply(Status = <<"204",_/bits>>, Headers, Body, Req) -> 815 0 = iolist_size(Body), 816 do_reply(Status, Headers, Body, Req); 817 reply(Status = <<"304",_/bits>>, Headers, Body, Req) -> 818 0 = iolist_size(Body), 819 do_reply(Status, Headers, Body, Req); 820 reply(Status, Headers, Body, Req) 821 when is_integer(Status); is_binary(Status) -> 822 do_reply(Status, Headers#{ 823 <<"content-length">> => integer_to_binary(iolist_size(Body)) 824 }, Body, Req). 825 826 %% Don't send any body for HEAD responses. While the protocol code is 827 %% supposed to enforce this rule, we prefer to avoid copying too much 828 %% data around if we can avoid it. 829 do_reply(Status, Headers, _, Req=#{method := <<"HEAD">>}) -> 830 cast({response, Status, response_headers(Headers, Req), <<>>}, Req), 831 done_replying(Req, true); 832 do_reply(Status, Headers, Body, Req) -> 833 cast({response, Status, response_headers(Headers, Req), Body}, Req), 834 done_replying(Req, true). 835 836 done_replying(Req, HasSentResp) -> 837 maps:without([resp_cookies, resp_headers, resp_body], Req#{has_sent_resp => HasSentResp}). 838 839 -spec stream_reply(cowboy:http_status(), Req) -> Req when Req::req(). 840 stream_reply(Status, Req) -> 841 stream_reply(Status, #{}, Req). 842 843 -spec stream_reply(cowboy:http_status(), cowboy:http_headers(), Req) 844 -> Req when Req::req(). 845 stream_reply(_, _, #{has_sent_resp := _}) -> 846 error(function_clause); 847 %% 204 and 304 responses must NOT send a body. We therefore 848 %% transform the call to a full response and expect the user 849 %% to NOT call stream_body/3 afterwards. (RFC7230 3.3) 850 stream_reply(Status = 204, Headers=#{}, Req) -> 851 reply(Status, Headers, <<>>, Req); 852 stream_reply(Status = <<"204",_/bits>>, Headers=#{}, Req) -> 853 reply(Status, Headers, <<>>, Req); 854 stream_reply(Status = 304, Headers=#{}, Req) -> 855 reply(Status, Headers, <<>>, Req); 856 stream_reply(Status = <<"304",_/bits>>, Headers=#{}, Req) -> 857 reply(Status, Headers, <<>>, Req); 858 stream_reply(Status, Headers=#{}, Req) when is_integer(Status); is_binary(Status) -> 859 cast({headers, Status, response_headers(Headers, Req)}, Req), 860 done_replying(Req, headers). 861 862 -spec stream_body(resp_body(), fin | nofin, req()) -> ok. 863 %% Error out if headers were not sent. 864 %% Don't send any body for HEAD responses. 865 stream_body(_, _, #{method := <<"HEAD">>, has_sent_resp := headers}) -> 866 ok; 867 %% Don't send a message if the data is empty, except for the 868 %% very last message with IsFin=fin. When using sendfile this 869 %% is converted to a data tuple, however. 870 stream_body({sendfile, _, 0, _}, nofin, _) -> 871 ok; 872 stream_body({sendfile, _, 0, _}, IsFin=fin, Req=#{has_sent_resp := headers}) -> 873 stream_body({data, self(), IsFin, <<>>}, Req); 874 stream_body({sendfile, O, B, P}, IsFin, Req=#{has_sent_resp := headers}) 875 when is_integer(O), O >= 0, is_integer(B), B > 0 -> 876 stream_body({data, self(), IsFin, {sendfile, O, B, P}}, Req); 877 stream_body(Data, IsFin=nofin, Req=#{has_sent_resp := headers}) 878 when not is_tuple(Data) -> 879 case iolist_size(Data) of 880 0 -> ok; 881 _ -> stream_body({data, self(), IsFin, Data}, Req) 882 end; 883 stream_body(Data, IsFin, Req=#{has_sent_resp := headers}) 884 when not is_tuple(Data) -> 885 stream_body({data, self(), IsFin, Data}, Req). 886 887 %% @todo Do we need a timeout? 888 stream_body(Msg, Req=#{pid := Pid}) -> 889 cast(Msg, Req), 890 receive {data_ack, Pid} -> ok end. 891 892 -spec stream_events(cow_sse:event() | [cow_sse:event()], fin | nofin, req()) -> ok. 893 stream_events(Event, IsFin, Req) when is_map(Event) -> 894 stream_events([Event], IsFin, Req); 895 stream_events(Events, IsFin, Req=#{has_sent_resp := headers}) -> 896 stream_body({data, self(), IsFin, cow_sse:events(Events)}, Req). 897 898 -spec stream_trailers(cowboy:http_headers(), req()) -> ok. 899 stream_trailers(Trailers, Req=#{has_sent_resp := headers}) -> 900 cast({trailers, Trailers}, Req). 901 902 -spec push(iodata(), cowboy:http_headers(), req()) -> ok. 903 push(Path, Headers, Req) -> 904 push(Path, Headers, Req, #{}). 905 906 %% @todo Optimization: don't send anything at all for HTTP/1.0 and HTTP/1.1. 907 %% @todo Path, Headers, Opts, everything should be in proper binary, 908 %% or normalized when creating the Req object. 909 -spec push(iodata(), cowboy:http_headers(), req(), push_opts()) -> ok. 910 push(Path, Headers, Req=#{scheme := Scheme0, host := Host0, port := Port0}, Opts) -> 911 Method = maps:get(method, Opts, <<"GET">>), 912 Scheme = maps:get(scheme, Opts, Scheme0), 913 Host = maps:get(host, Opts, Host0), 914 Port = maps:get(port, Opts, Port0), 915 Qs = maps:get(qs, Opts, <<>>), 916 cast({push, Method, Scheme, Host, Port, Path, Qs, Headers}, Req). 917 918 %% Stream handlers. 919 920 -spec cast(any(), req()) -> ok. 921 cast(Msg, #{pid := Pid, streamid := StreamID}) -> 922 Pid ! {{Pid, StreamID}, Msg}, 923 ok. 924 925 %% Internal. 926 927 %% @todo What about set-cookie headers set through set_resp_header or reply? 928 -spec response_headers(Headers, req()) -> Headers when Headers::cowboy:http_headers(). 929 response_headers(Headers0, Req) -> 930 RespHeaders = maps:get(resp_headers, Req, #{}), 931 Headers = maps:merge(#{ 932 <<"date">> => cowboy_clock:rfc1123(), 933 <<"server">> => <<"Cowboy">> 934 }, maps:merge(RespHeaders, Headers0)), 935 %% The set-cookie header is special; we can only send one cookie per header. 936 %% We send the list of values for many cookies in one key of the map, 937 %% and let the protocols deal with it directly. 938 case maps:get(resp_cookies, Req, undefined) of 939 undefined -> Headers; 940 RespCookies -> Headers#{<<"set-cookie">> => maps:values(RespCookies)} 941 end. 942 943 %% Create map, convert keys to atoms and group duplicate keys into lists. 944 %% Keys that are not found in the user provided list are entirely skipped. 945 %% @todo Can probably be done directly while parsing. 946 kvlist_to_map(Fields, KvList) -> 947 Keys = [case K of 948 {Key, _} -> Key; 949 {Key, _, _} -> Key; 950 Key -> Key 951 end || K <- Fields], 952 kvlist_to_map(Keys, KvList, #{}). 953 954 kvlist_to_map(_, [], Map) -> 955 Map; 956 kvlist_to_map(Keys, [{Key, Value}|Tail], Map) -> 957 try binary_to_existing_atom(Key, utf8) of 958 Atom -> 959 case lists:member(Atom, Keys) of 960 true -> 961 case maps:find(Atom, Map) of 962 {ok, MapValue} when is_list(MapValue) -> 963 kvlist_to_map(Keys, Tail, 964 Map#{Atom => [Value|MapValue]}); 965 {ok, MapValue} -> 966 kvlist_to_map(Keys, Tail, 967 Map#{Atom => [Value, MapValue]}); 968 error -> 969 kvlist_to_map(Keys, Tail, 970 Map#{Atom => Value}) 971 end; 972 false -> 973 kvlist_to_map(Keys, Tail, Map) 974 end 975 catch error:badarg -> 976 kvlist_to_map(Keys, Tail, Map) 977 end. 978 979 filter(Fields, Map0) -> 980 filter(Fields, Map0, #{}). 981 982 %% Loop through fields, if value is missing and no default, 983 %% record the error; else if value is missing and has a 984 %% default, set default; otherwise apply constraints. If 985 %% constraint fails, record the error. 986 %% 987 %% When there is an error at the end, crash. 988 filter([], Map, Errors) -> 989 case maps:size(Errors) of 990 0 -> {ok, Map}; 991 _ -> {error, Errors} 992 end; 993 filter([{Key, Constraints}|Tail], Map, Errors) -> 994 filter_constraints(Tail, Map, Errors, Key, maps:get(Key, Map), Constraints); 995 filter([{Key, Constraints, Default}|Tail], Map, Errors) -> 996 case maps:find(Key, Map) of 997 {ok, Value} -> 998 filter_constraints(Tail, Map, Errors, Key, Value, Constraints); 999 error -> 1000 filter(Tail, Map#{Key => Default}, Errors) 1001 end; 1002 filter([Key|Tail], Map, Errors) -> 1003 case maps:is_key(Key, Map) of 1004 true -> 1005 filter(Tail, Map, Errors); 1006 false -> 1007 filter(Tail, Map, Errors#{Key => required}) 1008 end. 1009 1010 filter_constraints(Tail, Map, Errors, Key, Value0, Constraints) -> 1011 case cowboy_constraints:validate(Value0, Constraints) of 1012 {ok, Value} -> 1013 filter(Tail, Map#{Key => Value}, Errors); 1014 {error, Reason} -> 1015 filter(Tail, Map, Errors#{Key => Reason}) 1016 end.