Home | History | Annotate | Download | only in webob
      1 """
      2 Represents the Cache-Control header
      3 """
      4 import re
      5 
      6 class UpdateDict(dict):
      7     """
      8     Dict that has a callback on all updates
      9     """
     10     # these are declared as class attributes so that
     11     # we don't need to override constructor just to
     12     # set some defaults
     13     updated = None
     14     updated_args = None
     15 
     16     def _updated(self):
     17         """
     18         Assign to new_dict.updated to track updates
     19         """
     20         updated = self.updated
     21         if updated is not None:
     22             args = self.updated_args
     23             if args is None:
     24                 args = (self,)
     25             updated(*args)
     26 
     27     def __setitem__(self, key, item):
     28         dict.__setitem__(self, key, item)
     29         self._updated()
     30 
     31     def __delitem__(self, key):
     32         dict.__delitem__(self, key)
     33         self._updated()
     34 
     35     def clear(self):
     36         dict.clear(self)
     37         self._updated()
     38 
     39     def update(self, *args, **kw):
     40         dict.update(self, *args, **kw)
     41         self._updated()
     42 
     43     def setdefault(self, key, value=None):
     44         val = dict.setdefault(self, key, value)
     45         if val is value:
     46             self._updated()
     47         return val
     48 
     49     def pop(self, *args):
     50         v = dict.pop(self, *args)
     51         self._updated()
     52         return v
     53 
     54     def popitem(self):
     55         v = dict.popitem(self)
     56         self._updated()
     57         return v
     58 
     59 
     60 token_re = re.compile(
     61     r'([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?')
     62 need_quote_re = re.compile(r'[^a-zA-Z0-9._-]')
     63 
     64 
     65 class exists_property(object):
     66     """
     67     Represents a property that either is listed in the Cache-Control
     68     header, or is not listed (has no value)
     69     """
     70     def __init__(self, prop, type=None):
     71         self.prop = prop
     72         self.type = type
     73 
     74     def __get__(self, obj, type=None):
     75         if obj is None:
     76             return self
     77         return self.prop in obj.properties
     78 
     79     def __set__(self, obj, value):
     80         if (self.type is not None
     81             and self.type != obj.type):
     82             raise AttributeError(
     83                 "The property %s only applies to %s Cache-Control" % (
     84                     self.prop, self.type))
     85 
     86         if value:
     87             obj.properties[self.prop] = None
     88         else:
     89             if self.prop in obj.properties:
     90                 del obj.properties[self.prop]
     91 
     92     def __delete__(self, obj):
     93         self.__set__(obj, False)
     94 
     95 
     96 class value_property(object):
     97     """
     98     Represents a property that has a value in the Cache-Control header.
     99 
    100     When no value is actually given, the value of self.none is returned.
    101     """
    102     def __init__(self, prop, default=None, none=None, type=None):
    103         self.prop = prop
    104         self.default = default
    105         self.none = none
    106         self.type = type
    107 
    108     def __get__(self, obj, type=None):
    109         if obj is None:
    110             return self
    111         if self.prop in obj.properties:
    112             value = obj.properties[self.prop]
    113             if value is None:
    114                 return self.none
    115             else:
    116                 return value
    117         else:
    118             return self.default
    119 
    120     def __set__(self, obj, value):
    121         if (self.type is not None
    122             and self.type != obj.type):
    123             raise AttributeError(
    124                 "The property %s only applies to %s Cache-Control" % (
    125                     self.prop, self.type))
    126         if value == self.default:
    127             if self.prop in obj.properties:
    128                 del obj.properties[self.prop]
    129         elif value is True:
    130             obj.properties[self.prop] = None # Empty value, but present
    131         else:
    132             obj.properties[self.prop] = value
    133 
    134     def __delete__(self, obj):
    135         if self.prop in obj.properties:
    136             del obj.properties[self.prop]
    137 
    138 
    139 class CacheControl(object):
    140 
    141     """
    142     Represents the Cache-Control header.
    143 
    144     By giving a type of ``'request'`` or ``'response'`` you can
    145     control what attributes are allowed (some Cache-Control values
    146     only apply to requests or responses).
    147     """
    148 
    149     update_dict = UpdateDict
    150 
    151     def __init__(self, properties, type):
    152         self.properties = properties
    153         self.type = type
    154 
    155     @classmethod
    156     def parse(cls, header, updates_to=None, type=None):
    157         """
    158         Parse the header, returning a CacheControl object.
    159 
    160         The object is bound to the request or response object
    161         ``updates_to``, if that is given.
    162         """
    163         if updates_to:
    164             props = cls.update_dict()
    165             props.updated = updates_to
    166         else:
    167             props = {}
    168         for match in token_re.finditer(header):
    169             name = match.group(1)
    170             value = match.group(2) or match.group(3) or None
    171             if value:
    172                 try:
    173                     value = int(value)
    174                 except ValueError:
    175                     pass
    176             props[name] = value
    177         obj = cls(props, type=type)
    178         if updates_to:
    179             props.updated_args = (obj,)
    180         return obj
    181 
    182     def __repr__(self):
    183         return '<CacheControl %r>' % str(self)
    184 
    185     # Request values:
    186     # no-cache shared (below)
    187     # no-store shared (below)
    188     # max-age shared  (below)
    189     max_stale = value_property('max-stale', none='*', type='request')
    190     min_fresh = value_property('min-fresh', type='request')
    191     # no-transform shared (below)
    192     only_if_cached = exists_property('only-if-cached', type='request')
    193 
    194     # Response values:
    195     public = exists_property('public', type='response')
    196     private = value_property('private', none='*', type='response')
    197     no_cache = value_property('no-cache', none='*')
    198     no_store = exists_property('no-store')
    199     no_transform = exists_property('no-transform')
    200     must_revalidate = exists_property('must-revalidate', type='response')
    201     proxy_revalidate = exists_property('proxy-revalidate', type='response')
    202     max_age = value_property('max-age', none=-1)
    203     s_maxage = value_property('s-maxage', type='response')
    204     s_max_age = s_maxage
    205     stale_while_revalidate = value_property(
    206         'stale-while-revalidate', type='response')
    207     stale_if_error = value_property('stale-if-error', type='response')
    208 
    209     def __str__(self):
    210         return serialize_cache_control(self.properties)
    211 
    212     def copy(self):
    213         """
    214         Returns a copy of this object.
    215         """
    216         return self.__class__(self.properties.copy(), type=self.type)
    217 
    218 
    219 def serialize_cache_control(properties):
    220     if isinstance(properties, CacheControl):
    221         properties = properties.properties
    222     parts = []
    223     for name, value in sorted(properties.items()):
    224         if value is None:
    225             parts.append(name)
    226             continue
    227         value = str(value)
    228         if need_quote_re.search(value):
    229             value = '"%s"' % value
    230         parts.append('%s=%s' % (name, value))
    231     return ', '.join(parts)
    232