Index: Doc/library/urllib2.rst =================================================================== --- Doc/library/urllib2.rst (revision 65357) +++ Doc/library/urllib2.rst (working copy) @@ -318,13 +318,15 @@ .. method:: Request.add_header(key, val) - Add another header to the request. Headers are currently ignored by all - handlers except HTTP handlers, where they are added to the list of headers sent - to the server. Note that there cannot be more than one header with the same - name, and later calls will overwrite previous calls in case the *key* collides. - Currently, this is no loss of HTTP functionality, since all headers which have - meaning when used more than once have a (header-specific) way of gaining the - same functionality using only one header. + Add another header to the request. This method adds the key in .capitalize() + format, but when headers are passed to HTTP Handlers, they are internally + coverted the required .title() format. Headers are currently ignored by all + handlers except HTTP handlers, where they are added to the list of headers + sent to the server. Note that there cannot be more than one header with the + same name, and later calls will overwrite previous calls in case the *key* + collides. Currently, this is no loss of HTTP functionality, since all + headers which have meaning when used more than once have a (header-specific) + way of gaining the same functionality using only one header. .. method:: Request.add_unredirected_header(key, header) @@ -337,7 +339,7 @@ .. method:: Request.has_header(header) Return whether the instance has the named header (checks both regular and - unredirected). + unredirected and in a case-insensitive manner. .. versionadded:: 2.4 Index: Lib/urllib2.py =================================================================== --- Lib/urllib2.py (revision 65357) +++ Lib/urllib2.py (working copy) @@ -182,6 +182,38 @@ host = _cut_port_re.sub("", host, 1) return host.lower() +class CaseInsensitiveDict(dict): + def __init__(self, *args, **kwargs): + self.keystore = {} + d = dict(*args, **kwargs) + for k in d.keys(): + self.keystore[self._get_lower(k)] = k + return super(CaseInsensitiveDict, self).__init__(*args, **kwargs) + def __setitem__(self, k, v): + if hasattr(self,'keystore'): + self.keystore[self._get_lower(k)] = k + return super(CaseInsensitiveDict, self).__setitem__(k, v) + def __getitem__(self, k): + if hasattr(self,'keystore') and self._get_lower(k) in self.keystore: + k = self.keystore[self._get_lower(k)] + return super(CaseInsensitiveDict, self).__getitem__(k) + def __contains__(self, k): + if hasattr(self,'keystore') and self._get_lower(k) in self.keystore: + k = self.keystore[self._get_lower(k)] + return super(CaseInsensitiveDict, self).__contains__(k) + def get(self, k, failobj=None): + if hasattr(self,'keystore') and self._get_lower(k) in self.keystore: + k = self.keystore[self._get_lower(k)] + return super(CaseInsensitiveDict, self).get(k, failobj) + + @staticmethod + def _get_lower(k): + if isinstance(k, str): + return k.lower() + else: + return k + + class Request: def __init__(self, url, data=None, headers={}, @@ -193,10 +225,12 @@ self.host = None self.port = None self.data = data - self.headers = {} + #self.headers = {} + self.headers = CaseInsensitiveDict() for key, value in headers.items(): self.add_header(key, value) - self.unredirected_hdrs = {} + #self.unredirected_hdrs = {} + self.unredirected_hdrs = CaseInsensitiveDict() if origin_req_host is None: origin_req_host = request_host(self) self.origin_req_host = origin_req_host Index: Lib/test/test_urllib2.py =================================================================== --- Lib/test/test_urllib2.py (revision 65357) +++ Lib/test/test_urllib2.py (working copy) @@ -53,7 +53,9 @@ stay that way, because the complete set of headers are only accessible through the .get_header(), .has_header(), .header_items() interface. However, .headers pre-dates those methods, and so real code will be using - the dictionary. + the dictionary. .get_header() and .has_header() and .headers support case- + insensitive dict lookup for header keys. .header_items() returns them in + .capitalize() form. The introduction in 2.4 of those methods was a mistake for the same reason: code that previously saw all (urllib2 user)-provided headers in .headers @@ -63,7 +65,6 @@ interface and also provided access to the "unredirected" headers. It's probably too late to fix that, though. - Check .capitalize() case normalization: >>> url = "http://example.com" @@ -72,9 +73,14 @@ >>> Request(url, headers={"spam-EggS": "blah"}).headers["Spam-eggs"] 'blah' - Currently, Request(url, "Spam-eggs").headers["Spam-Eggs"] raises KeyError, - but that could be changed in future. + Check case-insensitive dict lookup. + >>> Request(url, headers={"spam-EggS": "blah"}).headers["Spam-Eggs"] + 'blah' + >>> Request(url, headers={"spam-EggS": "blah"}).headers["spam-eggs"] + 'blah' + >>> Request(url, headers={"spam-EggS": "blah"}).headers["SPAM-EGGS"] + 'blah' """ def test_request_headers_methods(): @@ -82,12 +88,19 @@ Note the case normalization of header names here, to .capitalize()-case. This should be preserved for backwards-compatibility. (In the HTTP case, normalization to .title()-case is done by urllib2 before sending headers to - httplib). + httplib). .has_header and .get_header support case-insensitive lookup to + support cases where headers are retrived (mostly) through .title() form. >>> url = "http://example.com" >>> r = Request(url, headers={"Spam-eggs": "blah"}) >>> r.has_header("Spam-eggs") True + >>> r.has_header("Spam-Eggs") + True + >>> r.has_header("spam-eggs") + True + >>> r.has_header("spaM-EggS") + True >>> r.header_items() [('Spam-eggs', 'blah')] >>> r.add_header("Foo-Bar", "baz") @@ -96,9 +109,13 @@ >>> items [('Foo-bar', 'baz'), ('Spam-eggs', 'blah')] - Note that e.g. r.has_header("spam-EggS") is currently False, and - r.get_header("spam-EggS") returns None, but that could be changed in - future. + >>> req = urllib2.Request(url) + >>> req.add_header("spam-eggs", "eggs") + >>> req.add_header("SPAM-eggs", "ham") # Later headers will be over-written + >>> req.header_items() + [('Spam-eggs', 'ham')] + >>> req.headers.items() + [('Spam-eggs', 'ham')] >>> r.has_header("Not-there") False