Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 6d0a4c7

Browse files
committed
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.
1 parent 803526b commit 6d0a4c7

3 files changed

Lines changed: 131 additions & 4 deletions

File tree

Lib/httplib.py

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,112 @@
9393
_CS_REQ_STARTED = 'Request-started'
9494
_CS_REQ_SENT = 'Request-sent'
9595

96+
class HTTPMessage(mimetools.Message):
97+
98+
def addheader(self, key, value):
99+
"""Add header for field key handling repeats."""
100+
prev = self.dict.get(key)
101+
if prev is None:
102+
self.dict[key] = value
103+
else:
104+
combined = ", ".join((prev, value))
105+
self.dict[key] = combined
106+
107+
def addcontinue(self, key, more):
108+
"""Add more field data from a continuation line."""
109+
prev = self.dict[key]
110+
self.dict[key] = prev + "\n " + more
111+
112+
def readheaders(self):
113+
"""Read header lines.
114+
115+
Read header lines up to the entirely blank line that terminates them.
116+
The (normally blank) line that ends the headers is skipped, but not
117+
included in the returned list. If a non-header line ends the headers,
118+
(which is an error), an attempt is made to backspace over it; it is
119+
never included in the returned list.
120+
121+
The variable self.status is set to the empty string if all went well,
122+
otherwise it is an error message. The variable self.headers is a
123+
completely uninterpreted list of lines contained in the header (so
124+
printing them will reproduce the header exactly as it appears in the
125+
file).
126+
127+
If multiple header fields with the same name occur, they are combined
128+
according to the rules in RFC 2616 sec 4.2:
129+
130+
Appending each subsequent field-value to the first, each separated
131+
by a comma. The order in which header fields with the same field-name
132+
are received is significant to the interpretation of the combined
133+
field value.
134+
"""
135+
# XXX The implementation overrides the readheaders() method of
136+
# rfc822.Message. The base class design isn't amenable to
137+
# customized behavior here so the method here is a copy of the
138+
# base class code with a few small changes.
139+
140+
self.dict = {}
141+
self.unixfrom = ''
142+
self.headers = list = []
143+
self.status = ''
144+
headerseen = ""
145+
firstline = 1
146+
startofline = unread = tell = None
147+
if hasattr(self.fp, 'unread'):
148+
unread = self.fp.unread
149+
elif self.seekable:
150+
tell = self.fp.tell
151+
while 1:
152+
if tell:
153+
try:
154+
startofline = tell()
155+
except IOError:
156+
startofline = tell = None
157+
self.seekable = 0
158+
line = self.fp.readline()
159+
if not line:
160+
self.status = 'EOF in headers'
161+
break
162+
# Skip unix From name time lines
163+
if firstline and line.startswith('From '):
164+
self.unixfrom = self.unixfrom + line
165+
continue
166+
firstline = 0
167+
if headerseen and line[0] in ' \t':
168+
# XXX Not sure if continuation lines are handled properly
169+
# for http and/or for repeating headers
170+
# It's a continuation line.
171+
list.append(line)
172+
x = self.dict[headerseen] + "\n " + line.strip()
173+
self.addcontinue(headerseen, line.strip())
174+
continue
175+
elif self.iscomment(line):
176+
# It's a comment. Ignore it.
177+
continue
178+
elif self.islast(line):
179+
# Note! No pushback here! The delimiter line gets eaten.
180+
break
181+
headerseen = self.isheader(line)
182+
if headerseen:
183+
# It's a legal header line, save it.
184+
list.append(line)
185+
self.addheader(headerseen, line[len(headerseen)+1:].strip())
186+
continue
187+
else:
188+
# It's not a header line; throw it back and stop here.
189+
if not self.dict:
190+
self.status = 'No headers'
191+
else:
192+
self.status = 'Non-header line where header expected'
193+
# Try to undo the read.
194+
if unread:
195+
unread(line)
196+
elif tell:
197+
self.fp.seek(startofline)
198+
else:
199+
self.status = self.status + '; bad seek'
200+
break
201+
96202

97203
class HTTPResponse:
98204

@@ -186,10 +292,10 @@ def _begin(self):
186292
if self.version == 9:
187293
self.chunked = 0
188294
self.will_close = 1
189-
self.msg = mimetools.Message(StringIO())
295+
self.msg = HTTPMessage(StringIO())
190296
return
191297

192-
self.msg = mimetools.Message(self.fp, 0)
298+
self.msg = HTTPMessage(self.fp, 0)
193299
if self.debuglevel > 0:
194300
for hdr in self.msg.headers:
195301
print "header:", hdr,

Lib/test/output/test_httplib

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ reply: 'HTTP/1.1 400.100 Not Ok\r\n'
55
BadStatusLine raised as expected
66
InvalidURL raised as expected
77
InvalidURL raised as expected
8+
reply: 'HTTP/1.1 200 OK\r\n'
9+
header: Set-Cookie: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"
10+
header: Set-Cookie: Part_Number="Rocket_Launcher_0001"; Version="1"; Path="/acme"

Lib/test/test_httplib.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ def makefile(self, mode, bufsize=None):
1515

1616
body = "HTTP/1.1 200 Ok\r\n\r\nText"
1717
sock = FakeSocket(body)
18-
resp = httplib.HTTPResponse(sock,1)
18+
resp = httplib.HTTPResponse(sock, 1)
1919
resp._begin()
2020
print resp.read()
2121
resp.close()
2222

2323
body = "HTTP/1.1 400.100 Not Ok\r\n\r\nText"
2424
sock = FakeSocket(body)
25-
resp = httplib.HTTPResponse(sock,1)
25+
resp = httplib.HTTPResponse(sock, 1)
2626
try:
2727
resp._begin()
2828
except httplib.BadStatusLine:
@@ -39,3 +39,21 @@ def makefile(self, mode, bufsize=None):
3939
print "InvalidURL raised as expected"
4040
else:
4141
print "Expect InvalidURL"
42+
43+
# test response with multiple message headers with the same field name.
44+
text = ('HTTP/1.1 200 OK\r\n'
45+
'Set-Cookie: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"\r\n'
46+
'Set-Cookie: Part_Number="Rocket_Launcher_0001"; Version="1";'
47+
' Path="/acme"\r\n'
48+
'\r\n'
49+
'No body\r\n')
50+
hdr = ('Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"'
51+
', '
52+
'Part_Number="Rocket_Launcher_0001"; Version="1"; Path="/acme"')
53+
s = FakeSocket(text)
54+
r = httplib.HTTPResponse(s, 1)
55+
r._begin()
56+
cookies = r.getheader("Set-Cookie")
57+
if cookies != hdr:
58+
raise AssertionError, "multiple headers not combined properly"
59+

0 commit comments

Comments
 (0)