Skip to content

No content length on DELETE and OPTIONS #27880

Description

@ronag

Looking through http-party/node-http-proxy@8a24a1e and reading through the node code. I do believe we have an unhandled case.

I believe we are missing something here: https://github.com/nodejs/node/blob/master/lib/_http_outgoing.js#L349

To ensure content-length and transfer-encoding are correct.

Am I on to something or should I drop this?

Activity

  1. addaleax commented on May 26, 2019

    @addaleax
    Member

    /cc @nodejs/http

  2. added
    httpIssues and PRs related to the http subsystem.
    on May 26, 2019
  3. awwright commented on May 26, 2019

    @awwright
    Contributor

    I don't think node-http-proxy is doing that correctly, methods do not affect the length of a request body in HTTP. (Some methods such as TRACE prohibit request bodies, however, these are considered client errors by the origin server, they are otherwise perfectly parsable messages.) According to HTTP/1.1 the message body length is dependent on:

    • The HTTP version;
    • if the message is a request or response;
    • for the response, the request method and/or response status;
    • and several headers that indicate length (Transfer-Encoding and Content-Length).

    [edit] See discussion below, it turns out Node.js has behavior inconsistent with HTTP/1.1 messaging.

  4. lpinca commented on May 27, 2019

    @lpinca
    Member

    This is done on purpose, see

  5. awwright commented on May 27, 2019

    @awwright
    Contributor

    Oh, I didn't realize Node.js did this.

    Setting per-method defaults on what transfer method to use seems like the wrong way to go about this. POST can use or prohibit any transfer method - one endpoint might prohibit a request body and chunked encoding, another might require chunked encoding.

    Setting transfer codings (or lack thereof) based on request method isn't specified by HTTP and doesn't solve the general issue.

  6. awwright commented on May 27, 2019

    @awwright
    Contributor

    I wrote a little script to determine and log how requests are being made:

    // httpd
    const http = require('http');
    http.createServer(function(req, res){
        console.log(req.url);
        res.setHeader('Content-Type', 'text/plain');
        for(var i=0; i<req.rawHeaders.length; i+=2){
            res.write('' + req.rawHeaders[i]+': '+req.rawHeaders[i+1]+'\r\n');
        }
        res.end();
        return;
    }).listen(8081, '0.0.0.0');
    var http = require('http');
    var expectedHeaders = {
      'DELETE': ['host', 'connection'],
      'GET': ['host', 'connection'],
      'HEAD': ['host', 'connection'],
      'POST': ['host', 'connection', 'transfer-encoding'],
      'PUT': ['host', 'connection', 'transfer-encoding']
    };
    var expectedMethods = Object.keys(expectedHeaders);
    var i=0;
    function next(){
        method = expectedMethods[i++];
        if(!method) return;
        console.log('');
        console.log(method);
        var req = http.request({
            method: method,
            port: 8081,
        }, function(res){ res.pipe(process.stdout); res.on('end', next); res.on('error', console.error) });
        // req.removeHeader('Content-Length');
        // req.setHeader('Transfer-Encoding', 'chunked');
        // req.setHeader('Connection', 'Transfer-Encoding');
        req.write('1');
        req.end();
        req.on('error', console.error);
    }
    next();

    I can use curl to make DELETE requests with a body.

    But to get Node.js to, I have to explicitly set req.setHeader('Transfer-Encoding', 'chunked');, otherwise it emits an invalid HTTP request, it emits a request without a Transfer-Encoding, without a Content-Length, but writes three bytes to the request body anyways. The server tries to parse this as the start of the next message, and it closes the connection.

    This seems like a Node.js bug after all.

  7. awwright commented on May 27, 2019

    @awwright
    Contributor

    This is... something. If that DELETE request with a phantom body looks like an HTTP request (e.g. req.write('PUT / HTTP/1.1\r\n\r\n');), the downstream server won't close the socket, which confirms that Node.js is sending as HTTP headers what is being accepted as message data.

    This opens the potential for a carefully crafted message to bypass limits on the forwarded HTTP message.

    On top of that, it's strange that the Node.js server is accepting this phantom request, even though the first request specified Connection: close which should cause the Node.js server to ignore additional requests.

    Is Node.js doing anything to handle the Connection: close header?

  8. lpinca commented on May 27, 2019

    @lpinca
    Member

    the downstream server won't close the socket, which confirms that Node.js is sending as HTTP headers what is being accepted as message data.

    I don't understand can you please clarify? You are writing request headers in the response body in the above example. What's wrong with this

    const assert = require('assert');
    const http = require('http');
    
    const data = 'PUT / HTTP/1.1\r\n\r\n';
    
    const server = http.createServer(function(req, res) {
      req.on('data', function(chunk) {
        assert.strictEqual(chunk, Buffer.from(data))
      });
      res.setHeader('Content-Type', 'text/plain');
      for (let i = 0; i < req.rawHeaders.length; i += 2) {
        res.write(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r\n`);
      }
      res.end();
    });
    
    server.listen(8080, function() {
      const req = http.request({ method: 'DELETE', port: 8080 }, function(res) {
        console.log(res.headers);
    
        const chunks = [];
        res.on('data', function(chunk) {
          chunks.push(chunk);
        });
    
        res.on('end', function() {
          assert.deepStrictEqual(
            Buffer.concat(chunks),
            Buffer.from('Host: localhost:8080\r\nConnection: close\r\n')
          );
        });
      });
    
      req.write(data);
      req.end();
    });
  9. ronag commented on May 27, 2019

    @ronag
    MemberAuthor

    @awwright referenced this useful document in the other issue, https://httpwg.org/specs/rfc7230.html#message.body.length

  10. awwright commented on May 27, 2019

    @awwright
    Contributor
  11. lpinca commented on May 27, 2019

    @lpinca
    Member

    No, what Node.js actually writes appears to the server to be a second request!

    Oh I get it now.

  12. ronag commented on Jul 18, 2019

    @ronag
    MemberAuthor

    @lpinca do we do anything here or close?

  13. lpinca commented on Jul 18, 2019

    @lpinca
    Member

    The issue reported by @awwright is bad and should be fixed. We can close this but only because discussion is confusing here. If we close we should open a new issue only for #27880 (comment) and #27880 (comment).

  14. ronag commented on Jul 19, 2019

    @ronag
    MemberAuthor

    @lpinca I don't actually understand any of this... @awwright would you mind extracting your comments into an easier to follow issue?

  15. lpinca commented on Jul 19, 2019

    @lpinca
    Member

    @ronag basically with a single http.request() it is possible to trigger multiple 'request' events on the server.

  16. 11 remaining items

  17. JayHelton commented on Jul 17, 2021

    @JayHelton

    Is this available for contribution? I would like to look into this.

  18. lpinca commented on Jul 20, 2021

    @lpinca
    Member
  19. 98lenvi commented on Sep 26, 2022

    @98lenvi
    Contributor

    I'm interested in working on this. I see two action points from the above conversation

    • Finish up the smaller changes in this test diff
    • Come up with a proposal on how we are going to set the headers like Content-Length, Transfer-Encoding. Based on the above conversation, I think this RFC has all the answers.
  20. brc-dd commented on Oct 31, 2023

    @brc-dd

    Fixed via nodejs/undici#2305 I believe?

  21. awwright commented on Nov 1, 2023

    @awwright
    Contributor

    If this is fixed then #34066 should pass and be merged in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    confirmed-bugIssues and PRs for confirmed bugs.help wantedIssues that need assistance from volunteers or PRs that need help to proceed.httpIssues and PRs related to the http subsystem.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions