summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeremy Hylton <jeremy@alum.mit.edu>2002-07-07 16:51:37 (GMT)
committerJeremy Hylton <jeremy@alum.mit.edu>2002-07-07 16:51:37 (GMT)
commit6d0a4c79cff39f45b6990b3dcb6bd653950a8014 (patch)
treef66c8c15fad61a6750e74eb39316fc38a776bef1
parent803526b9e222077c5f9391d7e7348156eb7ba7da (diff)
downloadcpython-6d0a4c79cff39f45b6990b3dcb6bd653950a8014.zip
cpython-6d0a4c79cff39f45b6990b3dcb6bd653950a8014.tar.gz
cpython-6d0a4c79cff39f45b6990b3dcb6bd653950a8014.tar.bz2
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.
-rw-r--r--Lib/httplib.py110
-rw-r--r--Lib/test/output/test_httplib3
-rw-r--r--Lib/test/test_httplib.py22
3 files changed, 131 insertions, 4 deletions
diff --git a/Lib/httplib.py b/Lib/httplib.py
index 41eb3b0..8238f1a 100644
--- a/Lib/httplib.py
+++ b/Lib/httplib.py
@@ -93,6 +93,112 @@ _CS_IDLE = 'Idle'
_CS_REQ_STARTED = 'Request-started'
_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:
@@ -186,10 +292,10 @@ class HTTPResponse:
if self.version == 9:
self.chunked = 0
self.will_close = 1
- self.msg = mimetools.Message(StringIO())
+ self.msg = HTTPMessage(StringIO())
return
- self.msg = mimetools.Message(self.fp, 0)
+ self.msg = HTTPMessage(self.fp, 0)
if self.debuglevel > 0:
for hdr in self.msg.headers:
print "header:", hdr,
diff --git a/Lib/test/output/test_httplib b/Lib/test/output/test_httplib
index 683566b..d9b3fa1 100644
--- a/Lib/test/output/test_httplib
+++ b/Lib/test/output/test_httplib
@@ -5,3 +5,6 @@ reply: 'HTTP/1.1 400.100 Not Ok\r\n'
BadStatusLine 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"
diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py
index 39b1e13..1edb062 100644
--- a/Lib/test/test_httplib.py
+++ b/Lib/test/test_httplib.py
@@ -15,14 +15,14 @@ class FakeSocket:
body = "HTTP/1.1 200 Ok\r\n\r\nText"
sock = FakeSocket(body)
-resp = httplib.HTTPResponse(sock,1)
+resp = httplib.HTTPResponse(sock, 1)
resp._begin()
print resp.read()
resp.close()
body = "HTTP/1.1 400.100 Not Ok\r\n\r\nText"
sock = FakeSocket(body)
-resp = httplib.HTTPResponse(sock,1)
+resp = httplib.HTTPResponse(sock, 1)
try:
resp._begin()
except httplib.BadStatusLine:
@@ -39,3 +39,21 @@ for hp in ("www.python.org:abc", "www.python.org:"):
print "InvalidURL raised as expected"
else:
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"
+