rfc7231_SUITE.erl 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. %% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. -module(rfc7231_SUITE).
  15. -compile(export_all).
  16. -compile(nowarn_export_all).
  17. -import(ct_helper, [config/2]).
  18. -import(ct_helper, [doc/1]).
  19. -import(cowboy_test, [gun_open/1]).
  20. -import(cowboy_test, [raw_open/1]).
  21. -import(cowboy_test, [raw_send/2]).
  22. -import(cowboy_test, [raw_recv_head/1]).
  23. -import(cowboy_test, [raw_recv/3]).
  24. all() ->
  25. cowboy_test:common_all().
  26. groups() ->
  27. cowboy_test:common_groups(ct_helper:all(?MODULE)).
  28. init_per_group(Name, Config) ->
  29. cowboy_test:init_common_groups(Name, Config, ?MODULE).
  30. end_per_group(Name, _) ->
  31. cowboy:stop_listener(Name).
  32. init_dispatch(_) ->
  33. cowboy_router:compile([{"[...]", [
  34. {"*", asterisk_h, []},
  35. {"/", hello_h, []},
  36. {"/echo/:key", echo_h, []},
  37. {"/delay/echo/:key", echo_h, []},
  38. {"/resp/:key[/:arg]", resp_h, []},
  39. {"/ws", ws_init_h, []}
  40. ]}]).
  41. %% @todo The documentation should list what methods, headers and status codes
  42. %% are handled automatically so users can know what befalls to them to implement.
  43. %% Representations.
  44. %% Cowboy has cowboy_compress_h that could be concerned with this.
  45. %% However Cowboy will not attempt to compress if any content-coding
  46. %% is already applied, regardless of what they are.
  47. %
  48. % If one or more encodings have been applied to a representation, the
  49. % sender that applied the encodings MUST generate a Content-Encoding
  50. % header field that lists the content codings in the order in which
  51. % they were applied. Additional information about the encoding
  52. % parameters can be provided by other header fields not defined by this
  53. % specification. (RFC7231 3.1.2.2)
  54. %% Methods.
  55. method_get(Config) ->
  56. doc("The GET method is accepted. (RFC7231 4.3.1)"),
  57. ConnPid = gun_open(Config),
  58. Ref = gun:get(ConnPid, "/", [
  59. {<<"accept-encoding">>, <<"gzip">>}
  60. ]),
  61. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  62. {ok, <<"Hello world!">>} = gun:await_body(ConnPid, Ref),
  63. ok.
  64. method_head(Config) ->
  65. doc("The HEAD method is accepted. (RFC7231 4.3.2)"),
  66. ConnPid = gun_open(Config),
  67. Ref = gun:head(ConnPid, "/", [
  68. {<<"accept-encoding">>, <<"gzip">>}
  69. ]),
  70. {response, fin, 200, _} = gun:await(ConnPid, Ref),
  71. ok.
  72. method_head_same_resp_headers_as_get(Config) ->
  73. doc("Responses to HEAD should return the same headers as GET. (RFC7231 4.3.2)"),
  74. ConnPid = gun_open(Config),
  75. Ref1 = gun:get(ConnPid, "/", [
  76. {<<"accept-encoding">>, <<"gzip">>}
  77. ]),
  78. {response, nofin, 200, Headers1} = gun:await(ConnPid, Ref1),
  79. {ok, <<"Hello world!">>} = gun:await_body(ConnPid, Ref1),
  80. Ref2 = gun:head(ConnPid, "/", [
  81. {<<"accept-encoding">>, <<"gzip">>}
  82. ]),
  83. {response, fin, 200, Headers2} = gun:await(ConnPid, Ref2),
  84. %% We remove the date header since the date might have changed between requests.
  85. Headers = lists:keydelete(<<"date">>, 1, Headers1),
  86. Headers = lists:keydelete(<<"date">>, 1, Headers2),
  87. ok.
  88. method_head_same_resp_headers_as_get_stream_reply(Config) ->
  89. doc("Responses to HEAD should return the same headers as GET. (RFC7231 4.3.2)"),
  90. ConnPid = gun_open(Config),
  91. Ref1 = gun:get(ConnPid, "/resp/stream_reply2/200", [
  92. {<<"accept-encoding">>, <<"gzip">>}
  93. ]),
  94. {response, nofin, 200, Headers1} = gun:await(ConnPid, Ref1),
  95. {ok, _} = gun:await_body(ConnPid, Ref1),
  96. Ref2 = gun:head(ConnPid, "/resp/stream_reply2/200", [
  97. {<<"accept-encoding">>, <<"gzip">>}
  98. ]),
  99. {response, fin, 200, Headers2} = gun:await(ConnPid, Ref2),
  100. %% We remove the date header since the date might have changed between requests.
  101. Headers = lists:keydelete(<<"date">>, 1, Headers1),
  102. Headers = lists:keydelete(<<"date">>, 1, Headers2),
  103. ok.
  104. method_post(Config) ->
  105. doc("The POST method is accepted. (RFC7231 4.3.3)"),
  106. ConnPid = gun_open(Config),
  107. Ref = gun:post(ConnPid, "/echo/read_body", [
  108. {<<"accept-encoding">>, <<"gzip">>},
  109. {<<"content-type">>, <<"application/x-www-form-urlencoded">>}
  110. ], <<"hello=world">>),
  111. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  112. {ok, <<"hello=world">>} = gun:await_body(ConnPid, Ref),
  113. ok.
  114. method_put(Config) ->
  115. doc("The PUT method is accepted. (RFC7231 4.3.4)"),
  116. ConnPid = gun_open(Config),
  117. Ref = gun:put(ConnPid, "/echo/read_body", [
  118. {<<"accept-encoding">>, <<"gzip">>},
  119. {<<"content-type">>, <<"application/x-www-form-urlencoded">>}
  120. ], <<"hello=world">>),
  121. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  122. {ok, <<"hello=world">>} = gun:await_body(ConnPid, Ref),
  123. ok.
  124. method_delete(Config) ->
  125. doc("The DELETE method is accepted. (RFC7231 4.3.5)"),
  126. ConnPid = gun_open(Config),
  127. Ref = gun:delete(ConnPid, "/echo/method", [
  128. {<<"accept-encoding">>, <<"gzip">>}
  129. ]),
  130. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  131. {ok, <<"DELETE">>} = gun:await_body(ConnPid, Ref),
  132. ok.
  133. method_connect(Config) ->
  134. doc("The CONNECT method is currently not implemented. (RFC7231 4.3.6)"),
  135. ConnPid = gun_open(Config),
  136. Ref = gun:request(ConnPid, <<"CONNECT">>, "localhost:8080", [
  137. {<<"accept-encoding">>, <<"gzip">>}
  138. ]),
  139. {response, fin, 501, _} = gun:await(ConnPid, Ref),
  140. ok.
  141. % A client sending a CONNECT request MUST send the authority form of
  142. % request-target (Section 5.3 of [RFC7230]); i.e., the request-target
  143. % consists of only the host name and port number of the tunnel
  144. % destination, separated by a colon.
  145. %
  146. % A server MUST NOT send any Transfer-Encoding or Content-Length header
  147. % fields in a 2xx (Successful) response to CONNECT. A client MUST
  148. % ignore any Content-Length or Transfer-Encoding header fields received
  149. % in a successful response to CONNECT.
  150. %
  151. % A payload within a CONNECT request message has no defined semantics;
  152. % sending a payload body on a CONNECT request might cause some existing
  153. % implementations to reject the request.
  154. method_options(Config) ->
  155. doc("The OPTIONS method is accepted. (RFC7231 4.3.7)"),
  156. ConnPid = gun_open(Config),
  157. Ref = gun:options(ConnPid, "/echo/method", [
  158. {<<"accept-encoding">>, <<"gzip">>}
  159. ]),
  160. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  161. {ok, <<"OPTIONS">>} = gun:await_body(ConnPid, Ref),
  162. ok.
  163. method_options_asterisk(Config) ->
  164. doc("The OPTIONS method is accepted with an asterisk. (RFC7231 4.3.7)"),
  165. ConnPid = gun_open(Config),
  166. Ref = gun:options(ConnPid, "*", [
  167. {<<"accept-encoding">>, <<"gzip">>},
  168. {<<"x-echo">>, <<"method">>}
  169. ]),
  170. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  171. {ok, <<"OPTIONS">>} = gun:await_body(ConnPid, Ref),
  172. ok.
  173. method_options_content_length_0(Config) ->
  174. doc("The OPTIONS method must set the content-length header "
  175. "to 0 when no body is returned. (RFC7231 4.3.7)"),
  176. ConnPid = gun_open(Config),
  177. Ref = gun:options(ConnPid, "*", [
  178. {<<"accept-encoding">>, <<"gzip">>}
  179. ]),
  180. {response, fin, 200, Headers} = gun:await(ConnPid, Ref),
  181. {_, <<"0">>} = lists:keyfind(<<"content-length">>, 1, Headers),
  182. ok.
  183. method_trace(Config) ->
  184. doc("The TRACE method is currently not implemented. (RFC7231 4.3.8)"),
  185. ConnPid = gun_open(Config),
  186. Ref = gun:request(ConnPid, <<"TRACE">>, "/", [
  187. {<<"accept-encoding">>, <<"gzip">>}
  188. ]),
  189. {response, fin, 501, _} = gun:await(ConnPid, Ref),
  190. ok.
  191. %% Request headers.
  192. %% @todo It could be useful to check that we can parse all request headers defined in this RFC.
  193. %% @todo The same applies to any other RFC for which we have a test suite.
  194. expect(Config) ->
  195. doc("A server that receives a 100-continue expectation should honor it. (RFC7231 5.1.1)"),
  196. ConnPid = gun_open(Config),
  197. Ref = gun:post(ConnPid, "/echo/read_body", [
  198. {<<"accept-encoding">>, <<"gzip">>},
  199. {<<"content-type">>, <<"application/x-www-form-urlencoded">>},
  200. {<<"expect">>, <<"100-continue">>}
  201. ]),
  202. {inform, 100, _} = gun:await(ConnPid, Ref),
  203. ok.
  204. http10_expect(Config) ->
  205. case config(protocol, Config) of
  206. http ->
  207. do_http10_expect(Config);
  208. http2 ->
  209. expect(Config)
  210. end.
  211. do_http10_expect(Config) ->
  212. doc("A server that receives a 100-continue expectation "
  213. "in an HTTP/1.0 request must ignore it. (RFC7231 5.1.1)"),
  214. Body = <<"hello=world">>,
  215. ConnPid = gun_open([{http_opts, #{version => 'HTTP/1.0'}}|Config]),
  216. Ref = gun:post(ConnPid, "/echo/read_body", [
  217. {<<"accept-encoding">>, <<"gzip">>},
  218. {<<"content-type">>, <<"application/x-www-form-urlencoded">>},
  219. {<<"content-length">>, integer_to_binary(byte_size(Body))},
  220. {<<"expect">>, <<"100-continue">>}
  221. ]),
  222. timer:sleep(500),
  223. ok = gun:data(ConnPid, Ref, fin, Body),
  224. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  225. {ok, Body} = gun:await_body(ConnPid, Ref),
  226. ok.
  227. %% Cowboy ignores the expect header when the value is not 100-continue.
  228. %
  229. % A server that receives an Expect field-value other than 100-continue
  230. % MAY respond with a 417 (Expectation Failed) status code to indicate
  231. % that the unexpected expectation cannot be met.
  232. expect_receive_body_omit_100_continue(Config) ->
  233. doc("A server may omit sending a 100 Continue response if it has "
  234. "already started receiving the request body. (RFC7231 5.1.1)"),
  235. ConnPid = gun_open(Config),
  236. Ref = gun:post(ConnPid, "/delay/echo/read_body", [
  237. {<<"accept-encoding">>, <<"gzip">>},
  238. {<<"content-type">>, <<"application/x-www-form-urlencoded">>},
  239. {<<"expect">>, <<"100-continue">>}
  240. ], <<"hello=world">>),
  241. %% We receive the response directly without a 100 Continue.
  242. {response, nofin, 200, _} = gun:await(ConnPid, Ref),
  243. {ok, <<"hello=world">>} = gun:await_body(ConnPid, Ref),
  244. ok.
  245. expect_discard_body_skip(Config) ->
  246. doc("A server that responds with a final status code before reading "
  247. "the entire message body should keep the connection open and skip "
  248. "the body when appropriate. (RFC7231 5.1.1)"),
  249. ConnPid = gun_open(Config),
  250. Ref1 = gun:post(ConnPid, "/echo/method", [
  251. {<<"accept-encoding">>, <<"gzip">>},
  252. {<<"content-type">>, <<"application/x-www-form-urlencoded">>},
  253. {<<"expect">>, <<"100-continue">>}
  254. ], <<"hello=world">>),
  255. {response, nofin, 200, _} = gun:await(ConnPid, Ref1),
  256. {ok, <<"POST">>} = gun:await_body(ConnPid, Ref1),
  257. Ref2 = gun:get(ConnPid, "/echo/method", [
  258. {<<"accept-encoding">>, <<"gzip">>},
  259. {<<"content-type">>, <<"application/x-www-form-urlencoded">>}
  260. ]),
  261. {response, nofin, 200, _} = gun:await(ConnPid, Ref2),
  262. {ok, <<"GET">>} = gun:await_body(ConnPid, Ref2),
  263. ok.
  264. expect_discard_body_close(Config) ->
  265. case config(protocol, Config) of
  266. http ->
  267. do_expect_discard_body_close(Config);
  268. http2 ->
  269. doc("There's no reason to close the connection when using HTTP/2, "
  270. "even if a stream body is too large. We just cancel the stream.")
  271. end.
  272. do_expect_discard_body_close(Config) ->
  273. doc("A server that responds with a final status code before reading "
  274. "the entire message body may close the connection to avoid "
  275. "reading a potentially large request body. (RFC7231 5.1.1, RFC7230 6.6)"),
  276. ConnPid = gun_open(Config),
  277. Ref1 = gun:post(ConnPid, "/echo/method", [
  278. {<<"accept-encoding">>, <<"gzip">>},
  279. {<<"content-length">>, <<"10000000">>},
  280. {<<"content-type">>, <<"application/x-www-form-urlencoded">>},
  281. {<<"expect">>, <<"100-continue">>}
  282. ]),
  283. {response, nofin, 200, _Headers} = gun:await(ConnPid, Ref1),
  284. %% Ideally we would send a connection: close. Cowboy however
  285. %% cannot know the intent of the application until after we
  286. %% sent the response.
  287. % {_, <<"close">>} = lists:keyfind(<<"connection">>, 1, Headers),
  288. {ok, <<"POST">>} = gun:await_body(ConnPid, Ref1),
  289. %% The connection is gone.
  290. receive
  291. {gun_down, ConnPid, _, closed, _, _} ->
  292. ok
  293. after 1000 ->
  294. error(timeout)
  295. end.
  296. no_accept_encoding(Config) ->
  297. doc("While a request with no accept-encoding header implies the "
  298. "user agent has no preferences and any would be acceptable, "
  299. "Cowboy will not serve content-codings by defaults to ensure "
  300. "the content can safely be read. (RFC7231 5.3.4)"),
  301. ConnPid = gun_open(Config),
  302. Ref = gun:get(ConnPid, "/resp/stream_reply2/200"),
  303. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  304. false = lists:keyfind(<<"content-encoding">>, 1, Headers),
  305. ok.
  306. %% Cowboy currently ignores any information about the identity content-coding
  307. %% and instead considers it always acceptable.
  308. %
  309. % 2. If the representation has no content-coding, then it is
  310. % acceptable by default unless specifically excluded by the
  311. % Accept-Encoding field stating either "identity;q=0" or "*;q=0"
  312. % without a more specific entry for "identity".
  313. accept_encoding_gzip(Config) ->
  314. doc("No qvalue means the content-coding is acceptable. (RFC7231 5.3.4)"),
  315. ConnPid = gun_open(Config),
  316. Ref = gun:get(ConnPid, "/resp/stream_reply2/200", [
  317. {<<"accept-encoding">>, <<"gzip">>}
  318. ]),
  319. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  320. _ = case config(flavor, Config) of
  321. compress ->
  322. {_, <<"gzip">>} = lists:keyfind(<<"content-encoding">>, 1, Headers);
  323. _ ->
  324. false = lists:keyfind(<<"content-encoding">>, 1, Headers)
  325. end,
  326. ok.
  327. accept_encoding_gzip_1(Config) ->
  328. doc("A qvalue different than 0 means the content-coding is acceptable. (RFC7231 5.3.4)"),
  329. ConnPid = gun_open(Config),
  330. Ref = gun:get(ConnPid, "/resp/stream_reply2/200", [
  331. {<<"accept-encoding">>, <<"gzip;q=1.0">>}
  332. ]),
  333. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  334. _ = case config(flavor, Config) of
  335. compress ->
  336. {_, <<"gzip">>} = lists:keyfind(<<"content-encoding">>, 1, Headers);
  337. _ ->
  338. false = lists:keyfind(<<"content-encoding">>, 1, Headers)
  339. end,
  340. ok.
  341. accept_encoding_gzip_0(Config) ->
  342. doc("A qvalue of 0 means the content-coding is not acceptable. (RFC7231 5.3.1, RFC7231 5.3.4)"),
  343. ConnPid = gun_open(Config),
  344. Ref = gun:get(ConnPid, "/resp/stream_reply2/200", [
  345. {<<"accept-encoding">>, <<"gzip;q=0">>}
  346. ]),
  347. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  348. false = lists:keyfind(<<"content-encoding">>, 1, Headers),
  349. ok.
  350. %% Cowboy currently only supports gzip automatically via cowboy_compress_h.
  351. %
  352. % 4. If multiple content-codings are acceptable, then the acceptable
  353. % content-coding with the highest non-zero qvalue is preferred.
  354. accept_encoding_empty(Config) ->
  355. doc("An empty content-coding means that the user agent does not "
  356. "want any content-coding applied to the response. (RFC7231 5.3.4)"),
  357. ConnPid = gun_open(Config),
  358. Ref = gun:get(ConnPid, "/resp/stream_reply2/200", [
  359. {<<"accept-encoding">>, <<>>}
  360. ]),
  361. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  362. false = lists:keyfind(<<"content-encoding">>, 1, Headers),
  363. ok.
  364. accept_encoding_unknown(Config) ->
  365. doc("An accept-encoding header only containing unknown content-codings "
  366. "should result in no content-coding being applied. (RFC7231 5.3.4)"),
  367. ConnPid = gun_open(Config),
  368. Ref = gun:get(ConnPid, "/resp/stream_reply2/200", [
  369. {<<"accept-encoding">>, <<"deflate">>}
  370. ]),
  371. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  372. false = lists:keyfind(<<"content-encoding">>, 1, Headers),
  373. ok.
  374. %% Status codes.
  375. http10_status_code_100(Config) ->
  376. case config(protocol, Config) of
  377. http ->
  378. doc("The 100 Continue status code must not "
  379. "be sent to HTTP/1.0 endpoints. (RFC7231 6.2)"),
  380. do_http10_status_code_1xx(100, Config);
  381. http2 ->
  382. status_code_100(Config)
  383. end.
  384. http10_status_code_101(Config) ->
  385. case config(protocol, Config) of
  386. http ->
  387. doc("The 101 Switching Protocols status code must not "
  388. "be sent to HTTP/1.0 endpoints. (RFC7231 6.2)"),
  389. do_http10_status_code_1xx(101, Config);
  390. http2 ->
  391. status_code_101(Config)
  392. end.
  393. do_http10_status_code_1xx(StatusCode, Config) ->
  394. ConnPid = gun_open([{http_opts, #{version => 'HTTP/1.0'}}|Config]),
  395. Ref = gun:get(ConnPid, "/resp/inform2/" ++ integer_to_list(StatusCode), [
  396. {<<"accept-encoding">>, <<"gzip">>}
  397. ]),
  398. {response, _, 200, _} = gun:await(ConnPid, Ref),
  399. ok.
  400. status_code_100(Config) ->
  401. doc("The 100 Continue status code can be sent. (RFC7231 6.2.1)"),
  402. ConnPid = gun_open(Config),
  403. Ref = gun:get(ConnPid, "/resp/inform2/100", [
  404. {<<"accept-encoding">>, <<"gzip">>}
  405. ]),
  406. {inform, 100, []} = gun:await(ConnPid, Ref),
  407. ok.
  408. status_code_101(Config) ->
  409. doc("The 101 Switching Protocols status code can be sent. (RFC7231 6.2.2)"),
  410. ConnPid = gun_open(Config),
  411. Ref = gun:get(ConnPid, "/resp/inform2/101", [
  412. {<<"accept-encoding">>, <<"gzip">>}
  413. ]),
  414. {inform, 101, []} = gun:await(ConnPid, Ref),
  415. ok.
  416. status_code_200(Config) ->
  417. doc("The 200 OK status code can be sent. (RFC7231 6.3.1)"),
  418. ConnPid = gun_open(Config),
  419. Ref = gun:get(ConnPid, "/resp/reply2/200", [
  420. {<<"accept-encoding">>, <<"gzip">>}
  421. ]),
  422. {response, _, 200, _} = gun:await(ConnPid, Ref),
  423. ok.
  424. status_code_201(Config) ->
  425. doc("The 201 Created status code can be sent. (RFC7231 6.3.2)"),
  426. ConnPid = gun_open(Config),
  427. Ref = gun:get(ConnPid, "/resp/reply2/201", [
  428. {<<"accept-encoding">>, <<"gzip">>}
  429. ]),
  430. {response, _, 201, _} = gun:await(ConnPid, Ref),
  431. ok.
  432. status_code_202(Config) ->
  433. doc("The 202 Accepted status code can be sent. (RFC7231 6.3.3)"),
  434. ConnPid = gun_open(Config),
  435. Ref = gun:get(ConnPid, "/resp/reply2/202", [
  436. {<<"accept-encoding">>, <<"gzip">>}
  437. ]),
  438. {response, _, 202, _} = gun:await(ConnPid, Ref),
  439. ok.
  440. status_code_203(Config) ->
  441. doc("The 203 Non-Authoritative Information status code can be sent. (RFC7231 6.3.4)"),
  442. ConnPid = gun_open(Config),
  443. Ref = gun:get(ConnPid, "/resp/reply2/203", [
  444. {<<"accept-encoding">>, <<"gzip">>}
  445. ]),
  446. {response, _, 203, _} = gun:await(ConnPid, Ref),
  447. ok.
  448. status_code_204(Config) ->
  449. doc("The 204 No Content status code can be sent. (RFC7231 6.3.5)"),
  450. ConnPid = gun_open(Config),
  451. Ref = gun:get(ConnPid, "/resp/reply2/204", [
  452. {<<"accept-encoding">>, <<"gzip">>}
  453. ]),
  454. {response, _, 204, _} = gun:await(ConnPid, Ref),
  455. ok.
  456. status_code_205(Config) ->
  457. doc("The 205 Reset Content status code can be sent. (RFC7231 6.3.6)"),
  458. ConnPid = gun_open(Config),
  459. Ref = gun:get(ConnPid, "/resp/reply2/205", [
  460. {<<"accept-encoding">>, <<"gzip">>}
  461. ]),
  462. {response, _, 205, _} = gun:await(ConnPid, Ref),
  463. ok.
  464. status_code_300(Config) ->
  465. doc("The 300 Multiple Choices status code can be sent. (RFC7231 6.4.1)"),
  466. ConnPid = gun_open(Config),
  467. Ref = gun:get(ConnPid, "/resp/reply2/300", [
  468. {<<"accept-encoding">>, <<"gzip">>}
  469. ]),
  470. {response, _, 300, _} = gun:await(ConnPid, Ref),
  471. ok.
  472. status_code_301(Config) ->
  473. doc("The 301 Moved Permanently status code can be sent. (RFC7231 6.4.2)"),
  474. ConnPid = gun_open(Config),
  475. Ref = gun:get(ConnPid, "/resp/reply2/301", [
  476. {<<"accept-encoding">>, <<"gzip">>}
  477. ]),
  478. {response, _, 301, _} = gun:await(ConnPid, Ref),
  479. ok.
  480. status_code_302(Config) ->
  481. doc("The 302 Found status code can be sent. (RFC7231 6.4.3)"),
  482. ConnPid = gun_open(Config),
  483. Ref = gun:get(ConnPid, "/resp/reply2/302", [
  484. {<<"accept-encoding">>, <<"gzip">>}
  485. ]),
  486. {response, _, 302, _} = gun:await(ConnPid, Ref),
  487. ok.
  488. status_code_303(Config) ->
  489. doc("The 303 See Other status code can be sent. (RFC7231 6.4.4)"),
  490. ConnPid = gun_open(Config),
  491. Ref = gun:get(ConnPid, "/resp/reply2/303", [
  492. {<<"accept-encoding">>, <<"gzip">>}
  493. ]),
  494. {response, _, 303, _} = gun:await(ConnPid, Ref),
  495. ok.
  496. status_code_305(Config) ->
  497. doc("The 305 Use Proxy status code can be sent. (RFC7231 6.4.5)"),
  498. ConnPid = gun_open(Config),
  499. Ref = gun:get(ConnPid, "/resp/reply2/305", [
  500. {<<"accept-encoding">>, <<"gzip">>}
  501. ]),
  502. {response, _, 305, _} = gun:await(ConnPid, Ref),
  503. ok.
  504. %% The status code 306 is no longer used. (RFC7231 6.4.6)
  505. status_code_307(Config) ->
  506. doc("The 307 Temporary Redirect status code can be sent. (RFC7231 6.4.7)"),
  507. ConnPid = gun_open(Config),
  508. Ref = gun:get(ConnPid, "/resp/reply2/307", [
  509. {<<"accept-encoding">>, <<"gzip">>}
  510. ]),
  511. {response, _, 307, _} = gun:await(ConnPid, Ref),
  512. ok.
  513. status_code_400(Config) ->
  514. doc("The 400 Bad Request status code can be sent. (RFC7231 6.5.1)"),
  515. ConnPid = gun_open(Config),
  516. Ref = gun:get(ConnPid, "/resp/reply2/400", [
  517. {<<"accept-encoding">>, <<"gzip">>}
  518. ]),
  519. {response, _, 400, _} = gun:await(ConnPid, Ref),
  520. ok.
  521. status_code_402(Config) ->
  522. doc("The 402 Payment Required status code can be sent. (RFC7231 6.5.2)"),
  523. ConnPid = gun_open(Config),
  524. Ref = gun:get(ConnPid, "/resp/reply2/402", [
  525. {<<"accept-encoding">>, <<"gzip">>}
  526. ]),
  527. {response, _, 402, _} = gun:await(ConnPid, Ref),
  528. ok.
  529. status_code_403(Config) ->
  530. doc("The 403 Forbidden status code can be sent. (RFC7231 6.5.3)"),
  531. ConnPid = gun_open(Config),
  532. Ref = gun:get(ConnPid, "/resp/reply2/403", [
  533. {<<"accept-encoding">>, <<"gzip">>}
  534. ]),
  535. {response, _, 403, _} = gun:await(ConnPid, Ref),
  536. ok.
  537. status_code_404(Config) ->
  538. doc("The 404 Not Found status code can be sent. (RFC7231 6.5.4)"),
  539. ConnPid = gun_open(Config),
  540. Ref = gun:get(ConnPid, "/resp/reply2/404", [
  541. {<<"accept-encoding">>, <<"gzip">>}
  542. ]),
  543. {response, _, 404, _} = gun:await(ConnPid, Ref),
  544. ok.
  545. status_code_405(Config) ->
  546. doc("The 405 Method Not Allowed status code can be sent. (RFC7231 6.5.5)"),
  547. ConnPid = gun_open(Config),
  548. Ref = gun:get(ConnPid, "/resp/reply2/405", [
  549. {<<"accept-encoding">>, <<"gzip">>}
  550. ]),
  551. {response, _, 405, _} = gun:await(ConnPid, Ref),
  552. ok.
  553. status_code_406(Config) ->
  554. doc("The 406 Not Acceptable status code can be sent. (RFC7231 6.5.6)"),
  555. ConnPid = gun_open(Config),
  556. Ref = gun:get(ConnPid, "/resp/reply2/406", [
  557. {<<"accept-encoding">>, <<"gzip">>}
  558. ]),
  559. {response, _, 406, _} = gun:await(ConnPid, Ref),
  560. ok.
  561. status_code_408(Config) ->
  562. doc("The 408 Request Timeout status code can be sent. (RFC7231 6.5.7)"),
  563. ConnPid = gun_open(Config),
  564. Ref = gun:get(ConnPid, "/resp/reply2/408", [
  565. {<<"accept-encoding">>, <<"gzip">>}
  566. ]),
  567. {response, _, 408, _} = gun:await(ConnPid, Ref),
  568. ok.
  569. status_code_408_connection_close(Config) ->
  570. case config(protocol, Config) of
  571. http ->
  572. do_http11_status_code_408_connection_close(Config);
  573. http2 ->
  574. doc("HTTP/2 connections are not closed on 408 responses.")
  575. end.
  576. do_http11_status_code_408_connection_close(Config) ->
  577. doc("A 408 response should result in a connection close "
  578. "for HTTP/1.1 connections. (RFC7231 6.5.7)"),
  579. Client = raw_open(Config),
  580. ok = raw_send(Client, "GET / HTTP/1.1\r\n"),
  581. {_, 408, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),
  582. {Headers, <<>>} = cow_http:parse_headers(Rest),
  583. {_, <<"close">>} = lists:keyfind(<<"connection">>, 1, Headers),
  584. {error, closed} = raw_recv(Client, 0, 1000),
  585. ok.
  586. status_code_409(Config) ->
  587. doc("The 409 Conflict status code can be sent. (RFC7231 6.5.8)"),
  588. ConnPid = gun_open(Config),
  589. Ref = gun:get(ConnPid, "/resp/reply2/409", [
  590. {<<"accept-encoding">>, <<"gzip">>}
  591. ]),
  592. {response, _, 409, _} = gun:await(ConnPid, Ref),
  593. ok.
  594. status_code_410(Config) ->
  595. doc("The 410 Gone status code can be sent. (RFC7231 6.5.9)"),
  596. ConnPid = gun_open(Config),
  597. Ref = gun:get(ConnPid, "/resp/reply2/410", [
  598. {<<"accept-encoding">>, <<"gzip">>}
  599. ]),
  600. {response, _, 410, _} = gun:await(ConnPid, Ref),
  601. ok.
  602. status_code_411(Config) ->
  603. doc("The 411 Length Required status code can be sent. (RFC7231 6.5.10)"),
  604. ConnPid = gun_open(Config),
  605. Ref = gun:get(ConnPid, "/resp/reply2/411", [
  606. {<<"accept-encoding">>, <<"gzip">>}
  607. ]),
  608. {response, _, 411, _} = gun:await(ConnPid, Ref),
  609. ok.
  610. status_code_413(Config) ->
  611. doc("The 413 Payload Too Large status code can be sent. (RFC7231 6.5.11)"),
  612. ConnPid = gun_open(Config),
  613. Ref = gun:get(ConnPid, "/resp/reply2/413", [
  614. {<<"accept-encoding">>, <<"gzip">>}
  615. ]),
  616. {response, _, 413, _} = gun:await(ConnPid, Ref),
  617. ok.
  618. status_code_414(Config) ->
  619. doc("The 414 URI Too Long status code can be sent. (RFC7231 6.5.12)"),
  620. ConnPid = gun_open(Config),
  621. Ref = gun:get(ConnPid, "/resp/reply2/414", [
  622. {<<"accept-encoding">>, <<"gzip">>}
  623. ]),
  624. {response, _, 414, _} = gun:await(ConnPid, Ref),
  625. ok.
  626. status_code_415(Config) ->
  627. doc("The 415 Unsupported Media Type status code can be sent. (RFC7231 6.5.13)"),
  628. ConnPid = gun_open(Config),
  629. Ref = gun:get(ConnPid, "/resp/reply2/415", [
  630. {<<"accept-encoding">>, <<"gzip">>}
  631. ]),
  632. {response, _, 415, _} = gun:await(ConnPid, Ref),
  633. ok.
  634. status_code_417(Config) ->
  635. doc("The 417 Expectation Failed status code can be sent. (RFC7231 6.5.14)"),
  636. ConnPid = gun_open(Config),
  637. Ref = gun:get(ConnPid, "/resp/reply2/417", [
  638. {<<"accept-encoding">>, <<"gzip">>}
  639. ]),
  640. {response, _, 417, _} = gun:await(ConnPid, Ref),
  641. ok.
  642. status_code_426(Config) ->
  643. doc("The 426 Upgrade Required status code can be sent. (RFC7231 6.5.15)"),
  644. ConnPid = gun_open(Config),
  645. Ref = gun:get(ConnPid, "/resp/reply2/426", [
  646. {<<"accept-encoding">>, <<"gzip">>}
  647. ]),
  648. {response, _, 426, _} = gun:await(ConnPid, Ref),
  649. ok.
  650. status_code_426_upgrade_header(Config) ->
  651. case config(protocol, Config) of
  652. http ->
  653. do_status_code_426_upgrade_header(Config);
  654. http2 ->
  655. doc("HTTP/2 does not support the HTTP/1.1 Upgrade mechanism.")
  656. end.
  657. do_status_code_426_upgrade_header(Config) ->
  658. doc("A 426 response must include a upgrade header. (RFC7231 6.5.15)"),
  659. ConnPid = gun_open(Config),
  660. Ref = gun:get(ConnPid, "/ws?ok", [
  661. {<<"accept-encoding">>, <<"gzip">>}
  662. ]),
  663. {response, _, 426, Headers} = gun:await(ConnPid, Ref),
  664. {_, <<"upgrade">>} = lists:keyfind(<<"connection">>, 1, Headers),
  665. {_, <<"websocket">>} = lists:keyfind(<<"upgrade">>, 1, Headers),
  666. ok.
  667. status_code_500(Config) ->
  668. doc("The 500 Internal Server Error status code can be sent. (RFC7231 6.6.1)"),
  669. ConnPid = gun_open(Config),
  670. Ref = gun:get(ConnPid, "/resp/reply2/500", [
  671. {<<"accept-encoding">>, <<"gzip">>}
  672. ]),
  673. {response, _, 500, _} = gun:await(ConnPid, Ref),
  674. ok.
  675. status_code_501(Config) ->
  676. doc("The 501 Not Implemented status code can be sent. (RFC7231 6.6.2)"),
  677. ConnPid = gun_open(Config),
  678. Ref = gun:get(ConnPid, "/resp/reply2/501", [
  679. {<<"accept-encoding">>, <<"gzip">>}
  680. ]),
  681. {response, _, 501, _} = gun:await(ConnPid, Ref),
  682. ok.
  683. status_code_502(Config) ->
  684. doc("The 502 Bad Gateway status code can be sent. (RFC7231 6.6.3)"),
  685. ConnPid = gun_open(Config),
  686. Ref = gun:get(ConnPid, "/resp/reply2/502", [
  687. {<<"accept-encoding">>, <<"gzip">>}
  688. ]),
  689. {response, _, 502, _} = gun:await(ConnPid, Ref),
  690. ok.
  691. status_code_503(Config) ->
  692. doc("The 503 Service Unavailable status code can be sent. (RFC7231 6.6.4)"),
  693. ConnPid = gun_open(Config),
  694. Ref = gun:get(ConnPid, "/resp/reply2/503", [
  695. {<<"accept-encoding">>, <<"gzip">>}
  696. ]),
  697. {response, _, 503, _} = gun:await(ConnPid, Ref),
  698. ok.
  699. status_code_504(Config) ->
  700. doc("The 504 Gateway Timeout status code can be sent. (RFC7231 6.6.5)"),
  701. ConnPid = gun_open(Config),
  702. Ref = gun:get(ConnPid, "/resp/reply2/504", [
  703. {<<"accept-encoding">>, <<"gzip">>}
  704. ]),
  705. {response, _, 504, _} = gun:await(ConnPid, Ref),
  706. ok.
  707. status_code_505(Config) ->
  708. doc("The 505 HTTP Version Not Supported status code can be sent. (RFC7231 6.6.6)"),
  709. ConnPid = gun_open(Config),
  710. Ref = gun:get(ConnPid, "/resp/reply2/505", [
  711. {<<"accept-encoding">>, <<"gzip">>}
  712. ]),
  713. {response, _, 505, _} = gun:await(ConnPid, Ref),
  714. ok.
  715. %% The 505 response code is supposed to be about the major HTTP version.
  716. %% Cowboy instead rejects any version that isn't HTTP/1.0 or HTTP/1.1
  717. %% when expecting an h1 request. While this is not correct in theory
  718. %% it works in practice because there are no other minor versions.
  719. %%
  720. %% Cowboy does not do version checking for HTTP/2 since the protocol
  721. %% does not include a version number in the messages.
  722. %% Response headers.
  723. %% @todo No such header in this suite, but some in other suites (if-(un)modified-since).
  724. % A recipient that parses a timestamp value in an HTTP header field
  725. % MUST accept all three HTTP-date formats. (RFC7231 7.1.1.1)
  726. date_imf_fixdate(Config) ->
  727. doc("The date header uses the IMF-fixdate format. (RFC7231 7.1.1.1, RFC7231 7.1.1.2)"),
  728. ConnPid = gun_open(Config),
  729. Ref = gun:get(ConnPid, "/", [
  730. {<<"accept-encoding">>, <<"gzip">>}
  731. ]),
  732. {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
  733. {_, <<_,_,_,", ",_,_," ",_,_,_," ",_,_,_,_," ",_,_,":",_,_,":",_,_," GMT">>}
  734. = lists:keyfind(<<"date">>, 1, Headers),
  735. ok.
  736. %% @todo Applies to both date and other headers (if-(un)modified-since).
  737. % HTTP-date is case sensitive. A sender MUST NOT generate additional
  738. % whitespace in an HTTP-date beyond that specifically included as SP in
  739. % the grammar. The semantics of day-name, day, month, year, and
  740. % time-of-day are the same as those defined for the Internet Message
  741. % Format constructs with the corresponding name ([RFC5322], Section
  742. % 3.3). (RFC7231 7.1.1.1)
  743. %% @todo No such header in this suite, but some in other suites (if-(un)modified-since).
  744. % Recipients of a timestamp value in rfc850-date format, which uses a
  745. % two-digit year, MUST interpret a timestamp that appears to be more
  746. % than 50 years in the future as representing the most recent year in
  747. % the past that had the same last two digits. (RFC7231 7.1.1.1)
  748. %% @todo Add an option to disable sending the date header.
  749. % An origin server MUST NOT send a Date header field if it does not
  750. % have a clock capable of providing a reasonable approximation of the
  751. % current instance in Coordinated Universal Time. (RFC7231 7.1.1.2)
  752. no_date_1xx(Config) ->
  753. doc("The date header is optional for 1xx responses. "
  754. "Cowboy does not send it with those responses. (RFC7231 7.1.1.2)"),
  755. ConnPid = gun_open(Config),
  756. Ref = gun:get(ConnPid, "/resp/inform2/100", [
  757. {<<"accept-encoding">>, <<"gzip">>}
  758. ]),
  759. {inform, 100, Headers} = gun:await(ConnPid, Ref),
  760. false = lists:keyfind(<<"date">>, 1, Headers),
  761. ok.
  762. date_2xx(Config) ->
  763. doc("A date header must be sent for 2xx status codes. (RFC7231 7.1.1.2)"),
  764. ConnPid = gun_open(Config),
  765. Ref = gun:get(ConnPid, "/resp/reply2/200", [
  766. {<<"accept-encoding">>, <<"gzip">>}
  767. ]),
  768. {response, _, 200, Headers} = gun:await(ConnPid, Ref),
  769. {_, _} = lists:keyfind(<<"date">>, 1, Headers),
  770. ok.
  771. date_3xx(Config) ->
  772. doc("A date header must be sent for 3xx status codes. (RFC7231 7.1.1.2)"),
  773. ConnPid = gun_open(Config),
  774. Ref = gun:get(ConnPid, "/resp/reply2/300", [
  775. {<<"accept-encoding">>, <<"gzip">>}
  776. ]),
  777. {response, _, 300, Headers} = gun:await(ConnPid, Ref),
  778. {_, _} = lists:keyfind(<<"date">>, 1, Headers),
  779. ok.
  780. date_4xx(Config) ->
  781. doc("A date header must be sent for 4xx status codes. (RFC7231 7.1.1.2)"),
  782. ConnPid = gun_open(Config),
  783. Ref = gun:get(ConnPid, "/resp/reply2/400", [
  784. {<<"accept-encoding">>, <<"gzip">>}
  785. ]),
  786. {response, _, 400, Headers} = gun:await(ConnPid, Ref),
  787. {_, _} = lists:keyfind(<<"date">>, 1, Headers),
  788. ok.
  789. date_5xx(Config) ->
  790. doc("The date header is optional for 5xx status codes. "
  791. "Cowboy however does send it with those responses. (RFC7231 7.1.1.2)"),
  792. ConnPid = gun_open(Config),
  793. Ref = gun:get(ConnPid, "/resp/reply2/500", [
  794. {<<"accept-encoding">>, <<"gzip">>}
  795. ]),
  796. {response, _, 500, Headers} = gun:await(ConnPid, Ref),
  797. {_, _} = lists:keyfind(<<"date">>, 1, Headers),
  798. ok.
  799. %% @todo It's worth revisiting this RFC in the context of cowboy_rest
  800. %% to ensure the state machine is doing what's expected by the RFC.