Relax supported HTTP verbs

There's some really awkward duplication in the HTTP API right now, where
the Lua code reimplements the validation the Java implementation
provides, in order to throw errors at the right level.

In this particular case, our Lua validation was stricter than the Java,
only permitting a known set of verbs. This commit:

 - Makes the Java-side validation a little stricter, to only allow verbs
   matching [A-Z_-]{1,32}. This is strictly much stricter than what the
   HTTP spec allows, but I think makes sense as a restriction.

 - Update the Lua wrapper to match this logic, rather than being
   stricter.

Fixes #2501.
This commit is contained in:
Jonathan Coates
2026-08-28 20:36:19 +01:00
parent 3469060b6b
commit 358a6e6263
2 changed files with 30 additions and 13 deletions
@@ -103,10 +103,7 @@ public class HTTPAPI implements ILuaAPI {
if (requestMethod == null) {
httpMethod = postBody == null ? HttpMethod.GET : HttpMethod.POST;
} else {
httpMethod = HttpMethod.valueOf(requestMethod.toUpperCase(Locale.ROOT));
if (httpMethod == null || requestMethod.equalsIgnoreCase("CONNECT")) {
throw new LuaException("Unsupported HTTP method");
}
httpMethod = getMethod(requestMethod.toUpperCase(Locale.ROOT));
}
try {
@@ -174,9 +171,27 @@ public class HTTPAPI implements ILuaAPI {
}
}
private static HttpMethod getMethod(String method) throws LuaException {
// Reject CONNECT outright, as that requires the ability to read indefinitely.
if (method.equals("CONNECT")) throw new LuaException("Unsupported HTTP method");
// The HTTP specification allows the method to be any token[^1], which is all non-control/separator
// characters[^2]. However, we limit ourselves to a more reasonable subset. The allowed characters match
// what NGINX does[^3].
// [^1] https://www.rfc-editor.org/info/rfc2616/#section-5.1.1
// [^2] https://www.rfc-editor.org/info/rfc2616/#section-2.2
// [^3]: https://github.com/nginx/nginx/blob/5f54125dde0d144476155d8964f6ec3d449f578d/src/http/ngx_http_parse.c#L283-L285
if (method.isEmpty() || method.length() > 32 ||
!method.chars().allMatch(c -> (c >= 'A' && c <= 'Z') || c == '_' || c == '-')) {
throw new LuaException("Invalid HTTP method");
}
return HttpMethod.valueOf(method);
}
private HttpHeaders getHeaders(Map<?, ?> headerTable) throws LuaException {
HttpHeaders headers = new DefaultHttpHeaders();
for (Map.Entry<?, ?> entry : headerTable.entrySet()) {
for (var entry : headerTable.entrySet()) {
var value = entry.getValue();
if (entry.getKey() instanceof String && value instanceof String) {
try {
@@ -14,12 +14,6 @@ local expect = dofile("rom/modules/main/cc/expect.lua").expect
local native = http
local nativeHTTPRequest = http.request
local methods = {
GET = true, POST = true, HEAD = true,
OPTIONS = true, PUT = true, DELETE = true,
PATCH = true, TRACE = true,
}
local function check_key(options, key, ty, opt)
local value = options[key]
local valueTy = type(value)
@@ -41,8 +35,13 @@ local function check_request_options(options, body)
check_key(options, "redirect", "boolean", true)
check_key(options, "timeout", "number", true)
if options.method and not methods[options.method] then
error("Unsupported HTTP method", 3)
check_key(options, "method", "string", true)
if options.method then
local method = options.method:upper()
if method == "CONNECT" then error("Unsupported HTTP method", 3) end
if method == "" or #method > 32 or method:find("[^A-Z_-]") then
error("Invalid HTTP method", 3)
end
end
end
@@ -90,6 +89,7 @@ error or connection timeout.
@changed 1.105.0 Added support for custom timeouts.
@changed 1.109.0 The returned response now reads the body as raw bytes, rather
than decoding from UTF-8.
@changed 1.121.0 Relax HTTP verb validation to allow anything matching `[A-Z_-]{1,32}`.
@usage Make a request to [example.tweaked.cc](https://example.tweaked.cc),
and print the returned page.
@@ -144,6 +144,7 @@ error or connection timeout.
@changed 1.105.0 Added support for custom timeouts.
@changed 1.109.0 The returned response now reads the body as raw bytes, rather
than decoding from UTF-8.
@changed 1.121.0 Relax HTTP verb validation to allow anything matching `[A-Z_-]{1,32}`.
]]
function post(_url, _post, _headers, _binary)
if type(_url) == "table" then
@@ -197,6 +198,7 @@ from above are passed in as fields instead (for instance,
@changed 1.105.0 Added support for custom timeouts.
@changed 1.109.0 The returned response now reads the body as raw bytes, rather
than decoding from UTF-8.
@changed 1.121.0 Relax HTTP verb validation to allow anything matching `[A-Z_-]{1,32}`.
]]
function request(_url, _post, _headers, _binary)
local url