Skip to content

Commit 82cbff6

Browse files
authored
Support HTTP QUERY method (#1319)
Add `query/3` macro for routing, enable body parsing, and skip QUERY during CSRF protection
1 parent 1fea083 commit 82cbff6

7 files changed

Lines changed: 43 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Changelog
22

3+
## v1.21.0
4+
5+
### Enhancements
6+
7+
* [Plug.Router] Add the `query/3` macro for routing HTTP QUERY (RFC 10008) requests
8+
* [Plug.Parsers] Parse request bodies for the HTTP QUERY method (RFC 10008)
9+
* [Plug.CSRFProtection] Treat the safe, idempotent HTTP QUERY method (RFC 10008) as unprotected
10+
311
## v1.20.1 (2026-06-23)
412

513
### Bug fixes

lib/plug/csrf_protection.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ defmodule Plug.CSRFProtection do
108108
alias Plug.Crypto.MessageVerifier
109109

110110
@behaviour Plug
111-
@unprotected_methods ~w(HEAD GET OPTIONS)
111+
@unprotected_methods ~w(QUERY HEAD GET OPTIONS)
112112
@digest Base.url_encode64("HS256", padding: false) <> "."
113113

114114
# The token size value should not generate padding

lib/plug/parsers.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ defmodule Plug.Parsers do
7070
7171
This plug only parses the body if the request method is one of the following:
7272
73+
* `QUERY`
7374
* `POST`
7475
* `PUT`
7576
* `PATCH`
@@ -247,7 +248,7 @@ defmodule Plug.Parsers do
247248
| {:next, Conn.t()}
248249

249250
@behaviour Plug
250-
@methods ~w(POST PUT PATCH DELETE)
251+
@methods ~w(QUERY POST PUT PATCH DELETE)
251252

252253
@impl true
253254
def init(opts) do

lib/plug/router.ex

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ defmodule Plug.Router do
4444
4545
In the example above, a request will only match if it is a `GET`
4646
request and the route is "/hello". The supported HTTP methods are
47-
`get`, `post`, `put`, `patch`, `delete` and `options`.
47+
`get`, `query`, `post`, `put`, `patch`, `delete` and `options`.
4848
4949
A route can also specify parameters which will then be available
5050
in the function body:
@@ -389,6 +389,14 @@ defmodule Plug.Router do
389389
compile(:head, path, options, contents, __CALLER__)
390390
end
391391

392+
@doc """
393+
Dispatches to the path only if the request is a QUERY request.
394+
See `match/3` for more examples.
395+
"""
396+
defmacro query(path, options, contents \\ []) do
397+
compile(:query, path, options, contents, __CALLER__)
398+
end
399+
392400
@doc """
393401
Dispatches to the path only if the request is a POST request.
394402
See `match/3` for more examples.

test/plug/csrf_protection_test.exs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ defmodule Plug.CSRFProtectionTest do
205205
refute conn.halted
206206
refute get_session(conn, "_csrf_token")
207207

208+
conn = call(conn(:query, "/"))
209+
refute conn.halted
210+
refute get_session(conn, "_csrf_token")
211+
208212
conn = call(conn(:head, "/"))
209213
refute conn.halted
210214
refute get_session(conn, "_csrf_token")

test/plug/parsers_test.exs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,16 @@ defmodule Plug.ParsersTest do
150150
assert conn.params["foo"] == "baz"
151151
end
152152

153+
test "parses QUERY request bodies" do
154+
conn =
155+
conn(:query, "/?foo=bar", "foo=baz")
156+
|> put_req_header("content-type", "application/x-www-form-urlencoded")
157+
|> parse()
158+
159+
assert conn.params["foo"] == "baz"
160+
assert conn.body_params["foo"] == "baz"
161+
end
162+
153163
test "parses multipart bodies with test params" do
154164
conn = parse(conn(:post, "/?foo=bar"))
155165
assert conn.params == %{"foo" => "bar"}

test/plug/router_test.exs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ defmodule Plug.RouterTest do
175175
resp(conn, 200, "")
176176
end
177177

178+
query "/query" do
179+
resp(conn, 200, "")
180+
end
181+
178182
post "/post" do
179183
resp(conn, 200, "")
180184
end
@@ -562,6 +566,11 @@ defmodule Plug.RouterTest do
562566
assert conn.status == 200
563567
end
564568

569+
test "declare and call QUERY requests" do
570+
conn = call(Sample, conn(:query, "/query"))
571+
assert conn.status == 200
572+
end
573+
565574
test "forwards only :via methods" do
566575
for method <- [:post, :put] do
567576
resp = call(Forward, conn(method, "/forward_via"))

0 commit comments

Comments
 (0)