diff options
-rw-r--r-- | Lib/httplib.py | 110 | ||||
-rw-r--r-- | Lib/test/output/test_httplib | 3 | ||||
-rw-r--r-- | Lib/test/test_httplib.py | 22 |
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" + |