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