framework: HTTP header field name case-insensitivity is not being respected which leads to improper Session handling.

Overview

Laravel is incorrectly handling HTTP request header field names that are lowercase, which causes the Session handler to improperly issue a new session cookie on every request.

I encountered this bug when working with a NodeJS app configured to proxy requests to Laravel. The HTTP spec is pretty clear that header field names are case-insensitive:

4.2 Message Headers

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

Reproducing the Bug

Here’s the breakdown of how I tested this and my configuration that lead me to believe the problem exists in the way Laravel parses request headers:

  1. I have laravel configured to run php artisan serve --host=127.0.0.1 --port=3000
  2. I have node running on port 8000, using the npm module http-proxy to forward requests to port 3000

To rule out node and http-proxy as a potential source of problems, I first used netcat to listen on port 3000 (nc -l 3000) and capture requests that http-proxy was sending, here’s a dump of what http-proxy is forwarding to Laravel (note the lower case header field-names):

davidmosher@localhost:~/code/temp/laravel 
$ nc -l 3000
GET /auth/csrf_token HTTP/1.1
host: localhost:8000
connection: keep-alive
cache-control: no-cache
pragma: no-cache
accept: application/json, text/javascript
x-requested-with: XMLHttpRequest
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36
content-type: application/x-www-form-urlencoded
referer: http://localhost:8000/
accept-encoding: gzip,deflate,sdch
accept-language: en-US,en;q=0.8
cookie: laravel_session=doh0284ujk57ebnldlm2plp795
x-forwarded-for: 127.0.0.1
x-forwarded-port: 53245
x-forwarded-proto: http

When I use telnet 127.0.0.1 3000 in another terminal session I paste the above request that has lowercase header field-names in and receive the following response from Laravel:

HTTP/1.1 200 OK
Connection: close
X-Powered-By: PHP/5.4.14
Set-Cookie: laravel_session=kicg6iu0aobufkl036itar6km6; expires=Thu, 13-Jun-2013 18:48:12 GMT; path=/; HttpOnly
Set-Cookie: laravel_session=kicg6iu0aobufkl036itar6km6; expires=Thu, 13-Jun-2013 18:48:12 GMT; path=/; httponly
Cache-Control: no-cache
Date: Thu, 13 Jun 2013 16:48:12 GMT
Content-Type: application/json

Note the double Set-Cookie and the laravel_session has a different value than what was sent in the request. If I modify the header keys in my request to uppercase all the words like so:

GET /auth/csrf_token HTTP/1.1
Host: localhost:8000
Connection: keep-alive
Cache-Control: no-cache
Pragma: no-cache
Accept: application/json, text/javascript
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://localhost:8000/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Cookie: laravel_session=doh0284ujk57ebnldlm2plp795
X-Forwarded-For: 127.0.0.1
X-Forwarded-Port: 53245
X-Forwarded-Proto: http

Then I get a correct response from Laravel, as follows:

HTTP/1.1 200 OK
Host: localhost:8000
Connection: close
X-Powered-By: PHP/5.4.14
Set-Cookie: laravel_session=doh0284ujk57ebnldlm2plp795; expires=Thu, 13-Jun-2013 18:49:40 GMT; path=/; httponly
Cache-Control: no-cache
Date: Thu, 13 Jun 2013 16:49:40 GMT
Content-Type: application/json

Conclusion

Having ruled out my proxy completely, using netcat and telnet I can only conclude that Laravel is treating http request header field names differently depending on the casing; this causes major problems for anyone setting up a proxy that may use lower case request header field names to forward requests to laravel and expects Session management to work properly.

Is this something that should be fixed by Laravel or at a lower level in Symfony?

About this issue

  • Original URL
  • State: closed
  • Created 11 years ago
  • Reactions: 1
  • Comments: 35 (20 by maintainers)

Most upvoted comments

FWIW, I dug into Symfony symfony/http-foundation/Symfony/Component/HttpFoundation/HeaderBag.php and the code there seems to normalize header keys with strtolower, so the problem must exist at another layer.

/**
     * Returns a header value by name.
     *
     * @param string  $key     The header name
     * @param mixed   $default The default value
     * @param Boolean $first   Whether to return the first value or all header values
     *
     * @return string|array The first header value if $first is true, an array of values otherwise
     *
     * @api
     */
    public function get($key, $default = null, $first = true)
    {
        $key = strtr(strtolower($key), '_', '-');

        if (!array_key_exists($key, $this->headers)) {
            if (null === $default) {
                return $first ? null : array();
            }

            return $first ? $default : array($default);
        }

        if ($first) {
            return count($this->headers[$key]) ? $this->headers[$key][0] : $default;
        }

        return $this->headers[$key];
    }