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.
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_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 @@
         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 @@
 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 @@
 
 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 @@
         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"
+