1from collections import MutableMapping 2from webob.compat import ( 3 iteritems_, 4 string_types, 5 ) 6from webob.multidict import MultiDict 7 8__all__ = ['ResponseHeaders', 'EnvironHeaders'] 9 10class ResponseHeaders(MultiDict): 11 """ 12 Dictionary view on the response headerlist. 13 Keys are normalized for case and whitespace. 14 """ 15 def __getitem__(self, key): 16 key = key.lower() 17 for k, v in reversed(self._items): 18 if k.lower() == key: 19 return v 20 raise KeyError(key) 21 22 def getall(self, key): 23 key = key.lower() 24 result = [] 25 for k, v in self._items: 26 if k.lower() == key: 27 result.append(v) 28 return result 29 30 def mixed(self): 31 r = self.dict_of_lists() 32 for key, val in iteritems_(r): 33 if len(val) == 1: 34 r[key] = val[0] 35 return r 36 37 def dict_of_lists(self): 38 r = {} 39 for key, val in iteritems_(self): 40 r.setdefault(key.lower(), []).append(val) 41 return r 42 43 def __setitem__(self, key, value): 44 norm_key = key.lower() 45 items = self._items 46 for i in range(len(items)-1, -1, -1): 47 if items[i][0].lower() == norm_key: 48 del items[i] 49 self._items.append((key, value)) 50 51 def __delitem__(self, key): 52 key = key.lower() 53 items = self._items 54 found = False 55 for i in range(len(items)-1, -1, -1): 56 if items[i][0].lower() == key: 57 del items[i] 58 found = True 59 if not found: 60 raise KeyError(key) 61 62 def __contains__(self, key): 63 key = key.lower() 64 for k, v in self._items: 65 if k.lower() == key: 66 return True 67 return False 68 69 has_key = __contains__ 70 71 def setdefault(self, key, default=None): 72 c_key = key.lower() 73 for k, v in self._items: 74 if k.lower() == c_key: 75 return v 76 self._items.append((key, default)) 77 return default 78 79 def pop(self, key, *args): 80 if len(args) > 1: 81 raise TypeError("pop expected at most 2 arguments, got %s" 82 % repr(1 + len(args))) 83 key = key.lower() 84 for i in range(len(self._items)): 85 if self._items[i][0].lower() == key: 86 v = self._items[i][1] 87 del self._items[i] 88 return v 89 if args: 90 return args[0] 91 else: 92 raise KeyError(key) 93 94 95 96 97 98 99key2header = { 100 'CONTENT_TYPE': 'Content-Type', 101 'CONTENT_LENGTH': 'Content-Length', 102 'HTTP_CONTENT_TYPE': 'Content_Type', 103 'HTTP_CONTENT_LENGTH': 'Content_Length', 104} 105 106header2key = dict([(v.upper(),k) for (k,v) in key2header.items()]) 107 108def _trans_key(key): 109 if not isinstance(key, string_types): 110 return None 111 elif key in key2header: 112 return key2header[key] 113 elif key.startswith('HTTP_'): 114 return key[5:].replace('_', '-').title() 115 else: 116 return None 117 118def _trans_name(name): 119 name = name.upper() 120 if name in header2key: 121 return header2key[name] 122 return 'HTTP_'+name.replace('-', '_') 123 124class EnvironHeaders(MutableMapping): 125 """An object that represents the headers as present in a 126 WSGI environment. 127 128 This object is a wrapper (with no internal state) for a WSGI 129 request object, representing the CGI-style HTTP_* keys as a 130 dictionary. Because a CGI environment can only hold one value for 131 each key, this dictionary is single-valued (unlike outgoing 132 headers). 133 """ 134 135 def __init__(self, environ): 136 self.environ = environ 137 138 def __getitem__(self, hname): 139 return self.environ[_trans_name(hname)] 140 141 def __setitem__(self, hname, value): 142 self.environ[_trans_name(hname)] = value 143 144 def __delitem__(self, hname): 145 del self.environ[_trans_name(hname)] 146 147 def keys(self): 148 return filter(None, map(_trans_key, self.environ)) 149 150 def __contains__(self, hname): 151 return _trans_name(hname) in self.environ 152 153 def __len__(self): 154 return len(list(self.keys())) 155 156 def __iter__(self): 157 for k in self.keys(): 158 yield k 159