mirror of
https://github.com/python/cpython.git
synced 2025-09-27 18:59:43 +00:00
Fix for SF bug #432621: httplib: multiple Set-Cookie headers
If multiple header fields with the same name occur, they are combined according to the rules in RFC 2616 sec 4.2: 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 significant to the interpretation of the combined field value.
This commit is contained in:
parent
803526b9e2
commit
6d0a4c79cf
3 changed files with 131 additions and 4 deletions
110
Lib/httplib.py
110
Lib/httplib.py
|
@ -93,6 +93,112 @@ _CS_IDLE = 'Idle'
|
||||||
_CS_REQ_STARTED = 'Request-started'
|
_CS_REQ_STARTED = 'Request-started'
|
||||||
_CS_REQ_SENT = 'Request-sent'
|
_CS_REQ_SENT = 'Request-sent'
|
||||||
|
|
||||||
|
class HTTPMessage(mimetools.Message):
|
||||||
|
|
||||||
|
def addheader(self, key, value):
|
||||||
|
"""Add header for field key handling repeats."""
|
||||||
|
prev = self.dict.get(key)
|
||||||
|
if prev is None:
|
||||||
|
self.dict[key] = value
|
||||||
|
else:
|
||||||
|
combined = ", ".join((prev, value))
|
||||||
|
self.dict[key] = combined
|
||||||
|
|
||||||
|
def addcontinue(self, key, more):
|
||||||
|
"""Add more field data from a continuation line."""
|
||||||
|
prev = self.dict[key]
|
||||||
|
self.dict[key] = prev + "\n " + more
|
||||||
|
|
||||||
|
def readheaders(self):
|
||||||
|
"""Read header lines.
|
||||||
|
|
||||||
|
Read header lines up to the entirely blank line that terminates them.
|
||||||
|
The (normally blank) line that ends the headers is skipped, but not
|
||||||
|
included in the returned list. If a non-header line ends the headers,
|
||||||
|
(which is an error), an attempt is made to backspace over it; it is
|
||||||
|
never included in the returned list.
|
||||||
|
|
||||||
|
The variable self.status is set to the empty string if all went well,
|
||||||
|
otherwise it is an error message. The variable self.headers is a
|
||||||
|
completely uninterpreted list of lines contained in the header (so
|
||||||
|
printing them will reproduce the header exactly as it appears in the
|
||||||
|
file).
|
||||||
|
|
||||||
|
If multiple header fields with the same name occur, they are combined
|
||||||
|
according to the rules in RFC 2616 sec 4.2:
|
||||||
|
|
||||||
|
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 significant to the interpretation of the combined
|
||||||
|
field value.
|
||||||
|
"""
|
||||||
|
# XXX The implementation overrides the readheaders() method of
|
||||||
|
# rfc822.Message. The base class design isn't amenable to
|
||||||
|
# customized behavior here so the method here is a copy of the
|
||||||
|
# base class code with a few small changes.
|
||||||
|
|
||||||
|
self.dict = {}
|
||||||
|
self.unixfrom = ''
|
||||||
|
self.headers = list = []
|
||||||
|
self.status = ''
|
||||||
|
headerseen = ""
|
||||||
|
firstline = 1
|
||||||
|
startofline = unread = tell = None
|
||||||
|
if hasattr(self.fp, 'unread'):
|
||||||
|
unread = self.fp.unread
|
||||||
|
elif self.seekable:
|
||||||
|
tell = self.fp.tell
|
||||||
|
while 1:
|
||||||
|
if tell:
|
||||||
|
try:
|
||||||
|
startofline = tell()
|
||||||
|
except IOError:
|
||||||
|
startofline = tell = None
|
||||||
|
self.seekable = 0
|
||||||
|
line = self.fp.readline()
|
||||||
|
if not line:
|
||||||
|
self.status = 'EOF in headers'
|
||||||
|
break
|
||||||
|
# Skip unix From name time lines
|
||||||
|
if firstline and line.startswith('From '):
|
||||||
|
self.unixfrom = self.unixfrom + line
|
||||||
|
continue
|
||||||
|
firstline = 0
|
||||||
|
if headerseen and line[0] in ' \t':
|
||||||
|
# XXX Not sure if continuation lines are handled properly
|
||||||
|
# for http and/or for repeating headers
|
||||||
|
# It's a continuation line.
|
||||||
|
list.append(line)
|
||||||
|
x = self.dict[headerseen] + "\n " + line.strip()
|
||||||
|
self.addcontinue(headerseen, line.strip())
|
||||||
|
continue
|
||||||
|
elif self.iscomment(line):
|
||||||
|
# It's a comment. Ignore it.
|
||||||
|
continue
|
||||||
|
elif self.islast(line):
|
||||||
|
# Note! No pushback here! The delimiter line gets eaten.
|
||||||
|
break
|
||||||
|
headerseen = self.isheader(line)
|
||||||
|
if headerseen:
|
||||||
|
# It's a legal header line, save it.
|
||||||
|
list.append(line)
|
||||||
|
self.addheader(headerseen, line[len(headerseen)+1:].strip())
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# It's not a header line; throw it back and stop here.
|
||||||
|
if not self.dict:
|
||||||
|
self.status = 'No headers'
|
||||||
|
else:
|
||||||
|
self.status = 'Non-header line where header expected'
|
||||||
|
# Try to undo the read.
|
||||||
|
if unread:
|
||||||
|
unread(line)
|
||||||
|
elif tell:
|
||||||
|
self.fp.seek(startofline)
|
||||||
|
else:
|
||||||
|
self.status = self.status + '; bad seek'
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
class HTTPResponse:
|
class HTTPResponse:
|
||||||
|
|
||||||
|
@ -186,10 +292,10 @@ class HTTPResponse:
|
||||||
if self.version == 9:
|
if self.version == 9:
|
||||||
self.chunked = 0
|
self.chunked = 0
|
||||||
self.will_close = 1
|
self.will_close = 1
|
||||||
self.msg = mimetools.Message(StringIO())
|
self.msg = HTTPMessage(StringIO())
|
||||||
return
|
return
|
||||||
|
|
||||||
self.msg = mimetools.Message(self.fp, 0)
|
self.msg = HTTPMessage(self.fp, 0)
|
||||||
if self.debuglevel > 0:
|
if self.debuglevel > 0:
|
||||||
for hdr in self.msg.headers:
|
for hdr in self.msg.headers:
|
||||||
print "header:", hdr,
|
print "header:", hdr,
|
||||||
|
|
|
@ -5,3 +5,6 @@ reply: 'HTTP/1.1 400.100 Not Ok\r\n'
|
||||||
BadStatusLine raised as expected
|
BadStatusLine raised as expected
|
||||||
InvalidURL raised as expected
|
InvalidURL raised as expected
|
||||||
InvalidURL raised as expected
|
InvalidURL raised as expected
|
||||||
|
reply: 'HTTP/1.1 200 OK\r\n'
|
||||||
|
header: Set-Cookie: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"
|
||||||
|
header: Set-Cookie: Part_Number="Rocket_Launcher_0001"; Version="1"; Path="/acme"
|
||||||
|
|
|
@ -15,14 +15,14 @@ class FakeSocket:
|
||||||
|
|
||||||
body = "HTTP/1.1 200 Ok\r\n\r\nText"
|
body = "HTTP/1.1 200 Ok\r\n\r\nText"
|
||||||
sock = FakeSocket(body)
|
sock = FakeSocket(body)
|
||||||
resp = httplib.HTTPResponse(sock,1)
|
resp = httplib.HTTPResponse(sock, 1)
|
||||||
resp._begin()
|
resp._begin()
|
||||||
print resp.read()
|
print resp.read()
|
||||||
resp.close()
|
resp.close()
|
||||||
|
|
||||||
body = "HTTP/1.1 400.100 Not Ok\r\n\r\nText"
|
body = "HTTP/1.1 400.100 Not Ok\r\n\r\nText"
|
||||||
sock = FakeSocket(body)
|
sock = FakeSocket(body)
|
||||||
resp = httplib.HTTPResponse(sock,1)
|
resp = httplib.HTTPResponse(sock, 1)
|
||||||
try:
|
try:
|
||||||
resp._begin()
|
resp._begin()
|
||||||
except httplib.BadStatusLine:
|
except httplib.BadStatusLine:
|
||||||
|
@ -39,3 +39,21 @@ for hp in ("www.python.org:abc", "www.python.org:"):
|
||||||
print "InvalidURL raised as expected"
|
print "InvalidURL raised as expected"
|
||||||
else:
|
else:
|
||||||
print "Expect InvalidURL"
|
print "Expect InvalidURL"
|
||||||
|
|
||||||
|
# test response with multiple message headers with the same field name.
|
||||||
|
text = ('HTTP/1.1 200 OK\r\n'
|
||||||
|
'Set-Cookie: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"\r\n'
|
||||||
|
'Set-Cookie: Part_Number="Rocket_Launcher_0001"; Version="1";'
|
||||||
|
' Path="/acme"\r\n'
|
||||||
|
'\r\n'
|
||||||
|
'No body\r\n')
|
||||||
|
hdr = ('Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"'
|
||||||
|
', '
|
||||||
|
'Part_Number="Rocket_Launcher_0001"; Version="1"; Path="/acme"')
|
||||||
|
s = FakeSocket(text)
|
||||||
|
r = httplib.HTTPResponse(s, 1)
|
||||||
|
r._begin()
|
||||||
|
cookies = r.getheader("Set-Cookie")
|
||||||
|
if cookies != hdr:
|
||||||
|
raise AssertionError, "multiple headers not combined properly"
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue