From 358a6e6263decab39856e6ad7f2ff5cdbf5e5ac2 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Fri, 28 Aug 2026 20:32:13 +0100 Subject: [PATCH] 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. --- .../computercraft/core/apis/HTTPAPI.java | 25 +++++++++++++++---- .../computercraft/lua/rom/apis/http/http.lua | 18 +++++++------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/projects/core/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java b/projects/core/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java index 3cce0fafa..1741c0a3c 100644 --- a/projects/core/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java +++ b/projects/core/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java @@ -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 { diff --git a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/http/http.lua b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/http/http.lua index bc7f209e5..5bce5f4b0 100644 --- a/projects/core/src/main/resources/data/computercraft/lua/rom/apis/http/http.lua +++ b/projects/core/src/main/resources/data/computercraft/lua/rom/apis/http/http.lua @@ -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