Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unprocessable Entity status code when decoding empty Request bodies #3032

Open
vzsg opened this issue Jun 27, 2023 · 2 comments
Open

Unprocessable Entity status code when decoding empty Request bodies #3032

vzsg opened this issue Jun 27, 2023 · 2 comments
Labels
bug Something isn't working

Comments

@vzsg
Copy link
Member

vzsg commented Jun 27, 2023

Describe the bug

Given a POST route handler that expects an URL-encoded form body, when a client sends an empty request body, calling Request.content.decode(...) causes a 422 Unprocessable Entity abort to be thrown.

This is an issue, because browsers typically omit empty values when submitting a HTML form without JavaScript being involved. If every input is on their default/empty value, this means the request body could become an empty string.

In other words, a form body with Content-Length: 0 is valid, but throws an obscure error now.

To Reproduce

  1. Start a Vapor project, e.g. by cloning template-bare.

  2. Add the following two route handlers:

    app.get("form") { req -> Response in
        let body = """
        <html>
        <head></head>
        <body>
          <form method="POST">
            <select name="cars[]" id="cars" multiple>
              <option value="volvo">Volvo</option>
              <option value="saab">Saab</option>
              <option value="opel">Opel</option>
              <option value="audi">Audi</option>
            </select>
            <input type="submit"/>
          </form>
        </body>
        </html>
        """
    
        let res = Response(body: Response.Body(string: body))
        res.headers.contentType = .html
        return res
    }
    
    app.post("form") { req -> String in
        struct FormData: Content {
            var cars: [String]?
        }
    
        let data = try req.content.decode(FormData.self)
        return "Received cars: '\(data.cars?.joined(separator: ", ") ?? "NULL")'"
    }
  3. Start the application and navigate to http://localhost:8080/form.

  4. Select one or more car makes, press Submit.

  5. Observe that the response contains Received cars: '(comma-separated strings)'.

  6. Navigate back to the form and reload the page to clear the selection.

  7. Now, without selecting any of the makes, press Submit.

  8. Observe a 422 error is returned.

Expected behavior

The decode call should succeed, return a FormData struct with a nil array inside, and ultimately the route should return the string Received cars: 'NULL'.

Environment

  • Vapor Framework version: 4.77.0
  • Vapor Toolbox version: irrelevant
  • OS version: macOS Ventura 13.4.1 (but also irrelevant)
  • Browser: Chrome 114

Additional context

I did some debugging, originally thinking that the issue is somewhere inside URLEncodedFormDecoder. That was ultimately a bust, execution does not reach the decoder class at all! Instead, the error is thrown from here. In case of Content-Length: 0, the bodyStorage is none, which triggers this check.

Workaround

Adding a hidden input with an arbitrary (ignored) value will cause the browser to always include this key-value pair in the body, so it is no longer empty even if the user-facing inputs are, avoiding the 422 error.

<input type="hidden" name="_foo" value="foo"/>

Hidden inputs are pretty common for CSRF and other purposes, which might be why this issue went unnoticed for so long.

@vzsg vzsg added the bug Something isn't working label Jun 27, 2023
@hsharghi
Copy link
Contributor

I think this a same issue I’ve faced a while back.
#2606
But it’s closed.
I was thinking if a body contents is nil, can it be replaced with an empty body contents, that validations can be run, so the correct validation error thrown?

@vzsg
Copy link
Member Author

vzsg commented Jun 30, 2023

That indeed looks like the same issue, as validations also go through Content (Codable).
I'm more and more inclined to think that the empty body check is just wrong.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants