1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17  """ 
 18  HTTP utilities 
 19  ============== 
 20   
 21  This module implements various common HTTP utilities. 
 22   
 23  :Variables: 
 24   - `CR`: ASCII CR byte (\\r) 
 25   - `LF`: ASCII LF byte (\\n) 
 26   - `CRLF`: ASCII CRLF sequence (\\r\\n) 
 27   
 28  :Types: 
 29   - `CR`: ``str`` 
 30   - `LF`: ``str`` 
 31   - `CRLF`: ``str`` 
 32  """ 
 33  __author__ = u"Andr\xe9 Malo" 
 34  __docformat__ = "restructuredtext en" 
 35   
 36  import datetime as _datetime 
 37  import re as _re 
 38   
 39  from wtf import Error 
 40   
 41  CR = "\x0D" 
 42  LF = "\x0A" 
 43  CRLF = CR + LF 
 47      """ Base header parse error """ 
  48   
 50      """ A header line is invalid """ 
  51   
 53      """ The headers are incomplete """ 
  54   
 57      """ 
 58      Make a HTTP date 
 59   
 60      :Parameters: 
 61       - `stamp`: The UTC timestamp to process. If omitted or ``None``, the 
 62         current time is taken 
 63   
 64      :Types: 
 65       - `stamp`: ``datetime.datetime`` 
 66   
 67      :return: The HTTP date string 
 68      :rtype: ``str`` 
 69      """ 
 70      self = make_date 
 71      if stamp is None: 
 72          stamp = _datetime.datetime.utcnow() 
 73      return stamp.strftime( 
 74          "%%(wday)s, %d%%(sep)s%%(month)s%%(sep)s%Y %H:%M:%S GMT" 
 75      ) % { 
 76          'wday': self.wdays[stamp.weekday()],  
 77          'month': self.months[stamp.month],  
 78          'sep': [' ', '-'][bool(cookie)], 
 79      } 
  80  make_date.wdays = (  
 81      'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun' 
 82  ) 
 83  make_date.months = (None,  
 84      'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 
 85      'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 
 86  ) 
 90      """ 
 91      Read MIME headers from stream 
 92   
 93      :Parameters: 
 94       - `stream`: The stream to read from 
 95   
 96      :Types: 
 97       - `stream`: ``file`` 
 98   
 99      :return: Dictionary of lists of headers (``{'name': ['val', ...], ...}``) 
100      :rtype: ``dict`` 
101   
102      :Exceptions: 
103       - `httputil.InvalidHeaderLine`: Unparsable header line 
104       - `httputil.IncompleteHeaders`: Stream ended before the final empty line 
105      """ 
106      headers = {} 
107      self, name, values = read_headers, None, None 
108      while True: 
109          line = stream.readline() 
110          if not line: 
111              raise IncompleteHeaders("Headers not completed") 
112          line = line[:-1 - line.endswith(CRLF)] 
113          if self.CONT_MATCH(line):  
114              if name is None: 
115                  raise InvalidHeaderLine( 
116                      "Continuation line without line to continue") 
117              values.append(line.lstrip()) 
118              continue 
119          elif name is not None: 
120              headers.setdefault(name.lower(), [] 
121                  ).append(" ".join(values)) 
122          if not line:  
123              break 
124          match = self.HEADER_MATCH(line)  
125          if not match: 
126              raise InvalidHeaderLine("Invalid header line format") 
127          name, value = match.group('name', 'value') 
128          values = [value] 
129      return headers 
 130   
131  read_headers.CONT_MATCH = _re.compile(r'\s').match  
132   
133   
134   
135  read_headers.HEADER_MATCH = _re.compile(  
136      r'''(?P<name>[-!#$%&'*+.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ''' 
137      r'''^_`abcdefghijklmnopqrstuvwxyz|~]+)\s*:\s*(?P<value>.*)$''', 
138  _re.X).match 
142      """ Interface for Cookie codecs """ 
143   
145          """ 
146          Encode the cookie value to a 7bit US-ASCII string 
147   
148          This method is also responsible for quoting the value if necessary. 
149   
150          :Parameters: 
151           - `value`: The value to encode 
152   
153          :Types: 
154           - `value`: any 
155   
156          :return: The encoded value 
157          :rtype: ``str`` 
158          """ 
 159   
161          """ 
162          Decode the cookie value from 7bit US-ASCII string 
163   
164          :Parameters: 
165           - `value`: The cookie string (as submitted) 
166   
167          :Types: 
168           - `value`: ``str`` 
169   
170          :return: The decoded value 
171          :rtype: any 
172   
173          :Exceptions: 
174           - `ValueError`: The value could not be decoded properly 
175          """ 
  176   
179      """ 
180      Cookie maker helper class 
181   
182      :CVariables: 
183       - `UNSAFE_SEARCH`: Unsafe character search function 
184       - `_ATTR`: Attribute spelling and type getter 
185       - `KEYS`: Valid attribute keys 
186   
187      :IVariables: 
188       - `_encode`: Value encoder 
189   
190      :Types: 
191       - `UNSAFE_SEARCH`: ``callable`` 
192       - `_ATTR`: ``callable`` 
193       - `KEYS`: ``tuple`` 
194       - `_encode`: ``callable`` 
195      """ 
196      UNSAFE_SEARCH = _re.compile(r"[^a-zA-Z\d!#$%&'*+.^_`|~-]").search 
197      _ATTR = dict( 
198          expires=("expires", 'date'), 
199          path=   ("Path",    'ustring'), 
200          comment=("Comment", 'string'), 
201          domain= ("Domain",  'ustring'), 
202          max_age=("Max-Age", 'int'), 
203          secure= ("secure",  'bool'), 
204          version=("Version", 'string'), 
205      ) 
206      KEYS = tuple(sorted(_ATTR.keys())) 
207      _ATTR = _ATTR.get 
208   
210          """ 
211          Initialization 
212   
213          :Parameters: 
214           - `codec`: Cookie codec to apply. If unset or ``None``, an identity 
215             codec is applied (leaving 8bit chars as-is) 
216   
217          :Types: 
218           - `codec`: `CookieCodecInterface` 
219          """ 
220          if codec is None: 
221              encode = lambda x: x 
222          else: 
223              encode = codec.encode 
224          self._encode = encode 
 225   
226 -    def __call__(self, name, value, **kwargs): 
 227          """ 
228          Create the cookie string 
229   
230          Cookie parameters are given in kwargs. Valid keys are listed in 
231          `KEYS`. ``None``-values are ignored. Here are short descriptions of 
232          the valid parameters: 
233   
234          ``comment`` 
235            Cookie comment (``str``) 
236          ``domain`` 
237            Valid domain (``str``) 
238          ``expires`` 
239            Expire time of the cookie (``datetime.datetime``). If unset 
240            or ``None`` the cookie is dropped when the browser is closed. 
241            See also the ``max_age`` keyword. 
242          ``max_age`` 
243            Max age of the cookie in seconds (``int``). If set, make sure it 
244            matches the expiry time. The difference is that expires will be 
245            transformed to a HTTP date, while max-age will stay an integer. 
246            The expires parameter is the older one and better understood by 
247            the clients out there. For that reason if you set max_age only, 
248            expires will be set automatically to ``now + max_age``. If unset 
249            or ``None`` the cookie will be dropped when the browser is closed. 
250          ``path`` 
251            Valid URL base path for the cookie. It should always be set to a 
252            reasonable path (at least ``/``), otherwise the cookie will only 
253            be valid for the current URL and below. 
254          ``secure`` 
255            Whether this is an SSL-only cookie or not (``bool``) 
256          ``version`` 
257            Cookie spec version (``int``). See `RFC 2965`_ 
258           
259          .. _RFC 2965: http://www.ietf.org/rfc/rfc2965.txt 
260   
261          :Parameters: 
262           - `name`: Cookie name 
263           - `value`: Cookie value (if a codec was given, the type should be 
264             applicable for the codec encoder). 
265           - `kwargs`: Cookie parameters 
266   
267          :Types: 
268           - `name`: ``str`` 
269           - `value`: ``str`` 
270           - `kwargs`: ``dict`` 
271   
272          :return: The cookie string 
273          :rtype: ``str`` 
274   
275          :Exceptions: 
276           - `ValueError`: Invalid name or values given 
277           - `TypeError`: Unrecognized attributes given 
278          """ 
279          if self.UNSAFE_SEARCH(name): 
280              raise ValueError("%r is unsafe as key" % (name,)) 
281          elif name.lower().replace('-', '_') in self.KEYS: 
282              raise ValueError("%s is a reserved attribute and cannot be used " 
283                  "as name" % (name,)) 
284          items = ["%s=%s" % (str(name), str(self._encode(value)))] 
285          if kwargs.get('max_age') is not None: 
286              kwargs['max_age'] = max(0, kwargs['max_age']) 
287              if kwargs.get('expires') is None: 
288                  kwargs['expires'] = ( 
289                      _datetime.datetime.utcnow() + 
290                      _datetime.timedelta(seconds=kwargs['max_age']) 
291                  ) 
292   
293          for key in self.KEYS: 
294              if key in kwargs: 
295                  val = kwargs.pop(key) 
296                  if val is not None: 
297                      key, translator = self._ATTR(key) 
298                      value = getattr(self, '_' + translator)(key, val) 
299                      if value is not None: 
300                          items.append(str(value)) 
301          if kwargs: 
302              raise TypeError("Unrecognized keywords: %r" % (kwargs.keys(),)) 
303          return "; ".join(item for item in items if item is not None) 
 304   
305      @staticmethod 
309   
310      @staticmethod 
311 -    def _int(key, value): 
 312          """ Integer translator """ 
313          return "%s=%d" % (key, int(value)) 
 314   
315      @staticmethod 
317          """ Boolean translator """ 
318          if value: 
319              return key 
320          return None 
 321   
323          """ String translator """ 
324          return "%s=%s" % (key, self._encode(value)) 
 325   
326      @staticmethod 
328          """ Unquoted string translator """ 
329          return "%s=%s" % (key, value) 
  330   
331   
332 -def make_cookie(name, value, codec=None, **kwargs): 
 333      """ 
334      Make a cookie 
335   
336      The is a simple interface to the `CookieMaker` class. See there for 
337      detailed information. 
338   
339      :Parameters: 
340       - `name`: Cookie name 
341       - `value`: Cookie value 
342       - `codec`: Value codec. If unset or ``None``, the identity codec is 
343         applied. 
344       - `kwargs`: Cookie attributes 
345   
346      :Types: 
347       - `name`: ``str`` 
348       - `value`: ``str`` 
349       - `codec`: `CookieCodecInterface` 
350       - `kwargs`: ``dict`` 
351   
352      :return: The cookie string 
353      :rtype: ``str`` 
354   
355      :Exceptions: 
356       - `ValueError`: Invalid name or values given 
357       - `TypeError`: Unrecognized attributes given 
358      """ 
359      return CookieMaker(codec)(name, value, **kwargs) 
 360