Commit 8852bb90 authored by Laurence Rowe's avatar Laurence Rowe

Launchpad #267834: proper separation of HTTP header fields

        using CRLF as requested by RFC 2616. (merged 90980, 92625)
parent 9cd3743d
...@@ -27,6 +27,9 @@ Zope Changes ...@@ -27,6 +27,9 @@ Zope Changes
Bugs fixed Bugs fixed
- Launchpad #267834: proper separation of HTTP header fields
using CRLF as requested by RFC 2616. (merged 90980, 92625)
- Launchpad #348223: optimize catalog query by breaking out early from - Launchpad #348223: optimize catalog query by breaking out early from
loop over indexes if the result set is already empty. loop over indexes if the result set is already empty.
......
...@@ -102,7 +102,7 @@ class TestRequestRange(unittest.TestCase): ...@@ -102,7 +102,7 @@ class TestRequestRange(unittest.TestCase):
# Chop off any printed headers (only when response.write was used) # Chop off any printed headers (only when response.write was used)
if body: if body:
body = string.split(body, '\n\n', 1)[1] body = string.split(body, '\r\n\r\n', 1)[1]
return body + rv return body + rv
......
...@@ -106,7 +106,7 @@ class Functional(sandbox.Sandboxed): ...@@ -106,7 +106,7 @@ class Functional(sandbox.Sandboxed):
class ResponseWrapper: class ResponseWrapper:
'''Decorates a response object with additional introspective methods.''' '''Decorates a response object with additional introspective methods.'''
_bodyre = re.compile('^$^\n(.*)', re.MULTILINE | re.DOTALL) _bodyre = re.compile('\r\n\r\n(.*)', re.MULTILINE | re.DOTALL)
def __init__(self, response, outstream, path): def __init__(self, response, outstream, path):
self._response = response self._response = response
......
...@@ -51,7 +51,7 @@ class MakerequestTests(unittest.TestCase): ...@@ -51,7 +51,7 @@ class MakerequestTests(unittest.TestCase):
item.REQUEST.RESPONSE.write('aaa') item.REQUEST.RESPONSE.write('aaa')
out.seek(0) out.seek(0)
written = out.read() written = out.read()
self.failUnless(written.startswith('Status: 200 OK\n')) self.failUnless(written.startswith('Status: 200 OK\r\n'))
self.failUnless(written.endswith('\naaa')) self.failUnless(written.endswith('\naaa'))
def test_environ(self): def test_environ(self):
......
...@@ -221,7 +221,7 @@ class HTTPResponse(BaseResponse): ...@@ -221,7 +221,7 @@ class HTTPResponse(BaseResponse):
# It has already been determined. # It has already been determined.
return return
if (isinstance(status, types.ClassType) if (isinstance(status, (type, types.ClassType))
and issubclass(status, Exception)): and issubclass(status, Exception)):
status = status.__name__ status = status.__name__
...@@ -246,17 +246,18 @@ class HTTPResponse(BaseResponse): ...@@ -246,17 +246,18 @@ class HTTPResponse(BaseResponse):
if lock: if lock:
self._locked_status = 1 self._locked_status = 1
def setHeader(self, name, value, literal=0): def setHeader(self, name, value, literal=0, scrubbed=False):
'''\ '''\
Sets an HTTP return header "name" with value "value", clearing Sets an HTTP return header "name" with value "value", clearing
the previous value set for the header, if one exists. If the the previous value set for the header, if one exists. If the
literal flag is true, the case of the header name is preserved, literal flag is true, the case of the header name is preserved,
otherwise the header name will be lowercased.''' otherwise the header name will be lowercased.'''
if not scrubbed:
name, value = _scrubHeader(name, value) name, value = _scrubHeader(name, value)
key = name.lower() key = name.lower()
if accumulate_header(key): if accumulate_header(key):
self.accumulated_headers = ( self.accumulated_headers = (
"%s%s: %s\n" % (self.accumulated_headers, name, value)) "%s%s: %s\r\n" % (self.accumulated_headers, name, value))
return return
name = literal and name or key name = literal and name or key
self.headers[name] = value self.headers[name] = value
...@@ -279,7 +280,7 @@ class HTTPResponse(BaseResponse): ...@@ -279,7 +280,7 @@ class HTTPResponse(BaseResponse):
any previously set headers with the same name.''' any previously set headers with the same name.'''
name, value = _scrubHeader(name, value) name, value = _scrubHeader(name, value)
self.accumulated_headers = ( self.accumulated_headers = (
"%s%s: %s\n" % (self.accumulated_headers, name, value)) "%s%s: %s\r\n" % (self.accumulated_headers, name, value))
__setitem__ = setHeader __setitem__ = setHeader
...@@ -471,21 +472,20 @@ class HTTPResponse(BaseResponse): ...@@ -471,21 +472,20 @@ class HTTPResponse(BaseResponse):
# Encode the Unicode data as requested # Encode the Unicode data as requested
if self.headers.has_key('content-type'): ct = self.headers.get('content-type')
match = charset_re.match(self.headers['content-type']) if ct:
match = charset_re.match(ct)
if match: if match:
encoding = match.group(1) encoding = match.group(1)
body = body.encode(encoding) body = body.encode(encoding)
body = fix_xml_preamble(body, encoding) body = fix_xml_preamble(body, encoding)
return body return body
else: else:
ct = self.headers['content-type']
if ct.startswith('text/') or ct.startswith('application/'): if ct.startswith('text/') or ct.startswith('application/'):
self.headers['content-type'] = '%s; charset=%s' % (ct, default_encoding) self.headers['content-type'] = '%s; charset=%s' % (ct, default_encoding)
# Use the default character encoding # Use the default character encoding
body = body.encode(default_encoding,'replace') body = body.encode(default_encoding, 'replace')
body = fix_xml_preamble(body, default_encoding) body = fix_xml_preamble(body, default_encoding)
return body return body
...@@ -593,10 +593,10 @@ class HTTPResponse(BaseResponse): ...@@ -593,10 +593,10 @@ class HTTPResponse(BaseResponse):
headers = self.headers headers = self.headers
if headers.has_key(name): if headers.has_key(name):
h = headers[name] h = headers[name]
h = "%s%s\n\t%s" % (h,delimiter,value) h = "%s%s\r\n\t%s" % (h,delimiter,value)
else: else:
h = value h = value
self.setHeader(name,h) self.setHeader(name,h, scrubbed=True)
def isHTML(self, s): def isHTML(self, s):
s = s.lstrip() s = s.lstrip()
...@@ -895,7 +895,7 @@ class HTTPResponse(BaseResponse): ...@@ -895,7 +895,7 @@ class HTTPResponse(BaseResponse):
if self.cookies: if self.cookies:
headersl = headersl+self._cookie_list() headersl = headersl+self._cookie_list()
headersl[len(headersl):] = [self.accumulated_headers, body] headersl[len(headersl):] = [self.accumulated_headers, body]
return '\n'.join(headersl) return '\r\n'.join(headersl)
def write(self,data): def write(self,data):
"""\ """\
......
...@@ -72,10 +72,10 @@ class HTTPResponseTests(unittest.TestCase): ...@@ -72,10 +72,10 @@ class HTTPResponseTests(unittest.TestCase):
response = self._makeOne() response = self._makeOne()
response.setHeader('foo', 'bar') response.setHeader('foo', 'bar')
response.appendHeader('foo', 'foo') response.appendHeader('foo', 'foo')
self.assertEqual(response.headers.get('foo'), 'bar,\n\tfoo') self.assertEqual(response.headers.get('foo'), 'bar,\r\n\tfoo')
response.setHeader('xxx', 'bar') response.setHeader('xxx', 'bar')
response.appendHeader('XXX', 'foo') response.appendHeader('XXX', 'foo')
self.assertEqual(response.headers.get('xxx'), 'bar,\n\tfoo') self.assertEqual(response.headers.get('xxx'), 'bar,\r\n\tfoo')
def test_setHeader(self): def test_setHeader(self):
response = self._makeOne() response = self._makeOne()
...@@ -151,7 +151,7 @@ class HTTPResponseTests(unittest.TestCase): ...@@ -151,7 +151,7 @@ class HTTPResponseTests(unittest.TestCase):
response.addHeader('Location', response.addHeader('Location',
'http://www.ietf.org/rfc/\r\nrfc2616.txt') 'http://www.ietf.org/rfc/\r\nrfc2616.txt')
self.assertEqual(response.accumulated_headers, self.assertEqual(response.accumulated_headers,
'Location: http://www.ietf.org/rfc/rfc2616.txt\n') 'Location: http://www.ietf.org/rfc/rfc2616.txt\r\n')
def test_appendHeader_drops_CRLF(self): def test_appendHeader_drops_CRLF(self):
# RFC2616 disallows CRLF in a header value. # RFC2616 disallows CRLF in a header value.
...@@ -176,8 +176,9 @@ class HTTPResponseTests(unittest.TestCase): ...@@ -176,8 +176,9 @@ class HTTPResponseTests(unittest.TestCase):
response.setHeader('Set-Cookie', response.setHeader('Set-Cookie',
'violation="http://www.ietf.org/rfc/\r\nrfc2616.txt"') 'violation="http://www.ietf.org/rfc/\r\nrfc2616.txt"')
self.assertEqual(response.accumulated_headers, self.assertEqual(response.accumulated_headers,
'Set-Cookie: allowed="OK"\n' + 'Set-Cookie: allowed="OK"\r\n' +
'Set-Cookie: violation="http://www.ietf.org/rfc/rfc2616.txt"\n') 'Set-Cookie: '
'violation="http://www.ietf.org/rfc/rfc2616.txt"\r\n')
def test_suite(): def test_suite():
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment