Skip to content

multiple headers of the "same name" allowed in http #3591

Description

@danielb2

If { 'cookie: 'foo=bar;', 'Cookie: 'foo=meh;' } is passed, you have no idea which one will be used.

If a request relies on, for example, cookie information, a request may work seemingly at random and can be rather difficult to debug.

Activity

  1. added
    httpIssues and PRs related to the http subsystem.
    on Oct 29, 2015
  2. bnoordhuis commented on Oct 29, 2015

    @bnoordhuis
    Member

    It should be the last one. Headers are added in object insertion order, the last one wins out.

    The behavior itself is most likely undocumented but I think we have tests for this (not easy to grep for though.)

  3. danielb2 commented on Oct 30, 2015

    @danielb2
    Author

    Should it allow multiple by the same at all? Is it too costly to check for this?

  4. bricss commented on Oct 31, 2015

    @bricss
    Contributor

    FYI -> http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2

    Multiple message-header fields with the same field-name MAY be present in a message if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)]. It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma. The order in which header fields with the same field-name are received is therefore significant to the interpretation of the combined field value, and thus a proxy MUST NOT change the order of these field values when a message is forwarded.

  5. jasnell commented on Oct 31, 2015

    @jasnell
    Member

    /cc @nodejs/http

  6. dougwilson commented on Oct 31, 2015

    @dougwilson
    Member

    Potentially there are a few solutions here:

    1. Only use the first key when there are duplicate keys once the keys have been converted to lower-case.

    Basically, this solution would be to say that since HTTP headers are not case-sensitive, if { cookie: 'foo=bar;', Cookie: 'foo=meh;' } is given, act on it as if the user had really written { cookie: 'foo=bar;', cookie: 'foo=meh;' }, which in JavaScript would have been converted to { cookie: 'foo=meh;' }:

    $ node -pe "({ 'cookie': 'foo=bar;', 'cookie': 'foo=meh;' })"
    { cookie: 'foo=meh;' }
    
    1. When given an object to set headers from (i.e. res.writeHead(status, obj)), if the obj contains duplicate keys once the keys have been converted to lower case, throw a TypeError.

    This would signal to users that there is an issue in the given object, which was expected to only contains header-value pairs.

  7. danielb2 commented on Nov 2, 2015

    @danielb2
    Author

    Option two sounds more appealing since if you can comma separated list syntax to define multiple headers otherwise.

    Another option to consider is allowing an optional array syntax for that:

    { cookie: ['foo=bar', 'foo=meh'] }

    The problem I saw in production was where one library was using cookie and one external source used Cookie and the headers were merged and one was lost.

  8. dougwilson commented on Nov 2, 2015

    @dougwilson
    Member

    Another option to consider is allowing an optional array syntax for that:

    { cookie: ['foo=bar', 'foo=meh'] }

    I didn't mention it, because that is already supported; each element in the array will be a different header, making that response look like the following:

    Cookie: foo=bar
    Cookie: foo=meh
    

    Assuming we are talking about res.writeHead. If you are referring to a different Node.js API, let me know, as I don't see what API is being used actually mentioned here.

  9. danielb2 commented on Nov 2, 2015

    @danielb2
    Author

    Perfect :)

  10. bricss commented on Nov 2, 2015

    @bricss
    Contributor

    In that case I presume we can close an issue.

  11. danielb2 commented on Nov 2, 2015

    @danielb2
    Author

    Why would you make that presumption?

  12. bricss commented on Nov 2, 2015

    @bricss
    Contributor

    FYI -> http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2

    Each header field consists of a name followed by a colon (":") and the field value. Field names are case-insensitive.

  13. cressie176 commented on Jan 10, 2016

    @cressie176

    Having a similar problem with X-Robots-Tag. I need to support the following

    X-Robots-Tag: googlebot: nofollow
    X-Robots-Tag: otherbot: noindex, nofollow
    

    As specified here.

  14. Trott commented on Jan 10, 2016

    @Trott
    Member

    @cressie176 According to the documentation:

    Use an array of strings here if you need to send multiple headers with the same name.

    So I think this would work for your case:

    response.setHeader('X-Robots-Tag', ['googlebot: nofollow', 'otherbot: noindex, nofollow']);
    
  15. cressie176 commented on Jan 10, 2016

    @cressie176

    @Trott, thanks for the quick response. Unfortunately I've tried that - it generates a single header which I don't think conforms to googles spec.

    x-robots-tag: 'googlebot: nofollow, otherbot: noindex, nofollow',
    

    Furthermore if I add an additional directive to apply to all user agents, it gets concatenated with the last one in the list, i.e.

    response.setHeader('X-Robots-Tag', ['googlebot: nofollow', 'otherbot: noindex, nofollow', 'noimageindex']);
    

    Results in

    x-robots-tag: 'googlebot: nofollow, otherbot: noindex, nofollow, noimageindex'
    
  16. 47 remaining items

  17. reopened this on Mar 8, 2021
  18. ljharb commented on Mar 8, 2021

    @ljharb
    SponsorMember

    It seems like the solution here is to either allow multiple "same name" headers, or, fail loudly when the user tries to send them.

    If anyone is interested in solving this, the two things that would likely be helpful are:

    1. detailing the exact desired solution in a comment here (if different from my summary, or if adding important missing details)
    2. sending a PR implementing one or all of those solutions.
  19. cressie176 commented on Mar 8, 2021

    @cressie176

    It seems like the solution here is to either allow multiple "same name" headers, or, fail loudly when the user tries to send them.

    Failing loudly would be a breaking change, and also isn't really a solution since sending multiple headers with the same name is supported by the HTTP specification and required by certain clients. Failing loudly in response to a valid action could also result in someone posting another issue, and we'd be back to square one. Therefore I suggest the only actual solution is to allow multiple headers with the same name.

  20. danielb2 commented on Mar 8, 2021

    @danielb2
    Author

    Therefore I suggest the only actual solution is to allow multiple headers with the same name.

    I don't agree with continuing doing something bad simply because fixing it would break things.

    Can we start with a warning "depreciated" message ? Deprecated: Multiple header names are being passed <stack trace>. This will be deprecated as of version X

  21. cressie176 commented on Mar 8, 2021

    @cressie176

    @danielb2

    Maybe I've misunderstood something, or not explained myself clearly enough.

    As I understand things, what node does now is bad, because instead of honouring multiple headers with the same name (which is allowed in the HTTP specification and necessary in some circumstance) it squashes them.

    Changing this to instead of honouring multiple headers with the same name, node reports an error, is still bad and arguably even worse.

  22. danielb2 commented on Mar 9, 2021

    @danielb2
    Author

    You mean it's allowed because it's not expressly prohibited? Under what circumstances would it be necessary? (Aside from a mistake)

  23. bricss commented on Mar 9, 2021

    @bricss
    Contributor

    In current situation 💡, it would make sense to add Headers into Request and Response and gently deprecate ⛔ old methods, or even leave them as is for backward compatibility with few notes 📝

  24. cressie176 commented on Mar 9, 2021

    @cressie176

    You mean it's allowed because it's not expressly prohibited?

    @danielb2 The title of this issue is somewhat confusing. It talks about multiple headers of the "same name", but does not differentiate between requests and responses. From my understanding there are different problems with Node's implementation of both, however I am only talking about responses. Section 4.2 of the HTTP specification explicitly allows multiple headers with the same name to be sent, providing their values can be combined into a comma separated list.

    Multiple message-header fields with the same field-name MAY be present in a message if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)]. It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma. The order in which header fields with the same field-name are received is therefore significant to the interpretation of the combined field value, and thus a proxy MUST NOT change the order of these field values when a message is forwarded.

    Node does not currently support adding multiple headers to a response. The closest you can get is to call response.setHeader with an array, which will still result a single header. Therefore Node is not fully compatible with the HTTP specification.

    Under what circumstances would it be necessary? (Aside from a mistake)

    Google's X-Robots-Tag specification depends on being able to send multiple X-Robots-Tag headers on the response (in a violation of the HTTP specification since they cannot be combined). For example...

    X-Robots-Tag: googlebot: noindex
    X-Robots-Tag: bingbot: noindex, nofollow
    X-Robots-Tag: noimageindex
    

    The above headers:

    1. Instruct googlebot not to index the page, but allow it to follow links to other pages
    2. Instruct bingbot not to index the page or follow links to other pages
    3. Instruct all bots not to index images

    Combining this into a single header would result in:

    X-Robots-Tag: googlebot: noindex, bingbot: noindex, nofollow, noimageindex
    

    The X-Robots-Tag specification is silent of whether this syntax would even work, but at best the noimageindex directive would only apply to bingbot rather than all robots, thus changing the semantic meaning of the X-Robots-Tag header.

    Others have cited the following examples where the need to be able to send multiple headers with the same name as part of the HTTP response.

    It is not unreasonable to suspect there may be more examples which have not yet been discovered or reported.

  25. cressie176 commented on Mar 9, 2021

    @cressie176

    In current situation 💡, it would make sense to add Headers into Request and Response and gently deprecate ⛔ old methods, or even leave them as is for backward compatibility with few notes 📝

    Unfortunately this still wouldn't work where multiple response headers with the same name are necessary since according to the append will combine values, rather than writing multiple response headers, which is necessary in some situations.

    The difference between set() and append() is that if the specified header already exists and accepts multiple values, set() will overwrite the existing value with the new one, whereas append() will append the new value onto the end of the set of values.

  26. bricss commented on Mar 9, 2021

    @bricss
    Contributor

    @cressie176 what about semicolon or newline delimiter '\n' as an option to set multiple values for the same header? 🤔

  27. cressie176 commented on Mar 10, 2021

    @cressie176

    @cressie176 what about semicolon or newline delimiter '\n' as an option to set multiple values for the same header? 🤔

    Hi @bricss, I don't see anything in the HTTP specification about semicolons or new line delimiters having special meaning. I assume that they would be treated as part of the field value, rather than indicating multiple headers.

  28. PeterDaveHello commented on Mar 11, 2021

    @PeterDaveHello
    Member

    It seems like the solution here is to either allow multiple "same name" headers, or, fail loudly when the user tries to send them.

    If anyone is interested in solving this, the two things that would likely be helpful are:

    1. detailing the exact desired solution in a comment here (if different from my summary, or if adding important missing details)
    
    2. sending a PR implementing one or all of those solutions.
    

    @ljharb what do you think about this PR: #6865 😅 I know it's old

  29. rajeevnaikte commented on Jan 21, 2022

    @rajeevnaikte

    Construct header using req.rawHeaders

    // Server code
    http.createServer(function (req, res) {
          const rqHeaders = req.rawHeaders.reduce((map, header, i, rawHeaders) =>
          {
            if (i % 2 === 0) return map;
    
            const headerName = rawHeaders[i-1];
            const headerValue = rawHeaders[i];
            const prevHeaderValue = map[headerName];
            if (prevHeaderValue)
            {
              if (Array.isArray(prevHeaderValue)) prevHeaderValue.push(headerValue);
              else map[headerName] = [prevHeaderValue, headerValue];
            }
            else map[headerName] = headerValue;
            return map;
          }, {} as any);
    
          console.log(rqHeaders); // This prints - { 'X-Robots-Tag': ['googlebot: noindex', 'bingbot: noindex, nofollow', 'noimageindex'] }
    })
      .listen(0);
    
    
    // Client code
    const rq = http.request({ // options }, res =>
    {
          rq.setHeader('X-Robots-Tag', ['googlebot: noindex', 'bingbot: noindex, nofollow', 'noimageindex']);
    
          rq.write('Hello World!');
          rq.end();
    });
    
  30. ShogunPanda commented on May 23, 2022

    @ShogunPanda
    Contributor

    Relative PR has just landed in master. Closing this.

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

    httpIssues and PRs related to the http subsystem.stalledIssues and PRs manually marked as stalled and scheduled for automatic closure.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      Sponsor
      SponsoredKunjungi sekarang
      Promo