From: Ben Darnell Date: Tue, 6 Mar 2012 05:38:47 +0000 (-0800) Subject: Avoid modifying the headers object passed in by the client. X-Git-Tag: v2.3.0~73 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2f35821c235a5e747b57b8b2c3d84154dbcbaea1;p=thirdparty%2Ftornado.git Avoid modifying the headers object passed in by the client. This fixes a problem in which the header object was reused across requests and behavior became inconsistent when following redirects. Closes #459. --- diff --git a/tornado/httputil.py b/tornado/httputil.py index c44b735c5..a1068224e 100644 --- a/tornado/httputil.py +++ b/tornado/httputil.py @@ -58,7 +58,14 @@ class HTTPHeaders(dict): dict.__init__(self) self._as_list = {} self._last_key = None - self.update(*args, **kwargs) + if (len(args) == 1 and len(kwargs) == 0 and + isinstance(args[0], HTTPHeaders)): + # Copy constructor + for k,v in args[0].get_all(): + self.add(k,v) + else: + # Dict-style initialization + self.update(*args, **kwargs) # new public methods diff --git a/tornado/simple_httpclient.py b/tornado/simple_httpclient.py index 755c63ae0..2fca25784 100644 --- a/tornado/simple_httpclient.py +++ b/tornado/simple_httpclient.py @@ -94,8 +94,10 @@ class SimpleAsyncHTTPClient(AsyncHTTPClient): def fetch(self, request, callback, **kwargs): if not isinstance(request, HTTPRequest): request = HTTPRequest(url=request, **kwargs) - if not isinstance(request.headers, HTTPHeaders): - request.headers = HTTPHeaders(request.headers) + # We're going to modify this (to add Host, Accept-Encoding, etc), + # so make sure we don't modify the caller's object. This is also + # where normal dicts get converted to HTTPHeaders objects. + request.headers = HTTPHeaders(request.headers) callback = stack_context.wrap(callback) self.queue.append((request, callback)) self._process_queue() diff --git a/tornado/test/simple_httpclient_test.py b/tornado/test/simple_httpclient_test.py index f2fc12d3b..bbfd57b1e 100644 --- a/tornado/test/simple_httpclient_test.py +++ b/tornado/test/simple_httpclient_test.py @@ -6,6 +6,7 @@ import logging import re import socket +from tornado.httputil import HTTPHeaders from tornado.ioloop import IOLoop from tornado.simple_httpclient import SimpleAsyncHTTPClient, _DEFAULT_CA_CERTS from tornado.test.httpclient_test import HTTPClientCommonTestCase, ChunkHandler, CountdownHandler, HelloWorldHandler @@ -179,6 +180,13 @@ class SimpleHTTPClientTestCase(AsyncHTTPTestCase, LogTrapTestCase): self.assertTrue(response.effective_url.endswith("/countdown/2")) self.assertTrue(response.headers["Location"].endswith("/countdown/1")) + def test_header_reuse(self): + # Apps may reuse a headers object if they are only passing in constant + # headers like user-agent. The header object should not be modified. + headers = HTTPHeaders({'User-Agent': 'Foo'}) + self.fetch("/hello", headers=headers) + self.assertEqual(list(headers.get_all()), [('User-Agent', 'Foo')]) + def test_303_redirect(self): response = self.fetch("/303_post", method="POST", body="blah") self.assertEqual(200, response.code)