From cff9938a66e5529fae13d815ca9879a04a0ebf99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 17 Jan 2024 12:55:52 +0100 Subject: [PATCH 01/20] Show links in README in make prepare_tag --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 0a43187c..74aff5cf 100644 --- a/Makefile +++ b/Makefile @@ -101,6 +101,9 @@ prepare_tag: $(verbose) echo -n "GUIDE: " $(verbose) grep -h dep_$(PROJECT)_commit doc/src/guide/*.asciidoc || true $(verbose) echo + $(verbose) echo "Links in the README:" + $(verbose) grep http.*:// README.asciidoc + $(verbose) echo $(verbose) echo "Titles in most recent CHANGELOG:" $(verbose) for f in `ls -r doc/src/guide/migrating_from_*.asciidoc | head -n1`; do \ echo $$f:; \ From ecf3d43613ff69dd734411b35849dbfb0b33229b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 17 Jan 2024 17:16:38 +0100 Subject: [PATCH 02/20] Improve reliability of a few tests GitHub Actions runners are not as good as self-hosted BuildKite so some adjustments need to be made to timeouts and such. --- test/http_SUITE.erl | 2 ++ test/req_SUITE.erl | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index fb9f93b3..bfe55dab 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -329,6 +329,7 @@ do_idle_timeout_on_send(Config, Protocol) -> try ConnPid = gun_open([{type, tcp}, {protocol, Protocol}, {port, Port}|Config]), {ok, Protocol} = gun:await_up(ConnPid), + timer:sleep(500), #{socket := Socket} = gun:info(ConnPid), Pid = get_remote_pid_tcp(Socket), StreamRef = gun:get(ConnPid, "/streamed_result/10/250"), @@ -359,6 +360,7 @@ do_idle_timeout_reset_on_send(Config, Protocol) -> try ConnPid = gun_open([{type, tcp}, {protocol, Protocol}, {port, Port}|Config]), {ok, Protocol} = gun:await_up(ConnPid), + timer:sleep(500), #{socket := Socket} = gun:info(ConnPid), Pid = get_remote_pid_tcp(Socket), StreamRef = gun:get(ConnPid, "/streamed_result/10/250"), diff --git a/test/req_SUITE.erl b/test/req_SUITE.erl index f407853e..04da8fa7 100644 --- a/test/req_SUITE.erl +++ b/test/req_SUITE.erl @@ -57,7 +57,8 @@ init_dispatch(Config) -> {"/resp/:key[/:arg]", resp_h, []}, {"/multipart[/:key]", multipart_h, []}, {"/args/:key/:arg[/:default]", echo_h, []}, - {"/crash/:key/period", echo_h, #{length => 999999999, period => 1000, crash => true}}, + {"/crash/:key/period", echo_h, + #{length => 999999999, period => 1000, timeout => 5000, crash => true}}, {"/no-opts/:key", echo_h, #{crash => true}}, {"/opts/:key/length", echo_h, #{length => 1000}}, {"/opts/:key/period", echo_h, #{length => 999999999, period => 2000}}, From 992ee6241d76b768d6091c27696afe3bc437a40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 18 Jan 2024 11:13:51 +0100 Subject: [PATCH 03/20] Retry the read_urlencoded_body_too_large if timeout triggers This is caused by the timeout being 1s after the period. When the CI environment is overloaded, sometimes the timeout will trigger. We retry, knowing that the timetrap will catch us if we retry too much. --- test/req_SUITE.erl | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/req_SUITE.erl b/test/req_SUITE.erl index 04da8fa7..6e111bbd 100644 --- a/test/req_SUITE.erl +++ b/test/req_SUITE.erl @@ -591,8 +591,20 @@ do_read_urlencoded_body_too_large(Path, Body, Config) -> {<<"content-length">>, integer_to_binary(iolist_size(Body))} ]), gun:data(ConnPid, Ref, fin, Body), - {response, _, 413, _} = gun:await(ConnPid, Ref, infinity), - gun:close(ConnPid). + Response = gun:await(ConnPid, Ref, infinity), + gun:close(ConnPid), + case Response of + {response, _, 413, _} -> + ok; + %% We got the wrong crash, likely because the environment + %% was overloaded and the timeout triggered. Try again. + {response, _, 408, _} -> + do_read_urlencoded_body_too_large(Path, Body, Config); + %% Timing issues make it possible for the connection to be + %% closed before the data went through. We retry. + {error, {stream_error, {closed, {error,closed}}}} -> + do_read_urlencoded_body_too_large(Path, Body, Config) + end. read_urlencoded_body_too_long(Config) -> doc("application/x-www-form-urlencoded request body sent too slow. " From e8b4715a9f462f841d6d7d46eff82690f004232b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 18 Jan 2024 15:19:23 +0100 Subject: [PATCH 04/20] Reduce sleep in chunked_one_byte_at_a_time To avoid having the connection get closed due to us taking too long on unreliable environments like GitHub Actions. --- test/http_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index bfe55dab..070a6902 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -90,7 +90,7 @@ chunked_one_byte_at_a_time(Config) -> "Transfer-encoding: chunked\r\n\r\n"), _ = [begin raw_send(Client, <>), - timer:sleep(10) + timer:sleep(1) end || <> <= ChunkedBody], Rest = case catch raw_recv_head(Client) of {'EXIT', _} -> error(closed); From 4ffcbfbf43b317c95e920b0a77e04dc411af79ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 22 Jan 2024 11:41:46 +0100 Subject: [PATCH 05/20] Document range requests --- doc/src/manual/cowboy_rest.asciidoc | 136 ++++++++++++++++++++++++-- doc/src/manual/cowboy_static.asciidoc | 2 + 2 files changed, 132 insertions(+), 6 deletions(-) diff --git a/doc/src/manual/cowboy_rest.asciidoc b/doc/src/manual/cowboy_rest.asciidoc index ea6dba93..fcef7997 100644 --- a/doc/src/manual/cowboy_rest.asciidoc +++ b/doc/src/manual/cowboy_rest.asciidoc @@ -605,17 +605,139 @@ The response body can be provided either as the actual data to be sent or a tuple indicating which file to send. This function is called for both GET and HEAD requests. For -the latter the body is not sent, however. +the latter the body is not sent: it is only used to calculate +the content length. // @todo Perhaps we can optimize HEAD requests and just // allow calculating the length instead of returning the // whole thing. -Note that there used to be a way to stream the response body. -It was temporarily removed and will be added back in a later -release. +It is possible to stream the response body either by manually +sending the response and returning a `stop` value; or by +switching to a different handler (for example a loop handler) +and manually sending the response. All headers already set +by Cowboy will also be included in the response. -// @todo Add a way to switch to loop handler for streaming the body. +== RangeCallback + +[source,erlang] +---- +RangeCallback(Req, State) -> {Result, Req, State} + +Result :: [{Range, Body}] +Range :: {From, To, Total} | binary() +From :: non_neg_integer() +To :: non_neg_integer() +Total :: non_neg_integer() | '*' +Body :: cowboy_req:resp_body() +Default - crash +---- + +Return a list of ranges for the response body. + +The range selected can be found in the key `range` +in the Req object, as indicated in `range_satisfiable`. + +Instead of returning the full response body as would +be done in the `ProvideCallback`, a list of ranges +must be returned. There can be one or more range. +When one range is returned, a normal ranged response +is sent. When multiple ranges are returned, Cowboy +will automatically send a multipart/byteranges +response. + +When the total is not known the atom `'*'` can be +returned. + +== ranges_provided + +[source,erlang] +---- +ranges_provided(Req, State) -> {Result, Req, State} + +Result :: [Range | Auto] +Range :: { + binary(), %% lowercase; case insensitive + RangeCallback :: atom() +} +Auto :: {<<"bytes">>, auto} +Default - skip this step +---- + +Return the list of range units the resource provides. + +During content negotiation Cowboy will build an accept-ranges +response header with the list of ranges provided. Cowboy +does not choose a range at this time; ranges are choosen +when it comes time to call the `ProvideCallback`. + +By default ranged requests will be handled the same as normal +requests: the `ProvideCallback` will be called and the full +response body will be sent. + +It is possible to let Cowboy handle ranged responses +automatically when the range unit is bytes and the +atom returned is `auto` (instead of a callback name). +In that case Cowboy will call the `ProvideCallback` +and split the response automatically, including by +producing a multipart/byteranges response if necessary. + +== range_satisfiable + +[source,erlang] +---- +range_satisfiable(Req, State) -> {Result, Req, State} + +Result :: boolean() | {false, non_neg_integer() | iodata()} +Default :: true +---- + +Whether the range request is satisfiable. + +When the time comes to send the response body, and when +ranges have been provided via the `ranges_provided` +callback, Cowboy will process the if-range and the +range request headers and ensure it is satisfiable. + +This callback allows making resource-specific checks +before sending the ranged response. The default is +to accept sending a ranged response. + +Cowboy adds the requested `range` to the Req object +just before calling this callback: + +[source,erlang] +---- +req() :: #{ + range => { + binary(), %% lowercase; case insensitive + Range + } +} + +Range :: ByteRange | binary() + +ByteRange :: [{FirstByte, LastByte | infinity} | SuffixLen] +FirstByte :: non_neg_integer() +LastByte :: non_neg_integer() +SuffixLen :: neg_integer() +---- + +Only byte ranges are parsed. Other ranges are provided +as binary. Byte ranges may either be requested from first +to last bytes (inclusive); from first bytes to the end +(`infinity` is used to represent the last byte); or +the last bytes of the representation via a negative +integer (so -500 means the last 500 bytes). + +Returning `false` will result in a 416 Range Not Satisfiable +response being sent. The content-range header will be +set automatically in the response if a tuple is +returned. The integer value represents the total +size (in the choosen unit) of the resource. An +iodata value may also be returned and will be +used as-is to build the content range header, +prepended with the unit choosen. === rate_limited @@ -625,7 +747,7 @@ rate_limited(Req, State) -> {Result, Req, State} Result :: false | {true, RetryAfter} RetryAfter :: non_neg_integer() | calendar:datetime() -Default - false +Default :: false ---- Return whether the user is rate limited. @@ -734,6 +856,8 @@ listed here, like the authorization header. == Changelog +* *2.11*: The `ranges_provided`, `range_satisfiable` and + the `RangeCallback` callbacks have been added. * *2.11*: The `generate_etag` callback can now return `undefined` to conditionally avoid generating an etag. diff --git a/doc/src/manual/cowboy_static.asciidoc b/doc/src/manual/cowboy_static.asciidoc index 0e131dd1..dde3401b 100644 --- a/doc/src/manual/cowboy_static.asciidoc +++ b/doc/src/manual/cowboy_static.asciidoc @@ -129,6 +129,8 @@ when it fails to detect a file's MIME type. == Changelog +* *2.11*: Support for range requests was added in 2.6 and + is now considered stable. * *2.6*: The `charset` extra option was added. * *1.0*: Handler introduced. From 427a276ef2f2042ad312e0260535cbb4696f9072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Tue, 23 Jan 2024 13:15:55 +0100 Subject: [PATCH 06/20] Update the guide with range requests support Also update the list of headers cowboy_rest might set and tweak a small number of other items. --- doc/src/guide/resource_design.asciidoc | 17 +- doc/src/guide/rest_conneg.png | Bin 78133 -> 75587 bytes doc/src/guide/rest_conneg.svg | 277 +++-- doc/src/guide/rest_flowcharts.asciidoc | 18 +- doc/src/guide/rest_get_head.png | Bin 94321 -> 171818 bytes doc/src/guide/rest_get_head.svg | 1343 ++++++++++++++++++++---- doc/src/guide/rest_handlers.asciidoc | 19 +- src/cowboy_rest.erl | 1 + 8 files changed, 1350 insertions(+), 325 deletions(-) diff --git a/doc/src/guide/resource_design.asciidoc b/doc/src/guide/resource_design.asciidoc index 954d87d5..125b437c 100644 --- a/doc/src/guide/resource_design.asciidoc +++ b/doc/src/guide/resource_design.asciidoc @@ -144,6 +144,16 @@ never be called. Implement the `languages_provided` or `charsets_provided` callbacks if applicable. +Does the resource accept ranged requests? If it does, +implement the `ranges_provided` callback. Resources that +only accept `bytes` units can use the callback name +`auto` and let Cowboy automatically do ranged responses. +Other callbacks should have a name prefix of `ranged_` +for clarity. For example, `ranged_bytes` or `ranged_pages`. +If the resource needs to perform additional checks before +accepting to do a ranged responses, implement the +`range_satisfiable` callback. + Is there any other header that may make the representation of the resource vary? Implement the `variances` callback. @@ -191,10 +201,15 @@ the `options` method. === GET and HEAD methods If you implement the methods GET and/or HEAD, you must -implement one `ProvideResource` callback for each +implement one `ProvideCallback` callback for each content-type returned by the `content_types_provided` callback. +When range requests are accepted, you must implement one +`RangeCallback` for each range unit returned by +`ranges_provided` (unless `auto` was used). This is +in addition to the `ProvideCallback` callback. + === PUT, POST and PATCH methods If you implement the methods PUT, POST and/or PATCH, diff --git a/doc/src/guide/rest_conneg.png b/doc/src/guide/rest_conneg.png index 65ecdcf37d1cc9e73c41d77f2eb48321e9a9b61d..79aa69b6b0d33b33851fdd3061cfe056ca78686b 100644 GIT binary patch literal 75587 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

l=2M`e81!b7)`Bj{eo@AJzR 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 UcEK-lv?JhUY+#PA&~v!=UnhJ>8~^|S literal 78133 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 E0E_tc3jhEB diff --git a/doc/src/guide/rest_conneg.svg b/doc/src/guide/rest_conneg.svg index 247567a0..97bba6a3 100644 --- a/doc/src/guide/rest_conneg.svg +++ b/doc/src/guide/rest_conneg.svg @@ -2,24 +2,23 @@ + inkscape:export-ydpi="90" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + inkscape:swatch="solid"> + showguides="true" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1"> + snapvisiblegridlinesonly="true" + originx="0" + originy="0" + spacingy="1" + spacingx="1" + units="px" /> @@ -93,7 +100,7 @@ image/svg+xml - + @@ -112,7 +119,7 @@ some text + y="114.39204" + style="font-size:16px;line-height:1.25;font-family:sans-serif">some text some text + y="53.112247" + style="font-size:16px;line-height:1.25;font-family:sans-serif">some text has accept-language? + y="310.19913" + style="font-size:16px;line-height:1.25;font-family:sans-serif">has accept-language? has accept-charset? + y="477.47531" + style="font-size:16px;line-height:1.25;font-family:sans-serif">has accept-charset? some text + y="236.95154" + style="font-size:16px;line-height:1.25;font-family:sans-serif">some text start + id="tspan17171" + style="font-size:16px;line-height:1.25;font-family:sans-serif">start charsets_provided + y="561.14258" + style="font-size:16px;line-height:1.25;font-family:sans-serif">charsets_provided variances + y="646.58331" + style="font-size:16px;line-height:1.25;font-family:sans-serif">ranges_provided has accept? + y="142.80627" + style="font-size:16px;line-height:1.25;font-family:sans-serif">has accept? content_types_provided + y="226.4736" + style="font-size:16px;line-height:1.25;font-family:sans-serif">content_types_provided languages_provided + y="393.80801" + style="font-size:16px;line-height:1.25;font-family:sans-serif">languages_provided true + y="185.95248" + style="font-size:16px;line-height:1.25;font-family:sans-serif">true provided* + y="269.61978" + style="font-size:16px;line-height:1.25;font-family:sans-serif">provided* true + y="353.28702" + style="font-size:16px;line-height:1.25;font-family:sans-serif">true provided* + y="436.95425" + style="font-size:16px;line-height:1.25;font-family:sans-serif">provided* true + y="520.62152" + style="font-size:16px;line-height:1.25;font-family:sans-serif">true provided* + y="604.28876" + style="font-size:16px;line-height:1.25;font-family:sans-serif">provided* @@ -820,75 +827,75 @@ false + y="227.88033" + style="font-size:16px;line-height:1.25;font-family:sans-serif">false false + y="395.20209" + style="font-size:16px;line-height:1.25;font-family:sans-serif">false not provided* + y="374.19577" + style="font-size:16px;line-height:1.25;font-family:sans-serif">not provided* false + y="562.52386" + style="font-size:16px;line-height:1.25;font-family:sans-serif">false not provided* + y="663.24762" + style="font-size:16px;line-height:1.25;font-family:sans-serif">not provided* 406 not acceptable + y="394.09869" + style="font-size:16px;line-height:1.25;font-family:sans-serif">406 not acceptable middlewares + y="-354.17184" + style="font-size:16px;line-height:1.25;font-family:sans-serif">middlewares not provided* + y="-106.16136" + style="font-size:16px;line-height:1.25;font-family:sans-serif">not provided* + + + + ... + y="730.10156" + style="font-size:16px;line-height:1.25;font-family:sans-serif">variances + + ... diff --git a/doc/src/guide/rest_flowcharts.asciidoc b/doc/src/guide/rest_flowcharts.asciidoc index 308a919e..b8d0e0d5 100644 --- a/doc/src/guide/rest_flowcharts.asciidoc +++ b/doc/src/guide/rest_flowcharts.asciidoc @@ -95,6 +95,11 @@ callback will only be called at the end of the "GET and HEAD methods" diagram, when all conditions have been met. +Optionally, the `ranges_provided` also returns the +name of a callback for every range unit it accepts. This +will be called at the end of the "GET and HEAD methods" +diagram in the case of ranged requests. + The selected content-type, language and charset are saved as meta values in the Req object. You *should* use the appropriate representation if you set a @@ -121,11 +126,18 @@ succeed, the resource can be retrieved. Cowboy prepares the response by first retrieving metadata about the representation, then by calling -the `ProvideResource` callback. This is the callback +the `ProvideCallback` callback. This is the callback you defined for each content-types you returned from `content_types_provided`. This callback returns the body -that will be sent back to the client, or a fun if the -body must be streamed. +that will be sent back to the client. + +For ranged requests, but only when the `ranges_provided` +callback was defined earlier, Cowboy will add the selected +`range` information to the Req object and call the +`range_satisfiable` callback. After confirming that the +range can be provided, Cowboy calls the `RangeResource` +callback and produces a ranged response using the +ranged data from the callback. When the resource does not exist, Cowboy will figure out whether the resource existed previously, and if so whether diff --git a/doc/src/guide/rest_get_head.png b/doc/src/guide/rest_get_head.png index 211ab603566a8c52432373dc119fec7c28ec0c47..24f8de451be217782d9ec1f4d7ef5e0d4abc95f4 100644 GIT binary patch literal 171818 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

p7R%27@U()}RxG#P7M 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%0AXMlXyogSAR7RdW&BQCSPJ=JQ&WV%^=^?l50s7| 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 diff --git a/doc/src/guide/rest_get_head.svg b/doc/src/guide/rest_get_head.svg index 92030cf3..cf660897 100644 --- a/doc/src/guide/rest_get_head.svg +++ b/doc/src/guide/rest_get_head.svg @@ -2,24 +2,23 @@ + inkscape:export-ydpi="90" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + inkscape:swatch="solid"> + showguides="true" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1"> + snapvisiblegridlinesonly="true" + originx="0" + originy="0" + spacingy="1" + spacingx="1" + units="px" /> @@ -101,6 +108,223 @@ inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + d="m -57.78256,351.41962 v 52.3259" + style="opacity:0.8;fill:none;stroke:#6d8e41;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m -57.78256,351.41962 v 52.3259" + style="opacity:0.8;fill:none;stroke:#6d8e41;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m -57.78256,351.41962 v 52.3259" + style="opacity:0.8;fill:none;stroke:#6d8e41;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m -57.78256,351.41962 v 52.3259" + style="opacity:0.8;fill:none;stroke:#6d8e41;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m -57.78256,351.41962 v 52.3259" + style="opacity:0.8;fill:none;stroke:#6d8e41;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m -57.78256,351.41962 v 52.3259" + style="opacity:0.8;fill:none;stroke:#6d8e41;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + d="m -57.78256,351.41962 v 52.3259" + style="opacity:0.8;fill:none;stroke:#6d8e41;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + transform="rotate(90,-13.918835,262.77429)" /> + d="m -57.78256,343.20394 v 61.59661" + style="opacity:0.8;fill:none;stroke:#9b3b1c;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> last_modified ProvideCallback + + ProvideCallback conneg multiple_choices resource_exists generate_etag expires + + + + + + has range? + + + + + + resource provides ranges? + + + + + + has if-range? + + + + + + requested range provided? + + + + + + range_satisfiable + + + + + + + + + + automatic range? true true + true + true + false, or + true + true + false + false false + false + false + false + false + no match + error producingautomaticranged response + d="m -57.78256,343.20394 v 61.59661" + style="opacity:0.8;fill:none;stroke:#9b3b1c;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> true true + d="m -57.78256,351.41962 v 52.3259" + style="opacity:0.8;fill:none;stroke:#6d8e41;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> cond 300 multiple choices 200 OK + automaticranged response + strong etag match + + 206 partial content + + RangeCallback + + 206 partial content + transform="rotate(90,-13.918835,262.77429)" /> has if-match? false + d="m -57.78256,351.41962 v 52.3259" + style="opacity:0.8;fill:none;stroke:#6d8e41;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + transform="rotate(90,-13.918835,262.77429)" /> previously_existed 404 not found false + d="m -57.78256,343.20394 v 61.59661" + style="opacity:0.8;fill:none;stroke:#9b3b1c;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + transform="rotate(90,-13.918835,262.77429)" /> moved_permanently 412 precondition failed true true* false 301 moved permanently + d="m -57.78256,343.20394 v 61.59661" + style="opacity:0.8;fill:none;stroke:#9b3b1c;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + transform="rotate(90,-13.918835,262.77429)" /> moved_temporarily true* false 307 moved temporarily 410 gone + + 416 not satisfiable diff --git a/doc/src/guide/rest_handlers.asciidoc b/doc/src/guide/rest_handlers.asciidoc index baf8e6a2..19a98596 100644 --- a/doc/src/guide/rest_handlers.asciidoc +++ b/doc/src/guide/rest_handlers.asciidoc @@ -84,6 +84,8 @@ if it is undefined, moving directly to the next step. Similarly, | multiple_choices | `false` | options | `ok` | previously_existed | `false` +| ranges_provided | skip +| range_satisfiable | `true` | rate_limited | `false` | resource_exists | `true` | service_available | `true` @@ -97,8 +99,9 @@ As you can see, Cowboy tries to move on with the request whenever possible by using well thought out default values. In addition to these, there can be any number of user-defined -callbacks that are specified through `content_types_accepted/2` -and `content_types_provided/2`. They can take any name, however +callbacks that are specified through `content_types_accepted/2`, +`content_types_provided/2` or `ranges_provided/2`. They can take +any name (except `auto` for range callbacks), however it is recommended to use a separate prefix for the callbacks of each function. For example, `from_html` and `to_html` indicate in the first case that we're accepting a resource given as HTML, @@ -113,9 +116,10 @@ Req object directly. The values are defined in the following table: [cols="<,<",options="header"] |=== | Key | Details -| media_type | The content-type negotiated for the response entity. -| language | The language negotiated for the response entity. -| charset | The charset negotiated for the response entity. +| media_type | The content-type negotiated for the response entity +| language | The language negotiated for the response entity +| charset | The charset negotiated for the response entity +| range | The range selected for the ranged response |=== They can be used to send a proper body with the response to a @@ -129,11 +133,16 @@ of the REST code. They are listed in the following table. [cols="<,<",options="header"] |=== | Header name | Details +| accept-ranges | Range units accepted by the resource +| allow | HTTP methods allowed by the resource | content-language | Language used in the response body +| content-range | Range of the content found in the response | content-type | Media type and charset of the response body | etag | Etag of the resource | expires | Expiration date of the resource | last-modified | Last modification date for the resource | location | Relative or absolute URI to the requested resource +| retry-after | Delay or time the client should wait before accessing the resource | vary | List of headers that may change the representation of the resource +| www-authenticate | Authentication information to access the resource |=== diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index 003e5f9d..ace5986a 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -1196,6 +1196,7 @@ if_range(Req=#{headers := #{<<"if-range">> := _, <<"range">> := _}}, if_range(Req, State) -> range(Req, State). +%% @todo This can probably be moved to if_range directly. range(Req, State=#state{ranges_a=[]}) -> set_resp_body(Req, State); range(Req, State) -> From 3e145af9b9e8d61ca042d1f94287d16b5ac155dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Tue, 23 Jan 2024 14:08:06 +0100 Subject: [PATCH 07/20] Getting started must include relx in deps --- doc/src/guide/getting_started.asciidoc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/src/guide/getting_started.asciidoc b/doc/src/guide/getting_started.asciidoc index 4bdde923..731e4a57 100644 --- a/doc/src/guide/getting_started.asciidoc +++ b/doc/src/guide/getting_started.asciidoc @@ -62,7 +62,7 @@ handler. === Cowboy setup We will modify the 'Makefile' to tell the build system it needs to -fetch and compile Cowboy: +fetch and compile Cowboy, and that we will use releases: [source,makefile] ---- @@ -71,6 +71,8 @@ PROJECT = hello_erlang DEPS = cowboy dep_cowboy_commit = 2.10.0 +REL_DEPS = relx + DEP_PLUGINS = cowboy include erlang.mk @@ -80,6 +82,9 @@ The `DEP_PLUGINS` line tells the build system to load the plugins Cowboy provides. These include predefined templates that we will use soon. +The `REL_DEPS` line tells the build system to fetch and build +`relx`, the library that will create the release. + If you do `make run` now, Cowboy will be included in the release and started automatically. This is not enough however, as Cowboy doesn't do anything by default. We still need to tell Cowboy to From 08c2be058a1c376fcb80465473b53085c14f88f5 Mon Sep 17 00:00:00 2001 From: geeksilva97 Date: Thu, 18 Jan 2024 20:50:27 -0300 Subject: [PATCH 08/20] Fix match_qs with constraints when key is not present Original fix by Ali Farhadi . --- src/cowboy_req.erl | 7 ++++++- test/handlers/echo_h.erl | 1 + test/req_SUITE.erl | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 8edf4ff0..995d67c8 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -1024,7 +1024,12 @@ filter([], Map, Errors) -> _ -> {error, Errors} end; filter([{Key, Constraints}|Tail], Map, Errors) -> - filter_constraints(Tail, Map, Errors, Key, maps:get(Key, Map), Constraints); + case maps:find(Key, Map) of + {ok, Value} -> + filter_constraints(Tail, Map, Errors, Key, Value, Constraints); + error -> + filter(Tail, Map, Errors#{Key => required}) + end; filter([{Key, Constraints, Default}|Tail], Map, Errors) -> case maps:find(Key, Map) of {ok, Value} -> diff --git a/test/handlers/echo_h.erl b/test/handlers/echo_h.erl index f50bc792..d04d531d 100644 --- a/test/handlers/echo_h.erl +++ b/test/handlers/echo_h.erl @@ -86,6 +86,7 @@ echo(<<"match">>, Req, Opts) -> Fields = [binary_to_atom(F, latin1) || F <- Fields0], Value = case Type of <<"qs">> -> cowboy_req:match_qs(Fields, Req); + <<"qs_with_constraints">> -> cowboy_req:match_qs([{id, integer}], Req); <<"cookies">> -> cowboy_req:match_cookies(Fields, Req); <<"body_qs">> -> %% Note that the Req should not be discarded but for the diff --git a/test/req_SUITE.erl b/test/req_SUITE.erl index 6e111bbd..183bd4b2 100644 --- a/test/req_SUITE.erl +++ b/test/req_SUITE.erl @@ -266,6 +266,7 @@ match_qs(Config) -> end, %% Ensure match errors result in a 400 response. {400, _, _} = do_get("/match/qs/a/c?a=b", [], Config), + {400, _, _} = do_get("/match/qs_with_constraints", [], Config), %% This function is tested more extensively through unit tests. ok. From f060e6c4ffedca65111b8016d4a976e15bfdb2b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Tue, 23 Jan 2024 14:48:15 +0100 Subject: [PATCH 09/20] Document reset_idle_timeout_on_send option --- doc/src/manual/cowboy_http.asciidoc | 49 ++++++++++++++++------------ doc/src/manual/cowboy_http2.asciidoc | 7 ++++ 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/doc/src/manual/cowboy_http.asciidoc b/doc/src/manual/cowboy_http.asciidoc index 33d2888c..58f0435f 100644 --- a/doc/src/manual/cowboy_http.asciidoc +++ b/doc/src/manual/cowboy_http.asciidoc @@ -17,27 +17,28 @@ as a Ranch protocol. [source,erlang] ---- opts() :: #{ - active_n => pos_integer(), - chunked => boolean(), - connection_type => worker | supervisor, - http10_keepalive => boolean(), - idle_timeout => timeout(), - inactivity_timeout => timeout(), - initial_stream_flow_size => non_neg_integer(), - linger_timeout => timeout(), - logger => module(), - max_empty_lines => non_neg_integer(), - max_header_name_length => non_neg_integer(), - max_header_value_length => non_neg_integer(), - max_headers => non_neg_integer(), - max_keepalive => non_neg_integer(), - max_method_length => non_neg_integer(), - max_request_line_length => non_neg_integer(), - max_skip_body_length => non_neg_integer(), - proxy_header => boolean(), - request_timeout => timeout(), - sendfile => boolean(), - stream_handlers => [module()] + active_n => pos_integer(), + chunked => boolean(), + connection_type => worker | supervisor, + http10_keepalive => boolean(), + idle_timeout => timeout(), + inactivity_timeout => timeout(), + initial_stream_flow_size => non_neg_integer(), + linger_timeout => timeout(), + logger => module(), + max_empty_lines => non_neg_integer(), + max_header_name_length => non_neg_integer(), + max_header_value_length => non_neg_integer(), + max_headers => non_neg_integer(), + max_keepalive => non_neg_integer(), + max_method_length => non_neg_integer(), + max_request_line_length => non_neg_integer(), + max_skip_body_length => non_neg_integer(), + proxy_header => boolean(), + request_timeout => timeout(), + reset_idle_timeout_on_send => boolean(), + sendfile => boolean(), + stream_handlers => [module()] } ---- @@ -148,6 +149,11 @@ request_timeout (5000):: Time in ms with no requests before Cowboy closes the connection. +reset_idle_timeout_on_send (false):: + +Whether the `idle_timeout` gets reset when sending data +to the socket. + sendfile (true):: Whether the sendfile syscall may be used. It can be useful to disable @@ -160,6 +166,7 @@ Ordered list of stream handlers that will handle all stream events. == Changelog +* *2.11*: The `reset_idle_timeout_on_send` option was added. * *2.8*: The `active_n` option was added. * *2.7*: The `initial_stream_flow_size` and `logger` options were added. * *2.6*: The `chunked`, `http10_keepalive`, `proxy_header` and `sendfile` options were added. diff --git a/doc/src/manual/cowboy_http2.asciidoc b/doc/src/manual/cowboy_http2.asciidoc index 6af4fc00..a47d24ae 100644 --- a/doc/src/manual/cowboy_http2.asciidoc +++ b/doc/src/manual/cowboy_http2.asciidoc @@ -44,6 +44,7 @@ opts() :: #{ max_stream_window_size => 0..16#7fffffff, preface_timeout => timeout(), proxy_header => boolean(), + reset_idle_timeout_on_send => boolean(), sendfile => boolean(), settings_timeout => timeout(), stream_handlers => [module()], @@ -235,6 +236,11 @@ Whether incoming connections have a PROXY protocol header. The proxy information will be passed forward via the `proxy_header` key of the Req object. +reset_idle_timeout_on_send (false):: + +Whether the `idle_timeout` gets reset when sending data +to the socket. + sendfile (true):: Whether the sendfile syscall may be used. It can be useful to disable @@ -271,6 +277,7 @@ too many `WINDOW_UPDATE` frames. == Changelog +* *2.11*: The `reset_idle_timeout_on_send` option was added. * *2.11*: Add the option `max_cancel_stream_rate` to protect against another flood scenario. * *2.9*: The `goaway_initial_timeout` and `goaway_complete_timeout` From 8f9051519e56e0c49ec9c3d60ca9389104b1b18c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Tue, 23 Jan 2024 15:29:41 +0100 Subject: [PATCH 10/20] Cowboy 2.11 --- Makefile | 8 +- README.asciidoc | 4 +- doc/src/guide/book.asciidoc | 2 + doc/src/guide/getting_started.asciidoc | 2 +- doc/src/guide/migrating_from_2.10.asciidoc | 139 +++++++++++++++++++++ doc/src/manual/cowboy_http2.asciidoc | 8 +- doc/src/manual/cowboy_websocket.asciidoc | 1 + ebin/cowboy.app | 2 +- 8 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 doc/src/guide/migrating_from_2.10.asciidoc diff --git a/Makefile b/Makefile index 74aff5cf..1609bbf6 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PROJECT = cowboy PROJECT_DESCRIPTION = Small, fast, modern HTTP server. -PROJECT_VERSION = 2.10.0 +PROJECT_VERSION = 2.11.0 PROJECT_REGISTERED = cowboy_clock # Options. @@ -38,8 +38,8 @@ define HEX_TARBALL_EXTRA_METADATA #{ licenses => [<<"ISC">>], links => #{ - <<"User guide">> => <<"https://ninenines.eu/docs/en/cowboy/2.10/guide/">>, - <<"Function reference">> => <<"https://ninenines.eu/docs/en/cowboy/2.10/manual/">>, + <<"User guide">> => <<"https://ninenines.eu/docs/en/cowboy/2.11/guide/">>, + <<"Function reference">> => <<"https://ninenines.eu/docs/en/cowboy/2.11/manual/">>, <<"GitHub">> => <<"https://github.com/ninenines/cowboy">>, <<"Sponsor">> => <<"https://github.com/sponsors/essen">> } @@ -105,7 +105,7 @@ prepare_tag: $(verbose) grep http.*:// README.asciidoc $(verbose) echo $(verbose) echo "Titles in most recent CHANGELOG:" - $(verbose) for f in `ls -r doc/src/guide/migrating_from_*.asciidoc | head -n1`; do \ + $(verbose) for f in `ls -rv doc/src/guide/migrating_from_*.asciidoc | head -n1`; do \ echo $$f:; \ grep == $$f; \ done diff --git a/README.asciidoc b/README.asciidoc index cd110adc..4b850cdf 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -18,8 +18,8 @@ Cowboy is *clean* and *well tested* Erlang code. == Online documentation -* https://ninenines.eu/docs/en/cowboy/2.6/guide[User guide] -* https://ninenines.eu/docs/en/cowboy/2.6/manual[Function reference] +* https://ninenines.eu/docs/en/cowboy/2.11/guide[User guide] +* https://ninenines.eu/docs/en/cowboy/2.11/manual[Function reference] == Offline documentation diff --git a/doc/src/guide/book.asciidoc b/doc/src/guide/book.asciidoc index 582820f0..4448202c 100644 --- a/doc/src/guide/book.asciidoc +++ b/doc/src/guide/book.asciidoc @@ -75,6 +75,8 @@ include::performance.asciidoc[Performance] = Additional information +include::migrating_from_2.10.asciidoc[Migrating from Cowboy 2.10 to 2.11] + include::migrating_from_2.9.asciidoc[Migrating from Cowboy 2.9 to 2.10] include::migrating_from_2.8.asciidoc[Migrating from Cowboy 2.8 to 2.9] diff --git a/doc/src/guide/getting_started.asciidoc b/doc/src/guide/getting_started.asciidoc index 731e4a57..a26802de 100644 --- a/doc/src/guide/getting_started.asciidoc +++ b/doc/src/guide/getting_started.asciidoc @@ -69,7 +69,7 @@ fetch and compile Cowboy, and that we will use releases: PROJECT = hello_erlang DEPS = cowboy -dep_cowboy_commit = 2.10.0 +dep_cowboy_commit = 2.11.0 REL_DEPS = relx diff --git a/doc/src/guide/migrating_from_2.10.asciidoc b/doc/src/guide/migrating_from_2.10.asciidoc new file mode 100644 index 00000000..aaa8fe9d --- /dev/null +++ b/doc/src/guide/migrating_from_2.10.asciidoc @@ -0,0 +1,139 @@ +[appendix] +== Migrating from Cowboy 2.10 to 2.11 + +Cowboy 2.11 contains a variety of new features and bug +fixes. Nearly all previously experimental features are +now marked as stable, including Websocket over HTTP/2. +Included is a fix for an HTTP/2 protocol CVE. + +Cowboy 2.11 requires Erlang/OTP 24.0 or greater. + +Cowboy is now using GitHub Actions for CI. The main reason +for the move is to reduce costs by no longer having to +self-host CI runners. The downside is that GitHub runners +are less reliable and timing dependent tests are now more +likely to fail. + +=== Features added + +* A new HTTP/2 option `max_cancel_stream_rate` has been added + to control the rate of stream cancellation the server will + accept. By default Cowboy will accept 500 cancelled streams + every 10 seconds. + +* A new stream handler `cowboy_decompress_h` has been added. + It allows automatically decompressing incoming gzipped + request bodies. It includes options to protect against + zip bombs. + +* Websocket over HTTP/2 is no longer considered experimental. + Note that the `enable_connect_protocol` option must be set + to `true` in order to use Websocket over HTTP/2 for the + time being. + +* Automatic mode for reading request bodies has been + documented. In automatic mode, Cowboy waits indefinitely + for data and sends a `request_body` message when data + comes in. It mirrors `{active, once}` socket modes. + This is ideal for loop handlers and is also used + internally for HTTP/2 Websocket. + +* Ranged requests support is no longer considered + experimental. It was added in 2.6 to both `cowboy_static` + and `cowboy_rest`. Ranged responses can be produced + either automatically (for the `bytes` unit) or manually. + REST flowcharts have been updated with the new callbacks + and steps related to handling ranged requests. + +* A new HTTP/1.1 and HTTP/2 option `reset_idle_timeout_on_send` + has been added. When enabled, the `idle_timeout` will be + reset every time Cowboy sends data to the socket. + +* Loop handlers may now return a timeout value in the place + of `hibernate`. Timeouts behave the same as in `gen_server`. + +* The `generate_etag` callback of REST handlers now accepts + `undefined` as a return value to allow conditionally + generating etags. + +* The `cowboy_compress_h` options `compress_threshold` and + `compress_buffering` are no longer considered experimental. + They were de facto stable since 2.6 as they already were + documented. + +* Functions `cowboy:get_env/2,3` have been added. + +* Better error messages have been added when trying to send + a 204 or 304 response with a body; when attempting to + send two responses to a single request; when trying to + push a response after the final response; when trying + to send a `set-cookie` header without using + `cowboy_req:set_resp_cookie/3,4`. + +=== Features removed + +* Cowboy will no longer include the NPN extension when + starting a TLS listener. This extension has long been + deprecated and replaced with the ALPN extension. Cowboy + will continue using the ALPN extension for protocol + negotiation. + +=== Bugs fixed + +* A fix was made to address the HTTP/2 CVE CVE-2023-44487 + via the new HTTP/2 option `max_cancel_stream_rate`. + +* HTTP/1.1 requests that contain both a content-length and + a transfer-encoding header will now be rejected to avoid + security risks. Previous behavior was to ignore the + content-length header as recommended by the HTTP RFC. + +* HTTP/1.1 connections would sometimes use the wrong timeout + value to determine whether the connection should be closed. + This resulted in connections staying up longer than + intended. This should no longer be the case. + +* Cowboy now reacts to socket errors immediately for HTTP/1.1 + and HTTP/2 when possible. Cowboy will notice when connections + have been closed properly earlier than before. This also + means that the socket option `send_timeout_close` will work + as expected. + +* Shutting down HTTP/1.1 pipelined requests could lead to + the current request being terminated before the response + has been sent. This has been addressed. + +* When using HTTP/1.1 an invalid Connection header will now + be rejected with a 400 status code instead of crashing. + +* The documentation now recommends increasing the HTTP/2 + option `max_frame_size_received`. Cowboy currently uses + the protocol default but will increase its default in a + future release. Until then users are recommended to set + the option to ensure larger requests are accepted and + processed with acceptable performance. + +* Cowboy could sometimes send HTTP/2 WINDOW_UPDATE frames + twice in a row. Now they should be consolidated. + +* Cowboy would sometimes send HTTP/2 WINDOW_UPDATE frames + for streams that have stopped internally. This should + no longer be the case. + +* The `cowboy_compress_h` stream handler will no longer + attempt to compress responses that have an `etag` header + to avoid caching issues. + +* The `cowboy_compress_h` will now always add `accept-encoding` + to the `vary` header as it indicates that responses may + be compressed. + +* Cowboy will now remove the `trap_exit` process flag when + HTTP/1.1 connections upgrade to Websocket. + +* Exit gracefully instead of crashing when the socket gets + closed when reading the PROXY header. + +* Missing `cowboy_stream` manual pages have been added. + +* A number of fixes were made to documentation and examples. diff --git a/doc/src/manual/cowboy_http2.asciidoc b/doc/src/manual/cowboy_http2.asciidoc index a47d24ae..8eb3cf26 100644 --- a/doc/src/manual/cowboy_http2.asciidoc +++ b/doc/src/manual/cowboy_http2.asciidoc @@ -94,7 +94,10 @@ enable_connect_protocol (false):: Whether to enable the extended CONNECT method to allow protocols like Websocket to be used over an HTTP/2 stream. -This option is experimental and disabled by default. ++ +For backward compatibility reasons, this option is disabled +by default. It must be enabled to use Websocket over HTTP/2. +It will be enabled by default in a future release. goaway_initial_timeout (1000):: @@ -277,6 +280,7 @@ too many `WINDOW_UPDATE` frames. == Changelog +* *2.11*: Websocket over HTTP/2 is now considered stable. * *2.11*: The `reset_idle_timeout_on_send` option was added. * *2.11*: Add the option `max_cancel_stream_rate` to protect against another flood scenario. @@ -307,7 +311,7 @@ too many `WINDOW_UPDATE` frames. `max_frame_size_received`, `max_frame_size_sent` and `settings_timeout` to configure HTTP/2 SETTINGS and related behavior. -* *2.4*: Add the experimental option `enable_connect_protocol`. +* *2.4*: Add the option `enable_connect_protocol`. * *2.0*: Protocol introduced. == See also diff --git a/doc/src/manual/cowboy_websocket.asciidoc b/doc/src/manual/cowboy_websocket.asciidoc index b1eb5930..6d822d9a 100644 --- a/doc/src/manual/cowboy_websocket.asciidoc +++ b/doc/src/manual/cowboy_websocket.asciidoc @@ -285,6 +285,7 @@ normal circumstances if necessary. == Changelog +* *2.11*: Websocket over HTTP/2 is now considered stable. * *2.11*: HTTP/1.1 Websocket no longer traps exits by default. * *2.8*: The `active_n` option was added. * *2.7*: The commands based interface has been documented. diff --git a/ebin/cowboy.app b/ebin/cowboy.app index 9f3e1cbc..5dfa1639 100644 --- a/ebin/cowboy.app +++ b/ebin/cowboy.app @@ -1,6 +1,6 @@ {application, 'cowboy', [ {description, "Small, fast, modern HTTP server."}, - {vsn, "2.10.0"}, + {vsn, "2.11.0"}, {modules, ['cowboy','cowboy_app','cowboy_bstr','cowboy_children','cowboy_clear','cowboy_clock','cowboy_compress_h','cowboy_constraints','cowboy_decompress_h','cowboy_handler','cowboy_http','cowboy_http2','cowboy_loop','cowboy_metrics_h','cowboy_middleware','cowboy_req','cowboy_rest','cowboy_router','cowboy_static','cowboy_stream','cowboy_stream_h','cowboy_sub_protocol','cowboy_sup','cowboy_tls','cowboy_tracer_h','cowboy_websocket']}, {registered, [cowboy_sup,cowboy_clock]}, {applications, [kernel,stdlib,crypto,cowlib,ranch]}, From b36f064a91c36f3659263410fc6954c788a4609d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 25 Jan 2024 11:22:54 +0100 Subject: [PATCH 11/20] Refresh copyright lines --- LICENSE | 2 +- doc/src/guide/introduction.asciidoc | 2 +- src/cowboy.erl | 2 +- src/cowboy_app.erl | 2 +- src/cowboy_bstr.erl | 2 +- src/cowboy_children.erl | 2 +- src/cowboy_clear.erl | 2 +- src/cowboy_clock.erl | 2 +- src/cowboy_compress_h.erl | 2 +- src/cowboy_constraints.erl | 2 +- src/cowboy_handler.erl | 2 +- src/cowboy_http.erl | 2 +- src/cowboy_http2.erl | 2 +- src/cowboy_loop.erl | 2 +- src/cowboy_metrics_h.erl | 2 +- src/cowboy_middleware.erl | 2 +- src/cowboy_req.erl | 2 +- src/cowboy_rest.erl | 2 +- src/cowboy_router.erl | 2 +- src/cowboy_static.erl | 2 +- src/cowboy_stream.erl | 2 +- src/cowboy_stream_h.erl | 2 +- src/cowboy_sub_protocol.erl | 2 +- src/cowboy_sup.erl | 2 +- src/cowboy_tls.erl | 2 +- src/cowboy_tracer_h.erl | 2 +- src/cowboy_websocket.erl | 2 +- test/compress_SUITE.erl | 2 +- test/cowboy_ct_hook.erl | 2 +- test/cowboy_test.erl | 2 +- test/decompress_SUITE.erl | 1 + test/examples_SUITE.erl | 2 +- test/h2spec_SUITE.erl | 2 +- test/http2_SUITE.erl | 2 +- test/http_SUITE.erl | 2 +- test/loop_handler_SUITE.erl | 2 +- test/metrics_SUITE.erl | 2 +- test/misc_SUITE.erl | 2 +- test/plain_handler_SUITE.erl | 2 +- test/proxy_header_SUITE.erl | 2 +- test/req_SUITE.erl | 2 +- test/rest_handler_SUITE.erl | 2 +- test/rfc6585_SUITE.erl | 2 +- test/rfc7230_SUITE.erl | 2 +- test/rfc7231_SUITE.erl | 2 +- test/rfc7538_SUITE.erl | 2 +- test/rfc7540_SUITE.erl | 2 +- test/rfc8297_SUITE.erl | 2 +- test/rfc8441_SUITE.erl | 2 +- test/security_SUITE.erl | 2 +- test/static_handler_SUITE.erl | 2 +- test/stream_handler_SUITE.erl | 2 +- test/sys_SUITE.erl | 2 +- test/tracer_SUITE.erl | 2 +- test/ws_SUITE.erl | 2 +- test/ws_autobahn_SUITE.erl | 2 +- test/ws_handler_SUITE.erl | 2 +- 57 files changed, 57 insertions(+), 56 deletions(-) diff --git a/LICENSE b/LICENSE index 0b6647fb..efeaf45a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011-2022, Loïc Hoguin +Copyright (c) 2011-2024, Loïc Hoguin Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/doc/src/guide/introduction.asciidoc b/doc/src/guide/introduction.asciidoc index f81c8727..519608d1 100644 --- a/doc/src/guide/introduction.asciidoc +++ b/doc/src/guide/introduction.asciidoc @@ -42,7 +42,7 @@ Cowboy is developed for Erlang/OTP 22.0 and newer. Cowboy uses the ISC License. ---- -Copyright (c) 2011-2019, Loïc Hoguin +Copyright (c) 2011-2024, Loïc Hoguin Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy.erl b/src/cowboy.erl index 21d7f597..ad45919c 100644 --- a/src/cowboy.erl +++ b/src/cowboy.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_app.erl b/src/cowboy_app.erl index 74cba415..95ae5648 100644 --- a/src/cowboy_app.erl +++ b/src/cowboy_app.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_bstr.erl b/src/cowboy_bstr.erl index d8041e49..f23167d0 100644 --- a/src/cowboy_bstr.erl +++ b/src/cowboy_bstr.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_children.erl b/src/cowboy_children.erl index 05d39fb8..305c9897 100644 --- a/src/cowboy_children.erl +++ b/src/cowboy_children.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2017, Loïc Hoguin +%% Copyright (c) 2017-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_clear.erl b/src/cowboy_clear.erl index e4ca9da2..eaeab745 100644 --- a/src/cowboy_clear.erl +++ b/src/cowboy_clear.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2016-2017, Loïc Hoguin +%% Copyright (c) 2016-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_clock.erl b/src/cowboy_clock.erl index 28f8a1b2..e2cdf62d 100644 --- a/src/cowboy_clock.erl +++ b/src/cowboy_clock.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_compress_h.erl b/src/cowboy_compress_h.erl index 57d3dab9..338ea9f5 100644 --- a/src/cowboy_compress_h.erl +++ b/src/cowboy_compress_h.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2017, Loïc Hoguin +%% Copyright (c) 2017-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_constraints.erl b/src/cowboy_constraints.erl index 6509c4b2..33f0111f 100644 --- a/src/cowboy_constraints.erl +++ b/src/cowboy_constraints.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2014-2017, Loïc Hoguin +%% Copyright (c) 2014-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_handler.erl b/src/cowboy_handler.erl index c0f7ff71..50481681 100644 --- a/src/cowboy_handler.erl +++ b/src/cowboy_handler.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index 7aa799fd..ee1e7255 100644 --- a/src/cowboy_http.erl +++ b/src/cowboy_http.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2016-2017, Loïc Hoguin +%% Copyright (c) 2016-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl index cd685451..91e9c517 100644 --- a/src/cowboy_http2.erl +++ b/src/cowboy_http2.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2015-2017, Loïc Hoguin +%% Copyright (c) 2015-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_loop.erl b/src/cowboy_loop.erl index 9d070dbe..6859c824 100644 --- a/src/cowboy_loop.erl +++ b/src/cowboy_loop.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_metrics_h.erl b/src/cowboy_metrics_h.erl index 4107aac0..27f14d41 100644 --- a/src/cowboy_metrics_h.erl +++ b/src/cowboy_metrics_h.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2017, Loïc Hoguin +%% Copyright (c) 2017-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_middleware.erl b/src/cowboy_middleware.erl index 9a739f1f..efeef4f4 100644 --- a/src/cowboy_middleware.erl +++ b/src/cowboy_middleware.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2013-2017, Loïc Hoguin +%% Copyright (c) 2013-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 995d67c8..3f876777 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% Copyright (c) 2011, Anthony Ramine %% %% Permission to use, copy, modify, and/or distribute this software for any diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index ace5986a..fcea71ca 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_router.erl b/src/cowboy_router.erl index 0b7fe41b..61c9012c 100644 --- a/src/cowboy_router.erl +++ b/src/cowboy_router.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_static.erl b/src/cowboy_static.erl index b0cf1463..a185ef11 100644 --- a/src/cowboy_static.erl +++ b/src/cowboy_static.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2013-2017, Loïc Hoguin +%% Copyright (c) 2013-2024, Loïc Hoguin %% Copyright (c) 2011, Magnus Klaar %% %% Permission to use, copy, modify, and/or distribute this software for any diff --git a/src/cowboy_stream.erl b/src/cowboy_stream.erl index 2dad6d03..6ceb5bae 100644 --- a/src/cowboy_stream.erl +++ b/src/cowboy_stream.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2015-2017, Loïc Hoguin +%% Copyright (c) 2015-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_stream_h.erl b/src/cowboy_stream_h.erl index f516f3d2..842bd8d5 100644 --- a/src/cowboy_stream_h.erl +++ b/src/cowboy_stream_h.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2016-2017, Loïc Hoguin +%% Copyright (c) 2016-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_sub_protocol.erl b/src/cowboy_sub_protocol.erl index 67142898..062fd38f 100644 --- a/src/cowboy_sub_protocol.erl +++ b/src/cowboy_sub_protocol.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2013-2017, Loïc Hoguin +%% Copyright (c) 2013-2024, Loïc Hoguin %% Copyright (c) 2013, James Fish %% %% Permission to use, copy, modify, and/or distribute this software for any diff --git a/src/cowboy_sup.erl b/src/cowboy_sup.erl index d3ac3b0d..e37f4cf4 100644 --- a/src/cowboy_sup.erl +++ b/src/cowboy_sup.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_tls.erl b/src/cowboy_tls.erl index 4385cbc0..60ab2ed5 100644 --- a/src/cowboy_tls.erl +++ b/src/cowboy_tls.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2015-2017, Loïc Hoguin +%% Copyright (c) 2015-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_tracer_h.erl b/src/cowboy_tracer_h.erl index 9a19ae12..b1196fef 100644 --- a/src/cowboy_tracer_h.erl +++ b/src/cowboy_tracer_h.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2017, Loïc Hoguin +%% Copyright (c) 2017-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl index e7d8f318..5b98b430 100644 --- a/src/cowboy_websocket.erl +++ b/src/cowboy_websocket.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/compress_SUITE.erl b/test/compress_SUITE.erl index 75f6f5b5..46247a41 100644 --- a/test/compress_SUITE.erl +++ b/test/compress_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2017, Loïc Hoguin +%% Copyright (c) 2017-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/cowboy_ct_hook.erl b/test/cowboy_ct_hook.erl index 7d5a889a..e76ec210 100644 --- a/test/cowboy_ct_hook.erl +++ b/test/cowboy_ct_hook.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2014-2017, Loïc Hoguin +%% Copyright (c) 2014-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/cowboy_test.erl b/test/cowboy_test.erl index ed762f74..a8ee15be 100644 --- a/test/cowboy_test.erl +++ b/test/cowboy_test.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2014-2017, Loïc Hoguin +%% Copyright (c) 2014-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/decompress_SUITE.erl b/test/decompress_SUITE.erl index 36144276..7c3c6b70 100644 --- a/test/decompress_SUITE.erl +++ b/test/decompress_SUITE.erl @@ -1,4 +1,5 @@ %% Copyright (c) 2024, jdamanalo +%% Copyright (c) 2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/examples_SUITE.erl b/test/examples_SUITE.erl index 1e88f2f2..e2327bcc 100644 --- a/test/examples_SUITE.erl +++ b/test/examples_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2016-2017, Loïc Hoguin +%% Copyright (c) 2016-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/h2spec_SUITE.erl b/test/h2spec_SUITE.erl index 08497e92..67ccf031 100644 --- a/test/h2spec_SUITE.erl +++ b/test/h2spec_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2017, Loïc Hoguin +%% Copyright (c) 2017-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/http2_SUITE.erl b/test/http2_SUITE.erl index 37dfea3e..d17508a3 100644 --- a/test/http2_SUITE.erl +++ b/test/http2_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2017, Loïc Hoguin +%% Copyright (c) 2017-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index 070a6902..0325279d 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2018, Loïc Hoguin +%% Copyright (c) 2018-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/loop_handler_SUITE.erl b/test/loop_handler_SUITE.erl index 3a91e084..635fbf2c 100644 --- a/test/loop_handler_SUITE.erl +++ b/test/loop_handler_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/metrics_SUITE.erl b/test/metrics_SUITE.erl index ad4f5fb0..229e83a0 100644 --- a/test/metrics_SUITE.erl +++ b/test/metrics_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2017, Loïc Hoguin +%% Copyright (c) 2017-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/misc_SUITE.erl b/test/misc_SUITE.erl index ac8f1ddd..30abaf58 100644 --- a/test/misc_SUITE.erl +++ b/test/misc_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2017, Loïc Hoguin +%% Copyright (c) 2017-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/plain_handler_SUITE.erl b/test/plain_handler_SUITE.erl index e980d5ba..cd696dff 100644 --- a/test/plain_handler_SUITE.erl +++ b/test/plain_handler_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2018, Loïc Hoguin +%% Copyright (c) 2018-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/proxy_header_SUITE.erl b/test/proxy_header_SUITE.erl index 9d1ca2f9..cb6ab47a 100644 --- a/test/proxy_header_SUITE.erl +++ b/test/proxy_header_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2018, Loïc Hoguin +%% Copyright (c) 2018-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/req_SUITE.erl b/test/req_SUITE.erl index 183bd4b2..9f24ed17 100644 --- a/test/req_SUITE.erl +++ b/test/req_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2016-2017, Loïc Hoguin +%% Copyright (c) 2016-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/rest_handler_SUITE.erl b/test/rest_handler_SUITE.erl index 510098f1..e026552d 100644 --- a/test/rest_handler_SUITE.erl +++ b/test/rest_handler_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2017, Loïc Hoguin +%% Copyright (c) 2017-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/rfc6585_SUITE.erl b/test/rfc6585_SUITE.erl index 1f65f786..090f0286 100644 --- a/test/rfc6585_SUITE.erl +++ b/test/rfc6585_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2018, Loïc Hoguin +%% Copyright (c) 2018-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/rfc7230_SUITE.erl b/test/rfc7230_SUITE.erl index 64d9ce4a..17d1905a 100644 --- a/test/rfc7230_SUITE.erl +++ b/test/rfc7230_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2015-2017, Loïc Hoguin +%% Copyright (c) 2015-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/rfc7231_SUITE.erl b/test/rfc7231_SUITE.erl index a943d6d1..1d23cb93 100644 --- a/test/rfc7231_SUITE.erl +++ b/test/rfc7231_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2017, Loïc Hoguin +%% Copyright (c) 2017-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/rfc7538_SUITE.erl b/test/rfc7538_SUITE.erl index 5eb97054..d9bb1f67 100644 --- a/test/rfc7538_SUITE.erl +++ b/test/rfc7538_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2018, Loïc Hoguin +%% Copyright (c) 2018-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl index 986c3767..8e40c934 100644 --- a/test/rfc7540_SUITE.erl +++ b/test/rfc7540_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2016-2017, Loïc Hoguin +%% Copyright (c) 2016-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/rfc8297_SUITE.erl b/test/rfc8297_SUITE.erl index 9ae6180d..bf063515 100644 --- a/test/rfc8297_SUITE.erl +++ b/test/rfc8297_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2018, Loïc Hoguin +%% Copyright (c) 2018-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/rfc8441_SUITE.erl b/test/rfc8441_SUITE.erl index 245658fa..4c463749 100644 --- a/test/rfc8441_SUITE.erl +++ b/test/rfc8441_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2018, Loïc Hoguin +%% Copyright (c) 2018-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/security_SUITE.erl b/test/security_SUITE.erl index 6e217d57..fb630074 100644 --- a/test/security_SUITE.erl +++ b/test/security_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2018, Loïc Hoguin +%% Copyright (c) 2018-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/static_handler_SUITE.erl b/test/static_handler_SUITE.erl index 70762806..17a56e0d 100644 --- a/test/static_handler_SUITE.erl +++ b/test/static_handler_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2016-2017, Loïc Hoguin +%% Copyright (c) 2016-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/stream_handler_SUITE.erl b/test/stream_handler_SUITE.erl index e30d4201..bd87e400 100644 --- a/test/stream_handler_SUITE.erl +++ b/test/stream_handler_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2017, Loïc Hoguin +%% Copyright (c) 2017-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/sys_SUITE.erl b/test/sys_SUITE.erl index cd6cfa93..2feb716f 100644 --- a/test/sys_SUITE.erl +++ b/test/sys_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2018, Loïc Hoguin +%% Copyright (c) 2018-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/tracer_SUITE.erl b/test/tracer_SUITE.erl index 5a7d0f7f..d97ce447 100644 --- a/test/tracer_SUITE.erl +++ b/test/tracer_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2017, Loïc Hoguin +%% Copyright (c) 2017-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/ws_SUITE.erl b/test/ws_SUITE.erl index b0d590e0..3b743393 100644 --- a/test/ws_SUITE.erl +++ b/test/ws_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/ws_autobahn_SUITE.erl b/test/ws_autobahn_SUITE.erl index 24d76e8e..71e5c810 100644 --- a/test/ws_autobahn_SUITE.erl +++ b/test/ws_autobahn_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2017, Loïc Hoguin +%% Copyright (c) 2011-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above diff --git a/test/ws_handler_SUITE.erl b/test/ws_handler_SUITE.erl index 362b03ae..ab1ffc84 100644 --- a/test/ws_handler_SUITE.erl +++ b/test/ws_handler_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2018, Loïc Hoguin +%% Copyright (c) 2018-2024, Loïc Hoguin %% %% Permission to use, copy, modify, and/or distribute this software for any %% purpose with or without fee is hereby granted, provided that the above From 7d3aa6c9dd9ed6ae6aa769e54e139ec8ff7a49cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 25 Jan 2024 12:30:35 +0100 Subject: [PATCH 12/20] Run make ct-examples at the end of normal CI --- .github/workflows/ci.yaml | 37 +++++++++++++++++++++++++++++++++++++ Makefile | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f20a63f6..b6443f01 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,6 +9,9 @@ on: ## Every Monday at 2am. - cron: 0 2 * * 1 +env: + CI_ERLANG_MK: 1 + jobs: cleanup-master: name: Cleanup master build @@ -29,3 +32,37 @@ jobs: name: Cowboy needs: cleanup-master uses: ninenines/ci.erlang.mk/.github/workflows/ci.yaml@master + +# The examples test suite is nice to run but typically not +# important. So we run them after we are done with the other +# test suites. At this point we know that Erlang was built +# so we can just use the latest version. + + examples: + name: Check examples + needs: check + runs-on: 'ubuntu-latest' + if: ${{ always() }} + steps: + + - name: Checkout repository + uses: actions/checkout@v4.1.1 + + - name: Output latest Erlang/OTP version + id: latest_version + run: | + { + echo "latest<> "$GITHUB_OUTPUT" + + - name: Restore CI cache + uses: actions/cache/restore@v3.3.2 + with: + path: | + ~/erlang/ + key: ${{ runner.os }}-${{ runner.arch }}-Erlang-${{ steps.latest_version.outputs.latest }} + + - name: Run ct-examples + run: make ct-examples LATEST_ERLANG_OTP=1 diff --git a/Makefile b/Makefile index 1609bbf6..1be1885a 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ endef include erlang.mk -# Don't run the examples test suite by default. +# Don't run the examples/autobahn test suites by default. ifndef FULL CT_SUITES := $(filter-out examples ws_autobahn,$(CT_SUITES)) From a0314a6dff2f50f8cf21cd5ff0ca33b27355baf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Fri, 26 Jan 2024 12:35:25 +0100 Subject: [PATCH 13/20] Don't use specific actions versions and update cache to v4 --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b6443f01..7d5231fc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -46,7 +46,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4.1.1 + uses: actions/checkout@v4 - name: Output latest Erlang/OTP version id: latest_version @@ -58,7 +58,7 @@ jobs: } >> "$GITHUB_OUTPUT" - name: Restore CI cache - uses: actions/cache/restore@v3.3.2 + uses: actions/cache/restore@v4 with: path: | ~/erlang/ From 1c464083fa7232ac61984d0ce109ed50c85c4ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Fri, 26 Jan 2024 15:38:40 +0100 Subject: [PATCH 14/20] Update ssl_hello_world example certificate --- examples/ssl_hello_world/README.asciidoc | 7 +++-- examples/ssl_hello_world/priv/ssl/cert.pem | 20 +++++++++++++ .../ssl_hello_world/priv/ssl/cowboy-ca.crt | 16 ----------- examples/ssl_hello_world/priv/ssl/key.pem | 28 +++++++++++++++++++ examples/ssl_hello_world/priv/ssl/server.crt | 17 ----------- examples/ssl_hello_world/priv/ssl/server.key | 15 ---------- .../src/ssl_hello_world_app.erl | 5 ++-- 7 files changed, 54 insertions(+), 54 deletions(-) create mode 100644 examples/ssl_hello_world/priv/ssl/cert.pem delete mode 100644 examples/ssl_hello_world/priv/ssl/cowboy-ca.crt create mode 100644 examples/ssl_hello_world/priv/ssl/key.pem delete mode 100644 examples/ssl_hello_world/priv/ssl/server.crt delete mode 100644 examples/ssl_hello_world/priv/ssl/server.key diff --git a/examples/ssl_hello_world/README.asciidoc b/examples/ssl_hello_world/README.asciidoc index 70ee7f8c..feaa60c6 100644 --- a/examples/ssl_hello_world/README.asciidoc +++ b/examples/ssl_hello_world/README.asciidoc @@ -9,8 +9,9 @@ $ make run Then point your browser to https://localhost:8443 -You will need to temporarily trust the root certificate authority, -which can also be found in `priv/ssl/cowboy-ca.crt`. +You will be greeted by a security message. You can ask for more +information and ultimately accept to access localhost. This is +due to the example using a self-signed certificate. Recent browsers will communicate using HTTP/2. Older browsers will use HTTP/1.1. @@ -19,7 +20,7 @@ will use HTTP/1.1. [source,bash] ---- -$ curl --cacert priv/ssl/cowboy-ca.crt -i https://localhost:8443 +$ curl -k -i https://localhost:8443 HTTP/1.1 200 OK connection: keep-alive server: Cowboy diff --git a/examples/ssl_hello_world/priv/ssl/cert.pem b/examples/ssl_hello_world/priv/ssl/cert.pem new file mode 100644 index 00000000..69ed65fb --- /dev/null +++ b/examples/ssl_hello_world/priv/ssl/cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDTzCCAjegAwIBAgIUD7jNyCgABo8GlnEojOSTFWZzkJswDQYJKoZIhvcNAQEL +BQAwNzELMAkGA1UEBhMCRlIxEzARBgNVBAgMClNvbWUtU3RhdGUxEzARBgNVBAoM +Ck5pbmUgTmluZXMwHhcNMjQwMTI2MTQyODExWhcNMzcxMDA0MTQyODExWjA3MQsw +CQYDVQQGEwJGUjETMBEGA1UECAwKU29tZS1TdGF0ZTETMBEGA1UECgwKTmluZSBO +aW5lczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKfNEwF0v1Gm2e6a +M4hqI3JhmerZSNYWw8NiaUybR5hVUS9X4Chk+/y8kBLX2OYbGGlAxgbOZJa5D+kf +H1iakoUQaILinxPx3yxtIOePS3q/Xi5/EBVTdwLOoI26oSdzY2RTKKAPO1PCcAjq +6gDpw2u7q26sSU1kul6dD4Wle6+yNtnJdNKo9zLCLXr6TtuHdvbAU1oblLCKZ1Db +/uLkhGaUI/EUNeU1ZJrPmnoneYkTcG5mC5PMFVhqJ3bNYez5Hgr2Ra1Fz0dVgmRM +FpJ8NF6UQgA9dAs2Oh1uWbTjJiX0tO92RslXlhpLHS2VKZWsxiN2bniNXsNKzQ9M +ty0qnxkCAwEAAaNTMFEwHQYDVR0OBBYEFKuBPzB9rBCJNAnUyQMXjkVKIMJlMB8G +A1UdIwQYMBaAFKuBPzB9rBCJNAnUyQMXjkVKIMJlMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggEBAHWXDKlY39csROTQ2Dm3CnTj14tj3cW4onsOYTKW +FSlVdMOk3+ionB4vZA/Ino8OjrjiZ2dB3Tvl2J+AxEea3ltDbdh6qVuqSwvQZCeV +8gWp05wzyTfIpQRD10ZwOU6dzR89T+o7oG/7D8Ydk3nzecthF1aU0YBW8OtuZFog +lC/PIIoVEyUiTEnFJrkQge1OmZWiAuImIed+cEmkw9ZAN2/9i/OxWZKAGoKrmfPq +kzdOoxxFRLnqHo2OYdA0IPpSuGK5ayjYrLgXW0Wa4FKzmDh7Gy+JSrvLuFur9PEi +D0Encva2uX1hAcFQDrzICTsD6ANuIbw0cmlrCJYH6E21PrM= +-----END CERTIFICATE----- diff --git a/examples/ssl_hello_world/priv/ssl/cowboy-ca.crt b/examples/ssl_hello_world/priv/ssl/cowboy-ca.crt deleted file mode 100644 index a35ac390..00000000 --- a/examples/ssl_hello_world/priv/ssl/cowboy-ca.crt +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICeDCCAeGgAwIBAgIJAOvpU0y2e5J4MA0GCSqGSIb3DQEBBQUAMFUxCzAJBgNV -BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczETMBEGA1UECgwKTmluZSBOaW5lczEPMA0G -A1UECwwGQ293Ym95MRAwDgYDVQQDDAdST09UIENBMB4XDTEzMDIyODA1MTAwMVoX -DTMzMDIyMzA1MTAwMVowVTELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRMw -EQYDVQQKDApOaW5lIE5pbmVzMQ8wDQYDVQQLDAZDb3dib3kxEDAOBgNVBAMMB1JP -T1QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMzmY7Us06yjyUbpqwPx -Iv+xh/g3V7we07ClC9GEYnvr3OQvdA1jFEHccMBUUjRoQ8DPd6uSyK5UkixABs08 -Tt5B3VsnGKr0DIN+IO4SN2PkmBqIU/BN3KdcwN65YNr3iM0KsKWeFtAZdYx4CakX -7REbO0wjK20AH3xSBn3uFGiBAgMBAAGjUDBOMB0GA1UdDgQWBBRKfZ8KF2jlLBDm -NL6IuEuGY0pdbzAfBgNVHSMEGDAWgBRKfZ8KF2jlLBDmNL6IuEuGY0pdbzAMBgNV -HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAG1I0kBxXiLkM1b7rl2zPLizREYg -1m+ajb6rWzPOBg6TXjv58Be+H4tqoHIL/M/crixew5emftBkuAGjiKMhbIokjvan -aPTCV8U6HHvNvz9c68HpESWbd+56cHqfsS5XCKp1OpW5tbL2UQYpFKMP4qmbv3Ea -pBfPPmSFMBb1i2AI ------END CERTIFICATE----- diff --git a/examples/ssl_hello_world/priv/ssl/key.pem b/examples/ssl_hello_world/priv/ssl/key.pem new file mode 100644 index 00000000..3f9fbe47 --- /dev/null +++ b/examples/ssl_hello_world/priv/ssl/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCnzRMBdL9Rptnu +mjOIaiNyYZnq2UjWFsPDYmlMm0eYVVEvV+AoZPv8vJAS19jmGxhpQMYGzmSWuQ/p +Hx9YmpKFEGiC4p8T8d8sbSDnj0t6v14ufxAVU3cCzqCNuqEnc2NkUyigDztTwnAI +6uoA6cNru6turElNZLpenQ+FpXuvsjbZyXTSqPcywi16+k7bh3b2wFNaG5SwimdQ +2/7i5IRmlCPxFDXlNWSaz5p6J3mJE3BuZguTzBVYaid2zWHs+R4K9kWtRc9HVYJk +TBaSfDRelEIAPXQLNjodblm04yYl9LTvdkbJV5YaSx0tlSmVrMYjdm54jV7DSs0P +TLctKp8ZAgMBAAECggEAR5e6D6l5hUNcgS4+ZWnvhLo6utYI+vrMfFzNE3e+5LIm +CL6D74gicRMcn0WDj62ozSNrOfUuOpZrwOlb7OhKMkataIZ7G73bG6/V1aYwLIdg +jhL9UDQDt2lkXAPwBQ54rhHC6AOHqvVu6ocb3tbd32W7P2V3gvNChuKZAEr6Chwc +1JE5e1k7uZK4rjqZhd86pV2hks/jNknAZpEROTw80qpo3MzlMDMhXyKmyGa84t91 +1bijJ2DMPKsaxSYkWa06Zx3ymiX+qtKFRnSqZo2aEqpeTgQ0hRBSA429d7uCKO0o +kwqOyT85qMFRA+4jfkcAwUi4DELVCFlN/QNWCMH09wKBgQDVuw/sGnjVxCQ/s7pH +FuGA55S1qUtrcYsMHV5uZNtxLOqeAURomgiTpDVNNhLBuJwVjZrBv8Msl1/99EZ7 +8Hws+ERcjlbmyBiq6/VdRW6bJsrFnOS4qUbwWQp0Yztdeu6sTwIEI0KO/oFypf9G +L9mwjXwTvWEFg5etW1BPq+XmMwKBgQDI/KXNul1zCnrOY6sYrbPShYLZgPQRjNi5 +Ho6N5NxRc3xhyzExbjNtA/N/30d+/p7H8ND+TgpsYdjvEqqgpQQmCeg3/n6eSzb2 +hotCVBt8dU2TjD5v68DLzGv61s7PV81e4grkU5nCe+y7zJMwKGQ8BbmYTBBYEO0P +nTHwuwHhgwKBgQCx2B8OopRro/NZwm69Wq+3+HtIkh98vxUptoJuL6RdzzdG1N0c +gRej6t6jadw/sCLI2HSuxaddQnSQt6Oy29AoB0mzDooHLPdBumgH/Y9ksOnHd57m +fYzWz/CgGjY6ueFCJdgSo1ht7h6+zJvWxlhIzeIx9sJ1uSMMEFCKiwoY+wKBgGb+ +kTjLt/er9yKskJEk8nF/WX58RpZ3xteWgRbVoNFcjPDQX3UlM9U5oR52HP1HHbb4 +ASFQfKbtvW1F84o/BdE4YnfPQrN7d779U3+5+hvdQNPLmnNgLHxDVVJFodU++U8W +Jt66uKChQL88JnEXQcZAaMtSr01x3wmRVHY4Xs5hAoGBAMPfa+rcGukjbMF+MZ0P +ZV1Pq7AxVJ/C0XINnpZrsN+e6dO52Y2VXbnQkML7PKZXzSY88QwunBp88VoPlDux +llmLZc54zUFlsC1iHrEzt+hoxFG0tfL83vic5kSx6u5oZdxjZ2InqTzE8TmORU3v +6/ik7Q4VeDQ5uLnR4GiLW+qj +-----END PRIVATE KEY----- diff --git a/examples/ssl_hello_world/priv/ssl/server.crt b/examples/ssl_hello_world/priv/ssl/server.crt deleted file mode 100644 index 0bdfaed0..00000000 --- a/examples/ssl_hello_world/priv/ssl/server.crt +++ /dev/null @@ -1,17 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICpTCCAg6gAwIBAgIJAOvpU0y2e5J5MA0GCSqGSIb3DQEBBQUAMFUxCzAJBgNV -BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczETMBEGA1UECgwKTmluZSBOaW5lczEPMA0G -A1UECwwGQ293Ym95MRAwDgYDVQQDDAdST09UIENBMB4XDTEzMDIyODA1MjMzNFoX -DTMzMDIyMzA1MjMzNFowVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRMw -EQYDVQQKDApOaW5lIE5pbmVzMQ8wDQYDVQQLDAZDb3dib3kxEjAQBgNVBAMMCWxv -Y2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzbW1GjECzHUc/WST -qLiAGqjCNccR5saVS+yoz2SPRhpoyf0/qBrX5BY0tzmgozoTiRfE4wCiVD99Cc+D -rp/FM49r4EpZdocIovprmOmv/gwkoj95zaA6PKNn1OdmDp2hwJsX2Zm3kpbGUZTx -jDkkccmgUb4EjL7qNHq7saQtivUCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB -hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE -FB6jTEIWI8T1ckORA4GezbyYxtbvMB8GA1UdIwQYMBaAFEp9nwoXaOUsEOY0voi4 -S4ZjSl1vMA0GCSqGSIb3DQEBBQUAA4GBACMboVQjrx8u/fk3gl/sR0tbA0Wf/NcS -2Dzsy2czndgVUAG4Sqb+hfgn0dqAyUKghRrj3JDcYxYksGPIklDfPzZb7yJ39l16 -6x5ZiIzhp8CAVdPvRxRznw5rZwaXesryXu1jVSZxTr3MYZdkG6KaAM0t90+YlGLZ -UG8fAicx0Bf+ ------END CERTIFICATE----- diff --git a/examples/ssl_hello_world/priv/ssl/server.key b/examples/ssl_hello_world/priv/ssl/server.key deleted file mode 100644 index b6f73742..00000000 --- a/examples/ssl_hello_world/priv/ssl/server.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXQIBAAKBgQDNtbUaMQLMdRz9ZJOouIAaqMI1xxHmxpVL7KjPZI9GGmjJ/T+o -GtfkFjS3OaCjOhOJF8TjAKJUP30Jz4Oun8Uzj2vgSll2hwii+muY6a/+DCSiP3nN -oDo8o2fU52YOnaHAmxfZmbeSlsZRlPGMOSRxyaBRvgSMvuo0eruxpC2K9QIDAQAB -AoGAaD85c/h6bpq7Aj7CBbLaWKhFI3OqwsTITB22vsM7SE+B4zsP02UnG1OVi3UM -zytTUxpUkKV1njQ+bYZYOVqGWF4Up8tTqUglHn0FTPok1AIemELWtz3sXvdSHC1T -lqvFBAZ9kibn13qGyVOiyCFaMwfOM/05RvV7p3jfUMTWnNECQQDs7yCJZ8Ol8MyH -TGZzvkjoN2zg1KwmTbSD1hkP6QAJtPdRuqFbjlEru0/PefgOXsWLRIa3/3v0qw2G -xGkV6AXTAkEA3kNbFisqUydjPnZIYv/P6SvPdUimHJEjXbAbfNfzS9dzszrOVJd2 -XqGH7z5yzjoH3IyaIMW8GnubVzGDSjrHFwJAKSU5vELlygpwKkrNO+pelN0TLlQg -dSJnZ8GlZorq88SWcn37iX/EftivenNO7YftvEqxLoDSkOGnnrC7Iw/A+wJBAIEe -L/QY72WPJCBNJpAce/PA96vyoE1II3txqwZDjZspdpVQPDz4IFOpEwbxCFC1dYuy -Qnd3Z2cbF4r3wIWGz9ECQQCJGNhUNtY+Om1ELdqPcquxE2VRV/pucnvJSTKwyo2C -Rvm6H7kFDwPDuN23YnTOlTiho0zzCkclcIukhIVJ+dKz ------END RSA PRIVATE KEY----- diff --git a/examples/ssl_hello_world/src/ssl_hello_world_app.erl b/examples/ssl_hello_world/src/ssl_hello_world_app.erl index 959dc779..542e4d81 100644 --- a/examples/ssl_hello_world/src/ssl_hello_world_app.erl +++ b/examples/ssl_hello_world/src/ssl_hello_world_app.erl @@ -19,9 +19,8 @@ start(_Type, _Args) -> PrivDir = code:priv_dir(ssl_hello_world), {ok, _} = cowboy:start_tls(https, [ {port, 8443}, - {cacertfile, PrivDir ++ "/ssl/cowboy-ca.crt"}, - {certfile, PrivDir ++ "/ssl/server.crt"}, - {keyfile, PrivDir ++ "/ssl/server.key"} + {certfile, PrivDir ++ "/ssl/cert.pem"}, + {keyfile, PrivDir ++ "/ssl/key.pem"} ], #{env => #{dispatch => Dispatch}}), ssl_hello_world_sup:start_link(). From 81f3a21474155f68fbf494b7026b9678027d303e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 29 Jan 2024 11:38:35 +0100 Subject: [PATCH 15/20] Make sure we can cancel ct-examples in CI --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7d5231fc..f7af6d74 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,7 +42,7 @@ jobs: name: Check examples needs: check runs-on: 'ubuntu-latest' - if: ${{ always() }} + if: ${{ !cancelled() }} steps: - name: Checkout repository From cf71c742d6e04b625be1f32217c9ed11a1bd32b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 14 Mar 2024 12:36:54 +0100 Subject: [PATCH 16/20] Add max_fragmented_header_block_size HTTP/2 option --- Makefile | 2 +- doc/src/manual/cowboy_http2.asciidoc | 9 +++++++ rebar.config | 2 +- src/cowboy_http2.erl | 1 + test/security_SUITE.erl | 35 +++++++++++++++++++++++++++- 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 1be1885a..03c4f28e 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ CT_OPTS += -ct_hooks cowboy_ct_hook [] # -boot start_sasl LOCAL_DEPS = crypto DEPS = cowlib ranch -dep_cowlib = git https://github.com/ninenines/cowlib 2.12.1 +dep_cowlib = git https://github.com/ninenines/cowlib 2.13.0 dep_ranch = git https://github.com/ninenines/ranch 1.8.0 DOC_DEPS = asciideck diff --git a/doc/src/manual/cowboy_http2.asciidoc b/doc/src/manual/cowboy_http2.asciidoc index 8eb3cf26..1d2619ca 100644 --- a/doc/src/manual/cowboy_http2.asciidoc +++ b/doc/src/manual/cowboy_http2.asciidoc @@ -35,6 +35,7 @@ opts() :: #{ max_connection_window_size => 0..16#7fffffff, max_decode_table_size => non_neg_integer(), max_encode_table_size => non_neg_integer(), + max_fragmented_header_block_size => 16384..16#7fffffff, max_frame_size_received => 16384..16777215, max_frame_size_sent => 16384..16777215 | infinity, max_received_frame_rate => {pos_integer(), timeout()}, @@ -172,6 +173,14 @@ Maximum header table size in bytes used by the encoder. The server will compare this value to what the client advertises and choose the smallest one as the encoder's header table size. +max_fragmented_header_block_size (32768):: + +Maximum header block size when headers are split over multiple HEADERS +and CONTINUATION frames. Clients that attempt to send header blocks +larger than this value will receive an ENHANCE_YOUR_CALM connection +error. Note that this value is not advertised and should be large +enough for legitimate requests. + max_frame_size_received (16384):: Maximum size in bytes of the frames received by the server. This value is diff --git a/rebar.config b/rebar.config index 08bb1eca..27d06969 100644 --- a/rebar.config +++ b/rebar.config @@ -1,4 +1,4 @@ {deps, [ -{cowlib,".*",{git,"https://github.com/ninenines/cowlib","2.12.1"}},{ranch,".*",{git,"https://github.com/ninenines/ranch","1.8.0"}} +{cowlib,".*",{git,"https://github.com/ninenines/cowlib","2.13.0"}},{ranch,".*",{git,"https://github.com/ninenines/ranch","1.8.0"}} ]}. {erl_opts, [debug_info,warn_export_vars,warn_shadow_vars,warn_obsolete_guard,warn_missing_spec,warn_untyped_record]}. diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl index 91e9c517..5b1f1e15 100644 --- a/src/cowboy_http2.erl +++ b/src/cowboy_http2.erl @@ -44,6 +44,7 @@ max_connection_window_size => 0..16#7fffffff, max_decode_table_size => non_neg_integer(), max_encode_table_size => non_neg_integer(), + max_fragmented_header_block_size => 16384..16#7fffffff, max_frame_size_received => 16384..16777215, max_frame_size_sent => 16384..16777215 | infinity, max_received_frame_rate => {pos_integer(), timeout()}, diff --git a/test/security_SUITE.erl b/test/security_SUITE.erl index fb630074..a1ba9168 100644 --- a/test/security_SUITE.erl +++ b/test/security_SUITE.erl @@ -33,13 +33,14 @@ groups() -> Tests = [nc_rand, nc_zero], H1Tests = [slowloris, slowloris_chunks], H2CTests = [ + http2_cancel_flood, http2_data_dribble, http2_empty_frame_flooding_data, http2_empty_frame_flooding_headers_continuation, http2_empty_frame_flooding_push_promise, + http2_infinite_continuations, http2_ping_flood, http2_reset_flood, - http2_cancel_flood, http2_settings_flood, http2_zero_length_header_leak ], @@ -219,6 +220,38 @@ http2_empty_frame_flooding_push_promise(Config) -> {ok, <<_:24, 7:8, _:72, 1:32>>} = gen_tcp:recv(Socket, 17, 6000), ok. +http2_infinite_continuations(Config) -> + doc("Confirm that Cowboy rejects CONTINUATION frames when the " + "total size of HEADERS + CONTINUATION(s) exceeds the limit."), + {ok, Socket} = rfc7540_SUITE:do_handshake(Config), + %% Send a HEADERS frame followed by a large number + %% of continuation frames. + {HeadersBlock, _} = cow_hpack:encode([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<":path">>, <<"/">>} + ]), + HeadersBlockLen = iolist_size(HeadersBlock), + ok = gen_tcp:send(Socket, [ + %% HEADERS frame. + << + HeadersBlockLen:24, 1:8, 0:5, + 0:1, %% END_HEADERS + 0:1, + 1:1, %% END_STREAM + 0:1, + 1:31 %% Stream ID. + >>, + HeadersBlock, + %% CONTINUATION frames. + [<<1024:24, 9:8, 0:8, 0:1, 1:31, 0:1024/unit:8>> + || _ <- lists:seq(1, 100)] + ]), + %% Receive an ENHANCE_YOUR_CALM connection error. + {ok, <<_:24, 7:8, _:72, 11:32>>} = gen_tcp:recv(Socket, 17, 6000), + ok. + %% @todo http2_internal_data_buffering(Config) -> I do not know how to test this. % doc("Request many very large responses, with a larger than necessary window size, " % "but do not attempt to read from the socket. (CVE-2019-9517)"), From f7956a0f44fb3d7c926c4fe76d703b20d3ab734b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 14 Mar 2024 12:59:05 +0100 Subject: [PATCH 17/20] Update erlang.mk --- erlang.mk | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erlang.mk b/erlang.mk index 93c536ae..518a1d26 100644 --- a/erlang.mk +++ b/erlang.mk @@ -17,7 +17,7 @@ ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST))) export ERLANG_MK_FILENAME -ERLANG_MK_VERSION = bb811a8 +ERLANG_MK_VERSION = 61f58ff ERLANG_MK_WITHOUT = # Make 3.81 and 3.82 are deprecated. @@ -4665,7 +4665,6 @@ define makedep.erl end, MakeDepend = fun (F, Fd, Mod, StartLocation) -> - {ok, Filename} = file:pid2name(Fd), case io:parse_erl_form(Fd, undefined, StartLocation) of {ok, AbsData, EndLocation} -> case AbsData of From 3ea8395eb8f53a57acb5d3c00b99c70296e7cdbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Thu, 14 Mar 2024 15:06:07 +0100 Subject: [PATCH 18/20] Cowboy 2.12.0 --- Makefile | 6 +++--- README.asciidoc | 4 ++-- doc/src/guide/book.asciidoc | 2 ++ doc/src/guide/migrating_from_2.11.asciidoc | 15 +++++++++++++++ ebin/cowboy.app | 2 +- 5 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 doc/src/guide/migrating_from_2.11.asciidoc diff --git a/Makefile b/Makefile index 03c4f28e..5e88acf5 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ PROJECT = cowboy PROJECT_DESCRIPTION = Small, fast, modern HTTP server. -PROJECT_VERSION = 2.11.0 +PROJECT_VERSION = 2.12.0 PROJECT_REGISTERED = cowboy_clock # Options. @@ -38,8 +38,8 @@ define HEX_TARBALL_EXTRA_METADATA #{ licenses => [<<"ISC">>], links => #{ - <<"User guide">> => <<"https://ninenines.eu/docs/en/cowboy/2.11/guide/">>, - <<"Function reference">> => <<"https://ninenines.eu/docs/en/cowboy/2.11/manual/">>, + <<"User guide">> => <<"https://ninenines.eu/docs/en/cowboy/2.12/guide/">>, + <<"Function reference">> => <<"https://ninenines.eu/docs/en/cowboy/2.12/manual/">>, <<"GitHub">> => <<"https://github.com/ninenines/cowboy">>, <<"Sponsor">> => <<"https://github.com/sponsors/essen">> } diff --git a/README.asciidoc b/README.asciidoc index 4b850cdf..02acaa69 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -18,8 +18,8 @@ Cowboy is *clean* and *well tested* Erlang code. == Online documentation -* https://ninenines.eu/docs/en/cowboy/2.11/guide[User guide] -* https://ninenines.eu/docs/en/cowboy/2.11/manual[Function reference] +* https://ninenines.eu/docs/en/cowboy/2.12/guide[User guide] +* https://ninenines.eu/docs/en/cowboy/2.12/manual[Function reference] == Offline documentation diff --git a/doc/src/guide/book.asciidoc b/doc/src/guide/book.asciidoc index 4448202c..cf8c943e 100644 --- a/doc/src/guide/book.asciidoc +++ b/doc/src/guide/book.asciidoc @@ -75,6 +75,8 @@ include::performance.asciidoc[Performance] = Additional information +include::migrating_from_2.11.asciidoc[Migrating from Cowboy 2.11 to 2.12] + include::migrating_from_2.10.asciidoc[Migrating from Cowboy 2.10 to 2.11] include::migrating_from_2.9.asciidoc[Migrating from Cowboy 2.9 to 2.10] diff --git a/doc/src/guide/migrating_from_2.11.asciidoc b/doc/src/guide/migrating_from_2.11.asciidoc new file mode 100644 index 00000000..ab746426 --- /dev/null +++ b/doc/src/guide/migrating_from_2.11.asciidoc @@ -0,0 +1,15 @@ +[appendix] +== Migrating from Cowboy 2.11 to 2.12 + +Cowboy 2.12 contains a small security improvement for +the HTTP/2 protocol. + +Cowboy 2.12 requires Erlang/OTP 24.0 or greater. + +=== Features added + +* A new HTTP/2 option `max_fragmented_header_block_size` has + been added to limit the size of header blocks that are + sent over multiple HEADERS and CONTINUATION frames. + +* Update Cowlib to 2.13.0. diff --git a/ebin/cowboy.app b/ebin/cowboy.app index 5dfa1639..40508932 100644 --- a/ebin/cowboy.app +++ b/ebin/cowboy.app @@ -1,6 +1,6 @@ {application, 'cowboy', [ {description, "Small, fast, modern HTTP server."}, - {vsn, "2.11.0"}, + {vsn, "2.12.0"}, {modules, ['cowboy','cowboy_app','cowboy_bstr','cowboy_children','cowboy_clear','cowboy_clock','cowboy_compress_h','cowboy_constraints','cowboy_decompress_h','cowboy_handler','cowboy_http','cowboy_http2','cowboy_loop','cowboy_metrics_h','cowboy_middleware','cowboy_req','cowboy_rest','cowboy_router','cowboy_static','cowboy_stream','cowboy_stream_h','cowboy_sub_protocol','cowboy_sup','cowboy_tls','cowboy_tracer_h','cowboy_websocket']}, {registered, [cowboy_sup,cowboy_clock]}, {applications, [kernel,stdlib,crypto,cowlib,ranch]}, From 8cb9d242b0a665cada6de8b9a9dfa329e0c06ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Tue, 31 Jan 2023 11:07:31 +0100 Subject: [PATCH 19/20] Initial HTTP/3 implementation This includes Websocket over HTTP/3. Since quicer, which provides the QUIC implementation, is a NIF, Cowboy cannot depend directly on it. In order to enable QUIC and HTTP/3, users have to set the COWBOY_QUICER environment variable: export COWBOY_QUICER=1 In order to run the test suites, the same must be done for Gun: export GUN_QUICER=1 HTTP/3 support is currently not available on Windows due to compilation issues of quicer which have yet to be looked at or resolved. HTTP/3 support is also unavailable on the upcoming OTP-27 due to compilation errors in quicer dependencies. Once resolved HTTP/3 should work on OTP-27. Because of how QUIC currently works, it's possible that streams that get reset after sending a response do not receive that response. The test suite was modified to accomodate for that. A future extension to QUIC will allow us to gracefully reset streams. This also updates Erlang.mk. --- .github/workflows/ci.yaml | 2 + Makefile | 23 +- ebin/cowboy.app | 2 +- erlang.mk | 4 +- rebar.config | 2 +- src/cowboy.erl | 83 + src/cowboy_http.erl | 4 +- src/cowboy_http2.erl | 2 +- src/cowboy_http3.erl | 973 +++++++++++ src/cowboy_quicer.erl | 231 +++ src/cowboy_stream_h.erl | 5 + src/cowboy_websocket.erl | 18 +- test/compress_SUITE.erl | 16 +- test/cowboy_test.erl | 72 +- test/decompress_SUITE.erl | 6 +- test/handlers/resp_h.erl | 2 + test/loop_handler_SUITE.erl | 2 +- test/metrics_SUITE.erl | 89 +- test/misc_SUITE.erl | 2 +- test/plain_handler_SUITE.erl | 13 +- test/req_SUITE.erl | 200 ++- test/rest_handler_SUITE.erl | 18 +- test/rfc6585_SUITE.erl | 2 +- test/rfc7231_SUITE.erl | 27 +- test/rfc7538_SUITE.erl | 2 +- test/rfc7540_SUITE.erl | 7 +- test/rfc8297_SUITE.erl | 2 +- test/rfc8441_SUITE.erl | 13 +- test/rfc9114_SUITE.erl | 2426 ++++++++++++++++++++++++++++ test/rfc9114_SUITE_data/client.key | 5 + test/rfc9114_SUITE_data/client.pem | 12 + test/rfc9114_SUITE_data/server.key | 5 + test/rfc9114_SUITE_data/server.pem | 12 + test/rfc9204_SUITE.erl | 357 ++++ test/rfc9220_SUITE.erl | 485 ++++++ test/security_SUITE.erl | 6 +- test/static_handler_SUITE.erl | 68 +- test/stream_handler_SUITE.erl | 167 +- test/tracer_SUITE.erl | 3 +- 39 files changed, 5130 insertions(+), 238 deletions(-) create mode 100644 src/cowboy_http3.erl create mode 100644 src/cowboy_quicer.erl create mode 100644 test/rfc9114_SUITE.erl create mode 100644 test/rfc9114_SUITE_data/client.key create mode 100644 test/rfc9114_SUITE_data/client.pem create mode 100644 test/rfc9114_SUITE_data/server.key create mode 100644 test/rfc9114_SUITE_data/server.pem create mode 100644 test/rfc9204_SUITE.erl create mode 100644 test/rfc9220_SUITE.erl diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f7af6d74..6a2eb0c5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,6 +4,8 @@ name: Check Cowboy on: push: + branches: + - master pull_request: schedule: ## Every Monday at 2am. diff --git a/Makefile b/Makefile index 5e88acf5..6ffdfc4f 100644 --- a/Makefile +++ b/Makefile @@ -15,9 +15,14 @@ CT_OPTS += -ct_hooks cowboy_ct_hook [] # -boot start_sasl LOCAL_DEPS = crypto DEPS = cowlib ranch -dep_cowlib = git https://github.com/ninenines/cowlib 2.13.0 +dep_cowlib = git https://github.com/ninenines/cowlib master dep_ranch = git https://github.com/ninenines/ranch 1.8.0 +ifeq ($(COWBOY_QUICER),1) +DEPS += quicer +dep_quicer = git https://github.com/emqx/quic main +endif + DOC_DEPS = asciideck TEST_DEPS = $(if $(CI_ERLANG_MK),ci.erlang.mk) ct_helper gun @@ -56,15 +61,31 @@ ifndef FULL CT_SUITES := $(filter-out examples ws_autobahn,$(CT_SUITES)) endif +# Don't run HTTP/3 test suites on Windows. + +ifeq ($(PLATFORM),msys2) +CT_SUITES := $(filter-out rfc9114 rfc9204 rfc9220,$(CT_SUITES)) +endif + # Compile options. ERLC_OPTS += +warn_missing_spec +warn_untyped_record # +bin_opt_info TEST_ERLC_OPTS += +'{parse_transform, eunit_autoexport}' +ifeq ($(COWBOY_QUICER),1) +ERLC_OPTS += -D COWBOY_QUICER=1 +TEST_ERLC_OPTS += -D COWBOY_QUICER=1 +endif + # Generate rebar.config on build. app:: rebar.config +# Fix quicer compilation for HTTP/3. + +autopatch-quicer:: + $(verbose) printf "%s\n" "all: ;" > $(DEPS_DIR)/quicer/c_src/Makefile.erlang.mk + # Dialyze the tests. #DIALYZER_OPTS += --src -r test diff --git a/ebin/cowboy.app b/ebin/cowboy.app index 40508932..b5932d9f 100644 --- a/ebin/cowboy.app +++ b/ebin/cowboy.app @@ -1,7 +1,7 @@ {application, 'cowboy', [ {description, "Small, fast, modern HTTP server."}, {vsn, "2.12.0"}, - {modules, ['cowboy','cowboy_app','cowboy_bstr','cowboy_children','cowboy_clear','cowboy_clock','cowboy_compress_h','cowboy_constraints','cowboy_decompress_h','cowboy_handler','cowboy_http','cowboy_http2','cowboy_loop','cowboy_metrics_h','cowboy_middleware','cowboy_req','cowboy_rest','cowboy_router','cowboy_static','cowboy_stream','cowboy_stream_h','cowboy_sub_protocol','cowboy_sup','cowboy_tls','cowboy_tracer_h','cowboy_websocket']}, + {modules, ['cowboy','cowboy_app','cowboy_bstr','cowboy_children','cowboy_clear','cowboy_clock','cowboy_compress_h','cowboy_constraints','cowboy_decompress_h','cowboy_handler','cowboy_http','cowboy_http2','cowboy_http3','cowboy_loop','cowboy_metrics_h','cowboy_middleware','cowboy_quicer','cowboy_req','cowboy_rest','cowboy_router','cowboy_static','cowboy_stream','cowboy_stream_h','cowboy_sub_protocol','cowboy_sup','cowboy_tls','cowboy_tracer_h','cowboy_websocket']}, {registered, [cowboy_sup,cowboy_clock]}, {applications, [kernel,stdlib,crypto,cowlib,ranch]}, {optional_applications, []}, diff --git a/erlang.mk b/erlang.mk index 518a1d26..6c58ea8c 100644 --- a/erlang.mk +++ b/erlang.mk @@ -17,7 +17,7 @@ ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST))) export ERLANG_MK_FILENAME -ERLANG_MK_VERSION = 61f58ff +ERLANG_MK_VERSION = 16d60fa ERLANG_MK_WITHOUT = # Make 3.81 and 3.82 are deprecated. @@ -3565,7 +3565,7 @@ REBAR_DEPS_DIR = $(DEPS_DIR) export REBAR_DEPS_DIR REBAR3_GIT ?= https://github.com/erlang/rebar3 -REBAR3_COMMIT ?= 3f563feaf1091a1980241adefa83a32dd2eebf7c # 3.20.0 +REBAR3_COMMIT ?= 06aaecd51b0ce828b66bb65a74d3c1fd7833a4ba # 3.22.1 + OTP-27 fixes CACHE_DEPS ?= 0 diff --git a/rebar.config b/rebar.config index 27d06969..c22692c4 100644 --- a/rebar.config +++ b/rebar.config @@ -1,4 +1,4 @@ {deps, [ -{cowlib,".*",{git,"https://github.com/ninenines/cowlib","2.13.0"}},{ranch,".*",{git,"https://github.com/ninenines/ranch","1.8.0"}} +{cowlib,".*",{git,"https://github.com/ninenines/cowlib","master"}},{ranch,".*",{git,"https://github.com/ninenines/ranch","1.8.0"}} ]}. {erl_opts, [debug_info,warn_export_vars,warn_shadow_vars,warn_obsolete_guard,warn_missing_spec,warn_untyped_record]}. diff --git a/src/cowboy.erl b/src/cowboy.erl index ad45919c..e5ed831f 100644 --- a/src/cowboy.erl +++ b/src/cowboy.erl @@ -16,6 +16,7 @@ -export([start_clear/3]). -export([start_tls/3]). +-export([start_quic/3]). -export([stop_listener/1]). -export([get_env/2]). -export([get_env/3]). @@ -25,6 +26,9 @@ -export([log/2]). -export([log/4]). +%% Don't warn about the bad quicer specs. +-dialyzer([{nowarn_function, start_quic/3}]). + -type opts() :: cowboy_http:opts() | cowboy_http2:opts(). -export_type([opts/0]). @@ -44,6 +48,7 @@ -spec start_clear(ranch:ref(), ranch:opts(), opts()) -> {ok, pid()} | {error, any()}. + start_clear(Ref, TransOpts0, ProtoOpts0) -> TransOpts1 = ranch:normalize_opts(TransOpts0), {TransOpts, ConnectionType} = ensure_connection_type(TransOpts1), @@ -52,6 +57,7 @@ start_clear(Ref, TransOpts0, ProtoOpts0) -> -spec start_tls(ranch:ref(), ranch:opts(), opts()) -> {ok, pid()} | {error, any()}. + start_tls(Ref, TransOpts0, ProtoOpts0) -> TransOpts1 = ranch:normalize_opts(TransOpts0), SocketOpts = maps:get(socket_opts, TransOpts1, []), @@ -62,28 +68,103 @@ start_tls(Ref, TransOpts0, ProtoOpts0) -> ProtoOpts = ProtoOpts0#{connection_type => ConnectionType}, ranch:start_listener(Ref, ranch_ssl, TransOpts, cowboy_tls, ProtoOpts). +%% @todo Experimental function to start a barebone QUIC listener. +%% This will need to be reworked to be closer to Ranch +%% listeners and provide equivalent features. +%% +%% @todo Better type for transport options. Might require fixing quicer types. + +-spec start_quic(ranch:ref(), #{socket_opts => [{atom(), _}]}, cowboy_http3:opts()) + -> {ok, pid()}. + +start_quic(Ref, TransOpts, ProtoOpts) -> + {ok, _} = application:ensure_all_started(quicer), + Parent = self(), + SocketOpts0 = maps:get(socket_opts, TransOpts, []), + {Port, SocketOpts2} = case lists:keytake(port, 1, SocketOpts0) of + {value, {port, Port0}, SocketOpts1} -> + {Port0, SocketOpts1}; + false -> + {port_0(), SocketOpts0} + end, + SocketOpts = [ + {alpn, ["h3"]}, %% @todo Why not binary? + {peer_unidi_stream_count, 3}, %% We only need control and QPACK enc/dec. + {peer_bidi_stream_count, 100} + |SocketOpts2], + _ListenerPid = spawn(fun() -> + {ok, Listener} = quicer:listen(Port, SocketOpts), + Parent ! {ok, Listener}, + _AcceptorPid = [spawn(fun AcceptLoop() -> + {ok, Conn} = quicer:accept(Listener, []), + Pid = spawn(fun() -> + receive go -> ok end, + %% We have to do the handshake after handing control of + %% the connection otherwise streams may come in before + %% the controlling process is changed and messages will + %% not be sent to the correct process. + {ok, Conn} = quicer:handshake(Conn), + process_flag(trap_exit, true), %% @todo Only if supervisor though. + try cowboy_http3:init(Parent, Ref, Conn, ProtoOpts) + catch + exit:{shutdown,_} -> ok; + C:E:S -> log(error, "CRASH ~p:~p:~p", [C,E,S], ProtoOpts) + end + end), + ok = quicer:controlling_process(Conn, Pid), + Pid ! go, + AcceptLoop() + end) || _ <- lists:seq(1, 20)], + %% Listener process must not terminate. + receive after infinity -> ok end + end), + receive + {ok, Listener} -> + {ok, Listener} + end. + +%% Select a random UDP port using gen_udp because quicer +%% does not provide equivalent functionality. Taken from +%% quicer test suites. +port_0() -> + {ok, Socket} = gen_udp:open(0, [{reuseaddr, true}]), + {ok, {_, Port}} = inet:sockname(Socket), + gen_udp:close(Socket), + case os:type() of + {unix, darwin} -> + %% Apparently macOS doesn't free the port immediately. + timer:sleep(500); + _ -> + ok + end, + Port. + ensure_connection_type(TransOpts=#{connection_type := ConnectionType}) -> {TransOpts, ConnectionType}; ensure_connection_type(TransOpts) -> {TransOpts#{connection_type => supervisor}, supervisor}. -spec stop_listener(ranch:ref()) -> ok | {error, not_found}. + stop_listener(Ref) -> ranch:stop_listener(Ref). -spec get_env(ranch:ref(), atom()) -> ok. + get_env(Ref, Name) -> Opts = ranch:get_protocol_options(Ref), Env = maps:get(env, Opts, #{}), maps:get(Name, Env). -spec get_env(ranch:ref(), atom(), any()) -> ok. + get_env(Ref, Name, Default) -> Opts = ranch:get_protocol_options(Ref), Env = maps:get(env, Opts, #{}), maps:get(Name, Env, Default). -spec set_env(ranch:ref(), atom(), any()) -> ok. + set_env(Ref, Name, Value) -> Opts = ranch:get_protocol_options(Ref), Env = maps:get(env, Opts, #{}), @@ -93,10 +174,12 @@ set_env(Ref, Name, Value) -> %% Internal. -spec log({log, logger:level(), io:format(), list()}, opts()) -> ok. + log({log, Level, Format, Args}, Opts) -> log(Level, Format, Args, Opts). -spec log(logger:level(), io:format(), list(), opts()) -> ok. + log(Level, Format, Args, #{logger := Logger}) when Logger =/= error_logger -> _ = Logger:Level(Format, Args), diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index ee1e7255..9c92ec5a 100644 --- a/src/cowboy_http.erl +++ b/src/cowboy_http.erl @@ -12,6 +12,8 @@ %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +%% @todo Worth renaming to cowboy_http1. +%% @todo Change use of cow_http to cow_http1 where appropriate. -module(cowboy_http). -export([init/6]). @@ -1531,7 +1533,7 @@ maybe_socket_error(_, Result = {ok, _}, _) -> maybe_socket_error(State, {error, Reason}, Human) -> terminate(State, {socket_error, Reason, Human}). --spec terminate(_, _) -> no_return(). +-spec terminate(#state{} | undefined, _) -> no_return(). terminate(undefined, Reason) -> exit({shutdown, Reason}); terminate(State=#state{streams=Streams, children=Children}, Reason) -> diff --git a/src/cowboy_http2.erl b/src/cowboy_http2.erl index 5b1f1e15..2e73d5f8 100644 --- a/src/cowboy_http2.erl +++ b/src/cowboy_http2.erl @@ -1139,7 +1139,7 @@ maybe_socket_error(_, Result = {ok, _}, _) -> maybe_socket_error(State, {error, Reason}, Human) -> terminate(State, {socket_error, Reason, Human}). --spec terminate(#state{}, _) -> no_return(). +-spec terminate(#state{} | undefined, _) -> no_return(). terminate(undefined, Reason) -> exit({shutdown, Reason}); terminate(State=#state{socket=Socket, transport=Transport, http2_status=Status, diff --git a/src/cowboy_http3.erl b/src/cowboy_http3.erl new file mode 100644 index 00000000..ef3e3f6b --- /dev/null +++ b/src/cowboy_http3.erl @@ -0,0 +1,973 @@ +%% Copyright (c) 2023-2024, Loïc Hoguin +%% +%% 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. + +%% A key difference between cowboy_http2 and cowboy_http3 +%% is that HTTP/3 streams are QUIC streams and therefore +%% much of the connection state is handled outside of +%% Cowboy. + +-module(cowboy_http3). + +-export([init/4]). + +%% Temporary callback to do sendfile over QUIC. +-export([send/2]). + +%% @todo Graceful shutdown? Linger? Timeouts? Frame rates? PROXY header? +-type opts() :: #{ + compress_buffering => boolean(), + compress_threshold => non_neg_integer(), + connection_type => worker | supervisor, + enable_connect_protocol => boolean(), + env => cowboy_middleware:env(), + logger => module(), + max_decode_blocked_streams => 0..16#3fffffffffffffff, + max_decode_table_size => 0..16#3fffffffffffffff, + max_encode_blocked_streams => 0..16#3fffffffffffffff, + max_encode_table_size => 0..16#3fffffffffffffff, + max_ignored_frame_size_received => non_neg_integer() | infinity, + metrics_callback => cowboy_metrics_h:metrics_callback(), + metrics_req_filter => fun((cowboy_req:req()) -> map()), + metrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()), + middlewares => [module()], + shutdown_timeout => timeout(), + stream_handlers => [module()], + tracer_callback => cowboy_tracer_h:tracer_callback(), + tracer_flags => [atom()], + tracer_match_specs => cowboy_tracer_h:tracer_match_specs(), + %% Open ended because configured stream handlers might add options. + _ => _ +}. +-export_type([opts/0]). + +-record(stream, { + id :: cow_http3:stream_id(), + + %% Whether the stream is currently in a special state. + status :: header | {unidi, control | encoder | decoder} + | normal | {data | ignore, non_neg_integer()} | stopping, + + %% Stream buffer. + buffer = <<>> :: binary(), + + %% Stream state. + state = undefined :: undefined | {module, any()} +}). + +-record(state, { + parent :: pid(), + ref :: ranch:ref(), + conn :: cowboy_quicer:quicer_connection_handle(), + opts = #{} :: opts(), + + %% Remote address and port for the connection. + peer = undefined :: {inet:ip_address(), inet:port_number()}, + + %% Local address and port for the connection. + sock = undefined :: {inet:ip_address(), inet:port_number()}, + + %% Client certificate. + cert :: undefined | binary(), + + %% HTTP/3 state machine. + http3_machine :: cow_http3_machine:http3_machine(), + + %% Specially handled local unidi streams. + local_control_id = undefined :: undefined | cow_http3:stream_id(), + local_encoder_id = undefined :: undefined | cow_http3:stream_id(), + local_decoder_id = undefined :: undefined | cow_http3:stream_id(), + + %% Bidirectional streams used for requests and responses, + %% as well as unidirectional streams initiated by the client. + streams = #{} :: #{cow_http3:stream_id() => #stream{}}, + + %% Lingering streams that were recently reset. We may receive + %% pending data or messages for these streams a short while + %% after they have been reset. + lingering_streams = [] :: [non_neg_integer()], + + %% Streams can spawn zero or more children which are then managed + %% by this module if operating as a supervisor. + children = cowboy_children:init() :: cowboy_children:children() +}). + +-spec init(pid(), ranch:ref(), cowboy_quicer:quicer_connection_handle(), opts()) + -> no_return(). + +init(Parent, Ref, Conn, Opts) -> + {ok, SettingsBin, HTTP3Machine0} = cow_http3_machine:init(server, Opts), + %% Immediately open a control, encoder and decoder stream. + %% @todo An endpoint MAY avoid creating an encoder stream if it will not be used (for example, if its encoder does not wish to use the dynamic table or if the maximum size of the dynamic table permitted by the peer is zero). + %% @todo An endpoint MAY avoid creating a decoder stream if its decoder sets the maximum capacity of the dynamic table to zero. + {ok, ControlID} = maybe_socket_error(undefined, + cowboy_quicer:start_unidi_stream(Conn, [<<0>>, SettingsBin]), + 'A socket error occurred when opening the control stream.'), + {ok, EncoderID} = maybe_socket_error(undefined, + cowboy_quicer:start_unidi_stream(Conn, <<2>>), + 'A socket error occurred when opening the encoder stream.'), + {ok, DecoderID} = maybe_socket_error(undefined, + cowboy_quicer:start_unidi_stream(Conn, <<3>>), + 'A socket error occurred when opening the encoder stream.'), + %% Set the control, encoder and decoder streams in the machine. + HTTP3Machine = cow_http3_machine:init_unidi_local_streams( + ControlID, EncoderID, DecoderID, HTTP3Machine0), + %% Get the peername/sockname/cert. + {ok, Peer} = maybe_socket_error(undefined, cowboy_quicer:peername(Conn), + 'A socket error occurred when retrieving the peer name.'), + {ok, Sock} = maybe_socket_error(undefined, cowboy_quicer:sockname(Conn), + 'A socket error occurred when retrieving the sock name.'), + CertResult = case cowboy_quicer:peercert(Conn) of + {error, no_peercert} -> + {ok, undefined}; + Cert0 -> + Cert0 + end, + {ok, Cert} = maybe_socket_error(undefined, CertResult, + 'A socket error occurred when retrieving the client TLS certificate.'), + %% Quick! Let's go! + loop(#state{parent=Parent, ref=Ref, conn=Conn, + opts=Opts, peer=Peer, sock=Sock, cert=Cert, + http3_machine=HTTP3Machine, local_control_id=ControlID, + local_encoder_id=EncoderID, local_decoder_id=DecoderID}). + +loop(State0=#state{opts=Opts, children=Children}) -> + receive + Msg when element(1, Msg) =:= quic -> + handle_quic_msg(State0, Msg); + %% Timeouts. + {timeout, Ref, {shutdown, Pid}} -> + cowboy_children:shutdown_timeout(Children, Ref, Pid), + loop(State0); + %% Messages pertaining to a stream. + {{Pid, StreamID}, Msg} when Pid =:= self() -> + loop(info(State0, StreamID, Msg)); + %% Exit signal from children. + Msg = {'EXIT', Pid, _} -> + loop(down(State0, Pid, Msg)); + Msg -> + cowboy:log(warning, "Received stray message ~p.", [Msg], Opts), + loop(State0) + end. + +handle_quic_msg(State0=#state{opts=Opts}, Msg) -> + case cowboy_quicer:handle(Msg) of + {data, StreamID, IsFin, Data} -> + parse(State0, StreamID, Data, IsFin); + {stream_started, StreamID, StreamType} -> + State = stream_new_remote(State0, StreamID, StreamType), + loop(State); + {stream_closed, StreamID, ErrorCode} -> + State = stream_closed(State0, StreamID, ErrorCode), + loop(State); + closed -> + %% @todo Different error reason if graceful? + Reason = {socket_error, closed, 'The socket has been closed.'}, + terminate(State0, Reason); + ok -> + loop(State0); + unknown -> + cowboy:log(warning, "Received unknown QUIC message ~p.", [Msg], Opts), + loop(State0); + {socket_error, Reason} -> + terminate(State0, {socket_error, Reason, + 'An error has occurred on the socket.'}) + end. + +parse(State=#state{opts=Opts}, StreamID, Data, IsFin) -> + case stream_get(State, StreamID) of + Stream=#stream{buffer= <<>>} -> + parse1(State, Stream, Data, IsFin); + Stream=#stream{buffer=Buffer} -> + Stream1 = Stream#stream{buffer= <<>>}, + parse1(stream_store(State, Stream1), + Stream1, <>, IsFin); + %% Pending data for a stream that has been reset. Ignore. + error -> + case is_lingering_stream(State, StreamID) of + true -> + ok; + false -> + %% We avoid logging the data as it could be quite large. + cowboy:log(warning, "Received data for unknown stream ~p.", + [StreamID], Opts) + end, + loop(State) + end. + +parse1(State, Stream=#stream{status=header}, Data, IsFin) -> + parse_unidirectional_stream_header(State, Stream, Data, IsFin); +parse1(State=#state{http3_machine=HTTP3Machine0}, + #stream{status={unidi, Type}, id=StreamID}, Data, IsFin) + when Type =:= encoder; Type =:= decoder -> + case cow_http3_machine:unidi_data(Data, IsFin, StreamID, HTTP3Machine0) of + {ok, Instrs, HTTP3Machine} -> + loop(send_instructions(State#state{http3_machine=HTTP3Machine}, Instrs)); + {error, Error={connection_error, _, _}, HTTP3Machine} -> + terminate(State#state{http3_machine=HTTP3Machine}, Error) + end; +parse1(State, Stream=#stream{status={data, Len}, id=StreamID}, Data, IsFin) -> + DataLen = byte_size(Data), + if + DataLen < Len -> + %% We don't have the full frame but this is the end of the + %% data we have. So FrameIsFin is equivalent to IsFin here. + loop(frame(State, Stream#stream{status={data, Len - DataLen}}, {data, Data}, IsFin)); + true -> + <> = Data, + FrameIsFin = is_fin(IsFin, Rest), + parse(frame(State, Stream#stream{status=normal}, {data, Data1}, FrameIsFin), + StreamID, Rest, IsFin) + end; +parse1(State, Stream=#stream{status={ignore, Len}, id=StreamID}, Data, IsFin) -> + DataLen = byte_size(Data), + if + DataLen < Len -> + loop(stream_store(State, Stream#stream{status={ignore, Len - DataLen}})); + true -> + <<_:Len/binary, Rest/bits>> = Data, + parse(stream_store(State, Stream#stream{status=normal}), + StreamID, Rest, IsFin) + end; +%% @todo Clause that discards receiving data for stopping streams. +%% We may receive a few more frames after we abort receiving. +parse1(State=#state{opts=Opts}, Stream=#stream{id=StreamID}, Data, IsFin) -> + case cow_http3:parse(Data) of + {ok, Frame, Rest} -> + FrameIsFin = is_fin(IsFin, Rest), + parse(frame(State, Stream, Frame, FrameIsFin), StreamID, Rest, IsFin); + {more, Frame = {data, _}, Len} -> + %% We're at the end of the data so FrameIsFin is equivalent to IsFin. + case IsFin of + nofin -> + %% The stream will be stored at the end of processing commands. + loop(frame(State, Stream#stream{status={data, Len}}, Frame, nofin)); + fin -> + terminate(State, {connection_error, h3_frame_error, + 'Last frame on stream was truncated. (RFC9114 7.1)'}) + end; + {more, ignore, Len} -> + %% @todo This setting should be tested. + %% + %% While the default value doesn't warrant doing a streaming ignore + %% (and could work just fine with the 'more' clause), this value + %% is configurable and users may want to set it large. + MaxIgnoredLen = maps:get(max_ignored_frame_size_received, Opts, 16384), + %% We're at the end of the data so FrameIsFin is equivalent to IsFin. + case IsFin of + nofin when Len < MaxIgnoredLen -> + %% We are not processing commands so we must store the stream. + %% We also call ignored_frame here; we will not need to call + %% it again when ignoring the rest of the data. + Stream1 = Stream#stream{status={ignore, Len}}, + State1 = ignored_frame(State, Stream1), + loop(stream_store(State1, Stream1)); + nofin -> + terminate(State, {connection_error, h3_excessive_load, + 'Ignored frame larger than limit. (RFC9114 10.5)'}); + fin -> + terminate(State, {connection_error, h3_frame_error, + 'Last frame on stream was truncated. (RFC9114 7.1)'}) + end; + {ignore, Rest} -> + parse(ignored_frame(State, Stream), StreamID, Rest, IsFin); + Error = {connection_error, _, _} -> + terminate(State, Error); + more when Data =:= <<>> -> + %% The buffer was already reset to <<>>. + loop(stream_store(State, Stream)); + more -> + %% We're at the end of the data so FrameIsFin is equivalent to IsFin. + case IsFin of + nofin -> + loop(stream_store(State, Stream#stream{buffer=Data})); + fin -> + terminate(State, {connection_error, h3_frame_error, + 'Last frame on stream was truncated. (RFC9114 7.1)'}) + end + end. + +%% We may receive multiple frames in a single QUIC packet. +%% The FIN flag applies to the QUIC packet, not to the frame. +%% We must therefore only consider the frame to have a FIN +%% flag if there's no data remaining to be read. +is_fin(fin, <<>>) -> fin; +is_fin(_, _) -> nofin. + +parse_unidirectional_stream_header(State0=#state{http3_machine=HTTP3Machine0}, + Stream0=#stream{id=StreamID}, Data, IsFin) -> + case cow_http3:parse_unidi_stream_header(Data) of + {ok, Type, Rest} when Type =:= control; Type =:= encoder; Type =:= decoder -> + case cow_http3_machine:set_unidi_remote_stream_type( + StreamID, Type, HTTP3Machine0) of + {ok, HTTP3Machine} -> + State = State0#state{http3_machine=HTTP3Machine}, + Stream = Stream0#stream{status={unidi, Type}}, + parse(stream_store(State, Stream), StreamID, Rest, IsFin); + {error, Error={connection_error, _, _}, HTTP3Machine} -> + terminate(State0#state{http3_machine=HTTP3Machine}, Error) + end; + {ok, push, _} -> + terminate(State0, {connection_error, h3_stream_creation_error, + 'Only servers can push. (RFC9114 6.2.2)'}); + %% Unknown stream types must be ignored. We choose to abort the + %% stream instead of reading and discarding the incoming data. + {undefined, _} -> + loop(stream_abort_receive(State0, Stream0, h3_stream_creation_error)) + end. + +frame(State=#state{http3_machine=HTTP3Machine0}, + Stream=#stream{id=StreamID}, Frame, IsFin) -> + case cow_http3_machine:frame(Frame, IsFin, StreamID, HTTP3Machine0) of + {ok, HTTP3Machine} -> + State#state{http3_machine=HTTP3Machine}; + {ok, {data, Data}, HTTP3Machine} -> + data_frame(State#state{http3_machine=HTTP3Machine}, Stream, IsFin, Data); + {ok, {headers, Headers, PseudoHeaders, BodyLen}, Instrs, HTTP3Machine} -> + headers_frame(send_instructions(State#state{http3_machine=HTTP3Machine}, Instrs), + Stream, IsFin, Headers, PseudoHeaders, BodyLen); + {ok, {trailers, _Trailers}, Instrs, HTTP3Machine} -> + %% @todo Propagate trailers. + send_instructions(State#state{http3_machine=HTTP3Machine}, Instrs); + {ok, GoAway={goaway, _}, HTTP3Machine} -> + goaway(State#state{http3_machine=HTTP3Machine}, GoAway); + {error, Error={stream_error, _Reason, _Human}, Instrs, HTTP3Machine} -> + State1 = send_instructions(State#state{http3_machine=HTTP3Machine}, Instrs), + reset_stream(State1, Stream, Error); + {error, Error={connection_error, _, _}, HTTP3Machine} -> + terminate(State#state{http3_machine=HTTP3Machine}, Error) + end. + +data_frame(State=#state{opts=Opts}, + Stream=#stream{id=StreamID, state=StreamState0}, IsFin, Data) -> + try cowboy_stream:data(StreamID, IsFin, Data, StreamState0) of + {Commands, StreamState} -> + commands(State, Stream#stream{state=StreamState}, Commands) + catch Class:Exception:Stacktrace -> + cowboy:log(cowboy_stream:make_error_log(data, + [StreamID, IsFin, Data, StreamState0], + Class, Exception, Stacktrace), Opts), + reset_stream(State, Stream, {internal_error, {Class, Exception}, + 'Unhandled exception in cowboy_stream:data/4.'}) + end. + +headers_frame(State, Stream, IsFin, Headers, + PseudoHeaders=#{method := <<"CONNECT">>}, _) + when map_size(PseudoHeaders) =:= 2 -> + early_error(State, Stream, IsFin, Headers, PseudoHeaders, 501, + 'The CONNECT method is currently not implemented. (RFC7231 4.3.6)'); +headers_frame(State, Stream, IsFin, Headers, + PseudoHeaders=#{method := <<"TRACE">>}, _) -> + early_error(State, Stream, IsFin, Headers, PseudoHeaders, 501, + 'The TRACE method is currently not implemented. (RFC9114 4.4, RFC7231 4.3.8)'); +headers_frame(State, Stream, IsFin, Headers, PseudoHeaders=#{authority := Authority}, BodyLen) -> + headers_frame_parse_host(State, Stream, IsFin, Headers, PseudoHeaders, BodyLen, Authority); +headers_frame(State, Stream, IsFin, Headers, PseudoHeaders, BodyLen) -> + case lists:keyfind(<<"host">>, 1, Headers) of + {_, Authority} -> + headers_frame_parse_host(State, Stream, IsFin, Headers, PseudoHeaders, BodyLen, Authority); + _ -> + reset_stream(State, Stream, {stream_error, h3_message_error, + 'Requests translated from HTTP/1.1 must include a host header. (RFC7540 8.1.2.3, RFC7230 5.4)'}) + end. + +headers_frame_parse_host(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert}, + Stream=#stream{id=StreamID}, IsFin, Headers, + PseudoHeaders=#{method := Method, scheme := Scheme, path := PathWithQs}, + BodyLen, Authority) -> + try cow_http_hd:parse_host(Authority) of + {Host, Port0} -> + Port = ensure_port(Scheme, Port0), + try cow_http:parse_fullpath(PathWithQs) of + {<<>>, _} -> + reset_stream(State, Stream, {stream_error, h3_message_error, + 'The path component must not be empty. (RFC7540 8.1.2.3)'}); + {Path, Qs} -> + Req0 = #{ + ref => Ref, + pid => self(), + streamid => StreamID, + peer => Peer, + sock => Sock, + cert => Cert, + method => Method, + scheme => Scheme, + host => Host, + port => Port, + path => Path, + qs => Qs, + version => 'HTTP/3', + headers => headers_to_map(Headers, #{}), + has_body => IsFin =:= nofin, + body_length => BodyLen + }, + %% We add the protocol information for extended CONNECTs. + Req = case PseudoHeaders of + #{protocol := Protocol} -> Req0#{protocol => Protocol}; + _ -> Req0 + end, + headers_frame(State, Stream, Req) + catch _:_ -> + reset_stream(State, Stream, {stream_error, h3_message_error, + 'The :path pseudo-header is invalid. (RFC7540 8.1.2.3)'}) + end + catch _:_ -> + reset_stream(State, Stream, {stream_error, h3_message_error, + 'The :authority pseudo-header is invalid. (RFC7540 8.1.2.3)'}) + end. + +%% @todo Copied from cowboy_http2. +%% @todo How to handle "http"? +ensure_port(<<"http">>, undefined) -> 80; +ensure_port(<<"https">>, undefined) -> 443; +ensure_port(_, Port) -> Port. + +%% @todo Copied from cowboy_http2. +%% This function is necessary to properly handle duplicate headers +%% and the special-case cookie header. +headers_to_map([], Acc) -> + Acc; +headers_to_map([{Name, Value}|Tail], Acc0) -> + Acc = case Acc0 of + %% The cookie header does not use proper HTTP header lists. + #{Name := Value0} when Name =:= <<"cookie">> -> + Acc0#{Name => << Value0/binary, "; ", Value/binary >>}; + #{Name := Value0} -> + Acc0#{Name => << Value0/binary, ", ", Value/binary >>}; + _ -> + Acc0#{Name => Value} + end, + headers_to_map(Tail, Acc). + +headers_frame(State=#state{opts=Opts}, Stream=#stream{id=StreamID}, Req) -> + try cowboy_stream:init(StreamID, Req, Opts) of + {Commands, StreamState} -> + commands(State, Stream#stream{state=StreamState}, Commands) + catch Class:Exception:Stacktrace -> + cowboy:log(cowboy_stream:make_error_log(init, + [StreamID, Req, Opts], + Class, Exception, Stacktrace), Opts), + reset_stream(State, Stream, {internal_error, {Class, Exception}, + 'Unhandled exception in cowboy_stream:init/3.'}) + end. + +early_error(State0=#state{ref=Ref, opts=Opts, peer=Peer}, + Stream=#stream{id=StreamID}, _IsFin, Headers, #{method := Method}, + StatusCode0, HumanReadable) -> + %% We automatically terminate the stream but it is not an error + %% per se (at least not in the first implementation). + Reason = {stream_error, h3_no_error, HumanReadable}, + %% The partial Req is minimal for now. We only have one case + %% where it can be called (when a method is completely disabled). + PartialReq = #{ + ref => Ref, + peer => Peer, + method => Method, + headers => headers_to_map(Headers, #{}) + }, + Resp = {response, StatusCode0, RespHeaders0=#{<<"content-length">> => <<"0">>}, <<>>}, + try cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts) of + {response, StatusCode, RespHeaders, RespBody} -> + send_response(State0, Stream, StatusCode, RespHeaders, RespBody) + catch Class:Exception:Stacktrace -> + cowboy:log(cowboy_stream:make_error_log(early_error, + [StreamID, Reason, PartialReq, Resp, Opts], + Class, Exception, Stacktrace), Opts), + %% We still need to send an error response, so send what we initially + %% wanted to send. It's better than nothing. + send_headers(State0, Stream, fin, StatusCode0, RespHeaders0) + end. + +%% Erlang messages. + +down(State0=#state{opts=Opts, children=Children0}, Pid, Msg) -> + State = case cowboy_children:down(Children0, Pid) of + %% The stream was terminated already. + {ok, undefined, Children} -> + State0#state{children=Children}; + %% The stream is still running. + {ok, StreamID, Children} -> + info(State0#state{children=Children}, StreamID, Msg); + %% The process was unknown. + error -> + cowboy:log(warning, "Received EXIT signal ~p for unknown process ~p.~n", + [Msg, Pid], Opts), + State0 + end, + if +%% @todo +% State#state.http2_status =:= closing, State#state.streams =:= #{} -> +% terminate(State, {stop, normal, 'The connection is going away.'}); + true -> + State + end. + +info(State=#state{opts=Opts, http3_machine=_HTTP3Machine}, StreamID, Msg) -> + case stream_get(State, StreamID) of + Stream=#stream{state=StreamState0} -> + try cowboy_stream:info(StreamID, Msg, StreamState0) of + {Commands, StreamState} -> + commands(State, Stream#stream{state=StreamState}, Commands) + catch Class:Exception:Stacktrace -> + cowboy:log(cowboy_stream:make_error_log(info, + [StreamID, Msg, StreamState0], + Class, Exception, Stacktrace), Opts), + reset_stream(State, Stream, {internal_error, {Class, Exception}, + 'Unhandled exception in cowboy_stream:info/3.'}) + end; + error -> + case is_lingering_stream(State, StreamID) of + true -> + ok; + false -> + cowboy:log(warning, "Received message ~p for unknown stream ~p.", + [Msg, StreamID], Opts) + end, + State + end. + +%% Stream handler commands. + +commands(State, Stream, []) -> + stream_store(State, Stream); +%% Error responses are sent only if a response wasn't sent already. +commands(State=#state{http3_machine=HTTP3Machine}, Stream=#stream{id=StreamID}, + [{error_response, StatusCode, Headers, Body}|Tail]) -> + case cow_http3_machine:get_bidi_stream_local_state(StreamID, HTTP3Machine) of + {ok, idle} -> + commands(State, Stream, [{response, StatusCode, Headers, Body}|Tail]); + _ -> + commands(State, Stream, Tail) + end; +%% Send an informational response. +commands(State0, Stream, [{inform, StatusCode, Headers}|Tail]) -> + State = send_headers(State0, Stream, idle, StatusCode, Headers), + commands(State, Stream, Tail); +%% Send response headers. +commands(State0, Stream, [{response, StatusCode, Headers, Body}|Tail]) -> + State = send_response(State0, Stream, StatusCode, Headers, Body), + commands(State, Stream, Tail); +%% Send response headers. +commands(State0, Stream, [{headers, StatusCode, Headers}|Tail]) -> + State = send_headers(State0, Stream, nofin, StatusCode, Headers), + commands(State, Stream, Tail); +%%% Send a response body chunk. +commands(State0=#state{conn=Conn}, Stream=#stream{id=StreamID}, [{data, IsFin, Data}|Tail]) -> + _ = case Data of + {sendfile, Offset, Bytes, Path} -> + %% Temporary solution to do sendfile over QUIC. + {ok, _} = ranch_transport:sendfile(?MODULE, {Conn, StreamID}, + Path, Offset, Bytes, []), + ok = maybe_socket_error(State0, + cowboy_quicer:send(Conn, StreamID, cow_http3:data(<<>>), IsFin)); + _ -> + ok = maybe_socket_error(State0, + cowboy_quicer:send(Conn, StreamID, cow_http3:data(Data), IsFin)) + end, + State = maybe_send_is_fin(State0, Stream, IsFin), + commands(State, Stream, Tail); +%%% Send trailers. +commands(State0=#state{conn=Conn, http3_machine=HTTP3Machine0}, + Stream=#stream{id=StreamID}, [{trailers, Trailers}|Tail]) -> + State = case cow_http3_machine:prepare_trailers( + StreamID, HTTP3Machine0, maps:to_list(Trailers)) of + {trailers, HeaderBlock, Instrs, HTTP3Machine} -> + State1 = send_instructions(State0#state{http3_machine=HTTP3Machine}, Instrs), + ok = maybe_socket_error(State1, + cowboy_quicer:send(Conn, StreamID, cow_http3:headers(HeaderBlock), fin)), + State1; + {no_trailers, HTTP3Machine} -> + ok = maybe_socket_error(State0, + cowboy_quicer:send(Conn, StreamID, cow_http3:data(<<>>), fin)), + State0#state{http3_machine=HTTP3Machine} + end, + commands(State, Stream, Tail); +%% Send a push promise. +%% +%% @todo Responses sent as a result of a push_promise request +%% must not send push_promise frames themselves. +%% +%% @todo We should not send push_promise frames when we are +%% in the closing http2_status. +%commands(State0=#state{socket=Socket, transport=Transport, http3_machine=HTTP3Machine0}, +% Stream, [{push, Method, Scheme, Host, Port, Path, Qs, Headers0}|Tail]) -> +% Authority = case {Scheme, Port} of +% {<<"http">>, 80} -> Host; +% {<<"https">>, 443} -> Host; +% _ -> iolist_to_binary([Host, $:, integer_to_binary(Port)]) +% end, +% PathWithQs = iolist_to_binary(case Qs of +% <<>> -> Path; +% _ -> [Path, $?, Qs] +% end), +% PseudoHeaders = #{ +% method => Method, +% scheme => Scheme, +% authority => Authority, +% path => PathWithQs +% }, +% %% We need to make sure the header value is binary before we can +% %% create the Req object, as it expects them to be flat. +% Headers = maps:to_list(maps:map(fun(_, V) -> iolist_to_binary(V) end, Headers0)), +% %% @todo +% State = case cow_http2_machine:prepare_push_promise(StreamID, HTTP3Machine0, +% PseudoHeaders, Headers) of +% {ok, PromisedStreamID, HeaderBlock, HTTP3Machine} -> +% Transport:send(Socket, cow_http2:push_promise( +% StreamID, PromisedStreamID, HeaderBlock)), +% headers_frame(State0#state{http3_machine=HTTP2Machine}, +% PromisedStreamID, fin, Headers, PseudoHeaders, 0); +% {error, no_push} -> +% State0 +% end, +% commands(State, Stream, Tail); +%%% Read the request body. +%commands(State0=#state{flow=Flow, streams=Streams}, Stream, [{flow, Size}|Tail]) -> +commands(State, Stream, [{flow, _Size}|Tail]) -> + %% @todo We should tell the QUIC stream to increase its window size. +% #{StreamID := Stream=#stream{flow=StreamFlow}} = Streams, +% State = update_window(State0#state{flow=Flow + Size, +% streams=Streams#{StreamID => Stream#stream{flow=StreamFlow + Size}}}, +% StreamID), + commands(State, Stream, Tail); +%% Supervise a child process. +commands(State=#state{children=Children}, Stream=#stream{id=StreamID}, + [{spawn, Pid, Shutdown}|Tail]) -> + commands(State#state{children=cowboy_children:up(Children, Pid, StreamID, Shutdown)}, + Stream, Tail); +%% Error handling. +commands(State, Stream, [Error = {internal_error, _, _}|_Tail]) -> + %% @todo Do we want to run the commands after an internal_error? + %% @todo Do we even allow commands after? + %% @todo Only reset when the stream still exists. + reset_stream(State, Stream, Error); +%% Use a different protocol within the stream (CONNECT :protocol). +%% @todo Make sure we error out when the feature is disabled. +commands(State0, Stream0=#stream{id=StreamID}, + [{switch_protocol, Headers, _Mod, _ModState}|Tail]) -> + State = info(stream_store(State0, Stream0), StreamID, {headers, 200, Headers}), + Stream = stream_get(State, StreamID), + commands(State, Stream, Tail); +%% Set options dynamically. +commands(State, Stream, [{set_options, _Opts}|Tail]) -> + commands(State, Stream, Tail); +commands(State, Stream, [stop|_Tail]) -> + %% @todo Do we want to run the commands after a stop? + %% @todo Do we even allow commands after? + stop_stream(State, Stream); +%% Log event. +commands(State=#state{opts=Opts}, Stream, [Log={log, _, _, _}|Tail]) -> + cowboy:log(Log, Opts), + commands(State, Stream, Tail). + +send_response(State0=#state{conn=Conn, http3_machine=HTTP3Machine0}, + Stream=#stream{id=StreamID}, StatusCode, Headers, Body) -> + Size = case Body of + {sendfile, _, Bytes0, _} -> Bytes0; + _ -> iolist_size(Body) + end, + case Size of + 0 -> + State = send_headers(State0, Stream, fin, StatusCode, Headers), + maybe_send_is_fin(State, Stream, fin); + _ -> + %% @todo Add a test for HEAD to make sure we don't send the body when + %% returning {response...} from a stream handler (or {headers...} then {data...}). + {ok, _IsFin, HeaderBlock, Instrs, HTTP3Machine} + = cow_http3_machine:prepare_headers(StreamID, HTTP3Machine0, nofin, + #{status => cow_http:status_to_integer(StatusCode)}, + headers_to_list(Headers)), + State = send_instructions(State0#state{http3_machine=HTTP3Machine}, Instrs), + %% @todo It might be better to do async sends. + _ = case Body of + {sendfile, Offset, Bytes, Path} -> + ok = maybe_socket_error(State, + cowboy_quicer:send(Conn, StreamID, cow_http3:headers(HeaderBlock))), + %% Temporary solution to do sendfile over QUIC. + {ok, _} = maybe_socket_error(State, + ranch_transport:sendfile(?MODULE, {Conn, StreamID}, + Path, Offset, Bytes, [])), + ok = maybe_socket_error(State, + cowboy_quicer:send(Conn, StreamID, cow_http3:data(<<>>), fin)); + _ -> + ok = maybe_socket_error(State, + cowboy_quicer:send(Conn, StreamID, [ + cow_http3:headers(HeaderBlock), + cow_http3:data(Body) + ], fin)) + end, + maybe_send_is_fin(State, Stream, fin) + end. + +maybe_send_is_fin(State=#state{http3_machine=HTTP3Machine0}, + Stream=#stream{id=StreamID}, fin) -> + HTTP3Machine = cow_http3_machine:close_bidi_stream_for_sending(StreamID, HTTP3Machine0), + maybe_terminate_stream(State#state{http3_machine=HTTP3Machine}, Stream); +maybe_send_is_fin(State, _, _) -> + State. + +%% Temporary callback to do sendfile over QUIC. +-spec send({cowboy_quicer:quicer_connection_handle(), cow_http3:stream_id()}, + iodata()) -> ok | {error, any()}. + +send({Conn, StreamID}, IoData) -> + cowboy_quicer:send(Conn, StreamID, cow_http3:data(IoData)). + +send_headers(State0=#state{conn=Conn, http3_machine=HTTP3Machine0}, + #stream{id=StreamID}, IsFin0, StatusCode, Headers) -> + {ok, IsFin, HeaderBlock, Instrs, HTTP3Machine} + = cow_http3_machine:prepare_headers(StreamID, HTTP3Machine0, IsFin0, + #{status => cow_http:status_to_integer(StatusCode)}, + headers_to_list(Headers)), + State = send_instructions(State0#state{http3_machine=HTTP3Machine}, Instrs), + ok = maybe_socket_error(State, + cowboy_quicer:send(Conn, StreamID, cow_http3:headers(HeaderBlock), IsFin)), + State. + +%% The set-cookie header is special; we can only send one cookie per header. +headers_to_list(Headers0=#{<<"set-cookie">> := SetCookies}) -> + Headers = maps:to_list(maps:remove(<<"set-cookie">>, Headers0)), + Headers ++ [{<<"set-cookie">>, Value} || Value <- SetCookies]; +headers_to_list(Headers) -> + maps:to_list(Headers). + +%% @todo We would open unidi streams here if we only open on-demand. +%% No instructions. +send_instructions(State, undefined) -> + State; +%% Decoder instructions. +send_instructions(State=#state{conn=Conn, local_decoder_id=DecoderID}, + {decoder_instructions, DecData}) -> + ok = maybe_socket_error(State, + cowboy_quicer:send(Conn, DecoderID, DecData)), + State; +%% Encoder instructions. +send_instructions(State=#state{conn=Conn, local_encoder_id=EncoderID}, + {encoder_instructions, EncData}) -> + ok = maybe_socket_error(State, + cowboy_quicer:send(Conn, EncoderID, EncData)), + State. + +reset_stream(State0=#state{conn=Conn, http3_machine=HTTP3Machine0}, + Stream=#stream{id=StreamID}, Error) -> + Reason = case Error of + {internal_error, _, _} -> h3_internal_error; + {stream_error, Reason0, _} -> Reason0 + end, + %% @todo Do we want to close both sides? + %% @todo Should we close the send side if the receive side was already closed? + cowboy_quicer:shutdown_stream(Conn, StreamID, + both, cow_http3:error_to_code(Reason)), + State1 = case cow_http3_machine:reset_stream(StreamID, HTTP3Machine0) of + {ok, HTTP3Machine} -> + terminate_stream(State0#state{http3_machine=HTTP3Machine}, Stream, Error); + {error, not_found} -> + terminate_stream(State0, Stream, Error) + end, +%% @todo +% case reset_rate(State1) of +% {ok, State} -> +% State; +% error -> +% terminate(State1, {connection_error, enhance_your_calm, +% 'Stream reset rate larger than configuration allows. Flood? (CVE-2019-9514)'}) +% end. + State1. + +stop_stream(State0=#state{http3_machine=HTTP3Machine}, Stream=#stream{id=StreamID}) -> + %% We abort reading when stopping the stream but only + %% if the client was not finished sending data. + %% We mark the stream as 'stopping' either way. + State = case cow_http3_machine:get_bidi_stream_remote_state(StreamID, HTTP3Machine) of + {ok, fin} -> + stream_store(State0, Stream#stream{status=stopping}); + {error, not_found} -> + stream_store(State0, Stream#stream{status=stopping}); + _ -> + stream_abort_receive(State0, Stream, h3_no_error) + end, + %% Then we may need to send a response or terminate it + %% if the stream handler did not do so already. + case cow_http3_machine:get_bidi_stream_local_state(StreamID, HTTP3Machine) of + %% When the stream terminates normally (without resetting the stream) + %% and no response was sent, we need to send a proper response back to the client. + {ok, idle} -> + info(State, StreamID, {response, 204, #{}, <<>>}); + %% When a response was sent but not terminated, we need to close the stream. + %% We send a final DATA frame to complete the stream. + {ok, nofin} -> + info(State, StreamID, {data, fin, <<>>}); + %% When a response was sent fully we can terminate the stream, + %% regardless of the stream being in half-closed or closed state. + _ -> + terminate_stream(State, Stream, normal) + end. + +maybe_terminate_stream(State, Stream=#stream{status=stopping}) -> + terminate_stream(State, Stream, normal); +%% The Stream will be stored in the State at the end of commands processing. +maybe_terminate_stream(State, _) -> + State. + +terminate_stream(State=#state{streams=Streams0, children=Children0}, + #stream{id=StreamID, state=StreamState}, Reason) -> + Streams = maps:remove(StreamID, Streams0), + terminate_stream_handler(State, StreamID, Reason, StreamState), + Children = cowboy_children:shutdown(Children0, StreamID), + stream_linger(State#state{streams=Streams, children=Children}, StreamID). + +terminate_stream_handler(#state{opts=Opts}, StreamID, Reason, StreamState) -> + try + cowboy_stream:terminate(StreamID, Reason, StreamState) + catch Class:Exception:Stacktrace -> + cowboy:log(cowboy_stream:make_error_log(terminate, + [StreamID, Reason, StreamState], + Class, Exception, Stacktrace), Opts) + end. + +ignored_frame(State=#state{http3_machine=HTTP3Machine0}, #stream{id=StreamID}) -> + case cow_http3_machine:ignored_frame(StreamID, HTTP3Machine0) of + {ok, HTTP3Machine} -> + State#state{http3_machine=HTTP3Machine}; + {error, Error={connection_error, _, _}, HTTP3Machine} -> + terminate(State#state{http3_machine=HTTP3Machine}, Error) + end. + +stream_abort_receive(State=#state{conn=Conn}, Stream=#stream{id=StreamID}, Reason) -> + cowboy_quicer:shutdown_stream(Conn, StreamID, + receiving, cow_http3:error_to_code(Reason)), + stream_store(State, Stream#stream{status=stopping}). + +%% @todo Graceful connection shutdown. +%% We terminate the connection immediately if it hasn't fully been initialized. +-spec goaway(#state{}, {goaway, _}) -> no_return(). +goaway(State, {goaway, _}) -> + terminate(State, {stop, goaway, 'The connection is going away.'}). + +%% Function copied from cowboy_http. +maybe_socket_error(State, {error, closed}) -> + terminate(State, {socket_error, closed, 'The socket has been closed.'}); +maybe_socket_error(State, Reason) -> + maybe_socket_error(State, Reason, 'An error has occurred on the socket.'). + +maybe_socket_error(_, Result = ok, _) -> + Result; +maybe_socket_error(_, Result = {ok, _}, _) -> + Result; +maybe_socket_error(State, {error, Reason}, Human) -> + terminate(State, {socket_error, Reason, Human}). + +-spec terminate(#state{} | undefined, _) -> no_return(). +terminate(undefined, Reason) -> + exit({shutdown, Reason}); +terminate(State=#state{conn=Conn, %http3_status=Status, + %http3_machine=HTTP3Machine, + streams=Streams, children=Children}, Reason) -> +% if +% Status =:= connected; Status =:= closing_initiated -> +%% @todo +% %% We are terminating so it's OK if we can't send the GOAWAY anymore. +% _ = cowboy_quicer:send(Conn, ControlID, cow_http3:goaway( +% cow_http3_machine:get_last_streamid(HTTP3Machine))), + %% We already sent the GOAWAY frame. +% Status =:= closing -> +% ok +% end, + terminate_all_streams(State, maps:to_list(Streams), Reason), + cowboy_children:terminate(Children), +% terminate_linger(State), + _ = cowboy_quicer:shutdown(Conn, cow_http3:error_to_code(terminate_reason(Reason))), + exit({shutdown, Reason}). + +terminate_reason({connection_error, Reason, _}) -> Reason; +terminate_reason({stop, _, _}) -> h3_no_error; +terminate_reason({socket_error, _, _}) -> h3_internal_error. +%terminate_reason({internal_error, _, _}) -> internal_error. + +terminate_all_streams(_, [], _) -> + ok; +terminate_all_streams(State, [{StreamID, #stream{state=StreamState}}|Tail], Reason) -> + terminate_stream_handler(State, StreamID, Reason, StreamState), + terminate_all_streams(State, Tail, Reason). + +stream_get(#state{streams=Streams}, StreamID) -> + maps:get(StreamID, Streams, error). + +stream_new_remote(State=#state{http3_machine=HTTP3Machine0, streams=Streams}, + StreamID, StreamType) -> + {HTTP3Machine, Status} = case StreamType of + unidi -> + {cow_http3_machine:init_unidi_stream(StreamID, unidi_remote, HTTP3Machine0), + header}; + bidi -> + {cow_http3_machine:init_bidi_stream(StreamID, HTTP3Machine0), + normal} + end, + Stream = #stream{id=StreamID, status=Status}, + State#state{http3_machine=HTTP3Machine, streams=Streams#{StreamID => Stream}}. + +%% Stream closed message for a local (write-only) unidi stream. +stream_closed(State=#state{local_control_id=StreamID}, StreamID, _) -> + stream_closed1(State, StreamID); +stream_closed(State=#state{local_encoder_id=StreamID}, StreamID, _) -> + stream_closed1(State, StreamID); +stream_closed(State=#state{local_decoder_id=StreamID}, StreamID, _) -> + stream_closed1(State, StreamID); +stream_closed(State=#state{opts=Opts, + streams=Streams0, children=Children0}, StreamID, ErrorCode) -> + case maps:take(StreamID, Streams0) of + {#stream{state=undefined}, Streams} -> + %% Unidi stream has no handler/children. + stream_closed1(State#state{streams=Streams}, StreamID); + %% We only stop bidi streams if the stream was closed with an error + %% or the stream was already in the process of stopping. + {#stream{status=Status, state=StreamState}, Streams} + when Status =:= stopping; ErrorCode =/= 0 -> + terminate_stream_handler(State, StreamID, closed, StreamState), + Children = cowboy_children:shutdown(Children0, StreamID), + stream_closed1(State#state{streams=Streams, children=Children}, StreamID); + %% Don't remove a stream that terminated properly but + %% has chosen to remain up (custom stream handlers). + {_, _} -> + stream_closed1(State, StreamID); + %% Stream closed message for a stream that has been reset. Ignore. + error -> + case is_lingering_stream(State, StreamID) of + true -> + ok; + false -> + %% We avoid logging the data as it could be quite large. + cowboy:log(warning, "Received stream_closed for unknown stream ~p. ~p ~p", + [StreamID, self(), Streams0], Opts) + end, + State + end. + +stream_closed1(State=#state{http3_machine=HTTP3Machine0}, StreamID) -> + case cow_http3_machine:close_stream(StreamID, HTTP3Machine0) of + {ok, HTTP3Machine} -> + State#state{http3_machine=HTTP3Machine}; + {error, Error={connection_error, _, _}, HTTP3Machine} -> + terminate(State#state{http3_machine=HTTP3Machine}, Error) + end. + +stream_store(State=#state{streams=Streams}, Stream=#stream{id=StreamID}) -> + State#state{streams=Streams#{StreamID => Stream}}. + +stream_linger(State=#state{lingering_streams=Lingering0}, StreamID) -> + %% We only keep up to 100 streams in this state. @todo Make it configurable? + Lingering = [StreamID|lists:sublist(Lingering0, 100 - 1)], + State#state{lingering_streams=Lingering}. + +is_lingering_stream(#state{lingering_streams=Lingering}, StreamID) -> + lists:member(StreamID, Lingering). diff --git a/src/cowboy_quicer.erl b/src/cowboy_quicer.erl new file mode 100644 index 00000000..d9bbe1f7 --- /dev/null +++ b/src/cowboy_quicer.erl @@ -0,0 +1,231 @@ +%% Copyright (c) 2023, Loïc Hoguin +%% +%% 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. + +%% QUIC transport using the emqx/quicer NIF. + +-module(cowboy_quicer). + +%% Connection. +-export([peername/1]). +-export([sockname/1]). +-export([peercert/1]). +-export([shutdown/2]). + +%% Streams. +-export([start_unidi_stream/2]). +-export([send/3]). +-export([send/4]). +-export([shutdown_stream/4]). + +%% Messages. +-export([handle/1]). + +-ifndef(COWBOY_QUICER). + +-spec peername(_) -> no_return(). +peername(_) -> no_quicer(). + +-spec sockname(_) -> no_return(). +sockname(_) -> no_quicer(). + +-spec peercert(_) -> no_return(). +peercert(_) -> no_quicer(). + +-spec shutdown(_, _) -> no_return(). +shutdown(_, _) -> no_quicer(). + +-spec start_unidi_stream(_, _) -> no_return(). +start_unidi_stream(_, _) -> no_quicer(). + +-spec send(_, _, _) -> no_return(). +send(_, _, _) -> no_quicer(). + +-spec send(_, _, _, _) -> no_return(). +send(_, _, _, _) -> no_quicer(). + +-spec shutdown_stream(_, _, _, _) -> no_return(). +shutdown_stream(_, _, _, _) -> no_quicer(). + +-spec handle(_) -> no_return(). +handle(_) -> no_quicer(). + +no_quicer() -> + error({no_quicer, + "Cowboy must be compiled with environment variable COWBOY_QUICER=1 " + "or with compilation flag -D COWBOY_QUICER=1 in order to enable " + "QUIC support using the emqx/quic NIF"}). + +-else. + +%% @todo Make quicer export these types. +-type quicer_connection_handle() :: reference(). +-export_type([quicer_connection_handle/0]). + +-type quicer_app_errno() :: non_neg_integer(). + +-include_lib("quicer/include/quicer.hrl"). + +%% Connection. + +-spec peername(quicer_connection_handle()) + -> {ok, {inet:ip_address(), inet:port_number()}} + | {error, any()}. + +peername(Conn) -> + quicer:peername(Conn). + +-spec sockname(quicer_connection_handle()) + -> {ok, {inet:ip_address(), inet:port_number()}} + | {error, any()}. + +sockname(Conn) -> + quicer:sockname(Conn). + +-spec peercert(quicer_connection_handle()) + -> {ok, public_key:der_encoded()} + | {error, any()}. + +peercert(Conn) -> + quicer_nif:peercert(Conn). + +-spec shutdown(quicer_connection_handle(), quicer_app_errno()) + -> ok | {error, any()}. + +shutdown(Conn, ErrorCode) -> + quicer:shutdown_connection(Conn, + ?QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, + ErrorCode). + +%% Streams. + +-spec start_unidi_stream(quicer_connection_handle(), iodata()) + -> {ok, cow_http3:stream_id()} + | {error, any()}. + +start_unidi_stream(Conn, HeaderData) -> + case quicer:start_stream(Conn, #{ + active => true, + open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}) of + {ok, StreamRef} -> + case quicer:send(StreamRef, HeaderData) of + {ok, _} -> + {ok, StreamID} = quicer:get_stream_id(StreamRef), + put({quicer_stream, StreamID}, StreamRef), + {ok, StreamID}; + Error -> + Error + end; + {error, Reason1, Reason2} -> + {error, {Reason1, Reason2}}; + Error -> + Error + end. + +-spec send(quicer_connection_handle(), cow_http3:stream_id(), iodata()) + -> ok | {error, any()}. + +send(Conn, StreamID, Data) -> + send(Conn, StreamID, Data, nofin). + +-spec send(quicer_connection_handle(), cow_http3:stream_id(), iodata(), cow_http:fin()) + -> ok | {error, any()}. + +send(_Conn, StreamID, Data, IsFin) -> + StreamRef = get({quicer_stream, StreamID}), + Size = iolist_size(Data), + case quicer:send(StreamRef, Data, send_flag(IsFin)) of + {ok, Size} -> + ok; + {error, Reason1, Reason2} -> + {error, {Reason1, Reason2}}; + Error -> + Error + end. + +send_flag(nofin) -> ?QUIC_SEND_FLAG_NONE; +send_flag(fin) -> ?QUIC_SEND_FLAG_FIN. + +-spec shutdown_stream(quicer_connection_handle(), + cow_http3:stream_id(), both | receiving, quicer_app_errno()) + -> ok. + +shutdown_stream(_Conn, StreamID, Dir, ErrorCode) -> + StreamRef = get({quicer_stream, StreamID}), + _ = quicer:shutdown_stream(StreamRef, shutdown_flag(Dir), ErrorCode, infinity), + ok. + +shutdown_flag(both) -> ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT; +shutdown_flag(receiving) -> ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT_RECEIVE. + +%% Messages. + +%% @todo Probably should have the Conn given as argument too? +-spec handle({quic, _, _, _}) + -> {data, cow_http3:stream_id(), cow_http:fin(), binary()} + | {stream_started, cow_http3:stream_id(), unidi | bidi} + | {stream_closed, cow_http3:stream_id(), quicer_app_errno()} + | closed + | ok + | unknown + | {socket_error, any()}. + +handle({quic, Data, StreamRef, #{flags := Flags}}) when is_binary(Data) -> + {ok, StreamID} = quicer:get_stream_id(StreamRef), + IsFin = case Flags band ?QUIC_RECEIVE_FLAG_FIN of + ?QUIC_RECEIVE_FLAG_FIN -> fin; + _ -> nofin + end, + {data, StreamID, IsFin, Data}; +%% QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED. +handle({quic, new_stream, StreamRef, #{flags := Flags}}) -> + case quicer:setopt(StreamRef, active, true) of + ok -> + {ok, StreamID} = quicer:get_stream_id(StreamRef), + put({quicer_stream, StreamID}, StreamRef), + StreamType = case quicer:is_unidirectional(Flags) of + true -> unidi; + false -> bidi + end, + {stream_started, StreamID, StreamType}; + {error, Reason} -> + {socket_error, Reason} + end; +%% QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE. +handle({quic, stream_closed, StreamRef, #{error := ErrorCode}}) -> + {ok, StreamID} = quicer:get_stream_id(StreamRef), + {stream_closed, StreamID, ErrorCode}; +%% QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE. +handle({quic, closed, Conn, _Flags}) -> + _ = quicer:close_connection(Conn), + closed; +%% The following events are currently ignored either because +%% I do not know what they do or because we do not need to +%% take action. +handle({quic, streams_available, _Conn, _Props}) -> + ok; +handle({quic, dgram_state_changed, _Conn, _Props}) -> + ok; +%% QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT +handle({quic, transport_shutdown, _Conn, _Flags}) -> + ok; +handle({quic, peer_send_shutdown, _StreamRef, undefined}) -> + ok; +handle({quic, send_shutdown_complete, _StreamRef, _IsGraceful}) -> + ok; +handle({quic, shutdown, _Conn, success}) -> + ok; +handle(_Msg) -> + unknown. + +-endif. diff --git a/src/cowboy_stream_h.erl b/src/cowboy_stream_h.erl index 842bd8d5..b373344e 100644 --- a/src/cowboy_stream_h.erl +++ b/src/cowboy_stream_h.erl @@ -151,6 +151,11 @@ info(StreamID, Exit={'EXIT', Pid, {Reason, Stacktrace}}, State=#state{ref=Ref, p [Ref, self(), StreamID, Pid, Reason, Stacktrace]} |Commands0] end, + %% @todo We are trying to send a 500 response before resetting + %% the stream. But due to the way the RESET_STREAM frame + %% works in QUIC the data may be lost. The problem is + %% known and a draft RFC exists at + %% https://www.ietf.org/id/draft-ietf-quic-reliable-stream-reset-03.html do_info(StreamID, Exit, [ {error_response, 500, #{<<"content-length">> => <<"0">>}, <<>>} |Commands], State); diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl index 5b98b430..3cc4d303 100644 --- a/src/cowboy_websocket.erl +++ b/src/cowboy_websocket.erl @@ -103,7 +103,8 @@ %% is trying to upgrade to the Websocket protocol. -spec is_upgrade_request(cowboy_req:req()) -> boolean(). -is_upgrade_request(#{version := 'HTTP/2', method := <<"CONNECT">>, protocol := Protocol}) -> +is_upgrade_request(#{version := Version, method := <<"CONNECT">>, protocol := Protocol}) + when Version =:= 'HTTP/2'; Version =:= 'HTTP/3' -> <<"websocket">> =:= cowboy_bstr:to_lower(Protocol); is_upgrade_request(Req=#{version := 'HTTP/1.1', method := <<"GET">>}) -> ConnTokens = cowboy_req:parse_header(<<"connection">>, Req, []), @@ -148,13 +149,13 @@ upgrade(Req0=#{version := Version}, Env, Handler, HandlerState, Opts) -> <<"connection">> => <<"upgrade">>, <<"upgrade">> => <<"websocket">> }, Req0), Env}; - %% Use a generic 400 error for HTTP/2. + %% Use 501 Not Implemented for HTTP/2 and HTTP/3 as recommended + %% by RFC9220 3 (WebSockets Upgrade over HTTP/3). {error, upgrade_required} -> - {ok, cowboy_req:reply(400, Req0), Env} + {ok, cowboy_req:reply(501, Req0), Env} catch _:_ -> %% @todo Probably log something here? %% @todo Test that we can have 2 /ws 400 status code in a row on the same connection. - %% @todo Does this even work? {ok, cowboy_req:reply(400, Req0), Env} end. @@ -286,9 +287,12 @@ websocket_handshake(State, Req=#{ref := Ref, pid := Pid, streamid := StreamID}, module() | undefined, any(), binary(), {#state{}, any()}) -> no_return(). takeover(Parent, Ref, Socket, Transport, _Opts, Buffer, - {State0=#state{handler=Handler}, HandlerState}) -> - %% @todo We should have an option to disable this behavior. - ranch:remove_connection(Ref), + {State0=#state{handler=Handler, req=Req}, HandlerState}) -> + case Req of + #{version := 'HTTP/3'} -> ok; + %% @todo We should have an option to disable this behavior. + _ -> ranch:remove_connection(Ref) + end, Messages = case Transport of undefined -> undefined; _ -> Transport:messages() diff --git a/test/compress_SUITE.erl b/test/compress_SUITE.erl index 46247a41..a6a100c9 100644 --- a/test/compress_SUITE.erl +++ b/test/compress_SUITE.erl @@ -23,12 +23,20 @@ %% ct. all() -> - [ + All = [ {group, http_compress}, {group, https_compress}, {group, h2_compress}, - {group, h2c_compress} - ]. + {group, h2c_compress}, + {group, h3_compress} + ], + %% Don't run HTTP/3 tests on Windows for now. + case os:type() of + {win32, _} -> + All -- [{group, h3_compress}]; + _ -> + All + end. groups() -> cowboy_test:common_groups(ct_helper:all(?MODULE)). @@ -37,7 +45,7 @@ init_per_group(Name, Config) -> cowboy_test:init_common_groups(Name, Config, ?MODULE). end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). %% Routes. diff --git a/test/cowboy_test.erl b/test/cowboy_test.erl index a8ee15be..5a8fb13e 100644 --- a/test/cowboy_test.erl +++ b/test/cowboy_test.erl @@ -37,35 +37,82 @@ init_http2(Ref, ProtoOpts, Config) -> Port = ranch:get_port(Ref), [{ref, Ref}, {type, ssl}, {protocol, http2}, {port, Port}, {opts, Opts}|Config]. +%% @todo This will probably require TransOpts as argument. +init_http3(Ref, ProtoOpts, Config) -> + %% @todo Quicer does not currently support non-file cert/key, + %% so we use quicer test certificates for now. + %% @todo Quicer also does not support cacerts which means + %% we currently have no authentication based security. + DataDir = filename:dirname(filename:dirname(config(data_dir, Config))) + ++ "/rfc9114_SUITE_data", + TransOpts = #{ + socket_opts => [ + {certfile, DataDir ++ "/server.pem"}, + {keyfile, DataDir ++ "/server.key"} + ] + }, + {ok, Listener} = cowboy:start_quic(Ref, TransOpts, ProtoOpts), + {ok, {_, Port}} = quicer:sockname(Listener), + %% @todo Keep listener information around in a better place. + persistent_term:put({cowboy_test_quic, Ref}, Listener), + [{ref, Ref}, {type, quic}, {protocol, http3}, {port, Port}, {opts, TransOpts}|Config]. + +stop_group(Ref) -> + case persistent_term:get({cowboy_test_quic, Ref}, undefined) of + undefined -> + cowboy:stop_listener(Ref); + Listener -> + quicer:close_listener(Listener) + end. + %% Common group of listeners used by most suites. common_all() -> - [ + All = [ {group, http}, {group, https}, {group, h2}, {group, h2c}, + {group, h3}, {group, http_compress}, {group, https_compress}, {group, h2_compress}, - {group, h2c_compress} - ]. + {group, h2c_compress}, + {group, h3_compress} + ], + %% Don't run HTTP/3 tests on Windows for now. + case os:type() of + {win32, _} -> + All -- [{group, h3}, {group, h3_compress}]; + _ -> + All + end. common_groups(Tests) -> Opts = case os:getenv("NO_PARALLEL") of false -> [parallel]; _ -> [] end, - [ + Groups = [ {http, Opts, Tests}, {https, Opts, Tests}, {h2, Opts, Tests}, {h2c, Opts, Tests}, + {h3, Opts, Tests}, {http_compress, Opts, Tests}, {https_compress, Opts, Tests}, {h2_compress, Opts, Tests}, - {h2c_compress, Opts, Tests} - ]. + {h2c_compress, Opts, Tests}, + {h3_compress, Opts, Tests} + ], + %% Don't run HTTP/3 tests on Windows for now. + case os:type() of + {win32, _} -> + Groups -- [{h3, Opts, Tests}, {h3_compress, Opts, Tests}]; + _ -> + Groups + end. + init_common_groups(Name = http, Config, Mod) -> init_http(Name, #{ @@ -84,6 +131,10 @@ init_common_groups(Name = h2c, Config, Mod) -> env => #{dispatch => Mod:init_dispatch(Config)} }, [{flavor, vanilla}|Config]), lists:keyreplace(protocol, 1, Config1, {protocol, http2}); +init_common_groups(Name = h3, Config, Mod) -> + init_http3(Name, #{ + env => #{dispatch => Mod:init_dispatch(Config)} + }, [{flavor, vanilla}|Config]); init_common_groups(Name = http_compress, Config, Mod) -> init_http(Name, #{ env => #{dispatch => Mod:init_dispatch(Config)}, @@ -104,7 +155,12 @@ init_common_groups(Name = h2c_compress, Config, Mod) -> env => #{dispatch => Mod:init_dispatch(Config)}, stream_handlers => [cowboy_compress_h, cowboy_stream_h] }, [{flavor, compress}|Config]), - lists:keyreplace(protocol, 1, Config1, {protocol, http2}). + lists:keyreplace(protocol, 1, Config1, {protocol, http2}); +init_common_groups(Name = h3_compress, Config, Mod) -> + init_http3(Name, #{ + env => #{dispatch => Mod:init_dispatch(Config)}, + stream_handlers => [cowboy_compress_h, cowboy_stream_h] + }, [{flavor, compress}|Config]). %% Support functions for testing using Gun. @@ -114,7 +170,7 @@ gun_open(Config) -> gun_open(Config, Opts) -> TlsOpts = case proplists:get_value(no_cert, Config, false) of true -> [{verify, verify_none}]; - false -> ct_helper:get_certs_from_ets() + false -> ct_helper:get_certs_from_ets() %% @todo Wrong in current quicer. end, {ok, ConnPid} = gun:open("localhost", config(port, Config), Opts#{ retry => 0, diff --git a/test/decompress_SUITE.erl b/test/decompress_SUITE.erl index 7c3c6b70..f61bb5df 100644 --- a/test/decompress_SUITE.erl +++ b/test/decompress_SUITE.erl @@ -38,6 +38,8 @@ init_per_group(Name = h2, Config) -> init_per_group(Name = h2c, Config) -> Config1 = cowboy_test:init_http(Name, init_plain_opts(Config), Config), lists:keyreplace(protocol, 1, Config1, {protocol, http2}); +init_per_group(Name = h3, Config) -> + cowboy_test:init_http3(Name, init_plain_opts(Config), Config); init_per_group(Name = http_compress, Config) -> cowboy_test:init_http(Name, init_compress_opts(Config), Config); init_per_group(Name = https_compress, Config) -> @@ -46,7 +48,9 @@ init_per_group(Name = h2_compress, Config) -> cowboy_test:init_http2(Name, init_compress_opts(Config), Config); init_per_group(Name = h2c_compress, Config) -> Config1 = cowboy_test:init_http(Name, init_compress_opts(Config), Config), - lists:keyreplace(protocol, 1, Config1, {protocol, http2}). + lists:keyreplace(protocol, 1, Config1, {protocol, http2}); +init_per_group(Name = h3_compress, Config) -> + cowboy_test:init_http3(Name, init_compress_opts(Config), Config). end_per_group(Name, _) -> cowboy:stop_listener(Name). diff --git a/test/handlers/resp_h.erl b/test/handlers/resp_h.erl index aae9eb95..6e9b5f75 100644 --- a/test/handlers/resp_h.erl +++ b/test/handlers/resp_h.erl @@ -182,6 +182,7 @@ do(<<"reply2">>, Req0, Opts) -> <<"twice">> -> ct_helper:ignore(cowboy_req, reply, 4), Req1 = cowboy_req:reply(200, Req0), + timer:sleep(100), cowboy_req:reply(200, Req1); Status -> cowboy_req:reply(binary_to_integer(Status), Req0) @@ -245,6 +246,7 @@ do(<<"stream_reply2">>, Req0, Opts) -> <<"twice">> -> ct_helper:ignore(cowboy_req, stream_reply, 3), Req1 = cowboy_req:stream_reply(200, Req0), + timer:sleep(100), %% We will crash here so the body shouldn't be sent. Req = cowboy_req:stream_reply(200, Req1), stream_body(Req), diff --git a/test/loop_handler_SUITE.erl b/test/loop_handler_SUITE.erl index 635fbf2c..c5daaf84 100644 --- a/test/loop_handler_SUITE.erl +++ b/test/loop_handler_SUITE.erl @@ -32,7 +32,7 @@ init_per_group(Name, Config) -> cowboy_test:init_common_groups(Name, Config, ?MODULE). end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). %% Dispatch configuration. diff --git a/test/metrics_SUITE.erl b/test/metrics_SUITE.erl index 229e83a0..6a272f21 100644 --- a/test/metrics_SUITE.erl +++ b/test/metrics_SUITE.erl @@ -44,6 +44,8 @@ init_per_group(Name = h2, Config) -> init_per_group(Name = h2c, Config) -> Config1 = cowboy_test:init_http(Name, init_plain_opts(Config), Config), lists:keyreplace(protocol, 1, Config1, {protocol, http2}); +init_per_group(Name = h3, Config) -> + cowboy_test:init_http3(Name, init_plain_opts(Config), Config); init_per_group(Name = http_compress, Config) -> cowboy_test:init_http(Name, init_compress_opts(Config), Config); init_per_group(Name = https_compress, Config) -> @@ -52,10 +54,12 @@ init_per_group(Name = h2_compress, Config) -> cowboy_test:init_http2(Name, init_compress_opts(Config), Config); init_per_group(Name = h2c_compress, Config) -> Config1 = cowboy_test:init_http(Name, init_compress_opts(Config), Config), - lists:keyreplace(protocol, 1, Config1, {protocol, http2}). + lists:keyreplace(protocol, 1, Config1, {protocol, http2}); +init_per_group(Name = h3_compress, Config) -> + cowboy_test:init_http3(Name, init_compress_opts(Config), Config). end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). init_plain_opts(Config) -> #{ @@ -157,16 +161,24 @@ do_get(Path, UserData, Config) -> #{ ref := _, pid := From, - streamid := 1, - reason := normal, + streamid := StreamID, + reason := normal, %% @todo Getting h3_no_error here. req := #{}, informational := [], user_data := UserData } = Metrics, + do_check_streamid(StreamID, Config), %% All good! gun:close(ConnPid) end. +do_check_streamid(StreamID, Config) -> + case config(protocol, Config) of + http -> 1 = StreamID; + http2 -> 1 = StreamID; + http3 -> 0 = StreamID + end. + post_body(Config) -> doc("Confirm metrics are correct for a normal POST request."), %% Perform a POST request. @@ -218,12 +230,13 @@ post_body(Config) -> #{ ref := _, pid := From, - streamid := 1, + streamid := StreamID, reason := normal, req := #{}, informational := [], user_data := #{} } = Metrics, + do_check_streamid(StreamID, Config), %% All good! gun:close(ConnPid) end. @@ -273,12 +286,13 @@ no_resp_body(Config) -> #{ ref := _, pid := From, - streamid := 1, + streamid := StreamID, reason := normal, req := #{}, informational := [], user_data := #{} } = Metrics, + do_check_streamid(StreamID, Config), %% All good! gun:close(ConnPid) end. @@ -291,7 +305,8 @@ early_error(Config) -> %% reason in both protocols. {Method, Headers, Status, Error} = case config(protocol, Config) of http -> {<<"GET">>, [{<<"host">>, <<"host:port">>}], 400, protocol_error}; - http2 -> {<<"TRACE">>, [], 501, no_error} + http2 -> {<<"TRACE">>, [], 501, no_error}; + http3 -> {<<"TRACE">>, [], 501, h3_no_error} end, Ref = gun:request(ConnPid, Method, "/", [ {<<"accept-encoding">>, <<"gzip">>}, @@ -305,7 +320,7 @@ early_error(Config) -> #{ ref := _, pid := From, - streamid := 1, + streamid := StreamID, reason := {stream_error, Error, _}, partial_req := #{}, resp_status := Status, @@ -313,6 +328,7 @@ early_error(Config) -> early_error_time := _, resp_body_length := 0 } = Metrics, + do_check_streamid(StreamID, Config), ExpectedRespHeaders = maps:from_list(RespHeaders), %% All good! gun:close(ConnPid) @@ -321,7 +337,8 @@ early_error(Config) -> early_error_request_line(Config) -> case config(protocol, Config) of http -> do_early_error_request_line(Config); - http2 -> doc("There are no request lines in HTTP/2.") + http2 -> doc("There are no request lines in HTTP/2."); + http3 -> doc("There are no request lines in HTTP/3.") end. do_early_error_request_line(Config) -> @@ -341,7 +358,7 @@ do_early_error_request_line(Config) -> #{ ref := _, pid := From, - streamid := 1, + streamid := StreamID, reason := {connection_error, protocol_error, _}, partial_req := #{}, resp_status := 400, @@ -349,6 +366,7 @@ do_early_error_request_line(Config) -> early_error_time := _, resp_body_length := 0 } = Metrics, + do_check_streamid(StreamID, Config), ExpectedRespHeaders = maps:from_list(RespHeaders), %% All good! ok @@ -362,7 +380,9 @@ stream_reply(Config) -> ws(Config) -> case config(protocol, Config) of http -> do_ws(Config); - http2 -> doc("It is not currently possible to switch to Websocket over HTTP/2.") + %% @todo The test can be implemented for HTTP/2. + http2 -> doc("It is not currently possible to switch to Websocket over HTTP/2."); + http3 -> {skip, "Gun does not currently support Websocket over HTTP/3."} end. do_ws(Config) -> @@ -405,7 +425,7 @@ do_ws(Config) -> #{ ref := _, pid := From, - streamid := 1, + streamid := StreamID, reason := switch_protocol, req := #{}, %% A 101 upgrade response was sent. @@ -420,6 +440,7 @@ do_ws(Config) -> }], user_data := #{} } = Metrics, + do_check_streamid(StreamID, Config), %% All good! ok end, @@ -438,7 +459,15 @@ error_response(Config) -> {<<"accept-encoding">>, <<"gzip">>}, {<<"x-test-pid">>, pid_to_list(self())} ]), - {response, fin, 500, RespHeaders} = gun:await(ConnPid, Ref, infinity), + Protocol = config(protocol, Config), + RespHeaders = case gun:await(ConnPid, Ref, infinity) of + {response, fin, 500, RespHeaders0} -> + RespHeaders0; + %% The RST_STREAM arrived before the start of the response. + %% See maybe_h3_error comment for details. + {error, {stream_error, {stream_error, h3_internal_error, _}}} when Protocol =:= http3 -> + unknown + end, timer:sleep(100), %% Receive the metrics and validate them. receive @@ -463,7 +492,14 @@ error_response(Config) -> resp_headers := ExpectedRespHeaders, resp_body_length := 0 } = Metrics, - ExpectedRespHeaders = maps:from_list(RespHeaders), + case RespHeaders of + %% The HTTP/3 stream has reset too early so we can't + %% verify the response headers. + unknown -> + ok; + _ -> + ExpectedRespHeaders = maps:from_list(RespHeaders) + end, %% The request process executed normally. #{procs := Procs} = Metrics, [{_, #{ @@ -476,12 +512,13 @@ error_response(Config) -> #{ ref := _, pid := From, - streamid := 1, + streamid := StreamID, reason := {internal_error, {'EXIT', _Pid, {crash, StackTrace}}, 'Stream process crashed.'}, req := #{}, informational := [], user_data := #{} } = Metrics, + do_check_streamid(StreamID, Config), %% All good! gun:close(ConnPid) end. @@ -495,7 +532,15 @@ error_response_after_reply(Config) -> {<<"accept-encoding">>, <<"gzip">>}, {<<"x-test-pid">>, pid_to_list(self())} ]), - {response, fin, 200, RespHeaders} = gun:await(ConnPid, Ref, infinity), + Protocol = config(protocol, Config), + RespHeaders = case gun:await(ConnPid, Ref, infinity) of + {response, fin, 200, RespHeaders0} -> + RespHeaders0; + %% The RST_STREAM arrived before the start of the response. + %% See maybe_h3_error comment for details. + {error, {stream_error, {stream_error, h3_internal_error, _}}} when Protocol =:= http3 -> + unknown + end, timer:sleep(100), %% Receive the metrics and validate them. receive @@ -520,7 +565,14 @@ error_response_after_reply(Config) -> resp_headers := ExpectedRespHeaders, resp_body_length := 0 } = Metrics, - ExpectedRespHeaders = maps:from_list(RespHeaders), + case RespHeaders of + %% The HTTP/3 stream has reset too early so we can't + %% verify the response headers. + unknown -> + ok; + _ -> + ExpectedRespHeaders = maps:from_list(RespHeaders) + end, %% The request process executed normally. #{procs := Procs} = Metrics, [{_, #{ @@ -533,12 +585,13 @@ error_response_after_reply(Config) -> #{ ref := _, pid := From, - streamid := 1, + streamid := StreamID, reason := {internal_error, {'EXIT', _Pid, {crash, StackTrace}}, 'Stream process crashed.'}, req := #{}, informational := [], user_data := #{} } = Metrics, + do_check_streamid(StreamID, Config), %% All good! gun:close(ConnPid) end. diff --git a/test/misc_SUITE.erl b/test/misc_SUITE.erl index 30abaf58..c918321b 100644 --- a/test/misc_SUITE.erl +++ b/test/misc_SUITE.erl @@ -43,7 +43,7 @@ init_per_group(Name, Config) -> end_per_group(env, _) -> ok; end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). init_dispatch(_) -> cowboy_router:compile([{"localhost", [ diff --git a/test/plain_handler_SUITE.erl b/test/plain_handler_SUITE.erl index cd696dff..756c0a61 100644 --- a/test/plain_handler_SUITE.erl +++ b/test/plain_handler_SUITE.erl @@ -39,7 +39,7 @@ init_per_group(Name, Config) -> cowboy_test:init_common_groups(Name, Config, ?MODULE). end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). %% Routes. @@ -58,8 +58,15 @@ crash_after_reply(Config) -> Ref = gun:get(ConnPid, "/crash/reply", [ {<<"accept-encoding">>, <<"gzip">>} ]), - {response, fin, 200, _} = gun:await(ConnPid, Ref), - {error, timeout} = gun:await(ConnPid, Ref, 1000), + Protocol = config(protocol, Config), + _ = case gun:await(ConnPid, Ref) of + {response, fin, 200, _} -> + {error, timeout} = gun:await(ConnPid, Ref, 1000); + %% See maybe_h3_error comment for details. + {error, {stream_error, {stream_error, h3_internal_error, _}}} + when Protocol =:= http3 -> + ok + end, gun:close(ConnPid). crash_before_reply(Config) -> diff --git a/test/req_SUITE.erl b/test/req_SUITE.erl index 9f24ed17..9036cac9 100644 --- a/test/req_SUITE.erl +++ b/test/req_SUITE.erl @@ -46,7 +46,7 @@ init_per_group(Name, Config) -> cowboy_test:init_common_groups(Name, Config, ?MODULE). end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). %% Routes. @@ -107,13 +107,17 @@ do_get(Path, Config) -> do_get(Path, Headers, Config) -> ConnPid = gun_open(Config), Ref = gun:get(ConnPid, Path, [{<<"accept-encoding">>, <<"gzip">>}|Headers]), - {response, IsFin, Status, RespHeaders} = gun:await(ConnPid, Ref, infinity), - {ok, RespBody} = case IsFin of - nofin -> gun:await_body(ConnPid, Ref, infinity); - fin -> {ok, <<>>} - end, - gun:close(ConnPid), - {Status, RespHeaders, do_decode(RespHeaders, RespBody)}. + case gun:await(ConnPid, Ref, infinity) of + {response, IsFin, Status, RespHeaders} -> + {ok, RespBody} = case IsFin of + nofin -> gun:await_body(ConnPid, Ref, infinity); + fin -> {ok, <<>>} + end, + gun:close(ConnPid), + {Status, RespHeaders, do_decode(RespHeaders, RespBody)}; + {error, {stream_error, Error}} -> + Error + end. do_get_body(Path, Config) -> do_get_body(Path, [], Config). @@ -142,7 +146,9 @@ do_get_inform(Path, Config) -> fin -> {ok, <<>>} end, gun:close(ConnPid), - {InfoStatus, InfoHeaders, RespStatus, RespHeaders, do_decode(RespHeaders, RespBody)} + {InfoStatus, InfoHeaders, RespStatus, RespHeaders, do_decode(RespHeaders, RespBody)}; + {error, {stream_error, Error}} -> + Error end. do_decode(Headers, Body) -> @@ -184,7 +190,8 @@ bindings(Config) -> cert(Config) -> case config(type, Config) of tcp -> doc("TLS certificates can only be provided over TLS."); - ssl -> do_cert(Config) + ssl -> do_cert(Config); + quic -> do_cert(Config) end. do_cert(Config) -> @@ -386,7 +393,8 @@ port(Config) -> Port = do_get_body("/direct/port", Config), ExpectedPort = case config(type, Config) of tcp -> <<"80">>; - ssl -> <<"443">> + ssl -> <<"443">>; + quic -> <<"443">> end, ExpectedPort = do_get_body("/port", [{<<"host">>, <<"localhost">>}], Config), ExpectedPort = do_get_body("/direct/port", [{<<"host">>, <<"localhost">>}], Config), @@ -412,7 +420,8 @@ do_scheme(Path, Config) -> Transport = config(type, Config), case do_get_body(Path, Config) of <<"http">> when Transport =:= tcp -> ok; - <<"https">> when Transport =:= ssl -> ok + <<"https">> when Transport =:= ssl -> ok; + <<"https">> when Transport =:= quic -> ok end. sock(Config) -> @@ -425,7 +434,8 @@ uri(Config) -> doc("Request URI building/modification."), Scheme = case config(type, Config) of tcp -> <<"http">>; - ssl -> <<"https">> + ssl -> <<"https">>; + quic -> <<"https">> end, SLen = byte_size(Scheme), Port = integer_to_binary(config(port, Config)), @@ -459,7 +469,8 @@ do_version(Path, Config) -> Protocol = config(protocol, Config), case do_get_body(Path, Config) of <<"HTTP/1.1">> when Protocol =:= http -> ok; - <<"HTTP/2">> when Protocol =:= http2 -> ok + <<"HTTP/2">> when Protocol =:= http2 -> ok; + <<"HTTP/3">> when Protocol =:= http3 -> ok end. %% Tests: Request body. @@ -513,11 +524,19 @@ read_body_period(Config) -> %% for 2 seconds. The test succeeds if we get some of the data back %% (meaning the function will have returned after the period ends). gun:data(ConnPid, Ref, nofin, Body), - {response, nofin, 200, _} = gun:await(ConnPid, Ref, infinity), - {data, _, Data} = gun:await(ConnPid, Ref, infinity), - %% We expect to read at least some data. - true = Data =/= <<>>, - gun:close(ConnPid). + Response = gun:await(ConnPid, Ref, infinity), + case Response of + {response, nofin, 200, _} -> + {data, _, Data} = gun:await(ConnPid, Ref, infinity), + %% We expect to read at least some data. + true = Data =/= <<>>, + gun:close(ConnPid); + %% We got a crash, likely because the environment + %% was overloaded and the timeout triggered. Try again. + {response, _, 500, _} -> + gun:close(ConnPid), + read_body_period(Config) + end. %% We expect a crash. do_read_body_timeout(Path, Body, Config) -> @@ -525,7 +544,13 @@ do_read_body_timeout(Path, Body, Config) -> Ref = gun:headers(ConnPid, "POST", Path, [ {<<"content-length">>, integer_to_binary(byte_size(Body))} ]), - {response, _, 500, _} = gun:await(ConnPid, Ref, infinity), + case gun:await(ConnPid, Ref, infinity) of + {response, _, 500, _} -> + ok; + %% See do_maybe_h3_error comment for details. + {error, {stream_error, {stream_error, h3_internal_error, _}}} -> + ok + end, gun:close(ConnPid). read_body_auto(Config) -> @@ -620,15 +645,19 @@ do_read_urlencoded_body_too_long(Path, Body, Config) -> {<<"content-length">>, integer_to_binary(byte_size(Body) * 2)} ]), gun:data(ConnPid, Ref, nofin, Body), - {response, _, 408, RespHeaders} = gun:await(ConnPid, Ref, infinity), - _ = case config(protocol, Config) of - http -> + Protocol = config(protocol, Config), + case gun:await(ConnPid, Ref, infinity) of + {response, _, 408, RespHeaders} when Protocol =:= http -> %% 408 error responses should close HTTP/1.1 connections. - {_, <<"close">>} = lists:keyfind(<<"connection">>, 1, RespHeaders); - http2 -> - ok - end, - gun:close(ConnPid). + {_, <<"close">>} = lists:keyfind(<<"connection">>, 1, RespHeaders), + gun:close(ConnPid); + {response, _, 408, _} when Protocol =:= http2; Protocol =:= http3 -> + gun:close(ConnPid); + %% We must have hit the timeout due to busy CI environment. Retry. + {response, _, 500, _} -> + gun:close(ConnPid), + do_read_urlencoded_body_too_long(Path, Body, Config) + end. read_and_match_urlencoded_body(Config) -> doc("Read and match an application/x-www-form-urlencoded request body."), @@ -824,7 +853,7 @@ set_resp_header(Config) -> {200, Headers, <<"OK">>} = do_get("/resp/set_resp_header", Config), true = lists:keymember(<<"content-type">>, 1, Headers), %% The set-cookie header is special. set_resp_cookie must be used. - {500, _, _} = do_get("/resp/set_resp_header_cookie", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/resp/set_resp_header_cookie", Config)), ok. set_resp_headers(Config) -> @@ -833,7 +862,7 @@ set_resp_headers(Config) -> true = lists:keymember(<<"content-type">>, 1, Headers), true = lists:keymember(<<"content-encoding">>, 1, Headers), %% The set-cookie header is special. set_resp_cookie must be used. - {500, _, _} = do_get("/resp/set_resp_headers_cookie", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/resp/set_resp_headers_cookie", Config)), ok. resp_header(Config) -> @@ -895,28 +924,52 @@ delete_resp_header(Config) -> false = lists:keymember(<<"content-type">>, 1, Headers), ok. +%% Data may be lost due to how RESET_STREAM QUIC frame works. +%% Because there is ongoing work for a better way to reset streams +%% (https://www.ietf.org/archive/id/draft-ietf-quic-reliable-stream-reset-03.html) +%% we convert the error to a 500 to keep the tests more explicit +%% at what we expect. +%% @todo When RESET_STREAM_AT gets added we can remove this function. +do_maybe_h3_error2({stream_error, h3_internal_error, _}) -> {500, []}; +do_maybe_h3_error2(Result) -> Result. + +do_maybe_h3_error3({stream_error, h3_internal_error, _}) -> {500, [], <<>>}; +do_maybe_h3_error3(Result) -> Result. + inform2(Config) -> doc("Informational response(s) without headers, followed by the real response."), {102, [], 200, _, _} = do_get_inform("/resp/inform2/102", Config), {102, [], 200, _, _} = do_get_inform("/resp/inform2/binary", Config), - {500, _} = do_get_inform("/resp/inform2/error", Config), + {500, _} = do_maybe_h3_error2(do_get_inform("/resp/inform2/error", Config)), {102, [], 200, _, _} = do_get_inform("/resp/inform2/twice", Config), - %% @todo How to test this properly? This isn't enough. - {200, _} = do_get_inform("/resp/inform2/after_reply", Config), - ok. + %% With HTTP/1.1 and HTTP/2 we will not get an error. + %% With HTTP/3 however the stream will occasionally + %% be reset before Gun receives the response. + case do_get_inform("/resp/inform2/after_reply", Config) of + {200, _} -> + ok; + {stream_error, h3_internal_error, _} -> + ok + end. inform3(Config) -> doc("Informational response(s) with headers, followed by the real response."), Headers = [{<<"ext-header">>, <<"ext-value">>}], {102, Headers, 200, _, _} = do_get_inform("/resp/inform3/102", Config), {102, Headers, 200, _, _} = do_get_inform("/resp/inform3/binary", Config), - {500, _} = do_get_inform("/resp/inform3/error", Config), + {500, _} = do_maybe_h3_error2(do_get_inform("/resp/inform3/error", Config)), %% The set-cookie header is special. set_resp_cookie must be used. - {500, _} = do_get_inform("/resp/inform3/set_cookie", Config), + {500, _} = do_maybe_h3_error2(do_get_inform("/resp/inform3/set_cookie", Config)), {102, Headers, 200, _, _} = do_get_inform("/resp/inform3/twice", Config), - %% @todo How to test this properly? This isn't enough. - {200, _} = do_get_inform("/resp/inform3/after_reply", Config), - ok. + %% With HTTP/1.1 and HTTP/2 we will not get an error. + %% With HTTP/3 however the stream will occasionally + %% be reset before Gun receives the response. + case do_get_inform("/resp/inform3/after_reply", Config) of + {200, _} -> + ok; + {stream_error, h3_internal_error, _} -> + ok + end. reply2(Config) -> doc("Response with default headers and no body."), @@ -924,7 +977,7 @@ reply2(Config) -> {201, _, _} = do_get("/resp/reply2/201", Config), {404, _, _} = do_get("/resp/reply2/404", Config), {200, _, _} = do_get("/resp/reply2/binary", Config), - {500, _, _} = do_get("/resp/reply2/error", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/resp/reply2/error", Config)), %% @todo How to test this properly? This isn't enough. {200, _, _} = do_get("/resp/reply2/twice", Config), ok. @@ -937,9 +990,9 @@ reply3(Config) -> true = lists:keymember(<<"content-type">>, 1, Headers2), {404, Headers3, _} = do_get("/resp/reply3/404", Config), true = lists:keymember(<<"content-type">>, 1, Headers3), - {500, _, _} = do_get("/resp/reply3/error", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/resp/reply3/error", Config)), %% The set-cookie header is special. set_resp_cookie must be used. - {500, _, _} = do_get("/resp/reply3/set_cookie", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/resp/reply3/set_cookie", Config)), ok. reply4(Config) -> @@ -947,9 +1000,9 @@ reply4(Config) -> {200, _, <<"OK">>} = do_get("/resp/reply4/200", Config), {201, _, <<"OK">>} = do_get("/resp/reply4/201", Config), {404, _, <<"OK">>} = do_get("/resp/reply4/404", Config), - {500, _, _} = do_get("/resp/reply4/error", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/resp/reply4/error", Config)), %% The set-cookie header is special. set_resp_cookie must be used. - {500, _, _} = do_get("/resp/reply4/set_cookie", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/resp/reply4/set_cookie", Config)), ok. stream_reply2(Config) -> @@ -959,12 +1012,11 @@ stream_reply2(Config) -> {201, _, Body} = do_get("/resp/stream_reply2/201", Config), {404, _, Body} = do_get("/resp/stream_reply2/404", Config), {200, _, Body} = do_get("/resp/stream_reply2/binary", Config), - {500, _, _} = do_get("/resp/stream_reply2/error", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/resp/stream_reply2/error", Config)), ok. stream_reply2_twice(Config) -> - doc("Attempting to stream a response twice results in a crash. " - "This crash can only be properly detected in HTTP/2."), + doc("Attempting to stream a response twice results in a crash."), ConnPid = gun_open(Config), Ref = gun:get(ConnPid, "/resp/stream_reply2/twice", [{<<"accept-encoding">>, <<"gzip">>}]), @@ -983,8 +1035,10 @@ stream_reply2_twice(Config) -> zlib:inflateInit(Z, 31), 0 = iolist_size(zlib:inflate(Z, Data)), ok; - %% In HTTP/2 the stream gets reset with an appropriate error. + %% In HTTP/2 and HTTP/3 the stream gets reset with an appropriate error. {http2, _, {error, {stream_error, {stream_error, internal_error, _}}}} -> + ok; + {http3, _, {error, {stream_error, {stream_error, h3_internal_error, _}}}} -> ok end, gun:close(ConnPid). @@ -998,9 +1052,9 @@ stream_reply3(Config) -> true = lists:keymember(<<"content-type">>, 1, Headers2), {404, Headers3, Body} = do_get("/resp/stream_reply3/404", Config), true = lists:keymember(<<"content-type">>, 1, Headers3), - {500, _, _} = do_get("/resp/stream_reply3/error", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/resp/stream_reply3/error", Config)), %% The set-cookie header is special. set_resp_cookie must be used. - {500, _, _} = do_get("/resp/stream_reply3/set_cookie", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/resp/stream_reply3/set_cookie", Config)), ok. stream_body_fin0(Config) -> @@ -1084,8 +1138,11 @@ stream_body_content_length_nofin_error(Config) -> end end; http2 -> - %% @todo HTTP2 should have the same content-length checks - ok + %% @todo HTTP/2 should have the same content-length checks. + {skip, "Implement the test for HTTP/2."}; + http3 -> + %% @todo HTTP/3 should have the same content-length checks. + {skip, "Implement the test for HTTP/3."} end. stream_body_concurrent(Config) -> @@ -1187,16 +1244,24 @@ stream_trailers_set_cookie(Config) -> {<<"accept-encoding">>, <<"gzip">>}, {<<"te">>, <<"trailers">>} ]), - {response, nofin, 200, _} = gun:await(ConnPid, Ref, infinity), - case config(protocol, Config) of - http -> + Protocol = config(protocol, Config), + case gun:await(ConnPid, Ref, infinity) of + {response, nofin, 200, _} when Protocol =:= http -> %% Trailers are not sent because of the stream error. {ok, _Body} = gun:await_body(ConnPid, Ref, infinity), {error, timeout} = gun:await_body(ConnPid, Ref, 1000), ok; - http2 -> + {response, nofin, 200, _} when Protocol =:= http2 -> {error, {stream_error, {stream_error, internal_error, _}}} = gun:await_body(ConnPid, Ref, infinity), + ok; + {response, nofin, 200, _} when Protocol =:= http3 -> + {error, {stream_error, {stream_error, h3_internal_error, _}}} + = gun:await_body(ConnPid, Ref, infinity), + ok; + %% The RST_STREAM arrived before the start of the response. + %% See maybe_h3_error comment for details. + {error, {stream_error, {stream_error, h3_internal_error, _}}} when Protocol =:= http3 -> ok end, gun:close(ConnPid). @@ -1224,34 +1289,45 @@ do_trailers(Path, Config) -> push(Config) -> case config(protocol, Config) of http -> do_push_http("/resp/push", Config); - http2 -> do_push_http2(Config) + http2 -> do_push_http2(Config); + http3 -> {skip, "Implement server push for HTTP/3."} end. push_after_reply(Config) -> doc("Trying to push a response after the final response results in a crash."), ConnPid = gun_open(Config), Ref = gun:get(ConnPid, "/resp/push/after_reply", []), - %% @todo How to test this properly? This isn't enough. - {response, fin, 200, _} = gun:await(ConnPid, Ref, infinity), + %% With HTTP/1.1 and HTTP/2 we will not get an error. + %% With HTTP/3 however the stream will occasionally + %% be reset before Gun receives the response. + case gun:await(ConnPid, Ref, infinity) of + {response, fin, 200, _} -> + ok; + {error, {stream_error, {stream_error, h3_internal_error, _}}} -> + ok + end, gun:close(ConnPid). push_method(Config) -> case config(protocol, Config) of http -> do_push_http("/resp/push/method", Config); - http2 -> do_push_http2_method(Config) + http2 -> do_push_http2_method(Config); + http3 -> {skip, "Implement server push for HTTP/3."} end. push_origin(Config) -> case config(protocol, Config) of http -> do_push_http("/resp/push/origin", Config); - http2 -> do_push_http2_origin(Config) + http2 -> do_push_http2_origin(Config); + http3 -> {skip, "Implement server push for HTTP/3."} end. push_qs(Config) -> case config(protocol, Config) of http -> do_push_http("/resp/push/qs", Config); - http2 -> do_push_http2_qs(Config) + http2 -> do_push_http2_qs(Config); + http3 -> {skip, "Implement server push for HTTP/3."} end. do_push_http(Path, Config) -> diff --git a/test/rest_handler_SUITE.erl b/test/rest_handler_SUITE.erl index e026552d..6c1f1c19 100644 --- a/test/rest_handler_SUITE.erl +++ b/test/rest_handler_SUITE.erl @@ -32,7 +32,7 @@ init_per_group(Name, Config) -> cowboy_test:init_common_groups(Name, Config, ?MODULE). end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). %% Dispatch configuration. @@ -85,7 +85,7 @@ accept_callback_missing(Config) -> {<<"accept-encoding">>, <<"gzip">>}, {<<"content-type">>, <<"text/plain">>} ], <<"Missing!">>), - {response, fin, 500, _} = gun:await(ConnPid, Ref), + {response, fin, 500, _} = do_maybe_h3_error(gun:await(ConnPid, Ref)), ok. accept_callback_patch_false(Config) -> @@ -472,7 +472,7 @@ delete_resource_missing(Config) -> Ref = gun:delete(ConnPid, "/delete_resource?missing", [ {<<"accept-encoding">>, <<"gzip">>} ]), - {response, _, 500, _} = gun:await(ConnPid, Ref), + {response, _, 500, _} = do_maybe_h3_error(gun:await(ConnPid, Ref)), ok. create_resource_created(Config) -> @@ -650,10 +650,16 @@ do_generate_etag(Config, Qs, ReqHeaders, Status, Etag) -> {<<"accept-encoding">>, <<"gzip">>} |ReqHeaders ]), - {response, _, Status, RespHeaders} = gun:await(ConnPid, Ref), + {response, _, Status, RespHeaders} = do_maybe_h3_error(gun:await(ConnPid, Ref)), Etag = lists:keyfind(<<"etag">>, 1, RespHeaders), ok. +%% See do_maybe_h3_error2 comment. +do_maybe_h3_error({error, {stream_error, {stream_error, h3_internal_error, _}}}) -> + {response, fin, 500, []}; +do_maybe_h3_error(Result) -> + Result. + if_range_etag_equal(Config) -> doc("When the if-range header matches, a 206 partial content " "response is expected for an otherwise valid range request. (RFC7233 3.2)"), @@ -806,7 +812,7 @@ provide_callback_missing(Config) -> doc("A 500 response must be sent when the ProvideCallback can't be called."), ConnPid = gun_open(Config), Ref = gun:get(ConnPid, "/provide_callback_missing", [{<<"accept-encoding">>, <<"gzip">>}]), - {response, fin, 500, _} = gun:await(ConnPid, Ref), + {response, fin, 500, _} = do_maybe_h3_error(gun:await(ConnPid, Ref)), ok. provide_range_callback(Config) -> @@ -962,7 +968,7 @@ provide_range_callback_missing(Config) -> {<<"accept-encoding">>, <<"gzip">>}, {<<"range">>, <<"bytes=0-">>} ]), - {response, fin, 500, _} = gun:await(ConnPid, Ref), + {response, fin, 500, _} = do_maybe_h3_error(gun:await(ConnPid, Ref)), ok. range_ignore_unknown_unit(Config) -> diff --git a/test/rfc6585_SUITE.erl b/test/rfc6585_SUITE.erl index 090f0286..17cbb076 100644 --- a/test/rfc6585_SUITE.erl +++ b/test/rfc6585_SUITE.erl @@ -30,7 +30,7 @@ init_per_group(Name, Config) -> cowboy_test:init_common_groups(Name, Config, ?MODULE). end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). init_dispatch(_) -> cowboy_router:compile([{"[...]", [ diff --git a/test/rfc7231_SUITE.erl b/test/rfc7231_SUITE.erl index 1d23cb93..4475899a 100644 --- a/test/rfc7231_SUITE.erl +++ b/test/rfc7231_SUITE.erl @@ -35,7 +35,7 @@ init_per_group(Name, Config) -> cowboy_test:init_common_groups(Name, Config, ?MODULE). end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). init_dispatch(_) -> cowboy_router:compile([{"[...]", [ @@ -237,6 +237,8 @@ http10_expect(Config) -> http -> do_http10_expect(Config); http2 -> + expect(Config); + http3 -> expect(Config) end. @@ -303,6 +305,9 @@ expect_discard_body_close(Config) -> do_expect_discard_body_close(Config); http2 -> doc("There's no reason to close the connection when using HTTP/2, " + "even if a stream body is too large. We just cancel the stream."); + http3 -> + doc("There's no reason to close the connection when using HTTP/3, " "even if a stream body is too large. We just cancel the stream.") end. @@ -424,8 +429,10 @@ http10_status_code_100(Config) -> http -> doc("The 100 Continue status code must not " "be sent to HTTP/1.0 endpoints. (RFC7231 6.2)"), - do_http10_status_code_1xx(100, Config); + do_unsupported_status_code_1xx(100, Config); http2 -> + status_code_100(Config); + http3 -> status_code_100(Config) end. @@ -434,12 +441,16 @@ http10_status_code_101(Config) -> http -> doc("The 101 Switching Protocols status code must not " "be sent to HTTP/1.0 endpoints. (RFC7231 6.2)"), - do_http10_status_code_1xx(101, Config); + do_unsupported_status_code_1xx(101, Config); http2 -> + status_code_101(Config); + http3 -> + %% While 101 is not supported by HTTP/3, there is no + %% wording in RFC9114 that forbids sending it. status_code_101(Config) end. -do_http10_status_code_1xx(StatusCode, Config) -> +do_unsupported_status_code_1xx(StatusCode, Config) -> ConnPid = gun_open(Config, #{http_opts => #{version => 'HTTP/1.0'}}), Ref = gun:get(ConnPid, "/resp/inform2/" ++ integer_to_list(StatusCode), [ {<<"accept-encoding">>, <<"gzip">>} @@ -653,7 +664,9 @@ status_code_408_connection_close(Config) -> http -> do_http11_status_code_408_connection_close(Config); http2 -> - doc("HTTP/2 connections are not closed on 408 responses.") + doc("HTTP/2 connections are not closed on 408 responses."); + http3 -> + doc("HTTP/3 connections are not closed on 408 responses.") end. do_http11_status_code_408_connection_close(Config) -> @@ -744,7 +757,9 @@ status_code_426_upgrade_header(Config) -> http -> do_status_code_426_upgrade_header(Config); http2 -> - doc("HTTP/2 does not support the HTTP/1.1 Upgrade mechanism.") + doc("HTTP/2 does not support the HTTP/1.1 Upgrade mechanism."); + http3 -> + doc("HTTP/3 does not support the HTTP/1.1 Upgrade mechanism.") end. do_status_code_426_upgrade_header(Config) -> diff --git a/test/rfc7538_SUITE.erl b/test/rfc7538_SUITE.erl index d9bb1f67..c46d3883 100644 --- a/test/rfc7538_SUITE.erl +++ b/test/rfc7538_SUITE.erl @@ -30,7 +30,7 @@ init_per_group(Name, Config) -> cowboy_test:init_common_groups(Name, Config, ?MODULE). end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). init_dispatch(_) -> cowboy_router:compile([{"[...]", [ diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl index 8e40c934..f0406014 100644 --- a/test/rfc7540_SUITE.erl +++ b/test/rfc7540_SUITE.erl @@ -34,9 +34,9 @@ all() -> [{group, clear}, {group, tls}]. groups() -> - Modules = ct_helper:all(?MODULE), - Clear = [M || M <- Modules, lists:sublist(atom_to_list(M), 4) =/= "alpn"] -- [prior_knowledge_reject_tls], - TLS = [M || M <- Modules, lists:sublist(atom_to_list(M), 4) =:= "alpn"] ++ [prior_knowledge_reject_tls], + Tests = ct_helper:all(?MODULE), + Clear = [T || T <- Tests, lists:sublist(atom_to_list(T), 4) =/= "alpn"] -- [prior_knowledge_reject_tls], + TLS = [T || T <- Tests, lists:sublist(atom_to_list(T), 4) =:= "alpn"] ++ [prior_knowledge_reject_tls], [{clear, [parallel], Clear}, {tls, [parallel], TLS}]. init_per_group(Name = clear, Config) -> @@ -3893,6 +3893,7 @@ accept_host_header_on_missing_pseudo_header_authority(Config) -> %% When both :authority and host headers are received, the current behavior %% is to favor :authority and ignore the host header. The specification does %% not describe the correct behavior to follow in that case. +%% @todo The HTTP/3 spec says both values must be identical and non-empty. reject_many_pseudo_header_authority(Config) -> doc("A request containing more than one authority component must be rejected " diff --git a/test/rfc8297_SUITE.erl b/test/rfc8297_SUITE.erl index bf063515..c6c1c9df 100644 --- a/test/rfc8297_SUITE.erl +++ b/test/rfc8297_SUITE.erl @@ -30,7 +30,7 @@ init_per_group(Name, Config) -> cowboy_test:init_common_groups(Name, Config, ?MODULE). end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). init_dispatch(_) -> cowboy_router:compile([{"[...]", [ diff --git a/test/rfc8441_SUITE.erl b/test/rfc8441_SUITE.erl index 4c463749..3e71667c 100644 --- a/test/rfc8441_SUITE.erl +++ b/test/rfc8441_SUITE.erl @@ -126,6 +126,7 @@ reject_handshake_disabled_by_default(Config0) -> % The Extended CONNECT Method. +%% @todo Refer to RFC9110 7.8 about the case insensitive comparison. accept_uppercase_pseudo_header_protocol(Config) -> doc("The :protocol pseudo header is case insensitive. (draft-01 4)"), %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. @@ -172,6 +173,7 @@ reject_many_pseudo_header_protocol(Config) -> ok. reject_unknown_pseudo_header_protocol(Config) -> + %% @todo This probably shouldn't send 400 but 501 instead based on RFC 9220. doc("An extended CONNECT request with an unknown protocol must be rejected " "with a 400 error. (draft-01 4)"), %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. @@ -192,10 +194,11 @@ reject_unknown_pseudo_header_protocol(Config) -> {ok, << Len1:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000), {ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len1, 1000), {RespHeaders, _} = cow_hpack:decode(RespHeadersBlock), - {_, <<"400">>} = lists:keyfind(<<":status">>, 1, RespHeaders), + {_, <<"501">>} = lists:keyfind(<<":status">>, 1, RespHeaders), ok. reject_invalid_pseudo_header_protocol(Config) -> + %% @todo This probably shouldn't send 400 but 501 instead based on RFC 9220. doc("An extended CONNECT request with an invalid protocol must be rejected " "with a 400 error. (draft-01 4)"), %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. @@ -216,7 +219,7 @@ reject_invalid_pseudo_header_protocol(Config) -> {ok, << Len1:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000), {ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len1, 1000), {RespHeaders, _} = cow_hpack:decode(RespHeadersBlock), - {_, <<"400">>} = lists:keyfind(<<":status">>, 1, RespHeaders), + {_, <<"501">>} = lists:keyfind(<<":status">>, 1, RespHeaders), ok. reject_missing_pseudo_header_scheme(Config) -> @@ -293,7 +296,7 @@ reject_missing_pseudo_header_protocol(Config) -> %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. {ok, Socket, Settings} = do_handshake(Config), #{enable_connect_protocol := true} = Settings, - %% Send an extended CONNECT request without a :scheme pseudo-header. + %% Send an extended CONNECT request without a :protocol pseudo-header. {ReqHeadersBlock, _} = cow_hpack:encode([ {<<":method">>, <<"CONNECT">>}, {<<":scheme">>, <<"http">>}, @@ -317,7 +320,7 @@ reject_connection_header(Config) -> %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. {ok, Socket, Settings} = do_handshake(Config), #{enable_connect_protocol := true} = Settings, - %% Send an extended CONNECT request without a :scheme pseudo-header. + %% Send an extended CONNECT request with a connection header. {ReqHeadersBlock, _} = cow_hpack:encode([ {<<":method">>, <<"CONNECT">>}, {<<":protocol">>, <<"websocket">>}, @@ -339,7 +342,7 @@ reject_upgrade_header(Config) -> %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. {ok, Socket, Settings} = do_handshake(Config), #{enable_connect_protocol := true} = Settings, - %% Send an extended CONNECT request without a :scheme pseudo-header. + %% Send an extended CONNECT request with a upgrade header. {ReqHeadersBlock, _} = cow_hpack:encode([ {<<":method">>, <<"CONNECT">>}, {<<":protocol">>, <<"websocket">>}, diff --git a/test/rfc9114_SUITE.erl b/test/rfc9114_SUITE.erl new file mode 100644 index 00000000..4a36ee14 --- /dev/null +++ b/test/rfc9114_SUITE.erl @@ -0,0 +1,2426 @@ +%% Copyright (c) 2023-2024, Loïc Hoguin +%% +%% 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. + +-module(rfc9114_SUITE). +-compile(export_all). +-compile(nowarn_export_all). + +-import(ct_helper, [config/2]). +-import(ct_helper, [doc/1]). + +-ifdef(COWBOY_QUICER). + +-include_lib("quicer/include/quicer.hrl"). + +all() -> + [{group, h3}]. + +groups() -> + %% @todo Enable parallel tests but for this issues in the + %% QUIC accept loop need to be figured out (can't connect + %% concurrently somehow, no backlog?). + [{h3, [], ct_helper:all(?MODULE)}]. + +init_per_group(Name = h3, Config) -> + cowboy_test:init_http3(Name, #{ + env => #{dispatch => cowboy_router:compile(init_routes(Config))} + }, Config). + +end_per_group(Name, _) -> + cowboy_test:stop_group(Name). + +init_routes(_) -> [ + {"localhost", [ + {"/", hello_h, []}, + {"/echo/:key", echo_h, []} + ]} +]. + +%% Starting HTTP/3 for "https" URIs. + +alpn(Config) -> + doc("Successful ALPN negotiation. (RFC9114 3.1)"), + {ok, Conn} = quicer:connect("localhost", config(port, Config), + #{alpn => ["h3"], verify => none}, 5000), + {ok, <<"h3">>} = quicer:negotiated_protocol(Conn), + %% To make sure the connection is fully established we wait + %% to receive the SETTINGS frame on the control stream. + {ok, _ControlRef, _Settings} = do_wait_settings(Conn), + ok. + +alpn_error(Config) -> + doc("Failed ALPN negotiation using the 'h2' token. (RFC9114 3.1)"), + {error, transport_down, #{status := alpn_neg_failure}} + = quicer:connect("localhost", config(port, Config), + #{alpn => ["h2"], verify => none}, 5000), + ok. + +%% @todo 3.2. Connection Establishment +%% After the QUIC connection is established, a SETTINGS frame MUST be sent by each endpoint as the initial frame of their respective HTTP control stream. + +%% @todo 3.3. Connection Reuse +%% Servers are encouraged to maintain open HTTP/3 connections for as long as +%possible but are permitted to terminate idle connections if necessary. When +%either endpoint chooses to close the HTTP/3 connection, the terminating +%endpoint SHOULD first send a GOAWAY frame (Section 5.2) so that both endpoints +%can reliably determine whether previously sent frames have been processed and +%gracefully complete or terminate any necessary remaining tasks. + +%% Frame format. + +req_stream(Config) -> + doc("Complete lifecycle of a request stream. (RFC9114 4.1)"), + {ok, Conn} = quicer:connect("localhost", config(port, Config), + #{alpn => ["h3"], verify => none}, 5000), + %% To make sure the connection is fully established we wait + %% to receive the SETTINGS frame on the control stream. + {ok, ControlRef, _Settings} = do_wait_settings(Conn), + %% Send a request on a request stream. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ], ?QUIC_SEND_FLAG_FIN), + %% Receive the response. + {ok, Data} = do_receive_data(StreamRef), + {HLenEnc, HLenBits} = do_guess_int_encoding(Data), + << + 1, %% HEADERS frame. + HLenEnc:2, HLen:HLenBits, + EncodedResponse:HLen/bytes, + Rest/bits + >> = Data, + {ok, DecodedResponse, _DecData, _DecSt} + = cow_qpack:decode_field_section(EncodedResponse, 0, cow_qpack:init(decoder)), + #{ + <<":status">> := <<"200">>, + <<"content-length">> := BodyLen + } = maps:from_list(DecodedResponse), + {DLenEnc, DLenBits} = do_guess_int_encoding(Rest), + << + 0, %% DATA frame. + DLenEnc:2, DLen:DLenBits, + Body:DLen/bytes + >> = Rest, + <<"Hello world!">> = Body, + BodyLen = integer_to_binary(byte_size(Body)), + ok = do_wait_peer_send_shutdown(StreamRef), + ok = do_wait_stream_closed(StreamRef). + +%% @todo Same test as above but with content-length unset? + +req_stream_two_requests(Config) -> + doc("Receipt of multiple requests on a single stream must " + "be rejected with an H3_MESSAGE_ERROR stream error. " + "(RFC9114 4.1, RFC9114 4.1.2)"), + {ok, Conn} = quicer:connect("localhost", config(port, Config), + #{alpn => ["h3"], verify => none}, 5000), + %% To make sure the connection is fully established we wait + %% to receive the SETTINGS frame on the control stream. + {ok, ControlRef, _Settings} = do_wait_settings(Conn), + %% Send two requests on a request stream. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest1, _EncData1, EncSt0} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>} + ], 0, cow_qpack:init(encoder)), + {ok, EncodedRequest2, _EncData2, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>} + ], 0, EncSt0), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest1)), + EncodedRequest1, + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest2)), + EncodedRequest2 + ]), + %% The stream should have been aborted. + #{reason := h3_message_error} = do_wait_stream_aborted(StreamRef), + ok. + +headers_then_trailers(Config) -> + doc("Receipt of HEADERS followed by trailer HEADERS must be accepted. (RFC9114 4.1)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>} + ], 0, cow_qpack:init(encoder)), + {ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([ + {<<"content-type">>, <<"text/plain">>} + ], 0, EncSt0), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<1>>, %% HEADERS frame for trailers. + cow_http3:encode_int(iolist_size(EncodedTrailers)), + EncodedTrailers + ], ?QUIC_SEND_FLAG_FIN), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello world!">> + } = do_receive_response(StreamRef), + ok. + +headers_then_data_then_trailers(Config) -> + doc("Receipt of HEADERS followed by DATA followed by trailer HEADERS " + "must be accepted. (RFC9114 4.1)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"13">>} + ], 0, cow_qpack:init(encoder)), + {ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([ + {<<"content-type">>, <<"text/plain">>} + ], 0, EncSt0), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<0>>, %% DATA frame. + cow_http3:encode_int(13), + <<"Hello server!">>, + <<1>>, %% HEADERS frame for trailers. + cow_http3:encode_int(iolist_size(EncodedTrailers)), + EncodedTrailers + ], ?QUIC_SEND_FLAG_FIN), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello world!">> + } = do_receive_response(StreamRef), + ok. + +data_then_headers(Config) -> + doc("Receipt of DATA before HEADERS must be rejected " + "with an H3_FRAME_UNEXPECTED connection error. (RFC9114 4.1)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData1, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"13">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<0>>, %% DATA frame. + cow_http3:encode_int(13), + <<"Hello server!">>, + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders + ], ?QUIC_SEND_FLAG_FIN), + %% The connection should have been closed. + #{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn), + ok. + +headers_then_trailers_then_data(Config) -> + doc("Receipt of DATA after trailer HEADERS must be rejected " + "with an H3_FRAME_UNEXPECTED connection error. (RFC9114 4.1)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>} + ], 0, cow_qpack:init(encoder)), + {ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([ + {<<"content-type">>, <<"text/plain">>} + ], 0, EncSt0), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<1>>, %% HEADERS frame for trailers. + cow_http3:encode_int(iolist_size(EncodedTrailers)), + EncodedTrailers, + <<0>>, %% DATA frame. + cow_http3:encode_int(13), + <<"Hello server!">> + ], ?QUIC_SEND_FLAG_FIN), + %% The connection should have been closed. + #{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn), + ok. + +headers_then_data_then_trailers_then_data(Config) -> + doc("Receipt of DATA after trailer HEADERS must be rejected " + "with an H3_FRAME_UNEXPECTED connection error. (RFC9114 4.1)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"13">>} + ], 0, cow_qpack:init(encoder)), + {ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([ + {<<"content-type">>, <<"text/plain">>} + ], 0, EncSt0), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<0>>, %% DATA frame. + cow_http3:encode_int(13), + <<"Hello server!">>, + <<1>>, %% HEADERS frame for trailers. + cow_http3:encode_int(iolist_size(EncodedTrailers)), + EncodedTrailers, + <<0>>, %% DATA frame. + cow_http3:encode_int(13), + <<"Hello server!">> + ], ?QUIC_SEND_FLAG_FIN), + %% The connection should have been closed. + #{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn), + ok. + +headers_then_data_then_trailers_then_trailers(Config) -> + doc("Receipt of DATA after trailer HEADERS must be rejected " + "with an H3_FRAME_UNEXPECTED connection error. (RFC9114 4.1)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"13">>} + ], 0, cow_qpack:init(encoder)), + {ok, EncodedTrailers1, _EncData2, EncSt1} = cow_qpack:encode_field_section([ + {<<"content-type">>, <<"text/plain">>} + ], 0, EncSt0), + {ok, EncodedTrailers2, _EncData3, _EncSt} = cow_qpack:encode_field_section([ + {<<"content-type">>, <<"text/plain">>} + ], 0, EncSt1), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<0>>, %% DATA frame. + cow_http3:encode_int(13), + <<"Hello server!">>, + <<1>>, %% HEADERS frame for trailers. + cow_http3:encode_int(iolist_size(EncodedTrailers1)), + EncodedTrailers1, + <<1>>, %% HEADERS frame for trailers. + cow_http3:encode_int(iolist_size(EncodedTrailers2)), + EncodedTrailers2 + ], ?QUIC_SEND_FLAG_FIN), + %% The connection should have been closed. + #{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn), + ok. + +unknown_then_headers(Config) -> + doc("Receipt of unknown frame followed by HEADERS " + "must be accepted. (RFC9114 4.1, RFC9114 9)"), + unknown_then_headers(Config, do_unknown_frame_type(), + rand:bytes(rand:uniform(4096))). + +unknown_then_headers(Config, Type, Bytes) -> + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + cow_http3:encode_int(Type), %% Unknown frame. + cow_http3:encode_int(iolist_size(Bytes)), + Bytes, + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders + ], ?QUIC_SEND_FLAG_FIN), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello world!">> + } = do_receive_response(StreamRef), + ok. + +headers_then_unknown(Config) -> + doc("Receipt of HEADERS followed by unknown frame " + "must be accepted. (RFC9114 4.1, RFC9114 9)"), + headers_then_unknown(Config, do_unknown_frame_type(), + rand:bytes(rand:uniform(4096))). + +headers_then_unknown(Config, Type, Bytes) -> + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + cow_http3:encode_int(Type), %% Unknown frame. + cow_http3:encode_int(iolist_size(Bytes)), + Bytes + ], ?QUIC_SEND_FLAG_FIN), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello world!">> + } = do_receive_response(StreamRef), + ok. + +headers_then_data_then_unknown(Config) -> + doc("Receipt of HEADERS followed by DATA followed by unknown frame " + "must be accepted. (RFC9114 4.1, RFC9114 9)"), + headers_then_data_then_unknown(Config, do_unknown_frame_type(), + rand:bytes(rand:uniform(4096))). + +headers_then_data_then_unknown(Config, Type, Bytes) -> + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"13">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<0>>, %% DATA frame. + cow_http3:encode_int(13), + <<"Hello server!">>, + cow_http3:encode_int(Type), %% Unknown frame. + cow_http3:encode_int(iolist_size(Bytes)), + Bytes + ], ?QUIC_SEND_FLAG_FIN), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello world!">> + } = do_receive_response(StreamRef), + ok. + +headers_then_trailers_then_unknown(Config) -> + doc("Receipt of HEADERS followed by trailer HEADERS followed by unknown frame " + "must be accepted. (RFC9114 4.1, RFC9114 9)"), + headers_then_data_then_unknown(Config, do_unknown_frame_type(), + rand:bytes(rand:uniform(4096))). + +headers_then_trailers_then_unknown(Config, Type, Bytes) -> + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData, EncSt0} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>} + ], 0, cow_qpack:init(encoder)), + {ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([ + {<<"content-type">>, <<"text/plain">>} + ], 0, EncSt0), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<1>>, %% HEADERS frame for trailers. + cow_http3:encode_int(iolist_size(EncodedTrailers)), + EncodedTrailers, + cow_http3:encode_int(Type), %% Unknown frame. + cow_http3:encode_int(iolist_size(Bytes)), + Bytes + ], ?QUIC_SEND_FLAG_FIN), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello world!">> + } = do_receive_response(StreamRef), + ok. + +headers_then_data_then_unknown_then_trailers(Config) -> + doc("Receipt of HEADERS followed by DATA followed by " + "unknown frame followed by trailer HEADERS " + "must be accepted. (RFC9114 4.1, RFC9114 9)"), + headers_then_data_then_unknown_then_trailers(Config, + do_unknown_frame_type(), rand:bytes(rand:uniform(4096))). + +headers_then_data_then_unknown_then_trailers(Config, Type, Bytes) -> + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData, EncSt0} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"13">>} + ], 0, cow_qpack:init(encoder)), + {ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([ + {<<"content-type">>, <<"text/plain">>} + ], 0, EncSt0), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<0>>, %% DATA frame. + cow_http3:encode_int(13), + <<"Hello server!">>, + cow_http3:encode_int(Type), %% Unknown frame. + cow_http3:encode_int(iolist_size(Bytes)), + Bytes, + <<1>>, %% HEADERS frame for trailers. + cow_http3:encode_int(iolist_size(EncodedTrailers)), + EncodedTrailers + ], ?QUIC_SEND_FLAG_FIN), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello world!">> + } = do_receive_response(StreamRef), + ok. + +headers_then_data_then_unknown_then_data(Config) -> + doc("Receipt of HEADERS followed by DATA followed by " + "unknown frame followed by DATA " + "must be accepted. (RFC9114 4.1, RFC9114 9)"), + headers_then_data_then_unknown_then_data(Config, + do_unknown_frame_type(), rand:bytes(rand:uniform(4096))). + +headers_then_data_then_unknown_then_data(Config, Type, Bytes) -> + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"13">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<0>>, %% DATA frame. + cow_http3:encode_int(6), + <<"Hello ">>, + cow_http3:encode_int(Type), %% Unknown frame. + cow_http3:encode_int(iolist_size(Bytes)), + Bytes, + <<0>>, %% DATA frame. + cow_http3:encode_int(7), + <<"server!">> + ], ?QUIC_SEND_FLAG_FIN), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello world!">> + } = do_receive_response(StreamRef), + ok. + +headers_then_data_then_trailers_then_unknown(Config) -> + doc("Receipt of HEADERS followed by DATA followed by " + "trailer HEADERS followed by unknown frame " + "must be accepted. (RFC9114 4.1, RFC9114 9)"), + headers_then_data_then_trailers_then_unknown(Config, + do_unknown_frame_type(), rand:bytes(rand:uniform(4096))). + +headers_then_data_then_trailers_then_unknown(Config, Type, Bytes) -> + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData, EncSt0} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"13">>} + ], 0, cow_qpack:init(encoder)), + {ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([ + {<<"content-type">>, <<"text/plain">>} + ], 0, EncSt0), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<0>>, %% DATA frame. + cow_http3:encode_int(13), + <<"Hello server!">>, + <<1>>, %% HEADERS frame for trailers. + cow_http3:encode_int(iolist_size(EncodedTrailers)), + EncodedTrailers, + cow_http3:encode_int(Type), %% Unknown frame. + cow_http3:encode_int(iolist_size(Bytes)), + Bytes + ], ?QUIC_SEND_FLAG_FIN), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello world!">> + } = do_receive_response(StreamRef), + ok. + +do_unknown_frame_type() -> + Type = rand:uniform(4611686018427387904) - 1, + %% Retry if we get a value that's specified. + case lists:member(Type, [ + 16#0, 16#1, 16#3, 16#4, 16#5, 16#7, 16#d, %% HTTP/3 core frame types. + 16#2, 16#6, 16#8, 16#9 %% HTTP/3 reserved frame types that must be rejected. + ]) of + true -> do_unknown_frame_type(); + false -> Type + end. + +reserved_then_headers(Config) -> + doc("Receipt of reserved frame followed by HEADERS " + "must be accepted when the reserved frame type is " + "of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)"), + unknown_then_headers(Config, do_reserved_type(), + rand:bytes(rand:uniform(4096))). + +headers_then_reserved(Config) -> + doc("Receipt of HEADERS followed by reserved frame " + "must be accepted when the reserved frame type is " + "of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)"), + headers_then_unknown(Config, do_reserved_type(), + rand:bytes(rand:uniform(4096))). + +headers_then_data_then_reserved(Config) -> + doc("Receipt of HEADERS followed by DATA followed by reserved frame " + "must be accepted when the reserved frame type is " + "of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)"), + headers_then_data_then_unknown(Config, do_reserved_type(), + rand:bytes(rand:uniform(4096))). + +headers_then_trailers_then_reserved(Config) -> + doc("Receipt of HEADERS followed by trailer HEADERS followed by reserved frame " + "must be accepted when the reserved frame type is " + "of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)"), + headers_then_trailers_then_unknown(Config, do_reserved_type(), + rand:bytes(rand:uniform(4096))). + +headers_then_data_then_reserved_then_trailers(Config) -> + doc("Receipt of HEADERS followed by DATA followed by " + "reserved frame followed by trailer HEADERS " + "must be accepted when the reserved frame type is " + "of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)"), + headers_then_data_then_unknown_then_trailers(Config, + do_reserved_type(), rand:bytes(rand:uniform(4096))). + +headers_then_data_then_reserved_then_data(Config) -> + doc("Receipt of HEADERS followed by DATA followed by " + "reserved frame followed by DATA " + "must be accepted when the reserved frame type is " + "of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)"), + headers_then_data_then_unknown_then_data(Config, + do_reserved_type(), rand:bytes(rand:uniform(4096))). + +headers_then_data_then_trailers_then_reserved(Config) -> + doc("Receipt of HEADERS followed by DATA followed by " + "trailer HEADERS followed by reserved frame " + "must be accepted when the reserved frame type is " + "of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)"), + headers_then_data_then_trailers_then_unknown(Config, + do_reserved_type(), rand:bytes(rand:uniform(4096))). + +reject_transfer_encoding_header_with_body(Config) -> + doc("Requests containing a transfer-encoding header must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.1, RFC9114 4.1.2, RFC9114 4.2)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData1, _EncSt0} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"transfer-encoding">>, <<"chunked">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<0>>, %% DATA frame. + cow_http3:encode_int(24), + <<"13\r\nHello server!\r\n0\r\n\r\n">> + ]), + %% The stream should have been aborted. + #{reason := h3_message_error} = do_wait_stream_aborted(StreamRef), + ok. + +%% 4. Expressing HTTP Semantics in HTTP/3 +%% 4.1. HTTP Message Framing + +%% An HTTP request/response exchange fully consumes a client-initiated +%bidirectional QUIC stream. After sending a request, a client MUST close the +%stream for sending. Unless using the CONNECT method (see Section 4.4), clients +%MUST NOT make stream closure dependent on receiving a response to their +%request. After sending a final response, the server MUST close the stream for +%sending. At this point, the QUIC stream is fully closed. +%% @todo What to do with clients that DON'T close the stream +%% for sending after the request is sent? + +%% If a client-initiated stream terminates without enough of the HTTP message +%to provide a complete response, the server SHOULD abort its response stream +%with the error code H3_REQUEST_INCOMPLETE. +%% @todo difficult!! + +%% When the server does not need to receive the remainder of the request, it +%MAY abort reading the request stream, send a complete response, and cleanly +%close the sending part of the stream. The error code H3_NO_ERROR SHOULD be +%used when requesting that the client stop sending on the request stream. +%% @todo read_body related; h2 has this behavior but there is no corresponding test + +%% 4.1.1. Request Cancellation and Rejection + +%% When possible, it is RECOMMENDED that servers send an HTTP response with an +%appropriate status code rather than cancelling a request it has already begun +%processing. + +%% Implementations SHOULD cancel requests by abruptly terminating any +%directions of a stream that are still open. To do so, an implementation resets +%the sending parts of streams and aborts reading on the receiving parts of +%streams; see Section 2.4 of [QUIC-TRANSPORT]. + +%% When the server cancels a request without performing any application +%processing, the request is considered "rejected". The server SHOULD abort its +%response stream with the error code H3_REQUEST_REJECTED. In this context, +%"processed" means that some data from the stream was passed to some higher +%layer of software that might have taken some action as a result. The client +%can treat requests rejected by the server as though they had never been sent +%at all, thereby allowing them to be retried later. + +%% Servers MUST NOT use the H3_REQUEST_REJECTED error code for requests that +%were partially or fully processed. When a server abandons a response after +%partial processing, it SHOULD abort its response stream with the error code +%H3_REQUEST_CANCELLED. +%% @todo + +%% Client SHOULD use the error code H3_REQUEST_CANCELLED to cancel requests. +%Upon receipt of this error code, a server MAY abruptly terminate the response +%using the error code H3_REQUEST_REJECTED if no processing was performed. +%Clients MUST NOT use the H3_REQUEST_REJECTED error code, except when a server +%has requested closure of the request stream with this error code. +%% @todo + +%4.1.2. Malformed Requests and Responses +%A malformed request or response is one that is an otherwise valid sequence of +%frames but is invalid due to: +% +%the presence of prohibited fields or pseudo-header fields, +%% @todo reject_response_pseudo_headers +%% @todo reject_unknown_pseudo_headers +%% @todo reject_pseudo_headers_in_trailers + +%the absence of mandatory pseudo-header fields, +%invalid values for pseudo-header fields, +%pseudo-header fields after fields, +%% @todo reject_pseudo_headers_after_regular_headers + +%an invalid sequence of HTTP messages, +%the inclusion of invalid characters in field names or values. +% +%A request or response that is defined as having content when it contains a +%Content-Length header field (Section 8.6 of [HTTP]) is malformed if the value +%of the Content-Length header field does not equal the sum of the DATA frame +%lengths received. A response that is defined as never having content, even +%when a Content-Length is present, can have a non-zero Content-Length header +%field even though no content is included in DATA frames. +% +%For malformed requests, a server MAY send an HTTP response indicating the +%error prior to closing or resetting the stream. +%% @todo All the malformed tests + +headers_reject_uppercase_header_name(Config) -> + doc("Requests containing uppercase header names must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)"), + do_reject_malformed_header(Config, + {<<"I-AM-GIGANTIC">>, <<"How's the weather up there?">>} + ). + +%% 4.2. HTTP Fields +%% An endpoint MUST NOT generate an HTTP/3 field section containing +%connection-specific fields; any message containing connection-specific fields +%MUST be treated as malformed. + +reject_connection_header(Config) -> + doc("Requests containing a connection header must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)"), + do_reject_malformed_header(Config, + {<<"connection">>, <<"close">>} + ). + +reject_keep_alive_header(Config) -> + doc("Requests containing a keep-alive header must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)"), + do_reject_malformed_header(Config, + {<<"keep-alive">>, <<"timeout=5, max=1000">>} + ). + +reject_proxy_authenticate_header(Config) -> + doc("Requests containing a proxy-authenticate header must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)"), + do_reject_malformed_header(Config, + {<<"proxy-authenticate">>, <<"Basic">>} + ). + +reject_proxy_authorization_header(Config) -> + doc("Requests containing a proxy-authorization header must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)"), + do_reject_malformed_header(Config, + {<<"proxy-authorization">>, <<"Basic YWxhZGRpbjpvcGVuc2VzYW1l">>} + ). + +reject_transfer_encoding_header(Config) -> + doc("Requests containing a transfer-encoding header must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)"), + do_reject_malformed_header(Config, + {<<"transfer-encoding">>, <<"chunked">>} + ). + +reject_upgrade_header(Config) -> + doc("Requests containing an upgrade header must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.5, RFC9114 4.1.2)"), + do_reject_malformed_header(Config, + {<<"upgrade">>, <<"websocket">>} + ). + +accept_te_header_value_trailers(Config) -> + doc("Requests containing a TE header with a value of \"trailers\" " + "must be accepted. (RFC9114 4.2)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>}, + {<<"te">>, <<"trailers">>} + ], 0, cow_qpack:init(encoder)), + {ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([ + {<<"content-type">>, <<"text/plain">>} + ], 0, EncSt0), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<1>>, %% HEADERS frame for trailers. + cow_http3:encode_int(iolist_size(EncodedTrailers)), + EncodedTrailers + ], ?QUIC_SEND_FLAG_FIN), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello world!">> + } = do_receive_response(StreamRef), + ok. + +reject_te_header_other_values(Config) -> + doc("Requests containing a TE header with a value other than \"trailers\" must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)"), + do_reject_malformed_header(Config, + {<<"te">>, <<"trailers, deflate;q=0.5">>} + ). + +%% @todo response_dont_send_header_in_connection +%% @todo response_dont_send_connection_header +%% @todo response_dont_send_keep_alive_header +%% @todo response_dont_send_proxy_connection_header +%% @todo response_dont_send_transfer_encoding_header +%% @todo response_dont_send_upgrade_header + +%% 4.2.1. Field Compression +%% To allow for better compression efficiency, the Cookie header field +%([COOKIES]) MAY be split into separate field lines, each with one or more +%cookie-pairs, before compression. If a decompressed field section contains +%multiple cookie field lines, these MUST be concatenated into a single byte +%string using the two-byte delimiter of "; " (ASCII 0x3b, 0x20) before being +%passed into a context other than HTTP/2 or HTTP/3, such as an HTTP/1.1 +%connection, or a generic HTTP server application. + +%% 4.2.2. Header Size Constraints +%% An HTTP/3 implementation MAY impose a limit on the maximum size of the +%message header it will accept on an individual HTTP message. A server that +%receives a larger header section than it is willing to handle can send an HTTP +%431 (Request Header Fields Too Large) status code ([RFC6585]). The size of a +%field list is calculated based on the uncompressed size of fields, including +%the length of the name and value in bytes plus an overhead of 32 bytes for +%each field. +%% If an implementation wishes to advise its peer of this limit, it can be +%conveyed as a number of bytes in the SETTINGS_MAX_FIELD_SECTION_SIZE +%parameter. + +reject_unknown_pseudo_headers(Config) -> + doc("Requests containing unknown pseudo-headers must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3, RFC9114 4.1.2)"), + do_reject_malformed_header(Config, + {<<":upgrade">>, <<"websocket">>} + ). + +reject_response_pseudo_headers(Config) -> + doc("Requests containing response pseudo-headers must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3, RFC9114 4.1.2)"), + do_reject_malformed_header(Config, + {<<":status">>, <<"200">>} + ). + +reject_pseudo_headers_in_trailers(Config) -> + doc("Requests containing pseudo-headers in trailers must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3, RFC9114 4.1.2)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"trailer">>, <<"x-checksum">>} + ], 0, cow_qpack:init(encoder)), + {ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([ + {<<"x-checksum">>, <<"md5:4cc909a007407f3706399b6496babec3">>}, + {<<":path">>, <<"/">>} + ], 0, EncSt0), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<0>>, %% DATA frame. + cow_http3:encode_int(10000), + <<0:10000/unit:8>>, + <<1>>, %% HEADERS frame for trailers. + cow_http3:encode_int(iolist_size(EncodedTrailers)), + EncodedTrailers + ]), + %% The stream should have been aborted. + #{reason := h3_message_error} = do_wait_stream_aborted(StreamRef), + ok. + +reject_pseudo_headers_after_regular_headers(Config) -> + doc("Requests containing pseudo-headers after regular headers must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3, RFC9114 4.1.2)"), + do_reject_malformed_headers(Config, [ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<"content-length">>, <<"0">>}, + {<<":path">>, <<"/">>} + ]). + +reject_userinfo(Config) -> + doc("An authority containing a userinfo component must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)"), + do_reject_malformed_headers(Config, [ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"user@localhost">>}, + {<<":path">>, <<"/">>} + ]). + +%% To ensure that the HTTP/1.1 request line can be reproduced accurately, this +%% pseudo-header field (:authority) MUST be omitted when translating from an +%% HTTP/1.1 request that has a request target in a method-specific form; +%% see Section 7.1 of [HTTP]. + +reject_empty_path(Config) -> + doc("A request containing an empty path component must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)"), + do_reject_malformed_headers(Config, [ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<>>} + ]). + +reject_missing_pseudo_header_method(Config) -> + doc("A request without a method component must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)"), + do_reject_malformed_headers(Config, [ + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>} + ]). + +reject_many_pseudo_header_method(Config) -> + doc("A request containing more than one method component must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)"), + do_reject_malformed_headers(Config, [ + {<<":method">>, <<"GET">>}, + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>} + ]). + +reject_missing_pseudo_header_scheme(Config) -> + doc("A request without a scheme component must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)"), + do_reject_malformed_headers(Config, [ + {<<":method">>, <<"GET">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>} + ]). + +reject_many_pseudo_header_scheme(Config) -> + doc("A request containing more than one scheme component must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)"), + do_reject_malformed_headers(Config, [ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>} + ]). + +reject_missing_pseudo_header_authority(Config) -> + doc("A request without an authority or host component must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)"), + do_reject_malformed_headers(Config, [ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":path">>, <<"/">>} + ]). + +accept_host_header_on_missing_pseudo_header_authority(Config) -> + doc("A request without an authority but with a host header must be accepted. " + "(RFC9114 4.3.1)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData1, _EncSt0} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":path">>, <<"/">>}, + {<<"host">>, <<"localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders + ], ?QUIC_SEND_FLAG_FIN), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello world!">> + } = do_receive_response(StreamRef), + ok. + +%% @todo +%% If the :scheme pseudo-header field identifies a scheme that has a mandatory +%% authority component (including "http" and "https"), the request MUST contain +%% either an :authority pseudo-header field or a Host header field. +%% - If both fields are present, they MUST NOT be empty. +%% - If both fields are present, they MUST contain the same value. + +reject_many_pseudo_header_authority(Config) -> + doc("A request containing more than one authority component must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)"), + do_reject_malformed_headers(Config, [ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>} + ]). + +reject_missing_pseudo_header_path(Config) -> + doc("A request without a path component must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)"), + do_reject_malformed_headers(Config, [ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>} + ]). + +reject_many_pseudo_header_path(Config) -> + doc("A request containing more than one path component must be rejected " + "with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)"), + do_reject_malformed_headers(Config, [ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<":path">>, <<"/">>} + ]). + +do_reject_malformed_header(Config, Header) -> + do_reject_malformed_headers(Config, [ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + Header + ]). + +do_reject_malformed_headers(Config, Headers) -> + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData1, _EncSt0} + = cow_qpack:encode_field_section(Headers, 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders + ]), + %% The stream should have been aborted. + #{reason := h3_message_error} = do_wait_stream_aborted(StreamRef), + ok. + +%% For responses, a single ":status" pseudo-header field is defined that +%% carries the HTTP status code; see Section 15 of [HTTP]. This pseudo-header +%% field MUST be included in all responses; otherwise, the response is malformed +%% (see Section 4.1.2). + +%% @todo Implement CONNECT. (RFC9114 4.4. The CONNECT Method) + +%% @todo Maybe block the sending of 101 responses? (RFC9114 4.5. HTTP Upgrade) - also HTTP/2. + +%% @todo Implement server push (RFC9114 4.6. Server Push) + +%% @todo - need a way to list connections +%% 5.2. Connection Shutdown +%% Endpoints initiate the graceful shutdown of an HTTP/3 connection by sending +%% a GOAWAY frame. The GOAWAY frame contains an identifier that indicates to the +%% receiver the range of requests or pushes that were or might be processed in +%% this connection. The server sends a client-initiated bidirectional stream ID; +%% the client sends a push ID. Requests or pushes with the indicated identifier +%% or greater are rejected (Section 4.1.1) by the sender of the GOAWAY. This +%% identifier MAY be zero if no requests or pushes were processed. + +%% @todo +%% Upon sending a GOAWAY frame, the endpoint SHOULD explicitly cancel (see +%% Sections 4.1.1 and 7.2.3) any requests or pushes that have identifiers greater +%% than or equal to the one indicated, in order to clean up transport state for +%% the affected streams. The endpoint SHOULD continue to do so as more requests +%% or pushes arrive. + +%% @todo +%% Endpoints MUST NOT initiate new requests or promise new pushes on the +%% connection after receipt of a GOAWAY frame from the peer. + +%% @todo +%% Requests on stream IDs less than the stream ID in a GOAWAY frame from the +%% server might have been processed; their status cannot be known until a +%% response is received, the stream is reset individually, another GOAWAY is +%% received with a lower stream ID than that of the request in question, or the +%% connection terminates. + +%% @todo +%% Servers MAY reject individual requests on streams below the indicated ID if +%% these requests were not processed. + +%% @todo +%% If a server receives a GOAWAY frame after having promised pushes with a push +%% ID greater than or equal to the identifier contained in the GOAWAY frame, +%% those pushes will not be accepted. + +%% @todo +%% Servers SHOULD send a GOAWAY frame when the closing of a connection is known +%% in advance, even if the advance notice is small, so that the remote peer can +%% know whether or not a request has been partially processed. + +%% @todo +%% An endpoint MAY send multiple GOAWAY frames indicating different +%% identifiers, but the identifier in each frame MUST NOT be greater than the +%% identifier in any previous frame, since clients might already have retried +%% unprocessed requests on another HTTP connection. Receiving a GOAWAY containing +%% a larger identifier than previously received MUST be treated as a connection +%% error of type H3_ID_ERROR. + +%% @todo +%% An endpoint that is attempting to gracefully shut down a connection can send +%% a GOAWAY frame with a value set to the maximum possible value (2^62-4 for +%% servers, 2^62-1 for clients). + +%% @todo +%% Even when a GOAWAY indicates that a given request or push will not be +%% processed or accepted upon receipt, the underlying transport resources still +%% exist. The endpoint that initiated these requests can cancel them to clean up +%% transport state. + +%% @todo +%% Once all accepted requests and pushes have been processed, the endpoint can +%% permit the connection to become idle, or it MAY initiate an immediate closure +%% of the connection. An endpoint that completes a graceful shutdown SHOULD use +%% the H3_NO_ERROR error code when closing the connection. + +%% @todo +%% If a client has consumed all available bidirectional stream IDs with +%% requests, the server need not send a GOAWAY frame, since the client is unable +%% to make further requests. @todo OK that one's some weird stuff lol + +%% @todo +%% 5.3. Immediate Application Closure +%% Before closing the connection, a GOAWAY frame MAY be sent to allow the +%% client to retry some requests. Including the GOAWAY frame in the same packet +%% as the QUIC CONNECTION_CLOSE frame improves the chances of the frame being +%% received by clients. + +bidi_allow_at_least_a_hundred(Config) -> + doc("Endpoints must allow the peer to create at least " + "one hundred bidirectional streams. (RFC9114 6.1"), + #{conn := Conn} = do_connect(Config), + receive + {quic, streams_available, Conn, #{bidi_streams := NumStreams}} -> + true = NumStreams >= 100, + ok + after 5000 -> + error(timeout) + end. + +unidi_allow_at_least_three(Config) -> + doc("Endpoints must allow the peer to create at least " + "three unidirectional streams. (RFC9114 6.2"), + #{conn := Conn} = do_connect(Config), + %% Confirm that the server advertised support for at least 3 unidi streams. + receive + {quic, streams_available, Conn, #{unidi_streams := NumStreams}} -> + true = NumStreams >= 3, + ok + after 5000 -> + error(timeout) + end, + %% Confirm that we can create the unidi streams. + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, _} = quicer:send(ControlRef, [<<0>>, SettingsBin]), + {ok, EncoderRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, _} = quicer:send(EncoderRef, <<2>>), + {ok, DecoderRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, _} = quicer:send(DecoderRef, <<3>>), + %% Streams shouldn't get closed. + fun Loop() -> + receive + %% We don't care about these messages. + {quic, dgram_state_changed, Conn, _} -> + Loop(); + {quic, peer_needs_streams, Conn, _} -> + Loop(); + %% Any other we do care. + Msg -> + error(Msg) + after 1000 -> + ok + end + end(). + +unidi_create_critical_first(Config) -> + doc("Endpoints should create the HTTP control stream as well as " + "the QPACK encoder and decoder streams first. (RFC9114 6.2"), + %% The control stream is accepted in the do_connect/1 function. + #{conn := Conn} = do_connect(Config, #{peer_unidi_stream_count => 3}), + Unidi1 = do_accept_qpack_stream(Conn), + Unidi2 = do_accept_qpack_stream(Conn), + case {Unidi1, Unidi2} of + {{encoder, _}, {decoder, _}} -> + ok; + {{decoder, _}, {encoder, _}} -> + ok + end. + +do_accept_qpack_stream(Conn) -> + receive + {quic, new_stream, StreamRef, #{flags := Flags}} -> + ok = quicer:setopt(StreamRef, active, true), + true = quicer:is_unidirectional(Flags), + receive {quic, <>, StreamRef, _} -> + {case Type of + 2 -> encoder; + 3 -> decoder + end, StreamRef} + after 5000 -> + error(timeout) + end + after 5000 -> + error(timeout) + end. + +%% @todo We should also confirm that there's at least 1,024 bytes of +%% flow-control credit for each unidi stream the server creates. (How?) +%% It can be set via stream_recv_window_default in quicer. + +unidi_abort_unknown_type(Config) -> + doc("Receipt of an unknown stream type must be aborted " + "with an H3_STREAM_CREATION_ERROR stream error. (RFC9114 6.2, RFC9114 9)"), + #{conn := Conn} = do_connect(Config), + %% Create an unknown unidirectional stream. + {ok, StreamRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, _} = quicer:send(StreamRef, [ + cow_http3:encode_int(1 + do_reserved_type()), + rand:bytes(rand:uniform(4096)) + ]), + %% The stream should have been aborted. + #{reason := h3_stream_creation_error} = do_wait_stream_aborted(StreamRef), + ok. + +unidi_abort_reserved_type(Config) -> + doc("Receipt of a reserved stream type must be aborted " + "with an H3_STREAM_CREATION_ERROR stream error. " + "(RFC9114 6.2, RFC9114 6.2.3, RFC9114 9)"), + #{conn := Conn} = do_connect(Config), + %% Create a reserved unidirectional stream. + {ok, StreamRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, _} = quicer:send(StreamRef, [ + cow_http3:encode_int(do_reserved_type()), + rand:bytes(rand:uniform(4096)) + ]), + %% The stream should have been aborted. + #{reason := h3_stream_creation_error} = do_wait_stream_aborted(StreamRef), + ok. + +%% As certain stream types can affect connection state, a recipient SHOULD NOT +%% discard data from incoming unidirectional streams prior to reading the stream type. + +%% Implementations MAY send stream types before knowing whether the peer +%supports them. However, stream types that could modify the state or semantics +%of existing protocol components, including QPACK or other extensions, MUST NOT +%be sent until the peer is known to support them. +%% @todo It may make sense for Cowboy to delay the creation of unidi streams +%% a little in order to save resources. We could create them when the +%% client does as well, or something similar. + +%% A receiver MUST tolerate unidirectional streams being closed or reset prior +%% to the reception of the unidirectional stream header. + +%% Each side MUST initiate a single control stream at the beginning of the +%% connection and send its SETTINGS frame as the first frame on this stream. +%% @todo What to do when the client never opens a control stream? +%% @todo Similarly, a stream could be opened but with no data being sent. +%% @todo Similarly, a control stream could be opened with no SETTINGS frame sent. + +control_reject_first_frame_data(Config) -> + doc("The first frame on a control stream must be a SETTINGS frame " + "or the connection must be closed with an H3_MISSING_SETTINGS " + "connection error. (RFC9114 6.2.1, RFC9114 9)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + <<0>>, %% DATA frame. + cow_http3:encode_int(12), + <<"Hello world!">> + ]), + %% The connection should have been closed. + #{reason := h3_missing_settings} = do_wait_connection_closed(Conn), + ok. + +control_reject_first_frame_headers(Config) -> + doc("The first frame on a control stream must be a SETTINGS frame " + "or the connection must be closed with an H3_MISSING_SETTINGS " + "connection error. (RFC9114 6.2.1, RFC9114 9)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders + ]), + %% The connection should have been closed. + #{reason := h3_missing_settings} = do_wait_connection_closed(Conn), + ok. + +control_reject_first_frame_cancel_push(Config) -> + doc("The first frame on a control stream must be a SETTINGS frame " + "or the connection must be closed with an H3_MISSING_SETTINGS " + "connection error. (RFC9114 6.2.1, RFC9114 9)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + <<3>>, %% CANCEL_PUSH frame. + cow_http3:encode_int(1), + cow_http3:encode_int(0) + ]), + %% The connection should have been closed. + #{reason := h3_missing_settings} = do_wait_connection_closed(Conn), + ok. + +control_accept_first_frame_settings(Config) -> + doc("The first frame on a control stream " + "must be a SETTINGS frame. (RFC9114 6.2.1, RFC9114 9)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsBin + ]), + %% The connection should remain up. + receive + {quic, shutdown, Conn, {unknown_quic_status, Code}} -> + Reason = cow_http3:code_to_error(Code), + error(Reason) + after 1000 -> + ok + end. + +control_reject_first_frame_push_promise(Config) -> + doc("The first frame on a control stream must be a SETTINGS frame " + "or the connection must be closed with an H3_MISSING_SETTINGS " + "connection error. (RFC9114 6.2.1, RFC9114 9)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>} + ], 0, cow_qpack:init(encoder)), + + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + <<5>>, %% PUSH_PROMISE frame. + cow_http3:encode_int(iolist_size(EncodedHeaders) + 1), + cow_http3:encode_int(0), + EncodedHeaders + ]), + %% The connection should have been closed. + #{reason := h3_missing_settings} = do_wait_connection_closed(Conn), + ok. + +control_reject_first_frame_goaway(Config) -> + doc("The first frame on a control stream must be a SETTINGS frame " + "or the connection must be closed with an H3_MISSING_SETTINGS " + "connection error. (RFC9114 6.2.1, RFC9114 9)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + <<7>>, %% GOAWAY frame. + cow_http3:encode_int(1), + cow_http3:encode_int(0) + ]), + %% The connection should have been closed. + #{reason := h3_missing_settings} = do_wait_connection_closed(Conn), + ok. + +control_reject_first_frame_max_push_id(Config) -> + doc("The first frame on a control stream must be a SETTINGS frame " + "or the connection must be closed with an H3_MISSING_SETTINGS " + "connection error. (RFC9114 6.2.1, RFC9114 9)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + <<13>>, %% MAX_PUSH_ID frame. + cow_http3:encode_int(1), + cow_http3:encode_int(0) + ]), + %% The connection should have been closed. + #{reason := h3_missing_settings} = do_wait_connection_closed(Conn), + ok. + +control_reject_first_frame_reserved(Config) -> + doc("The first frame on a control stream must be a SETTINGS frame " + "or the connection must be closed with an H3_MISSING_SETTINGS " + "connection error. (RFC9114 6.2.1, RFC9114 9)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + Len = rand:uniform(512), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + cow_http3:encode_int(do_reserved_type()), + cow_http3:encode_int(Len), + rand:bytes(Len) + ]), + %% The connection should have been closed. + #{reason := h3_missing_settings} = do_wait_connection_closed(Conn), + ok. + +control_reject_multiple(Config) -> + doc("Endpoints must not create multiple control streams. (RFC9114 6.2.1)"), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + do_critical_reject_multiple(Config, [<<0>>, SettingsBin]). + +do_critical_reject_multiple(Config, HeaderData) -> + #{conn := Conn} = do_connect(Config), + %% Create two critical streams. + {ok, StreamRef1} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, _} = quicer:send(StreamRef1, HeaderData), + {ok, StreamRef2} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, _} = quicer:send(StreamRef2, HeaderData), + %% The connection should have been closed. + #{reason := h3_stream_creation_error} = do_wait_connection_closed(Conn), + ok. + +control_local_closed_abort(Config) -> + doc("Endpoints must not close the control stream. (RFC9114 6.2.1)"), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + do_critical_local_closed_abort(Config, [<<0>>, SettingsBin]). + +do_critical_local_closed_abort(Config, HeaderData) -> + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, _} = quicer:send(StreamRef, HeaderData), + %% Wait a little to make sure the stream data was received before we abort. + timer:sleep(100), + %% Close the critical stream. + quicer:async_shutdown_stream(StreamRef, ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0), + %% The connection should have been closed. + timer:sleep(1000), + #{reason := h3_closed_critical_stream} = do_wait_connection_closed(Conn), + ok. + +control_local_closed_graceful(Config) -> + doc("Endpoints must not close the control stream. (RFC9114 6.2.1)"), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + do_critical_local_closed_graceful(Config, [<<0>>, SettingsBin]). + +do_critical_local_closed_graceful(Config, HeaderData) -> + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, _} = quicer:send(StreamRef, HeaderData), + %% Close the critical stream. + quicer:async_shutdown_stream(StreamRef, ?QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, 0), + %% The connection should have been closed. + #{reason := h3_closed_critical_stream} = do_wait_connection_closed(Conn), + ok. + +control_remote_closed_abort(Config) -> + doc("Endpoints must not close the control stream. (RFC9114 6.2.1)"), + #{conn := Conn, control := ControlRef} = do_connect(Config), + %% Close the control stream. + quicer:async_shutdown_stream(ControlRef, ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0), + %% The connection should have been closed. + #{reason := h3_closed_critical_stream} = do_wait_connection_closed(Conn), + ok. + +%% We cannot gracefully shutdown a remote unidi stream; only abort reading. + +%% Because the contents of the control stream are used to manage the behavior +%% of other streams, endpoints SHOULD provide enough flow-control credit to keep +%% the peer's control stream from becoming blocked. + +%% @todo Implement server push (RFC9114 6.2.2 Push Streams) + +data_frame_can_span_multiple_packets(Config) -> + doc("HTTP/3 frames can span multiple packets. (RFC9114 7)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/echo/read_body">>}, + {<<"content-length">>, <<"13">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<0>>, %% DATA frame. + cow_http3:encode_int(13), + <<"Hello ">> + ]), + timer:sleep(100), + {ok, _} = quicer:send(StreamRef, [ + <<"server!">> + ], ?QUIC_SEND_FLAG_FIN), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello server!">> + } = do_receive_response(StreamRef), + ok. + +headers_frame_can_span_multiple_packets(Config) -> + doc("HTTP/3 frames can span multiple packets. (RFC9114 7)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>} + ], 0, cow_qpack:init(encoder)), + Half = iolist_size(EncodedHeaders) div 2, + <> + = iolist_to_binary(EncodedHeaders), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeadersPart1 + ]), + timer:sleep(100), + {ok, _} = quicer:send(StreamRef, [ + EncodedHeadersPart2 + ]), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello world!">> + } = do_receive_response(StreamRef), + ok. + +%% @todo Implement server push. cancel_push_frame_can_span_multiple_packets(Config) -> + +settings_frame_can_span_multiple_packets(Config) -> + doc("HTTP/3 frames can span multiple packets. (RFC9114 7)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + <> = SettingsBin, + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsPart1 + ]), + timer:sleep(100), + {ok, _} = quicer:send(ControlRef, [ + SettingsPart2 + ]), + %% The connection should remain up. + receive + {quic, shutdown, Conn, {unknown_quic_status, Code}} -> + Reason = cow_http3:code_to_error(Code), + error(Reason) + after 1000 -> + ok + end. + +goaway_frame_can_span_multiple_packets(Config) -> + doc("HTTP/3 frames can span multiple packets. (RFC9114 7)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsBin, + <<7>>, cow_http3:encode_int(1) %% GOAWAY part 1. + ]), + timer:sleep(100), + {ok, _} = quicer:send(ControlRef, [ + cow_http3:encode_int(0) %% GOAWAY part 2. + ]), + %% The connection should be closed gracefully. + receive + {quic, shutdown, Conn, {unknown_quic_status, Code}} -> + h3_no_error = cow_http3:code_to_error(Code), + ok; + %% @todo Temporarily also accept this message. I am + %% not sure why it happens but it isn't wrong per se. + {quic, shutdown, Conn, success} -> + ok + after 1000 -> + error(timeout) + end. + +max_push_id_frame_can_span_multiple_packets(Config) -> + doc("HTTP/3 frames can span multiple packets. (RFC9114 7)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsBin, + <<13>>, cow_http3:encode_int(1) %% MAX_PUSH_ID part 1. + ]), + timer:sleep(100), + {ok, _} = quicer:send(ControlRef, [ + cow_http3:encode_int(0) %% MAX_PUSH_ID part 2. + ]), + %% The connection should remain up. + receive + {quic, shutdown, Conn, {unknown_quic_status, Code}} -> + Reason = cow_http3:code_to_error(Code), + error(Reason) + after 1000 -> + ok + end. + +unknown_frame_can_span_multiple_packets(Config) -> + doc("HTTP/3 frames can span multiple packets. (RFC9114 7)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, _} = quicer:send(StreamRef, [ + cow_http3:encode_int(do_unknown_frame_type()), + cow_http3:encode_int(16383) + ]), + timer:sleep(100), + {ok, _} = quicer:send(StreamRef, rand:bytes(4096)), + timer:sleep(100), + {ok, _} = quicer:send(StreamRef, rand:bytes(4096)), + timer:sleep(100), + {ok, _} = quicer:send(StreamRef, rand:bytes(4096)), + timer:sleep(100), + {ok, _} = quicer:send(StreamRef, rand:bytes(4095)), + {ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders + ], ?QUIC_SEND_FLAG_FIN), + #{ + headers := #{<<":status">> := <<"200">>}, + body := <<"Hello world!">> + } = do_receive_response(StreamRef), + ok. + +%% The DATA and SETTINGS frames can be zero-length therefore +%% they cannot be too short. + +headers_frame_too_short(Config) -> + doc("Frames that terminate before the end of identified fields " + "must be rejected with an H3_FRAME_ERROR connection error. " + "(RFC9114 7.1, RFC9114 10.8)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(0) + ]), + %% The connection should have been closed. + #{reason := h3_frame_error} = do_wait_connection_closed(Conn), + ok. + +%% @todo Implement server push. cancel_push_frame_too_short(Config) -> + +goaway_frame_too_short(Config) -> + doc("Frames that terminate before the end of identified fields " + "must be rejected with an H3_FRAME_ERROR connection error. " + "(RFC9114 7.1, RFC9114 10.8)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsBin, + <<7>>, cow_http3:encode_int(0) %% GOAWAY. + ]), + %% The connection should have been closed. + #{reason := h3_frame_error} = do_wait_connection_closed(Conn), + ok. + +max_push_id_frame_too_short(Config) -> + doc("Frames that terminate before the end of identified fields " + "must be rejected with an H3_FRAME_ERROR connection error. " + "(RFC9114 7.1, RFC9114 10.8)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsBin, + <<13>>, cow_http3:encode_int(0) %% MAX_PUSH_ID. + ]), + %% The connection should have been closed. + #{reason := h3_frame_error} = do_wait_connection_closed(Conn), + ok. + +data_frame_truncated(Config) -> + doc("Truncated frames must be rejected with an " + "H3_FRAME_ERROR connection error. (RFC9114 7.1, RFC9114 10.8)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/echo/read_body">>}, + {<<"content-length">>, <<"13">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders, + <<0>>, %% DATA frame. + cow_http3:encode_int(13), + <<"Hello ">> + ], ?QUIC_SEND_FLAG_FIN), + %% The connection should have been closed. + #{reason := h3_frame_error} = do_wait_connection_closed(Conn), + ok. + +headers_frame_truncated(Config) -> + doc("Truncated frames must be rejected with an " + "H3_FRAME_ERROR connection error. (RFC9114 7.1, RFC9114 10.8)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)) + ], ?QUIC_SEND_FLAG_FIN), + %% The connection should have been closed. + #{reason := h3_frame_error} = do_wait_connection_closed(Conn), + ok. + +%% I am not sure how to test truncated CANCEL_PUSH, SETTINGS, GOAWAY +%% or MAX_PUSH_ID frames, as those are sent on the control stream, +%% which we cannot terminate. + +%% The DATA, HEADERS and SETTINGS frames can be of any length +%% therefore they cannot be too long per se, even if unwanted +%% data can be included at the end of the frame's payload. + +%% @todo Implement server push. cancel_push_frame_too_long(Config) -> + +goaway_frame_too_long(Config) -> + doc("Frames that contain additional bytes after the end of identified fields " + "must be rejected with an H3_FRAME_ERROR connection error. " + "(RFC9114 7.1, RFC9114 10.8)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsBin, + <<7>>, cow_http3:encode_int(3), %% GOAWAY. + <<0, 1, 2>> + ]), + %% The connection should have been closed. + #{reason := h3_frame_error} = do_wait_connection_closed(Conn), + ok. + +max_push_id_frame_too_long(Config) -> + doc("Frames that contain additional bytes after the end of identified fields " + "must be rejected with an H3_FRAME_ERROR connection error. " + "(RFC9114 7.1, RFC9114 10.8)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsBin, + <<13>>, cow_http3:encode_int(9), %% MAX_PUSH_ID. + <<0, 1, 2, 3, 4, 5, 6, 7, 8>> + ]), + %% The connection should have been closed. + #{reason := h3_frame_error} = do_wait_connection_closed(Conn), + ok. + +%% Streams may terminate abruptly in the middle of frames. + +data_frame_rejected_on_control_stream(Config) -> + doc("DATA frames received on the control stream must be rejected " + "with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.1)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsBin, + <<0>>, %% DATA frame. + cow_http3:encode_int(12), + <<"Hello world!">> + ]), + %% The connection should have been closed. + #{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn), + ok. + +headers_frame_rejected_on_control_stream(Config) -> + doc("HEADERS frames received on the control stream must be rejected " + "with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.2)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + {ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsBin, + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders + ]), + %% The connection should have been closed. + #{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn), + ok. + +%% @todo Implement server push. (RFC9114 7.2.3. CANCEL_PUSH) + +settings_twice(Config) -> + doc("Receipt of a second SETTINGS frame on the control stream " + "must be rejected with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.4)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsBin, + SettingsBin + ]), + %% The connection should have been closed. + #{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn), + ok. + +settings_on_bidi_stream(Config) -> + doc("Receipt of a SETTINGS frame on a bidirectional stream " + "must be rejected with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.4)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + SettingsBin, + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ], ?QUIC_SEND_FLAG_FIN), + %% The connection should have been closed. + #{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn), + ok. + +settings_identifier_twice(Config) -> + doc("Receipt of a duplicate SETTINGS identifier must be rejected " + "with an H3_SETTINGS_ERROR connection error. (RFC9114 7.2.4)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + SettingsPayload = [ + cow_http3:encode_int(6), cow_http3:encode_int(4096), + cow_http3:encode_int(6), cow_http3:encode_int(8192) + ], + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + <<4>>, %% SETTINGS frame. + cow_http3:encode_int(iolist_size(SettingsPayload)), + SettingsPayload + ]), + %% The connection should have been closed. + #{reason := h3_settings_error} = do_wait_connection_closed(Conn), + ok. + +settings_ignore_unknown_identifier(Config) -> + doc("Unknown SETTINGS identifiers must be ignored (RFC9114 7.2.4, RFC9114 9)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + SettingsPayload = [ + cow_http3:encode_int(999), cow_http3:encode_int(4096) + ], + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + <<4>>, %% SETTINGS frame. + cow_http3:encode_int(iolist_size(SettingsPayload)), + SettingsPayload + ]), + %% The connection should remain up. + receive + {quic, shutdown, Conn, {unknown_quic_status, Code}} -> + Reason = cow_http3:code_to_error(Code), + error(Reason) + after 1000 -> + ok + end. + +settings_ignore_reserved_identifier(Config) -> + doc("Reserved SETTINGS identifiers must be ignored (RFC9114 7.2.4.1)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + SettingsPayload = [ + cow_http3:encode_int(do_reserved_type()), cow_http3:encode_int(4096) + ], + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + <<4>>, %% SETTINGS frame. + cow_http3:encode_int(iolist_size(SettingsPayload)), + SettingsPayload + ]), + %% The connection should remain up. + receive + {quic, shutdown, Conn, {unknown_quic_status, Code}} -> + Reason = cow_http3:code_to_error(Code), + error(Reason) + after 1000 -> + ok + end. + +%% @todo Check that we send a reserved SETTINGS identifier when sending a +%% non-empty SETTINGS frame. (7.2.4.1. Defined SETTINGS Parameters) + +%% @todo Check that setting SETTINGS_MAX_FIELD_SECTION_SIZE works. + +%% It is unclear whether the SETTINGS identifier 0x00 must be rejected or ignored. + +settings_reject_http2_0x02(Config) -> + do_settings_reject_http2(Config, 2, 1). + +settings_reject_http2_0x03(Config) -> + do_settings_reject_http2(Config, 3, 100). + +settings_reject_http2_0x04(Config) -> + do_settings_reject_http2(Config, 4, 128000). + +settings_reject_http2_0x05(Config) -> + do_settings_reject_http2(Config, 5, 1000000). + +do_settings_reject_http2(Config, Identifier, Value) -> + doc("Receipt of an unused HTTP/2 SETTINGS identifier must be rejected " + "with an H3_SETTINGS_ERROR connection error. (RFC9114 7.2.4, RFC9114 11.2.2)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + SettingsPayload = [ + cow_http3:encode_int(Identifier), cow_http3:encode_int(Value) + ], + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + <<4>>, %% SETTINGS frame. + cow_http3:encode_int(iolist_size(SettingsPayload)), + SettingsPayload + ]), + %% The connection should have been closed. + #{reason := h3_settings_error} = do_wait_connection_closed(Conn), + ok. + +%% 7.2.4.2. Initialization +%% An HTTP implementation MUST NOT send frames or requests that would be +%% invalid based on its current understanding of the peer's settings. +%% @todo In the case of SETTINGS_MAX_FIELD_SECTION_SIZE I don't think we have a choice. + +%% All settings begin at an initial value. Each endpoint SHOULD use these +%% initial values to send messages before the peer's SETTINGS frame has arrived, +%% as packets carrying the settings can be lost or delayed. When the SETTINGS +%% frame arrives, any settings are changed to their new values. + +%% Endpoints MUST NOT require any data to be received from the peer prior to +%% sending the SETTINGS frame; settings MUST be sent as soon as the transport is +%% ready to send data. + +%% @todo Implement 0-RTT. (7.2.4.2. Initialization) + +%% @todo Implement server push. (7.2.5. PUSH_PROMISE) + +goaway_on_bidi_stream(Config) -> + doc("Receipt of a GOAWAY frame on a bidirectional stream " + "must be rejected with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.6)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, _} = quicer:send(StreamRef, [ + <<7>>, cow_http3:encode_int(1), cow_http3:encode_int(0) %% GOAWAY. + ], ?QUIC_SEND_FLAG_FIN), + %% The connection should have been closed. + #{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn), + ok. + +%% @todo Implement server push. (7.2.6 GOAWAY - will have to reject too large push IDs) + +max_push_id_on_bidi_stream(Config) -> + doc("Receipt of a MAX_PUSH_ID frame on a bidirectional stream " + "must be rejected with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.7)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, _} = quicer:send(StreamRef, [ + <<13>>, cow_http3:encode_int(1), cow_http3:encode_int(0) %% MAX_PUSH_ID. + ], ?QUIC_SEND_FLAG_FIN), + %% The connection should have been closed. + #{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn), + ok. + +%% @todo Implement server push. (7.2.7 MAX_PUSH_ID) + +max_push_id_reject_lower(Config) -> + doc("Receipt of a MAX_PUSH_ID value lower than previously received " + "must be rejected with an H3_ID_ERROR connection error. (RFC9114 7.2.7)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsBin, + <<13>>, cow_http3:encode_int(1), cow_http3:encode_int(20), %% MAX_PUSH_ID. + <<13>>, cow_http3:encode_int(1), cow_http3:encode_int(10) %% MAX_PUSH_ID. + ]), + %% The connection should have been closed. + #{reason := h3_id_error} = do_wait_connection_closed(Conn), + ok. + +reserved_on_control_stream(Config) -> + doc("Receipt of a reserved frame type on a control stream " + "must be ignored. (RFC9114 7.2.8)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + Len = rand:uniform(512), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsBin, + cow_http3:encode_int(do_reserved_type()), + cow_http3:encode_int(Len), + rand:bytes(Len) + ]), + %% The connection should remain up. + receive + {quic, shutdown, Conn, {unknown_quic_status, Code}} -> + Reason = cow_http3:code_to_error(Code), + error(Reason) + after 1000 -> + ok + end. + +reserved_reject_http2_0x02_control(Config) -> + do_reserved_reject_http2_control(Config, 2). + +reserved_reject_http2_0x06_control(Config) -> + do_reserved_reject_http2_control(Config, 6). + +reserved_reject_http2_0x08_control(Config) -> + do_reserved_reject_http2_control(Config, 8). + +reserved_reject_http2_0x09_control(Config) -> + do_reserved_reject_http2_control(Config, 9). + +do_reserved_reject_http2_control(Config, Type) -> + doc("Receipt of an unused HTTP/2 frame type must be rejected " + "with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.8, RFC9114 11.2.1)"), + #{conn := Conn} = do_connect(Config), + {ok, ControlRef} = quicer:start_stream(Conn, + #{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}), + {ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}), + Len = rand:uniform(512), + {ok, _} = quicer:send(ControlRef, [ + <<0>>, %% CONTROL stream. + SettingsBin, + cow_http3:encode_int(Type), + cow_http3:encode_int(Len), + rand:bytes(Len) + ]), + %% The connection should have been closed. + #{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn), + ok. + +reserved_reject_http2_0x02_bidi(Config) -> + do_reserved_reject_http2_bidi(Config, 2). + +reserved_reject_http2_0x06_bidi(Config) -> + do_reserved_reject_http2_bidi(Config, 6). + +reserved_reject_http2_0x08_bidi(Config) -> + do_reserved_reject_http2_bidi(Config, 8). + +reserved_reject_http2_0x09_bidi(Config) -> + do_reserved_reject_http2_bidi(Config, 9). + +do_reserved_reject_http2_bidi(Config, Type) -> + doc("Receipt of an unused HTTP/2 frame type must be rejected " + "with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.8, RFC9114 11.2.1)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, + {<<":path">>, <<"/">>}, + {<<"content-length">>, <<"0">>} + ], 0, cow_qpack:init(encoder)), + Len = rand:uniform(512), + {ok, _} = quicer:send(StreamRef, [ + cow_http3:encode_int(Type), + cow_http3:encode_int(Len), + rand:bytes(Len), + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedHeaders)), + EncodedHeaders + ], ?QUIC_SEND_FLAG_FIN), + %% The connection should have been closed. + #{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn), + ok. + +%% An endpoint MAY choose to treat a stream error as a connection error under +%% certain circumstances, closing the entire connection in response to a +%% condition on a single stream. + +%% Because new error codes can be defined without negotiation (see Section 9), +%% use of an error code in an unexpected context or receipt of an unknown error +%% code MUST be treated as equivalent to H3_NO_ERROR. + +%% 8.1. HTTP/3 Error Codes +%% H3_INTERNAL_ERROR (0x0102): An internal error has occurred in the HTTP stack. +%% H3_EXCESSIVE_LOAD (0x0107): The endpoint detected that its peer is +%% exhibiting a behavior that might be generating excessive load. +%% H3_MISSING_SETTINGS (0x010a): No SETTINGS frame was received +%% at the beginning of the control stream. +%% H3_REQUEST_REJECTED (0x010b): A server rejected a request without +%% performing any application processing. +%% H3_REQUEST_CANCELLED (0x010c): The request or its response +%% (including pushed response) is cancelled. +%% H3_REQUEST_INCOMPLETE (0x010d): The client's stream terminated +%% without containing a fully formed request. +%% H3_CONNECT_ERROR (0x010f): The TCP connection established in +%% response to a CONNECT request was reset or abnormally closed. +%% H3_VERSION_FALLBACK (0x0110): The requested operation cannot +%% be served over HTTP/3. The peer should retry over HTTP/1.1. + +%% 9. Extensions to HTTP/3 +%% If a setting is used for extension negotiation, the default value MUST be +%% defined in such a fashion that the extension is disabled if the setting is +%% omitted. + +%% 10. Security Considerations +%% 10.3. Intermediary-Encapsulation Attacks +%% Requests or responses containing invalid field names MUST be treated as malformed. +%% Any request or response that contains a character not permitted in a field +%% value MUST be treated as malformed. + +%% 10.5. Denial-of-Service Considerations +%% Implementations SHOULD track the use of these features and set limits on +%% their use. An endpoint MAY treat activity that is suspicious as a connection +%% error of type H3_EXCESSIVE_LOAD, but false positives will result in disrupting +%% valid connections and requests. + +reject_large_unknown_frame(Config) -> + doc("Large unknown frames may risk denial-of-service " + "and should be rejected. (RFC9114 10.5)"), + #{conn := Conn} = do_connect(Config), + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, _} = quicer:send(StreamRef, [ + cow_http3:encode_int(do_unknown_frame_type()), + cow_http3:encode_int(16385) + ]), + #{reason := h3_excessive_load} = do_wait_connection_closed(Conn), + ok. + +%% 10.5.1. Limits on Field Section Size +%% An endpoint can use the SETTINGS_MAX_FIELD_SECTION_SIZE (Section 4.2.2) +%% setting to advise peers of limits that might apply on the size of field +%% sections. +%% +%% A server that receives a larger field section than it is willing to handle +%% can send an HTTP 431 (Request Header Fields Too Large) status code +%% ([RFC6585]). + +%% 10.6. Use of Compression +%% Implementations communicating on a secure channel MUST NOT compress content +%% that includes both confidential and attacker-controlled data unless separate +%% compression contexts are used for each source of data. Compression MUST NOT be +%% used if the source of data cannot be reliably determined. + +%% 10.9. Early Data +%% The anti-replay mitigations in [HTTP-REPLAY] MUST be applied when using HTTP/3 with 0-RTT. + +%% 10.10. Migration +%% Certain HTTP implementations use the client address for logging or +%% access-control purposes. Since a QUIC client's address might change during a +%% connection (and future versions might support simultaneous use of multiple +%% addresses), such implementations will need to either actively retrieve the +%% client's current address or addresses when they are relevant or explicitly +%% accept that the original address might change. @todo Document this behavior. + +%% Appendix A. Considerations for Transitioning from HTTP/2 +%% A.1. Streams +%% QUIC considers a stream closed when all data has been received and sent data +%% has been acknowledged by the peer. HTTP/2 considers a stream closed when the +%% frame containing the END_STREAM bit has been committed to the transport. As a +%% result, the stream for an equivalent exchange could remain "active" for a +%% longer period of time. HTTP/3 servers might choose to permit a larger number +%% of concurrent client-initiated bidirectional streams to achieve equivalent +%% concurrency to HTTP/2, depending on the expected usage patterns. @todo Document this. + +%% Helper functions. + +%% @todo Maybe have a function in cow_http3. +do_reserved_type() -> + 16#1f * (rand:uniform(148764065110560900) - 1) + 16#21. + +do_connect(Config) -> + do_connect(Config, #{}). + +do_connect(Config, Opts) -> + {ok, Conn} = quicer:connect("localhost", config(port, Config), + Opts#{alpn => ["h3"], verify => none}, 5000), + %% To make sure the connection is fully established we wait + %% to receive the SETTINGS frame on the control stream. + {ok, ControlRef, Settings} = do_wait_settings(Conn), + #{ + conn => Conn, + control => ControlRef, %% This is the peer control stream. + settings => Settings + }. + +do_wait_settings(Conn) -> + receive + {quic, new_stream, StreamRef, #{flags := Flags}} -> + ok = quicer:setopt(StreamRef, active, true), + true = quicer:is_unidirectional(Flags), + receive {quic, << + 0, %% Control stream. + SettingsFrame/bits + >>, StreamRef, _} -> + {ok, {settings, Settings}, <<>>} = cow_http3:parse(SettingsFrame), + {ok, StreamRef, Settings} + after 5000 -> + {error, timeout} + end + after 5000 -> + {error, timeout} + end. + +do_receive_data(StreamRef) -> + receive + {quic, Data, StreamRef, _Flags} when is_binary(Data) -> + {ok, Data} + after 5000 -> + {error, timeout} + end. + +do_guess_int_encoding(Data) -> + SizeWithLen = byte_size(Data) - 1, + if + SizeWithLen < 64 + 1 -> + {0, 6}; + SizeWithLen < 16384 + 2 -> + {1, 14}; + SizeWithLen < 1073741824 + 4 -> + {2, 30}; + SizeWithLen < 4611686018427387904 + 8 -> + {3, 62} + end. + +do_wait_peer_send_shutdown(StreamRef) -> + receive + {quic, peer_send_shutdown, StreamRef, undefined} -> + ok + after 5000 -> + {error, timeout} + end. + +do_wait_stream_aborted(StreamRef) -> + receive + {quic, peer_send_aborted, StreamRef, Code} -> + Reason = cow_http3:code_to_error(Code), + #{reason => Reason}; + {quic, peer_receive_aborted, StreamRef, Code} -> + Reason = cow_http3:code_to_error(Code), + #{reason => Reason} + after 5000 -> + {error, timeout} + end. + +do_wait_stream_closed(StreamRef) -> + receive + {quic, stream_closed, StreamRef, #{error := Error, is_conn_shutdown := false}} -> + 0 = Error, + ok + after 5000 -> + {error, timeout} + end. + +do_receive_response(StreamRef) -> + {ok, Data} = do_receive_data(StreamRef), + {HLenEnc, HLenBits} = do_guess_int_encoding(Data), + << + 1, %% HEADERS frame. + HLenEnc:2, HLen:HLenBits, + EncodedResponse:HLen/bytes, + Rest/bits + >> = Data, + {ok, DecodedResponse, _DecData, _DecSt} + = cow_qpack:decode_field_section(EncodedResponse, 0, cow_qpack:init(decoder)), + Headers = maps:from_list(DecodedResponse), + #{<<"content-length">> := BodyLen} = Headers, + {DLenEnc, DLenBits} = do_guess_int_encoding(Rest), + Body = case Rest of + <<>> -> + <<>>; + << + 0, %% DATA frame. + DLenEnc:2, DLen:DLenBits, + Body0:DLen/bytes + >> -> + BodyLen = integer_to_binary(byte_size(Body0)), + Body0 + end, + ok = do_wait_peer_send_shutdown(StreamRef), + #{ + headers => Headers, + body => Body + }. + +do_wait_connection_closed(Conn) -> + receive + {quic, shutdown, Conn, {unknown_quic_status, Code}} -> + Reason = cow_http3:code_to_error(Code), + #{reason => Reason} + after 5000 -> + {error, timeout} + end. + +-endif. diff --git a/test/rfc9114_SUITE_data/client.key b/test/rfc9114_SUITE_data/client.key new file mode 100644 index 00000000..9c5e1ced --- /dev/null +++ b/test/rfc9114_SUITE_data/client.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgVJakPYfQA1Hr6Gnq +GYmpMfXpxUi2QwDBrZfw8dBcVqKhRANCAAQDHeeAvjwD7p+Mg1F+G9FBNy+7Wcms +HEw4sGMzhUL4wjwsqKHpoiuQg3qUXXK0gamx0l77vFjrUc6X1al4+ZM5 +-----END PRIVATE KEY----- diff --git a/test/rfc9114_SUITE_data/client.pem b/test/rfc9114_SUITE_data/client.pem new file mode 100644 index 00000000..cd9dc8cb --- /dev/null +++ b/test/rfc9114_SUITE_data/client.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBtTCCAVugAwIBAgIUeAPi9oyMIE/KRpsRdukfx2eMuuswCgYIKoZIzj0EAwIw +IDELMAkGA1UEBhMCU0UxETAPBgNVBAoMCE5PQk9EWUFCMB4XDTIzMDcwNTEwMjIy +MloXDTI0MTExNjEwMjIyMlowMTELMAkGA1UEBhMCU0UxETAPBgNVBAoMCE5PQk9E +WUFCMQ8wDQYDVQQDDAZjbGllbnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQD +HeeAvjwD7p+Mg1F+G9FBNy+7WcmsHEw4sGMzhUL4wjwsqKHpoiuQg3qUXXK0gamx +0l77vFjrUc6X1al4+ZM5o2IwYDALBgNVHQ8EBAMCA4gwEQYDVR0RBAowCIIGY2xp +ZW50MB0GA1UdDgQWBBTnhPpO+rSIFAxvkwVjlkKOO2jOeDAfBgNVHSMEGDAWgBSD +Hw8A4XXG3jB1Atrqux7AUsf+KjAKBggqhkjOPQQDAgNIADBFAiEA2qf29EBp2hcL +sEO7MM0ZLm4gnaMdcxtyneF3+c7Lg3cCIBFTVP8xHlhCJyb8ESV7S052VU0bKQFN +ioyoYtcycxuZ +-----END CERTIFICATE----- diff --git a/test/rfc9114_SUITE_data/server.key b/test/rfc9114_SUITE_data/server.key new file mode 100644 index 00000000..45ea890b --- /dev/null +++ b/test/rfc9114_SUITE_data/server.key @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvykUYMOS2gW8XTTh +HgmeJM36NT8GGTNXzzt4sIs0o9ahRANCAATnQOMkKbLFQCZY/cxf8otEJG2tVuG6 +QvLqUdERV2+gzE+4ROGDqbb2Jk1szyz4CfBMB4ZfLA/PdSiO+KrOeOcj +-----END PRIVATE KEY----- diff --git a/test/rfc9114_SUITE_data/server.pem b/test/rfc9114_SUITE_data/server.pem new file mode 100644 index 00000000..43cce8e3 --- /dev/null +++ b/test/rfc9114_SUITE_data/server.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBtTCCAVugAwIBAgIUeAPi9oyMIE/KRpsRdukfx2eMuuowCgYIKoZIzj0EAwIw +IDELMAkGA1UEBhMCU0UxETAPBgNVBAoMCE5PQk9EWUFCMB4XDTIzMDcwNTEwMjIy +MloXDTI0MTExNjEwMjIyMlowMTELMAkGA1UEBhMCU0UxETAPBgNVBAoMCE5PQk9E +WUFCMQ8wDQYDVQQDDAZzZXJ2ZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATn +QOMkKbLFQCZY/cxf8otEJG2tVuG6QvLqUdERV2+gzE+4ROGDqbb2Jk1szyz4CfBM +B4ZfLA/PdSiO+KrOeOcjo2IwYDALBgNVHQ8EBAMCA4gwEQYDVR0RBAowCIIGc2Vy +dmVyMB0GA1UdDgQWBBS+Np5J8BtmWU534pm9hqhrG/EQ7zAfBgNVHSMEGDAWgBSD +Hw8A4XXG3jB1Atrqux7AUsf+KjAKBggqhkjOPQQDAgNIADBFAiEApRfjIEJfO1VH +ETgNG3/MzDayYScPocVn4v8U15ygEw8CIFUY3xMZzJ5AmiRe9PhIUgueOKQNMtds +wdF9+097+Ey0 +-----END CERTIFICATE----- diff --git a/test/rfc9204_SUITE.erl b/test/rfc9204_SUITE.erl new file mode 100644 index 00000000..e8defd2a --- /dev/null +++ b/test/rfc9204_SUITE.erl @@ -0,0 +1,357 @@ +%% Copyright (c) 2024, Loïc Hoguin +%% +%% 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. + +-module(rfc9204_SUITE). +-compile(export_all). +-compile(nowarn_export_all). + +-import(ct_helper, [config/2]). +-import(ct_helper, [doc/1]). + +-ifdef(COWBOY_QUICER). + +-include_lib("quicer/include/quicer.hrl"). + +all() -> + [{group, h3}]. + +groups() -> + %% @todo Enable parallel tests but for this issues in the + %% QUIC accept loop need to be figured out (can't connect + %% concurrently somehow, no backlog?). + [{h3, [], ct_helper:all(?MODULE)}]. + +init_per_group(Name = h3, Config) -> + cowboy_test:init_http3(Name, #{ + env => #{dispatch => cowboy_router:compile(init_routes(Config))} + }, Config). + +end_per_group(Name, _) -> + cowboy_test:stop_group(Name). + +init_routes(_) -> [ + {"localhost", [ + {"/", hello_h, []} + ]} +]. + +%% Encoder. + +%% 2.1 +%% QPACK preserves the ordering of field lines within +%% each field section. An encoder MUST emit field +%% representations in the order they appear in the +%% input field section. + +%% 2.1.1 +%% If the dynamic table does not contain enough room +%% for a new entry without evicting other entries, +%% and the entries that would be evicted are not evictable, +%% the encoder MUST NOT insert that entry into the dynamic +%% table (including duplicates of existing entries). +%% In order to avoid this, an encoder that uses the +%% dynamic table has to keep track of each dynamic +%% table entry referenced by each field section until +%% those representations are acknowledged by the decoder; +%% see Section 4.4.1. + +%% 2.1.2 +%% The decoder specifies an upper bound on the number +%% of streams that can be blocked using the +%% SETTINGS_QPACK_BLOCKED_STREAMS setting; see Section 5. +%% An encoder MUST limit the number of streams that could +%% become blocked to the value of SETTINGS_QPACK_BLOCKED_STREAMS +%% at all times. If a decoder encounters more blocked streams +%% than it promised to support, it MUST treat this as a +%% connection error of type QPACK_DECOMPRESSION_FAILED. + +%% 2.1.3 +%% To avoid these deadlocks, an encoder SHOULD NOT +%% write an instruction unless sufficient stream and +%% connection flow-control credit is available for +%% the entire instruction. + +%% Decoder. + +%% 2.2 +%% The decoder MUST emit field lines in the order their +%% representations appear in the encoded field section. + +%% 2.2.1 +%% While blocked, encoded field section data SHOULD +%% remain in the blocked stream's flow-control window. + +%% If it encounters a Required Insert Count smaller than +%% expected, it MUST treat this as a connection error of +%% type QPACK_DECOMPRESSION_FAILED; see Section 2.2.3. + +%% If it encounters a Required Insert Count larger than +%% expected, it MAY treat this as a connection error of +%% type QPACK_DECOMPRESSION_FAILED. + +%% After the decoder finishes decoding a field section +%% encoded using representations containing dynamic table +%% references, it MUST emit a Section Acknowledgment +%% instruction (Section 4.4.1). + +%% 2.2.2.2 +%% A decoder with a maximum dynamic table capacity +%% (Section 3.2.3) equal to zero MAY omit sending Stream +%% Cancellations, because the encoder cannot have any +%% dynamic table references. + +%% 2.2.3 +%% If the decoder encounters a reference in a field line +%% representation to a dynamic table entry that has already +%% been evicted or that has an absolute index greater than +%% or equal to the declared Required Insert Count (Section 4.5.1), +%% it MUST treat this as a connection error of type +%% QPACK_DECOMPRESSION_FAILED. + +%% If the decoder encounters a reference in an encoder +%% instruction to a dynamic table entry that has already +%% been evicted, it MUST treat this as a connection error +%% of type QPACK_ENCODER_STREAM_ERROR. + +%% Static table. + +%% 3.1 +%% When the decoder encounters an invalid static table index +%% in a field line representation, it MUST treat this as a +%% connection error of type QPACK_DECOMPRESSION_FAILED. +%% +%% If this index is received on the encoder stream, this +%% MUST be treated as a connection error of type +%% QPACK_ENCODER_STREAM_ERROR. + +%% Dynamic table. + +%% 3.2 +%% The dynamic table can contain duplicate entries +%% (i.e., entries with the same name and same value). +%% Therefore, duplicate entries MUST NOT be treated +%% as an error by the decoder. + +%% 3.2.2 +%% The encoder MUST NOT cause a dynamic table entry to be +%% evicted unless that entry is evictable; see Section 2.1.1. + +%% It is an error if the encoder attempts to add an entry +%% that is larger than the dynamic table capacity; the +%% decoder MUST treat this as a connection error of type +%% QPACK_ENCODER_STREAM_ERROR. + +%% 3.2.3 +%% The encoder MUST NOT set a dynamic table capacity that +%% exceeds this maximum, but it can choose to use a lower +%% dynamic table capacity; see Section 4.3.1. + +%% When the client's 0-RTT value of the SETTING is zero, +%% the server MAY set it to a non-zero value in its SETTINGS +%% frame. If the remembered value is non-zero, the server +%% MUST send the same non-zero value in its SETTINGS frame. +%% If it specifies any other value, or omits +%% SETTINGS_QPACK_MAX_TABLE_CAPACITY from SETTINGS, +%% the encoder must treat this as a connection error of +%% type QPACK_DECODER_STREAM_ERROR. + +%% When the maximum table capacity is zero, the encoder +%% MUST NOT insert entries into the dynamic table and +%% MUST NOT send any encoder instructions on the encoder stream. + +%% Wire format. + +%% 4.1.1 +%% QPACK implementations MUST be able to decode integers +%% up to and including 62 bits long. + +%% Encoder and decoder streams. + +decoder_reject_multiple(Config) -> + doc("Endpoints must not create multiple decoder streams. (RFC9204 4.2)"), + rfc9114_SUITE:do_critical_reject_multiple(Config, <<3>>). + +encoder_reject_multiple(Config) -> + doc("Endpoints must not create multiple encoder streams. (RFC9204 4.2)"), + rfc9114_SUITE:do_critical_reject_multiple(Config, <<2>>). + +%% 4.2 +%% The sender MUST NOT close either of these streams, +%% and the receiver MUST NOT request that the sender close +%% either of these streams. Closure of either unidirectional +%% stream type MUST be treated as a connection error of type +%% H3_CLOSED_CRITICAL_STREAM. + +decoder_local_closed_abort(Config) -> + doc("Endpoints must not close the decoder stream. (RFC9204 4.2)"), + rfc9114_SUITE:do_critical_local_closed_abort(Config, <<3>>). + +decoder_local_closed_graceful(Config) -> + doc("Endpoints must not close the decoder stream. (RFC9204 4.2)"), + rfc9114_SUITE:do_critical_local_closed_graceful(Config, <<3>>). + +decoder_remote_closed_abort(Config) -> + doc("Endpoints must not close the decoder stream. (RFC9204 4.2)"), + #{conn := Conn} = rfc9114_SUITE:do_connect(Config, #{peer_unidi_stream_count => 3}), + {ok, #{decoder := StreamRef}} = do_wait_unidi_streams(Conn, #{}), + %% Close the control stream. + quicer:async_shutdown_stream(StreamRef, ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0), + %% The connection should have been closed. + #{reason := h3_closed_critical_stream} = rfc9114_SUITE:do_wait_connection_closed(Conn), + ok. + +encoder_local_closed_abort(Config) -> + doc("Endpoints must not close the encoder stream. (RFC9204 4.2)"), + rfc9114_SUITE:do_critical_local_closed_abort(Config, <<2>>). + +encoder_local_closed_graceful(Config) -> + doc("Endpoints must not close the encoder stream. (RFC9204 4.2)"), + rfc9114_SUITE:do_critical_local_closed_graceful(Config, <<2>>). + +encoder_remote_closed_abort(Config) -> + doc("Endpoints must not close the encoder stream. (RFC9204 4.2)"), + #{conn := Conn} = rfc9114_SUITE:do_connect(Config, #{peer_unidi_stream_count => 3}), + {ok, #{encoder := StreamRef}} = do_wait_unidi_streams(Conn, #{}), + %% Close the control stream. + quicer:async_shutdown_stream(StreamRef, ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0), + %% The connection should have been closed. + #{reason := h3_closed_critical_stream} = rfc9114_SUITE:do_wait_connection_closed(Conn), + ok. + +do_wait_unidi_streams(_, Acc=#{decoder := _, encoder := _}) -> + {ok, Acc}; +do_wait_unidi_streams(Conn, Acc) -> + receive + {quic, new_stream, StreamRef, #{flags := Flags}} -> + ok = quicer:setopt(StreamRef, active, true), + true = quicer:is_unidirectional(Flags), + receive {quic, <>, StreamRef, _} -> + Type = case TypeValue of + 2 -> encoder; + 3 -> decoder + end, + do_wait_unidi_streams(Conn, Acc#{Type => StreamRef}) + after 5000 -> + {error, timeout} + end + after 5000 -> + {error, timeout} + end. + +%% An endpoint MAY avoid creating an encoder stream if it will +%% not be used (for example, if its encoder does not wish to +%% use the dynamic table or if the maximum size of the dynamic +%% table permitted by the peer is zero). + +%% An endpoint MAY avoid creating a decoder stream if its +%% decoder sets the maximum capacity of the dynamic table to zero. + +%% An endpoint MUST allow its peer to create an encoder stream +%% and a decoder stream even if the connection's settings +%% prevent their use. + +%% Encoder instructions. + +%% 4.3.1 +%% The new capacity MUST be lower than or equal to the limit +%% described in Section 3.2.3. In HTTP/3, this limit is the +%% value of the SETTINGS_QPACK_MAX_TABLE_CAPACITY parameter +%% (Section 5) received from the decoder. The decoder MUST +%% treat a new dynamic table capacity value that exceeds this +%% limit as a connection error of type QPACK_ENCODER_STREAM_ERROR. + +%% Reducing the dynamic table capacity can cause entries to be +%% evicted; see Section 3.2.2. This MUST NOT cause the eviction +%% of entries that are not evictable; see Section 2.1.1. + +%% Decoder instructions. + +%% 4.4.1 +%% If an encoder receives a Section Acknowledgment instruction +%% referring to a stream on which every encoded field section +%% with a non-zero Required Insert Count has already been +%% acknowledged, this MUST be treated as a connection error +%% of type QPACK_DECODER_STREAM_ERROR. + +%% 4.4.3 +%% An encoder that receives an Increment field equal to zero, +%% or one that increases the Known Received Count beyond what +%% the encoder has sent, MUST treat this as a connection error +%% of type QPACK_DECODER_STREAM_ERROR. + +%% Field line representation. + +%% 4.5.1.1 +%% If the decoder encounters a value of EncodedInsertCount that +%% could not have been produced by a conformant encoder, it MUST +%% treat this as a connection error of type QPACK_DECOMPRESSION_FAILED. + +%% 4.5.1.2 +%% The value of Base MUST NOT be negative. Though the protocol +%% might operate correctly with a negative Base using post-Base +%% indexing, it is unnecessary and inefficient. An endpoint MUST +%% treat a field block with a Sign bit of 1 as invalid if the +%% value of Required Insert Count is less than or equal to the +%% value of Delta Base. + +%% 4.5.4 +%% When the 'N' bit is set, the encoded field line MUST always +%% be encoded with a literal representation. In particular, +%% when a peer sends a field line that it received represented +%% as a literal field line with the 'N' bit set, it MUST use a +%% literal representation to forward this field line. This bit +%% is intended for protecting field values that are not to be +%% put at risk by compressing them; see Section 7.1 for more details. + +%% Configuration. + +%% 5 +%% SETTINGS_QPACK_MAX_TABLE_CAPACITY +%% SETTINGS_QPACK_BLOCKED_STREAMS + +%% Security considerations. + +%% 7.1.2 +%% (security if used as a proxy merging many connections into one) +%% An ideal solution segregates access to the dynamic table +%% based on the entity that is constructing the message. +%% Field values that are added to the table are attributed +%% to an entity, and only the entity that created a particular +%% value can extract that value. + +%% 7.1.3 +%% An intermediary MUST NOT re-encode a value that uses a +%% literal representation with the 'N' bit set with another +%% representation that would index it. If QPACK is used for +%% re-encoding, a literal representation with the 'N' bit set +%% MUST be used. If HPACK is used for re-encoding, the +%% never-indexed literal representation (see Section 6.2.3 +%% of [RFC7541]) MUST be used. + +%% 7.4 +%% An implementation has to set a limit for the values it +%% accepts for integers, as well as for the encoded length; +%% see Section 4.1.1. In the same way, it has to set a limit +%% to the length it accepts for string literals; see Section 4.1.2. +%% These limits SHOULD be large enough to process the largest +%% individual field the HTTP implementation can be configured +%% to accept. + +%% If an implementation encounters a value larger than it is +%% able to decode, this MUST be treated as a stream error of +%% type QPACK_DECOMPRESSION_FAILED if on a request stream or +%% a connection error of the appropriate type if on the +%% encoder or decoder stream. + +-endif. diff --git a/test/rfc9220_SUITE.erl b/test/rfc9220_SUITE.erl new file mode 100644 index 00000000..7f447ed8 --- /dev/null +++ b/test/rfc9220_SUITE.erl @@ -0,0 +1,485 @@ +%% Copyright (c) 2018, Loïc Hoguin +%% +%% 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. + +-module(rfc9220_SUITE). +-compile(export_all). +-compile(nowarn_export_all). + +-import(ct_helper, [config/2]). +-import(ct_helper, [doc/1]). + +all() -> + [{group, enabled}]. + +groups() -> + Tests = ct_helper:all(?MODULE), + [{enabled, [], Tests}]. %% @todo Enable parallel when all is better. + +init_per_group(Name = enabled, Config) -> + cowboy_test:init_http3(Name, #{ + enable_connect_protocol => true, + env => #{dispatch => cowboy_router:compile(init_routes(Config))} + }, Config). + +end_per_group(Name, _) -> + cowboy_test:stop_group(Name). + +init_routes(_) -> [ + {"localhost", [ + {"/ws", ws_echo, []} + ]} +]. + +% The SETTINGS_ENABLE_CONNECT_PROTOCOL SETTINGS Parameter. + +% The new parameter name is SETTINGS_ENABLE_CONNECT_PROTOCOL. The +% value of the parameter MUST be 0 or 1. + +% Upon receipt of SETTINGS_ENABLE_CONNECT_PROTOCOL with a value of 1 a +% client MAY use the Extended CONNECT definition of this document when +% creating new streams. Receipt of this parameter by a server does not +% have any impact. +%% @todo ignore_client_enable_setting(Config) -> + +reject_handshake_when_disabled(Config0) -> + doc("Extended CONNECT requests MUST be rejected with a " + "H3_MESSAGE_ERROR stream error when enable_connect_protocol=false. " + "(RFC9220, RFC8441 4)"), + Config = cowboy_test:init_http3(disabled, #{ + enable_connect_protocol => false, + env => #{dispatch => cowboy_router:compile(init_routes(Config0))} + }, Config0), + %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 0. + #{ + conn := Conn, + settings := Settings + } = rfc9114_SUITE:do_connect(Config), + case Settings of + #{enable_connect_protocol := false} -> ok; + _ when map_size(Settings) =:= 0 -> ok + end, + %% Send a CONNECT :protocol request to upgrade the stream to Websocket. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"CONNECT">>}, + {<<":protocol">>, <<"websocket">>}, + {<<":scheme">>, <<"https">>}, + {<<":path">>, <<"/ws">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<"sec-websocket-version">>, <<"13">>}, + {<<"origin">>, <<"http://localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ]), + %% The stream should have been aborted. + #{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef), + ok. + +reject_handshake_disabled_by_default(Config0) -> + doc("Extended CONNECT requests MUST be rejected with a " + "H3_MESSAGE_ERROR stream error when enable_connect_protocol=false. " + "(RFC9220, RFC8441 4)"), + Config = cowboy_test:init_http3(disabled, #{ + env => #{dispatch => cowboy_router:compile(init_routes(Config0))} + }, Config0), + %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 0. + #{ + conn := Conn, + settings := Settings + } = rfc9114_SUITE:do_connect(Config), + case Settings of + #{enable_connect_protocol := false} -> ok; + _ when map_size(Settings) =:= 0 -> ok + end, + %% Send a CONNECT :protocol request to upgrade the stream to Websocket. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"CONNECT">>}, + {<<":protocol">>, <<"websocket">>}, + {<<":scheme">>, <<"https">>}, + {<<":path">>, <<"/ws">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<"sec-websocket-version">>, <<"13">>}, + {<<"origin">>, <<"http://localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ]), + %% The stream should have been aborted. + #{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef), + ok. + +% The Extended CONNECT Method. + +accept_uppercase_pseudo_header_protocol(Config) -> + doc("The :protocol pseudo header is case insensitive. (RFC9220, RFC8441 4, RFC9110 7.8)"), + %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. + #{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config), + #{enable_connect_protocol := true} = Settings, + %% Send a CONNECT :protocol request to upgrade the stream to Websocket. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"CONNECT">>}, + {<<":protocol">>, <<"WEBSOCKET">>}, + {<<":scheme">>, <<"https">>}, + {<<":path">>, <<"/ws">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<"sec-websocket-version">>, <<"13">>}, + {<<"origin">>, <<"http://localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ]), + %% Receive a 200 response. + {ok, Data} = rfc9114_SUITE:do_receive_data(StreamRef), + {HLenEnc, HLenBits} = rfc9114_SUITE:do_guess_int_encoding(Data), + << + 1, %% HEADERS frame. + HLenEnc:2, HLen:HLenBits, + EncodedResponse:HLen/bytes + >> = Data, + {ok, DecodedResponse, _DecData, _DecSt} + = cow_qpack:decode_field_section(EncodedResponse, 0, cow_qpack:init(decoder)), + #{<<":status">> := <<"200">>} = maps:from_list(DecodedResponse), + ok. + +reject_many_pseudo_header_protocol(Config) -> + doc("An extended CONNECT request containing more than one " + "protocol component must be rejected with a H3_MESSAGE_ERROR " + "stream error. (RFC9220, RFC9114 4.3.1, RFC9114 4.1.2)"), + %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. + #{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config), + #{enable_connect_protocol := true} = Settings, + %% Send an extended CONNECT request with more than one :protocol pseudo-header. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"CONNECT">>}, + {<<":protocol">>, <<"websocket">>}, + {<<":protocol">>, <<"mqtt">>}, + {<<":scheme">>, <<"https">>}, + {<<":path">>, <<"/ws">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<"sec-websocket-version">>, <<"13">>}, + {<<"origin">>, <<"http://localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ]), + %% The stream should have been aborted. + #{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef), + ok. + +reject_unknown_pseudo_header_protocol(Config) -> + doc("An extended CONNECT request containing more than one " + "protocol component must be rejected with a 501 Not Implemented " + "response. (RFC9220, RFC8441 4)"), + %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. + #{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config), + #{enable_connect_protocol := true} = Settings, + %% Send an extended CONNECT request with an unknown protocol. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"CONNECT">>}, + {<<":protocol">>, <<"mqtt">>}, + {<<":scheme">>, <<"https">>}, + {<<":path">>, <<"/ws">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<"sec-websocket-version">>, <<"13">>}, + {<<"origin">>, <<"http://localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ]), + %% The stream should have been rejected with a 501 Not Implemented. + #{headers := #{<<":status">> := <<"501">>}} = rfc9114_SUITE:do_receive_response(StreamRef), + ok. + +reject_invalid_pseudo_header_protocol(Config) -> + doc("An extended CONNECT request with an invalid protocol " + "component must be rejected with a 501 Not Implemented " + "response. (RFC9220, RFC8441 4)"), + %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. + #{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config), + #{enable_connect_protocol := true} = Settings, + %% Send an extended CONNECT request with an invalid protocol. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"CONNECT">>}, + {<<":protocol">>, <<"websocket mqtt">>}, + {<<":scheme">>, <<"https">>}, + {<<":path">>, <<"/ws">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<"sec-websocket-version">>, <<"13">>}, + {<<"origin">>, <<"http://localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ]), + %% The stream should have been rejected with a 501 Not Implemented. + #{headers := #{<<":status">> := <<"501">>}} = rfc9114_SUITE:do_receive_response(StreamRef), + ok. + +reject_missing_pseudo_header_scheme(Config) -> + doc("An extended CONNECT request whtout a scheme component " + "must be rejected with a H3_MESSAGE_ERROR stream error. " + "(RFC9220, RFC9114 4.3.1, RFC9114 4.1.2)"), + %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. + #{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config), + #{enable_connect_protocol := true} = Settings, + %% Send an extended CONNECT request without a :scheme pseudo-header. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"CONNECT">>}, + {<<":protocol">>, <<"websocket">>}, + {<<":path">>, <<"/ws">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<"sec-websocket-version">>, <<"13">>}, + {<<"origin">>, <<"http://localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ]), + %% The stream should have been aborted. + #{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef), + ok. + +reject_missing_pseudo_header_path(Config) -> + doc("An extended CONNECT request whtout a path component " + "must be rejected with a H3_MESSAGE_ERROR stream error. " + "(RFC9220, RFC9114 4.3.1, RFC9114 4.1.2)"), + %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. + #{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config), + #{enable_connect_protocol := true} = Settings, + %% Send an extended CONNECT request without a :path pseudo-header. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"CONNECT">>}, + {<<":protocol">>, <<"websocket">>}, + {<<":scheme">>, <<"https">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<"sec-websocket-version">>, <<"13">>}, + {<<"origin">>, <<"http://localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ]), + %% The stream should have been aborted. + #{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef), + ok. + +% On requests bearing the :protocol pseudo-header, the :authority +% pseudo-header field is interpreted according to Section 8.1.2.3 of +% [RFC7540] instead of Section 8.3 of [RFC7540]. In particular the +% server MUST not make a new TCP connection to the host and port +% indicated by the :authority. + +reject_missing_pseudo_header_authority(Config) -> + doc("An extended CONNECT request whtout an authority component " + "must be rejected with a H3_MESSAGE_ERROR stream error. " + "(RFC9220, RFC9114 4.3.1, RFC9114 4.1.2)"), + %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. + #{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config), + #{enable_connect_protocol := true} = Settings, + %% Send an extended CONNECT request without an :authority pseudo-header. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"CONNECT">>}, + {<<":protocol">>, <<"websocket">>}, + {<<":scheme">>, <<"https">>}, + {<<":path">>, <<"/ws">>}, + {<<"sec-websocket-version">>, <<"13">>}, + {<<"origin">>, <<"http://localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ]), + %% The stream should have been aborted. + #{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef), + ok. + +% Using Extended CONNECT To Bootstrap The WebSocket Protocol. + +reject_missing_pseudo_header_protocol(Config) -> + doc("An extended CONNECT request whtout a protocol component " + "must be rejected with a H3_MESSAGE_ERROR stream error. " + "(RFC9220, RFC9114 4.3.1, RFC9114 4.1.2)"), + %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. + #{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config), + #{enable_connect_protocol := true} = Settings, + %% Send an extended CONNECT request without a :protocol pseudo-header. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"CONNECT">>}, + {<<":scheme">>, <<"https">>}, + {<<":path">>, <<"/ws">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<"sec-websocket-version">>, <<"13">>}, + {<<"origin">>, <<"http://localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ]), + %% The stream should have been aborted. + #{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef), + ok. + +% The scheme of the Target URI [RFC7230] MUST be https for wss schemed +% WebSockets. HTTP/3 does not provide support for ws schemed WebSockets. +% The websocket URI is still used for proxy autoconfiguration. + +reject_connection_header(Config) -> + doc("An extended CONNECT request with a connection header " + "must be rejected with a H3_MESSAGE_ERROR stream error. " + "(RFC9220, RFC8441 4, RFC9114 4.2, RFC9114 4.5, RFC9114 4.1.2)"), + %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. + #{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config), + #{enable_connect_protocol := true} = Settings, + %% Send an extended CONNECT request with a connection header. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"CONNECT">>}, + {<<":protocol">>, <<"websocket">>}, + {<<":scheme">>, <<"https">>}, + {<<":path">>, <<"/ws">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<"connection">>, <<"upgrade">>}, + {<<"sec-websocket-version">>, <<"13">>}, + {<<"origin">>, <<"http://localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ]), + %% The stream should have been aborted. + #{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef), + ok. + +reject_upgrade_header(Config) -> + doc("An extended CONNECT request with a upgrade header " + "must be rejected with a H3_MESSAGE_ERROR stream error. " + "(RFC9220, RFC8441 4, RFC9114 4.2, RFC9114 4.5, RFC9114 4.1.2)"), + %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. + #{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config), + #{enable_connect_protocol := true} = Settings, + %% Send an extended CONNECT request with a upgrade header. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"CONNECT">>}, + {<<":protocol">>, <<"websocket">>}, + {<<":scheme">>, <<"https">>}, + {<<":path">>, <<"/ws">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<"upgrade">>, <<"websocket">>}, + {<<"sec-websocket-version">>, <<"13">>}, + {<<"origin">>, <<"http://localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ]), + %% The stream should have been aborted. + #{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef), + ok. + +% After successfully processing the opening handshake the peers should +% proceed with The WebSocket Protocol [RFC6455] using the HTTP/2 stream +% from the CONNECT transaction as if it were the TCP connection +% referred to in [RFC6455]. The state of the WebSocket connection at +% this point is OPEN as defined by [RFC6455], Section 4.1. +%% @todo I'm guessing we should test for things like RST_STREAM, +%% closing the connection and others? + +% Examples. + +accept_handshake_when_enabled(Config) -> + doc("Confirm the example for Websocket over HTTP/2 works. (RFC9220, RFC8441 5.1)"), + %% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1. + #{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config), + #{enable_connect_protocol := true} = Settings, + %% Send a CONNECT :protocol request to upgrade the stream to Websocket. + {ok, StreamRef} = quicer:start_stream(Conn, #{}), + {ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([ + {<<":method">>, <<"CONNECT">>}, + {<<":protocol">>, <<"websocket">>}, + {<<":scheme">>, <<"https">>}, + {<<":path">>, <<"/ws">>}, + {<<":authority">>, <<"localhost">>}, %% @todo Correct port number. + {<<"sec-websocket-version">>, <<"13">>}, + {<<"origin">>, <<"http://localhost">>} + ], 0, cow_qpack:init(encoder)), + {ok, _} = quicer:send(StreamRef, [ + <<1>>, %% HEADERS frame. + cow_http3:encode_int(iolist_size(EncodedRequest)), + EncodedRequest + ]), + %% Receive a 200 response. + {ok, Data} = rfc9114_SUITE:do_receive_data(StreamRef), + {HLenEnc, HLenBits} = rfc9114_SUITE:do_guess_int_encoding(Data), + << + 1, %% HEADERS frame. + HLenEnc:2, HLen:HLenBits, + EncodedResponse:HLen/bytes + >> = Data, + {ok, DecodedResponse, _DecData, _DecSt} + = cow_qpack:decode_field_section(EncodedResponse, 0, cow_qpack:init(decoder)), + #{<<":status">> := <<"200">>} = maps:from_list(DecodedResponse), + %% Masked text hello echoed back clear by the server. + Mask = 16#37fa213d, + MaskedHello = ws_SUITE:do_mask(<<"Hello">>, Mask, <<>>), + {ok, _} = quicer:send(StreamRef, cow_http3:data( + <<1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary>>)), + {ok, WsData} = rfc9114_SUITE:do_receive_data(StreamRef), + << + 0, %% DATA frame. + 0:2, 7:6, %% Length (2 bytes header + "Hello"). + 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" %% Websocket frame. + >> = WsData, + ok. + +%% Closing a Websocket stream. + +% The HTTP/3 stream closure is also analogous to the TCP connection +% closure of [RFC6455]. Orderly TCP-level closures are represented +% as a FIN bit on the stream (Section 4.4 of [HTTP/3]). RST exceptions +% are represented with a stream error (Section 8 of [HTTP/3]) of type +% H3_REQUEST_CANCELLED (Section 8.1 of [HTTP/3]). + +%% @todo client close frame with FIN +%% @todo server close frame with FIN +%% @todo client other frame with FIN +%% @todo server other frame with FIN +%% @todo client close connection diff --git a/test/security_SUITE.erl b/test/security_SUITE.erl index a1ba9168..666dccef 100644 --- a/test/security_SUITE.erl +++ b/test/security_SUITE.erl @@ -49,10 +49,12 @@ groups() -> {https, [parallel], Tests ++ H1Tests}, {h2, [parallel], Tests}, {h2c, [parallel], Tests ++ H2CTests}, + {h3, [], Tests}, {http_compress, [parallel], Tests ++ H1Tests}, {https_compress, [parallel], Tests ++ H1Tests}, {h2_compress, [parallel], Tests}, - {h2c_compress, [parallel], Tests ++ H2CTests} + {h2c_compress, [parallel], Tests ++ H2CTests}, + {h3_compress, [], Tests} ]. init_per_suite(Config) -> @@ -66,7 +68,7 @@ init_per_group(Name, Config) -> cowboy_test:init_common_groups(Name, Config, ?MODULE). end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). %% Routes. diff --git a/test/static_handler_SUITE.erl b/test/static_handler_SUITE.erl index 17a56e0d..9620f666 100644 --- a/test/static_handler_SUITE.erl +++ b/test/static_handler_SUITE.erl @@ -20,6 +20,12 @@ -import(ct_helper, [doc/1]). -import(cowboy_test, [gun_open/1]). +%% Import useful functions from req_SUITE. +%% @todo Maybe move these functions to cowboy_test. +-import(req_SUITE, [do_get/2]). +-import(req_SUITE, [do_get/3]). +-import(req_SUITE, [do_maybe_h3_error3/1]). + %% ct. all() -> @@ -39,16 +45,22 @@ groups() -> {dir, [parallel], DirTests}, {priv_dir, [parallel], DirTests} ], + GroupTestsNoParallel = OtherTests ++ [ + {dir, [], DirTests}, + {priv_dir, [], DirTests} + ], [ {http, [parallel], GroupTests}, {https, [parallel], GroupTests}, {h2, [parallel], GroupTests}, {h2c, [parallel], GroupTests}, + {h3, [], GroupTestsNoParallel}, %% @todo Enable parallel when it works better. {http_compress, [parallel], GroupTests}, {https_compress, [parallel], GroupTests}, {h2_compress, [parallel], GroupTests}, {h2c_compress, [parallel], GroupTests}, - %% No real need to test sendfile disabled against https or h2. + {h3_compress, [], GroupTestsNoParallel}, %% @todo Enable parallel when it works better. + %% No real need to test sendfile disabled against https, h2 or h3. {http_no_sendfile, [parallel], GroupTests}, {h2c_no_sendfile, [parallel], GroupTests} ]. @@ -116,6 +128,17 @@ init_per_group(Name=h2c_no_sendfile, Config) -> sendfile => false }, [{flavor, vanilla}|Config]), lists:keyreplace(protocol, 1, Config1, {protocol, http2}); +init_per_group(Name=h3, Config) -> + cowboy_test:init_http3(Name, #{ + env => #{dispatch => init_dispatch(Config)}, + middlewares => [?MODULE, cowboy_router, cowboy_handler] + }, [{flavor, vanilla}|Config]); +init_per_group(Name=h3_compress, Config) -> + cowboy_test:init_http3(Name, #{ + env => #{dispatch => init_dispatch(Config)}, + middlewares => [?MODULE, cowboy_router, cowboy_handler], + stream_handlers => [cowboy_compress_h, cowboy_stream_h] + }, [{flavor, vanilla}|Config]); init_per_group(Name, Config) -> Config1 = cowboy_test:init_common_groups(Name, Config, ?MODULE), Opts = ranch:get_protocol_options(Name), @@ -129,7 +152,7 @@ end_per_group(dir, _) -> end_per_group(priv_dir, _) -> ok; end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). %% Large file. @@ -248,25 +271,11 @@ do_mime_custom(Path) -> _ -> {<<"application">>, <<"octet-stream">>, []} end. -do_get(Path, Config) -> - do_get(Path, [], Config). - -do_get(Path, ReqHeaders, Config) -> - ConnPid = gun_open(Config), - Ref = gun:get(ConnPid, Path, [{<<"accept-encoding">>, <<"gzip">>}|ReqHeaders]), - {response, IsFin, Status, RespHeaders} = gun:await(ConnPid, Ref), - {ok, Body} = case IsFin of - nofin -> gun:await_body(ConnPid, Ref); - fin -> {ok, <<>>} - end, - gun:close(ConnPid), - {Status, RespHeaders, Body}. - %% Tests. bad(Config) -> doc("Bad cowboy_static options: not a tuple."), - {500, _, _} = do_get("/bad", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/bad", Config)), ok. bad_dir_path(Config) -> @@ -276,7 +285,7 @@ bad_dir_path(Config) -> bad_dir_route(Config) -> doc("Bad cowboy_static options: missing [...] in route."), - {500, _, _} = do_get("/bad/dir/route", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/bad/dir/route", Config)), ok. bad_file_in_priv_dir_in_ez_archive(Config) -> @@ -291,27 +300,27 @@ bad_file_path(Config) -> bad_options(Config) -> doc("Bad cowboy_static extra options: not a list."), - {500, _, _} = do_get("/bad/options", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/bad/options", Config)), ok. bad_options_charset(Config) -> doc("Bad cowboy_static extra options: invalid charset option."), - {500, _, _} = do_get("/bad/options/charset", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/bad/options/charset", Config)), ok. bad_options_etag(Config) -> doc("Bad cowboy_static extra options: invalid etag option."), - {500, _, _} = do_get("/bad/options/etag", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/bad/options/etag", Config)), ok. bad_options_mime(Config) -> doc("Bad cowboy_static extra options: invalid mimetypes option."), - {500, _, _} = do_get("/bad/options/mime", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/bad/options/mime", Config)), ok. bad_priv_dir_app(Config) -> doc("Bad cowboy_static options: wrong application name."), - {500, _, _} = do_get("/bad/priv_dir/app/style.css", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/bad/priv_dir/app/style.css", Config)), ok. bad_priv_dir_in_ez_archive(Config) -> @@ -331,12 +340,12 @@ bad_priv_dir_path(Config) -> bad_priv_dir_route(Config) -> doc("Bad cowboy_static options: missing [...] in route."), - {500, _, _} = do_get("/bad/priv_dir/route", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/bad/priv_dir/route", Config)), ok. bad_priv_file_app(Config) -> doc("Bad cowboy_static options: wrong application name."), - {500, _, _} = do_get("/bad/priv_file/app", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/bad/priv_file/app", Config)), ok. bad_priv_file_in_ez_archive(Config) -> @@ -535,7 +544,7 @@ dir_unknown(Config) -> etag_crash(Config) -> doc("Get a file with a crashing etag function."), - {500, _, _} = do_get("/etag/crash", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/etag/crash", Config)), ok. etag_custom(Config) -> @@ -813,7 +822,7 @@ mime_all_uppercase(Config) -> mime_crash(Config) -> doc("Get a file with a crashing mimetype function."), - {500, _, _} = do_get("/mime/crash/style.css", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/mime/crash/style.css", Config)), ok. mime_custom_cowboy(Config) -> @@ -848,7 +857,7 @@ mime_hardcode_tuple(Config) -> charset_crash(Config) -> doc("Get a file with a crashing charset function."), - {500, _, _} = do_get("/charset/crash/style.css", Config), + {500, _, _} = do_maybe_h3_error3(do_get("/charset/crash/style.css", Config)), ok. charset_custom_cowboy(Config) -> @@ -933,7 +942,8 @@ unicode_basic_error(Config) -> %% # and ? indicate fragment and query components %% and are therefore not part of the path. http -> "\r\s#?"; - http2 -> "#?" + http2 -> "#?"; + http3 -> "#?" end, _ = [case do_get("/char/" ++ [C], Config) of {400, _, _} -> ok; diff --git a/test/stream_handler_SUITE.erl b/test/stream_handler_SUITE.erl index bd87e400..f8e2200c 100644 --- a/test/stream_handler_SUITE.erl +++ b/test/stream_handler_SUITE.erl @@ -31,50 +31,42 @@ groups() -> %% We set this module as a logger in order to silence expected errors. init_per_group(Name = http, Config) -> - cowboy_test:init_http(Name, #{ - logger => ?MODULE, - stream_handlers => [stream_handler_h] - }, Config); + cowboy_test:init_http(Name, init_plain_opts(), Config); init_per_group(Name = https, Config) -> - cowboy_test:init_https(Name, #{ - logger => ?MODULE, - stream_handlers => [stream_handler_h] - }, Config); + cowboy_test:init_https(Name, init_plain_opts(), Config); init_per_group(Name = h2, Config) -> - cowboy_test:init_http2(Name, #{ - logger => ?MODULE, - stream_handlers => [stream_handler_h] - }, Config); + cowboy_test:init_http2(Name, init_plain_opts(), Config); init_per_group(Name = h2c, Config) -> - Config1 = cowboy_test:init_http(Name, #{ - logger => ?MODULE, - stream_handlers => [stream_handler_h] - }, Config), + Config1 = cowboy_test:init_http(Name, init_plain_opts(), Config), lists:keyreplace(protocol, 1, Config1, {protocol, http2}); +init_per_group(Name = h3, Config) -> + cowboy_test:init_http3(Name, init_plain_opts(), Config); init_per_group(Name = http_compress, Config) -> - cowboy_test:init_http(Name, #{ - logger => ?MODULE, - stream_handlers => [cowboy_compress_h, stream_handler_h] - }, Config); + cowboy_test:init_http(Name, init_compress_opts(), Config); init_per_group(Name = https_compress, Config) -> - cowboy_test:init_https(Name, #{ - logger => ?MODULE, - stream_handlers => [cowboy_compress_h, stream_handler_h] - }, Config); + cowboy_test:init_https(Name, init_compress_opts(), Config); init_per_group(Name = h2_compress, Config) -> - cowboy_test:init_http2(Name, #{ - logger => ?MODULE, - stream_handlers => [cowboy_compress_h, stream_handler_h] - }, Config); + cowboy_test:init_http2(Name, init_compress_opts(), Config); init_per_group(Name = h2c_compress, Config) -> - Config1 = cowboy_test:init_http(Name, #{ - logger => ?MODULE, - stream_handlers => [cowboy_compress_h, stream_handler_h] - }, Config), - lists:keyreplace(protocol, 1, Config1, {protocol, http2}). + Config1 = cowboy_test:init_http(Name, init_compress_opts(), Config), + lists:keyreplace(protocol, 1, Config1, {protocol, http2}); +init_per_group(Name = h3_compress, Config) -> + cowboy_test:init_http3(Name, init_compress_opts(), Config). end_per_group(Name, _) -> - cowboy:stop_listener(Name). + cowboy_test:stop_group(Name). + +init_plain_opts() -> + #{ + logger => ?MODULE, + stream_handlers => [stream_handler_h] + }. + +init_compress_opts() -> + #{ + logger => ?MODULE, + stream_handlers => [cowboy_compress_h, stream_handler_h] + }. %% Logger function silencing the expected crashes. @@ -99,15 +91,20 @@ crash_in_init(Config) -> %% Confirm terminate/3 is NOT called. We have no state to give to it. receive {Self, Pid, terminate, _, _, _} -> error(terminate) after 1000 -> ok end, %% Confirm early_error/5 is called in HTTP/1.1's case. - %% HTTP/2 does not send a response back so there is no early_error call. + %% HTTP/2 and HTTP/3 do not send a response back so there is no early_error call. case config(protocol, Config) of http -> receive {Self, Pid, early_error, _, _, _, _, _} -> ok after 1000 -> error(timeout) end; - http2 -> ok + http2 -> ok; + http3 -> ok end, - %% Receive a 500 error response. - case gun:await(ConnPid, Ref) of - {response, fin, 500, _} -> ok; - {error, {stream_error, {stream_error, internal_error, _}}} -> ok + do_await_internal_error(ConnPid, Ref, Config). + +do_await_internal_error(ConnPid, Ref, Config) -> + Protocol = config(protocol, Config), + case {Protocol, gun:await(ConnPid, Ref)} of + {http, {response, fin, 500, _}} -> ok; + {http2, {error, {stream_error, {stream_error, internal_error, _}}}} -> ok; + {http3, {error, {stream_error, {stream_error, h3_internal_error, _}}}} -> ok end. crash_in_data(Config) -> @@ -126,11 +123,7 @@ crash_in_data(Config) -> gun:data(ConnPid, Ref, fin, <<"Hello!">>), %% Confirm terminate/3 is called, indicating the stream ended. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end, - %% Receive a 500 error response. - case gun:await(ConnPid, Ref) of - {response, fin, 500, _} -> ok; - {error, {stream_error, {stream_error, internal_error, _}}} -> ok - end. + do_await_internal_error(ConnPid, Ref, Config). crash_in_info(Config) -> doc("Confirm an error is sent when a stream handler crashes in info/3."), @@ -144,14 +137,14 @@ crash_in_info(Config) -> %% Confirm init/3 is called. Pid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end, %% Send a message to make the stream handler crash. - Pid ! {{Pid, 1}, crash}, + StreamID = case config(protocol, Config) of + http3 -> 0; + _ -> 1 + end, + Pid ! {{Pid, StreamID}, crash}, %% Confirm terminate/3 is called, indicating the stream ended. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end, - %% Receive a 500 error response. - case gun:await(ConnPid, Ref) of - {response, fin, 500, _} -> ok; - {error, {stream_error, {stream_error, internal_error, _}}} -> ok - end. + do_await_internal_error(ConnPid, Ref, Config). crash_in_terminate(Config) -> doc("Confirm the state is correct when a stream handler crashes in terminate/3."), @@ -185,10 +178,12 @@ crash_in_terminate(Config) -> {ok, <<"Hello world!">>} = gun:await_body(ConnPid, Ref2), ok. +%% @todo The callbacks ARE used for HTTP/2 and HTTP/3 CONNECT/TRACE requests. crash_in_early_error(Config) -> case config(protocol, Config) of http -> do_crash_in_early_error(Config); - http2 -> doc("The callback early_error/5 is not currently used for HTTP/2.") + http2 -> doc("The callback early_error/5 is not currently used for HTTP/2."); + http3 -> doc("The callback early_error/5 is not currently used for HTTP/3.") end. do_crash_in_early_error(Config) -> @@ -225,10 +220,12 @@ do_crash_in_early_error(Config) -> {response, fin, 500, _} = gun:await(ConnPid, Ref2), ok. +%% @todo The callbacks ARE used for HTTP/2 and HTTP/3 CONNECT/TRACE requests. crash_in_early_error_fatal(Config) -> case config(protocol, Config) of http -> do_crash_in_early_error_fatal(Config); - http2 -> doc("The callback early_error/5 is not currently used for HTTP/2.") + http2 -> doc("The callback early_error/5 is not currently used for HTTP/2."); + http3 -> doc("The callback early_error/5 is not currently used for HTTP/3.") end. do_crash_in_early_error_fatal(Config) -> @@ -262,7 +259,8 @@ early_error_stream_error_reason(Config) -> %% reason in both protocols. {Method, Headers, Status, Error} = case config(protocol, Config) of http -> {<<"GET">>, [{<<"host">>, <<"host:port">>}], 400, protocol_error}; - http2 -> {<<"TRACE">>, [], 501, no_error} + http2 -> {<<"TRACE">>, [], 501, no_error}; + http3 -> {<<"TRACE">>, [], 501, h3_no_error} end, Ref = gun:request(ConnPid, Method, "/long_polling", [ {<<"accept-encoding">>, <<"gzip">>}, @@ -355,11 +353,20 @@ shutdown_on_socket_close(Config) -> Spawn ! {Self, ready}, %% Close the socket. ok = gun:close(ConnPid), - %% Confirm terminate/3 is called, indicating the stream ended. - receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end, - %% Confirm we receive a DOWN message for the child process. - receive {'DOWN', MRef, process, Spawn, shutdown} -> ok after 1000 -> error(timeout) end, - ok. + Protocol = config(protocol, Config), + try + %% Confirm terminate/3 is called, indicating the stream ended. + receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end, + %% Confirm we receive a DOWN message for the child process. + receive {'DOWN', MRef, process, Spawn, shutdown} -> ok after 1000 -> error(timeout) end, + ok + catch error:timeout when Protocol =:= http3 -> + %% @todo Figure out why this happens. Could be a timing issue + %% or a legitimate bug. I suspect that the server just + %% doesn't receive the GOAWAY frame from Gun because + %% Gun is too quick to close the connection. + shutdown_on_socket_close(Config) + end. shutdown_timeout_on_stream_stop(Config) -> doc("Confirm supervised processes are killed " @@ -406,33 +413,45 @@ shutdown_timeout_on_socket_close(Config) -> Spawn ! {Self, ready}, %% Close the socket. ok = gun:close(ConnPid), - %% Confirm terminate/3 is called, indicating the stream ended. - receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end, - %% We should NOT receive a DOWN message immediately. - receive {'DOWN', MRef, process, Spawn, killed} -> error(killed) after 1500 -> ok end, - %% We should receive it now. - receive {'DOWN', MRef, process, Spawn, killed} -> ok after 1000 -> error(timeout) end, - ok. + Protocol = config(protocol, Config), + try + %% Confirm terminate/3 is called, indicating the stream ended. + receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end, + %% We should NOT receive a DOWN message immediately. + receive {'DOWN', MRef, process, Spawn, killed} -> error(killed) after 1500 -> ok end, + %% We should receive it now. + receive {'DOWN', MRef, process, Spawn, killed} -> ok after 1000 -> error(timeout) end, + ok + catch error:timeout when Protocol =:= http3 -> + %% @todo Figure out why this happens. Could be a timing issue + %% or a legitimate bug. I suspect that the server just + %% doesn't receive the GOAWAY frame from Gun because + %% Gun is too quick to close the connection. + shutdown_timeout_on_socket_close(Config) + end. switch_protocol_after_headers(Config) -> case config(protocol, Config) of http -> do_switch_protocol_after_response( <<"switch_protocol_after_headers">>, Config); - http2 -> doc("The switch_protocol command is not currently supported for HTTP/2.") + http2 -> doc("The switch_protocol command is not currently supported for HTTP/2."); + http3 -> doc("The switch_protocol command is not currently supported for HTTP/3.") end. switch_protocol_after_headers_data(Config) -> case config(protocol, Config) of http -> do_switch_protocol_after_response( <<"switch_protocol_after_headers_data">>, Config); - http2 -> doc("The switch_protocol command is not currently supported for HTTP/2.") + http2 -> doc("The switch_protocol command is not currently supported for HTTP/2."); + http3 -> doc("The switch_protocol command is not currently supported for HTTP/3.") end. switch_protocol_after_response(Config) -> case config(protocol, Config) of http -> do_switch_protocol_after_response( <<"switch_protocol_after_response">>, Config); - http2 -> doc("The switch_protocol command is not currently supported for HTTP/2.") + http2 -> doc("The switch_protocol command is not currently supported for HTTP/2."); + http3 -> doc("The switch_protocol command is not currently supported for HTTP/3.") end. do_switch_protocol_after_response(TestCase, Config) -> @@ -502,7 +521,12 @@ terminate_on_stop(Config) -> {response, fin, 204, _} = gun:await(ConnPid, Ref), %% Confirm the stream is still alive even though we %% received the response fully, and tell it to stop. - Pid ! {{Pid, 1}, please_stop}, + StreamID = case config(protocol, Config) of + http -> 1; + http2 -> 1; + http3 -> 0 + end, + Pid ! {{Pid, StreamID}, please_stop}, receive {Self, Pid, info, _, please_stop, _} -> ok after 1000 -> error(timeout) end, %% Confirm terminate/3 is called. receive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end, @@ -511,7 +535,8 @@ terminate_on_stop(Config) -> terminate_on_switch_protocol(Config) -> case config(protocol, Config) of http -> do_terminate_on_switch_protocol(Config); - http2 -> doc("The switch_protocol command is not currently supported for HTTP/2.") + http2 -> doc("The switch_protocol command is not currently supported for HTTP/2."); + http3 -> doc("The switch_protocol command is not currently supported for HTTP/3.") end. do_terminate_on_switch_protocol(Config) -> diff --git a/test/tracer_SUITE.erl b/test/tracer_SUITE.erl index d97ce447..af1f8f3f 100644 --- a/test/tracer_SUITE.erl +++ b/test/tracer_SUITE.erl @@ -29,7 +29,8 @@ suite() -> %% We initialize trace patterns here. Appropriate would be in %% init_per_suite/1, but this works just as well. all() -> - cowboy_test:common_all(). + %% @todo Implement these tests for HTTP/3. + cowboy_test:common_all() -- [{group, h3}, {group, h3_compress}]. init_per_suite(Config) -> cowboy_tracer_h:set_trace_patterns(), From b77dd29133365a11a4884af8f0fe432981d3f8a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Fri, 5 Apr 2024 22:08:59 +0200 Subject: [PATCH 20/20] Add VU#421644 to the HTTP/2 CONTINUATION Flood test --- test/security_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/security_SUITE.erl b/test/security_SUITE.erl index 666dccef..944c4919 100644 --- a/test/security_SUITE.erl +++ b/test/security_SUITE.erl @@ -224,7 +224,7 @@ http2_empty_frame_flooding_push_promise(Config) -> http2_infinite_continuations(Config) -> doc("Confirm that Cowboy rejects CONTINUATION frames when the " - "total size of HEADERS + CONTINUATION(s) exceeds the limit."), + "total size of HEADERS + CONTINUATION(s) exceeds the limit. (VU#421644)"), {ok, Socket} = rfc7540_SUITE:do_handshake(Config), %% Send a HEADERS frame followed by a large number %% of continuation frames.