0001"""
0002Represents the Cache-Control header
0003"""
0004
0005import re
0006from webob.updatedict import UpdateDict
0007try:
0008    sorted
0009except NameError:
0010    from webob.compat import sorted
0011
0012token_re = re.compile(
0013    r'([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?')
0014need_quote_re = re.compile(r'[^a-zA-Z0-9._-]')
0015
0016class exists_property(object):
0017    """
0018    Represents a property that either is listed in the Cache-Control
0019    header, or is not listed (has no value)
0020    """
0021    def __init__(self, prop, type=None):
0022        self.prop = prop
0023        self.type = type
0024
0025    def __get__(self, obj, type=None):
0026        if obj is None:
0027            return self
0028        return self.prop in obj.properties
0029    def __set__(self, obj, value):
0030        if (self.type is not None
0031            and self.type != obj.type):
0032            raise AttributeError(
0033                "The property %s only applies to %s Cache-Control" % (self.prop, self.type))
0034        if value:
0035            obj.properties[self.prop] = None
0036        else:
0037            if self.prop in obj.properties:
0038                del obj.properties[self.prop]
0039    def __delete__(self, obj):
0040        self.__set__(obj, False)
0041
0042class value_property(object):
0043    """
0044    Represents a property that has a value in the Cache-Control header.
0045
0046    When no value is actually given, the value of self.none is returned.
0047    """
0048    def __init__(self, prop, default=None, none=None, type=None):
0049        self.prop = prop
0050        self.default = default
0051        self.none = none
0052        self.type = type
0053    def __get__(self, obj, type=None):
0054        if obj is None:
0055            return self
0056        if self.prop in obj.properties:
0057            value = obj.properties[self.prop]
0058            if value is None:
0059                return self.none
0060            else:
0061                return value
0062        else:
0063            return self.default
0064    def __set__(self, obj, value):
0065        if (self.type is not None
0066            and self.type != obj.type):
0067            raise AttributeError(
0068                "The property %s only applies to %s Cache-Control" % (self.prop, self.type))
0069        if value == self.default:
0070            if self.prop in obj.properties:
0071                del obj.properties[self.prop]
0072        elif value is True:
0073            obj.properties[self.prop] = None # Empty value, but present
0074        else:
0075            obj.properties[self.prop] = value
0076    def __delete__(self, obj):
0077        if self.prop in obj.properties:
0078            del obj.properties[self.prop]
0079
0080class CacheControl(object):
0081
0082    """
0083    Represents the Cache-Control header.
0084
0085    By giving a type of ``'request'`` or ``'response'`` you can
0086    control what attributes are allowed (some Cache-Control values
0087    only apply to requests or responses).
0088    """
0089
0090    def __init__(self, properties, type):
0091        self.properties = properties
0092        self.type = type
0093
0094    #@classmethod
0095    def parse(cls, header, updates_to=None, type=None):
0096        """
0097        Parse the header, returning a CacheControl object.
0098
0099        The object is bound to the request or response object
0100        ``updates_to``, if that is given.
0101        """
0102        if updates_to:
0103            props = UpdateDict()
0104            props.updated = updates_to
0105        else:
0106            props = {}
0107        for match in token_re.finditer(header):
0108            name = match.group(1)
0109            value = match.group(2) or match.group(3) or None
0110            if value:
0111                try:
0112                    value = int(value)
0113                except ValueError:
0114                    pass
0115            props[name] = value
0116        obj = cls(props, type=type)
0117        if updates_to:
0118            props.updated_args = (obj,)
0119        return obj
0120
0121    parse = classmethod(parse)
0122
0123    def __repr__(self):
0124        return '<CacheControl %r>' % str(self)
0125
0126    # Request values:
0127    # no-cache shared (below)
0128    # no-store shared (below)
0129    # max-age shared  (below)
0130    max_stale = value_property('max-stale', none='*', type='request')
0131    min_fresh = value_property('min-fresh', type='request')
0132    # no-transform shared (below)
0133    only_if_cached = exists_property('only-if-cached', type='request')
0134
0135    # Response values:
0136    public = exists_property('public', type='response')
0137    private = value_property('private', none='*', type='response')
0138    no_cache = value_property('no-cache', none='*')
0139    no_store = exists_property('no-store')
0140    no_transform = exists_property('no-transform')
0141    must_revalidate = exists_property('must-revalidate', type='response')
0142    proxy_revalidate = exists_property('proxy-revalidate', type='response')
0143    max_age = value_property('max-age', none=-1)
0144    s_maxage = value_property('s-maxage', type='response')
0145    s_max_age = s_maxage
0146
0147    def __str__(self):
0148        return serialize_cache_control(self.properties)
0149
0150    def copy(self):
0151        """
0152        Returns a copy of this object.
0153        """
0154        return self.__class__(self.properties.copy(), type=self.type)
0155
0156def serialize_cache_control(properties):
0157    if isinstance(properties, CacheControl):
0158        properties = properties.properties
0159    parts = []
0160    for name, value in sorted(properties.items()):
0161        if value is None:
0162            parts.append(name)
0163            continue
0164        value = str(value)
0165        if need_quote_re.search(value):
0166            value = '"%s"' % value
0167        parts.append('%s=%s' % (name, value))
0168    return ', '.join(parts)