New submission from E. M. P. Höller <paul.hoel...@web.de>:

urllib.request.Request internally .capitalize()s header names before adding 
them, as can be seen here:  
https://github.com/python/cpython/blob/3.9/Lib/urllib/request.py#L399

Since HTTP headers are case-insensitive, but dicts are not, this ensures that 
add_header and add_unredirected_header overwrite an existing header (as 
documented) even if they were passed in different cases.  However, this also 
carries two problems with it:

1. has_header, get_header, and remove_header do not apply this normalisation to 
their header_name parameter, causing them to fail unexpectedly when the header 
is passed in the wrong case.
2. Some servers do not comply with the standard and check some headers 
case-sensitively.  If the case they expect is different from the result of 
.capitalize(), those headers effectively cannot be passed to them via urllib.

These problems have already been discussed quite some time ago, and yet they 
still are present:
https://bugs.python.org/issue2275
https://bugs.python.org/issue12455
Or did I overlook something and there is a good reason why things are this way?

If not, I suggest that add_header and add_unredirected_header store the headers 
in the case they were passed (while preserving the case-insensitive overwriting 
behaviour) and that has_header, get_header, and remove_header find headers 
independent of case.

Here is a possible implementation:

# Helper outside class

# Stops after the first hit since there should be at most one of each header in 
the dict
def _find_key_insensitive(d, key):
  key = key.lower()
  for key2 in d:
    if key2.lower() == key:
      return key2
  return None # Unnecessary, but explicit is better than implicit ;-)

# Methods of Request

def add_header(self, key, val):
  # useful for something like authentication
  existing_key = _find_key_insensitive(self.headers, key)
  if existing_key:
    self.headers.pop(existing_key)
  self.headers[key] = val

def add_unredirected_header(self, key, val):
  # will not be added to a redirected request
  existing_key = _find_key_insensitive(self.unredirected_hdrs, key)
  if existing_key:
    self.unredirected_hdrs.pop(existing_key)
  self.unredirected_hdrs[key] = val

def has_header(self, header_name):
  return bool(_find_key_insensitive(self.headers, header_name) or
      _find_key_insensitive(self.unredirected_hdrs, header_name))

def get_header(self, header_name, default=None):
  key = _find_key_insensitive(self.headers, header_name)
  if key:
    return self.headers[key]
  key = _find_key_insensitive(self.unredirected_hdrs, header_name)
  if key:
    return self.unredirected_hdrs[key]
  return default

def remove_header(self, header_name):
  key = _find_key_insensitive(self.headers, header_name)
  if key:
    self.headers.pop(key)
  key = _find_key_insensitive(self.unredirected_hdrs, header_name)
  if key:
    self.unredirected_hdrs.pop(key)

I’m sorry if it is frowned upon to post code suggestions here like that; I 
didn’t have the confidence to create a pull request right away.

----------
components: Library (Lib)
messages: 401429
nosy: emphoeller
priority: normal
severity: normal
status: open
title: Case of headers in urllib.request.Request
type: behavior

_______________________________________
Python tracker <rep...@bugs.python.org>
<https://bugs.python.org/issue45145>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to